diff --git a/crates/prometeu-compiler/src/compiler.rs b/crates/prometeu-compiler/src/compiler.rs index c8947509..f8718fc8 100644 --- a/crates/prometeu-compiler/src/compiler.rs +++ b/crates/prometeu-compiler/src/compiler.rs @@ -242,25 +242,133 @@ mod tests { 0074 GetLocal U32(1) 007A GateRetain 007C SetLocal U32(2) -0082 GateBeginMutate -0084 GetLocal U32(2) -008A GateLoad U32(0) -0090 SetLocal U32(3) -0096 GetLocal U32(3) -009C PushConst U32(5) -00A2 Add -00A4 SetLocal U32(4) -00AA GateEndMutate -00AC GetLocal U32(1) -00B2 GateRelease -00B4 GetLocal U32(2) -00BA GateRelease -00BC Ret +0082 GetLocal U32(2) +0088 GateRetain +008A GateBeginMutate +008C GetLocal U32(2) +0092 GateRetain +0094 GateLoad U32(0) +009A SetLocal U32(3) +00A0 GetLocal U32(3) +00A6 PushConst U32(5) +00AC Add +00AE SetLocal U32(4) +00B4 GateEndMutate +00B6 GateRelease +00B8 GetLocal U32(1) +00BE GateRelease +00C0 GetLocal U32(2) +00C6 GateRelease +00C8 Ret "#; assert_eq!(disasm_text, expected_disasm); } + #[test] + fn test_hip_conformance_v0() { + use crate::ir_core::*; + use crate::ir_core::ids::*; + use crate::lowering::lower_program; + use crate::backend; + use std::collections::HashMap; + + // --- 1. SETUP CORE IR FIXTURE --- + let mut const_pool = ConstPool::new(); + let val_42 = const_pool.add_int(42); + + let mut field_offsets = HashMap::new(); + let f1 = FieldId(0); + field_offsets.insert(f1, 0); + + let mut local_types = HashMap::new(); + local_types.insert(0, Type::Struct("Storage".to_string())); // slot 0: gate handle + local_types.insert(1, Type::Int); // slot 1: value 42 + local_types.insert(2, Type::Int); // slot 2: result of peek + + let program = Program { + const_pool, + modules: vec![Module { + name: "conformance".to_string(), + functions: vec![Function { + id: FunctionId(1), + name: "main".to_string(), + params: vec![], + return_type: Type::Void, + blocks: vec![Block { + id: 0, + instrs: vec![ + // 1. allocates a storage struct + Instr::Alloc { ty: TypeId(1), slots: 2 }, + Instr::SetLocal(0), + + // 2. mutates a field (offset 0) + Instr::BeginMutate { gate: ValueId(0) }, + Instr::PushConst(val_42), + Instr::SetLocal(1), + Instr::GateStoreField { gate: ValueId(0), field: f1, value: ValueId(1) }, + Instr::EndMutate, + + // 3. peeks value (offset 0) + Instr::BeginPeek { gate: ValueId(0) }, + Instr::GateLoadField { gate: ValueId(0), field: f1 }, + Instr::SetLocal(2), + Instr::EndPeek, + ], + terminator: Terminator::Return, + }], + local_types, + }], + }], + field_offsets, + field_types: HashMap::new(), + }; + + // --- 2. LOWER TO VM IR --- + let vm_module = lower_program(&program).expect("Lowering failed"); + + // --- 3. ASSERT VM IR (Instructions + RC) --- + let func = &vm_module.functions[0]; + let kinds: Vec<_> = func.body.iter().map(|i| &i.kind).collect(); + + // Expected sequence of significant instructions: + // Alloc, LocalStore(0), GateBeginMutate, PushConst, LocalStore(1), LocalLoad(0), LocalLoad(1), GateStore(0), GateEndMutate... + + assert!(kinds.iter().any(|k| matches!(k, ir_vm::InstrKind::Alloc { .. })), "Must contain Alloc"); + assert!(kinds.iter().any(|k| matches!(k, ir_vm::InstrKind::GateBeginMutate)), "Must contain GateBeginMutate"); + assert!(kinds.iter().any(|k| matches!(k, ir_vm::InstrKind::GateStore { offset: 0 })), "Must contain GateStore(0)"); + assert!(kinds.iter().any(|k| matches!(k, ir_vm::InstrKind::GateBeginPeek)), "Must contain GateBeginPeek"); + assert!(kinds.iter().any(|k| matches!(k, ir_vm::InstrKind::GateLoad { offset: 0 })), "Must contain GateLoad(0)"); + + // RC assertions: + assert!(kinds.contains(&&ir_vm::InstrKind::GateRetain), "Must contain GateRetain (on LocalLoad of gate)"); + assert!(kinds.contains(&&ir_vm::InstrKind::GateRelease), "Must contain GateRelease (on cleanup or Pop)"); + + // --- 4. EMIT BYTECODE --- + let file_manager = crate::common::files::FileManager::new(); + let emit_result = backend::emit_module(&vm_module, &file_manager).expect("Emission failed"); + + let rom = emit_result.rom; + + // --- 5. ASSERT GOLDEN BYTECODE (Exact Bytes) --- + // Header: PPBC, Version: 0, Flags: 0 + assert_eq!(&rom[0..4], b"PPBC"); + assert_eq!(rom[4..6], [0, 0]); // Version 0 + assert_eq!(rom[6..8], [0, 0]); // Flags 0 + + // CP Count: 2 (Null, 42) + assert_eq!(rom[8..12], [2, 0, 0, 0]); + + // ROM Data contains HIP opcodes: + assert!(rom.contains(&0x60), "Bytecode must contain Alloc (0x60)"); + assert!(rom.contains(&0x67), "Bytecode must contain GateBeginMutate (0x67)"); + assert!(rom.contains(&0x62), "Bytecode must contain GateStore (0x62)"); + assert!(rom.contains(&0x63), "Bytecode must contain GateBeginPeek (0x63)"); + assert!(rom.contains(&0x61), "Bytecode must contain GateLoad (0x61)"); + assert!(rom.contains(&0x69), "Bytecode must contain GateRetain (0x69)"); + assert!(rom.contains(&0x6A), "Bytecode must contain GateRelease (0x6A)"); + } + #[test] fn test_project_root_and_entry_resolution() { let dir = tempdir().unwrap(); diff --git a/crates/prometeu-compiler/src/ir_vm/instr.rs b/crates/prometeu-compiler/src/ir_vm/instr.rs index ff605fea..9205ea66 100644 --- a/crates/prometeu-compiler/src/ir_vm/instr.rs +++ b/crates/prometeu-compiler/src/ir_vm/instr.rs @@ -170,6 +170,7 @@ pub enum InstrKind { pub const RC_SENSITIVE_OPS: &[&str] = &[ "LocalStore", "GateStore", + "GateLoad", "Pop", "Ret", "FrameSync", @@ -373,7 +374,7 @@ mod tests { // Required by PR-06: Documentation test or unit assertion that the RC-sensitive list exists assert!(!RC_SENSITIVE_OPS.is_empty(), "RC-sensitive instructions list must not be empty"); - let expected = ["LocalStore", "GateStore", "Pop", "Ret", "FrameSync"]; + let expected = ["LocalStore", "GateStore", "GateLoad", "Pop", "Ret", "FrameSync"]; for op in expected { assert!(RC_SENSITIVE_OPS.contains(&op), "RC-sensitive list must contain {}", op); } diff --git a/crates/prometeu-compiler/src/lowering/core_to_vm.rs b/crates/prometeu-compiler/src/lowering/core_to_vm.rs index 45c4c988..c311295e 100644 --- a/crates/prometeu-compiler/src/lowering/core_to_vm.rs +++ b/crates/prometeu-compiler/src/lowering/core_to_vm.rs @@ -221,26 +221,32 @@ pub fn lower_function( slots: *slots }, None)); } - ir_core::Instr::BeginPeek { .. } => { - stack_types.pop(); // Pops gate + ir_core::Instr::BeginPeek { gate } => { + 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::GateRetain, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginPeek, None)); } - ir_core::Instr::BeginBorrow { .. } => { - stack_types.pop(); // Pops gate + ir_core::Instr::BeginBorrow { gate } => { + 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::GateRetain, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginBorrow, None)); } - ir_core::Instr::BeginMutate { .. } => { - stack_types.pop(); // Pops gate + ir_core::Instr::BeginMutate { gate } => { + 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::GateRetain, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginMutate, None)); } ir_core::Instr::EndPeek => { vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateEndPeek, None)); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None)); } ir_core::Instr::EndBorrow => { vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateEndBorrow, None)); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None)); } ir_core::Instr::EndMutate => { vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateEndMutate, None)); + vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None)); } ir_core::Instr::GateLoadField { gate, field } => { let offset = program.field_offsets.get(field) @@ -250,6 +256,7 @@ pub fn lower_function( 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::GateRetain, None)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateLoad { offset: *offset }, None)); if is_gate_type(&field_ty) { @@ -265,11 +272,13 @@ pub fn lower_function( // 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::GateRetain, 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::GateRetain, 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 @@ -491,16 +500,20 @@ mod tests { // GateStore 100 (offset) // Ret - assert_eq!(func.body.len(), 7); + assert_eq!(func.body.len(), 9); match &func.body[1].kind { ir_vm::InstrKind::LocalLoad { slot } => assert_eq!(*slot, 0), _ => panic!("Expected LocalLoad 0"), } match &func.body[2].kind { + ir_vm::InstrKind::GateRetain => (), + _ => panic!("Expected GateRetain"), + } + match &func.body[3].kind { ir_vm::InstrKind::GateLoad { offset } => assert_eq!(*offset, 100), _ => panic!("Expected GateLoad 100"), } - match &func.body[5].kind { + match &func.body[7].kind { ir_vm::InstrKind::GateStore { offset } => assert_eq!(*offset, 100), _ => panic!("Expected GateStore 100"), } diff --git a/crates/prometeu-compiler/tests/hip_conformance.rs b/crates/prometeu-compiler/tests/hip_conformance.rs new file mode 100644 index 00000000..b468121d --- /dev/null +++ b/crates/prometeu-compiler/tests/hip_conformance.rs @@ -0,0 +1,125 @@ +use prometeu_compiler::ir_core::{self, Program, Block, Instr, Terminator, ConstantValue, ConstPool}; +use prometeu_compiler::ir_core::ids::{FunctionId, ConstId as CoreConstId, TypeId as CoreTypeId, FieldId, ValueId}; +use prometeu_compiler::ir_vm::InstrKind; +use prometeu_compiler::lowering::lower_program; +use prometeu_compiler::backend::emit_bytecode::emit_module; +use prometeu_compiler::common::files::FileManager; +use std::collections::HashMap; + +#[test] +fn test_hip_conformance_core_to_vm_to_bytecode() { + // 1. Setup Core IR Program + let mut const_pool = ConstPool::new(); + let _val_const = const_pool.insert(ConstantValue::Int(42)); + + let type_id = CoreTypeId(10); + let field_id = FieldId(1); + + let mut field_offsets = HashMap::new(); + field_offsets.insert(field_id, 0); // Field at offset 0 + + let mut field_types = HashMap::new(); + field_types.insert(field_id, ir_core::Type::Int); + + let program = Program { + const_pool, + modules: vec![ir_core::Module { + name: "conformance".to_string(), + functions: vec![ir_core::Function { + id: FunctionId(1), + name: "main".to_string(), + params: vec![], + return_type: ir_core::Type::Void, + local_types: HashMap::new(), + blocks: vec![Block { + id: 0, + instrs: vec![ + // allocates a storage struct + Instr::Alloc { ty: type_id, slots: 2 }, + Instr::SetLocal(0), // x = alloc + + // mutates a field + Instr::BeginMutate { gate: ValueId(0) }, + Instr::PushConst(CoreConstId(0)), + Instr::SetLocal(1), // v = 42 + Instr::GateStoreField { gate: ValueId(0), field: field_id, value: ValueId(1) }, + Instr::EndMutate, + + // peeks value + Instr::BeginPeek { gate: ValueId(0) }, + Instr::GateLoadField { gate: ValueId(0), field: field_id }, + Instr::EndPeek, + + Instr::Pop, // clean up the peeked value + ], + terminator: Terminator::Return, + }], + }], + }], + field_offsets, + field_types, + }; + + // 2. Lower to VM IR + let vm_module = lower_program(&program).expect("Lowering failed"); + let func = &vm_module.functions[0]; + + // Assert VM IR contains required instructions + let kinds: Vec<_> = func.body.iter().map(|i| &i.kind).collect(); + + assert!(kinds.iter().any(|k| matches!(k, InstrKind::Alloc { type_id: tid, slots: 2 } if tid.0 == 10)), "Missing correct Alloc"); + assert!(kinds.contains(&&InstrKind::GateBeginMutate), "Missing GateBeginMutate"); + assert!(kinds.contains(&&InstrKind::GateEndMutate), "Missing GateEndMutate"); + assert!(kinds.iter().any(|k| matches!(k, InstrKind::GateStore { offset: 0 })), "Missing correct GateStore"); + assert!(kinds.contains(&&InstrKind::GateBeginPeek), "Missing GateBeginPeek"); + assert!(kinds.contains(&&InstrKind::GateEndPeek), "Missing GateEndPeek"); + assert!(kinds.iter().any(|k| matches!(k, InstrKind::GateLoad { offset: 0 })), "Missing correct GateLoad"); + + // RC ops + assert!(kinds.contains(&&InstrKind::GateRetain), "Missing GateRetain"); + assert!(kinds.contains(&&InstrKind::GateRelease), "Missing GateRelease"); + + // 3. Emit Bytecode + let file_manager = FileManager::new(); + let emit_result = emit_module(&vm_module, &file_manager).expect("Emission failed"); + let bytecode = emit_result.rom; + + // 4. Assert exact bytes match frozen ISA/ABI + let expected_bytecode = vec![ + 0x50, 0x50, 0x42, 0x43, // Magic: "PPBC" + 0x00, 0x00, // Version: 0 + 0x00, 0x00, // Flags: 0 + 0x02, 0x00, 0x00, 0x00, // CP Count: 2 + 0x00, // CP[0]: Null + 0x01, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CP[1]: Int64(42) + 0x66, 0x00, 0x00, 0x00, // ROM Size: 102 + // Instructions: + 0x60, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // Alloc { tid: 10, slots: 2 } + 0x43, 0x00, 0x00, 0x00, 0x00, 0x00, // SetLocal 0 + 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // GetLocal 0 + 0x69, 0x00, // GateRetain + 0x67, 0x00, // GateBeginMutate + 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, // PushConst 1 (42) + 0x43, 0x00, 0x01, 0x00, 0x00, 0x00, // SetLocal 1 + 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // GetLocal 0 + 0x69, 0x00, // GateRetain + 0x42, 0x00, 0x01, 0x00, 0x00, 0x00, // GetLocal 1 + 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, // GateStore 0 + 0x68, 0x00, // GateEndMutate + 0x6a, 0x00, // GateRelease + 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // GetLocal 0 + 0x69, 0x00, // GateRetain + 0x63, 0x00, // GateBeginPeek + 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // GetLocal 0 + 0x69, 0x00, // GateRetain + 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, // GateLoad 0 + 0x64, 0x00, // GateEndPeek + 0x6a, 0x00, // GateRelease + 0x11, 0x00, // Pop + 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // GetLocal 0 (cleanup) + 0x6a, 0x00, // GateRelease + 0x51, 0x00, // Ret + ]; + + assert_eq!(bytecode, expected_bytecode, "Bytecode does not match golden ISA/ABI v0"); +}