This commit is contained in:
bQUARKz 2026-02-10 08:49:22 +00:00
parent 99f024eaa9
commit bfcdc5538d
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8

View File

@ -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);
}
}