This commit is contained in:
Nilton Constantino 2026-01-30 15:52:05 +00:00
parent a534b226fb
commit ca3a5d4d3f
No known key found for this signature in database
10 changed files with 371 additions and 61 deletions

View File

@ -170,7 +170,8 @@ impl<'a> BytecodeEmitter<'a> {
} }
InstrKind::GateBeginPeek | InstrKind::GateEndPeek | InstrKind::GateBeginPeek | InstrKind::GateEndPeek |
InstrKind::GateBeginBorrow | InstrKind::GateEndBorrow | InstrKind::GateBeginBorrow | InstrKind::GateEndBorrow |
InstrKind::GateBeginMutate | InstrKind::GateEndMutate => { InstrKind::GateBeginMutate | InstrKind::GateEndMutate |
InstrKind::GateRetain | InstrKind::GateRelease => {
asm_instrs.push(Asm::Op(OpCode::Nop, vec![])); asm_instrs.push(Asm::Op(OpCode::Nop, vec![]));
} }
} }

View File

@ -240,17 +240,22 @@ mod tests {
0064 Alloc 0064 Alloc
0066 SetLocal U32(1) 0066 SetLocal U32(1)
006C GetLocal U32(1) 006C GetLocal U32(1)
0072 SetLocal U32(2) 0072 Nop
0078 Nop 0074 SetLocal U32(2)
007A GetLocal U32(2) 007A Nop
0080 LoadRef U32(0) 007C GetLocal U32(2)
0086 SetLocal U32(3) 0082 LoadRef U32(0)
008C GetLocal U32(3) 0088 SetLocal U32(3)
0092 PushConst U32(5) 008E GetLocal U32(3)
0098 Add 0094 PushConst U32(5)
009A SetLocal U32(4) 009A Add
00A0 Nop 009C SetLocal U32(4)
00A2 Ret 00A2 Nop
00A4 GetLocal U32(1)
00AA Nop
00AC GetLocal U32(2)
00B2 Nop
00B4 Ret
"#; "#;
assert_eq!(disasm_text, expected_disasm); assert_eq!(disasm_text, expected_disasm);

View File

@ -34,6 +34,7 @@ impl<'a> Lowerer<'a> {
const_pool: ir_core::ConstPool::new(), const_pool: ir_core::ConstPool::new(),
modules: Vec::new(), modules: Vec::new(),
field_offsets, field_offsets,
field_types: HashMap::new(),
}, },
current_function: None, current_function: None,
current_block: None, current_block: None,
@ -106,6 +107,7 @@ impl<'a> Lowerer<'a> {
self.local_vars = vec![HashMap::new()]; self.local_vars = vec![HashMap::new()];
let mut params = Vec::new(); let mut params = Vec::new();
let mut local_types = HashMap::new();
for (i, param) in n.params.iter().enumerate() { for (i, param) in n.params.iter().enumerate() {
let ty = self.lower_type_node(&param.ty); let ty = self.lower_type_node(&param.ty);
params.push(Param { params.push(Param {
@ -113,6 +115,7 @@ impl<'a> Lowerer<'a> {
ty: ty.clone(), ty: ty.clone(),
}); });
self.local_vars[0].insert(param.name.clone(), i as u32); 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 { let ret_ty = if let Some(ret) = &n.ret {
@ -127,6 +130,7 @@ impl<'a> Lowerer<'a> {
params, params,
return_type: ret_ty, return_type: ret_ty,
blocks: Vec::new(), blocks: Vec::new(),
local_types,
}; };
self.current_function = Some(func); self.current_function = Some(func);

View File

@ -3,6 +3,8 @@ use super::ids::FunctionId;
use super::block::Block; use super::block::Block;
use super::types::Type; use super::types::Type;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Param { pub struct Param {
pub name: String, pub name: String,
@ -17,4 +19,6 @@ pub struct Function {
pub params: Vec<Param>, pub params: Vec<Param>,
pub return_type: Type, pub return_type: Type,
pub blocks: Vec<Block>, pub blocks: Vec<Block>,
#[serde(default)]
pub local_types: HashMap<u32, Type>,
} }

View File

@ -47,9 +47,11 @@ mod tests {
], ],
terminator: Terminator::Return, terminator: Terminator::Return,
}], }],
local_types: std::collections::HashMap::new(),
}], }],
}], }],
field_offsets: 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(); let json = serde_json::to_string_pretty(&program).unwrap();
@ -87,12 +89,14 @@ mod tests {
], ],
"terminator": "Return" "terminator": "Return"
} }
] ],
"local_types": {}
} }
] ]
} }
], ],
"field_offsets": {} "field_offsets": {},
"field_types": {}
}"#; }"#;
assert_eq!(json, expected); assert_eq!(json, expected);
} }

View File

@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize};
use super::module::Module; use super::module::Module;
use super::const_pool::ConstPool; use super::const_pool::ConstPool;
use super::ids::FieldId; use super::ids::FieldId;
use super::types::Type;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
@ -10,4 +11,6 @@ pub struct Program {
pub modules: Vec<Module>, pub modules: Vec<Module>,
#[serde(default)] #[serde(default)]
pub field_offsets: HashMap<FieldId, u32>, pub field_offsets: HashMap<FieldId, u32>,
#[serde(default)]
pub field_types: HashMap<FieldId, Type>,
} }

View File

@ -161,6 +161,7 @@ mod tests {
params: vec![], params: vec![],
return_type: Type::Void, return_type: Type::Void,
blocks, blocks,
local_types: HashMap::new(),
} }
} }
@ -172,6 +173,7 @@ mod tests {
functions: vec![func], functions: vec![func],
}], }],
field_offsets: HashMap::new(), field_offsets: HashMap::new(),
field_types: HashMap::new(),
} }
} }

View File

@ -32,7 +32,7 @@ pub struct Label(pub String);
/// The various types of operations that can be performed in the IR. /// 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. /// 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 { pub enum InstrKind {
/// Does nothing. /// Does nothing.
Nop, Nop,
@ -154,6 +154,15 @@ pub enum InstrKind {
GateEndBorrow, GateEndBorrow,
GateBeginMutate, GateBeginMutate,
GateEndMutate, 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). /// List of instructions that are sensitive to Reference Counting (RC).
@ -237,6 +246,8 @@ mod tests {
InstrKind::GateEndBorrow, InstrKind::GateEndBorrow,
InstrKind::GateBeginMutate, InstrKind::GateBeginMutate,
InstrKind::GateEndMutate, InstrKind::GateEndMutate,
InstrKind::GateRetain,
InstrKind::GateRelease,
]; ];
let serialized = serde_json::to_string_pretty(&instructions).unwrap(); let serialized = serde_json::to_string_pretty(&instructions).unwrap();
@ -332,7 +343,9 @@ mod tests {
"GateBeginBorrow", "GateBeginBorrow",
"GateEndBorrow", "GateEndBorrow",
"GateBeginMutate", "GateBeginMutate",
"GateEndMutate" "GateEndMutate",
"GateRetain",
"GateRelease"
]"#; ]"#;
assert_eq!(serialized, expected_json); assert_eq!(serialized, expected_json);
} }
@ -346,7 +359,8 @@ mod tests {
"GateLoad", "GateStore", "Alloc", "GateLoad", "GateStore", "Alloc",
"GateBeginPeek", "GateEndPeek", "GateBeginPeek", "GateEndPeek",
"GateBeginBorrow", "GateEndBorrow", "GateBeginBorrow", "GateEndBorrow",
"GateBeginMutate", "GateEndMutate" "GateBeginMutate", "GateEndMutate",
"GateRetain", "GateRelease"
]; ];
for name in instructions { for name in instructions {

View File

@ -131,9 +131,11 @@ mod tests {
], ],
terminator: ir_core::Terminator::Return, terminator: ir_core::Terminator::Return,
}], }],
local_types: std::collections::HashMap::new(),
}], }],
}], }],
field_offsets: 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"); let vm_module = lower_program(&program).expect("Lowering failed");

View File

@ -1,32 +1,48 @@
use crate::ir_vm; use crate::ir_vm;
use crate::ir_core; use crate::ir_core;
use anyhow::Result; use anyhow::Result;
use std::collections::HashMap;
/// Lowers a Core IR program into a VM IR module. /// Lowers a Core IR program into a VM IR module.
pub fn lower_program(program: &ir_core::Program) -> Result<ir_vm::Module> { pub fn lower_program(program: &ir_core::Program) -> Result<ir_vm::Module> {
// 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. // 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() { if let Some(core_module) = program.modules.first() {
lower_module(core_module, program) lower_module(core_module, program, &function_returns)
} else { } else {
anyhow::bail!("No modules in core program") anyhow::bail!("No modules in core program")
} }
} }
/// Lowers a single Core IR module into a VM IR module. /// 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<ir_vm::Module> { pub fn lower_module(
core_module: &ir_core::Module,
program: &ir_core::Program,
function_returns: &HashMap<ir_core::ids::FunctionId, ir_core::Type>
) -> Result<ir_vm::Module> {
let mut vm_module = ir_vm::Module::new(core_module.name.clone()); let mut vm_module = ir_vm::Module::new(core_module.name.clone());
vm_module.const_pool = program.const_pool.clone(); vm_module.const_pool = program.const_pool.clone();
for core_func in &core_module.functions { 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) Ok(vm_module)
} }
/// Lowers a Core IR function into a VM IR function. /// Lowers a Core IR function into a VM IR function.
pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program) -> Result<ir_vm::Function> { pub fn lower_function(
core_func: &ir_core::Function,
program: &ir_core::Program,
function_returns: &HashMap<ir_core::ids::FunctionId, ir_core::Type>
) -> Result<ir_vm::Function> {
let mut vm_func = ir_vm::Function { let mut vm_func = ir_vm::Function {
id: core_func.id, id: core_func.id,
name: core_func.name.clone(), name: core_func.name.clone(),
@ -38,6 +54,17 @@ pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program)
body: vec![], 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 { for block in &core_func.blocks {
// Core blocks map to labels in the flat VM IR instruction list. // Core blocks map to labels in the flat VM IR instruction list.
vm_func.body.push(ir_vm::Instruction::new( 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, 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 { for instr in &block.instrs {
match instr { match instr {
ir_core::Instr::PushConst(id) => { 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)); 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) => { 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 { vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Call {
func_id: *func_id, func_id: *func_id,
arg_count: *arg_count arg_count: *arg_count
}, None)); }, None));
} }
ir_core::Instr::HostCall(id) => { 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)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Syscall(*id), None));
} }
ir_core::Instr::GetLocal(slot) => { 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)); 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) => { 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)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalStore { slot: *slot }, None));
} }
ir_core::Instr::Pop => { ir_core::Instr::Pop => {
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)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Pop, None));
} }
}
ir_core::Instr::Dup => { 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)); 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 => { ir_core::Instr::Add | ir_core::Instr::Sub | ir_core::Instr::Mul | ir_core::Instr::Div => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Sub, None)); stack_types.pop();
} stack_types.pop();
ir_core::Instr::Mul => { stack_types.push(ir_core::Type::Int); // Assume Int for arithmetic
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Mul, None)); let kind = match instr {
} ir_core::Instr::Add => ir_vm::InstrKind::Add,
ir_core::Instr::Div => { ir_core::Instr::Sub => ir_vm::InstrKind::Sub,
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Div, None)); 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 => { ir_core::Instr::Neg => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Neg, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Neg, None));
} }
ir_core::Instr::Eq => { ir_core::Instr::Eq | ir_core::Instr::Neq | ir_core::Instr::Lt | ir_core::Instr::Lte | ir_core::Instr::Gt | ir_core::Instr::Gte => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Eq, None)); 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 => { ir_core::Instr::And | ir_core::Instr::Or => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Neq, None)); stack_types.pop();
} stack_types.pop();
ir_core::Instr::Lt => { stack_types.push(ir_core::Type::Bool);
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Lt, None)); let kind = match instr {
} ir_core::Instr::And => ir_vm::InstrKind::And,
ir_core::Instr::Lte => { ir_core::Instr::Or => ir_vm::InstrKind::Or,
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Lte, None)); _ => unreachable!(),
} };
ir_core::Instr::Gt => { vm_func.body.push(ir_vm::Instruction::new(kind, None));
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::Not => { 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)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Not, None));
} }
ir_core::Instr::Alloc { ty, slots } => { 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 { vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Alloc {
type_id: ir_vm::TypeId(ty.0), type_id: ir_vm::TypeId(ty.0),
slots: *slots slots: *slots
}, None)); }, None));
} }
ir_core::Instr::BeginPeek { .. } => { ir_core::Instr::BeginPeek { .. } => {
stack_types.pop(); // Pops gate
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginPeek, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginPeek, None));
} }
ir_core::Instr::BeginBorrow { .. } => { ir_core::Instr::BeginBorrow { .. } => {
stack_types.pop(); // Pops gate
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginBorrow, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginBorrow, None));
} }
ir_core::Instr::BeginMutate { .. } => { ir_core::Instr::BeginMutate { .. } => {
stack_types.pop(); // Pops gate
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginMutate, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginMutate, None));
} }
ir_core::Instr::EndPeek => { 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) let offset = program.field_offsets.get(field)
.ok_or_else(|| anyhow::anyhow!("E_LOWER_UNRESOLVED_OFFSET: Field {:?} offset cannot be resolved", 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::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::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 } => { ir_core::Instr::GateStoreField { gate, field, value } => {
let offset = program.field_offsets.get(field) let offset = program.field_offsets.get(field)
.ok_or_else(|| anyhow::anyhow!("E_LOWER_UNRESOLVED_OFFSET: Field {:?} offset cannot be resolved", 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: gate.0 }, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::LocalLoad { slot: value.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)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateStore { offset: *offset }, None));
} }
ir_core::Instr::GateLoadIndex { .. } => { 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"); anyhow::bail!("E_LOWER_UNSUPPORTED: Dynamic HIP index access not supported in v0 lowering");
} }
ir_core::Instr::GateStoreIndex { .. } => { ir_core::Instr::GateStoreIndex { .. } => {
@ -169,6 +293,17 @@ pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program)
match &block.terminator { match &block.terminator {
ir_core::Terminator::Return => { 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)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Ret, None));
} }
ir_core::Terminator::Jump(target) => { 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 } => { ir_core::Terminator::JumpIfFalse { target, else_target } => {
stack_types.pop();
vm_func.body.push(ir_vm::Instruction::new( vm_func.body.push(ir_vm::Instruction::new(
ir_vm::InstrKind::JmpIfFalse(ir_vm::Label(format!("block_{}", target))), ir_vm::InstrKind::JmpIfFalse(ir_vm::Label(format!("block_{}", target))),
None, None,
@ -193,6 +329,20 @@ pub fn lower_function(core_func: &ir_core::Function, program: &ir_core::Program)
Ok(vm_func) 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 { fn lower_type(ty: &ir_core::Type) -> ir_vm::Type {
match ty { match ty {
ir_core::Type::Void => ir_vm::Type::Void, ir_core::Type::Void => ir_vm::Type::Void,
@ -250,9 +400,11 @@ mod tests {
terminator: Terminator::Return, terminator: Terminator::Return,
}, },
], ],
local_types: HashMap::new(),
}], }],
}], }],
field_offsets: 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"); let vm_module = lower_program(&program).expect("Lowering failed");
@ -320,9 +472,11 @@ mod tests {
], ],
terminator: Terminator::Return, terminator: Terminator::Return,
}], }],
local_types: HashMap::new(),
}], }],
}], }],
field_offsets, field_offsets,
field_types: HashMap::new(),
}; };
let vm_module = lower_program(&program).expect("Lowering failed"); let vm_module = lower_program(&program).expect("Lowering failed");
@ -370,9 +524,11 @@ mod tests {
], ],
terminator: Terminator::Return, terminator: Terminator::Return,
}], }],
local_types: HashMap::new(),
}], }],
}], }],
field_offsets: std::collections::HashMap::new(), field_offsets: std::collections::HashMap::new(),
field_types: HashMap::new(),
}; };
let result = lower_program(&program); let result = lower_program(&program);
@ -380,6 +536,121 @@ mod tests {
assert!(result.unwrap_err().to_string().contains("E_LOWER_UNRESOLVED_OFFSET")); 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] #[test]
fn test_no_implicit_offsets_in_vm_ir() { fn test_no_implicit_offsets_in_vm_ir() {
// This test ensures that GateLoad and GateStore in VM IR always have explicit offsets. // This test ensures that GateLoad and GateStore in VM IR always have explicit offsets.