pr 33
This commit is contained in:
parent
a534b226fb
commit
ca3a5d4d3f
@ -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![]));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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<Param>,
|
||||
pub return_type: Type,
|
||||
pub blocks: Vec<Block>,
|
||||
#[serde(default)]
|
||||
pub local_types: HashMap<u32, Type>,
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<Module>,
|
||||
#[serde(default)]
|
||||
pub field_offsets: HashMap<FieldId, u32>,
|
||||
#[serde(default)]
|
||||
pub field_types: HashMap<FieldId, Type>,
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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<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.
|
||||
// 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<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());
|
||||
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<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 {
|
||||
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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user