From bfcdc5538d1b2095904362aebbcb0c35c0679735 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Tue, 10 Feb 2026 08:49:22 +0000 Subject: [PATCH] pr 06 --- .../prometeu-compiler/src/building/linker.rs | 85 ++++++++++++++++--- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/crates/prometeu-compiler/src/building/linker.rs b/crates/prometeu-compiler/src/building/linker.rs index f75fa50e..cd79a06e 100644 --- a/crates/prometeu-compiler/src/building/linker.rs +++ b/crates/prometeu-compiler/src/building/linker.rs @@ -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); + } }