This commit is contained in:
bQUARKz 2026-02-18 15:29:55 +00:00
parent a5daebd849
commit 8b744a120e
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 6 additions and 228 deletions

View File

@ -1,6 +1,5 @@
mod call_frame; mod call_frame;
pub mod local_addressing; pub mod local_addressing;
mod scope_frame;
pub mod verifier; pub mod verifier;
mod virtual_machine; mod virtual_machine;
pub mod vm_init_error; pub mod vm_init_error;

View File

@ -1,5 +1,4 @@
use crate::call_frame::CallFrame; use crate::call_frame::CallFrame;
use crate::scope_frame::ScopeFrame;
use crate::verifier::Verifier; use crate::verifier::Verifier;
use crate::vm_init_error::VmInitError; use crate::vm_init_error::VmInitError;
use crate::{HostContext, NativeInterface}; use crate::{HostContext, NativeInterface};
@ -79,16 +78,12 @@ pub struct VirtualMachine {
pub operand_stack: Vec<Value>, pub operand_stack: Vec<Value>,
/// Call Stack: Manages function call context (return addresses, frame limits). /// Call Stack: Manages function call context (return addresses, frame limits).
pub call_stack: Vec<CallFrame>, pub call_stack: Vec<CallFrame>,
/// Scope Stack: Handles block-level local variable visibility (scopes).
pub scope_stack: Vec<ScopeFrame>,
/// Global Variable Store: Variables that persist for the lifetime of the program. /// Global Variable Store: Variables that persist for the lifetime of the program.
pub globals: Vec<Value>, pub globals: Vec<Value>,
/// The loaded executable (Bytecode + Constant Pool), that is the ROM translated. /// The loaded executable (Bytecode + Constant Pool), that is the ROM translated.
pub program: ProgramImage, pub program: ProgramImage,
/// Heap Memory: Dynamic allocation pool. /// Heap Memory: Dynamic allocation pool.
pub heap: Vec<Value>, pub heap: Vec<Value>,
/// Gate Pool: indirection table for heap objects. Value::Gate carries an index into this pool.
pub gate_pool: Vec<GateEntry>,
/// Total virtual cycles consumed since the VM started. /// Total virtual cycles consumed since the VM started.
pub cycles: u64, pub cycles: u64,
/// Stop flag: true if a `HALT` opcode was encountered. /// Stop flag: true if a `HALT` opcode was encountered.
@ -97,22 +92,7 @@ pub struct VirtualMachine {
pub breakpoints: std::collections::HashSet<usize>, pub breakpoints: std::collections::HashSet<usize>,
} }
/// Identifier for a gate (index into `gate_pool`). // HIP/Gate runtime structures removed per PR-2.1
/// We keep using `Value::Gate(usize)` in the ABI, but inside the VM this represents a `GateId`.
pub type GateId = u32;
/// Metadata for a gate entry, which resolves a `GateId` into a slice of the heap.
#[derive(Debug, Clone)]
pub struct GateEntry {
/// True if this entry is currently alive and usable.
pub alive: bool,
/// Base index into the heap vector where this gate's slots start.
pub base: u32,
/// Number of heap slots reserved for this gate.
pub slots: u32,
/// Type identifier for the gate's storage (kept for future checks; unused for now).
pub type_id: u32,
}
impl Default for VirtualMachine { impl Default for VirtualMachine {
fn default() -> Self { fn default() -> Self {
@ -127,7 +107,6 @@ impl VirtualMachine {
pc: 0, pc: 0,
operand_stack: Vec::new(), operand_stack: Vec::new(),
call_stack: Vec::new(), call_stack: Vec::new(),
scope_stack: Vec::new(),
globals: Vec::new(), globals: Vec::new(),
program: ProgramImage::new( program: ProgramImage::new(
rom, rom,
@ -137,7 +116,6 @@ impl VirtualMachine {
std::collections::HashMap::new(), std::collections::HashMap::new(),
), ),
heap: Vec::new(), heap: Vec::new(),
gate_pool: Vec::new(),
cycles: 0, cycles: 0,
halted: false, halted: false,
breakpoints: std::collections::HashSet::new(), breakpoints: std::collections::HashSet::new(),
@ -157,10 +135,8 @@ impl VirtualMachine {
self.pc = 0; self.pc = 0;
self.operand_stack.clear(); self.operand_stack.clear();
self.call_stack.clear(); self.call_stack.clear();
self.scope_stack.clear();
self.globals.clear(); self.globals.clear();
self.heap.clear(); self.heap.clear();
self.gate_pool.clear();
self.cycles = 0; self.cycles = 0;
self.halted = true; // execution is impossible until a successful load self.halted = true; // execution is impossible until a successful load
@ -244,7 +220,6 @@ impl VirtualMachine {
// cause the VM to stop after returning from the entrypoint. // cause the VM to stop after returning from the entrypoint.
self.operand_stack.clear(); self.operand_stack.clear();
self.call_stack.clear(); self.call_stack.clear();
self.scope_stack.clear();
// Entrypoint also needs locals allocated. // Entrypoint also needs locals allocated.
// For the sentinel frame, stack_base is always 0. // For the sentinel frame, stack_base is always 0.
@ -961,15 +936,9 @@ impl VirtualMachine {
} }
self.pc = frame.return_pc as usize; self.pc = frame.return_pc as usize;
} }
OpCode::PushScope => { // Scope opcodes are no-ops under the new architecture (PR-2.1)
self.scope_stack.push(ScopeFrame { scope_stack_base: self.operand_stack.len() }); OpCode::PushScope => {}
} OpCode::PopScope => {}
OpCode::PopScope => {
let frame = self.scope_stack.pop().ok_or_else(|| {
LogicalFrameEndingReason::Panic("Scope stack underflow".into())
})?;
self.operand_stack.truncate(frame.scope_stack_base);
}
OpCode::Syscall => { OpCode::Syscall => {
let pc_at_syscall = start_pc as u32; let pc_at_syscall = start_pc as u32;
let id = instr let id = instr
@ -1034,28 +1003,6 @@ impl VirtualMachine {
Ok(()) Ok(())
} }
/// Resolves a `GateId` to an immutable reference to its `GateEntry` or returns a trap reason.
fn resolve_gate(
&self,
gate_id: GateId,
opcode: u16,
pc: u32,
) -> Result<&GateEntry, LogicalFrameEndingReason> {
let idx = gate_id as usize;
let entry = self.gate_pool.get(idx).ok_or_else(|| {
self.trap(TRAP_OOB, opcode, format!("Invalid gate id: {}", gate_id), pc)
})?;
if !entry.alive {
return Err(self.trap(
TRAP_OOB,
opcode,
format!("Dead gate id: {}", gate_id),
pc,
));
}
Ok(entry)
}
pub fn trap( pub fn trap(
&self, &self,
code: u32, code: u32,
@ -1407,7 +1354,7 @@ mod tests {
assert_eq!(vm.pop_integer().unwrap(), 30); assert_eq!(vm.pop_integer().unwrap(), 30);
assert_eq!(vm.operand_stack.len(), 0); assert_eq!(vm.operand_stack.len(), 0);
assert_eq!(vm.call_stack.len(), 1); assert_eq!(vm.call_stack.len(), 1);
assert_eq!(vm.scope_stack.len(), 0); // Scope frames removed: no scope stack to assert on
} }
#[test] #[test]
@ -1497,127 +1444,7 @@ mod tests {
assert_eq!(vm2.pop().unwrap(), Value::Int64(123)); assert_eq!(vm2.pop().unwrap(), Value::Int64(123));
} }
#[test] // Scope tests removed under PR-2.1 (scope frames eliminated)
fn test_nested_scopes() {
let mut rom = Vec::new();
// PUSH_I64 1
// PUSH_SCOPE
// PUSH_I64 2
// PUSH_SCOPE
// PUSH_I64 3
// POP_SCOPE
// POP_SCOPE
// HALT
rom.extend_from_slice(&(OpCode::PushI64 as u16).to_le_bytes());
rom.extend_from_slice(&1i64.to_le_bytes());
rom.extend_from_slice(&(OpCode::PushScope as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::PushI64 as u16).to_le_bytes());
rom.extend_from_slice(&2i64.to_le_bytes());
rom.extend_from_slice(&(OpCode::PushScope as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::PushI64 as u16).to_le_bytes());
rom.extend_from_slice(&3i64.to_le_bytes());
rom.extend_from_slice(&(OpCode::PopScope as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::PopScope as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = new_test_vm(rom.clone(), vec![]);
let mut native = MockNative;
let mut ctx = HostContext::new(None);
// Execute step by step and check stack
vm.step(&mut native, &mut ctx).unwrap(); // Push 1
assert_eq!(vm.operand_stack.len(), 1);
vm.step(&mut native, &mut ctx).unwrap(); // PushScope 1
assert_eq!(vm.scope_stack.len(), 1);
assert_eq!(vm.scope_stack.last().unwrap().scope_stack_base, 1);
vm.step(&mut native, &mut ctx).unwrap(); // Push 2
assert_eq!(vm.operand_stack.len(), 2);
vm.step(&mut native, &mut ctx).unwrap(); // PushScope 2
assert_eq!(vm.scope_stack.len(), 2);
assert_eq!(vm.scope_stack.last().unwrap().scope_stack_base, 2);
vm.step(&mut native, &mut ctx).unwrap(); // Push 3
assert_eq!(vm.operand_stack.len(), 3);
vm.step(&mut native, &mut ctx).unwrap(); // PopScope 2
assert_eq!(vm.scope_stack.len(), 1);
assert_eq!(vm.operand_stack.len(), 2);
assert_eq!(vm.operand_stack.last().unwrap(), &Value::Int64(2));
vm.step(&mut native, &mut ctx).unwrap(); // PopScope 1
assert_eq!(vm.scope_stack.len(), 0);
assert_eq!(vm.operand_stack.len(), 1);
assert_eq!(vm.operand_stack.last().unwrap(), &Value::Int64(1));
}
#[test]
fn test_pop_scope_does_not_affect_ret() {
let mut rom = Vec::new();
// PUSH_I64 100
// CALL func_id 1
// HALT
rom.extend_from_slice(&(OpCode::PushI64 as u16).to_le_bytes());
rom.extend_from_slice(&100i64.to_le_bytes());
rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes());
rom.extend_from_slice(&1u32.to_le_bytes()); // func_id 1
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let func_addr = rom.len();
// func:
// PUSH_I64 200
// PUSH_SCOPE
// PUSH_I64 300
// RET
rom.extend_from_slice(&(OpCode::PushI64 as u16).to_le_bytes());
rom.extend_from_slice(&200i64.to_le_bytes());
rom.extend_from_slice(&(OpCode::PushScope as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::PushI64 as u16).to_le_bytes());
rom.extend_from_slice(&300i64.to_le_bytes());
rom.extend_from_slice(&(OpCode::PopScope as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let functions = vec![
FunctionMeta { code_offset: 0, code_len: func_addr as u32, ..Default::default() },
FunctionMeta {
code_offset: func_addr as u32,
code_len: (rom.len() - func_addr) as u32,
param_slots: 0,
return_slots: 1,
..Default::default()
},
];
let mut vm = VirtualMachine {
program: ProgramImage::new(
rom,
vec![],
functions,
None,
std::collections::HashMap::new(),
),
..Default::default()
};
vm.prepare_call("0");
let mut native = MockNative;
let mut ctx = HostContext::new(None);
let mut steps = 0;
while !vm.halted && steps < 100 {
vm.step(&mut native, &mut ctx).unwrap();
steps += 1;
}
assert!(vm.halted);
assert_eq!(vm.operand_stack.len(), 2);
assert_eq!(vm.operand_stack[0], Value::Int64(100));
assert_eq!(vm.operand_stack[1], Value::Int64(200));
}
#[test] #[test]
fn test_push_i32() { fn test_push_i32() {

View File

@ -1,51 +1,3 @@
# PR-2.1 — Remove ScopeFrame and HIP Runtime Structures
### Briefing
The new architecture removes HIP, borrow/mutate/peek semantics, and any gate-based lifetime tracking. The VM must no longer depend on `ScopeFrame` or related structures.
### Target
* Remove `ScopeFrame` and any HIP-related runtime data structures.
* Ensure the VM compiles and runs without any scope/gate logic.
### Work items
* Delete `scope_frame.rs` and any modules dedicated to HIP or gate lifetimes.
* Remove fields in VM state that track scope frames, gates, or borrow state.
* Update the main VM execution loop to no longer push/pop scope frames.
* Remove any trap logic that references scope or gate violations.
### Acceptance checklist
* [ ] `ScopeFrame` and related modules are fully removed.
* [ ] VM compiles without scope/gate concepts.
* [ ] No HIP-related symbols remain in the VM crate.
* [ ] `cargo test` passes.
### Tests
* Existing tests only.
### Junie instructions
**You MAY:**
* Delete scope/gate-related modules and fields.
* Update code to remove references to them.
**You MUST NOT:**
* Introduce new lifetime or ownership systems.
* Replace scope frames with another architecture.
* Add compatibility layers.
**If unclear:**
* Ask for clarification instead of inventing new runtime concepts.
---
# PR-2.2 — Simplify VM Execution Loop (Pure Stack Machine) # PR-2.2 — Simplify VM Execution Loop (Pure Stack Machine)
### Briefing ### Briefing