dev/pbs #8
@ -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();
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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"),
|
||||
}
|
||||
|
||||
125
crates/prometeu-compiler/tests/hip_conformance.rs
Normal file
125
crates/prometeu-compiler/tests/hip_conformance.rs
Normal file
@ -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");
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user