pr 04
This commit is contained in:
parent
cf94ecda84
commit
6eda2d21ac
@ -62,10 +62,7 @@ pub struct TrapInfo {
|
||||
|
||||
/// Checks if an instruction is a jump (branch) instruction.
|
||||
pub fn is_jump(opcode: OpCode) -> bool {
|
||||
match opcode {
|
||||
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => true,
|
||||
_ => false,
|
||||
}
|
||||
opcode.spec().is_branch
|
||||
}
|
||||
|
||||
/// Checks if an instruction has any immediate operands in the instruction stream.
|
||||
|
||||
11
crates/prometeu-bytecode/src/io.rs
Normal file
11
crates/prometeu-bytecode/src/io.rs
Normal file
@ -0,0 +1,11 @@
|
||||
pub fn read_u32_le(buf: &[u8], pos: usize) -> Option<u32> {
|
||||
let b = buf.get(pos..pos + 4)?;
|
||||
Some(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
|
||||
}
|
||||
|
||||
pub fn write_u32_le(buf: &mut [u8], pos: usize, v: u32) -> Option<()> {
|
||||
let b = buf.get_mut(pos..pos + 4)?;
|
||||
let le = v.to_le_bytes();
|
||||
b.copy_from_slice(&le);
|
||||
Some(())
|
||||
}
|
||||
@ -53,12 +53,3 @@ pub fn function_index_by_pc(functions: &[FunctionMeta], code_len_total: usize, p
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Clamps an absolute jump target to the end (exclusive) of the enclosing
|
||||
/// function identified by `func_idx`.
|
||||
#[inline]
|
||||
pub fn clamp_jump_target(functions: &[FunctionMeta], code_len_total: usize, func_idx: usize, target_abs: u32) -> u32 {
|
||||
let start = functions.get(func_idx).map(|f| f.code_offset as usize).unwrap_or(0);
|
||||
let end = function_end_from_next(functions, func_idx, code_len_total);
|
||||
if (target_abs as usize) > end { end as u32 } else { target_abs }
|
||||
}
|
||||
|
||||
@ -23,5 +23,6 @@ pub mod layout;
|
||||
pub mod decoder;
|
||||
|
||||
mod model;
|
||||
pub mod io;
|
||||
|
||||
pub use model::*;
|
||||
|
||||
@ -2,14 +2,16 @@ use crate::building::output::CompiledModule;
|
||||
use crate::building::plan::BuildStep;
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::layout;
|
||||
use prometeu_bytecode::opcode_spec::OpCodeSpecExt;
|
||||
use prometeu_bytecode::decoder::decode_next;
|
||||
use prometeu_bytecode::{ConstantPoolEntry, DebugInfo};
|
||||
use std::collections::HashMap;
|
||||
use prometeu_abi::virtual_machine::{ProgramImage, Value};
|
||||
use prometeu_analysis::ids::ProjectId;
|
||||
use prometeu_bytecode::readwrite::{read_u32_le, write_u32_le};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum LinkError {
|
||||
OutOfBounds(usize, usize),
|
||||
UnresolvedSymbol(String),
|
||||
DuplicateExport(String),
|
||||
IncompatibleSymbolSignature(String),
|
||||
@ -18,6 +20,7 @@ pub enum LinkError {
|
||||
impl std::fmt::Display for LinkError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LinkError::OutOfBounds(pos, len) => write!(f, "Out of bounds: pos={} len={}", pos, len),
|
||||
LinkError::UnresolvedSymbol(s) => write!(f, "Unresolved symbol: {}", s),
|
||||
LinkError::DuplicateExport(s) => write!(f, "Duplicate export: {}", s),
|
||||
LinkError::IncompatibleSymbolSignature(s) => write!(f, "Incompatible symbol signature: {}", s),
|
||||
@ -64,6 +67,7 @@ impl ConstantPoolBitKey {
|
||||
}
|
||||
|
||||
impl Linker {
|
||||
|
||||
pub fn link(modules: Vec<CompiledModule>, steps: Vec<BuildStep>) -> Result<ProgramImage, LinkError> {
|
||||
if modules.len() != steps.len() {
|
||||
return Err(LinkError::IncompatibleSymbolSignature(format!("Module count ({}) does not match build steps count ({})", modules.len(), steps.len())));
|
||||
@ -195,72 +199,75 @@ impl Linker {
|
||||
}
|
||||
}
|
||||
|
||||
// Internal call relocation (from module-local func_idx to global func_idx)
|
||||
// And PUSH_CONST relocation.
|
||||
// Also relocate intra-module jump target addresses when modules are concatenated.
|
||||
|
||||
// Small helper to patch a 32-bit immediate at `pos` using a transformer function.
|
||||
// Safety: caller must ensure `pos + 4 <= end`.
|
||||
let mut patch_u32_at = |buf: &mut Vec<u8>, pos: usize, f: &dyn Fn(u32) -> u32| {
|
||||
let current = u32::from_le_bytes(buf[pos..pos+4].try_into().unwrap());
|
||||
let next = f(current);
|
||||
buf[pos..pos+4].copy_from_slice(&next.to_le_bytes());
|
||||
};
|
||||
let mut pos = code_offset;
|
||||
let mut pc = 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;
|
||||
while pc < end {
|
||||
// Scope the immutable borrow from decode_next so we can mutate combined_code afterwards
|
||||
let (opcode, next_pc, imm_start, imm_u32_opt) = {
|
||||
match decode_next(pc, &combined_code) {
|
||||
Ok(instr) => {
|
||||
let opcode = instr.opcode;
|
||||
let next_pc = instr.next_pc;
|
||||
let imm_start = instr.pc + 2; // start of immediate payload
|
||||
let imm_u32_opt = match opcode {
|
||||
OpCode::PushConst | OpCode::Call | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => {
|
||||
match instr.imm_u32() {
|
||||
Ok(v) => Some(v),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
(opcode, next_pc, imm_start, imm_u32_opt)
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(LinkError::IncompatibleSymbolSignature(format!(
|
||||
"Bytecode decode error at pc {}: {:?}",
|
||||
pc - code_offset, e
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
pos += 2;
|
||||
|
||||
let imm_len = opcode.spec().imm_bytes as usize;
|
||||
match opcode {
|
||||
OpCode::PushConst => {
|
||||
if pos + imm_len <= end && imm_len == 4 {
|
||||
let local_idx = u32::from_le_bytes(combined_code[pos..pos+4].try_into().unwrap()) as usize;
|
||||
if let Some(&global_idx) = local_to_global_const.get(local_idx) {
|
||||
combined_code[pos..pos+4].copy_from_slice(&global_idx.to_le_bytes());
|
||||
}
|
||||
let local_idx = imm_u32_opt.ok_or_else(|| LinkError::IncompatibleSymbolSignature(format!(
|
||||
"Invalid PUSH_CONST immediate at pc {}",
|
||||
pc - code_offset
|
||||
)))? as usize;
|
||||
if let Some(&global_idx) = local_to_global_const.get(local_idx) {
|
||||
patch_u32_at(&mut combined_code, imm_start, &|_| global_idx);
|
||||
}
|
||||
pos += imm_len;
|
||||
}
|
||||
OpCode::Call => {
|
||||
if pos + imm_len <= end && imm_len == 4 {
|
||||
let local_func_idx = u32::from_le_bytes(combined_code[pos..pos+4].try_into().unwrap());
|
||||
|
||||
// Check if this PC was already patched by an import.
|
||||
// If it wasn't, it's an internal call that needs relocation.
|
||||
// `import.relocation_pcs` holds the PC at the start of the CALL immediate (after opcode),
|
||||
// and here `pos` currently points exactly at that immediate.
|
||||
let reloc_pc = (pos - code_offset) as u32;
|
||||
let is_import = module.imports.iter().any(|imp| imp.relocation_pcs.contains(&reloc_pc));
|
||||
|
||||
if !is_import {
|
||||
let global_func_idx = module_function_offsets[i] + local_func_idx;
|
||||
combined_code[pos..pos+4].copy_from_slice(&global_func_idx.to_le_bytes());
|
||||
}
|
||||
let local_func_idx = imm_u32_opt.ok_or_else(|| LinkError::IncompatibleSymbolSignature(format!(
|
||||
"Invalid CALL immediate at pc {}",
|
||||
pc - code_offset
|
||||
)))?;
|
||||
// Determine if this CALL site corresponds to an import relocation.
|
||||
let reloc_rel_pc = (imm_start - code_offset) as u32;
|
||||
let is_import = module
|
||||
.imports
|
||||
.iter()
|
||||
.any(|imp| imp.relocation_pcs.contains(&reloc_rel_pc));
|
||||
if !is_import {
|
||||
let global_func_idx = module_function_offsets[i] + local_func_idx;
|
||||
patch_u32_at(&mut combined_code, imm_start, &|_| global_func_idx);
|
||||
}
|
||||
pos += imm_len;
|
||||
}
|
||||
// Relocate intra-function control-flow immediates by module code offset to preserve absolute PCs
|
||||
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => {
|
||||
if pos + imm_len <= end && imm_len == 4 {
|
||||
patch_u32_at(&mut combined_code, pos, &|cur| cur + (code_offset as u32));
|
||||
}
|
||||
pos += imm_len;
|
||||
}
|
||||
_ => {
|
||||
// Generic advance using canonical immediate length.
|
||||
pos += imm_len;
|
||||
// For branches, immediate must be present and represents rel PC from function start
|
||||
let _ = imm_u32_opt.ok_or_else(|| LinkError::IncompatibleSymbolSignature(format!(
|
||||
"Invalid branch immediate at pc {}",
|
||||
pc - code_offset
|
||||
)))?;
|
||||
patch_u32_at(&mut combined_code, imm_start, &|cur| cur + (code_offset as u32));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
pc = next_pc;
|
||||
}
|
||||
}
|
||||
|
||||
@ -384,6 +391,17 @@ impl Linker {
|
||||
}
|
||||
}
|
||||
|
||||
fn patch_u32_at(
|
||||
buf: &mut [u8],
|
||||
pos: usize,
|
||||
f: impl FnOnce(u32) -> u32,
|
||||
) -> Result<(), LinkError> {
|
||||
let current = prometeu_bytecode::io::read_u32_le(buf, pos).ok_or(LinkError::OutOfBounds(pos, buf.len()))?;
|
||||
let next = f(current);
|
||||
prometeu_bytecode::io::write_u32_le(buf, pos, next).ok_or(LinkError::OutOfBounds(pos, buf.len()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@ -248,7 +248,6 @@ mod tests {
|
||||
#[test]
|
||||
fn test_framesync_injected_end_to_end() {
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::opcode_spec::OpCodeSpecExt;
|
||||
let dir = tempdir().unwrap();
|
||||
let project_dir = dir.path().to_path_buf();
|
||||
fs::create_dir_all(project_dir.join("src/main/modules")).unwrap();
|
||||
@ -280,22 +279,15 @@ mod tests {
|
||||
let end = (meta.code_offset + meta.code_len) as usize;
|
||||
let code = &res.image.rom[start..end];
|
||||
|
||||
// Decode sequentially: each instruction is a u16 opcode (LE) followed by operands.
|
||||
// We'll walk forward and record the sequence of opcodes, ignoring operands based on known sizes.
|
||||
fn operand_size(op: u16) -> usize {
|
||||
match OpCode::try_from(op) {
|
||||
Ok(opc) => opc.spec().imm_bytes as usize,
|
||||
Err(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Decode sequentially using the canonical decoder; record opcode stream.
|
||||
let mut pcs = Vec::new();
|
||||
let mut i = 0usize;
|
||||
while i + 1 < code.len() {
|
||||
let op = u16::from_le_bytes([code[i], code[i + 1]]);
|
||||
pcs.push(op);
|
||||
i += 2 + operand_size(op);
|
||||
while i < code.len() {
|
||||
let instr = prometeu_bytecode::decoder::decode_next(i, code).expect("decoder should succeed");
|
||||
pcs.push(instr.opcode as u16);
|
||||
i = instr.next_pc;
|
||||
}
|
||||
assert_eq!(i, code.len(), "decoder must end exactly at function end");
|
||||
|
||||
assert!(pcs.len() >= 2);
|
||||
let last = *pcs.last().unwrap();
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
// Re-export canonical decoder from prometeu-bytecode to eliminate bespoke implementation in VM.
|
||||
pub use prometeu_bytecode::decoder::{decode_next, DecodeError, DecodedInstr};
|
||||
|
||||
/// Backwards-compatible shim for legacy call sites; delegates to canonical decoder.
|
||||
#[inline]
|
||||
pub fn decode_at(rom: &[u8], pc: usize) -> Result<DecodedInstr<'_>, DecodeError> {
|
||||
decode_next(pc, rom)
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
pub mod decoder;
|
||||
@ -2,8 +2,6 @@ mod virtual_machine;
|
||||
mod call_frame;
|
||||
mod scope_frame;
|
||||
pub mod local_addressing;
|
||||
pub mod opcode_spec;
|
||||
pub mod bytecode;
|
||||
pub mod verifier;
|
||||
|
||||
pub use prometeu_abi::virtual_machine::program::ProgramImage;
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
// Canonical `OpcodeSpec` now lives in `prometeu-bytecode`.
|
||||
// Keep this module as a thin re-export for compatibility.
|
||||
pub use prometeu_bytecode::opcode_spec::*;
|
||||
@ -1,5 +1,5 @@
|
||||
use prometeu_abi::syscalls::Syscall;
|
||||
use crate::bytecode::decoder::{decode_at, DecodeError};
|
||||
use prometeu_bytecode::decoder::{decode_next, DecodeError};
|
||||
use prometeu_bytecode::opcode::OpCode;
|
||||
use prometeu_bytecode::opcode_spec::OpCodeSpecExt;
|
||||
use prometeu_bytecode::FunctionMeta;
|
||||
@ -62,7 +62,7 @@ impl Verifier {
|
||||
let mut pc = 0;
|
||||
while pc < func_code.len() {
|
||||
valid_pc.insert(pc);
|
||||
let instr = decode_at(func_code, pc).map_err(|e| match e {
|
||||
let instr = decode_next(pc, func_code).map_err(|e| match e {
|
||||
DecodeError::UnknownOpcode { pc: _, opcode } =>
|
||||
VerifierError::UnknownOpcode { pc: func_start + pc, opcode },
|
||||
DecodeError::TruncatedOpcode { pc: _ } =>
|
||||
@ -89,7 +89,7 @@ impl Verifier {
|
||||
|
||||
while let Some(pc) = worklist.pop_front() {
|
||||
let in_height = *stack_height_in.get(&pc).unwrap();
|
||||
let instr = decode_at(func_code, pc).unwrap(); // Guaranteed to succeed due to first pass
|
||||
let instr = decode_next(pc, func_code).unwrap(); // Guaranteed to succeed due to first pass
|
||||
let spec = instr.opcode.spec();
|
||||
|
||||
// Resolve dynamic pops/pushes
|
||||
|
||||
@ -366,7 +366,7 @@ impl VirtualMachine {
|
||||
let start_pc = self.pc;
|
||||
|
||||
// Fetch & Decode
|
||||
let instr = crate::bytecode::decoder::decode_at(&self.program.rom, self.pc)
|
||||
let instr = prometeu_bytecode::decoder::decode_next(self.pc, &self.program.rom)
|
||||
.map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?;
|
||||
|
||||
let opcode = instr.opcode;
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user