pr 06
This commit is contained in:
parent
99f024eaa9
commit
bfcdc5538d
@ -7,7 +7,6 @@ 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 {
|
||||
@ -210,7 +209,7 @@ impl Linker {
|
||||
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 => {
|
||||
OpCode::PushConst | OpCode::Call => {
|
||||
match instr.imm_u32() {
|
||||
Ok(v) => Some(v),
|
||||
Err(_) => None,
|
||||
@ -255,15 +254,9 @@ impl Linker {
|
||||
patch_u32_at(&mut combined_code, imm_start, &|_| global_func_idx);
|
||||
}
|
||||
}
|
||||
// Branches are strictly function-relative. Do NOT relocate.
|
||||
// Branches are strictly function-relative. Do NOT relocate or inspect immediates.
|
||||
// The emitter encodes `target_rel = label - func_start` and the verifier enforces it.
|
||||
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => {
|
||||
let _ = imm_u32_opt.ok_or_else(|| LinkError::IncompatibleSymbolSignature(format!(
|
||||
"Invalid branch immediate at pc {}",
|
||||
pc - code_offset
|
||||
)))?;
|
||||
// Intentionally no patch here.
|
||||
}
|
||||
OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue => { /* no-op */ }
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@ -640,4 +633,76 @@ mod tests {
|
||||
let cjmp_patched = u32::from_le_bytes(result.rom[cjmp_imm_off..cjmp_imm_off+4].try_into().unwrap());
|
||||
assert_eq!(cjmp_patched, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jump_link_order_invariance() {
|
||||
// Same setup as previous test, but link order is [m2, m1]
|
||||
let key1 = ProjectKey { name: "m1".into(), version: "1.0.0".into() };
|
||||
let id1 = ProjectId(0);
|
||||
let step1 = BuildStep { project_id: id1, project_key: key1.clone(), project_dir: "".into(), target: BuildTarget::Main, sources: vec![], deps: BTreeMap::new() };
|
||||
|
||||
let mut code1 = Vec::new();
|
||||
code1.extend_from_slice(&(OpCode::Add as u16).to_le_bytes());
|
||||
code1.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes());
|
||||
let m1 = CompiledModule {
|
||||
project_id: id1,
|
||||
project_key: key1.clone(),
|
||||
target: BuildTarget::Main,
|
||||
exports: BTreeMap::new(),
|
||||
imports: vec![],
|
||||
const_pool: vec![],
|
||||
code: code1.clone(),
|
||||
function_metas: vec![FunctionMeta { code_offset: 0, code_len: code1.len() as u32, ..Default::default() }],
|
||||
debug_info: None,
|
||||
symbols: vec![],
|
||||
};
|
||||
|
||||
let key2 = ProjectKey { name: "m2".into(), version: "1.0.0".into() };
|
||||
let id2 = ProjectId(1);
|
||||
let step2 = BuildStep { project_id: id2, project_key: key2.clone(), project_dir: "".into(), target: BuildTarget::Main, sources: vec![], deps: BTreeMap::new() };
|
||||
|
||||
let mut code2 = Vec::new();
|
||||
let jmp_pc = code2.len() as u32; // where opcode will be placed
|
||||
code2.extend_from_slice(&(OpCode::Jmp as u16).to_le_bytes());
|
||||
code2.extend_from_slice(&0u32.to_le_bytes());
|
||||
|
||||
code2.extend_from_slice(&(OpCode::PushBool as u16).to_le_bytes());
|
||||
code2.push(1u8);
|
||||
let cjmp_pc = code2.len() as u32;
|
||||
code2.extend_from_slice(&(OpCode::JmpIfTrue as u16).to_le_bytes());
|
||||
code2.extend_from_slice(&0u32.to_le_bytes());
|
||||
|
||||
code2.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||
|
||||
let m2 = CompiledModule {
|
||||
project_id: id2,
|
||||
project_key: key2.clone(),
|
||||
target: BuildTarget::Main,
|
||||
exports: BTreeMap::new(),
|
||||
imports: vec![],
|
||||
const_pool: vec![],
|
||||
code: code2.clone(),
|
||||
function_metas: vec![FunctionMeta { code_offset: 0, code_len: code2.len() as u32, ..Default::default() }],
|
||||
debug_info: None,
|
||||
symbols: vec![],
|
||||
};
|
||||
|
||||
// Link with order [m2, m1]
|
||||
let result = Linker::link(vec![m2, m1], vec![step2, step1]).unwrap();
|
||||
|
||||
// Module 2 is now at offset 0
|
||||
let module2_offset = 0usize;
|
||||
|
||||
// Verify that the JMP immediate remains function-relative (0), no relocation applied
|
||||
let jmp_abs_pc = module2_offset + jmp_pc as usize;
|
||||
let jmp_imm_off = jmp_abs_pc + 2; // skip opcode
|
||||
let jmp_patched = u32::from_le_bytes(result.rom[jmp_imm_off..jmp_imm_off+4].try_into().unwrap());
|
||||
assert_eq!(jmp_patched, 0);
|
||||
|
||||
// Verify that the conditional JMP immediate also remains function-relative (0)
|
||||
let cjmp_abs_pc = module2_offset + cjmp_pc as usize;
|
||||
let cjmp_imm_off = cjmp_abs_pc + 2;
|
||||
let cjmp_patched = u32::from_le_bytes(result.rom[cjmp_imm_off..cjmp_imm_off+4].try_into().unwrap());
|
||||
assert_eq!(cjmp_patched, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user