This commit is contained in:
Nilton Constantino 2026-01-30 16:07:45 +00:00
parent ca3a5d4d3f
commit c797be9287
No known key found for this signature in database
6 changed files with 173 additions and 108 deletions

View File

@ -41,7 +41,7 @@ pub fn disasm(rom: &[u8]) -> Result<Vec<Instr>, String> {
match opcode { match opcode {
OpCode::PushConst | OpCode::PushI32 | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue OpCode::PushConst | OpCode::PushI32 | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue
| OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal | OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal
| OpCode::PopN | OpCode::Syscall | OpCode::LoadRef | OpCode::StoreRef => { | OpCode::PopN | OpCode::Syscall | OpCode::GateLoad | OpCode::GateStore => {
let v = read_u32_le(&mut cursor).map_err(|e| e.to_string())?; let v = read_u32_le(&mut cursor).map_err(|e| e.to_string())?;
operands.push(DisasmOperand::U32(v)); operands.push(DisasmOperand::U32(v));
} }
@ -58,11 +58,11 @@ pub fn disasm(rom: &[u8]) -> Result<Vec<Instr>, String> {
cursor.read_exact(&mut b_buf).map_err(|e| e.to_string())?; cursor.read_exact(&mut b_buf).map_err(|e| e.to_string())?;
operands.push(DisasmOperand::Bool(b_buf[0] != 0)); operands.push(DisasmOperand::Bool(b_buf[0] != 0));
} }
OpCode::Call => { OpCode::Call | OpCode::Alloc => {
let addr = read_u32_le(&mut cursor).map_err(|e| e.to_string())?; let v1 = read_u32_le(&mut cursor).map_err(|e| e.to_string())?;
let args = read_u32_le(&mut cursor).map_err(|e| e.to_string())?; let v2 = read_u32_le(&mut cursor).map_err(|e| e.to_string())?;
operands.push(DisasmOperand::U32(addr)); operands.push(DisasmOperand::U32(v1));
operands.push(DisasmOperand::U32(args)); operands.push(DisasmOperand::U32(v2));
} }
_ => {} _ => {}
} }

View File

@ -153,19 +153,46 @@ pub enum OpCode {
/// Ends the current local scope, discarding its local variables. /// Ends the current local scope, discarding its local variables.
PopScope = 0x53, PopScope = 0x53,
// --- 6.7 Heap --- // --- 6.7 HIP (Heap Interface Protocol) ---
/// Allocates `size` slots on the heap. /// Allocates `slots` slots on the heap with the given `type_id`.
/// Stack: [size] -> [reference] /// Operands: type_id (u32), slots (u32)
/// Stack: [] -> [gate]
Alloc = 0x60, Alloc = 0x60,
/// Reads a value from the heap at `reference + offset`. /// Reads a value from the heap at `gate + offset`.
/// Operand: offset (u32) /// Operand: offset (u32)
/// Stack: [reference] -> [value] /// Stack: [gate] -> [value]
LoadRef = 0x61, GateLoad = 0x61,
/// Writes a value to the heap at `reference + offset`. /// Writes a value to the heap at `gate + offset`.
/// Operand: offset (u32) /// Operand: offset (u32)
/// Stack: [reference, value] -> [] /// Stack: [gate, value] -> []
StoreRef = 0x62, GateStore = 0x62,
/// Marks the beginning of a Peek scope for a gate.
/// Stack: [gate] -> [gate]
GateBeginPeek = 0x63,
/// Marks the end of a Peek scope for a gate.
/// Stack: [gate] -> [gate]
GateEndPeek = 0x64,
/// Marks the beginning of a Borrow scope for a gate.
/// Stack: [gate] -> [gate]
GateBeginBorrow = 0x65,
/// Marks the end of a Borrow scope for a gate.
/// Stack: [gate] -> [gate]
GateEndBorrow = 0x66,
/// Marks the beginning of a Mutate scope for a gate.
/// Stack: [gate] -> [gate]
GateBeginMutate = 0x67,
/// Marks the end of a Mutate scope for a gate.
/// Stack: [gate] -> [gate]
GateEndMutate = 0x68,
/// Increments the reference count of a gate.
/// Stack: [gate] -> [gate]
GateRetain = 0x69,
/// Decrements the reference count of a gate.
/// Stack: [gate] -> []
GateRelease = 0x6A,
// --- 6.8 Peripherals and System --- // --- 6.8 Peripherals and System ---
@ -226,8 +253,16 @@ impl TryFrom<u16> for OpCode {
0x52 => Ok(OpCode::PushScope), 0x52 => Ok(OpCode::PushScope),
0x53 => Ok(OpCode::PopScope), 0x53 => Ok(OpCode::PopScope),
0x60 => Ok(OpCode::Alloc), 0x60 => Ok(OpCode::Alloc),
0x61 => Ok(OpCode::LoadRef), 0x61 => Ok(OpCode::GateLoad),
0x62 => Ok(OpCode::StoreRef), 0x62 => Ok(OpCode::GateStore),
0x63 => Ok(OpCode::GateBeginPeek),
0x64 => Ok(OpCode::GateEndPeek),
0x65 => Ok(OpCode::GateBeginBorrow),
0x66 => Ok(OpCode::GateEndBorrow),
0x67 => Ok(OpCode::GateBeginMutate),
0x68 => Ok(OpCode::GateEndMutate),
0x69 => Ok(OpCode::GateRetain),
0x6A => Ok(OpCode::GateRelease),
0x70 => Ok(OpCode::Syscall), 0x70 => Ok(OpCode::Syscall),
0x80 => Ok(OpCode::FrameSync), 0x80 => Ok(OpCode::FrameSync),
_ => Err(format!("Invalid OpCode: 0x{:04X}", value)), _ => Err(format!("Invalid OpCode: 0x{:04X}", value)),
@ -283,10 +318,76 @@ impl OpCode {
OpCode::PushScope => 3, OpCode::PushScope => 3,
OpCode::PopScope => 3, OpCode::PopScope => 3,
OpCode::Alloc => 10, OpCode::Alloc => 10,
OpCode::LoadRef => 3, OpCode::GateLoad => 3,
OpCode::StoreRef => 3, OpCode::GateStore => 3,
OpCode::GateBeginPeek => 1,
OpCode::GateEndPeek => 1,
OpCode::GateBeginBorrow => 1,
OpCode::GateEndBorrow => 1,
OpCode::GateBeginMutate => 1,
OpCode::GateEndMutate => 1,
OpCode::GateRetain => 1,
OpCode::GateRelease => 1,
OpCode::Syscall => 1, OpCode::Syscall => 1,
OpCode::FrameSync => 1, OpCode::FrameSync => 1,
} }
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::asm::{assemble, Asm, Operand};
#[test]
fn test_opcode_stability() {
// Normative test: ensures opcode numeric values are frozen.
assert_eq!(OpCode::Nop as u16, 0x00);
assert_eq!(OpCode::PushConst as u16, 0x10);
assert_eq!(OpCode::Alloc as u16, 0x60);
assert_eq!(OpCode::GateLoad as u16, 0x61);
assert_eq!(OpCode::GateStore as u16, 0x62);
assert_eq!(OpCode::GateBeginPeek as u16, 0x63);
assert_eq!(OpCode::GateEndPeek as u16, 0x64);
assert_eq!(OpCode::GateBeginBorrow as u16, 0x65);
assert_eq!(OpCode::GateEndBorrow as u16, 0x66);
assert_eq!(OpCode::GateBeginMutate as u16, 0x67);
assert_eq!(OpCode::GateEndMutate as u16, 0x68);
assert_eq!(OpCode::GateRetain as u16, 0x69);
assert_eq!(OpCode::GateRelease as u16, 0x6A);
assert_eq!(OpCode::FrameSync as u16, 0x80);
}
#[test]
fn test_hip_bytecode_golden() {
// Golden test for HIP opcodes and their encodings.
// Rule: All multi-byte operands are little-endian.
let instructions = vec![
Asm::Op(OpCode::Alloc, vec![Operand::U32(0x11223344), Operand::U32(0x55667788)]),
Asm::Op(OpCode::GateLoad, vec![Operand::U32(0xAABBCCDD)]),
Asm::Op(OpCode::GateStore, vec![Operand::U32(0x11223344)]),
Asm::Op(OpCode::GateBeginPeek, vec![]),
Asm::Op(OpCode::GateRetain, vec![]),
Asm::Op(OpCode::GateRelease, vec![]),
];
let bytes = assemble(&instructions).unwrap();
let mut expected = Vec::new();
// Alloc (0x60, 0x00) + type_id (44 33 22 11) + slots (88 77 66 55)
expected.extend_from_slice(&[0x60, 0x00, 0x44, 0x33, 0x22, 0x11, 0x88, 0x77, 0x66, 0x55]);
// GateLoad (0x61, 0x00) + offset (DD CC BB AA)
expected.extend_from_slice(&[0x61, 0x00, 0xDD, 0xCC, 0xBB, 0xAA]);
// GateStore (0x62, 0x00) + offset (44 33 22 11)
expected.extend_from_slice(&[0x62, 0x00, 0x44, 0x33, 0x22, 0x11]);
// GateBeginPeek (0x63, 0x00)
expected.extend_from_slice(&[0x63, 0x00]);
// GateRetain (0x69, 0x00)
expected.extend_from_slice(&[0x69, 0x00]);
// GateRelease (0x6A, 0x00)
expected.extend_from_slice(&[0x6A, 0x00]);
assert_eq!(bytes, expected);
}
}

View File

@ -161,19 +161,23 @@ impl<'a> BytecodeEmitter<'a> {
asm_instrs.push(Asm::Op(OpCode::Syscall, vec![Operand::U32(*id)])); asm_instrs.push(Asm::Op(OpCode::Syscall, vec![Operand::U32(*id)]));
} }
InstrKind::FrameSync => asm_instrs.push(Asm::Op(OpCode::FrameSync, vec![])), InstrKind::FrameSync => asm_instrs.push(Asm::Op(OpCode::FrameSync, vec![])),
InstrKind::Alloc { .. } => asm_instrs.push(Asm::Op(OpCode::Alloc, vec![])), InstrKind::Alloc { type_id, slots } => {
asm_instrs.push(Asm::Op(OpCode::Alloc, vec![Operand::U32(type_id.0), Operand::U32(*slots)]));
}
InstrKind::GateLoad { offset } => { InstrKind::GateLoad { offset } => {
asm_instrs.push(Asm::Op(OpCode::LoadRef, vec![Operand::U32(*offset)])); asm_instrs.push(Asm::Op(OpCode::GateLoad, vec![Operand::U32(*offset)]));
} }
InstrKind::GateStore { offset } => { InstrKind::GateStore { offset } => {
asm_instrs.push(Asm::Op(OpCode::StoreRef, vec![Operand::U32(*offset)])); asm_instrs.push(Asm::Op(OpCode::GateStore, vec![Operand::U32(*offset)]));
}
InstrKind::GateBeginPeek | InstrKind::GateEndPeek |
InstrKind::GateBeginBorrow | InstrKind::GateEndBorrow |
InstrKind::GateBeginMutate | InstrKind::GateEndMutate |
InstrKind::GateRetain | InstrKind::GateRelease => {
asm_instrs.push(Asm::Op(OpCode::Nop, vec![]));
} }
InstrKind::GateBeginPeek => asm_instrs.push(Asm::Op(OpCode::GateBeginPeek, vec![])),
InstrKind::GateEndPeek => asm_instrs.push(Asm::Op(OpCode::GateEndPeek, vec![])),
InstrKind::GateBeginBorrow => asm_instrs.push(Asm::Op(OpCode::GateBeginBorrow, vec![])),
InstrKind::GateEndBorrow => asm_instrs.push(Asm::Op(OpCode::GateEndBorrow, vec![])),
InstrKind::GateBeginMutate => asm_instrs.push(Asm::Op(OpCode::GateBeginMutate, vec![])),
InstrKind::GateEndMutate => asm_instrs.push(Asm::Op(OpCode::GateEndMutate, vec![])),
InstrKind::GateRetain => asm_instrs.push(Asm::Op(OpCode::GateRetain, vec![])),
InstrKind::GateRelease => asm_instrs.push(Asm::Op(OpCode::GateRelease, vec![])),
} }
let end_idx = asm_instrs.len(); let end_idx = asm_instrs.len();

View File

@ -158,10 +158,10 @@ mod tests {
let opcodes: Vec<_> = instrs.iter().map(|i| i.opcode).collect(); let opcodes: Vec<_> = instrs.iter().map(|i| i.opcode).collect();
assert!(opcodes.contains(&OpCode::Alloc)); assert!(opcodes.contains(&OpCode::Alloc));
assert!(opcodes.contains(&OpCode::LoadRef)); assert!(opcodes.contains(&OpCode::GateLoad));
// After PR-05, BeginMutate/EndMutate map to GateLoad/Nop for now // After PR-09, BeginMutate/EndMutate map to their respective opcodes
// because VM is feature-frozen. StoreRef is removed from lowering. assert!(opcodes.contains(&OpCode::GateBeginMutate));
assert!(opcodes.contains(&OpCode::Nop)); assert!(opcodes.contains(&OpCode::GateEndMutate));
assert!(opcodes.contains(&OpCode::Add)); assert!(opcodes.contains(&OpCode::Add));
assert!(opcodes.contains(&OpCode::Ret)); assert!(opcodes.contains(&OpCode::Ret));
} }
@ -237,25 +237,25 @@ mod tests {
0052 SetLocal U32(1) 0052 SetLocal U32(1)
0058 Jmp U32(100) 0058 Jmp U32(100)
005E Jmp U32(100) 005E Jmp U32(100)
0064 Alloc 0064 Alloc U32(2) U32(1)
0066 SetLocal U32(1) 006E SetLocal U32(1)
006C GetLocal U32(1) 0074 GetLocal U32(1)
0072 Nop 007A GateRetain
0074 SetLocal U32(2) 007C SetLocal U32(2)
007A Nop 0082 GateBeginMutate
007C GetLocal U32(2) 0084 GetLocal U32(2)
0082 LoadRef U32(0) 008A GateLoad U32(0)
0088 SetLocal U32(3) 0090 SetLocal U32(3)
008E GetLocal U32(3) 0096 GetLocal U32(3)
0094 PushConst U32(5) 009C PushConst U32(5)
009A Add 00A2 Add
009C SetLocal U32(4) 00A4 SetLocal U32(4)
00A2 Nop 00AA GateEndMutate
00A4 GetLocal U32(1) 00AC GetLocal U32(1)
00AA Nop 00B2 GateRelease
00AC GetLocal U32(2) 00B4 GetLocal U32(2)
00B2 Nop 00BA GateRelease
00B4 Ret 00BC Ret
"#; "#;
assert_eq!(disasm_text, expected_disasm); assert_eq!(disasm_text, expected_disasm);

View File

@ -544,15 +544,16 @@ impl VirtualMachine {
self.operand_stack.truncate(frame.scope_stack_base); self.operand_stack.truncate(frame.scope_stack_base);
} }
OpCode::Alloc => { OpCode::Alloc => {
// Allocates 'size' values on the heap and pushes a reference to the stack // Allocates 'slots' values on the heap and pushes a reference to the stack
let size = self.read_u32()? as usize; let _type_id = self.read_u32()?;
let slots = self.read_u32()? as usize;
let ref_idx = self.heap.len(); let ref_idx = self.heap.len();
for _ in 0..size { for _ in 0..slots {
self.heap.push(Value::Null); self.heap.push(Value::Null);
} }
self.push(Value::Ref(ref_idx)); self.push(Value::Ref(ref_idx));
} }
OpCode::LoadRef => { OpCode::GateLoad => {
// Reads a value from a heap reference at a specific offset // Reads a value from a heap reference at a specific offset
let offset = self.read_u32()? as usize; let offset = self.read_u32()? as usize;
let ref_val = self.pop()?; let ref_val = self.pop()?;
@ -560,10 +561,10 @@ impl VirtualMachine {
let val = self.heap.get(base + offset).cloned().ok_or("Invalid heap access")?; let val = self.heap.get(base + offset).cloned().ok_or("Invalid heap access")?;
self.push(val); self.push(val);
} else { } else {
return Err("Expected reference for LOAD_REF".into()); return Err("Expected reference for GATE_LOAD".into());
} }
} }
OpCode::StoreRef => { OpCode::GateStore => {
// Writes a value to a heap reference at a specific offset // Writes a value to a heap reference at a specific offset
let offset = self.read_u32()? as usize; let offset = self.read_u32()? as usize;
let val = self.pop()?; let val = self.pop()?;
@ -574,9 +575,18 @@ impl VirtualMachine {
} }
self.heap[base + offset] = val; self.heap[base + offset] = val;
} else { } else {
return Err("Expected reference for STORE_REF".into()); return Err("Expected reference for GATE_STORE".into());
} }
} }
OpCode::GateBeginPeek | OpCode::GateEndPeek |
OpCode::GateBeginBorrow | OpCode::GateEndBorrow |
OpCode::GateBeginMutate | OpCode::GateEndMutate |
OpCode::GateRetain => {
// These are no-ops in v0, but they preserve the gate on the stack.
}
OpCode::GateRelease => {
self.pop()?;
}
OpCode::Syscall => { OpCode::Syscall => {
// Calls a native function implemented by the Firmware/OS. // Calls a native function implemented by the Firmware/OS.
// ABI Rule: Arguments are pushed in call order (LIFO). // ABI Rule: Arguments are pushed in call order (LIFO).

View File

@ -1,53 +1,3 @@
## PR-09 — HIP ISA Freeze v0: Opcode Table + Encoding Contract (Bytecode)
### Goal
Freeze the HIP-related opcode set and encoding so bytecode becomes stable.
### Required Changes
1. Update `prometeu-bytecode`:
* Define the canonical HIP opcode subset:
* `PUSH_CONST`
* `ALLOC(type_id, slots)`
* `GATE_BEGIN_PEEK`, `GATE_END_PEEK`
* `GATE_BEGIN_BORROW`, `GATE_END_BORROW`
* `GATE_BEGIN_MUTATE`, `GATE_END_MUTATE`
* `GATE_LOAD(offset)`
* `GATE_STORE(offset)`
* `GATE_RETAIN`, `GATE_RELEASE`
* `FRAME_SYNC` (if included)
2. Define canonical encodings (normative in comments/doc):
* `GateId` encoding: `u32` little-endian
* `TypeId` encoding: `u32` little-endian
* `ConstId` encoding: `u32` little-endian
* `slots`: `u32` little-endian
* `offset`: `u32` little-endian
3. Update bytecode emitter so it emits these exact opcodes with these exact payloads.
### Non-goals
* No runtime execution changes
### Tests (Mandatory)
1. **Golden bytecode tests**:
* Given a minimal VM IR program using each HIP opcode, assert the exact emitted bytes.
2. **Opcode stability test**:
* Snapshot test of the opcode enum ordering and numeric values.
> If opcode numeric values already exist, DO NOT renumber. If new opcodes are added, append them.
---
## PR-10 — HIP ABI Freeze v0: Trap Conditions + Debug Surface ## PR-10 — HIP ABI Freeze v0: Trap Conditions + Debug Surface
### Goal ### Goal