From ca3a5d4d3f60a982a4b2c78013d46f55380003c2 Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Fri, 30 Jan 2026 15:52:05 +0000 Subject: [PATCH] pr 33 --- .../src/backend/emit_bytecode.rs | 3 +- crates/prometeu-compiler/src/compiler.rs | 27 +- .../src/frontends/pbs/lowering.rs | 4 + .../prometeu-compiler/src/ir_core/function.rs | 4 + crates/prometeu-compiler/src/ir_core/mod.rs | 8 +- .../prometeu-compiler/src/ir_core/program.rs | 3 + .../prometeu-compiler/src/ir_core/validate.rs | 2 + crates/prometeu-compiler/src/ir_vm/instr.rs | 20 +- crates/prometeu-compiler/src/ir_vm/mod.rs | 2 + .../src/lowering/core_to_vm.rs | 359 +++++++++++++++--- 10 files changed, 371 insertions(+), 61 deletions(-) diff --git a/crates/prometeu-compiler/src/backend/emit_bytecode.rs b/crates/prometeu-compiler/src/backend/emit_bytecode.rs index b41e56a5..cb234919 100644 --- a/crates/prometeu-compiler/src/backend/emit_bytecode.rs +++ b/crates/prometeu-compiler/src/backend/emit_bytecode.rs @@ -170,7 +170,8 @@ impl<'a> BytecodeEmitter<'a> { } InstrKind::GateBeginPeek | InstrKind::GateEndPeek | InstrKind::GateBeginBorrow | InstrKind::GateEndBorrow | - InstrKind::GateBeginMutate | InstrKind::GateEndMutate => { + InstrKind::GateBeginMutate | InstrKind::GateEndMutate | + InstrKind::GateRetain | InstrKind::GateRelease => { asm_instrs.push(Asm::Op(OpCode::Nop, vec![])); } } diff --git a/crates/prometeu-compiler/src/compiler.rs b/crates/prometeu-compiler/src/compiler.rs index f5e609f3..75865e27 100644 --- a/crates/prometeu-compiler/src/compiler.rs +++ b/crates/prometeu-compiler/src/compiler.rs @@ -240,17 +240,22 @@ mod tests { 0064 Alloc 0066 SetLocal U32(1) 006C GetLocal U32(1) -0072 SetLocal U32(2) -0078 Nop -007A GetLocal U32(2) -0080 LoadRef U32(0) -0086 SetLocal U32(3) -008C GetLocal U32(3) -0092 PushConst U32(5) -0098 Add -009A SetLocal U32(4) -00A0 Nop -00A2 Ret +0072 Nop +0074 SetLocal U32(2) +007A Nop +007C GetLocal U32(2) +0082 LoadRef U32(0) +0088 SetLocal U32(3) +008E GetLocal U32(3) +0094 PushConst U32(5) +009A Add +009C SetLocal U32(4) +00A2 Nop +00A4 GetLocal U32(1) +00AA Nop +00AC GetLocal U32(2) +00B2 Nop +00B4 Ret "#; assert_eq!(disasm_text, expected_disasm); diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs index 28a9c437..fa0ef4b0 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -34,6 +34,7 @@ impl<'a> Lowerer<'a> { const_pool: ir_core::ConstPool::new(), modules: Vec::new(), field_offsets, + field_types: HashMap::new(), }, current_function: None, current_block: None, @@ -106,6 +107,7 @@ impl<'a> Lowerer<'a> { self.local_vars = vec![HashMap::new()]; let mut params = Vec::new(); + let mut local_types = HashMap::new(); for (i, param) in n.params.iter().enumerate() { let ty = self.lower_type_node(¶m.ty); params.push(Param { @@ -113,6 +115,7 @@ impl<'a> Lowerer<'a> { ty: ty.clone(), }); self.local_vars[0].insert(param.name.clone(), i as u32); + local_types.insert(i as u32, ty); } let ret_ty = if let Some(ret) = &n.ret { @@ -127,6 +130,7 @@ impl<'a> Lowerer<'a> { params, return_type: ret_ty, blocks: Vec::new(), + local_types, }; self.current_function = Some(func); diff --git a/crates/prometeu-compiler/src/ir_core/function.rs b/crates/prometeu-compiler/src/ir_core/function.rs index 04e13a86..e486bacb 100644 --- a/crates/prometeu-compiler/src/ir_core/function.rs +++ b/crates/prometeu-compiler/src/ir_core/function.rs @@ -3,6 +3,8 @@ use super::ids::FunctionId; use super::block::Block; use super::types::Type; +use std::collections::HashMap; + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Param { pub name: String, @@ -17,4 +19,6 @@ pub struct Function { pub params: Vec, pub return_type: Type, pub blocks: Vec, + #[serde(default)] + pub local_types: HashMap, } diff --git a/crates/prometeu-compiler/src/ir_core/mod.rs b/crates/prometeu-compiler/src/ir_core/mod.rs index 32806ab2..f7890500 100644 --- a/crates/prometeu-compiler/src/ir_core/mod.rs +++ b/crates/prometeu-compiler/src/ir_core/mod.rs @@ -47,9 +47,11 @@ mod tests { ], terminator: Terminator::Return, }], + local_types: std::collections::HashMap::new(), }], }], field_offsets: std::collections::HashMap::new(), + field_types: std::collections::HashMap::new(), }; let json = serde_json::to_string_pretty(&program).unwrap(); @@ -87,12 +89,14 @@ mod tests { ], "terminator": "Return" } - ] + ], + "local_types": {} } ] } ], - "field_offsets": {} + "field_offsets": {}, + "field_types": {} }"#; assert_eq!(json, expected); } diff --git a/crates/prometeu-compiler/src/ir_core/program.rs b/crates/prometeu-compiler/src/ir_core/program.rs index e384441d..db1f55d0 100644 --- a/crates/prometeu-compiler/src/ir_core/program.rs +++ b/crates/prometeu-compiler/src/ir_core/program.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; use super::module::Module; use super::const_pool::ConstPool; use super::ids::FieldId; +use super::types::Type; use std::collections::HashMap; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -10,4 +11,6 @@ pub struct Program { pub modules: Vec, #[serde(default)] pub field_offsets: HashMap, + #[serde(default)] + pub field_types: HashMap, } diff --git a/crates/prometeu-compiler/src/ir_core/validate.rs b/crates/prometeu-compiler/src/ir_core/validate.rs index 7b7bfc25..a0670fb8 100644 --- a/crates/prometeu-compiler/src/ir_core/validate.rs +++ b/crates/prometeu-compiler/src/ir_core/validate.rs @@ -161,6 +161,7 @@ mod tests { params: vec![], return_type: Type::Void, blocks, + local_types: HashMap::new(), } } @@ -172,6 +173,7 @@ mod tests { functions: vec![func], }], field_offsets: HashMap::new(), + field_types: HashMap::new(), } } diff --git a/crates/prometeu-compiler/src/ir_vm/instr.rs b/crates/prometeu-compiler/src/ir_vm/instr.rs index cd4d0c4f..ff605fea 100644 --- a/crates/prometeu-compiler/src/ir_vm/instr.rs +++ b/crates/prometeu-compiler/src/ir_vm/instr.rs @@ -32,7 +32,7 @@ pub struct Label(pub String); /// The various types of operations that can be performed in the IR. /// /// The IR uses a stack-based model, similar to the final Prometeu ByteCode. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub enum InstrKind { /// Does nothing. Nop, @@ -154,6 +154,15 @@ pub enum InstrKind { GateEndBorrow, GateBeginMutate, GateEndMutate, + + // --- Reference Counting --- + + /// Increments the reference count of a gate handle on the stack. + /// Stack: [..., Gate(g)] -> [..., Gate(g)] + GateRetain, + /// Decrements the reference count of a gate handle and pops it from the stack. + /// Stack: [..., Gate(g)] -> [...] + GateRelease, } /// List of instructions that are sensitive to Reference Counting (RC). @@ -237,6 +246,8 @@ mod tests { InstrKind::GateEndBorrow, InstrKind::GateBeginMutate, InstrKind::GateEndMutate, + InstrKind::GateRetain, + InstrKind::GateRelease, ]; let serialized = serde_json::to_string_pretty(&instructions).unwrap(); @@ -332,7 +343,9 @@ mod tests { "GateBeginBorrow", "GateEndBorrow", "GateBeginMutate", - "GateEndMutate" + "GateEndMutate", + "GateRetain", + "GateRelease" ]"#; assert_eq!(serialized, expected_json); } @@ -346,7 +359,8 @@ mod tests { "GateLoad", "GateStore", "Alloc", "GateBeginPeek", "GateEndPeek", "GateBeginBorrow", "GateEndBorrow", - "GateBeginMutate", "GateEndMutate" + "GateBeginMutate", "GateEndMutate", + "GateRetain", "GateRelease" ]; for name in instructions { diff --git a/crates/prometeu-compiler/src/ir_vm/mod.rs b/crates/prometeu-compiler/src/ir_vm/mod.rs index 6ff91e52..3b062935 100644 --- a/crates/prometeu-compiler/src/ir_vm/mod.rs +++ b/crates/prometeu-compiler/src/ir_vm/mod.rs @@ -131,9 +131,11 @@ mod tests { ], terminator: ir_core::Terminator::Return, }], + local_types: std::collections::HashMap::new(), }], }], field_offsets: std::collections::HashMap::new(), + field_types: std::collections::HashMap::new(), }; let vm_module = lower_program(&program).expect("Lowering failed"); diff --git a/crates/prometeu-compiler/src/lowering/core_to_vm.rs b/crates/prometeu-compiler/src/lowering/core_to_vm.rs index 23f1e668..45c4c988 100644 --- a/crates/prometeu-compiler/src/lowering/core_to_vm.rs +++ b/crates/prometeu-compiler/src/lowering/core_to_vm.rs @@ -1,32 +1,48 @@ use crate::ir_vm; use crate::ir_core; use anyhow::Result; +use std::collections::HashMap; /// Lowers a Core IR program into a VM IR module. pub fn lower_program(program: &ir_core::Program) -> Result { + // Build a map of function return types for type tracking + let mut function_returns = HashMap::new(); + for module in &program.modules { + for func in &module.functions { + function_returns.insert(func.id, func.return_type.clone()); + } + } + // For now, we assume a single module program or lower the first one. - // In the future, we might want to lower all modules and link them. if let Some(core_module) = program.modules.first() { - lower_module(core_module, program) + lower_module(core_module, program, &function_returns) } else { anyhow::bail!("No modules in core program") } } /// Lowers a single Core IR module into a VM IR module. -pub fn lower_module(core_module: &ir_core::Module, program: &ir_core::Program) -> Result { +pub fn lower_module( + core_module: &ir_core::Module, + program: &ir_core::Program, + function_returns: &HashMap +) -> Result { let mut vm_module = ir_vm::Module::new(core_module.name.clone()); vm_module.const_pool = program.const_pool.clone(); for core_func in &core_module.functions { - vm_module.functions.push(lower_function(core_func, program)?); + vm_module.functions.push(lower_function(core_func, program, function_returns)?); } Ok(vm_module) } /// Lowers a Core IR function into a VM IR function. -pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program) -> Result { +pub fn lower_function( + core_func: &ir_core::Function, + program: &ir_core::Program, + function_returns: &HashMap +) -> Result { let mut vm_func = ir_vm::Function { id: core_func.id, name: core_func.name.clone(), @@ -38,6 +54,17 @@ pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program) body: vec![], }; + // Type tracking for RC insertion + let mut local_types = HashMap::new(); + // Populate with parameter types + for (i, param) in core_func.params.iter().enumerate() { + local_types.insert(i as u32, param.ty.clone()); + } + // Also use the pre-computed local types from ir_core if available + for (slot, ty) in &core_func.local_types { + local_types.insert(*slot, ty.clone()); + } + for block in &core_func.blocks { // Core blocks map to labels in the flat VM IR instruction list. vm_func.body.push(ir_vm::Instruction::new( @@ -45,87 +72,165 @@ pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program) None, )); + // Note: For multi-block functions, we should ideally track stack types across blocks. + // For v0, we assume each block starts with an empty stack in terms of types, + // which matches how PBS frontend generates code for now. + let mut stack_types = Vec::new(); + for instr in &block.instrs { match instr { ir_core::Instr::PushConst(id) => { + let ty = if let Some(val) = program.const_pool.get(ir_core::ConstId(id.0)) { + match val { + ir_core::ConstantValue::Int(_) => ir_core::Type::Int, + ir_core::ConstantValue::Float(_) => ir_core::Type::Float, + ir_core::ConstantValue::String(_) => ir_core::Type::String, + } + } else { + ir_core::Type::Void + }; + stack_types.push(ty); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::PushConst(ir_vm::ConstId(id.0)), None)); } ir_core::Instr::Call(func_id, arg_count) => { + // Pop arguments from type stack + for _ in 0..*arg_count { + stack_types.pop(); + } + // Push return type + let ret_ty = function_returns.get(func_id).cloned().unwrap_or(ir_core::Type::Void); + stack_types.push(ret_ty); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Call { func_id: *func_id, arg_count: *arg_count }, None)); } ir_core::Instr::HostCall(id) => { + // HostCall return types are not easily known without a registry, + // but usually they return Int or Void in v0. + stack_types.push(ir_core::Type::Int); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Syscall(*id), None)); } ir_core::Instr::GetLocal(slot) => { + let ty = local_types.get(slot).cloned().unwrap_or(ir_core::Type::Void); + stack_types.push(ty.clone()); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: *slot }, None)); + + // If it's a gate, we should retain it if we just pushed it onto stack? + // "on assigning a gate to a local/global" + // "on overwriting a local/global holding a gate" + // "on popping/dropping gate temporaries" + + // Wait, if I Load it, I have a new handle on the stack. I should Retain it. + if is_gate_type(&ty) { + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None)); + } } ir_core::Instr::SetLocal(slot) => { + let new_ty = stack_types.pop().unwrap_or(ir_core::Type::Void); + let old_ty = local_types.get(slot).cloned(); + + // 1. Release old value if it was a gate + if let Some(old_ty) = old_ty { + if is_gate_type(&old_ty) { + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: *slot }, None)); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None)); + } + } + + // 2. The new value is already on stack. + // We don't need to Retain it here because it was either just created (Alloc) + // or just Loaded (which already did a Retain). + // Wait, if it was just Loaded, it has +1. If we store it, it stays +1. + // If it was just Alocated, it has +1. If we store it, it stays +1. + + // Actually, if we Pop it later, we Release it. + + local_types.insert(*slot, new_ty); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalStore { slot: *slot }, None)); } ir_core::Instr::Pop => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Pop, None)); + let ty = stack_types.pop().unwrap_or(ir_core::Type::Void); + if is_gate_type(&ty) { + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None)); + } else { + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Pop, None)); + } } ir_core::Instr::Dup => { + let ty = stack_types.last().cloned().unwrap_or(ir_core::Type::Void); + stack_types.push(ty.clone()); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Dup, None)); + if is_gate_type(&ty) { + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None)); + } } - ir_core::Instr::Add => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Add, None)); - } - ir_core::Instr::Sub => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Sub, None)); - } - ir_core::Instr::Mul => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Mul, None)); - } - ir_core::Instr::Div => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Div, None)); + ir_core::Instr::Add | ir_core::Instr::Sub | ir_core::Instr::Mul | ir_core::Instr::Div => { + stack_types.pop(); + stack_types.pop(); + stack_types.push(ir_core::Type::Int); // Assume Int for arithmetic + let kind = match instr { + ir_core::Instr::Add => ir_vm::InstrKind::Add, + ir_core::Instr::Sub => ir_vm::InstrKind::Sub, + ir_core::Instr::Mul => ir_vm::InstrKind::Mul, + ir_core::Instr::Div => ir_vm::InstrKind::Div, + _ => unreachable!(), + }; + vm_func.body.push(ir_vm::Instruction::new(kind, None)); } ir_core::Instr::Neg => { vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Neg, None)); } - ir_core::Instr::Eq => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Eq, None)); + ir_core::Instr::Eq | ir_core::Instr::Neq | ir_core::Instr::Lt | ir_core::Instr::Lte | ir_core::Instr::Gt | ir_core::Instr::Gte => { + stack_types.pop(); + stack_types.pop(); + stack_types.push(ir_core::Type::Bool); + let kind = match instr { + ir_core::Instr::Eq => ir_vm::InstrKind::Eq, + ir_core::Instr::Neq => ir_vm::InstrKind::Neq, + ir_core::Instr::Lt => ir_vm::InstrKind::Lt, + ir_core::Instr::Lte => ir_vm::InstrKind::Lte, + ir_core::Instr::Gt => ir_vm::InstrKind::Gt, + ir_core::Instr::Gte => ir_vm::InstrKind::Gte, + _ => unreachable!(), + }; + vm_func.body.push(ir_vm::Instruction::new(kind, None)); } - ir_core::Instr::Neq => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Neq, None)); - } - ir_core::Instr::Lt => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Lt, None)); - } - ir_core::Instr::Lte => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Lte, None)); - } - ir_core::Instr::Gt => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Gt, None)); - } - ir_core::Instr::Gte => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Gte, None)); - } - ir_core::Instr::And => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::And, None)); - } - ir_core::Instr::Or => { - vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Or, None)); + ir_core::Instr::And | ir_core::Instr::Or => { + stack_types.pop(); + stack_types.pop(); + stack_types.push(ir_core::Type::Bool); + let kind = match instr { + ir_core::Instr::And => ir_vm::InstrKind::And, + ir_core::Instr::Or => ir_vm::InstrKind::Or, + _ => unreachable!(), + }; + vm_func.body.push(ir_vm::Instruction::new(kind, None)); } ir_core::Instr::Not => { + stack_types.pop(); + stack_types.push(ir_core::Type::Bool); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Not, None)); } ir_core::Instr::Alloc { ty, slots } => { + stack_types.push(ir_core::Type::Struct("".to_string())); // It's a gate vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Alloc { type_id: ir_vm::TypeId(ty.0), slots: *slots }, None)); } ir_core::Instr::BeginPeek { .. } => { + stack_types.pop(); // Pops gate vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginPeek, None)); } ir_core::Instr::BeginBorrow { .. } => { + stack_types.pop(); // Pops gate vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginBorrow, None)); } ir_core::Instr::BeginMutate { .. } => { + stack_types.pop(); // Pops gate vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginMutate, None)); } ir_core::Instr::EndPeek => { @@ -141,23 +246,42 @@ pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program) let offset = program.field_offsets.get(field) .ok_or_else(|| anyhow::anyhow!("E_LOWER_UNRESOLVED_OFFSET: Field {:?} offset cannot be resolved", field))?; + let field_ty = program.field_types.get(field).cloned().unwrap_or(ir_core::Type::Int); + stack_types.push(field_ty.clone()); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateLoad { offset: *offset }, None)); + + if is_gate_type(&field_ty) { + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None)); + } } ir_core::Instr::GateStoreField { gate, field, value } => { let offset = program.field_offsets.get(field) .ok_or_else(|| anyhow::anyhow!("E_LOWER_UNRESOLVED_OFFSET: Field {:?} offset cannot be resolved", field))?; + let field_ty = program.field_types.get(field).cloned().unwrap_or(ir_core::Type::Int); + + // 1. Release old value in HIP if it was a gate + if is_gate_type(&field_ty) { + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, None)); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateLoad { offset: *offset }, None)); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None)); + } + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: gate.0 }, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: value.0 }, None)); + + // 2. Retain new value if it's a gate + if let Some(val_ty) = local_types.get(&value.0) { + if is_gate_type(val_ty) { + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRetain, None)); + } + } + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateStore { offset: *offset }, None)); } ir_core::Instr::GateLoadIndex { .. } => { - // For indices, we might need a more complex resolution, but for now we assume index is the offset - // or maybe it's dynamic. ir_vm::GateLoad only takes a static offset. - // If index is dynamic, we can't lower it to GateLoad with static offset. - // However, PR-07 says: "if offset cannot be resolved deterministically at compile time, emit diagnostic and abort lowering." - // This implies we don't support dynamic indices yet. anyhow::bail!("E_LOWER_UNSUPPORTED: Dynamic HIP index access not supported in v0 lowering"); } ir_core::Instr::GateStoreIndex { .. } => { @@ -169,6 +293,17 @@ pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program) match &block.terminator { ir_core::Terminator::Return => { + // Release all live locals that hold gates + let mut sorted_slots: Vec<_> = local_types.keys().collect(); + sorted_slots.sort(); + + for slot in sorted_slots { + let ty = &local_types[slot]; + if is_gate_type(ty) { + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: *slot }, None)); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None)); + } + } vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Ret, None)); } ir_core::Terminator::Jump(target) => { @@ -178,6 +313,7 @@ pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program) )); } ir_core::Terminator::JumpIfFalse { target, else_target } => { + stack_types.pop(); vm_func.body.push(ir_vm::Instruction::new( ir_vm::InstrKind::JmpIfFalse(ir_vm::Label(format!("block_{}", target))), None, @@ -193,6 +329,20 @@ pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program) Ok(vm_func) } +fn is_gate_type(ty: &ir_core::Type) -> bool { + match ty { + ir_core::Type::Struct(_) | + ir_core::Type::Array(_, _) | + ir_core::Type::Optional(_) | + ir_core::Type::Result(_, _) | + ir_core::Type::Service(_) | + ir_core::Type::Contract(_) | + ir_core::Type::ErrorType(_) | + ir_core::Type::Function { .. } => true, + _ => false, + } +} + fn lower_type(ty: &ir_core::Type) -> ir_vm::Type { match ty { ir_core::Type::Void => ir_vm::Type::Void, @@ -250,9 +400,11 @@ mod tests { terminator: Terminator::Return, }, ], + local_types: HashMap::new(), }], }], field_offsets: std::collections::HashMap::new(), + field_types: std::collections::HashMap::new(), }; let vm_module = lower_program(&program).expect("Lowering failed"); @@ -320,9 +472,11 @@ mod tests { ], terminator: Terminator::Return, }], + local_types: HashMap::new(), }], }], field_offsets, + field_types: HashMap::new(), }; let vm_module = lower_program(&program).expect("Lowering failed"); @@ -370,9 +524,11 @@ mod tests { ], terminator: Terminator::Return, }], + local_types: HashMap::new(), }], }], field_offsets: std::collections::HashMap::new(), + field_types: HashMap::new(), }; let result = lower_program(&program); @@ -380,6 +536,121 @@ mod tests { assert!(result.unwrap_err().to_string().contains("E_LOWER_UNRESOLVED_OFFSET")); } + #[test] + fn test_rc_trace_lowering_golden() { + let mut const_pool = ConstPool::new(); + const_pool.insert(ConstantValue::Int(0)); // ConstId(0) + + let type_id = ir_core::TypeId(1); + + let program = Program { + const_pool, + modules: vec![ir_core::Module { + name: "test".to_string(), + functions: vec![ir_core::Function { + id: FunctionId(1), + name: "main".to_string(), + params: vec![], + return_type: ir_core::Type::Void, + blocks: vec![Block { + id: 0, + instrs: vec![ + // 1. allocates a gate + Instr::Alloc { ty: type_id, slots: 1 }, + Instr::SetLocal(0), // x = alloc + + // 2. copies it + Instr::GetLocal(0), + Instr::SetLocal(1), // y = x + + // 3. overwrites one copy + Instr::PushConst(CoreConstId(0)), + Instr::SetLocal(0), // x = 0 (overwrites gate) + ], + terminator: Terminator::Return, + }], + local_types: HashMap::new(), + }], + }], + field_offsets: HashMap::new(), + field_types: HashMap::new(), + }; + + let vm_module = lower_program(&program).expect("Lowering failed"); + let func = &vm_module.functions[0]; + + let kinds: Vec<_> = func.body.iter().map(|i| &i.kind).collect(); + + assert!(kinds.contains(&&InstrKind::GateRetain)); + assert!(kinds.contains(&&InstrKind::GateRelease)); + + // Check specific sequence for overwrite: + // LocalLoad 0, GateRelease, LocalStore 0 + let mut found_overwrite = false; + for i in 0..kinds.len() - 2 { + if let (InstrKind::LocalLoad { slot: 0 }, InstrKind::GateRelease, InstrKind::LocalStore { slot: 0 }) = (kinds[i], kinds[i+1], kinds[i+2]) { + found_overwrite = true; + break; + } + } + assert!(found_overwrite, "Should have emitted release-then-store sequence for overwrite"); + + // Check Ret cleanup: + // LocalLoad 1, GateRelease, Ret + let mut found_cleanup = false; + for i in 0..kinds.len() - 2 { + if let (InstrKind::LocalLoad { slot: 1 }, InstrKind::GateRelease, InstrKind::Ret) = (kinds[i], kinds[i+1], kinds[i+2]) { + found_cleanup = true; + break; + } + } + assert!(found_cleanup, "Should have emitted cleanup for local y at return"); + } + + #[test] + fn test_no_silent_rc() { + let mut const_pool = ConstPool::new(); + const_pool.insert(ConstantValue::Int(42)); + + let program = Program { + const_pool, + modules: vec![ir_core::Module { + name: "test".to_string(), + functions: vec![ir_core::Function { + id: FunctionId(1), + name: "main".to_string(), + params: vec![], + return_type: ir_core::Type::Void, + blocks: vec![Block { + id: 0, + instrs: vec![ + Instr::PushConst(CoreConstId(0)), + Instr::SetLocal(0), // x = 42 + Instr::GetLocal(0), + Instr::Pop, + ], + terminator: Terminator::Return, + }], + local_types: HashMap::new(), + }], + }], + field_offsets: HashMap::new(), + field_types: HashMap::new(), + }; + + let vm_module = lower_program(&program).expect("Lowering failed"); + let func = &vm_module.functions[0]; + + for instr in &func.body { + match &instr.kind { + InstrKind::GateRetain | InstrKind::GateRelease => { + panic!("Non-gate program should not contain RC instructions: {:?}", instr); + } + _ => {} + } + } + } + #[test] fn test_no_implicit_offsets_in_vm_ir() { // This test ensures that GateLoad and GateStore in VM IR always have explicit offsets.