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)
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();

View File

@ -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);
}

View File

@ -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"),
}

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