Refactor: Implement block scoping and statement separation
This commit introduces fundamental changes to Myc's language structure to enhance type safety and scoping clarity. Key changes include: - Introducing local scopes for `do` blocks. - Separating statements (`def`, `assign`) from expressions, where statements now have no return value and are only allowed within blocks. - A block now consists of multiple statements followed by a single final expression. - Stricter validation rules are enforced, such as disallowing redefinition of local symbols in the same scope while allowing shadowing of outer symbols. - Compiler errors are generated for statements used in expression contexts. The implementation involved modifications to the AST, parser, binder (scope-stack), and various compiler passes, along with VM runtime optimizations.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use crate::ast::compiler::bound_nodes::{
|
||||
Address, AnalyzedNode, BoundKind, GlobalAnalyzedRegistry, GlobalIdx, UpvalueIdx,
|
||||
Address, AnalyzedNode, BoundKind, GlobalAnalyzedRegistry, GlobalIdx, UpvalueIdx, UpvalueSource,
|
||||
};
|
||||
use crate::ast::nodes::Node;
|
||||
use crate::ast::types::{Purity, Value};
|
||||
@@ -65,8 +65,6 @@ impl Optimizer {
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
// Unwrap the final Rc if we are at the end, or return a clone of the inner node.
|
||||
// Since AnalyzedNode is small now (header + Rcs), cloning is cheap.
|
||||
(*current).clone()
|
||||
}
|
||||
|
||||
@@ -85,7 +83,6 @@ impl Optimizer {
|
||||
if inliner.prepare_beta_reduction(params, arg_nodes, body, &mut inner_sub).is_some() {
|
||||
let res = self.visit_node(Rc::new(body.clone()), &mut inner_sub, path);
|
||||
|
||||
// Sync back state to parent substitution map
|
||||
sub.next_slot = inner_sub.next_slot;
|
||||
sub.used.extend(inner_sub.used.iter().cloned());
|
||||
sub.assigned.extend(inner_sub.assigned.iter().cloned());
|
||||
@@ -111,19 +108,16 @@ impl Optimizer {
|
||||
BoundKind::Get { addr, name } => {
|
||||
let addr = *addr;
|
||||
if !sub.assigned.contains(&addr) {
|
||||
// 1. Try inlining from current value substitution map (locals/globals/upvalues)
|
||||
if let Some(val) = sub.get_value(&addr)
|
||||
&& inliner.is_inlinable_value(val, addr)
|
||||
{
|
||||
return Rc::new(folder.make_constant_node(val.clone(), node));
|
||||
}
|
||||
|
||||
// 2. Try inlining from AST substitution map (pure expressions)
|
||||
if let Some(inlined_node) = sub.ast_substitutions.get(&addr) {
|
||||
return inlined_node.clone();
|
||||
}
|
||||
|
||||
// 3. Fallback for Globals: check the actual VM environment
|
||||
if let Address::Global(idx) = addr
|
||||
&& let Some(globals_rc) = &self.globals
|
||||
{
|
||||
@@ -151,7 +145,6 @@ impl Optimizer {
|
||||
BoundKind::GetField { rec, field } => {
|
||||
let rec_opt = self.visit_node(rec.clone(), sub, path);
|
||||
|
||||
// Constant folding for Field Access
|
||||
if let BoundKind::Constant(Value::Record(layout, values)) = &rec_opt.kind
|
||||
&& let Some(idx) = layout.index_of(*field)
|
||||
{
|
||||
@@ -197,12 +190,13 @@ impl Optimizer {
|
||||
addr,
|
||||
kind,
|
||||
value,
|
||||
identity,
|
||||
captured_by,
|
||||
} => {
|
||||
let value_opt = self.visit_node(value.clone(), sub, path);
|
||||
|
||||
if let Address::Local(slot) = addr
|
||||
&& !captured_by.is_empty()
|
||||
&& !captured_by.borrow().is_empty()
|
||||
{
|
||||
sub.captured_slots.insert(*slot);
|
||||
}
|
||||
@@ -232,6 +226,7 @@ impl Optimizer {
|
||||
addr: sub.map_address(*addr),
|
||||
kind: *kind,
|
||||
value: value_opt,
|
||||
identity: identity.clone(),
|
||||
captured_by: captured_by.clone(),
|
||||
},
|
||||
node.ty.clone(),
|
||||
@@ -243,7 +238,6 @@ impl Optimizer {
|
||||
let args_opt = self.visit_node(args.clone(), sub, path);
|
||||
|
||||
if self.enabled {
|
||||
// Optimized Field Access Transformation
|
||||
if let BoundKind::FieldAccessor(k) = &callee_opt.kind
|
||||
&& let BoundKind::Tuple { elements } = &args_opt.kind
|
||||
&& elements.len() == 1
|
||||
@@ -267,10 +261,9 @@ impl Optimizer {
|
||||
params,
|
||||
body,
|
||||
upvalues,
|
||||
positional_count,
|
||||
..
|
||||
} = &callee_opt.kind
|
||||
&& upvalues.is_empty()
|
||||
&& positional_count.is_some()
|
||||
&& path.inlining_depth < 5
|
||||
&& !callee_opt.ty.is_recursive
|
||||
&& path.enter_lambda(&callee_opt.identity)
|
||||
@@ -299,10 +292,9 @@ impl Optimizer {
|
||||
params,
|
||||
body,
|
||||
upvalues,
|
||||
positional_count,
|
||||
..
|
||||
} = &lambda_node.kind
|
||||
&& upvalues.is_empty()
|
||||
&& positional_count.is_some()
|
||||
&& !lambda_node.ty.is_recursive
|
||||
{
|
||||
path.inlining_stack.insert(*idx);
|
||||
@@ -453,31 +445,29 @@ impl Optimizer {
|
||||
node.ty.clone(),
|
||||
)
|
||||
}
|
||||
BoundKind::Block { exprs } => {
|
||||
BoundKind::Block { statements, result } => {
|
||||
let mut info = UsageInfo::default();
|
||||
if !exprs.is_empty() {
|
||||
for e in exprs {
|
||||
info.collect(e);
|
||||
}
|
||||
for s in statements {
|
||||
info.collect(s);
|
||||
}
|
||||
info.collect(result);
|
||||
|
||||
sub.assigned.extend(info.assigned.iter().cloned());
|
||||
|
||||
let mut new_exprs = Vec::with_capacity(exprs.len());
|
||||
let last_idx = exprs.len().saturating_sub(1);
|
||||
let mut new_statements = Vec::with_capacity(statements.len());
|
||||
let mut block_changed = false;
|
||||
|
||||
for (i, e) in exprs.iter().enumerate() {
|
||||
let is_last = i == last_idx;
|
||||
if self.enabled && !is_last {
|
||||
let removable = match &e.kind {
|
||||
BoundKind::Define { addr, value, .. } => {
|
||||
for s in statements {
|
||||
if self.enabled {
|
||||
let removable = match &s.kind {
|
||||
BoundKind::Define { addr, value, captured_by, .. } => {
|
||||
!info.is_used(addr)
|
||||
&& (if let Address::Local(slot) = addr {
|
||||
!sub.captured_slots.contains(slot)
|
||||
} else {
|
||||
true
|
||||
})
|
||||
&& captured_by.borrow().is_empty()
|
||||
&& (value.ty.purity >= Purity::SideEffectFree
|
||||
|| matches!(value.kind, BoundKind::Lambda { .. }))
|
||||
}
|
||||
@@ -490,7 +480,7 @@ impl Optimizer {
|
||||
})
|
||||
&& value.ty.purity >= Purity::SideEffectFree
|
||||
}
|
||||
_ => e.ty.purity >= Purity::SideEffectFree,
|
||||
_ => s.ty.purity >= Purity::SideEffectFree,
|
||||
};
|
||||
|
||||
if removable {
|
||||
@@ -499,15 +489,20 @@ impl Optimizer {
|
||||
}
|
||||
}
|
||||
|
||||
let opt = self.visit_node(e.clone(), sub, path);
|
||||
if self.enabled && matches!(opt.kind, BoundKind::Nop) && !is_last {
|
||||
let opt = self.visit_node(s.clone(), sub, path);
|
||||
if self.enabled && matches!(opt.kind, BoundKind::Nop) {
|
||||
block_changed = true;
|
||||
continue;
|
||||
}
|
||||
if !Rc::ptr_eq(&opt, e) {
|
||||
if !Rc::ptr_eq(&opt, s) {
|
||||
block_changed = true;
|
||||
}
|
||||
new_exprs.push(opt);
|
||||
new_statements.push(opt);
|
||||
}
|
||||
|
||||
let new_result = self.visit_node(result.clone(), sub, path);
|
||||
if !Rc::ptr_eq(&new_result, result) {
|
||||
block_changed = true;
|
||||
}
|
||||
|
||||
if !block_changed {
|
||||
@@ -515,14 +510,12 @@ impl Optimizer {
|
||||
}
|
||||
|
||||
if self.enabled {
|
||||
if new_exprs.is_empty() {
|
||||
return Rc::new(folder.make_nop_node(node));
|
||||
} else if new_exprs.len() == 1 {
|
||||
return new_exprs.pop().unwrap();
|
||||
if new_statements.is_empty() {
|
||||
return new_result;
|
||||
}
|
||||
}
|
||||
|
||||
(BoundKind::Block { exprs: new_exprs }, node.ty.clone())
|
||||
(BoundKind::Block { statements: new_statements, result: new_result }, node.ty.clone())
|
||||
}
|
||||
|
||||
BoundKind::Lambda {
|
||||
@@ -540,16 +533,21 @@ impl Optimizer {
|
||||
next_inner_subs.assigned = info.assigned;
|
||||
let mut upvalues_changed = false;
|
||||
|
||||
for (old_idx, capture_addr) in upvalues.iter().enumerate() {
|
||||
for (old_idx, source) in upvalues.iter().enumerate() {
|
||||
let capture_addr = match source {
|
||||
UpvalueSource::Local(s) => Address::Local(*s),
|
||||
UpvalueSource::Upvalue(i) => Address::Upvalue(*i),
|
||||
};
|
||||
|
||||
let mut inlined_val = None;
|
||||
if !sub.assigned.contains(capture_addr)
|
||||
&& let Some(val) = sub.get_value(capture_addr)
|
||||
if !sub.assigned.contains(&capture_addr)
|
||||
&& let Some(val) = sub.get_value(&capture_addr)
|
||||
{
|
||||
inlined_val = Some(val.clone());
|
||||
}
|
||||
|
||||
if let Address::Local(slot) = capture_addr {
|
||||
sub.captured_slots.insert(*slot);
|
||||
sub.captured_slots.insert(slot);
|
||||
}
|
||||
|
||||
if let Some(val) = inlined_val {
|
||||
@@ -559,11 +557,16 @@ impl Optimizer {
|
||||
upvalues_changed = true;
|
||||
} else {
|
||||
mapping.push(Some(new_upvalues.len() as u32));
|
||||
let mapped = sub.map_address(*capture_addr);
|
||||
if mapped != *capture_addr {
|
||||
let mapped_addr = sub.map_address(capture_addr);
|
||||
let mapped_source = match mapped_addr {
|
||||
Address::Local(s) => UpvalueSource::Local(s),
|
||||
Address::Upvalue(i) => UpvalueSource::Upvalue(i),
|
||||
Address::Global(_) => panic!("Cannot map upvalue to global")
|
||||
};
|
||||
if mapped_source != *source {
|
||||
upvalues_changed = true;
|
||||
}
|
||||
new_upvalues.push(mapped);
|
||||
new_upvalues.push(mapped_source);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user