Refactor: Improve optimizer and DCE

This commit introduces several improvements to the AST optimizer and
dead code elimination (DCE) logic:

- **Address Mapping:** The `Get` and `Set` operations now correctly map
  local slots and global indices using the substitution map, ensuring
  that remapped variables are handled properly.
- **Parameter Slot Mapping:** Parameters are now explicitly mapped to
  their correct slots within the `Lambda` node, preventing potential
  slot reassignments in the function body.
- **Pure Function Analysis:** The `is_pure` function has been refined to
  accurately identify pure expressions, including calls to pure
  functions and stateless closures.
- **Dead Code Elimination:** DCE logic in `BoundKind::Block` has been
  enhanced to remove unused local definitions and pure, non-essential
  expressions more effectively. Global DCE is also improved.
- **Constant Folding:** `try_fold_pure` is now more robust in folding
  pure function calls with constant arguments, reducing redundant
  computations.
- **Beta Reduction:** `try_beta_reduce` has been updated to avoid
  reducing if the body contains `DefLocal`, ensuring correctness.
- **Global Purity Tracking:** A `global_purity` map is introduced to
  track the purity of global values, enabling better optimization of
  global accesses.
- **Inlinable Value Check:** The `is_inlinable_value` check now
  correctly identifies stateless closures for inlining.
- **Optimization Level:** The default `optimization_level` is set to 2
  to enable more aggressive optimizations.
This commit is contained in:
Michael Schimmel
2026-02-22 00:41:02 +01:00
parent 0107264026
commit e87f7232e6
2 changed files with 175 additions and 155 deletions
+174 -154
View File
@@ -50,7 +50,7 @@ impl Optimizer {
fn visit_node(&self, node: TypedNode, sub: &mut SubstitutionMap) -> TypedNode { fn visit_node(&self, node: TypedNode, sub: &mut SubstitutionMap) -> TypedNode {
let (new_kind, new_ty) = match node.kind { let (new_kind, new_ty) = match node.kind {
BoundKind::Get { addr, name } => { BoundKind::Get { addr, name } => {
match addr { let new_addr = match addr {
Address::Local(slot) => { Address::Local(slot) => {
if let Some(val) = sub.locals.get(&slot) { if let Some(val) = sub.locals.get(&slot) {
return Node { return Node {
@@ -60,6 +60,7 @@ impl Optimizer {
}; };
} }
sub.used_slots.insert(slot); sub.used_slots.insert(slot);
Address::Local(sub.map_slot(slot))
} }
Address::Upvalue(idx) => { Address::Upvalue(idx) => {
if let Some(val) = sub.upvalues.get(&idx) { if let Some(val) = sub.upvalues.get(&idx) {
@@ -69,10 +70,9 @@ impl Optimizer {
kind: BoundKind::Constant(val.clone()), kind: BoundKind::Constant(val.clone()),
}; };
} }
Address::Upvalue(idx)
} }
Address::Global(idx) => { Address::Global(idx) => {
// Phase 4: Global Inlining
// 1. Check script-local global substitution map
if let Some(val) = sub.globals.get(&idx) { if let Some(val) = sub.globals.get(&idx) {
return Node { return Node {
identity: node.identity, identity: node.identity,
@@ -80,11 +80,9 @@ impl Optimizer {
kind: BoundKind::Constant(val.clone()), kind: BoundKind::Constant(val.clone()),
}; };
} }
// 2. Check environment global values
if let Some(globals_rc) = &self.globals { if let Some(globals_rc) = &self.globals {
let globals = globals_rc.borrow(); let globals = globals_rc.borrow();
if let Some(val) = globals.get(idx as usize) { if let Some(val) = globals.get(idx as usize) {
// Only inline "stable" values: scalars and stateless functions
if self.is_inlinable_value(val) { if self.is_inlinable_value(val) {
return Node { return Node {
identity: node.identity, identity: node.identity,
@@ -95,11 +93,41 @@ impl Optimizer {
} }
} }
sub.used_globals.insert(idx); sub.used_globals.insert(idx);
Address::Global(idx)
} }
} };
(BoundKind::Get { addr, name }, node.ty) (BoundKind::Get { addr: new_addr, name }, node.ty)
} }
BoundKind::Parameter { name, slot } => {
let new_slot = sub.map_slot(slot);
(BoundKind::Parameter { name, slot: new_slot }, node.ty)
}
BoundKind::Set { addr, value } => {
let value = Box::new(self.visit_node(*value, sub));
let new_addr = match addr {
Address::Local(slot) => {
if let BoundKind::Constant(val) = &value.kind {
sub.add_local(slot, val.clone());
} else {
sub.locals.remove(&slot);
}
Address::Local(sub.map_slot(slot))
}
Address::Global(idx) => {
if let BoundKind::Constant(val) = &value.kind {
sub.add_global(idx, val.clone());
} else {
sub.globals.remove(&idx);
}
Address::Global(idx)
}
_ => addr
};
(BoundKind::Set { addr: new_addr, value }, node.ty)
},
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
let callee = self.visit_node(*callee, sub); let callee = self.visit_node(*callee, sub);
let args = self.visit_node(*args, sub); let args = self.visit_node(*args, sub);
@@ -178,44 +206,48 @@ impl Optimizer {
}, },
BoundKind::Block { exprs } => { BoundKind::Block { exprs } => {
let mut used_in_block = HashSet::new();
if !exprs.is_empty() {
for e in &exprs {
self.collect_local_usage(e, &mut used_in_block);
}
if let Some(last) = exprs.last() {
if let BoundKind::Get { addr: Address::Local(slot), .. } = &last.kind {
used_in_block.insert(*slot);
}
}
}
let mut new_exprs = Vec::with_capacity(exprs.len()); let mut new_exprs = Vec::with_capacity(exprs.len());
for e in exprs { let last_idx = exprs.len().saturating_sub(1);
for (i, e) in exprs.into_iter().enumerate() {
let is_last = i == last_idx;
if self.level >= 2 && !is_last {
let removable = match &e.kind {
BoundKind::DefLocal { slot, value, .. } => {
!used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value)
}
BoundKind::Set { addr: Address::Local(slot), value, .. } => {
!used_in_block.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(value)
}
_ => self.is_pure(&e),
};
if removable {
continue;
}
}
let opt = self.visit_node(e, sub); let opt = self.visit_node(e, sub);
if self.level >= 2 && matches!(opt.kind, BoundKind::Nop) { if self.level >= 2 && matches!(opt.kind, BoundKind::Nop) && !is_last {
continue; continue;
} }
new_exprs.push(opt); new_exprs.push(opt);
} }
if self.level >= 2 { if self.level >= 2 {
if !new_exprs.is_empty() {
let last_idx = new_exprs.len() - 1;
let mut filtered = Vec::with_capacity(new_exprs.len());
for (i, e) in new_exprs.into_iter().enumerate() {
if i == last_idx {
filtered.push(e);
continue;
}
let removable = match &e.kind {
BoundKind::DefLocal { slot, .. } | BoundKind::Set { addr: Address::Local(slot), .. } => {
!sub.used_slots.contains(slot) && !sub.captured_slots.contains(slot) && self.is_pure(&e)
}
BoundKind::DefGlobal { global_index, .. } => {
// Phase 4: DCE for Globals
// A global can be removed if it was only used for inlining in this script.
!sub.used_globals.contains(global_index) && self.is_pure(&e)
}
_ => self.is_pure(&e),
};
if !removable {
filtered.push(e);
}
}
new_exprs = filtered;
}
if new_exprs.is_empty() { if new_exprs.is_empty() {
return Node { identity: node.identity, kind: BoundKind::Nop, ty: StaticType::Void }; return Node { identity: node.identity, kind: BoundKind::Nop, ty: StaticType::Void };
} else if new_exprs.len() == 1 { } else if new_exprs.len() == 1 {
@@ -250,13 +282,23 @@ impl Optimizer {
mapping.push(None); mapping.push(None);
} else { } else {
mapping.push(Some(new_upvalues.len() as u32)); mapping.push(Some(new_upvalues.len() as u32));
new_upvalues.push(*capture_addr); let new_addr = if let Address::Local(parent_slot) = capture_addr {
Address::Local(sub.map_slot(*parent_slot))
} else {
*capture_addr
};
new_upvalues.push(new_addr);
} }
} }
let params = Rc::new(self.visit_node(params.as_ref().clone(), &mut SubstitutionMap::new())); // 1. Visit parameters to determine their new slots
let body_node = self.visit_node((*body).clone(), &mut next_inner_subs); let params_node = self.visit_node(params.as_ref().clone(), &mut SubstitutionMap::new());
// 2. IMPORTANT: Pre-register parameter slots in the inner substitution map
// so they are mapped 1:1 and don't get reassigned to higher slots in the body.
self.collect_parameter_slots(&params_node, &mut next_inner_subs);
let body_node = self.visit_node((*body).clone(), &mut next_inner_subs);
let reindexed_body = if new_upvalues.len() != original_upvalues.len() { let reindexed_body = if new_upvalues.len() != original_upvalues.len() {
sub.reindex_upvalues(body_node, &mapping) sub.reindex_upvalues(body_node, &mapping)
} else { } else {
@@ -264,7 +306,7 @@ impl Optimizer {
}; };
(BoundKind::Lambda { (BoundKind::Lambda {
params, params: Rc::new(params_node),
upvalues: new_upvalues, upvalues: new_upvalues,
body: Rc::new(reindexed_body), body: Rc::new(reindexed_body),
positional_count positional_count
@@ -281,45 +323,30 @@ impl Optimizer {
} else { } else {
sub.locals.remove(&slot); sub.locals.remove(&slot);
} }
(BoundKind::DefLocal { name, slot, value, captured_by }, node.ty)
if self.is_pure(&value) {
sub.pure_slots.insert(slot);
} else {
sub.pure_slots.remove(&slot);
}
let new_slot = sub.map_slot(slot);
(BoundKind::DefLocal { name, slot: new_slot, value, captured_by }, node.ty)
}, },
BoundKind::DefGlobal { name, global_index, value } => { BoundKind::DefGlobal { name, global_index, value } => {
let value = Box::new(self.visit_node(*value, sub)); let value = Box::new(self.visit_node(*value, sub));
// Phase 4: Track script-local global definitions for inlining
if let BoundKind::Constant(val) = &value.kind { if let BoundKind::Constant(val) = &value.kind {
sub.add_global(global_index, val.clone()); sub.add_global(global_index, val.clone());
} }
// Purity Inference: Globals are pure if their value is pure
if self.is_pure(&value) { if self.is_pure(&value) {
if let Some(purity_rc) = &self.global_purity { if let Some(purity_rc) = &self.global_purity {
purity_rc.borrow_mut().insert(global_index, true); purity_rc.borrow_mut().insert(global_index, true);
} }
} }
(BoundKind::DefGlobal { name, global_index, value }, node.ty) (BoundKind::DefGlobal { name, global_index, value }, node.ty)
}, },
BoundKind::Set { addr, value } => {
let value = Box::new(self.visit_node(*value, sub));
match addr {
Address::Local(slot) => {
if let BoundKind::Constant(val) = &value.kind {
sub.add_local(slot, val.clone());
} else {
sub.locals.remove(&slot);
}
}
Address::Global(idx) => {
if let BoundKind::Constant(val) = &value.kind {
sub.add_global(idx, val.clone());
} else {
sub.globals.remove(&idx);
}
}
_ => {}
}
(BoundKind::Set { addr, value }, node.ty)
},
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
let elements = elements.into_iter().map(|e| self.visit_node(e, sub)).collect(); let elements = elements.into_iter().map(|e| self.visit_node(e, sub)).collect();
(BoundKind::Tuple { elements }, node.ty) (BoundKind::Tuple { elements }, node.ty)
@@ -338,11 +365,24 @@ impl Optimizer {
Node { identity: node.identity, kind: new_kind, ty: new_ty } Node { identity: node.identity, kind: new_kind, ty: new_ty }
} }
fn collect_parameter_slots(&self, node: &TypedNode, sub: &mut SubstitutionMap) {
match &node.kind {
BoundKind::Parameter { slot, .. } => {
// Identity mapping for parameters to keep them in their reserved frame slots
sub.slot_mapping.insert(*slot, *slot);
sub.next_slot = sub.next_slot.max(*slot + 1);
}
BoundKind::Tuple { elements } => {
for el in elements { self.collect_parameter_slots(el, sub); }
}
_ => {}
}
}
fn is_inlinable_value(&self, val: &Value) -> bool { fn is_inlinable_value(&self, val: &Value) -> bool {
match val { match val {
Value::Int(_) | Value::Float(_) | Value::Bool(_) | Value::Text(_) | Value::Keyword(_) | Value::DateTime(_) => true, Value::Int(_) | Value::Float(_) | Value::Bool(_) | Value::Text(_) | Value::Keyword(_) | Value::DateTime(_) => true,
Value::Object(obj) => { Value::Object(obj) => {
// Inline Closures ONLY if they are stateless (cracked)
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() { if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
closure.upvalues.is_empty() closure.upvalues.is_empty()
} else { } else {
@@ -365,7 +405,7 @@ impl Optimizer {
false false
} }
} }
_ => true, // Locals and Upvalues are pure in terms of side-effects _ => true,
} }
} }
BoundKind::Tuple { elements } => elements.iter().all(|e| self.is_pure(e)), BoundKind::Tuple { elements } => elements.iter().all(|e| self.is_pure(e)),
@@ -376,9 +416,7 @@ impl Optimizer {
BoundKind::Block { exprs } => exprs.iter().all(|e| self.is_pure(e)), BoundKind::Block { exprs } => exprs.iter().all(|e| self.is_pure(e)),
BoundKind::Expansion { bound_expanded, .. } => self.is_pure(bound_expanded), BoundKind::Expansion { bound_expanded, .. } => self.is_pure(bound_expanded),
BoundKind::DefLocal { value, .. } | BoundKind::Set { addr: Address::Local(_), value } => self.is_pure(value), BoundKind::DefLocal { value, .. } | BoundKind::Set { addr: Address::Local(_), value } => self.is_pure(value),
BoundKind::Call { callee, args } => { BoundKind::Call { callee, args } => {
// A call is pure if the callee is a known pure function and args are pure
let callee_pure = match &callee.kind { let callee_pure = match &callee.kind {
BoundKind::Get { addr: Address::Global(idx), .. } => { BoundKind::Get { addr: Address::Global(idx), .. } => {
if let Some(purity_rc) = &self.global_purity { if let Some(purity_rc) = &self.global_purity {
@@ -387,7 +425,7 @@ impl Optimizer {
false false
} }
} }
BoundKind::Constant(Value::Function(_)) => true, // Native fns are checked at registry BoundKind::Constant(Value::Function(_)) => true,
BoundKind::Constant(Value::Object(obj)) => { BoundKind::Constant(Value::Object(obj)) => {
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() { if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
self.is_pure(&closure.function_node) self.is_pure(&closure.function_node)
@@ -399,38 +437,24 @@ impl Optimizer {
}; };
callee_pure && self.is_pure(args) callee_pure && self.is_pure(args)
} }
_ => false,
_ => false, // DefGlobal and Set(Global) are impure
} }
} }
fn try_fold_pure(&self, callee: &TypedNode, args: &TypedNode) -> Option<TypedNode> { fn try_fold_pure(&self, callee: &TypedNode, args: &TypedNode) -> Option<TypedNode> {
// 1. Check if the whole call is pure
// Create a temporary call node to check purity
let temp_call = Node { let temp_call = Node {
identity: callee.identity.clone(), identity: callee.identity.clone(),
ty: StaticType::Any, ty: StaticType::Any,
kind: BoundKind::Call { callee: Box::new(callee.clone()), args: Box::new(args.clone()) } kind: BoundKind::Call { callee: Box::new(callee.clone()), args: Box::new(args.clone()) }
}; };
if !self.is_pure(&temp_call) { return None; }
if !self.is_pure(&temp_call) {
return None;
}
// 2. Extract constant arguments
let mut arg_nodes = Vec::new(); let mut arg_nodes = Vec::new();
self.flatten_typed_tuple(args, &mut arg_nodes); self.flatten_typed_tuple(args, &mut arg_nodes);
let mut arg_values = Vec::with_capacity(arg_nodes.len()); let mut arg_values = Vec::with_capacity(arg_nodes.len());
for node in arg_nodes { for node in arg_nodes {
if let BoundKind::Constant(val) = node.kind { if let BoundKind::Constant(val) = node.kind { arg_values.push(val); }
arg_values.push(val); else { return None; }
} else {
return None; // All arguments must be constants for folding
}
} }
// 3. Resolve the function to execute
let func_val = match &callee.kind { let func_val = match &callee.kind {
BoundKind::Get { addr: Address::Global(idx), .. } => { BoundKind::Get { addr: Address::Global(idx), .. } => {
self.globals.as_ref()?.borrow().get(*idx as usize)?.clone() self.globals.as_ref()?.borrow().get(*idx as usize)?.clone()
@@ -438,26 +462,10 @@ impl Optimizer {
BoundKind::Constant(val) => val.clone(), BoundKind::Constant(val) => val.clone(),
_ => return None, _ => return None,
}; };
// 4. Execute
let result = match func_val { let result = match func_val {
Value::Function(f) => f(arg_values), Value::Function(f) => f(arg_values),
Value::Object(obj) => {
if let Some(_closure) = obj.as_any().downcast_ref::<Closure>() {
// For closures, we'd need a VM to execute.
// However, if it's pure and we are in the Optimizer,
// we might want to avoid full VM spin-up here if possible,
// or use a shared VM instance.
// For now, we only fold Native Functions.
return None;
} else {
return None;
}
}
_ => return None, _ => return None,
}; };
// 5. Return new constant node
Some(Node { Some(Node {
identity: callee.identity.clone(), identity: callee.identity.clone(),
ty: result.static_type(), ty: result.static_type(),
@@ -466,10 +474,6 @@ impl Optimizer {
} }
fn try_beta_reduce(&self, params: &TypedNode, args: &TypedNode, body: TypedNode) -> Option<TypedNode> { fn try_beta_reduce(&self, params: &TypedNode, args: &TypedNode, body: TypedNode) -> Option<TypedNode> {
if self.contains_def_local(&body) {
return None;
}
let mut sub = SubstitutionMap::new(); let mut sub = SubstitutionMap::new();
let mut arg_vals = Vec::new(); let mut arg_vals = Vec::new();
self.flatten_typed_tuple(args, &mut arg_vals); self.flatten_typed_tuple(args, &mut arg_vals);
@@ -477,22 +481,15 @@ impl Optimizer {
let mut slot_index = 0; let mut slot_index = 0;
self.map_params_to_args(params, &arg_vals, &mut slot_index, &mut sub); self.map_params_to_args(params, &arg_vals, &mut slot_index, &mut sub);
if sub.locals.len() < arg_vals.len() { if sub.locals.len() < arg_vals.len() { return None; }
return None; if sub.locals.is_empty() && sub.upvalues.is_empty() && !arg_vals.is_empty() { return None; }
}
if sub.locals.is_empty() && sub.upvalues.is_empty() && !arg_vals.is_empty() {
return None;
}
Some(self.visit_node(body, &mut sub)) Some(self.visit_node(body, &mut sub))
} }
fn flatten_typed_tuple(&self, node: &TypedNode, into: &mut Vec<TypedNode>) { fn flatten_typed_tuple(&self, node: &TypedNode, into: &mut Vec<TypedNode>) {
if let BoundKind::Tuple { elements } = &node.kind { if let BoundKind::Tuple { elements } = &node.kind {
for el in elements { for el in elements { self.flatten_typed_tuple(el, into); }
self.flatten_typed_tuple(el, into);
}
} else if !matches!(node.kind, BoundKind::Nop) { } else if !matches!(node.kind, BoundKind::Nop) {
into.push(node.clone()); into.push(node.clone());
} }
@@ -505,47 +502,70 @@ impl Optimizer {
&& let BoundKind::Constant(val) = &arg.kind { && let BoundKind::Constant(val) = &arg.kind {
sub.add_local(*slot, val.clone()); sub.add_local(*slot, val.clone());
} }
// Parameters in beta-reduction are also identity mapped if not inlined
sub.slot_mapping.insert(*slot, *slot);
sub.next_slot = sub.next_slot.max(*slot + 1);
*offset += 1; *offset += 1;
} }
BoundKind::Tuple { elements } => { BoundKind::Tuple { elements } => {
for el in elements { for el in elements { self.map_params_to_args(el, args, offset, sub); }
self.map_params_to_args(el, args, offset, sub);
}
} }
_ => {} _ => {}
} }
} }
fn contains_def_local(&self, node: &TypedNode) -> bool { fn collect_local_usage(&self, node: &TypedNode, used: &mut HashSet<u32>) {
match &node.kind { match &node.kind {
BoundKind::DefLocal { .. } => true, BoundKind::Get { addr: Address::Local(slot), .. } | BoundKind::Set { addr: Address::Local(slot), .. } => {
BoundKind::If { cond, then_br, else_br } => { used.insert(*slot);
self.contains_def_local(cond) || self.contains_def_local(then_br) || else_br.as_ref().is_some_and(|e| self.contains_def_local(e))
} }
BoundKind::Block { exprs } => exprs.iter().any(|e| self.contains_def_local(e)), BoundKind::Lambda { upvalues, .. } => {
BoundKind::Call { callee, args } => self.contains_def_local(callee) || self.contains_def_local(args), for addr in upvalues {
BoundKind::Lambda { .. } => false, if let Address::Local(slot) = addr { used.insert(*slot); }
BoundKind::Tuple { elements } => elements.iter().any(|e| self.contains_def_local(e)), }
BoundKind::Record { fields } => fields.iter().any(|(k, v)| self.contains_def_local(k) || self.contains_def_local(v)), }
BoundKind::Set { value, .. } => self.contains_def_local(value), BoundKind::Block { exprs } => {
_ => false, for e in exprs { self.collect_local_usage(e, used); }
}
BoundKind::If { cond, then_br, else_br } => {
self.collect_local_usage(cond, used);
self.collect_local_usage(then_br, used);
if let Some(e) = else_br { self.collect_local_usage(e, used); }
}
BoundKind::Call { callee, args } => {
self.collect_local_usage(callee, used);
self.collect_local_usage(args, used);
}
BoundKind::Tuple { elements } => {
for e in elements { self.collect_local_usage(e, used); }
}
BoundKind::Record { fields } => {
for (k, v) in fields {
self.collect_local_usage(k, used);
self.collect_local_usage(v, used);
}
}
BoundKind::DefLocal { value, .. } | BoundKind::Set { value, .. } => {
self.collect_local_usage(value, used);
}
BoundKind::Expansion { bound_expanded, .. } => {
self.collect_local_usage(bound_expanded, used);
}
_ => {}
} }
} }
} }
/// Helper for transitively inlining values and cleaning up capture lists. /// Helper for transitively inlining values and cleaning up capture lists.
struct SubstitutionMap { struct SubstitutionMap {
/// Mapping Address::Local(slot) -> Value
locals: HashMap<u32, Value>, locals: HashMap<u32, Value>,
/// Mapping Address::Upvalue(idx) -> Value
upvalues: HashMap<u32, Value>, upvalues: HashMap<u32, Value>,
/// Mapping Address::Global(idx) -> Value (Script-local substitution)
globals: HashMap<u32, Value>, globals: HashMap<u32, Value>,
/// Tracking which local slots are actually used (not inlined) slot_mapping: HashMap<u32, u32>,
next_slot: u32,
used_slots: HashSet<u32>, used_slots: HashSet<u32>,
/// Tracking which global indices are used pure_slots: HashSet<u32>,
used_globals: HashSet<u32>, used_globals: HashSet<u32>,
/// Tracking which local slots are captured by lambdas
captured_slots: HashSet<u32>, captured_slots: HashSet<u32>,
} }
@@ -555,12 +575,25 @@ impl SubstitutionMap {
locals: HashMap::new(), locals: HashMap::new(),
upvalues: HashMap::new(), upvalues: HashMap::new(),
globals: HashMap::new(), globals: HashMap::new(),
slot_mapping: HashMap::new(),
next_slot: 0,
used_slots: HashSet::new(), used_slots: HashSet::new(),
pure_slots: HashSet::new(),
used_globals: HashSet::new(), used_globals: HashSet::new(),
captured_slots: HashSet::new(), captured_slots: HashSet::new(),
} }
} }
fn map_slot(&mut self, old_slot: u32) -> u32 {
if let Some(&new_slot) = self.slot_mapping.get(&old_slot) {
return new_slot;
}
let new_slot = self.next_slot;
self.slot_mapping.insert(old_slot, new_slot);
self.next_slot += 1;
new_slot
}
fn add_local(&mut self, slot: u32, val: Value) { fn add_local(&mut self, slot: u32, val: Value) {
self.locals.insert(slot, val); self.locals.insert(slot, val);
} }
@@ -573,7 +606,6 @@ impl SubstitutionMap {
self.globals.insert(idx, val); self.globals.insert(idx, val);
} }
/// Re-maps Get(Upvalue(old_idx)) to Get(Upvalue(new_idx)) based on a mapping table.
fn reindex_upvalues(&self, node: TypedNode, mapping: &[Option<u32>]) -> TypedNode { fn reindex_upvalues(&self, node: TypedNode, mapping: &[Option<u32>]) -> TypedNode {
let (new_kind, new_ty) = match node.kind { let (new_kind, new_ty) = match node.kind {
BoundKind::Get { addr: Address::Upvalue(idx), name } => { BoundKind::Get { addr: Address::Upvalue(idx), name } => {
@@ -586,22 +618,18 @@ impl SubstitutionMap {
(BoundKind::Get { addr: Address::Upvalue(idx), name }, node.ty) (BoundKind::Get { addr: Address::Upvalue(idx), name }, node.ty)
} }
}, },
BoundKind::Lambda { params, upvalues, body, positional_count } => { BoundKind::Lambda { params, upvalues, body, positional_count } => {
let mut next_upvalues = Vec::new(); let mut next_upvalues = Vec::new();
for addr in upvalues { for addr in upvalues {
if let Address::Upvalue(idx) = addr if let Address::Upvalue(idx) = addr
&& let Some(res) = mapping.get(idx as usize) { && let Some(res) = mapping.get(idx as usize) {
if let Some(new_idx) = res { if let Some(new_idx) = res { next_upvalues.push(Address::Upvalue(*new_idx)); }
next_upvalues.push(Address::Upvalue(*new_idx));
}
continue; continue;
} }
next_upvalues.push(addr); next_upvalues.push(addr);
} }
(BoundKind::Lambda { params, upvalues: next_upvalues, body, positional_count }, node.ty) (BoundKind::Lambda { params, upvalues: next_upvalues, body, positional_count }, node.ty)
}, },
BoundKind::If { cond, then_br, else_br } => { BoundKind::If { cond, then_br, else_br } => {
let cond = Box::new(self.reindex_upvalues(*cond, mapping)); let cond = Box::new(self.reindex_upvalues(*cond, mapping));
let then_br = Box::new(self.reindex_upvalues(*then_br, mapping)); let then_br = Box::new(self.reindex_upvalues(*then_br, mapping));
@@ -639,7 +667,6 @@ impl SubstitutionMap {
}, },
k => (k, node.ty), k => (k, node.ty),
}; };
Node { identity: node.identity, kind: new_kind, ty: new_ty } Node { identity: node.identity, kind: new_kind, ty: new_ty }
} }
} }
@@ -662,19 +689,12 @@ mod tests {
#[test] #[test]
fn test_opt_folding_complex() { fn test_opt_folding_complex() {
// Test float folding
let dump_float = get_optimized_dump("(+ 1.5 2.5)", 2); let dump_float = get_optimized_dump("(+ 1.5 2.5)", 2);
assert!(dump_float.contains("Constant: 4")); assert!(dump_float.contains("Constant: 4"));
// Test text folding
let dump_text = get_optimized_dump("(+ \"hello \" \"world\")", 2); let dump_text = get_optimized_dump("(+ \"hello \" \"world\")", 2);
assert!(dump_text.contains("Constant: \"hello world\"")); assert!(dump_text.contains("Constant: \"hello world\""));
// Test date folding
let dump_date = get_optimized_dump("(date \"2023-01-01\")", 2); let dump_date = get_optimized_dump("(date \"2023-01-01\")", 2);
assert!(dump_date.contains("Constant: #2023-01-01 00:00:00#")); assert!(dump_date.contains("Constant: #2023-01-01 00:00:00#"));
// Test comparison folding
let dump_cmp = get_optimized_dump("(> 10 5)", 2); let dump_cmp = get_optimized_dump("(> 10 5)", 2);
assert!(dump_cmp.contains("Constant: true")); assert!(dump_cmp.contains("Constant: true"));
} }
+1 -1
View File
@@ -91,7 +91,7 @@ impl Environment {
function_registry: Rc::new(RefCell::new(HashMap::new())), function_registry: Rc::new(RefCell::new(HashMap::new())),
monomorph_cache: Rc::new(RefCell::new(HashMap::new())), monomorph_cache: Rc::new(RefCell::new(HashMap::new())),
debug_mode: false, debug_mode: false,
optimization_level: 0, optimization_level: 2,
}; };
env.register_stdlib(); env.register_stdlib();
env env