Files
RustAst/src/ast/compiler/tco.rs
T
Michael Schimmel 7c38dee243 Refactor AST nodes to use Rc
This commit refactors various AST node types to use `Rc` (Reference
Counting) instead of `Box`. This change is primarily driven by the need
for shared ownership and more efficient handling of potentially
recursive or shared data structures within the AST.

The main benefits of this change include:

- **Reduced cloning:** `Rc` allows multiple parts of the AST to refer to
  the same node without needing to clone the entire node. This is
  particularly beneficial for optimization passes where nodes might be
  visited multiple times.
- **Enabling sharing:** It makes it easier to represent scenarios where
  a single AST node might be a child of multiple parent nodes.
- **Performance improvements:** By avoiding unnecessary deep copies of
  AST nodes, this change can lead to performance gains, especially
  during complex compilation phases.
2026-03-03 16:07:42 +01:00

188 lines
6.6 KiB
Rust

use crate::ast::compiler::bound_nodes::{AnalyzedNode, BoundKind};
use crate::ast::nodes::Node;
use crate::ast::types::StaticType;
use std::fmt::Debug;
use std::rc::Rc;
#[derive(Clone)]
pub struct RuntimeMetadata {
pub ty: StaticType,
pub is_tail: bool,
/// The analyzed node, containing metrics and a link to the original TypedNode.
pub original: Rc<AnalyzedNode>,
}
impl Debug for RuntimeMetadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Metadata")
.field("ty", &self.ty)
.field("is_tail", &self.is_tail)
.finish()
}
}
/// The ExecNode is the AST used by the VM. It carries TCO flags and links to metrics.
pub type ExecNode = Node<BoundKind<RuntimeMetadata>, RuntimeMetadata>;
pub struct TCO;
impl TCO {
/// Lowers an AnalyzedNode to an ExecNode and marks tail positions.
pub fn optimize(node: AnalyzedNode) -> ExecNode {
Self::transform(Rc::new(node), true)
}
fn transform(node_rc: Rc<AnalyzedNode>, is_tail_position: bool) -> ExecNode {
let node = &*node_rc;
let new_kind = match &node.kind {
BoundKind::Call { callee, args } => BoundKind::Call {
callee: Rc::new(Self::transform(callee.clone(), false)),
args: Rc::new(Self::transform(args.clone(), false)),
},
BoundKind::Again { args } => {
if !is_tail_position {
panic!("'again' is only allowed in tail position to avoid dead code.");
}
BoundKind::Again {
args: Rc::new(Self::transform(args.clone(), false)),
}
}
BoundKind::Pipe {
inputs,
lambda,
out_type,
} => {
let mut t_inputs = Vec::with_capacity(inputs.len());
for input in inputs {
t_inputs.push(Rc::new(Self::transform(input.clone(), false)));
}
BoundKind::Pipe {
inputs: t_inputs,
lambda: Rc::new(Self::transform(lambda.clone(), false)),
out_type: out_type.clone(),
}
}
BoundKind::If {
cond,
then_br,
else_br,
} => BoundKind::If {
cond: Rc::new(Self::transform(cond.clone(), false)),
then_br: Rc::new(Self::transform(
then_br.clone(),
is_tail_position,
)),
else_br: else_br
.as_ref()
.map(|e| Rc::new(Self::transform(e.clone(), is_tail_position))),
},
BoundKind::Block { exprs } => {
if exprs.is_empty() {
BoundKind::Block { exprs: vec![] }
} else {
let last_idx = exprs.len() - 1;
let mut new_exprs = Vec::with_capacity(exprs.len());
for (i, expr) in exprs.iter().enumerate() {
let is_last = i == last_idx;
new_exprs.push(Rc::new(Self::transform(
expr.clone(),
is_tail_position && is_last,
)));
}
BoundKind::Block { exprs: new_exprs }
}
}
BoundKind::Lambda {
params,
upvalues,
body,
positional_count,
} => BoundKind::Lambda {
params: Rc::new(Self::transform(params.clone(), false)),
upvalues: upvalues.clone(),
body: Rc::new(Self::transform(body.clone(), true)),
positional_count: *positional_count,
},
BoundKind::Set { addr, value } => BoundKind::Set {
addr: *addr,
value: Rc::new(Self::transform(value.clone(), false)),
},
BoundKind::Define {
name,
addr,
kind,
value,
captured_by,
} => BoundKind::Define {
name: name.clone(),
addr: *addr,
kind: *kind,
value: Rc::new(Self::transform(value.clone(), false)),
captured_by: captured_by.clone(),
},
BoundKind::Destructure { pattern, value } => BoundKind::Destructure {
pattern: Rc::new(Self::transform(pattern.clone(), false)),
value: Rc::new(Self::transform(value.clone(), false)),
},
BoundKind::Record { layout, values } => {
let new_values = values
.iter()
.map(|v| Rc::new(Self::transform(v.clone(), false)))
.collect();
BoundKind::Record {
layout: layout.clone(),
values: new_values,
}
}
BoundKind::Tuple { elements } => {
let new_elements = elements
.iter()
.map(|e| Rc::new(Self::transform(e.clone(), false)))
.collect();
BoundKind::Tuple {
elements: new_elements,
}
}
BoundKind::Constant(v) => BoundKind::Constant(v.clone()),
BoundKind::Get { addr, name } => BoundKind::Get {
addr: *addr,
name: name.clone(),
},
BoundKind::FieldAccessor(k) => BoundKind::FieldAccessor(*k),
BoundKind::GetField { rec, field } => BoundKind::GetField {
rec: Rc::new(Self::transform(rec.clone(), false)),
field: *field,
},
BoundKind::Nop => BoundKind::Nop,
BoundKind::Expansion {
original_call,
bound_expanded,
} => BoundKind::Expansion {
original_call: original_call.clone(),
bound_expanded: Rc::new(Self::transform(
bound_expanded.clone(),
is_tail_position,
)),
},
BoundKind::Extension(_) => BoundKind::Nop,
BoundKind::Error => BoundKind::Error,
};
Node {
identity: node.identity.clone(),
kind: new_kind,
ty: RuntimeMetadata {
ty: node.ty.original.ty.clone(),
is_tail: is_tail_position,
original: node_rc,
},
}
}
}