This commit is contained in:
Nilton Constantino 2026-02-02 21:08:46 +00:00
parent 7819dd2d0a
commit 3af2aa1328
No known key found for this signature in database
8 changed files with 109 additions and 471 deletions

View File

@ -20,7 +20,5 @@ pub mod asm;
pub mod disasm;
mod model;
mod module_linker;
pub use model::*;
pub use module_linker::*;

View File

@ -61,12 +61,12 @@ pub struct Export {
pub func_idx: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Import {
pub symbol: String,
pub relocation_pcs: Vec<u32>,
}
/// Represents the final serialized format of a PBS v0 module.
///
/// This structure is a pure data container for the PBS format. It does NOT
/// contain any linker-like logic (symbol resolution, patching, etc.).
/// All multi-module programs must be flattened and linked by the compiler
/// before being serialized into this format.
#[derive(Debug, Clone, PartialEq)]
pub struct BytecodeModule {
pub version: u16,
@ -75,7 +75,6 @@ pub struct BytecodeModule {
pub code: Vec<u8>,
pub debug_info: Option<DebugInfo>,
pub exports: Vec<Export>,
pub imports: Vec<Import>,
}
impl BytecodeModule {
@ -85,7 +84,6 @@ impl BytecodeModule {
let code_data = self.code.clone();
let debug_data = self.debug_info.as_ref().map(|di| self.serialize_debug(di)).unwrap_or_default();
let export_data = self.serialize_exports();
let import_data = self.serialize_imports();
let mut final_sections = Vec::new();
if !cp_data.is_empty() { final_sections.push((0, cp_data)); }
@ -93,7 +91,6 @@ impl BytecodeModule {
if !code_data.is_empty() { final_sections.push((2, code_data)); }
if !debug_data.is_empty() { final_sections.push((3, debug_data)); }
if !export_data.is_empty() { final_sections.push((4, export_data)); }
if !import_data.is_empty() { final_sections.push((5, import_data)); }
let mut out = Vec::new();
// Magic "PBS\0"
@ -207,21 +204,6 @@ impl BytecodeModule {
data
}
fn serialize_imports(&self) -> Vec<u8> {
if self.imports.is_empty() { return Vec::new(); }
let mut data = Vec::new();
data.extend_from_slice(&(self.imports.len() as u32).to_le_bytes());
for imp in &self.imports {
let s_bytes = imp.symbol.as_bytes();
data.extend_from_slice(&(s_bytes.len() as u32).to_le_bytes());
data.extend_from_slice(s_bytes);
data.extend_from_slice(&(imp.relocation_pcs.len() as u32).to_le_bytes());
for pc in &imp.relocation_pcs {
data.extend_from_slice(&pc.to_le_bytes());
}
}
data
}
}
pub struct BytecodeLoader;
@ -287,7 +269,6 @@ impl BytecodeLoader {
code: Vec::new(),
debug_info: None,
exports: Vec::new(),
imports: Vec::new(),
};
for (kind, offset, length) in sections {
@ -308,9 +289,6 @@ impl BytecodeLoader {
4 => { // Exports
module.exports = parse_exports(section_data)?;
}
5 => { // Imports
module.imports = parse_imports(section_data)?;
}
_ => {} // Skip unknown or optional sections
}
}
@ -493,45 +471,6 @@ fn parse_exports(data: &[u8]) -> Result<Vec<Export>, LoadError> {
Ok(exports)
}
fn parse_imports(data: &[u8]) -> Result<Vec<Import>, LoadError> {
if data.is_empty() {
return Ok(Vec::new());
}
if data.len() < 4 {
return Err(LoadError::MalformedSection);
}
let count = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
let mut imports = Vec::with_capacity(count);
let mut pos = 4;
for _ in 0..count {
if pos + 8 > data.len() {
return Err(LoadError::UnexpectedEof);
}
let relocation_count = u32::from_le_bytes(data[pos..pos+4].try_into().unwrap()) as usize;
let name_len = u32::from_le_bytes(data[pos+4..pos+8].try_into().unwrap()) as usize;
pos += 8;
if pos + name_len > data.len() {
return Err(LoadError::UnexpectedEof);
}
let symbol = String::from_utf8_lossy(&data[pos..pos+name_len]).into_owned();
pos += name_len;
if pos + relocation_count * 4 > data.len() {
return Err(LoadError::UnexpectedEof);
}
let mut relocation_pcs = Vec::with_capacity(relocation_count);
for _ in 0..relocation_count {
let pc = u32::from_le_bytes(data[pos..pos+4].try_into().unwrap());
relocation_pcs.push(pc);
pos += 4;
}
imports.push(Import { symbol, relocation_pcs });
}
Ok(imports)
}
fn validate_module(module: &BytecodeModule) -> Result<(), LoadError> {
for func in &module.functions {

View File

@ -1,296 +0,0 @@
use crate::opcode::OpCode;
use crate::{BytecodeModule, ConstantPoolEntry, DebugInfo, FunctionMeta};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LinkError {
UnresolvedSymbol(String),
DuplicateExport(String),
}
pub struct ModuleLinker;
/// Internal representation for linking process
#[derive(Debug)]
pub struct LinkedProgram {
pub rom: Vec<u8>,
pub constant_pool: Vec<ConstantPoolEntry>,
pub functions: Vec<FunctionMeta>,
pub debug_info: Option<DebugInfo>,
pub exports: HashMap<String, u32>,
}
impl ModuleLinker {
pub fn link(modules: &[BytecodeModule]) -> Result<LinkedProgram, LinkError> {
let mut combined_code = Vec::new();
let mut combined_functions = Vec::new();
let mut combined_constants = Vec::new();
let mut combined_debug_pc_to_span = Vec::new();
let mut combined_debug_function_names = Vec::new();
let mut exports = HashMap::new();
// Offset mapping for each module
let mut module_code_offsets = Vec::with_capacity(modules.len());
let mut module_function_offsets = Vec::with_capacity(modules.len());
// First pass: collect exports and calculate offsets
for module in modules {
let code_offset = combined_code.len() as u32;
let function_offset = combined_functions.len() as u32;
module_code_offsets.push(code_offset);
module_function_offsets.push(function_offset);
for export in &module.exports {
if exports.contains_key(&export.symbol) {
return Err(LinkError::DuplicateExport(export.symbol.clone()));
}
exports.insert(export.symbol.clone(), (function_offset + export.func_idx) as u32);
}
combined_code.extend_from_slice(&module.code);
for func in &module.functions {
let mut linked_func = func.clone();
linked_func.code_offset += code_offset;
combined_functions.push(linked_func);
}
}
// Second pass: resolve imports and relocate constants/code
for (i, module) in modules.iter().enumerate() {
let code_offset = module_code_offsets[i] as usize;
let const_base = combined_constants.len() as u32;
// Relocate constant pool entries for this module
for entry in &module.const_pool {
combined_constants.push(entry.clone());
}
// Patch relocations for imports
for import in &module.imports {
let target_func_idx = exports.get(&import.symbol)
.ok_or_else(|| LinkError::UnresolvedSymbol(import.symbol.clone()))?;
for &reloc_pc in &import.relocation_pcs {
let absolute_pc = code_offset + reloc_pc as usize;
// CALL opcode is 2 bytes, immediate is next 4 bytes
let imm_offset = absolute_pc + 2;
if imm_offset + 4 <= combined_code.len() {
let bytes = target_func_idx.to_le_bytes();
combined_code[imm_offset..imm_offset+4].copy_from_slice(&bytes);
}
}
}
// Relocate PUSH_CONST instructions
if const_base > 0 {
let mut pos = code_offset;
let end = code_offset + module.code.len();
while pos < end {
if pos + 2 > end { break; }
let op_val = u16::from_le_bytes([combined_code[pos], combined_code[pos+1]]);
let opcode = match OpCode::try_from(op_val) {
Ok(op) => op,
Err(_) => {
pos += 2;
continue;
}
};
pos += 2;
match opcode {
OpCode::PushConst => {
if pos + 4 <= end {
let old_idx = u32::from_le_bytes(combined_code[pos..pos+4].try_into().unwrap());
let new_idx = old_idx + const_base;
combined_code[pos..pos+4].copy_from_slice(&new_idx.to_le_bytes());
pos += 4;
}
}
OpCode::PushI32 | OpCode::PushBounded | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue
| OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal
| OpCode::PopN | OpCode::Syscall | OpCode::GateLoad | OpCode::GateStore | OpCode::Call => {
pos += 4;
}
OpCode::PushI64 | OpCode::PushF64 | OpCode::Alloc => {
pos += 8;
}
OpCode::PushBool => {
pos += 1;
}
_ => {}
}
}
}
// Handle debug info
if let Some(debug_info) = &module.debug_info {
for (pc, span) in &debug_info.pc_to_span {
combined_debug_pc_to_span.push((pc + module_code_offsets[i], span.clone()));
}
for (func_idx, name) in &debug_info.function_names {
combined_debug_function_names.push((func_idx + module_function_offsets[i], name.clone()));
}
}
}
let debug_info = if !combined_debug_pc_to_span.is_empty() {
Some(DebugInfo {
pc_to_span: combined_debug_pc_to_span,
function_names: combined_debug_function_names,
})
} else {
None
};
Ok(LinkedProgram {
rom: combined_code,
constant_pool: combined_constants,
functions: combined_functions,
debug_info,
exports,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::opcode::OpCode;
use crate::{BytecodeModule, Export, FunctionMeta, Import};
#[test]
fn test_linker_basic() {
// Module 1: defines 'foo', calls 'bar'
let mut code1 = Vec::new();
// Function 'foo' at offset 0
code1.extend_from_slice(&(OpCode::Call as u16).to_le_bytes());
code1.extend_from_slice(&0u32.to_le_bytes()); // placeholder for 'bar'
code1.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let m1 = BytecodeModule {
version: 0,
const_pool: vec![],
functions: vec![FunctionMeta {
code_offset: 0,
code_len: code1.len() as u32,
..Default::default()
}],
code: code1,
debug_info: None,
exports: vec![Export { symbol: "foo".to_string(), func_idx: 0 }],
imports: vec![Import { symbol: "bar".to_string(), relocation_pcs: vec![0] }],
};
// Module 2: defines 'bar'
let mut code2 = Vec::new();
code2.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let m2 = BytecodeModule {
version: 0,
const_pool: vec![],
functions: vec![FunctionMeta {
code_offset: 0,
code_len: code2.len() as u32,
..Default::default()
}],
code: code2,
debug_info: None,
exports: vec![Export { symbol: "bar".to_string(), func_idx: 0 }],
imports: vec![],
};
let result = ModuleLinker::link(&[m1, m2]).unwrap();
assert_eq!(result.functions.len(), 2);
// 'foo' is func 0, 'bar' is func 1
assert_eq!(result.functions[0].code_offset, 0);
assert_eq!(result.functions[1].code_offset, 8);
// Let's check patched code
let patched_func_id = u32::from_le_bytes(result.rom[2..6].try_into().unwrap());
assert_eq!(patched_func_id, 1); // Points to 'bar'
}
#[test]
fn test_linker_unresolved() {
let m1 = BytecodeModule {
version: 0,
const_pool: vec![],
functions: vec![],
code: vec![],
debug_info: None,
exports: vec![],
imports: vec![Import { symbol: "missing".to_string(), relocation_pcs: vec![] }],
};
let result = ModuleLinker::link(&[m1]);
assert_eq!(result.unwrap_err(), LinkError::UnresolvedSymbol("missing".to_string()));
}
#[test]
fn test_linker_duplicate_export() {
let m1 = BytecodeModule {
version: 0,
const_pool: vec![],
functions: vec![],
code: vec![],
debug_info: None,
exports: vec![Export { symbol: "dup".to_string(), func_idx: 0 }],
imports: vec![],
};
let m2 = m1.clone();
let result = ModuleLinker::link(&[m1, m2]);
assert_eq!(result.unwrap_err(), LinkError::DuplicateExport("dup".to_string()));
}
#[test]
fn test_linker_const_relocation() {
// Module 1: uses constants
let mut code1 = Vec::new();
code1.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
code1.extend_from_slice(&0u32.to_le_bytes()); // Index 0
code1.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let m1 = BytecodeModule {
version: 0,
const_pool: vec![ConstantPoolEntry::Int32(42)],
functions: vec![FunctionMeta { code_offset: 0, code_len: code1.len() as u32, ..Default::default() }],
code: code1,
debug_info: None,
exports: vec![],
imports: vec![],
};
// Module 2: also uses constants
let mut code2 = Vec::new();
code2.extend_from_slice(&(OpCode::PushConst as u16).to_le_bytes());
code2.extend_from_slice(&0u32.to_le_bytes()); // Index 0 (local to module 2)
code2.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let m2 = BytecodeModule {
version: 0,
const_pool: vec![ConstantPoolEntry::Int32(99)],
functions: vec![FunctionMeta { code_offset: 0, code_len: code2.len() as u32, ..Default::default() }],
code: code2,
debug_info: None,
exports: vec![],
imports: vec![],
};
let result = ModuleLinker::link(&[m1, m2]).unwrap();
assert_eq!(result.constant_pool.len(), 2);
assert_eq!(result.constant_pool[0], ConstantPoolEntry::Int32(42));
assert_eq!(result.constant_pool[1], ConstantPoolEntry::Int32(99));
// Code for module 1 (starts at 0)
let idx1 = u32::from_le_bytes(result.rom[2..6].try_into().unwrap());
assert_eq!(idx1, 0);
// Code for module 2 (starts at 8)
let idx2 = u32::from_le_bytes(result.rom[10..14].try_into().unwrap());
assert_eq!(idx2, 1);
}
}

View File

@ -50,8 +50,7 @@ pub fn emit_module(module: &ir_vm::Module) -> Result<EmitResult> {
functions: fragments.functions,
code: fragments.code,
debug_info: fragments.debug_info,
exports,
imports: vec![],
exports,
};
Ok(EmitResult {

View File

@ -448,7 +448,6 @@ mod tests {
code: vec![0x02, 0x00, 0x00, 0x00, 0x00, 0x00],
debug_info: None,
exports: vec![prometeu_bytecode::Export { symbol: "main".into(), func_idx: 0 }],
imports: vec![],
}.serialize();
let cartridge = Cartridge {
app_id: 1234,
@ -505,7 +504,6 @@ mod tests {
],
debug_info: None,
exports: vec![prometeu_bytecode::Export { symbol: "main".into(), func_idx: 0 }],
imports: vec![],
}.serialize();
let cartridge = Cartridge {
app_id: 1234,
@ -721,7 +719,6 @@ mod tests {
],
debug_info: None,
exports: vec![prometeu_bytecode::Export { symbol: "main".into(), func_idx: 0 }],
imports: vec![],
}.serialize();
let cartridge = Cartridge {
app_id: 1234,

View File

@ -4,6 +4,14 @@ use prometeu_bytecode::{BytecodeModule, ConstantPoolEntry, DebugInfo, Export, Fu
use std::collections::HashMap;
use std::sync::Arc;
/// Represents a fully linked, executable PBS program image.
///
/// Under the Prometeu architecture, the ProgramImage is a "closed-world" artifact
/// produced by the compiler. All linking, relocation, and symbol resolution
/// MUST be performed by the compiler before this image is created.
///
/// The runtime (VM) assumes this image is authoritative and performs no
/// additional linking or fixups.
#[derive(Debug, Clone, Default)]
pub struct ProgramImage {
pub rom: Arc<[u8]>,
@ -14,14 +22,7 @@ pub struct ProgramImage {
}
impl ProgramImage {
pub fn new(rom: Vec<u8>, constant_pool: Vec<Value>, mut functions: Vec<FunctionMeta>, debug_info: Option<DebugInfo>, exports: HashMap<String, u32>) -> Self {
if functions.is_empty() && !rom.is_empty() {
functions.push(FunctionMeta {
code_offset: 0,
code_len: rom.len() as u32,
..Default::default()
});
}
pub fn new(rom: Vec<u8>, constant_pool: Vec<Value>, functions: Vec<FunctionMeta>, debug_info: Option<DebugInfo>, exports: HashMap<String, u32>) -> Self {
Self {
rom: Arc::from(rom),
constant_pool: Arc::from(constant_pool),
@ -117,7 +118,6 @@ impl From<ProgramImage> for BytecodeModule {
code: program.rom.as_ref().to_vec(),
debug_info: program.debug_info.clone(),
exports,
imports: vec![],
}
}
}

View File

@ -148,14 +148,13 @@ impl VirtualMachine {
return Err(VmInitError::InvalidFormat);
};
// Resolve the entrypoint: empty (defaults to 0), numeric PC, or symbol name.
// Resolve the entrypoint: empty (defaults to func 0), numeric func_idx, or symbol name.
let pc = if entrypoint.is_empty() {
0
} else if let Ok(addr) = entrypoint.parse::<usize>() {
if addr >= program.rom.len() && (addr > 0 || !program.rom.is_empty()) {
return Err(VmInitError::EntrypointNotFound);
}
addr
program.functions.get(0).map(|f| f.code_offset as usize).unwrap_or(0)
} else if let Ok(func_idx) = entrypoint.parse::<usize>() {
program.functions.get(func_idx)
.map(|f| f.code_offset as usize)
.ok_or(VmInitError::EntrypointNotFound)?
} else {
// Try to resolve as a symbol name from the exports map
if let Some(&func_idx) = program.exports.get(entrypoint) {
@ -163,18 +162,7 @@ impl VirtualMachine {
.map(|f| f.code_offset as usize)
.ok_or(VmInitError::EntrypointNotFound)?
} else {
// Suffix match fallback (e.g. "frame" matches "src/main/modules/main:frame")
let suffix = format!(":{}", entrypoint);
let found = program.exports.iter()
.find(|(name, _)| name == &entrypoint || name.ends_with(&suffix));
if let Some((_, &func_idx)) = found {
program.functions.get(func_idx as usize)
.map(|f| f.code_offset as usize)
.ok_or(VmInitError::EntrypointNotFound)?
} else {
return Err(VmInitError::EntrypointNotFound);
}
return Err(VmInitError::EntrypointNotFound);
}
};
@ -189,33 +177,18 @@ impl VirtualMachine {
/// Prepares the VM to execute a specific entrypoint by setting the PC and
/// pushing an initial call frame.
pub fn prepare_call(&mut self, entrypoint: &str) {
let (addr, func_idx) = if let Ok(addr) = entrypoint.parse::<usize>() {
let idx = self.program.functions.iter().position(|f| {
addr >= f.code_offset as usize && addr < (f.code_offset + f.code_len) as usize
}).unwrap_or(0);
(addr, idx)
let func_idx = if let Ok(idx) = entrypoint.parse::<usize>() {
idx
} else {
// Try to resolve as a symbol name
let found = self.program.exports.get(entrypoint)
.map(|&idx| (idx, entrypoint.to_string()))
.or_else(|| {
let suffix = format!(":{}", entrypoint);
self.program.exports.iter()
.find(|(name, _)| name == &entrypoint || name.ends_with(&suffix))
.map(|(name, &idx)| (idx, name.clone()))
});
if let Some((func_idx, _)) = found {
let addr = self.program.functions.get(func_idx as usize)
.map(|f| f.code_offset as usize)
.unwrap_or(0);
(addr, func_idx as usize)
} else {
// Default to 0 for non-numeric entrypoints that aren't found.
(0, 0)
}
self.program.exports.get(entrypoint)
.map(|&idx| idx as usize)
.ok_or(()).unwrap_or(0) // Default to 0 if not found
};
let callee = self.program.functions.get(func_idx).cloned().unwrap_or_default();
let addr = callee.code_offset as usize;
self.pc = addr;
self.halted = false;
@ -943,6 +916,17 @@ impl VirtualMachine {
#[cfg(test)]
mod tests {
use super::*;
fn new_test_vm(rom: Vec<u8>, constant_pool: Vec<Value>) -> VirtualMachine {
let rom_len = rom.len() as u32;
let mut vm = VirtualMachine::new(rom, constant_pool);
vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta {
code_offset: 0,
code_len: rom_len,
..Default::default()
}]);
vm
}
use crate::hardware::HardwareBridge;
use crate::virtual_machine::{expect_int, HostReturn, Value, VmFault};
use prometeu_bytecode::abi::SourceSpan;
@ -993,7 +977,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Mod as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.run_budget(100, &mut native, &mut hw).unwrap();
assert_eq!(vm.pop().unwrap(), Value::Int32(0));
@ -1012,7 +996,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Div as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
match report.reason {
@ -1035,7 +1019,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::IntToBoundChecked as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
match report.reason {
@ -1060,7 +1044,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Add as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
match report.reason {
@ -1086,7 +1070,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Lt as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.run_budget(100, &mut native, &mut hw).unwrap();
assert_eq!(vm.pop().unwrap(), Value::Boolean(true));
}
@ -1098,7 +1082,7 @@ mod tests {
rom.extend_from_slice(&42i64.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let mut native = MockNative;
let mut hw = MockHardware;
@ -1113,7 +1097,7 @@ mod tests {
rom.extend_from_slice(&3.14f64.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let mut native = MockNative;
let mut hw = MockHardware;
@ -1132,7 +1116,7 @@ mod tests {
rom.push(0);
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let mut native = MockNative;
let mut hw = MockHardware;
@ -1328,7 +1312,7 @@ mod tests {
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 = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let mut native = MockNative;
let mut hw = MockHardware;
@ -1427,7 +1411,7 @@ mod tests {
rom.extend_from_slice(&42i32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let mut native = MockNative;
let mut hw = MockHardware;
@ -1449,7 +1433,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::BitAnd as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
@ -1464,7 +1448,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::BitOr as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
@ -1485,7 +1469,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Lte as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
@ -1500,7 +1484,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Gte as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
@ -1518,7 +1502,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Neg as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.step(&mut native, &mut hw).unwrap();
vm.step(&mut native, &mut hw).unwrap();
assert_eq!(vm.pop().unwrap(), Value::Int32(-42));
@ -1549,7 +1533,7 @@ mod tests {
rom.extend_from_slice(&100i32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.step(&mut native, &mut hw).unwrap(); // PushBool
vm.step(&mut native, &mut hw).unwrap(); // JmpIfTrue
assert_eq!(vm.pc, 11);
@ -1568,7 +1552,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Trap as u16).to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
assert_eq!(report.reason, LogicalFrameEndingReason::Breakpoint);
@ -1592,7 +1576,7 @@ mod tests {
rom.extend_from_slice(&2u32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.run_budget(100, &mut native, &mut hw).unwrap();
assert_eq!(vm.pop().unwrap(), Value::Int32(1));
@ -1614,7 +1598,7 @@ mod tests {
rom.extend_from_slice(&1u32.to_le_bytes()); // offset 1
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
match report.reason {
@ -1641,7 +1625,7 @@ mod tests {
rom.extend_from_slice(&0u32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
match report.reason {
@ -1662,7 +1646,12 @@ mod tests {
0x11, 0x00, // Pop
0x51, 0x00 // Ret
];
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = VirtualMachine::new(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta {
code_offset: 0,
code_len: rom.len() as u32,
..Default::default()
}]);
let mut hw = crate::Hardware::new();
struct TestNative;
impl NativeInterface for TestNative {
@ -1692,7 +1681,12 @@ mod tests {
}
}
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = VirtualMachine::new(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta {
code_offset: 0,
code_len: rom.len() as u32,
..Default::default()
}]);
let mut native = MultiReturnNative;
let mut hw = crate::Hardware::new();
@ -1718,7 +1712,12 @@ mod tests {
}
}
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = VirtualMachine::new(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta {
code_offset: 0,
code_len: rom.len() as u32,
..Default::default()
}]);
let mut native = VoidReturnNative;
let mut hw = crate::Hardware::new();
@ -1748,7 +1747,12 @@ mod tests {
}
}
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = VirtualMachine::new(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![prometeu_bytecode::FunctionMeta {
code_offset: 0,
code_len: rom.len() as u32,
..Default::default()
}]);
let mut native = ArgCheckNative;
let mut hw = crate::Hardware::new();
@ -1770,7 +1774,7 @@ mod tests {
0x70, 0x00, // Syscall + Reserved
0xEF, 0xBE, 0xAD, 0xDE, // 0xDEADBEEF
];
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let mut native = MockNative;
let mut hw = MockHardware;
@ -1795,7 +1799,7 @@ mod tests {
0x70, 0x00, // Syscall + Reserved
0x01, 0x10, 0x00, 0x00, // Syscall ID 0x1001
];
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let mut native = MockNative;
let mut hw = MockHardware;
@ -1832,7 +1836,7 @@ mod tests {
}
}
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let mut native = BadNative;
let mut hw = MockHardware;
@ -1962,7 +1966,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let f1_len = rom.len() as u32 - f1_start;
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![
FunctionMeta {
code_offset: f0_start as u32,
@ -2014,7 +2018,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let f1_len = rom.len() as u32 - f1_start;
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![
FunctionMeta {
code_offset: f0_start as u32,
@ -2065,7 +2069,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let f1_len = rom.len() as u32 - f1_start;
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![
FunctionMeta {
code_offset: f0_start as u32,
@ -2097,7 +2101,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Call as u16).to_le_bytes());
rom.extend_from_slice(&99u32.to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
let report = vm.run_budget(100, &mut native, &mut hw).unwrap();
match report.reason {
@ -2130,7 +2134,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let f1_len = rom.len() as u32 - f1_start;
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![
FunctionMeta {
code_offset: f0_start as u32,
@ -2180,7 +2184,7 @@ mod tests {
rom.extend_from_slice(&0u32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
code_offset: 0,
code_len: 20,
@ -2234,7 +2238,7 @@ mod tests {
rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
let f1_len = rom.len() as u32 - f1_start;
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![
FunctionMeta {
code_offset: f0_start as u32,
@ -2271,7 +2275,7 @@ mod tests {
rom.extend_from_slice(&1u32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
code_offset: 0,
code_len: 8,
@ -2346,7 +2350,7 @@ mod tests {
// 48: HALT
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
// We need to set up the function meta for absolute jumps to work correctly
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
code_offset: 0,
@ -2385,7 +2389,7 @@ mod tests {
// 15-16: HALT
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
code_offset: 0,
code_len: 17,
@ -2413,7 +2417,7 @@ mod tests {
rom.extend_from_slice(&9u32.to_le_bytes());
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
let mut vm = VirtualMachine::new(rom, vec![]);
let mut vm = new_test_vm(rom.clone(), vec![]);
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
code_offset: 0,
code_len: 14,
@ -2454,7 +2458,11 @@ mod tests {
function_names: vec![(0, "main".to_string())],
};
let program = ProgramImage::new(rom, vec![], vec![], Some(debug_info), std::collections::HashMap::new());
let program = ProgramImage::new(rom.clone(), vec![], vec![FunctionMeta {
code_offset: 0,
code_len: rom.len() as u32,
..Default::default()
}], Some(debug_info), std::collections::HashMap::new());
let mut vm = VirtualMachine {
program,
..Default::default()

View File

@ -39,20 +39,13 @@ fn test_canonical_cartridge_heartbeat() {
let pbc_bytes = fs::read(pbc_path).expect("Failed to read canonical PBC. Did you run the generation test?");
// Determine numeric entrypoint PC from the compiled module exports
// Determine entrypoint from the compiled module exports
let module = BytecodeLoader::load(&pbc_bytes).expect("Failed to parse PBC");
let func_idx = module
.exports
.iter()
.find(|e| e.symbol == "src/main/modules:frame")
.map(|e| e.func_idx as usize)
.expect("Entrypoint symbol not found in exports");
let entry_pc = module.functions[func_idx].code_offset as usize;
let entry_pc_str = entry_pc.to_string();
let entry_symbol = "src/main/modules:frame";
let mut vm = VirtualMachine::new(vec![], vec![]);
vm.initialize(pbc_bytes, &entry_pc_str).expect("Failed to initialize VM with canonical cartridge");
vm.prepare_call(&entry_pc_str);
vm.initialize(pbc_bytes, entry_symbol).expect("Failed to initialize VM with canonical cartridge");
vm.prepare_call(entry_symbol);
let mut native = MockNative;
let mut hw = Hardware::new();