dev/pbs #8

Merged
bquarkz merged 74 commits from dev/pbs into master 2026-02-03 15:28:31 +00:00
4 changed files with 270 additions and 23 deletions
Showing only changes of commit 926ad2a807 - Show all commits

View File

@ -242,25 +242,133 @@ mod tests {
0074 GetLocal U32(1) 0074 GetLocal U32(1)
007A GateRetain 007A GateRetain
007C SetLocal U32(2) 007C SetLocal U32(2)
0082 GateBeginMutate 0082 GetLocal U32(2)
0084 GetLocal U32(2) 0088 GateRetain
008A GateLoad U32(0) 008A GateBeginMutate
0090 SetLocal U32(3) 008C GetLocal U32(2)
0096 GetLocal U32(3) 0092 GateRetain
009C PushConst U32(5) 0094 GateLoad U32(0)
00A2 Add 009A SetLocal U32(3)
00A4 SetLocal U32(4) 00A0 GetLocal U32(3)
00AA GateEndMutate 00A6 PushConst U32(5)
00AC GetLocal U32(1) 00AC Add
00B2 GateRelease 00AE SetLocal U32(4)
00B4 GetLocal U32(2) 00B4 GateEndMutate
00BA GateRelease 00B6 GateRelease
00BC Ret 00B8 GetLocal U32(1)
00BE GateRelease
00C0 GetLocal U32(2)
00C6 GateRelease
00C8 Ret
"#; "#;
assert_eq!(disasm_text, expected_disasm); 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] #[test]
fn test_project_root_and_entry_resolution() { fn test_project_root_and_entry_resolution() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();

View File

@ -170,6 +170,7 @@ pub enum InstrKind {
pub const RC_SENSITIVE_OPS: &[&str] = &[ pub const RC_SENSITIVE_OPS: &[&str] = &[
"LocalStore", "LocalStore",
"GateStore", "GateStore",
"GateLoad",
"Pop", "Pop",
"Ret", "Ret",
"FrameSync", "FrameSync",
@ -373,7 +374,7 @@ mod tests {
// Required by PR-06: Documentation test or unit assertion that the RC-sensitive list exists // 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"); 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 { for op in expected {
assert!(RC_SENSITIVE_OPS.contains(&op), "RC-sensitive list must contain {}", op); assert!(RC_SENSITIVE_OPS.contains(&op), "RC-sensitive list must contain {}", op);
} }

View File

@ -221,26 +221,32 @@ pub fn lower_function(
slots: *slots slots: *slots
}, None)); }, None));
} }
ir_core::Instr::BeginPeek { .. } => { ir_core::Instr::BeginPeek { gate } => {
stack_types.pop(); // Pops 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)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginPeek, None));
} }
ir_core::Instr::BeginBorrow { .. } => { ir_core::Instr::BeginBorrow { gate } => {
stack_types.pop(); // Pops 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)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginBorrow, None));
} }
ir_core::Instr::BeginMutate { .. } => { ir_core::Instr::BeginMutate { gate } => {
stack_types.pop(); // Pops 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)); vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginMutate, None));
} }
ir_core::Instr::EndPeek => { 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::GateEndPeek, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
} }
ir_core::Instr::EndBorrow => { 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::GateEndBorrow, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
} }
ir_core::Instr::EndMutate => { 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::GateEndMutate, None));
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
} }
ir_core::Instr::GateLoadField { gate, field } => { ir_core::Instr::GateLoadField { gate, field } => {
let offset = program.field_offsets.get(field) let offset = program.field_offsets.get(field)
@ -250,6 +256,7 @@ pub fn lower_function(
stack_types.push(field_ty.clone()); 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::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::GateLoad { offset: *offset }, None));
if is_gate_type(&field_ty) { 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 // 1. Release old value in HIP if it was a gate
if is_gate_type(&field_ty) { 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::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::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::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::GateRetain, 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 // 2. Retain new value if it's a gate
@ -491,16 +500,20 @@ mod tests {
// GateStore 100 (offset) // GateStore 100 (offset)
// Ret // Ret
assert_eq!(func.body.len(), 7); assert_eq!(func.body.len(), 9);
match &func.body[1].kind { match &func.body[1].kind {
ir_vm::InstrKind::LocalLoad { slot } => assert_eq!(*slot, 0), ir_vm::InstrKind::LocalLoad { slot } => assert_eq!(*slot, 0),
_ => panic!("Expected LocalLoad 0"), _ => panic!("Expected LocalLoad 0"),
} }
match &func.body[2].kind { 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), ir_vm::InstrKind::GateLoad { offset } => assert_eq!(*offset, 100),
_ => panic!("Expected GateLoad 100"), _ => panic!("Expected GateLoad 100"),
} }
match &func.body[5].kind { match &func.body[7].kind {
ir_vm::InstrKind::GateStore { offset } => assert_eq!(*offset, 100), ir_vm::InstrKind::GateStore { offset } => assert_eq!(*offset, 100),
_ => panic!("Expected GateStore 100"), _ => panic!("Expected GateStore 100"),
} }

View 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");
}