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 {
OpCode::PushConst | OpCode::PushI32 | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue
| 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())?;
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())?;
operands.push(DisasmOperand::Bool(b_buf[0] != 0));
}
OpCode::Call => {
let addr = read_u32_le(&mut cursor).map_err(|e| e.to_string())?;
let args = read_u32_le(&mut cursor).map_err(|e| e.to_string())?;
operands.push(DisasmOperand::U32(addr));
operands.push(DisasmOperand::U32(args));
OpCode::Call | OpCode::Alloc => {
let v1 = 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(v1));
operands.push(DisasmOperand::U32(v2));
}
_ => {}
}

View File

@ -153,19 +153,46 @@ pub enum OpCode {
/// Ends the current local scope, discarding its local variables.
PopScope = 0x53,
// --- 6.7 Heap ---
// --- 6.7 HIP (Heap Interface Protocol) ---
/// Allocates `size` slots on the heap.
/// Stack: [size] -> [reference]
/// Allocates `slots` slots on the heap with the given `type_id`.
/// Operands: type_id (u32), slots (u32)
/// Stack: [] -> [gate]
Alloc = 0x60,
/// Reads a value from the heap at `reference + offset`.
/// Reads a value from the heap at `gate + offset`.
/// Operand: offset (u32)
/// Stack: [reference] -> [value]
LoadRef = 0x61,
/// Writes a value to the heap at `reference + offset`.
/// Stack: [gate] -> [value]
GateLoad = 0x61,
/// Writes a value to the heap at `gate + offset`.
/// Operand: offset (u32)
/// Stack: [reference, value] -> []
StoreRef = 0x62,
/// Stack: [gate, value] -> []
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 ---
@ -226,8 +253,16 @@ impl TryFrom<u16> for OpCode {
0x52 => Ok(OpCode::PushScope),
0x53 => Ok(OpCode::PopScope),
0x60 => Ok(OpCode::Alloc),
0x61 => Ok(OpCode::LoadRef),
0x62 => Ok(OpCode::StoreRef),
0x61 => Ok(OpCode::GateLoad),
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),
0x80 => Ok(OpCode::FrameSync),
_ => Err(format!("Invalid OpCode: 0x{:04X}", value)),
@ -283,10 +318,76 @@ impl OpCode {
OpCode::PushScope => 3,
OpCode::PopScope => 3,
OpCode::Alloc => 10,
OpCode::LoadRef => 3,
OpCode::StoreRef => 3,
OpCode::GateLoad => 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::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)]));
}
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 } => {
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 } => {
asm_instrs.push(Asm::Op(OpCode::StoreRef, 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![]));
asm_instrs.push(Asm::Op(OpCode::GateStore, vec![Operand::U32(*offset)]));
}
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();

View File

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

View File

@ -544,15 +544,16 @@ impl VirtualMachine {
self.operand_stack.truncate(frame.scope_stack_base);
}
OpCode::Alloc => {
// Allocates 'size' values on the heap and pushes a reference to the stack
let size = self.read_u32()? as usize;
// Allocates 'slots' values on the heap and pushes a reference to the stack
let _type_id = self.read_u32()?;
let slots = self.read_u32()? as usize;
let ref_idx = self.heap.len();
for _ in 0..size {
for _ in 0..slots {
self.heap.push(Value::Null);
}
self.push(Value::Ref(ref_idx));
}
OpCode::LoadRef => {
OpCode::GateLoad => {
// Reads a value from a heap reference at a specific offset
let offset = self.read_u32()? as usize;
let ref_val = self.pop()?;
@ -560,10 +561,10 @@ impl VirtualMachine {
let val = self.heap.get(base + offset).cloned().ok_or("Invalid heap access")?;
self.push(val);
} 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
let offset = self.read_u32()? as usize;
let val = self.pop()?;
@ -574,9 +575,18 @@ impl VirtualMachine {
}
self.heap[base + offset] = val;
} 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 => {
// Calls a native function implemented by the Firmware/OS.
// 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
### Goal