pr 27
This commit is contained in:
parent
91af0d6f98
commit
76660294d5
@ -161,13 +161,18 @@ 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 { .. } => asm_instrs.push(Asm::Op(OpCode::Alloc, vec![])),
|
||||||
InstrKind::LoadRef(offset) => {
|
InstrKind::GateLoad { offset } => {
|
||||||
asm_instrs.push(Asm::Op(OpCode::LoadRef, vec![Operand::U32(*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)]));
|
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();
|
let end_idx = asm_instrs.len();
|
||||||
@ -253,8 +258,8 @@ mod tests {
|
|||||||
params: vec![],
|
params: vec![],
|
||||||
return_type: Type::Void,
|
return_type: Type::Void,
|
||||||
body: vec![
|
body: vec![
|
||||||
Instruction::new(InstrKind::PushConst(id_int), None),
|
Instruction::new(InstrKind::PushConst(ir_vm::ConstId(id_int.0)), None),
|
||||||
Instruction::new(InstrKind::PushConst(id_str), None),
|
Instruction::new(InstrKind::PushConst(ir_vm::ConstId(id_str.0)), None),
|
||||||
Instruction::new(InstrKind::Ret, None),
|
Instruction::new(InstrKind::Ret, None),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -241,14 +241,15 @@ mod tests {
|
|||||||
0066 SetLocal U32(1)
|
0066 SetLocal U32(1)
|
||||||
006C GetLocal U32(1)
|
006C GetLocal U32(1)
|
||||||
0072 SetLocal U32(2)
|
0072 SetLocal U32(2)
|
||||||
0078 LoadRef U32(0)
|
0078 Nop
|
||||||
007E SetLocal U32(3)
|
007A LoadRef U32(0)
|
||||||
0084 GetLocal U32(3)
|
0080 SetLocal U32(3)
|
||||||
008A PushConst U32(5)
|
0086 GetLocal U32(3)
|
||||||
0090 Add
|
008C PushConst U32(5)
|
||||||
0092 SetLocal U32(4)
|
0092 Add
|
||||||
0098 Nop
|
0094 SetLocal U32(4)
|
||||||
009A Ret
|
009A Nop
|
||||||
|
009C Ret
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
assert_eq!(disasm_text, expected_disasm);
|
assert_eq!(disasm_text, expected_disasm);
|
||||||
|
|||||||
@ -5,7 +5,8 @@
|
|||||||
//! easy to lower into VM-specific bytecode.
|
//! easy to lower into VM-specific bytecode.
|
||||||
|
|
||||||
use crate::common::spans::Span;
|
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
|
/// An `Instruction` combines an instruction's behavior (`kind`) with its
|
||||||
/// source code location (`span`) for debugging and error reporting.
|
/// source code location (`span`) for debugging and error reporting.
|
||||||
@ -139,10 +140,186 @@ pub enum InstrKind {
|
|||||||
|
|
||||||
// --- HIP / Memory ---
|
// --- HIP / Memory ---
|
||||||
|
|
||||||
/// Allocates memory on the heap. Pops size from stack.
|
/// Allocates memory on the heap.
|
||||||
Alloc,
|
Alloc { type_id: TypeId, slots: u32 },
|
||||||
/// Reads from heap at reference + offset. Pops reference, pushes value.
|
/// Reads from heap at gate + offset. Pops gate, pushes value.
|
||||||
LoadRef(u32),
|
GateLoad { offset: u32 },
|
||||||
/// Writes to heap at reference + offset. Pops reference and value.
|
/// Writes to heap at gate + offset. Pops gate and value.
|
||||||
StoreRef(u32),
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,9 +15,7 @@ pub mod validate;
|
|||||||
|
|
||||||
pub use instr::{Instruction, InstrKind, Label};
|
pub use instr::{Instruction, InstrKind, Label};
|
||||||
pub use module::{Module, Function, Global, Param};
|
pub use module::{Module, Function, Global, Param};
|
||||||
pub use types::{Type, Value, GateId};
|
pub use types::{Type, Value, GateId, ConstId, TypeId};
|
||||||
// Note: ConstId and TypeId are not exported here to avoid conflict with ir_core::ids
|
|
||||||
// until the crates are fully decoupled.
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@ -40,7 +38,7 @@ mod tests {
|
|||||||
params: vec![],
|
params: vec![],
|
||||||
return_type: Type::Null,
|
return_type: Type::Null,
|
||||||
body: vec![
|
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::Call { func_id: FunctionId(2), arg_count: 1 }, None),
|
||||||
Instruction::new(InstrKind::Ret, None),
|
Instruction::new(InstrKind::Ret, None),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -47,7 +47,7 @@ pub fn lower_function(core_func: &ir_core::Function) -> Result<ir_vm::Function>
|
|||||||
|
|
||||||
for instr in &block.instrs {
|
for instr in &block.instrs {
|
||||||
let kind = match instr {
|
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 {
|
ir_core::Instr::Call(func_id, arg_count) => ir_vm::InstrKind::Call {
|
||||||
func_id: *func_id,
|
func_id: *func_id,
|
||||||
arg_count: *arg_count
|
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::And => ir_vm::InstrKind::And,
|
||||||
ir_core::Instr::Or => ir_vm::InstrKind::Or,
|
ir_core::Instr::Or => ir_vm::InstrKind::Or,
|
||||||
ir_core::Instr::Not => ir_vm::InstrKind::Not,
|
ir_core::Instr::Not => ir_vm::InstrKind::Not,
|
||||||
ir_core::Instr::Alloc { .. } => ir_vm::InstrKind::Alloc,
|
ir_core::Instr::Alloc { ty, slots } => ir_vm::InstrKind::Alloc {
|
||||||
ir_core::Instr::BeginPeek { .. } |
|
type_id: ir_vm::TypeId(ty.0),
|
||||||
ir_core::Instr::BeginBorrow { .. } |
|
slots: *slots
|
||||||
ir_core::Instr::BeginMutate { .. } => ir_vm::InstrKind::LoadRef(0),
|
},
|
||||||
ir_core::Instr::EndPeek |
|
ir_core::Instr::BeginPeek { .. } => {
|
||||||
ir_core::Instr::EndBorrow |
|
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateBeginPeek, None));
|
||||||
ir_core::Instr::EndMutate => ir_vm::InstrKind::Nop,
|
ir_vm::InstrKind::GateLoad { offset: 0 }
|
||||||
ir_core::Instr::LoadRef(offset) => ir_vm::InstrKind::LoadRef(*offset),
|
}
|
||||||
ir_core::Instr::StoreRef(offset) => ir_vm::InstrKind::StoreRef(*offset),
|
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,
|
ir_core::Instr::Free => ir_vm::InstrKind::Nop,
|
||||||
};
|
};
|
||||||
vm_func.body.push(ir_vm::Instruction::new(kind, None));
|
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 {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ir_core;
|
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::*;
|
use crate::ir_vm::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -154,7 +167,7 @@ mod tests {
|
|||||||
Block {
|
Block {
|
||||||
id: 0,
|
id: 0,
|
||||||
instrs: vec![
|
instrs: vec![
|
||||||
Instr::PushConst(ConstId(0)),
|
Instr::PushConst(CoreConstId(0)),
|
||||||
Instr::Call(FunctionId(2), 1),
|
Instr::Call(FunctionId(2), 1),
|
||||||
],
|
],
|
||||||
terminator: Terminator::Jump(1),
|
terminator: Terminator::Jump(1),
|
||||||
|
|||||||
@ -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)
|
# PR-04 — Update `core_to_vm` Lowering (Kill Placeholders)
|
||||||
|
|
||||||
### Goal
|
### Goal
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user