This commit is contained in:
Nilton Constantino 2026-01-30 11:58:15 +00:00
parent 91af0d6f98
commit 76660294d5
No known key found for this signature in database
6 changed files with 230 additions and 105 deletions

View File

@ -161,13 +161,18 @@ 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::LoadRef(offset) => {
InstrKind::Alloc { .. } => asm_instrs.push(Asm::Op(OpCode::Alloc, vec![])),
InstrKind::GateLoad { offset } => {
asm_instrs.push(Asm::Op(OpCode::LoadRef, vec![Operand::U32(*offset)]));
}
InstrKind::StoreRef(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 => {
asm_instrs.push(Asm::Op(OpCode::Nop, vec![]));
}
}
let end_idx = asm_instrs.len();
@ -253,8 +258,8 @@ mod tests {
params: vec![],
return_type: Type::Void,
body: vec![
Instruction::new(InstrKind::PushConst(id_int), None),
Instruction::new(InstrKind::PushConst(id_str), None),
Instruction::new(InstrKind::PushConst(ir_vm::ConstId(id_int.0)), None),
Instruction::new(InstrKind::PushConst(ir_vm::ConstId(id_str.0)), None),
Instruction::new(InstrKind::Ret, None),
],
};

View File

@ -241,14 +241,15 @@ mod tests {
0066 SetLocal U32(1)
006C GetLocal U32(1)
0072 SetLocal U32(2)
0078 LoadRef U32(0)
007E SetLocal U32(3)
0084 GetLocal U32(3)
008A PushConst U32(5)
0090 Add
0092 SetLocal U32(4)
0098 Nop
009A Ret
0078 Nop
007A LoadRef U32(0)
0080 SetLocal U32(3)
0086 GetLocal U32(3)
008C PushConst U32(5)
0092 Add
0094 SetLocal U32(4)
009A Nop
009C Ret
"#;
assert_eq!(disasm_text, expected_disasm);

View File

@ -5,7 +5,8 @@
//! easy to lower into VM-specific bytecode.
use crate::common::spans::Span;
use crate::ir_core::ids::{ConstId, FunctionId};
use crate::ir_vm::types::{ConstId, TypeId};
use crate::ir_core::ids::FunctionId;
/// An `Instruction` combines an instruction's behavior (`kind`) with its
/// source code location (`span`) for debugging and error reporting.
@ -139,10 +140,186 @@ pub enum InstrKind {
// --- HIP / Memory ---
/// Allocates memory on the heap. Pops size from stack.
Alloc,
/// Reads from heap at reference + offset. Pops reference, pushes value.
LoadRef(u32),
/// Writes to heap at reference + offset. Pops reference and value.
StoreRef(u32),
/// Allocates memory on the heap.
Alloc { type_id: TypeId, slots: u32 },
/// Reads from heap at gate + offset. Pops gate, pushes value.
GateLoad { offset: u32 },
/// Writes to heap at gate + offset. Pops gate and value.
GateStore { offset: u32 },
// --- Scope Markers ---
GateBeginPeek,
GateEndPeek,
GateBeginBorrow,
GateEndBorrow,
GateBeginMutate,
GateEndMutate,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir_vm::types::{ConstId, TypeId};
#[test]
fn test_instr_kind_is_cloneable() {
let instr = InstrKind::Alloc { type_id: TypeId(1), slots: 2 };
let cloned = instr.clone();
match cloned {
InstrKind::Alloc { type_id, slots } => {
assert_eq!(type_id, TypeId(1));
assert_eq!(slots, 2);
}
_ => panic!("Clone failed"),
}
}
#[test]
fn test_isa_surface_snapshot() {
// This test ensures that the instruction set surface remains stable.
// If you add/remove/change instructions, this test will fail,
// prompting an explicit review of the ISA change.
let instructions = vec![
InstrKind::Nop,
InstrKind::Halt,
InstrKind::PushConst(ConstId(0)),
InstrKind::PushBool(true),
InstrKind::PushNull,
InstrKind::Pop,
InstrKind::Dup,
InstrKind::Swap,
InstrKind::Add,
InstrKind::Sub,
InstrKind::Mul,
InstrKind::Div,
InstrKind::Neg,
InstrKind::Eq,
InstrKind::Neq,
InstrKind::Lt,
InstrKind::Gt,
InstrKind::Lte,
InstrKind::Gte,
InstrKind::And,
InstrKind::Or,
InstrKind::Not,
InstrKind::BitAnd,
InstrKind::BitOr,
InstrKind::BitXor,
InstrKind::Shl,
InstrKind::Shr,
InstrKind::GetLocal(0),
InstrKind::SetLocal(0),
InstrKind::GetGlobal(0),
InstrKind::SetGlobal(0),
InstrKind::Jmp(Label("target".to_string())),
InstrKind::JmpIfFalse(Label("target".to_string())),
InstrKind::Label(Label("target".to_string())),
InstrKind::Call { func_id: FunctionId(0), arg_count: 0 },
InstrKind::Ret,
InstrKind::Syscall(0),
InstrKind::FrameSync,
InstrKind::Alloc { type_id: TypeId(0), slots: 0 },
InstrKind::GateLoad { offset: 0 },
InstrKind::GateStore { offset: 0 },
InstrKind::GateBeginPeek,
InstrKind::GateEndPeek,
InstrKind::GateBeginBorrow,
InstrKind::GateEndBorrow,
InstrKind::GateBeginMutate,
InstrKind::GateEndMutate,
];
let serialized = serde_json::to_string_pretty(&instructions).unwrap();
// This is a "lock" on the ISA surface.
// If the structure of InstrKind changes, the serialization will change.
let expected_json = r#"[
"Nop",
"Halt",
{
"PushConst": 0
},
{
"PushBool": true
},
"PushNull",
"Pop",
"Dup",
"Swap",
"Add",
"Sub",
"Mul",
"Div",
"Neg",
"Eq",
"Neq",
"Lt",
"Gt",
"Lte",
"Gte",
"And",
"Or",
"Not",
"BitAnd",
"BitOr",
"BitXor",
"Shl",
"Shr",
{
"GetLocal": 0
},
{
"SetLocal": 0
},
{
"GetGlobal": 0
},
{
"SetGlobal": 0
},
{
"Jmp": "target"
},
{
"JmpIfFalse": "target"
},
{
"Label": "target"
},
{
"Call": {
"func_id": 0,
"arg_count": 0
}
},
"Ret",
{
"Syscall": 0
},
"FrameSync",
{
"Alloc": {
"type_id": 0,
"slots": 0
}
},
{
"GateLoad": {
"offset": 0
}
},
{
"GateStore": {
"offset": 0
}
},
"GateBeginPeek",
"GateEndPeek",
"GateBeginBorrow",
"GateEndBorrow",
"GateBeginMutate",
"GateEndMutate"
]"#;
assert_eq!(serialized, expected_json);
}
}

View File

@ -15,9 +15,7 @@ pub mod validate;
pub use instr::{Instruction, InstrKind, Label};
pub use module::{Module, Function, Global, Param};
pub use types::{Type, Value, GateId};
// Note: ConstId and TypeId are not exported here to avoid conflict with ir_core::ids
// until the crates are fully decoupled.
pub use types::{Type, Value, GateId, ConstId, TypeId};
#[cfg(test)]
mod tests {
@ -40,7 +38,7 @@ mod tests {
params: vec![],
return_type: Type::Null,
body: vec![
Instruction::new(InstrKind::PushConst(ConstId(0)), None),
Instruction::new(InstrKind::PushConst(crate::ir_vm::types::ConstId(0)), None),
Instruction::new(InstrKind::Call { func_id: FunctionId(2), arg_count: 1 }, None),
Instruction::new(InstrKind::Ret, None),
],

View File

@ -47,7 +47,7 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir_vm::Function>
for instr in &block.instrs {
let kind = match instr {
ir_core::Instr::PushConst(id) => ir_vm::InstrKind::PushConst(*id),
ir_core::Instr::PushConst(id) => ir_vm::InstrKind::PushConst(ir_vm::ConstId(id.0)),
ir_core::Instr::Call(func_id, arg_count) => ir_vm::InstrKind::Call {
func_id: *func_id,
arg_count: *arg_count
@ -71,15 +71,27 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir_vm::Function>
ir_core::Instr::And => ir_vm::InstrKind::And,
ir_core::Instr::Or => ir_vm::InstrKind::Or,
ir_core::Instr::Not => ir_vm::InstrKind::Not,
ir_core::Instr::Alloc { .. } => ir_vm::InstrKind::Alloc,
ir_core::Instr::BeginPeek { .. } |
ir_core::Instr::BeginBorrow { .. } |
ir_core::Instr::BeginMutate { .. } => ir_vm::InstrKind::LoadRef(0),
ir_core::Instr::EndPeek |
ir_core::Instr::EndBorrow |
ir_core::Instr::EndMutate => ir_vm::InstrKind::Nop,
ir_core::Instr::LoadRef(offset) => ir_vm::InstrKind::LoadRef(*offset),
ir_core::Instr::StoreRef(offset) => ir_vm::InstrKind::StoreRef(*offset),
ir_core::Instr::Alloc { ty, slots } => ir_vm::InstrKind::Alloc {
type_id: ir_vm::TypeId(ty.0),
slots: *slots
},
ir_core::Instr::BeginPeek { .. } => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginPeek, None));
ir_vm::InstrKind::GateLoad { offset: 0 }
}
ir_core::Instr::BeginBorrow { .. } => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginBorrow, None));
ir_vm::InstrKind::GateLoad { offset: 0 }
}
ir_core::Instr::BeginMutate { .. } => {
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginMutate, None));
ir_vm::InstrKind::GateLoad { offset: 0 }
}
ir_core::Instr::EndPeek => ir_vm::InstrKind::GateEndPeek,
ir_core::Instr::EndBorrow => ir_vm::InstrKind::GateEndBorrow,
ir_core::Instr::EndMutate => ir_vm::InstrKind::GateEndMutate,
ir_core::Instr::LoadRef(offset) => ir_vm::InstrKind::GateLoad { offset: *offset },
ir_core::Instr::StoreRef(offset) => ir_vm::InstrKind::GateStore { offset: *offset },
ir_core::Instr::Free => ir_vm::InstrKind::Nop,
};
vm_func.body.push(ir_vm::Instruction::new(kind, None));
@ -133,7 +145,8 @@ fn lower_type(ty: &ir_core::Type) -> ir_vm::Type {
mod tests {
use super::*;
use crate::ir_core;
use crate::ir_core::*;
use crate::ir_core::{Block, Instr, Terminator, ConstantValue, Program, ConstPool};
use crate::ir_core::ids::{FunctionId, ConstId as CoreConstId};
use crate::ir_vm::*;
#[test]
@ -154,7 +167,7 @@ mod tests {
Block {
id: 0,
instrs: vec![
Instr::PushConst(ConstId(0)),
Instr::PushConst(CoreConstId(0)),
Instr::Call(FunctionId(2), 1),
],
terminator: Terminator::Jump(1),

View File

@ -1,72 +1,3 @@
# PR-02 — Define `ir_vm` ISA v0 (Memory & Gates Only)
### Goal
Define a minimal, PBS-compatible but PBS-agnostic VM instruction set.
### Required Instructions
#### Constant Pool
* `PushConst(ConstId)`
#### HIP Allocation (Deterministic)
* `Alloc { type_id: TypeId, slots: u32 }`
#### Gate-Based Heap Access
* `GateLoad { offset: u32 }`
* `GateStore { offset: u32 }`
> All heap access must follow: gate validation → base+slots resolution → bounds check → read/write.
#### Scope Markers (Semantic Preservation)
* `GateBeginPeek` / `GateEndPeek`
* `GateBeginBorrow` / `GateEndBorrow`
* `GateBeginMutate` / `GateEndMutate`
> These may be runtime no-ops in v0 but must exist to preserve semantics and debug invariants.
#### Safe Point Hook
* `FrameSync` (optional but recommended)
### Non-goals
* No RC implementation
* No VM execution logic
### Tests
* Unit tests ensuring the instruction enum is stable and cloneable
* Snapshot or debug-format test to lock the ISA surface
---
# PR-03 — Remove “Ref” Leakage from `ir_vm`
### Goal
Eliminate pointer-based mental models from the VM IR.
### Required Changes
* Rename any existing `LoadRef` / `StoreRef` to:
* `LocalLoad { slot: u32 }`
* `LocalStore { slot: u32 }`
* Remove or rename any type named `Ref` that refers to HIP
**Hard rule:** the word `Ref` must never refer to HIP memory in `ir_vm`.
### Tests
* Grep-style or unit test ensuring no `Ref`-named HIP ops exist in `ir_vm`
---
# PR-04 — Update `core_to_vm` Lowering (Kill Placeholders)
### Goal