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.