pub mod cfg;
pub mod dominators;
pub mod escape;
pub mod inspect;
pub mod loop_analysis;
pub mod monomorph;
pub mod nogc_verify;
pub mod optimize;
pub mod reduction;
pub mod ssa;
pub mod ssa_loop_overlay;
pub mod ssa_optimize;
pub mod verify;
use cjc_ast::{BinOp, UnaryOp, Visibility};
use std::collections::BTreeMap;
pub use escape::AllocHint;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct MirFnId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct BlockId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TempId(pub u32);
#[derive(Debug, Clone)]
pub struct MirProgram {
pub functions: Vec<MirFunction>,
pub struct_defs: Vec<MirStructDef>,
pub enum_defs: Vec<MirEnumDef>,
pub entry: MirFnId,
}
#[derive(Debug, Clone)]
pub struct MirStructDef {
pub name: String,
pub fields: Vec<(String, String)>,
pub is_record: bool,
pub vis: Visibility,
}
#[derive(Debug, Clone)]
pub struct MirEnumDef {
pub name: String,
pub variants: Vec<MirVariantDef>,
}
#[derive(Debug, Clone)]
pub struct MirVariantDef {
pub name: String,
pub fields: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct MirFunction {
pub id: MirFnId,
pub name: String,
pub type_params: Vec<(String, Vec<String>)>,
pub params: Vec<MirParam>,
pub return_type: Option<String>,
pub body: MirBody,
pub is_nogc: bool,
pub cfg_body: Option<cfg::MirCfg>,
pub decorators: Vec<String>,
pub vis: Visibility,
pub local_count: u32,
}
#[derive(Debug, Clone)]
pub struct MirParam {
pub name: String,
pub ty_name: String,
pub default: Option<MirExpr>,
pub is_variadic: bool,
}
impl MirFunction {
pub fn build_cfg(&mut self) {
let cfg = cfg::CfgBuilder::build(&self.body);
self.cfg_body = Some(cfg);
}
pub fn cfg(&mut self) -> &cfg::MirCfg {
if self.cfg_body.is_none() {
self.build_cfg();
}
self.cfg_body.as_ref().unwrap()
}
}
impl MirProgram {
pub fn build_all_cfgs(&mut self) {
for func in &mut self.functions {
func.build_cfg();
}
}
}
#[derive(Debug, Clone)]
pub struct MirBody {
pub stmts: Vec<MirStmt>,
pub result: Option<Box<MirExpr>>,
}
#[derive(Debug, Clone)]
pub enum MirStmt {
Let {
name: String,
mutable: bool,
init: MirExpr,
alloc_hint: Option<AllocHint>,
slot: Option<u32>,
},
Expr(MirExpr),
If {
cond: MirExpr,
then_body: MirBody,
else_body: Option<MirBody>,
},
While {
cond: MirExpr,
body: MirBody,
},
Return(Option<MirExpr>),
Break,
Continue,
NoGcBlock(MirBody),
}
#[derive(Debug, Clone)]
pub struct MirExpr {
pub kind: MirExprKind,
}
#[derive(Debug, Clone)]
pub enum MirExprKind {
IntLit(i64),
FloatLit(f64),
BoolLit(bool),
StringLit(String),
ByteStringLit(Vec<u8>),
ByteCharLit(u8),
RawStringLit(String),
RawByteStringLit(Vec<u8>),
RegexLit {
pattern: String,
flags: String,
},
TensorLit {
rows: Vec<Vec<MirExpr>>,
},
NaLit,
Var(String),
VarLocal {
name: String,
slot: u32,
},
Binary {
op: BinOp,
left: Box<MirExpr>,
right: Box<MirExpr>,
},
Unary {
op: UnaryOp,
operand: Box<MirExpr>,
},
Call {
callee: Box<MirExpr>,
args: Vec<MirExpr>,
},
Field {
object: Box<MirExpr>,
name: String,
},
Index {
object: Box<MirExpr>,
index: Box<MirExpr>,
},
MultiIndex {
object: Box<MirExpr>,
indices: Vec<MirExpr>,
},
Assign {
target: Box<MirExpr>,
value: Box<MirExpr>,
},
Block(MirBody),
StructLit {
name: String,
fields: Vec<(String, MirExpr)>,
},
ArrayLit(Vec<MirExpr>),
Col(String),
Lambda {
params: Vec<MirParam>,
body: Box<MirExpr>,
},
MakeClosure {
fn_name: String,
captures: Vec<MirExpr>,
},
If {
cond: Box<MirExpr>,
then_body: MirBody,
else_body: Option<MirBody>,
},
Match {
scrutinee: Box<MirExpr>,
arms: Vec<MirMatchArm>,
},
VariantLit {
enum_name: String,
variant: String,
fields: Vec<MirExpr>,
},
TupleLit(Vec<MirExpr>),
LinalgLU {
operand: Box<MirExpr>,
},
LinalgQR {
operand: Box<MirExpr>,
},
LinalgCholesky {
operand: Box<MirExpr>,
},
LinalgInv {
operand: Box<MirExpr>,
},
Broadcast {
operand: Box<MirExpr>,
target_shape: Vec<MirExpr>,
},
Void,
}
#[derive(Debug, Clone)]
pub struct MirMatchArm {
pub pattern: MirPattern,
pub body: MirBody,
}
#[derive(Debug, Clone)]
pub enum MirPattern {
Wildcard,
Binding { name: String, slot: Option<u32> },
LitInt(i64),
LitFloat(f64),
LitBool(bool),
LitString(String),
Tuple(Vec<MirPattern>),
Struct {
name: String,
fields: Vec<(String, MirPattern)>,
},
Variant {
enum_name: String,
variant: String,
fields: Vec<MirPattern>,
},
}
use cjc_hir::*;
pub struct HirToMir {
next_fn_id: u32,
next_lambda_id: u32,
lifted_functions: Vec<MirFunction>,
scope_stack: Vec<BTreeMap<String, u32>>,
slot_counter: u32,
slot_resolution_active: bool,
}
#[derive(Debug)]
struct TrackerState {
scope_stack: Vec<BTreeMap<String, u32>>,
slot_counter: u32,
slot_resolution_active: bool,
}
impl HirToMir {
pub fn new() -> Self {
Self {
next_fn_id: 0,
next_lambda_id: 0,
lifted_functions: Vec::new(),
scope_stack: Vec::new(),
slot_counter: 0,
slot_resolution_active: false,
}
}
fn fresh_fn_id(&mut self) -> MirFnId {
let id = MirFnId(self.next_fn_id);
self.next_fn_id += 1;
id
}
fn fresh_lambda_name(&mut self) -> String {
let name = format!("__closure_{}", self.next_lambda_id);
self.next_lambda_id += 1;
name
}
fn enter_function(&mut self, params: &[MirParam]) {
self.scope_stack.clear();
self.scope_stack.push(BTreeMap::new());
self.slot_counter = 0;
self.slot_resolution_active = true;
for p in params {
self.define_local(&p.name);
}
}
fn exit_function(&mut self) -> u32 {
let count = self.slot_counter;
self.scope_stack.clear();
self.slot_counter = 0;
self.slot_resolution_active = false;
count
}
fn push_scope(&mut self) {
if self.slot_resolution_active {
self.scope_stack.push(BTreeMap::new());
}
}
fn pop_scope(&mut self) {
if self.slot_resolution_active {
self.scope_stack.pop();
}
}
fn define_local(&mut self, name: &str) -> u32 {
let slot = self.slot_counter;
if let Some(top) = self.scope_stack.last_mut() {
top.insert(name.to_string(), slot);
}
self.slot_counter += 1;
slot
}
fn resolve_local(&self, name: &str) -> Option<u32> {
if !self.slot_resolution_active {
return None;
}
for scope in self.scope_stack.iter().rev() {
if let Some(&slot) = scope.get(name) {
return Some(slot);
}
}
None
}
fn save_tracker(&mut self) -> TrackerState {
TrackerState {
scope_stack: std::mem::take(&mut self.scope_stack),
slot_counter: self.slot_counter,
slot_resolution_active: self.slot_resolution_active,
}
}
fn restore_tracker(&mut self, saved: TrackerState) {
self.scope_stack = saved.scope_stack;
self.slot_counter = saved.slot_counter;
self.slot_resolution_active = saved.slot_resolution_active;
}
pub fn lower_program(&mut self, hir: &HirProgram) -> MirProgram {
let mut functions = Vec::new();
let mut struct_defs = Vec::new();
let mut enum_defs = Vec::new();
let mut main_stmts: Vec<MirStmt> = Vec::new();
for item in &hir.items {
match item {
HirItem::Fn(f) => {
functions.push(self.lower_fn(f));
}
HirItem::Struct(s) => {
struct_defs.push(MirStructDef {
name: s.name.clone(),
fields: s.fields.clone(),
is_record: false,
vis: s.vis,
});
}
HirItem::Class(c) => {
struct_defs.push(MirStructDef {
name: c.name.clone(),
fields: c.fields.clone(),
is_record: false,
vis: c.vis,
});
}
HirItem::Record(r) => {
struct_defs.push(MirStructDef {
name: r.name.clone(),
fields: r.fields.clone(),
is_record: true,
vis: r.vis,
});
}
HirItem::Enum(e) => {
enum_defs.push(MirEnumDef {
name: e.name.clone(),
variants: e
.variants
.iter()
.map(|v| MirVariantDef {
name: v.name.clone(),
fields: v.fields.clone(),
})
.collect(),
});
}
HirItem::Let(l) => {
main_stmts.push(MirStmt::Let {
name: l.name.clone(),
mutable: l.mutable,
init: self.lower_expr(&l.init),
alloc_hint: None,
slot: None,
});
}
HirItem::Stmt(s) => {
main_stmts.push(self.lower_stmt(s));
}
HirItem::Impl(i) => {
for method in &i.methods {
let mut mir_fn = self.lower_fn(method);
mir_fn.name = format!("{}.{}", i.target, method.name);
functions.push(mir_fn);
}
}
HirItem::Trait(_) => {
}
}
}
let main_id = self.fresh_fn_id();
functions.push(MirFunction {
id: main_id,
name: "__main".to_string(),
type_params: vec![],
params: vec![],
return_type: None,
body: MirBody {
stmts: main_stmts,
result: None,
},
is_nogc: false,
cfg_body: None,
decorators: vec![],
vis: Visibility::Private,
local_count: 0,
});
functions.append(&mut self.lifted_functions);
MirProgram {
functions,
struct_defs,
enum_defs,
entry: main_id,
}
}
pub fn lower_fn(&mut self, f: &HirFn) -> MirFunction {
let id = self.fresh_fn_id();
let saved = self.save_tracker();
let params: Vec<MirParam> = f
.params
.iter()
.map(|p| MirParam {
name: p.name.clone(),
ty_name: p.ty_name.clone(),
default: p.default.as_ref().map(|d| self.lower_expr(d)),
is_variadic: p.is_variadic,
})
.collect();
self.enter_function(¶ms);
let body = self.lower_block(&f.body);
let local_count = self.exit_function();
self.restore_tracker(saved);
MirFunction {
id,
name: f.name.clone(),
type_params: f.type_params.clone(),
params,
return_type: f.return_type.clone(),
body,
is_nogc: f.is_nogc,
cfg_body: None,
decorators: f.decorators.clone(),
vis: f.vis,
local_count,
}
}
fn lower_block(&mut self, block: &HirBlock) -> MirBody {
self.push_scope();
let stmts = block.stmts.iter().map(|s| self.lower_stmt(s)).collect();
let result = block.expr.as_ref().map(|e| Box::new(self.lower_expr(e)));
self.pop_scope();
MirBody { stmts, result }
}
fn lower_stmt(&mut self, stmt: &HirStmt) -> MirStmt {
match &stmt.kind {
HirStmtKind::Let {
name,
mutable,
init,
..
} => {
let init = self.lower_expr(init);
let slot = if self.slot_resolution_active {
Some(self.define_local(name))
} else {
None
};
MirStmt::Let {
name: name.clone(),
mutable: *mutable,
init,
alloc_hint: None,
slot,
}
}
HirStmtKind::Expr(e) => MirStmt::Expr(self.lower_expr(e)),
HirStmtKind::If(if_expr) => self.lower_if_stmt(if_expr),
HirStmtKind::While { cond, body } => MirStmt::While {
cond: self.lower_expr(cond),
body: self.lower_block(body),
},
HirStmtKind::Return(e) => {
MirStmt::Return(e.as_ref().map(|ex| self.lower_expr(ex)))
}
HirStmtKind::Break => MirStmt::Break,
HirStmtKind::Continue => MirStmt::Continue,
HirStmtKind::NoGcBlock(block) => MirStmt::NoGcBlock(self.lower_block(block)),
}
}
pub fn lower_if_stmt(&mut self, if_expr: &HirIfExpr) -> MirStmt {
let cond = self.lower_expr(&if_expr.cond);
let then_body = self.lower_block(&if_expr.then_block);
let else_body = if_expr.else_branch.as_ref().map(|eb| match eb {
HirElseBranch::ElseIf(elif) => {
let nested = self.lower_if_stmt(elif);
MirBody {
stmts: vec![nested],
result: None,
}
}
HirElseBranch::Else(block) => self.lower_block(block),
});
MirStmt::If {
cond,
then_body,
else_body,
}
}
pub fn lower_expr(&mut self, expr: &HirExpr) -> MirExpr {
let kind = match &expr.kind {
HirExprKind::IntLit(v) => MirExprKind::IntLit(*v),
HirExprKind::FloatLit(v) => MirExprKind::FloatLit(*v),
HirExprKind::BoolLit(b) => MirExprKind::BoolLit(*b),
HirExprKind::NaLit => MirExprKind::NaLit,
HirExprKind::StringLit(s) => MirExprKind::StringLit(s.clone()),
HirExprKind::ByteStringLit(bytes) => MirExprKind::ByteStringLit(bytes.clone()),
HirExprKind::ByteCharLit(b) => MirExprKind::ByteCharLit(*b),
HirExprKind::RawStringLit(s) => MirExprKind::RawStringLit(s.clone()),
HirExprKind::RawByteStringLit(bytes) => MirExprKind::RawByteStringLit(bytes.clone()),
HirExprKind::RegexLit { pattern, flags } => MirExprKind::RegexLit { pattern: pattern.clone(), flags: flags.clone() },
HirExprKind::TensorLit { rows } => {
let mir_rows = rows.iter().map(|row| {
row.iter().map(|e| self.lower_expr(e)).collect()
}).collect();
MirExprKind::TensorLit { rows: mir_rows }
}
HirExprKind::Var(name) => {
match self.resolve_local(name) {
Some(slot) => MirExprKind::VarLocal {
name: name.clone(),
slot,
},
None => MirExprKind::Var(name.clone()),
}
}
HirExprKind::Binary { op, left, right } => MirExprKind::Binary {
op: *op,
left: Box::new(self.lower_expr(left)),
right: Box::new(self.lower_expr(right)),
},
HirExprKind::Unary { op, operand } => MirExprKind::Unary {
op: *op,
operand: Box::new(self.lower_expr(operand)),
},
HirExprKind::Call { callee, args } => MirExprKind::Call {
callee: Box::new(self.lower_expr(callee)),
args: args.iter().map(|a| self.lower_expr(a)).collect(),
},
HirExprKind::Field { object, name } => MirExprKind::Field {
object: Box::new(self.lower_expr(object)),
name: name.clone(),
},
HirExprKind::Index { object, index } => MirExprKind::Index {
object: Box::new(self.lower_expr(object)),
index: Box::new(self.lower_expr(index)),
},
HirExprKind::MultiIndex { object, indices } => MirExprKind::MultiIndex {
object: Box::new(self.lower_expr(object)),
indices: indices.iter().map(|i| self.lower_expr(i)).collect(),
},
HirExprKind::Assign { target, value } => MirExprKind::Assign {
target: Box::new(self.lower_expr(target)),
value: Box::new(self.lower_expr(value)),
},
HirExprKind::Block(block) => MirExprKind::Block(self.lower_block(block)),
HirExprKind::StructLit { name, fields } => MirExprKind::StructLit {
name: name.clone(),
fields: fields
.iter()
.map(|(n, e)| (n.clone(), self.lower_expr(e)))
.collect(),
},
HirExprKind::ArrayLit(elems) => {
MirExprKind::ArrayLit(elems.iter().map(|e| self.lower_expr(e)).collect())
}
HirExprKind::Col(name) => MirExprKind::Col(name.clone()),
HirExprKind::Lambda { params, body } => MirExprKind::Lambda {
params: params
.iter()
.map(|p| MirParam {
name: p.name.clone(),
ty_name: p.ty_name.clone(),
default: p.default.as_ref().map(|d| self.lower_expr(d)),
is_variadic: p.is_variadic,
})
.collect(),
body: Box::new(self.lower_expr(body)),
},
HirExprKind::Closure {
params,
body,
captures,
} => {
let lifted_name = self.fresh_lambda_name();
let lifted_id = self.fresh_fn_id();
let mut lifted_params: Vec<MirParam> = captures
.iter()
.map(|c| MirParam {
name: c.name.clone(),
ty_name: "any".to_string(), default: None,
is_variadic: false,
})
.collect();
for p in params {
lifted_params.push(MirParam {
name: p.name.clone(),
ty_name: p.ty_name.clone(),
default: p.default.as_ref().map(|d| self.lower_expr(d)),
is_variadic: p.is_variadic,
});
}
let capture_exprs: Vec<MirExpr> = captures
.iter()
.map(|c| {
let kind = match self.resolve_local(&c.name) {
Some(slot) => MirExprKind::VarLocal {
name: c.name.clone(),
slot,
},
None => MirExprKind::Var(c.name.clone()),
};
MirExpr { kind }
})
.collect();
let saved = self.save_tracker();
self.enter_function(&lifted_params);
let lifted_body = MirBody {
stmts: vec![],
result: Some(Box::new(self.lower_expr(body))),
};
let local_count = self.exit_function();
self.restore_tracker(saved);
self.lifted_functions.push(MirFunction {
id: lifted_id,
name: lifted_name.clone(),
type_params: vec![],
params: lifted_params,
return_type: None,
body: lifted_body,
is_nogc: false,
cfg_body: None,
decorators: vec![],
vis: Visibility::Private,
local_count,
});
MirExprKind::MakeClosure {
fn_name: lifted_name,
captures: capture_exprs,
}
}
HirExprKind::Match { scrutinee, arms } => {
let mir_scrutinee = Box::new(self.lower_expr(scrutinee));
let mir_arms = arms
.iter()
.map(|arm| {
self.push_scope();
let pattern = self.lower_pattern(&arm.pattern);
let body = MirBody {
stmts: vec![],
result: Some(Box::new(self.lower_expr(&arm.body))),
};
self.pop_scope();
MirMatchArm { pattern, body }
})
.collect();
MirExprKind::Match {
scrutinee: mir_scrutinee,
arms: mir_arms,
}
}
HirExprKind::TupleLit(elems) => {
MirExprKind::TupleLit(elems.iter().map(|e| self.lower_expr(e)).collect())
}
HirExprKind::VariantLit {
enum_name,
variant,
fields,
} => MirExprKind::VariantLit {
enum_name: enum_name.clone(),
variant: variant.clone(),
fields: fields.iter().map(|f| self.lower_expr(f)).collect(),
},
HirExprKind::If { cond, then_block, else_branch } => {
let mir_cond = Box::new(self.lower_expr(cond));
let mir_then = self.lower_block(then_block);
let mir_else = else_branch.as_ref().map(|eb| match eb {
HirElseBranch::ElseIf(elif) => {
let nested = self.lower_if_stmt(elif);
MirBody {
stmts: vec![nested],
result: None,
}
}
HirElseBranch::Else(block) => self.lower_block(block),
});
MirExprKind::If {
cond: mir_cond,
then_body: mir_then,
else_body: mir_else,
}
}
HirExprKind::Void => MirExprKind::Void,
};
MirExpr { kind }
}
fn lower_pattern(&mut self, pat: &HirPattern) -> MirPattern {
match &pat.kind {
HirPatternKind::Wildcard => MirPattern::Wildcard,
HirPatternKind::Binding(name) => {
let slot = if self.slot_resolution_active {
Some(self.define_local(name))
} else {
None
};
MirPattern::Binding {
name: name.clone(),
slot,
}
}
HirPatternKind::LitInt(v) => MirPattern::LitInt(*v),
HirPatternKind::LitFloat(v) => MirPattern::LitFloat(*v),
HirPatternKind::LitBool(b) => MirPattern::LitBool(*b),
HirPatternKind::LitString(s) => MirPattern::LitString(s.clone()),
HirPatternKind::Tuple(pats) => MirPattern::Tuple(
pats.iter().map(|p| self.lower_pattern(p)).collect(),
),
HirPatternKind::Struct { name, fields } => MirPattern::Struct {
name: name.clone(),
fields: fields
.iter()
.map(|f| (f.name.clone(), self.lower_pattern(&f.pattern)))
.collect(),
},
HirPatternKind::Variant {
enum_name,
variant,
fields,
} => MirPattern::Variant {
enum_name: enum_name.clone(),
variant: variant.clone(),
fields: fields.iter().map(|f| self.lower_pattern(f)).collect(),
},
}
}
}
impl Default for HirToMir {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use cjc_hir::*;
fn hir_id(n: u32) -> HirId {
HirId(n)
}
fn hir_int(v: i64) -> HirExpr {
HirExpr {
kind: HirExprKind::IntLit(v),
hir_id: hir_id(0),
}
}
fn hir_var(name: &str) -> HirExpr {
HirExpr {
kind: HirExprKind::Var(name.to_string()),
hir_id: hir_id(0),
}
}
#[test]
fn test_lower_hir_literal() {
let mut lowering = HirToMir::new();
let hir = hir_int(42);
let mir = lowering.lower_expr(&hir);
assert!(matches!(mir.kind, MirExprKind::IntLit(42)));
}
#[test]
fn test_lower_hir_binary() {
let mut lowering = HirToMir::new();
let hir = HirExpr {
kind: HirExprKind::Binary {
op: BinOp::Add,
left: Box::new(hir_int(1)),
right: Box::new(hir_int(2)),
},
hir_id: hir_id(0),
};
let mir = lowering.lower_expr(&hir);
match &mir.kind {
MirExprKind::Binary { op, .. } => assert_eq!(*op, BinOp::Add),
_ => panic!("expected Binary"),
}
}
#[test]
fn test_lower_hir_fn() {
let mut lowering = HirToMir::new();
let hir_fn = HirFn {
name: "add".to_string(),
type_params: vec![],
params: vec![
HirParam {
name: "a".to_string(),
ty_name: "i64".to_string(),
default: None,
is_variadic: false,
hir_id: hir_id(1),
},
HirParam {
name: "b".to_string(),
ty_name: "i64".to_string(),
default: None,
is_variadic: false,
hir_id: hir_id(2),
},
],
return_type: Some("i64".to_string()),
body: HirBlock {
stmts: vec![],
expr: Some(Box::new(HirExpr {
kind: HirExprKind::Binary {
op: BinOp::Add,
left: Box::new(hir_var("a")),
right: Box::new(hir_var("b")),
},
hir_id: hir_id(3),
})),
hir_id: hir_id(4),
},
is_nogc: false,
hir_id: hir_id(5),
decorators: vec![],
vis: cjc_ast::Visibility::Private,
};
let mir_fn = lowering.lower_fn(&hir_fn);
assert_eq!(mir_fn.name, "add");
assert_eq!(mir_fn.params.len(), 2);
assert!(mir_fn.body.result.is_some());
}
#[test]
fn test_lower_hir_program_entry() {
let mut lowering = HirToMir::new();
let hir = HirProgram {
items: vec![
HirItem::Let(HirLetDecl {
name: "x".to_string(),
mutable: false,
ty_name: None,
init: hir_int(42),
hir_id: hir_id(0),
}),
HirItem::Fn(HirFn {
name: "f".to_string(),
type_params: vec![],
params: vec![],
return_type: None,
body: HirBlock {
stmts: vec![],
expr: Some(Box::new(hir_var("x"))),
hir_id: hir_id(1),
},
is_nogc: false,
hir_id: hir_id(2),
decorators: vec![],
vis: cjc_ast::Visibility::Private,
}),
],
};
let mir = lowering.lower_program(&hir);
assert_eq!(mir.functions.len(), 2);
let main = mir.functions.iter().find(|f| f.name == "__main").unwrap();
assert_eq!(main.body.stmts.len(), 1); assert_eq!(mir.entry, main.id);
}
#[test]
fn test_lower_hir_if_stmt() {
let mut lowering = HirToMir::new();
let hir_if = HirIfExpr {
cond: Box::new(HirExpr {
kind: HirExprKind::BoolLit(true),
hir_id: hir_id(0),
}),
then_block: HirBlock {
stmts: vec![],
expr: Some(Box::new(hir_int(1))),
hir_id: hir_id(1),
},
else_branch: Some(HirElseBranch::Else(HirBlock {
stmts: vec![],
expr: Some(Box::new(hir_int(2))),
hir_id: hir_id(2),
})),
hir_id: hir_id(3),
};
let mir_stmt = lowering.lower_if_stmt(&hir_if);
match &mir_stmt {
MirStmt::If {
then_body,
else_body,
..
} => {
assert!(then_body.result.is_some());
assert!(else_body.is_some());
}
_ => panic!("expected If"),
}
}
#[test]
fn test_lower_struct_def() {
let mut lowering = HirToMir::new();
let hir = HirProgram {
items: vec![HirItem::Struct(HirStructDef {
name: "Point".to_string(),
fields: vec![
("x".to_string(), "f64".to_string()),
("y".to_string(), "f64".to_string()),
],
hir_id: hir_id(0),
vis: cjc_ast::Visibility::Private,
})],
};
let mir = lowering.lower_program(&hir);
assert_eq!(mir.struct_defs.len(), 1);
assert_eq!(mir.struct_defs[0].name, "Point");
assert_eq!(mir.struct_defs[0].fields.len(), 2);
}
fn mk_fn(name: &str, params: Vec<(&str, &str)>, body_expr: HirExpr) -> HirFn {
HirFn {
name: name.to_string(),
type_params: vec![],
params: params
.into_iter()
.map(|(n, t)| HirParam {
name: n.to_string(),
ty_name: t.to_string(),
default: None,
is_variadic: false,
hir_id: hir_id(0),
})
.collect(),
return_type: None,
body: HirBlock {
stmts: vec![],
expr: Some(Box::new(body_expr)),
hir_id: hir_id(0),
},
is_nogc: false,
hir_id: hir_id(0),
decorators: vec![],
vis: cjc_ast::Visibility::Private,
}
}
#[test]
fn t0b_stage2_params_get_sequential_slots() {
let mut lowering = HirToMir::new();
let body = HirExpr {
kind: HirExprKind::Binary {
op: BinOp::Add,
left: Box::new(HirExpr {
kind: HirExprKind::Binary {
op: BinOp::Add,
left: Box::new(hir_var("a")),
right: Box::new(hir_var("b")),
},
hir_id: hir_id(0),
}),
right: Box::new(hir_var("c")),
},
hir_id: hir_id(0),
};
let hir_fn = mk_fn("f", vec![("a", "i64"), ("b", "i64"), ("c", "i64")], body);
let mir_fn = lowering.lower_fn(&hir_fn);
assert_eq!(mir_fn.local_count, 3, "three params -> three slots");
fn collect_var_locals(expr: &MirExpr, out: &mut Vec<(String, u32)>) {
match &expr.kind {
MirExprKind::VarLocal { name, slot } => out.push((name.clone(), *slot)),
MirExprKind::Binary { left, right, .. } => {
collect_var_locals(left, out);
collect_var_locals(right, out);
}
_ => {}
}
}
let mut found = Vec::new();
collect_var_locals(mir_fn.body.result.as_ref().unwrap(), &mut found);
assert_eq!(
found,
vec![
("a".to_string(), 0),
("b".to_string(), 1),
("c".to_string(), 2),
],
"params should slot-resolve to their declaration order"
);
}
#[test]
fn t0b_stage2_let_binding_gets_next_slot_after_params() {
let mut lowering = HirToMir::new();
let let_stmt = HirStmt {
kind: HirStmtKind::Let {
name: "b".to_string(),
mutable: false,
ty_name: None,
init: hir_var("a"),
},
hir_id: hir_id(0),
};
let hir_fn = HirFn {
name: "f".to_string(),
type_params: vec![],
params: vec![HirParam {
name: "a".to_string(),
ty_name: "i64".to_string(),
default: None,
is_variadic: false,
hir_id: hir_id(0),
}],
return_type: None,
body: HirBlock {
stmts: vec![let_stmt],
expr: Some(Box::new(hir_var("b"))),
hir_id: hir_id(0),
},
is_nogc: false,
hir_id: hir_id(0),
decorators: vec![],
vis: cjc_ast::Visibility::Private,
};
let mir_fn = lowering.lower_fn(&hir_fn);
assert_eq!(mir_fn.local_count, 2);
match &mir_fn.body.stmts[0] {
MirStmt::Let { init, .. } => match &init.kind {
MirExprKind::VarLocal { name, slot } => {
assert_eq!(name, "a");
assert_eq!(*slot, 0);
}
other => panic!("expected VarLocal for `a`, got {other:?}"),
},
other => panic!("expected Let stmt, got {other:?}"),
}
match &mir_fn.body.result.as_ref().unwrap().kind {
MirExprKind::VarLocal { name, slot } => {
assert_eq!(name, "b");
assert_eq!(*slot, 1);
}
other => panic!("expected VarLocal for `b`, got {other:?}"),
}
}
#[test]
fn t0b_stage2_let_rhs_resolves_to_outer_for_shadowing() {
let mut lowering = HirToMir::new();
let let_stmt = HirStmt {
kind: HirStmtKind::Let {
name: "x".to_string(),
mutable: false,
ty_name: None,
init: HirExpr {
kind: HirExprKind::Binary {
op: BinOp::Add,
left: Box::new(hir_var("x")),
right: Box::new(hir_int(1)),
},
hir_id: hir_id(0),
},
},
hir_id: hir_id(0),
};
let hir_fn = HirFn {
name: "f".to_string(),
type_params: vec![],
params: vec![HirParam {
name: "x".to_string(),
ty_name: "i64".to_string(),
default: None,
is_variadic: false,
hir_id: hir_id(0),
}],
return_type: None,
body: HirBlock {
stmts: vec![let_stmt],
expr: Some(Box::new(hir_var("x"))),
hir_id: hir_id(0),
},
is_nogc: false,
hir_id: hir_id(0),
decorators: vec![],
vis: cjc_ast::Visibility::Private,
};
let mir_fn = lowering.lower_fn(&hir_fn);
assert_eq!(mir_fn.local_count, 2, "param x (slot 0) + let x (slot 1)");
match &mir_fn.body.stmts[0] {
MirStmt::Let { init, .. } => match &init.kind {
MirExprKind::Binary { left, .. } => match &left.kind {
MirExprKind::VarLocal { slot, .. } => assert_eq!(*slot, 0),
other => panic!("expected VarLocal in RHS, got {other:?}"),
},
other => panic!("expected Binary init, got {other:?}"),
},
other => panic!("expected Let stmt, got {other:?}"),
}
match &mir_fn.body.result.as_ref().unwrap().kind {
MirExprKind::VarLocal { slot, .. } => assert_eq!(*slot, 1),
other => panic!("expected VarLocal in body, got {other:?}"),
}
}
#[test]
fn t0b_stage2_main_function_not_slot_resolved() {
let mut lowering = HirToMir::new();
let hir = HirProgram {
items: vec![HirItem::Let(HirLetDecl {
name: "x".to_string(),
mutable: false,
ty_name: None,
init: hir_int(42),
hir_id: hir_id(0),
})],
};
let mir = lowering.lower_program(&hir);
let main = mir.functions.iter().find(|f| f.name == "__main").unwrap();
assert_eq!(main.local_count, 0, "__main left on name fallback in Stage 2");
}
#[test]
fn t0b_stage2_unresolved_name_stays_as_var() {
let mut lowering = HirToMir::new();
let hir_fn = mk_fn("f", vec![], hir_var("undefined_global"));
let mir_fn = lowering.lower_fn(&hir_fn);
assert_eq!(mir_fn.local_count, 0, "no params + no lets -> zero slots");
match &mir_fn.body.result.as_ref().unwrap().kind {
MirExprKind::Var(name) => assert_eq!(name, "undefined_global"),
other => panic!(
"expected Var(name) for unresolved reference, got {other:?}"
),
}
}
#[test]
fn t0b_stage4_closure_body_is_slot_resolved() {
let mut lowering = HirToMir::new();
let lambda_body = HirExpr {
kind: HirExprKind::Binary {
op: BinOp::Add,
left: Box::new(hir_var("x")),
right: Box::new(hir_var("y")),
},
hir_id: hir_id(0),
};
let closure = HirExpr {
kind: HirExprKind::Closure {
params: vec![HirParam {
name: "y".to_string(),
ty_name: "i64".to_string(),
default: None,
is_variadic: false,
hir_id: hir_id(0),
}],
body: Box::new(lambda_body),
captures: vec![HirCapture {
name: "x".to_string(),
mode: CaptureMode::Ref,
hir_id: hir_id(0),
}],
},
hir_id: hir_id(0),
};
let let_x = HirStmt {
kind: HirStmtKind::Let {
name: "x".to_string(),
mutable: false,
ty_name: None,
init: hir_int(1),
},
hir_id: hir_id(0),
};
let outer = HirFn {
name: "outer".to_string(),
type_params: vec![],
params: vec![],
return_type: None,
body: HirBlock {
stmts: vec![let_x],
expr: Some(Box::new(closure)),
hir_id: hir_id(0),
},
is_nogc: false,
hir_id: hir_id(0),
decorators: vec![],
vis: cjc_ast::Visibility::Private,
};
let _outer_mir = lowering.lower_fn(&outer);
assert_eq!(lowering.lifted_functions.len(), 1);
let lifted = &lowering.lifted_functions[0];
assert_eq!(
lifted.local_count, 2,
"Stage 4 slot-resolves closure bodies; lifted params -> slots 0..N"
);
match &lifted.body.result.as_ref().unwrap().kind {
MirExprKind::Binary { left, right, .. } => {
match &left.kind {
MirExprKind::VarLocal { name, slot } => {
assert_eq!(name, "x");
assert_eq!(*slot, 0, "capture-param `x` -> slot 0");
}
other => panic!(
"expected VarLocal for capture `x`, got {other:?}"
),
}
match &right.kind {
MirExprKind::VarLocal { name, slot } => {
assert_eq!(name, "y");
assert_eq!(*slot, 1, "original param `y` -> slot 1");
}
other => {
panic!("expected VarLocal for param `y`, got {other:?}")
}
}
}
other => panic!("expected Binary in closure body, got {other:?}"),
}
}
#[test]
fn t0b_stage2_capture_expr_in_outer_is_slot_resolved() {
let mut lowering = HirToMir::new();
let lambda_body = HirExpr {
kind: HirExprKind::Binary {
op: BinOp::Add,
left: Box::new(hir_var("x")),
right: Box::new(hir_var("y")),
},
hir_id: hir_id(0),
};
let closure = HirExpr {
kind: HirExprKind::Closure {
params: vec![HirParam {
name: "y".to_string(),
ty_name: "i64".to_string(),
default: None,
is_variadic: false,
hir_id: hir_id(0),
}],
body: Box::new(lambda_body),
captures: vec![HirCapture {
name: "x".to_string(),
mode: CaptureMode::Ref,
hir_id: hir_id(0),
}],
},
hir_id: hir_id(0),
};
let let_x = HirStmt {
kind: HirStmtKind::Let {
name: "x".to_string(),
mutable: false,
ty_name: None,
init: hir_int(1),
},
hir_id: hir_id(0),
};
let outer = HirFn {
name: "outer".to_string(),
type_params: vec![],
params: vec![],
return_type: None,
body: HirBlock {
stmts: vec![let_x],
expr: Some(Box::new(closure)),
hir_id: hir_id(0),
},
is_nogc: false,
hir_id: hir_id(0),
decorators: vec![],
vis: cjc_ast::Visibility::Private,
};
let outer_mir = lowering.lower_fn(&outer);
assert_eq!(outer_mir.local_count, 1, "outer fn has one let (x)");
match &outer_mir.body.result.as_ref().unwrap().kind {
MirExprKind::MakeClosure { captures, .. } => {
assert_eq!(captures.len(), 1);
match &captures[0].kind {
MirExprKind::VarLocal { name, slot } => {
assert_eq!(name, "x");
assert_eq!(*slot, 0);
}
other => panic!(
"expected VarLocal in MakeClosure capture, got {other:?}"
),
}
}
other => panic!("expected MakeClosure, got {other:?}"),
}
}
#[test]
fn t0b_stage2_nested_blocks_use_distinct_slots() {
let mut lowering = HirToMir::new();
let then_block = HirBlock {
stmts: vec![HirStmt {
kind: HirStmtKind::Let {
name: "x".to_string(),
mutable: false,
ty_name: None,
init: hir_int(1),
},
hir_id: hir_id(0),
}],
expr: Some(Box::new(hir_var("x"))),
hir_id: hir_id(0),
};
let else_block = HirBlock {
stmts: vec![HirStmt {
kind: HirStmtKind::Let {
name: "y".to_string(),
mutable: false,
ty_name: None,
init: hir_int(2),
},
hir_id: hir_id(0),
}],
expr: Some(Box::new(hir_var("y"))),
hir_id: hir_id(0),
};
let if_expr = HirExpr {
kind: HirExprKind::If {
cond: Box::new(HirExpr {
kind: HirExprKind::BoolLit(true),
hir_id: hir_id(0),
}),
then_block,
else_branch: Some(HirElseBranch::Else(else_block)),
},
hir_id: hir_id(0),
};
let hir_fn = mk_fn("f", vec![], if_expr);
let mir_fn = lowering.lower_fn(&hir_fn);
assert_eq!(
mir_fn.local_count, 2,
"shadowing across siblings consumes two slots"
);
}
#[test]
fn t0b_stage4_match_arm_pattern_bindings_get_slots() {
let mut lowering = HirToMir::new();
let arm_body = HirExpr {
kind: HirExprKind::Binary {
op: BinOp::Add,
left: Box::new(hir_var("outer")),
right: Box::new(hir_var("inner")),
},
hir_id: hir_id(0),
};
let match_expr = HirExpr {
kind: HirExprKind::Match {
scrutinee: Box::new(hir_var("outer")),
arms: vec![HirMatchArm {
pattern: HirPattern {
kind: HirPatternKind::Binding("inner".to_string()),
hir_id: hir_id(0),
},
body: arm_body,
hir_id: hir_id(0),
}],
},
hir_id: hir_id(0),
};
let hir_fn = mk_fn("f", vec![("outer", "i64")], match_expr);
let mir_fn = lowering.lower_fn(&hir_fn);
assert_eq!(
mir_fn.local_count, 2,
"param outer (slot 0) + pattern binding inner (slot 1)"
);
match &mir_fn.body.result.as_ref().unwrap().kind {
MirExprKind::Match { scrutinee, arms } => {
match &scrutinee.kind {
MirExprKind::VarLocal { name, slot } => {
assert_eq!(name, "outer");
assert_eq!(*slot, 0);
}
other => {
panic!("scrutinee should slot-resolve, got {other:?}")
}
}
match &arms[0].pattern {
MirPattern::Binding { name, slot } => {
assert_eq!(name, "inner");
assert_eq!(
*slot,
Some(1),
"pattern binding -> slot 1"
);
}
other => panic!(
"expected Binding pattern, got {other:?}"
),
}
match &arms[0].body.result.as_ref().unwrap().kind {
MirExprKind::Binary { left, right, .. } => {
match &left.kind {
MirExprKind::VarLocal { name, slot } => {
assert_eq!(name, "outer");
assert_eq!(*slot, 0);
}
other => panic!(
"expected VarLocal for `outer`, got {other:?}"
),
}
match &right.kind {
MirExprKind::VarLocal { name, slot } => {
assert_eq!(name, "inner");
assert_eq!(*slot, 1);
}
other => panic!(
"expected VarLocal for `inner`, got {other:?}"
),
}
}
other => {
panic!("expected Binary in arm body, got {other:?}")
}
}
}
other => panic!("expected Match, got {other:?}"),
}
}
#[test]
fn t0b_stage2_function_calls_dont_disturb_outer_slots() {
let mut lowering = HirToMir::new();
let f1 = mk_fn("f1", vec![("a", "i64"), ("b", "i64")], hir_var("a"));
let f2 = mk_fn("f2", vec![("x", "i64")], hir_var("x"));
let m1 = lowering.lower_fn(&f1);
let m2 = lowering.lower_fn(&f2);
assert_eq!(m1.local_count, 2);
assert_eq!(m2.local_count, 1, "f2 should not inherit f1's slot count");
}
}