diff --git a/aarchmrs-instructions/src/lib.rs b/aarchmrs-instructions/src/lib.rs index 59138da..e3ed4a7 100644 --- a/aarchmrs-instructions/src/lib.rs +++ b/aarchmrs-instructions/src/lib.rs @@ -15,7 +15,7 @@ instruction variant described in the dataset, a corresponding Rust function is generated. The source code of this crate is generated by tools at the same repository at -https://github.com/monoid/harm. +. As with the original dataset, this code is licensed under BSD-3-Clause license. diff --git a/harm/src/instructions/control/branch_imm.rs b/harm/src/instructions/control/branch_imm.rs index 70ad4ed..8db9b48 100644 --- a/harm/src/instructions/control/branch_imm.rs +++ b/harm/src/instructions/control/branch_imm.rs @@ -127,7 +127,7 @@ impl RelocatableInstruction for Branch { let zero = BranchOffset::default(); let code = B_only_branch_imm(zero.into()); - let reloc = Rel64::Jump26(self.0); + let reloc = Rel64::jump26(self.0); (code, Some(reloc)) } @@ -186,7 +186,7 @@ impl RelocatableInstruction for Branch<(BranchCond, LabelRef)> { let (cond, label_ref) = self.0; let code = B_only_condbranch(zero.into(), cond.into()); - let reloc = Rel64::CondBr19(label_ref); + let reloc = Rel64::cond_br19(label_ref); (code, Some(reloc)) } @@ -254,7 +254,7 @@ impl RelocatableInstruction for BranchLink { let zero = BranchOffset::default(); let code = BL_only_branch_imm(zero.into()); - let reloc = Rel64::Call26(self.0); + let reloc = Rel64::call26(self.0); (code, Some(reloc)) } @@ -394,7 +394,7 @@ impl RelocatableInstruction for CompareBranch { } .to_code(); - let reloc = Rel64::CondBr19(self.offset); + let reloc = Rel64::cond_br19(self.offset); (code, Some(reloc)) } @@ -425,7 +425,7 @@ impl RelocatableInstruction for CompareBranch { } .to_code(); - let reloc = Rel64::CondBr19(self.offset); + let reloc = Rel64::cond_br19(self.offset); (code, Some(reloc)) } @@ -712,7 +712,7 @@ mod tests { let (code, reloc) = inst.to_code_with_reloc(); assert_eq!(code, cbz(X3, CompareBranchOffset::default()).to_code()); - assert_eq!(reloc, Some(Rel64::CondBr19(label))); + assert_eq!(reloc, Some(Rel64::cond_br19(label))); } #[test] @@ -725,7 +725,7 @@ mod tests { let (code, reloc) = inst.to_code_with_reloc(); assert_eq!(code, cbz(W4, CompareBranchOffset::default()).to_code()); - assert_eq!(reloc, Some(Rel64::CondBr19(label))); + assert_eq!(reloc, Some(Rel64::cond_br19(label))); } #[test] @@ -738,7 +738,7 @@ mod tests { let (code, reloc) = inst.to_code_with_reloc(); assert_eq!(code, cbnz(X5, CompareBranchOffset::default()).to_code()); - assert_eq!(reloc, Some(Rel64::CondBr19(label))); + assert_eq!(reloc, Some(Rel64::cond_br19(label))); } #[test] @@ -751,6 +751,6 @@ mod tests { let (code, reloc) = inst.to_code_with_reloc(); assert_eq!(code, cbnz(W6, CompareBranchOffset::default()).to_code()); - assert_eq!(reloc, Some(Rel64::CondBr19(label))); + assert_eq!(reloc, Some(Rel64::cond_br19(label))); } } diff --git a/harm/src/instructions/control/testbranch.rs b/harm/src/instructions/control/testbranch.rs index e428f75..8bee907 100644 --- a/harm/src/instructions/control/testbranch.rs +++ b/harm/src/instructions/control/testbranch.rs @@ -128,7 +128,7 @@ impl RelocatableInstruction for TestBranch Rel64 { - Rel64::MovWAbsG0(self.0) + Rel64::mov_w_abs_g0(self.0) } } @@ -176,7 +176,7 @@ impl MoveWideReloc64 for AbsG0Nc { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG0Nc(self.0) + Rel64::mov_w_abs_g0nc(self.0) } } @@ -188,7 +188,7 @@ impl MoveWideReloc64 for AbsG0S { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG0S(self.0) + Rel64::mov_w_abs_g0s(self.0) } } @@ -200,7 +200,7 @@ impl MoveWideReloc64 for AbsG1 { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG1(self.0) + Rel64::mov_w_abs_g1(self.0) } } @@ -212,7 +212,7 @@ impl MoveWideReloc64 for AbsG1Nc { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG1Nc(self.0) + Rel64::mov_w_abs_g1nc(self.0) } } @@ -224,7 +224,7 @@ impl MoveWideReloc64 for AbsG1S { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG1S(self.0) + Rel64::mov_w_abs_g1s(self.0) } } @@ -236,7 +236,7 @@ impl MoveWideReloc64 for AbsG2 { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG2(self.0) + Rel64::mov_w_abs_g2(self.0) } } @@ -248,7 +248,7 @@ impl MoveWideReloc64 for AbsG2Nc { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG2Nc(self.0) + Rel64::mov_w_abs_g2nc(self.0) } } @@ -260,7 +260,7 @@ impl MoveWideReloc64 for AbsG2S { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG2S(self.0) + Rel64::mov_w_abs_g2s(self.0) } } @@ -272,7 +272,7 @@ impl MoveWideReloc64 for AbsG3 { #[inline] fn get_relocation(&self) -> Rel64 { - Rel64::MovWAbsG3(self.0) + Rel64::mov_w_abs_g3(self.0) } } @@ -414,7 +414,7 @@ mod tests { }; let inst = movz(X1, abs_g0(label)).to_code_with_reloc(); assert_eq!(inst.0, movz(X1, (0, 0)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG0(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g0(label))); } #[test] @@ -425,7 +425,7 @@ mod tests { }; let inst = movk(X2, abs_g1(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(X2, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1(label))); } #[test] @@ -437,7 +437,7 @@ mod tests { // Makes little sense, but gas accepts it. let inst = movn(X3, abs_g1_s(label)).to_code_with_reloc(); assert_eq!(inst.0, movn(X3, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1S(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1s(label))); } #[test] @@ -448,7 +448,7 @@ mod tests { }; let inst = movk(X4, abs_g1_nc(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(X4, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1Nc(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1nc(label))); } #[test] @@ -459,7 +459,7 @@ mod tests { }; let inst = movz(X5, abs_g2(label)).to_code_with_reloc(); assert_eq!(inst.0, movz(X5, (0, 32)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG2(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g2(label))); } #[test] @@ -470,7 +470,7 @@ mod tests { }; let inst = movz(X6, abs_g2_s(label)).to_code_with_reloc(); assert_eq!(inst.0, movz(X6, (0, 32)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG2S(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g2s(label))); } #[test] @@ -481,7 +481,7 @@ mod tests { }; let inst = movk(X7, abs_g2_nc(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(X7, (0, 32)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG2Nc(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g2nc(label))); } #[test] @@ -492,7 +492,7 @@ mod tests { }; let inst = movk(X8, abs_g3(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(X8, (0, 48)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG3(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g3(label))); } #[test] @@ -503,7 +503,7 @@ mod tests { }; let inst = movz(W1, abs_g0(label)).to_code_with_reloc(); assert_eq!(inst.0, movz(W1, (0, 0)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG0(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g0(label))); } #[test] @@ -514,7 +514,7 @@ mod tests { }; let inst = movk(W2, abs_g1(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(W2, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1(label))); } #[test] @@ -526,7 +526,7 @@ mod tests { // Makes little sense, but gas accepts it. let inst = movn(W3, abs_g1_s(label)).to_code_with_reloc(); assert_eq!(inst.0, movn(W3, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1S(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1s(label))); } #[test] @@ -537,6 +537,6 @@ mod tests { }; let inst = movk(W4, abs_g1_nc(label)).to_code_with_reloc(); assert_eq!(inst.0, movk(W4, (0, 16)).unwrap().to_code()); - assert_eq!(inst.1, Some(Rel64::MovWAbsG1Nc(label))); + assert_eq!(inst.1, Some(Rel64::mov_w_abs_g1nc(label))); } } diff --git a/harm/src/instructions/ldst.rs b/harm/src/instructions/ldst.rs index 23e71aa..3a1e2c1 100644 --- a/harm/src/instructions/ldst.rs +++ b/harm/src/instructions/ldst.rs @@ -67,6 +67,7 @@ pub type ScaledOffset8 = UBitValue<12, 0>; pub type ScaledOffset16 = UBitValue<12, 1>; pub type ScaledOffset32 = UBitValue<12, 2>; pub type ScaledOffset64 = UBitValue<12, 3>; +pub type ScaledOffset128 = UBitValue<12, 4>; pub type UnscaledOffset = SBitValue<9>; diff --git a/harm/src/instructions/ldst/ldr.rs b/harm/src/instructions/ldst/ldr.rs index 44b0caf..a93d39a 100644 --- a/harm/src/instructions/ldst/ldr.rs +++ b/harm/src/instructions/ldst/ldr.rs @@ -171,7 +171,7 @@ define_pc_offset_rules!( LDR, RegOrZero64, 64, - crate::reloc::Rel64::LdPrelLo19 + crate::reloc::Rel64::ld_prel_lo19 ); define_pc_offset_rules!( Ldr, @@ -179,7 +179,7 @@ define_pc_offset_rules!( LDR, RegOrZero32, 32, - crate::reloc::Rel64::LdPrelLo19 + crate::reloc::Rel64::ld_prel_lo19 ); // diff --git a/harm/src/instructions/ldst/ldrsw.rs b/harm/src/instructions/ldst/ldrsw.rs index d4b7404..dbef6d5 100644 --- a/harm/src/instructions/ldst/ldrsw.rs +++ b/harm/src/instructions/ldst/ldrsw.rs @@ -163,7 +163,7 @@ define_pc_offset_rules!( LDRSW, RegOrZero64, 64, - crate::reloc::Rel64::LdPrelLo19 + crate::reloc::Rel64::ld_prel_lo19 ); // diff --git a/harm/src/reloc.rs b/harm/src/reloc.rs index e6e3957..467fd42 100644 --- a/harm/src/reloc.rs +++ b/harm/src/reloc.rs @@ -1,8 +1,37 @@ -/* Copyright (C) 2025 Ivan Boldyrev +/* Copyright (C) 2026 Ivan Boldyrev * * This document is licensed under the BSD 3-clause license. */ +//! AArch64 instruction relocations. +//! +//! This module defines relocation types used by the `harm` project, and code to apply them to instructions. +//! +//! There relocations follow the static AArch64 ELF relocation types. It is important to note that while the spec +//! uses `S+A` for destination address, this module assumes that `A` is already added to the symbol address. +//! +//! The functions do not require the memory to be in place. The functions' parameters are: +//! - `base`: the base (starting) address of the memory. This can be different from the real memory location for +//! flexibility: the memory can be moved to final location later, even to another host. +//! - `value`: the real target address (`S+A`). +//! - `memory`: the memory mutable slice to apply the relocation to. +//! - `offset`: the offset in the memory to apply the relocation at. +//! +//! So, `P` in the spec is `base + offset`, and the memory to be modified starts from `&memory[offset]`. + +mod addr; +mod control; +mod data; +mod movs; + +use ::core::fmt; + +pub use self::addr::*; +pub use self::control::*; +pub use self::data::*; +pub use self::movs::*; +use crate::bits::BitError; + // TODO These type definitions should probably be moved to `harm-types` crate. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -15,7 +44,6 @@ pub type Offset = i64; pub type Addr = u64; -// b_cond(Cond, LabelRef) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct LabelRef { pub id: LabelId, @@ -23,39 +51,459 @@ pub struct LabelRef { } #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Rel64 { +pub struct Rel64 { + pub rel: Rel64Tag, + pub label: LabelRef, +} + +impl Rel64 { + #[inline] + pub const fn new(rel: Rel64Tag, label: LabelRef) -> Self { + Self { rel, label } + } + + #[inline] + pub const fn ld_prel_lo19(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::LdPrelLo19, + label, + } + } + + #[inline] + pub const fn adr_prel_lo21(label: LabelRef) -> Self { + Self::new(Rel64Tag::AdrPrelLo21, label) + } + + #[inline] + pub const fn adr_prel_pg_hi21(label: LabelRef) -> Self { + Self::new(Rel64Tag::AdrPrelPgHi21, label) + } + + #[inline] + pub const fn adr_prel_pg_hi21nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::AdrPrelPgHi21Nc, label) + } + + #[inline] + pub const fn add_abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::AddAbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st8abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt8AbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st16abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt16AbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st32abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt32AbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st64abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt64AbsLo12Nc, label) + } + + #[inline] + pub const fn ld_st128abs_lo12nc(label: LabelRef) -> Self { + Self::new(Rel64Tag::LdSt128AbsLo12Nc, label) + } + + #[inline] + pub const fn cond_br19(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::CondBr19, + label, + } + } + + #[inline] + pub const fn tst_br14(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::TstBr14, + label, + } + } + + #[inline] + pub const fn jump26(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::Jump26, + label, + } + } + + #[inline] + pub const fn call26(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::Call26, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g0(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG0, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g0nc(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG0Nc, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g0s(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG0S, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g1(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG1, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g1nc(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG1Nc, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g1s(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG1S, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g2(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG2, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g2nc(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG2Nc, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g2s(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG2S, + label, + } + } + + #[inline] + pub const fn mov_w_abs_g3(label: LabelRef) -> Self { + Self { + rel: Rel64Tag::MovWAbsG3, + label, + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Rel64Error { + NotEnoughMemory { offset: usize }, + InvalidOffset { offset: usize }, + InvalidValue(::core::num::TryFromIntError), + InvalidBits(BitError), +} + +impl fmt::Display for Rel64Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Rel64Error::NotEnoughMemory { offset } => { + write!(f, "Not enough memory at 0x{:x}", offset) + } + Rel64Error::InvalidOffset { offset } => write!(f, "Invalid offset 0x{:x}", offset), + Rel64Error::InvalidValue(try_from_int_error) => { + write!(f, "Invalid value: {try_from_int_error}") + } + Rel64Error::InvalidBits(bit_error) => write!(f, "Invalid bit value: {bit_error}"), + } + } +} + +impl ::core::error::Error for Rel64Error {} + +impl From<::core::num::TryFromIntError> for Rel64Error { + fn from(value: core::num::TryFromIntError) -> Self { + Rel64Error::InvalidValue(value) + } +} + +impl From for Rel64Error { + fn from(value: BitError) -> Self { + Rel64Error::InvalidBits(value) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Rel64Tag { None, // Static data relocations // ... - Abs64(LabelRef), - Abs32(LabelRef), - Abs16(LabelRef), - PRel64(LabelRef), - PRel32(LabelRef), - PRel16(LabelRef), - Plt32(LabelRef), - - // Static AArch64 relocations - LdPrelLo19(LabelRef), - AdrPrelLo21(LabelRef), - AdrPrelPgHi21(LabelRef), - AdrPrelPgHi21Nc(LabelRef), - AddAbsLo12Nc(LabelRef), + Abs64, + Abs32, + Abs16, + PRel64, + PRel32, + PRel16, + + // Static AArch64 address relocations + LdPrelLo19, + AdrPrelLo21, + AdrPrelPgHi21, + AdrPrelPgHi21Nc, + AddAbsLo12Nc, + + LdSt8AbsLo12Nc, + LdSt16AbsLo12Nc, + LdSt32AbsLo12Nc, + LdSt64AbsLo12Nc, + LdSt128AbsLo12Nc, + // Static control flow relocations - TstBr14(LabelRef), - CondBr19(LabelRef), - Jump26(LabelRef), - Call26(LabelRef), // same as Jump26 actually? - - // TODO `MOVW` and some `add`/`ldst`-related relocations - MovWAbsG0(LabelRef), - MovWAbsG0Nc(LabelRef), - MovWAbsG0S(LabelRef), - MovWAbsG1(LabelRef), - MovWAbsG1Nc(LabelRef), - MovWAbsG1S(LabelRef), - MovWAbsG2(LabelRef), - MovWAbsG2Nc(LabelRef), - MovWAbsG2S(LabelRef), - MovWAbsG3(LabelRef), + TstBr14, + CondBr19, + Jump26, + Call26, // same as Jump26 actually + + MovWAbsG0, + MovWAbsG0Nc, + MovWAbsG0S, + MovWAbsG1, + MovWAbsG1Nc, + MovWAbsG1S, + MovWAbsG2, + MovWAbsG2Nc, + MovWAbsG2S, + MovWAbsG3, +} + +impl Rel64Tag { + /// Applies the relocation in the `memory` at the `offset`, presuming the real target address ('S+A') is `value`, + /// presuming that base (starting) address of the `memory` is `base` (the can be different from real `memory` + /// location for flexibility: the memory can be moved to real destination later). + pub fn apply( + self, + base: Addr, + value: Addr, + memory: &mut [u8], + offset: usize, + ) -> Result<(), Rel64Error> { + match self { + Rel64Tag::None => { + // check parameters validity + get_bytes_mut::<0>(memory, offset)?; + Ok(()) + } + Rel64Tag::Abs64 => abs64_reloc(value, memory, offset), + Rel64Tag::Abs32 => abs32_reloc(value.cast_signed(), memory, offset), + Rel64Tag::Abs16 => abs16_reloc(value.cast_signed(), memory, offset), + Rel64Tag::PRel64 => prel64_reloc(base, value, memory, offset), + Rel64Tag::PRel32 => prel32_reloc(base, value, memory, offset), + Rel64Tag::PRel16 => prel16_reloc(base, value, memory, offset), + + Rel64Tag::LdPrelLo19 => ld_prel_lo19_reloc(base, value, memory, offset), + Rel64Tag::AdrPrelLo21 => adr_prel_lo21_reloc(base, value, memory, offset), + Rel64Tag::AdrPrelPgHi21 => adrp_prel_pg_hi21_reloc(base, value, memory, offset), + Rel64Tag::AdrPrelPgHi21Nc => adrp_prel_pg_hi21_nc_reloc(base, value, memory, offset), + Rel64Tag::AddAbsLo12Nc => add_abs_lo12_nc_reloc(value, memory, offset), + + Rel64Tag::LdSt8AbsLo12Nc => ldst8_abs_lo12_nc_reloc(value, memory, offset), + Rel64Tag::LdSt16AbsLo12Nc => ldst16_abs_lo12_nc_reloc(value, memory, offset), + Rel64Tag::LdSt32AbsLo12Nc => ldst32_abs_lo12_nc_reloc(value, memory, offset), + Rel64Tag::LdSt64AbsLo12Nc => ldst64_abs_lo12_nc_reloc(value, memory, offset), + Rel64Tag::LdSt128AbsLo12Nc => ldst128_abs_lo12_nc_reloc(value, memory, offset), + + Rel64Tag::TstBr14 => tst_br14_reloc(base, value, memory, offset), + Rel64Tag::CondBr19 => cond_br19_reloc(base, value, memory, offset), + Rel64Tag::Jump26 | Rel64Tag::Call26 => jump26_reloc(base, value, memory, offset), + + Rel64Tag::MovWAbsG0 => mov_w_abs_g0_reloc(value, memory, offset), + Rel64Tag::MovWAbsG0Nc => mov_w_abs_g0nc_reloc(value, memory, offset), + Rel64Tag::MovWAbsG0S => mov_w_abs_g0s_reloc(value.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG1 => mov_w_abs_g1_reloc(value, memory, offset), + Rel64Tag::MovWAbsG1Nc => mov_w_abs_g1nc_reloc(value, memory, offset), + Rel64Tag::MovWAbsG1S => mov_w_abs_g1s_reloc(value.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG2 => mov_w_abs_g2_reloc(value, memory, offset), + Rel64Tag::MovWAbsG2Nc => mov_w_abs_g2nc_reloc(value, memory, offset), + Rel64Tag::MovWAbsG2S => mov_w_abs_g2s_reloc(value.cast_signed(), memory, offset), + Rel64Tag::MovWAbsG3 => mov_w_abs_g3_reloc(value, memory, offset), + } + } +} + +fn get_bytes_mut( + mem: &mut [u8], + offset: usize, +) -> Result<&mut [u8; N], Rel64Error> { + let mem_chunk = mem + .get_mut(offset..) + .ok_or(Rel64Error::InvalidOffset { offset })?; + let bytes: &mut [u8; N] = mem_chunk + .get_mut(..N) + .and_then(|chunk| chunk.as_mut_array()) + .ok_or(Rel64Error::NotEnoughMemory { offset })?; + Ok(bytes) +} + +/// A function for calculating PC-relative relocation difference S - P, where P is `base + offset` and S is `value`. +pub fn calc_offset(base: u64, value: u64, offset: usize) -> Result { + let offset64 = offset + .try_into() + .map_err(|_e| Rel64Error::InvalidOffset { offset })?; + + let instruction_addr = base + .checked_add(offset64) + .ok_or(Rel64Error::InvalidOffset { offset })?; + + Ok(value.wrapping_sub(instruction_addr).cast_signed()) +} + +/// A function for calculating PC-relative relocation difference Page(S) - Page(P), where P is `base + offset` and S is +/// `value`. +/// +/// Please note that the difference is uses address offsets, i.e. the difference is not divided by page size (4096). +pub fn calc_page_offset(base: u64, value: u64, offset: usize) -> Result { + const PAGE_MASK: u64 = !0xfff; + let offset64 = offset + .try_into() + .map_err(|_e| Rel64Error::InvalidOffset { offset })?; + + let instruction_addr = base + .checked_add(offset64) + .ok_or(Rel64Error::InvalidOffset { offset })?; + + Ok((value & PAGE_MASK) + .wrapping_sub(instruction_addr & PAGE_MASK) + .cast_signed()) +} + +#[cfg(test)] +mod claude_tests; + +#[cfg(test)] +mod tests { + use crate::instructions::RawInstruction; + + use super::*; + + #[test] + fn test_none() { + const MEM: [u8; 8] = [0xfb, 0xa6, 0xd3, 0x67, 0x58, 0x50, 0x1d, 0x46]; + + let mut mem = MEM; + Rel64Tag::None.apply(0, 0, &mut mem, 0).unwrap(); + assert_eq!(mem, MEM); + } + + #[test] + fn test_abs64() { + let mut mem = [0; 8]; + Rel64Tag::Abs64 + .apply(0, 0x123456789abcdef0, &mut mem, 0) + .unwrap(); + assert_eq!(mem, 0x123456789abcdef0u64.to_le_bytes()); + } + + #[test] + fn test_abs64_invalid_offset() { + let mut mem = [0; 16]; + let err = Rel64Tag::Abs64.apply(0, 0, &mut mem, 17).unwrap_err(); + assert!( + matches!(err, Rel64Error::InvalidOffset { offset: 17 }), + "{err:?}" + ); + } + + #[test] + fn test_abs32() { + let mut mem = [0; 4]; + Rel64Tag::Abs32.apply(0, 0x12345678, &mut mem, 0).unwrap(); + assert_eq!(mem, 0x12345678u32.to_le_bytes()); + } + + #[test] + fn test_abs32_invalid_offset() { + let mut mem = [0; 16]; + let err = Rel64Tag::Abs32.apply(0, 0, &mut mem, 17).unwrap_err(); + assert!( + matches!(err, Rel64Error::InvalidOffset { offset: 17 }), + "{err:?}" + ); + } + + #[test] + fn test_prel64() { + let mut mem = [0; 8]; + Rel64Tag::PRel64 + .apply(0x1000, 0x123456789abcdef0, &mut mem, 0) + .unwrap(); + assert_eq!( + mem, + 0x123456789abcdef0u64.wrapping_sub(0x1000).to_le_bytes() + ); + } + + #[test] + fn test_prel64_invalid_offset() { + let mut mem = [0; 16]; + let err = Rel64Tag::PRel64 + .apply(0x1000, 0x123456789abcdef0, &mut mem, 17) + .unwrap_err(); + assert!( + matches!(err, Rel64Error::InvalidOffset { offset: 17 }), + "{err:?}" + ); + } + + #[test] + fn test_jump26() { + let mut mem = crate::instructions::control::b(40).unwrap().to_code().0; + let expected = crate::instructions::control::b(0x1020).unwrap().to_code().0; + Rel64Tag::Jump26.apply(0x1000, 0x2020, &mut mem, 0).unwrap(); + assert_eq!(mem, expected); + } + + #[test] + fn test_jump26_unaligned() { + let mut mem = crate::instructions::control::b(40).unwrap().to_code().0; + let err = Rel64Tag::Jump26.apply(0x1000, 0x2022, &mut mem, 0); + assert!( + matches!( + err, + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + ), + "{err:?}" + ); + } } diff --git a/harm/src/reloc/addr.rs b/harm/src/reloc/addr.rs new file mode 100644 index 0000000..d57c174 --- /dev/null +++ b/harm/src/reloc/addr.rs @@ -0,0 +1,226 @@ +/* Copyright (C) 2026 Ivan Boldyrev + * + * This document is licensed under the BSD 3-clause license. + */ + +use aarchmrs_types::InstructionCode; + +use super::{Addr, Rel64Error, cond_br19_reloc}; +use crate::instructions::dpimm::{AdrOffset, AdrpOffset}; +use crate::instructions::ldst::{ScaledOffset16, ScaledOffset32, ScaledOffset64, ScaledOffset128}; +use crate::reloc::get_bytes_mut; +use crate::reloc::{calc_offset, calc_page_offset}; + +const ADR_ADRP_IMMHI_OFFSET: u32 = 5u32; +const ADR_ADRP_IMMHI_WIDTH: u32 = 19u32; +const ADR_ADRP_IMMLO_OFFSET: u32 = 29u32; +const ADR_ADRP_IMMLO_WIDTH: u32 = 2u32; + +const ADD_IMM12_OFFSET: u32 = 10u32; +const ADD_IMM12_WIDTH: u32 = 12u32; + +const LDST16_IMM12_OFFSET: u32 = 10u32; +// The value width is 12 bits to be compatible with ADRP, even though 13-bit values (12 bit value + 1 bit shift) can be +// encoded in the instruction. +const LDST16_IMM12_VALUE_WIDTH: u32 = 12u32; +const LDST16_IMM12_CLEAR_WIDTH: u32 = 12u32; + +const LDST32_IMM12_OFFSET: u32 = 10u32; +// The value width is 12 bits to be compatible with ADRP, even though 14-bit values (12 bit value + 2 bit shift) can be +// encoded in the instruction. +const LDST32_IMM12_VALUE_WIDTH: u32 = 12u32; +const LDST32_IMM12_CLEAR_WIDTH: u32 = 12u32; + +const LDST64_IMM12_OFFSET: u32 = 10u32; +// The value width is 12 bits to be compatible with ADRP, even though 15-bit values (12 bit value + 3 bit shift) can be +// encoded in the instruction. +const LDST64_IMM12_VALUE_WIDTH: u32 = 12u32; +const LDST64_IMM12_CLEAR_WIDTH: u32 = 12u32; + +const LDST128_IMM12_OFFSET: u32 = 10u32; +// The value width is 12 bits to be compatible with ADRP, even though 16-bit values (12 bit value + 4 bit shift) can be +// encoded in the instruction. +const LDST128_IMM12_VALUE_WIDTH: u32 = 12u32; +const LDST128_IMM12_CLEAR_WIDTH: u32 = 12u32; + +#[inline] +pub fn ld_prel_lo19_reloc( + base: Addr, + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + cond_br19_reloc(base, symbol, mem, offset) +} + +pub fn adr_prel_lo21_reloc( + base: Addr, + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + + let delta = calc_offset(base, symbol, offset)?; + let delta = AdrOffset::new_i64(delta)?; + patch_adr_adrp(bytes, delta.bits()); + + Ok(()) +} + +#[inline] +pub fn adrp_prel_pg_hi21_reloc( + base: Addr, + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const VALUE_MASK: u32 = (1 << (ADR_ADRP_IMMHI_WIDTH + ADR_ADRP_IMMLO_WIDTH)) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + + let delta = calc_page_offset(base, symbol, offset)?; + let delta = AdrpOffset::new_i64(delta).map_err(Rel64Error::InvalidBits)?; + patch_adr_adrp(bytes, delta.bits() & VALUE_MASK); + + Ok(()) +} + +#[inline] +pub fn adrp_prel_pg_hi21_nc_reloc( + base: Addr, + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const VALUE_MASK: u32 = (1 << (ADR_ADRP_IMMHI_WIDTH + ADR_ADRP_IMMLO_WIDTH)) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // It is not checked. + let delta = calc_page_offset(base, symbol, offset)? >> 12; + + patch_adr_adrp(bytes, (delta as u32) & VALUE_MASK); + Ok(()) +} + +#[inline] +pub fn add_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const MASK: u32 = (1 << ADD_IMM12_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(MASK << ADD_IMM12_OFFSET); + inst_code |= (symbol as u32 & MASK) << ADD_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} + +fn patch_adr_adrp(mem: &mut [u8; 4], checked_value: u32) { + const INST_MASK: u32 = ((1 << ADR_ADRP_IMMHI_WIDTH) - 1) << ADR_ADRP_IMMHI_OFFSET + | ((1 << ADR_ADRP_IMMLO_WIDTH) - 1) << ADR_ADRP_IMMLO_OFFSET; + + let lo = checked_value & ((1 << ADR_ADRP_IMMLO_WIDTH) - 1); + let hi = checked_value >> ADR_ADRP_IMMLO_WIDTH; + + let mut inst_code = InstructionCode(*mem).unpack(); + inst_code &= !INST_MASK; + inst_code |= (hi << ADR_ADRP_IMMHI_OFFSET) | (lo << ADR_ADRP_IMMLO_OFFSET); + + *mem = InstructionCode::from_u32(inst_code).0; +} + +#[inline] +pub fn ldst8_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + add_abs_lo12_nc_reloc(symbol, mem, offset) +} + +pub fn ldst16_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const CLEAR_MASK: u32 = (1 << LDST16_IMM12_CLEAR_WIDTH) - 1; + const VALUE_MASK: u32 = (1 << LDST16_IMM12_VALUE_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // ... a linker should check that the value of X is aligned to a multiple of the datum size. + let symbol_bits = ScaledOffset16::new(symbol as u32 & VALUE_MASK)?; + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(CLEAR_MASK << LDST16_IMM12_OFFSET); + inst_code |= symbol_bits.bits() << LDST16_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} + +pub fn ldst32_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const CLEAR_MASK: u32 = (1 << LDST32_IMM12_CLEAR_WIDTH) - 1; + const VALUE_MASK: u32 = (1 << LDST32_IMM12_VALUE_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // ... a linker should check that the value of X is aligned to a multiple of the datum size. + let symbol_bits = ScaledOffset32::new(symbol as u32 & VALUE_MASK)?; + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(CLEAR_MASK << LDST32_IMM12_OFFSET); + inst_code |= symbol_bits.bits() << LDST32_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} + +pub fn ldst64_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const CLEAR_MASK: u32 = (1 << LDST64_IMM12_CLEAR_WIDTH) - 1; + const VALUE_MASK: u32 = (1 << LDST64_IMM12_VALUE_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // ... a linker should check that the value of X is aligned to a multiple of the datum size. + let symbol_bits = ScaledOffset64::new(symbol as u32 & VALUE_MASK)?; + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(CLEAR_MASK << LDST64_IMM12_OFFSET); + inst_code |= symbol_bits.bits() << LDST64_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} + +pub fn ldst128_abs_lo12_nc_reloc( + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + const CLEAR_MASK: u32 = (1 << LDST128_IMM12_CLEAR_WIDTH) - 1; + const VALUE_MASK: u32 = (1 << LDST128_IMM12_VALUE_WIDTH) - 1; + + let bytes = get_bytes_mut(mem, offset)?; + // ... a linker should check that the value of X is aligned to a multiple of the datum size. + let symbol_bits = ScaledOffset128::new(symbol as u32 & VALUE_MASK)?; + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !(CLEAR_MASK << LDST128_IMM12_OFFSET); + inst_code |= symbol_bits.bits() << LDST128_IMM12_OFFSET; + + *bytes = InstructionCode::from_u32(inst_code).0; + + Ok(()) +} diff --git a/harm/src/reloc/claude_tests.rs b/harm/src/reloc/claude_tests.rs new file mode 100644 index 0000000..7fb071f --- /dev/null +++ b/harm/src/reloc/claude_tests.rs @@ -0,0 +1,1990 @@ +/* Tests based on code generated by Claude Sonnet 4.6 in March 2026. + */ +use super::*; + +// ═══════════════════════════════════════════════════════════════════════ +// Test helpers +// ═══════════════════════════════════════════════════════════════════════ + +const MOV_W_OPC_MOVZ: u32 = 0b10; +const MOV_W_OPC_MOVN: u32 = 0b00; + +// ── Byte-level readers ──────────────────────────────────────────────── + +fn u16_at(mem: &[u8], off: usize) -> u16 { + u16::from_le_bytes(mem[off..off + 2].try_into().unwrap()) +} +fn u32_at(mem: &[u8], off: usize) -> u32 { + u32::from_le_bytes(mem[off..off + 4].try_into().unwrap()) +} +fn u64_at(mem: &[u8], off: usize) -> u64 { + u64::from_le_bytes(mem[off..off + 8].try_into().unwrap()) +} + +// ── Instruction-field decoders ──────────────────────────────────────── + +/// Sign-extend the lowest `width` bits of `raw` to i32. +fn sign_ext(raw: u32, width: u32) -> i32 { + let sh = 32 - width; + ((raw << sh) as i32) >> sh +} + +fn dec_imm14(insn: u32) -> i32 { + sign_ext((insn >> 5) & 0x3FFF, 14) +} +fn dec_imm19(insn: u32) -> i32 { + sign_ext((insn >> 5) & 0x007F_FFFF, 19) +} +fn dec_imm26(insn: u32) -> i32 { + sign_ext(insn & 0x03FF_FFFF, 26) +} +fn dec_imm12(insn: u32) -> u32 { + (insn >> 10) & 0xFFF +} +fn dec_movw(insn: u32) -> u16 { + ((insn >> 5) & 0xFFFF) as u16 +} + +fn dec_imm21_adr(insn: u32) -> i32 { + let lo = (insn >> 29) & 0x3; + let hi = (insn >> 5) & 0x0007_FFFF; + sign_ext((hi << 2) | lo, 21) +} + +fn mov_w_opc(insn: u32) -> u32 { + (insn >> 29) & 0x3 +} + +// ── Skeleton instruction words ──────────────────────────────────────── + +// LDR X0, #0 → 0x5800_0000 +const LDR_LIT: u32 = 0x5800_0000; +// ADR X1, #0 → 0x0000_0001 +const ADR: u32 = 0x0000_0001; +// ADRP X0, #0 → 0x9000_0000 +const ADRP: u32 = 0x9000_0000; +// ADD X0, X1, #0 → 0x9100_0020 +const ADD: u32 = 0x9100_0020; +// TBZ X0, #0, #0 → 0x3600_0000 +const TBZ: u32 = 0x3600_0000; +// B.EQ #0 → 0x5400_0000 +const BEQ: u32 = 0x5400_0000; +// B #0 → 0x1400_0000 +const B: u32 = 0x1400_0000; +// BL #0 → 0x9400_0000 +const BL: u32 = 0x9400_0000; +// MOVZ X0, #0 → 0xD280_0000 +const MOVZ: u32 = 0xD280_0000; + +/// Return a 4-byte buffer pre-loaded with an instruction word. +fn insn_buf(insn: u32) -> [u8; 4] { + insn.to_le_bytes() +} + +// ═══════════════════════════════════════════════════════════════════════ +// get_bytes_mut / get_bytes +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn get_bytes_mut_ok() { + let mut mem = [1u8, 2, 3, 4, 5, 6, 7, 8]; + let arr: &mut [u8; 4] = get_bytes_mut(&mut mem, 2).unwrap(); + assert_eq!(*arr, [3, 4, 5, 6]); +} + +#[test] +fn get_bytes_mut_invalid_offset() { + let mut mem = [0u8; 4]; + assert_eq!( + get_bytes_mut::<4>(&mut mem, 5), + Err(Rel64Error::InvalidOffset { offset: 5 }) + ); +} + +#[test] +fn get_bytes_mut_not_enough_memory() { + let mut mem = [0u8; 4]; + // offset 2 is valid, but only 2 bytes remain — not enough for N=4 + assert_eq!( + get_bytes_mut::<4>(&mut mem, 2), + Err(Rel64Error::NotEnoughMemory { offset: 2 }) + ); +} + +// ═══════════════════════════════════════════════════════════════════════ +// Buffer-error variants (exercised through a public function) +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn invalid_offset_error() { + let mut mem = [0u8; 4]; + assert_eq!( + abs64_reloc(0, &mut mem, 8), + Err(Rel64Error::InvalidOffset { offset: 8 }) + ); +} + +#[test] +fn not_enough_memory_error() { + let mut mem = [0u8; 4]; + assert_eq!( + abs64_reloc(0, &mut mem, 0), + Err(Rel64Error::NotEnoughMemory { offset: 0 }) + ); +} + +// ═══════════════════════════════════════════════════════════════════════ +// abs64 (target, mem, offset) +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn abs64_basic() { + let mut mem = [0u8; 16]; + abs64_reloc(0xDEAD_BEEF_CAFE_1234, &mut mem, 4).unwrap(); + assert_eq!(u64_at(&mem, 4), 0xDEAD_BEEF_CAFE_1234); + assert_eq!(&mem[..4], &[0u8; 4]); // guard bytes untouched +} + +#[test] +fn abs64_zero() { + let mut mem = [0xFFu8; 8]; + abs64_reloc(0, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0), 0); +} + +#[test] +fn abs64_max() { + let mut mem = [0u8; 8]; + abs64_reloc(u64::MAX, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0), u64::MAX); +} + +// ═══════════════════════════════════════════════════════════════════════ +// abs32 (target, mem, offset) +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn abs32_basic() { + let mut mem = [0u8; 8]; + abs32_reloc(0x7BCD_1234, &mut mem, 2).unwrap(); + assert_eq!(u32_at(&mem, 2), 0x7BCD_1234); +} + +#[test] +fn abs32_neg() { + let mut mem = [0u8; 4]; + abs32_reloc(-1, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), (-1i32) as u32); +} + +#[test] +fn abs32_max() { + let mut mem = [0u8; 4]; + abs32_reloc(i32::MAX as i64, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), i32::MAX as u32); +} + +#[test] +fn abs32_overflow() { + let mut mem = [0u8; 4]; + assert!(matches!( + abs32_reloc(0x1_0000_0000 as i64, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +#[test] +fn abs32_unsigned_boundary() { + // 0x8000_0000 = i32::MAX + 1: valid as an unsigned 32-bit value, + // but a buggy impl that checks fits_signed(v, 32) rejects it. + let mut mem = [0u8; 4]; + abs32_reloc(0x8000_0000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0x8000_0000); +} + +#[test] +fn abs32_unsigned_mid() { + // Mid-point of the unsigned-only half [2^31, 2^32). + let mut mem = [0u8; 4]; + abs32_reloc(0xC000_0000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0xC000_0000); +} + +// ═══════════════════════════════════════════════════════════════════════ +// abs16 (target, mem, offset) +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn abs16_basic() { + let mut mem = [0u8; 4]; + abs16_reloc(0x7EEF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0x7EEF); +} + +#[test] +fn abs16_neg() { + let mut mem = [0u8; 4]; + abs16_reloc(-1, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), (-1i16) as u16); +} + +#[test] +fn abs16_max() { + let mut mem = [0u8; 2]; + abs16_reloc(i16::MAX as _, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), i16::MAX as u16); +} + +#[test] +fn abs16_overflow() { + let mut mem = [0u8; 2]; + assert!(matches!( + abs16_reloc(0x1_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +#[test] +fn abs16_unsigned_boundary() { + // 0x8000 = i16::MAX + 1: valid as an unsigned 16-bit value, + // but a buggy impl that checks fits_signed(v, 16) rejects it. + let mut mem = [0u8; 2]; + abs16_reloc(0x8000, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0x8000); +} + +#[test] +fn abs16_unsigned_mid() { + // Mid-point of the unsigned-only half [2^15, 2^16). + let mut mem = [0u8; 2]; + abs16_reloc(0xC000, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0xC000); +} + +// ═══════════════════════════════════════════════════════════════════════ +// prel64 (base, target, mem, offset) +// +// place = base + offset +// written = target − place (wrapping u64) +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn prel64_forward() { + // place = 0x1000 + 0 = 0x1000; diff = 0x2000 − 0x1000 = +0x1000 + let mut mem = [0u8; 8]; + prel64_reloc(0x1000, 0x2000, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0), 0x1000); +} + +#[test] +fn prel64_backward() { + // place = 0x2000; diff = 0x1000 − 0x2000 (wrapping) = −0x1000 + let mut mem = [0u8; 8]; + prel64_reloc(0x2000, 0x1000, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0) as i64, -0x1000i64); +} + +#[test] +fn prel64_zero_diff() { + let mut mem = [0xFFu8; 8]; + prel64_reloc(0x4000, 0x4000, &mut mem, 0).unwrap(); + assert_eq!(u64_at(&mem, 0), 0); +} + +#[test] +fn prel64_nonzero_offset() { + // base=0x0FF8, offset=8 → place=0x1000; diff=0x2000−0x1000=0x1000 + let mut mem = [0u8; 16]; + prel64_reloc(0x0FF8, 0x2000, &mut mem, 8).unwrap(); + assert_eq!(u64_at(&mem, 8), 0x1000); +} + +// ═══════════════════════════════════════════════════════════════════════ +// prel32 (base, target, mem, offset) +// +// diff = target − place; must fit in [i32::MIN, u32::MAX] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn prel32_forward() { + let mut mem = [0u8; 4]; + prel32_reloc(0x4000, 0x5000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, 0x1000); +} + +#[test] +fn prel32_backward() { + // place=0x5000; diff = 0x4000 − 0x5000 = −0x1000 + let mut mem = [0u8; 4]; + prel32_reloc(0x5000, 0x4000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, -0x1000); +} + +#[test] +fn prel32_max_positive() { + // diff = i32::MAX = 0x7FFF_FFFF — just fits + let mut mem = [0u8; 4]; + prel32_reloc(0x0, 0x7FFF_FFFF, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, i32::MAX); +} + +#[test] +fn prel32_max_negative() { + // place=0x8000_0000; diff = 0 − 0x8000_0000 = i32::MIN — just fits + let mut mem = [0u8; 4]; + prel32_reloc(0x8000_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, i32::MIN); +} + +#[test] +fn prel32_unsigned_boundary() { + // diff = 0x8000_0000: valid as an unsigned 32-bit value; + // a buggy fits_signed(diff, 32) check incorrectly rejects it. + let mut mem = [0u8; 4]; + prel32_reloc(0x0, 0x8000_0000, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0x8000_0000); +} + +#[test] +fn prel32_unsigned_max() { + // diff = 0xFFFF_FFFF = u32::MAX: the largest valid unsigned 32-bit value. + let mut mem = [0u8; 4]; + prel32_reloc(0x0, 0xFFFF_FFFF, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0xFFFF_FFFF); +} + +#[test] +fn prel32_overflow_positive() { + // diff = 0x1_0000_0000 = u32::MAX + 1: truly out of range. + let mut mem = [0u8; 4]; + assert!(matches!( + prel32_reloc(0x0, 0x1_0000_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +#[test] +fn prel32_overflow_negative() { + // place=0x8000_0001; diff = −0x8000_0001 < i32::MIN + let mut mem = [0u8; 4]; + assert!(matches!( + prel32_reloc(0x8000_0001, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +#[test] +fn prel32_nonzero_offset() { + // base=0x0, offset=8 → place=0x8; diff=0x1008−0x8=0x1000 + let mut mem = [0u8; 12]; + prel32_reloc(0x0, 0x1008, &mut mem, 8).unwrap(); + assert_eq!(u32_at(&mem, 8) as i32, 0x1000); +} + +// ═══════════════════════════════════════════════════════════════════════ +// prel16 (base, target, mem, offset) +// +// diff must fit in [i16::MIN, u16::MAX] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn prel16_forward() { + // place=0x3FFF; diff=0x4000−0x3FFF=+1 + let mut mem = [0u8; 2]; + prel16_reloc(0x3FFF, 0x4000, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0) as i16, 1); +} + +#[test] +fn prel16_backward() { + // place=0x4000; diff=0x3FFF−0x4000=−1 + let mut mem = [0u8; 2]; + prel16_reloc(0x4000, 0x3FFF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0) as i16, -1); +} + +#[test] +fn prel16_max_positive() { + let mut mem = [0u8; 2]; + prel16_reloc(0x0, 0x7FFF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0) as i16, i16::MAX); +} + +#[test] +fn prel16_max_negative() { + // place=0x8000; diff=0−0x8000=i16::MIN — just fits + let mut mem = [0u8; 2]; + prel16_reloc(0x8000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0) as i16, i16::MIN); +} + +#[test] +fn prel16_unsigned_boundary() { + // diff = 0x8000: valid as an unsigned 16-bit value; + // a buggy fits_signed(diff, 16) check incorrectly rejects it. + let mut mem = [0u8; 2]; + prel16_reloc(0x0, 0x8000, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0x8000); +} + +#[test] +fn prel16_unsigned_max() { + // diff = 0xFFFF = u16::MAX: the largest valid unsigned 16-bit value. + let mut mem = [0u8; 2]; + prel16_reloc(0x0, 0xFFFF, &mut mem, 0).unwrap(); + assert_eq!(u16_at(&mem, 0), 0xFFFF); +} + +#[test] +fn prel16_overflow_positive() { + // diff = 0x1_0000 = u16::MAX + 1: truly out of range. + let mut mem = [0u8; 2]; + assert!(matches!( + prel16_reloc(0x0, 0x1_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +#[test] +fn prel16_overflow_negative() { + // place=0x8001; diff=−0x8001 < i16::MIN + let mut mem = [0u8; 2]; + assert!(matches!( + prel16_reloc(0x8001, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ═══════════════════════════════════════════════════════════════════════ +// ld_prel_lo19 (base, target, mem, offset) +// +// imm19 = (target − place) / 4 +// Signed 19-bit word offset → byte range [−(1<<20), (1<<20)−4] +// target must be 4-byte aligned relative to place +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn ld_prel_lo19_forward() { + // place=0; diff=8; imm19=2 + let mut mem = insn_buf(LDR_LIT); + ld_prel_lo19_reloc(0x0, 0x0008, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), 2); +} + +#[test] +fn ld_prel_lo19_backward() { + // place=0x100; diff=−0x100; imm19=−64 + let mut mem = insn_buf(LDR_LIT); + ld_prel_lo19_reloc(0x0100, 0x0000, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), -64); +} + +#[test] +fn ld_prel_lo19_max_positive() { + // imm19 max = (1<<18)−1 word offsets = 0x3_FFFF → byte diff = 0x0F_FFFC + let mut mem = insn_buf(LDR_LIT); + ld_prel_lo19_reloc(0x0, 0x0F_FFFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), (1 << 18) - 1); +} + +#[test] +fn ld_prel_lo19_max_negative() { + // imm19 min = −(1<<18) word offsets → byte diff = −0x10_0000 + // place=0x10_0000, target=0 + let mut mem = insn_buf(LDR_LIT); + ld_prel_lo19_reloc(0x10_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), -(1 << 18)); +} + +#[test] +fn ld_prel_lo19_overflow_positive() { + // byte diff = 0x10_0000 → word imm = 1<<18, one above max + let mut mem = [0u8; 4]; + assert!(matches!( + ld_prel_lo19_reloc(0x0, 0x10_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 19, + align: 2 + })) + )); +} + +#[test] +fn ld_prel_lo19_overflow_negative() { + // place=0x10_0004, target=0 → byte diff=−0x10_0004 → one below min + let mut mem = [0u8; 4]; + assert!(matches!( + ld_prel_lo19_reloc(0x10_0004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 19, + align: 2 + })) + )); +} + +#[test] +fn ld_prel_lo19_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + ld_prel_lo19_reloc(0x0, 0x0002, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +#[test] +fn ld_prel_lo19_nonzero_offset() { + // base=0, offset=4 → place=4; diff=0x10−4=0xC; imm19=3 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&LDR_LIT.to_le_bytes()); + ld_prel_lo19_reloc(0x0, 0x10, &mut mem, 4).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 4)), 3); +} + +// ═══════════════════════════════════════════════════════════════════════ +// adr_prel_lo21 (base, target, mem, offset) +// +// imm21 = target − place (byte offset, not scaled) +// Signed 21-bit → byte range [−(1<<20), (1<<20)−1] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn adr_prel_lo21_forward() { + // place=0x1000; diff=4; imm21=4 + let mut mem = insn_buf(ADR); + adr_prel_lo21_reloc(0x1000, 0x1004, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 4); +} + +#[test] +fn adr_prel_lo21_backward() { + // place=0x1000; diff=0x0FF8−0x1000=−8 + let mut mem = insn_buf(ADR); + adr_prel_lo21_reloc(0x1000, 0x0FF8, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -8); +} + +#[test] +fn adr_prel_lo21_max_positive() { + // max diff = (1<<20)−1 = 0xF_FFFF + let mut mem = insn_buf(ADR); + adr_prel_lo21_reloc(0x0, 0x0F_FFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), (1 << 20) - 1); +} + +#[test] +fn adr_prel_lo21_max_negative() { + // min diff = −(1<<20) = −0x10_0000; place=0x10_0000, target=0 + let mut mem = insn_buf(ADR); + adr_prel_lo21_reloc(0x10_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -(1 << 20)); +} + +#[test] +fn adr_prel_lo21_overflow_positive() { + let mut mem = [0u8; 4]; + assert!(matches!( + adr_prel_lo21_reloc(0x0, 0x10_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 21, + align: 0 + })) + )); +} + +#[test] +fn adr_prel_lo21_overflow_negative() { + let mut mem = [0u8; 4]; + assert!(matches!( + adr_prel_lo21_reloc(0x10_0001, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 21, + align: 0 + })) + )); +} + +#[test] +fn adr_prel_lo21_nonzero_offset() { + // base=0x0FFC, offset=4 → place=0x1000; diff=0x1008−0x1000=8 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&ADR.to_le_bytes()); + adr_prel_lo21_reloc(0x0FFC, 0x1008, &mut mem, 4).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 4)), 8); +} + +// ═══════════════════════════════════════════════════════════════════════ +// adrp_prel_pg_hi21 (base, target, mem, offset) +// +// imm21 = (Page(target) − Page(place)) / 4096 +// Signed 21-bit page offset → ±4 GiB range +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn adrp_same_page() { + // Both on page 0x1000 → imm21=0 + let mut mem = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x1000, 0x1234, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 0); +} + +#[test] +fn adrp_next_page() { + // place page=0x1000; target page=0x2000 → imm21=1 + let mut mem = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x1ABC, 0x2ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 1); +} + +#[test] +fn adrp_prev_page() { + // place page=0x2000; target page=0x1000 → imm21=−1 + let mut mem = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x2ABC, 0x1ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -1); +} + +#[test] +fn adrp_max_positive() { + // imm21 max = (1<<20)−1 page offsets + // target page = ((1<<20)−1) * 0x1000 = 0xF_FFFF_000 + let mut mem = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x0, 0xF_FFFF_FFF, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), (1 << 20) - 1); +} + +#[test] +fn adrp_max_negative() { + // imm21 min = −(1<<20); page diff = −(1<<20) + // place page = (1<<20)*0x1000 = 0x10_0000_000; target=0 + let mut mem = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x10_0000_000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -(1 << 20)); +} + +#[test] +fn adrp_overflow_positive() { + let mut mem = [0u8; 4]; + assert!(matches!( + adrp_prel_pg_hi21_reloc(0x0, 0x1_0000_0000_0000, &mut mem, 0), + // TODO Unsupported seems to have obsure semantics. + Err(Rel64Error::InvalidBits(BitError::Unsupported)) + )); +} + +#[test] +fn adrp_overflow_negative() { + let mut mem = [0u8; 4]; + assert!(matches!( + adrp_prel_pg_hi21_reloc(0x1_0000_0000_0000, 0x0, &mut mem, 0), + // TODO Unsupported seems to have obsure semantics. + // Actually it should be BitError::Overflow. + Err(Rel64Error::InvalidBits(BitError::Unsupported)) + ),); +} + +#[test] +fn adrp_nc_identical_to_checked_in_range() { + let mut mc = insn_buf(ADRP); + let mut mn = insn_buf(ADRP); + adrp_prel_pg_hi21_reloc(0x1000, 0x5678, &mut mc, 0).unwrap(); + adrp_prel_pg_hi21_nc_reloc(0x1000, 0x5678, &mut mn, 0).unwrap(); + assert_eq!(mc, mn); +} + +#[test] +fn adrp_nc_no_overflow() { + // Would be rejected by the checked variant; NC silently truncates + let mut mem = [0u8; 4]; + adrp_prel_pg_hi21_nc_reloc(0x0, 0x1_0000_0000_0000, &mut mem, 0).unwrap(); +} + +#[test] +fn adrp_nonzero_offset() { + // base=0x0FFC, offset=4 → place=0x1000 (page 0x1000) + // target=0x3ABC → page=0x3000; imm21=2 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&ADRP.to_le_bytes()); + adrp_prel_pg_hi21_reloc(0x0FFC, 0x3ABC, &mut mem, 4).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 4)), 2); +} + +// Additionaly ADRP tests +#[test] +fn lo12_target_below_lo12_place_typical() { + // S+A = 0x4_300 → Page(S+A) = 0x4_000 (lo12 = 0x300) + // P = 0x2_A00 → Page(P) = 0x2_000 (lo12 = 0xA00) + // + // 0x300 < 0xA00, so formulas diverge: + // correct: (0x4_000 - 0x2_000) >> 12 = 2 + // buggy: Page(0x4_300 - 0x2_A00) = Page(0x1_900) >> 12 = 1 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x2_A00, 0x4_300, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn lo12_target_one_lo12_place_max() { + // Extreme: target lo12 nearly zero, place lo12 nearly 0xFFF. + // + // S+A = 0x3_001 → Page(S+A) = 0x3_000 (lo12 = 0x001) + // P = 0x1_FFF → Page(P) = 0x1_000 (lo12 = 0xFFF) + // + // correct: (0x3_000 - 0x1_000) >> 12 = 2 + // buggy: Page(0x3_001 - 0x1_FFF) = Page(0x1_002) >> 12 = 1 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x1_FFF, 0x3_001, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn lo12_target_zero_lo12_place_nonzero() { + // Target is page-aligned; any non-zero lo12 in place triggers the bug. + // + // S+A = 0x5_000 → Page(S+A) = 0x5_000 (lo12 = 0x000) + // P = 0x3_001 → Page(P) = 0x3_000 (lo12 = 0x001) + // + // correct: (0x5_000 - 0x3_000) >> 12 = 2 + // buggy: Page(0x5_000 - 0x3_001) = Page(0x1_FFF) >> 12 = 1 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x3_001, 0x5_000, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn lo12_target_below_lo12_place_negative_imm() { + // Backward reference — same divergence condition, negative result. + // + // S+A = 0x1_200 → Page(S+A) = 0x1_000 (lo12 = 0x200) + // P = 0x3_800 → Page(P) = 0x3_000 (lo12 = 0x800) + // + // correct: (0x1_000 - 0x3_000) >> 12 = -2 + // buggy: Page(0x1_200 - 0x3_800) wrapping = Page(…E400) >> 12 + // interpreted as signed: -3 ← one page off + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x3_800, 0x1_200, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -2); +} + +#[test] +fn lo12_target_below_lo12_place_with_offset() { + // Confirms offset is included when computing place = base + offset. + // (With the current impl signature, pass the pre-computed place directly; + // here offset advances within the buffer, not changing the place value.) + // + // place = 0x2_600 (lo12 = 0x600) + // S+A = 0x4_300 (lo12 = 0x300) + // + // 0x300 < 0x600: + // correct: (0x4_000 - 0x2_000) >> 12 = 2 + // buggy: Page(0x4_300 - 0x2_600) = Page(0x1_D00) >> 12 = 1 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&ADRP.to_le_bytes()); + adrp_prel_pg_hi21_reloc(0x2_600, 0x4_300, &mut mem, 4).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 4)), 2); +} + +// ───────────────────────────────────────────────────────────────────── +// Same diverging cases for the NC (no-overflow-check) variant +// ───────────────────────────────────────────────────────────────────── + +#[test] +fn nc_lo12_target_below_lo12_place_typical() { + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_nc_reloc(0x2_A00, 0x4_300, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn nc_lo12_target_zero_lo12_place_nonzero() { + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_nc_reloc(0x3_001, 0x5_000, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn nc_lo12_target_below_lo12_place_negative_imm() { + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_nc_reloc(0x3_800, 0x1_200, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), -2); +} + +// ───────────────────────────────────────────────────────────────────── +// Sanity: cases where the formulas agree (lo12 target >= lo12 place). +// Included to confirm the test logic and to show the reader why the +// failing cases above are the interesting ones. +// ───────────────────────────────────────────────────────────────────── + +#[test] +fn sanity_lo12_equal_formulas_agree() { + // lo12(S+A) = lo12(P) = 0x500 → no divergence, imm = 2 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x2_500, 0x4_500, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn sanity_lo12_target_above_lo12_place_formulas_agree() { + // lo12(S+A) = 0xF00 > lo12(P) = 0x100 → no divergence, imm = 2 + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x2_100, 0x4_F00, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn sanity_page_aligned_place_formulas_always_agree() { + // lo12(P) = 0 → condition lo12(target) < lo12(place) can never hold + let mut mem = ADRP.to_le_bytes(); + adrp_prel_pg_hi21_reloc(0x2_000, 0x4_300, &mut mem, 0).unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +// ═══════════════════════════════════════════════════════════════════════ +// add_abs_lo12_nc (target, mem, offset) +// +// imm12 = target & 0xFFF (no overflow check) +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn add_abs_lo12_nc_basic() { + let mut mem = insn_buf(ADD); + add_abs_lo12_nc_reloc(0x1ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} + +#[test] +fn add_abs_lo12_nc_zero() { + let mut mem = insn_buf(ADD); + add_abs_lo12_nc_reloc(0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0); +} + +#[test] +fn add_abs_lo12_nc_max_field() { + let mut mem = insn_buf(ADD); + add_abs_lo12_nc_reloc(0xFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFFF); +} + +#[test] +fn add_abs_lo12_nc_masks_high_bits() { + let mut mem = insn_buf(ADD); + add_abs_lo12_nc_reloc(0xDEAD_BEEF_FFFF_1234, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234); +} +// ───────────────────────────────────────────────────────────────────── +// Cases where (S+A) & 0xFFF ≠ (S+A − P) & 0xFFF +// ───────────────────────────────────────────────────────────────────── + +#[test] +fn place_nonzero_lo12_typical() { + // target = 0x2_ABC → correct imm12 = 0xABC + // place = 0x1_400 → buggy imm12 = (0x2_ABC - 0x1_400) & 0xFFF = 0x6BC + // + // base=0x1_000, offset=0x400 → place = 0x1_400 + let mut mem = [0u8; 0x404]; + mem[0x400..0x404].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_000, 0x2_ABC, &mut mem, 0x400) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0x400)), 0xABC); +} + +#[test] +fn place_lo12_exceeds_target_lo12() { + // target = 0x2_200 → correct imm12 = 0x200 + // place = 0x1_800 → buggy imm12 = (0x2_200 - 0x1_800) & 0xFFF = 0xA00 + // + // base=0x1_000, offset=0x800 → place = 0x1_800 + let mut mem = [0u8; 0x804]; + mem[0x800..0x804].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_000, 0x2_200, &mut mem, 0x800) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0x800)), 0x200); +} + +#[test] +fn place_lo12_equals_target_lo12() { + // target = 0x2_500 → correct imm12 = 0x500 + // place = 0x1_500 → buggy imm12 = (0x2_500 - 0x1_500) & 0xFFF = 0x000 + // + // base=0x1_000, offset=0x500 → place = 0x1_500 + let mut mem = [0u8; 0x504]; + mem[0x500..0x504].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_000, 0x2_500, &mut mem, 0x500) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0x500)), 0x500); +} + +#[test] +fn target_page_aligned_place_not() { + // target = 0x3_000 → correct imm12 = 0x000 + // place = 0x1_ABC → buggy imm12 = (0x3_000 - 0x1_ABC) & 0xFFF = 0x544 + // + // base=0x1_000, offset=0xABC → place = 0x1_ABC + let mut mem = [0u8; 0xAC0]; + mem[0xABC..0xAC0].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_000, 0x3_000, &mut mem, 0xABC) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0xABC)), 0x000); +} + +#[test] +fn place_from_nonzero_base_and_offset_both_contribute() { + // Checks that place = base + offset, not just base or just offset. + // + // target = 0x5_700 → correct imm12 = 0x700 + // base=0x1_200, offset=0x400 → place = 0x1_600 + // buggy: (0x5_700 - 0x1_600) & 0xFFF = 0x100 ← wrong + let mut mem = [0u8; 0x404]; + mem[0x400..0x404].copy_from_slice(&ADD.to_le_bytes()); + Rel64Tag::AddAbsLo12Nc + .apply(0x1_200, 0x5_700, &mut mem, 0x400) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0x400)), 0x700); +} + +// ───────────────────────────────────────────────────────────────────── +// Sanity: page-aligned place — both formulas agree, bug is invisible +// ───────────────────────────────────────────────────────────────────── + +#[test] +fn sanity_page_aligned_place_formulas_agree() { + // place = 0x2_000 (page-aligned) → P & 0xFFF = 0 + // Both formulas give the same result; existing tests all look like this. + let mut mem = ADD.to_le_bytes(); + Rel64Tag::AddAbsLo12Nc + .apply(0x2_000, 0x4_ABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} + +#[test] +fn sanity_zero_base_zero_offset_formulas_agree() { + // place = 0 → both formulas identical; this is what the original tests use. + let mut mem = ADD.to_le_bytes(); + Rel64Tag::AddAbsLo12Nc + .apply(0, 0x4_ABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} + +// ═══════════════════════════════════════════════════════════════════════ +// tst_br14 (base, target, mem, offset) +// +// imm14 = (target − place) / 4 +// Signed 14-bit word offset → byte range [−(1<<15), (1<<15)−4] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn tst_br14_forward() { + // place=0x1000; diff=8; imm14=2 + let mut mem = insn_buf(TBZ); + tst_br14_reloc(0x1000, 0x1008, &mut mem, 0).unwrap(); + assert_eq!(dec_imm14(u32_at(&mem, 0)), 2); +} + +#[test] +fn tst_br14_backward() { + // place=0x1008; diff=−8; imm14=−2 + let mut mem = insn_buf(TBZ); + tst_br14_reloc(0x1008, 0x1000, &mut mem, 0).unwrap(); + assert_eq!(dec_imm14(u32_at(&mem, 0)), -2); +} + +#[test] +fn tst_br14_max_positive() { + // imm14 max = (1<<13)−1 = 8191 word offsets → byte diff = 0x7FFC + let mut mem = insn_buf(TBZ); + tst_br14_reloc(0x0, 0x7FFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm14(u32_at(&mem, 0)), (1 << 13) - 1); +} + +#[test] +fn tst_br14_max_negative() { + // imm14 min = −(1<<13) → byte diff = −0x8000; place=0x8000, target=0 + let mut mem = insn_buf(TBZ); + tst_br14_reloc(0x8000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm14(u32_at(&mem, 0)), -(1 << 13)); +} + +#[test] +fn tst_br14_overflow_positive() { + // byte diff=0x8000 → word imm=1<<13, one above max + let mut mem = [0u8; 4]; + assert!(matches!( + tst_br14_reloc(0x0, 0x8000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 14, + align: 2 + })) + )); +} + +#[test] +fn tst_br14_overflow_negative() { + // place=0x8004, target=0 → byte diff=−0x8004 → one below min + let mut mem = [0u8; 4]; + assert!(matches!( + tst_br14_reloc(0x8004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 14, + align: 2 + })) + )); +} + +#[test] +fn tst_br14_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + tst_br14_reloc(0x0, 0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +// ═══════════════════════════════════════════════════════════════════════ +// cond_br19 (base, target, mem, offset) +// +// imm19 = (target − place) / 4 +// Signed 19-bit word offset → byte range [−(1<<20), (1<<20)−4] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn cond_br19_forward() { + // diff=4; imm19=1 + let mut mem = insn_buf(BEQ); + cond_br19_reloc(0x0, 0x0004, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), 1); +} + +#[test] +fn cond_br19_backward() { + // place=0x100; diff=−0x100; imm19=−64 + let mut mem = insn_buf(BEQ); + cond_br19_reloc(0x100, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), -64); +} + +#[test] +fn cond_br19_max_positive() { + // imm19 max = (1<<18)−1 → byte diff = 0x0F_FFFC + let mut mem = insn_buf(BEQ); + cond_br19_reloc(0x0, 0x0F_FFFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), (1 << 18) - 1); +} + +#[test] +fn cond_br19_max_negative() { + // imm19 min = −(1<<18) → byte diff = −0x10_0000; place=0x10_0000, target=0 + let mut mem = insn_buf(BEQ); + cond_br19_reloc(0x10_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm19(u32_at(&mem, 0)), -(1 << 18)); +} + +#[test] +fn cond_br19_overflow_positive() { + // byte diff=0x10_0000 → word imm=1<<18, one above max + let mut mem = [0u8; 4]; + assert!(matches!( + cond_br19_reloc(0x0, 0x10_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 19, + align: 2 + })) + )); +} + +#[test] +fn cond_br19_overflow_negative() { + let mut mem = [0u8; 4]; + assert!(matches!( + cond_br19_reloc(0x10_0004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 19, + align: 2 + })) + )); +} + +#[test] +fn cond_br19_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + cond_br19_reloc(0x0, 0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +// ═══════════════════════════════════════════════════════════════════════ +// jump26 (base, target, mem, offset) +// +// imm26 = (target − place) / 4 +// Signed 26-bit word offset → byte range [−(1<<27), (1<<27)−4] +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn jump26_forward() { + // diff=0x10; imm26=4 + let mut mem = insn_buf(B); + jump26_reloc(0x0, 0x10, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), 4); +} + +#[test] +fn jump26_backward() { + // place=0x10; diff=−0x10; imm26=−4 + let mut mem = insn_buf(B); + jump26_reloc(0x10, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), -4); +} + +#[test] +fn jump26_max_positive() { + // imm26 max = (1<<25)−1 → byte diff = 0x07FF_FFFC + let mut mem = insn_buf(B); + jump26_reloc(0x0, 0x07FF_FFFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), (1 << 25) - 1); +} + +#[test] +fn jump26_max_negative() { + // imm26 min = −(1<<25) → byte diff = −0x0800_0000 + // place=0x0800_0000, target=0 + let mut mem = insn_buf(B); + jump26_reloc(0x0800_0000, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), -(1 << 25)); +} + +#[test] +fn jump26_overflow_positive() { + // byte diff=0x0800_0000 → word imm=1<<25, one above max + let mut mem = [0u8; 4]; + assert!(matches!( + jump26_reloc(0x0, 0x0800_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 26, + align: 2 + })) + )); +} + +#[test] +fn jump26_overflow_negative() { + // place=0x0800_0004, target=0 → one below min + let mut mem = [0u8; 4]; + assert!(matches!( + jump26_reloc(0x0800_0004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 26, + align: 2 + })) + )); +} + +#[test] +fn jump26_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + jump26_reloc(0x0, 0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +// ═══════════════════════════════════════════════════════════════════════ +// call26 (base, target, mem, offset) +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn call26_forward() { + let mut mem = insn_buf(BL); + call26_reloc(0x0, 0x0004, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), 1); +} + +#[test] +fn call26_backward() { + // place=4; diff=−4; imm26=−1 + let mut mem = insn_buf(BL); + call26_reloc(0x4, 0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), -1); +} + +#[test] +fn call26_overflow_positive() { + let mut mem = [0u8; 4]; + assert!(matches!( + call26_reloc(0x0, 0x0800_0000, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 26, + align: 2 + })) + )); +} + +#[test] +fn call26_overflow_negative() { + let mut mem = [0u8; 4]; + assert!(matches!( + call26_reloc(0x0800_0004, 0x0, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Overflow { + significant_bits: 26, + align: 2 + })) + )); +} + +#[test] +fn call26_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + call26_reloc(0x0, 0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +// ═══════════════════════════════════════════════════════════════════════ +// MOV_W relocations (target, mem, offset) +// +// Unsigned Gk: S[k*16+15 : k*16] → imm16 +// Overflow if any bit above chunk k is set (except NC variants). +// Signed Gk: same chunk, sign-checked to (k+1)*16 bits. +// Negative → MOVN with ~chunk; non-negative → MOVZ. +// ═══════════════════════════════════════════════════════════════════════ + +// ── G0 ─────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g0_basic() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0_reloc(0xABCD, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +#[test] +fn mov_w_abs_g0_max() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0_reloc(0xFFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xFFFF); +} + +#[test] +fn mov_w_abs_g0_overflow() { + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g0_reloc(0x1_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G0 NC ───────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g0nc_basic() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0nc_reloc(0x1234, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x1234); +} + +#[test] +fn mov_w_abs_g0nc_strips_high_bits() { + // Only S[15:0] written; upper bits silently discarded + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0nc_reloc(0xDEAD_BEEF_0000_1234, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x1234); +} + +// ── G0 S ────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g0s_positive() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(0x1234, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x1234); +} + +#[test] +fn mov_w_abs_g0s_max_positive() { + // 0x7FFF = i16::MAX — largest that fits + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(0x7FFF, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g0s_minus_one() { + // MOVN imm=0 encodes −1 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(-1, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x0000); // ~(−1)[15:0] = 0 +} + +#[test] +fn mov_w_abs_g0s_max_negative() { + // i16::MIN = −0x8000; imm = ~(−0x8000)[15:0] = 0x7FFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(-0x8000, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g0s_negative_value() { + // −0x1234; imm = ~(−0x1234)[15:0] = 0x1233 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g0s_reloc(-0x1234, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x1233); +} + +#[test] +fn mov_w_abs_g0s_overflow_positive() { + // 0x8000 > i16::MAX + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g0s_reloc(0x8000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +#[test] +fn mov_w_abs_g0s_overflow_negative() { + // −0x8001 < i16::MIN + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g0s_reloc(-0x8001, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G1 ─────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g1_basic() { + // S[31:16] of 0xABCD_0000 = 0xABCD + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1_reloc(0xABCD_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +#[test] +fn mov_w_abs_g1_max() { + // 0xFFFF_FFFF — max value; S[31:16] = 0xFFFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1_reloc(0xFFFF_FFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xFFFF); +} + +#[test] +fn mov_w_abs_g1_overflow() { + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g1_reloc(0x1_0000_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G1 NC ───────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g1nc_basic() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1nc_reloc(0xABCD_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +#[test] +fn mov_w_abs_g1nc_strips_high_bits() { + // Only S[31:16] written; bits [63:32] discarded + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1nc_reloc(0xDEAD_BEEF_ABCD_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +// ── G1 S ────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g1s_positive() { + // 0x7FFF_0000 fits in signed 32-bit; chunk 1 = 0x7FFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1s_reloc(0x7FFF_0000, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g1s_max_positive() { + // i32::MAX = 0x7FFF_FFFF; chunk 1 = 0x7FFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1s_reloc(0x7FFF_FFFF, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g1s_negative() { + // −0x8000_0000 = i32::MIN; ~target = 0x7FFF_FFFF; chunk 1 = 0x7FFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1s_reloc(-0x8000_0000, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x7FFF); +} + +#[test] +fn mov_w_abs_g1s_negative_value() { + // −0x1234_5678; ~target = 0x1234_5677; chunk 1 = 0x1234 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g1s_reloc(-0x1234_5678, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x1234); +} + +#[test] +fn mov_w_abs_g1s_overflow_positive() { + // 0x8000_0000 = i32::MAX + 1 + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g1s_reloc(0x8000_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +#[test] +fn mov_w_abs_g1s_overflow_negative() { + // −0x8000_0001 < i32::MIN + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g1s_reloc(-0x8000_0001, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G2 ─────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g2_basic() { + // S[47:32] of 0x0001_0000_0000 = 0x0001 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2_reloc(0x0001_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x0001); +} + +#[test] +fn mov_w_abs_g2_max() { + // 0xFFFF_FFFF_FFFF — max value; S[47:32] = 0xFFFF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2_reloc(0xFFFF_FFFF_FFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xFFFF); +} + +#[test] +fn mov_w_abs_g2_overflow() { + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g2_reloc(0x0001_0000_0000_0000, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G2 NC ───────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g2nc_basic() { + // S[47:32] of 0xDEAD_1234_0000_0000 = 0x1234 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2nc_reloc(0xDEAD_1234_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x1234); +} + +#[test] +fn mov_w_abs_g2nc_strips_high_bits() { + // Bits [63:48] discarded without error + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2nc_reloc(0xFFFF_ABCD_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +// ── G2 S ────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g2s_positive() { + // 0x0000_1234_0000_0000 fits in signed 48-bit; chunk 2 = 0x1234 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2s_reloc(0x0000_1234_0000_0000_i64, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVZ); + assert_eq!(dec_movw(i), 0x1234); +} + +#[test] +fn mov_w_abs_g2s_negative() { + // −0x1234_0000_0000; ~target = 0x1233_FFFF_FFFF; chunk 2 = 0x1233 + let mut mem = insn_buf(MOVZ); + mov_w_abs_g2s_reloc(-0x1234_0000_0000_i64, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(mov_w_opc(i), MOV_W_OPC_MOVN); + assert_eq!(dec_movw(i), 0x1233); +} + +#[test] +fn mov_w_abs_g2s_overflow_positive() { + // 2^47 — one above signed 48-bit max (2^47 − 1) + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g2s_reloc(0x0000_8000_0000_0000_u64 as i64, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +#[test] +fn mov_w_abs_g2s_overflow_negative() { + // −(2^47 + 1) — one below signed 48-bit min (−2^47) + let mut mem = insn_buf(MOVZ); + assert!(matches!( + mov_w_abs_g2s_reloc(-0x8000_0000_0001_i64, &mut mem, 0), + Err(Rel64Error::InvalidValue(_)) + )); +} + +// ── G3 ─────────────────────────────────────────────────────────────── + +#[test] +fn mov_w_abs_g3_basic() { + // S[63:48] of 0xBEEF_0000_0000_0000 = 0xBEEF + let mut mem = insn_buf(MOVZ); + mov_w_abs_g3_reloc(0xBEEF_0000_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xBEEF); +} + +#[test] +fn mov_w_abs_g3_max() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g3_reloc(0xFFFF_0000_0000_0000, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xFFFF); +} + +#[test] +fn mov_w_abs_g3_zero() { + let mut mem = insn_buf(MOVZ); + mov_w_abs_g3_reloc(0x0, &mut mem, 0).unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x0000); +} + +// ── Cross-cutting: bits outside the immediate field are preserved ───── + +#[test] +fn patch_preserves_non_immediate_fields() { + // MOVK X5, #0 (opc=11, Rd=5) → 0xF280_0005 + let movk_x5: u32 = 0xF280_0005; + let mut mem = movk_x5.to_le_bytes().to_vec(); + mov_w_abs_g0nc_reloc(0xABCD, &mut mem, 0).unwrap(); + let i = u32_at(&mem, 0); + assert_eq!(dec_movw(i), 0xABCD); + assert_eq!(mov_w_opc(i), 0b11); // opc preserved + assert_eq!(i & 0x1F, 5); // Rd preserved +} + +// ═══════════════════════════════════════════════════════════════════════ +// Rel64Tag::apply — checks dispatch and the place = base + offset formula +// ═══════════════════════════════════════════════════════════════════════ + +#[test] +fn tag_none_is_noop() { + let mut mem = [0xFFu8; 8]; + Rel64Tag::None.apply(0, 0, &mut mem, 0).unwrap(); + assert!(mem.iter().all(|&b| b == 0xFF)); +} + +#[test] +fn tag_abs64_roundtrip() { + let mut mem = [0u8; 8]; + Rel64Tag::Abs64 + .apply(0, 0x1122_3344_5566_7788, &mut mem, 0) + .unwrap(); + assert_eq!(u64_at(&mem, 0), 0x1122_3344_5566_7788); +} + +#[test] +fn tag_abs32_via_apply() { + let mut mem = [0u8; 4]; + Rel64Tag::Abs32.apply(0, 0x7EAD_BEEF, &mut mem, 0).unwrap(); + assert_eq!(u32_at(&mem, 0), 0x7EAD_BEEF); +} + +#[test] +fn tag_prel32_via_apply() { + // base=0, offset=0 → place=0; diff=i32::MAX + let mut mem = [0u8; 4]; + Rel64Tag::PRel32 + .apply(0x0, 0x7FFF_FFFF, &mut mem, 0) + .unwrap(); + assert_eq!(u32_at(&mem, 0) as i32, i32::MAX); +} + +#[test] +fn tag_jump26_via_apply() { + // base=0, offset=0 → place=0; diff=8; imm26=2 + let mut mem = insn_buf(B); + Rel64Tag::Jump26.apply(0x0, 0x0008, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), 2); +} + +#[test] +fn tag_call26_via_apply() { + // base=0, offset=0 → place=0; diff=4; imm26=1 + let mut mem = insn_buf(BL); + Rel64Tag::Call26.apply(0x0, 0x0004, &mut mem, 0).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 0)), 1); +} + +#[test] +fn tag_adrp_via_apply() { + // base=0x1100, offset=0 → place=0x1100 (page 0x1000) + // target=0x3500 → page 0x3000; imm21=2 + let mut mem = [0u8; 4]; + Rel64Tag::AdrPrelPgHi21 + .apply(0x1100, 0x3500, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm21_adr(u32_at(&mem, 0)), 2); +} + +#[test] +fn tag_mov_w_g1nc_via_apply() { + let mut mem = insn_buf(MOVZ); + Rel64Tag::MovWAbsG1Nc + .apply(0, 0xDEAD_BEEF_ABCD_0000, &mut mem, 0) + .unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xABCD); +} + +#[test] +fn tag_mov_w_g2nc_via_apply() { + let mut mem = insn_buf(MOVZ); + Rel64Tag::MovWAbsG2Nc + .apply(0, 0xDEAD_1234_0000_0000, &mut mem, 0) + .unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0x1234); +} + +#[test] +fn tag_mov_w_g3_via_apply() { + let mut mem = insn_buf(MOVZ); + Rel64Tag::MovWAbsG3 + .apply(0, 0xBEEF_0000_0000_0000, &mut mem, 0) + .unwrap(); + assert_eq!(dec_movw(u32_at(&mem, 0)), 0xBEEF); +} + +#[test] +fn tag_place_is_base_plus_offset() { + // Verifies apply() uses place = base + offset, not just base. + // B at mem[4]; base=0x0FFC, offset=4 → place=0x1000 + // target=0x1010; diff=0x10; imm26=4 + let mut mem = [0u8; 8]; + mem[4..8].copy_from_slice(&B.to_le_bytes()); + Rel64Tag::Jump26.apply(0x0FFC, 0x1010, &mut mem, 4).unwrap(); + assert_eq!(dec_imm26(u32_at(&mem, 4)), 4); +} + +// ═══════════════════════════════════════════════════════════════════════ +// ldst{8,16,32,64,128}_abs_lo12_nc (target, mem, offset) +// +// imm12 = (target & 0xFFF) >> shift +// where shift = 0 / 1 / 2 / 3 / 4 for 8/16/32/64/128-bit access. +// UnalignedValue if the low `shift` bits of target are non-zero. +// No overflow check (NC): high bits above 12 are silently discarded. +// +// Instruction skeletons (LDR Rt, [Rn, #0]): +// LDRB W0,[X1] → 0x3940_0020 (8-bit) +// LDRH W0,[X1] → 0x7940_0020 (16-bit) +// LDR W0,[X1] → 0xB940_0020 (32-bit) +// LDR X0,[X1] → 0xF940_0020 (64-bit) +// LDR Q0,[X1] → 0x3DC0_0020 (128-bit) +// ═══════════════════════════════════════════════════════════════════════ + +// Skeleton instruction words for load/store unsigned-offset forms. +const LDRB: u32 = 0x3940_0020; // LDRB W0, [X1, #0] +const LDRH: u32 = 0x7940_0020; // LDRH W0, [X1, #0] +const LDR_W: u32 = 0xB940_0020; // LDR W0, [X1, #0] +const LDR_X: u32 = 0xF940_0020; // LDR X0, [X1, #0] +const LDR_Q: u32 = 0x3DC0_0020; // LDR Q0, [X1, #0] (128-bit) + +// ── ldst8 (shift = 0, no alignment requirement) ─────────────────────── + +#[test] +fn ldst8_basic() { + let mut mem = insn_buf(LDRB); + ldst8_abs_lo12_nc_reloc(0x1ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} + +#[test] +fn ldst8_odd_target() { + // Byte access: odd addresses are valid. + let mut mem = insn_buf(LDRB); + ldst8_abs_lo12_nc_reloc(0x1, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x001); +} + +#[test] +fn ldst8_max_field() { + let mut mem = insn_buf(LDRB); + ldst8_abs_lo12_nc_reloc(0xFFF, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFFF); +} + +#[test] +fn ldst8_masks_high_bits() { + let mut mem = insn_buf(LDRB); + ldst8_abs_lo12_nc_reloc(0xDEAD_BEEF_CAFE_1234, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234); +} + +// ── ldst16 (shift = 1, must be 2-byte aligned) ──────────────────────── + +#[test] +fn ldst16_basic() { + // target = 0x1_ABC (even) → imm12 = 0xABC >> 1 = 0x55E + let mut mem = insn_buf(LDRH); + ldst16_abs_lo12_nc_reloc(0x1_ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC >> 1); +} + +#[test] +fn ldst16_max_field() { + // Max aligned lo12 = 0xFFE → imm12 = 0x7FF + let mut mem = insn_buf(LDRH); + ldst16_abs_lo12_nc_reloc(0xFFE, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x7FF); +} + +#[test] +fn ldst16_masks_high_bits() { + // Only lo12 matters; high bits discarded before shift. + let mut mem = insn_buf(LDRH); + ldst16_abs_lo12_nc_reloc(0xDEAD_BEEF_CAFE_1234, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234 >> 1); +} + +#[test] +fn ldst16_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst16_abs_lo12_nc_reloc(0x1, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 1 })) + )); +} + +// ── ldst32 (shift = 2, must be 4-byte aligned) ──────────────────────── + +#[test] +fn ldst32_basic() { + // target = 0x1_ABC, but 0xABC & 3 = 0 → only multiples of 4 are valid. + // Use 0x1_ABC & !3 = 0x1_AB8 → wait, 0xABC = 0b1010_1011_1100, & 3 = 0 ✓ + // Actually 0xABC = 2748, 2748 % 4 = 0 ✓ + let mut mem = insn_buf(LDR_W); + ldst32_abs_lo12_nc_reloc(0x1_ABC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC >> 2); +} + +#[test] +fn ldst32_max_field() { + // Max aligned lo12 = 0xFFC → imm12 = 0x3FF + let mut mem = insn_buf(LDR_W); + ldst32_abs_lo12_nc_reloc(0xFFC, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x3FF); +} + +#[test] +fn ldst32_masks_high_bits() { + let mut mem = insn_buf(LDR_W); + ldst32_abs_lo12_nc_reloc(0xDEAD_BEEF_CAFE_1234, &mut mem, 0).unwrap(); + // 0x234 & !3 = 0x234 (already aligned); 0x234 >> 2 = 0x8D + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x234 >> 2); +} + +#[test] +fn ldst32_unaligned_by_1() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst32_abs_lo12_nc_reloc(0x1, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +#[test] +fn ldst32_unaligned_by_2() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst32_abs_lo12_nc_reloc(0x2, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 2 })) + )); +} + +// ── ldst64 (shift = 3, must be 8-byte aligned) ──────────────────────── + +#[test] +fn ldst64_basic() { + // 0xFF8 & 7 = 0 ✓ → imm12 = 0xFF8 >> 3 = 0x1FF + let mut mem = insn_buf(LDR_X); + ldst64_abs_lo12_nc_reloc(0xFF8, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF8 >> 3); +} + +#[test] +fn ldst64_max_field() { + // Max aligned lo12 = 0xFF8 → imm12 = 0x1FF + let mut mem = insn_buf(LDR_X); + ldst64_abs_lo12_nc_reloc(0xFF8, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0x1FF); +} + +#[test] +fn ldst64_masks_high_bits() { + // 0x1_000_000_FF8: lo12 = 0xFF8, already 8-aligned; imm12 = 0x1FF + let mut mem = insn_buf(LDR_X); + ldst64_abs_lo12_nc_reloc(0x1_0000_0000_0FF8, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF8 >> 3); +} + +#[test] +fn ldst64_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst64_abs_lo12_nc_reloc(0x4, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 3 })) + )); +} + +// ── ldst128 (shift = 4, must be 16-byte aligned) ────────────────────── + +#[test] +fn ldst128_basic() { + // 0xFF0 & 15 = 0 ✓ → imm12 = 0xFF0 >> 4 = 0xFF + let mut mem = insn_buf(LDR_Q); + ldst128_abs_lo12_nc_reloc(0xFF0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF); +} + +#[test] +fn ldst128_max_field() { + // Max aligned lo12 = 0xFF0 → imm12 = 0xFF + let mut mem = insn_buf(LDR_Q); + ldst128_abs_lo12_nc_reloc(0xFF0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF); +} + +#[test] +fn ldst128_masks_high_bits() { + let mut mem = insn_buf(LDR_Q); + ldst128_abs_lo12_nc_reloc(0xDEAD_BEEF_0001_0FF0, &mut mem, 0).unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF0 >> 4); +} + +#[test] +fn ldst128_unaligned() { + let mut mem = [0u8; 4]; + assert!(matches!( + ldst128_abs_lo12_nc_reloc(0x8, &mut mem, 0), + Err(Rel64Error::InvalidBits(BitError::Alignment { align: 4 })) + )); +} + +// ── Cross-cutting: non-immediate bits are preserved ─────────────────── + +#[test] +fn ldst_patch_preserves_non_imm12_fields() { + // Use LDR X5, [X3, #0]: size=11, V=0, opc=01, Rn=3, Rt=5 → 0xF940_0065 + let ldr_x5_x3: u32 = 0xF940_0065; + let mut mem = ldr_x5_x3.to_le_bytes(); + ldst64_abs_lo12_nc_reloc(0x1_0000_0000_0FF8, &mut mem, 0).unwrap(); + let insn = u32_at(&mem, 0); + assert_eq!(dec_imm12(insn), 0xFF8 >> 3); + assert_eq!(insn & 0xFC00_03FF, ldr_x5_x3 & 0xFC00_03FF); // size/opc/Rn/Rt preserved +} + +// ── Tag dispatch ────────────────────────────────────────────────────── + +#[test] +fn tag_ldst8_via_apply() { + let mut mem = insn_buf(LDRB); + Rel64Tag::LdSt8AbsLo12Nc + .apply(0, 0xABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC); +} + +#[test] +fn tag_ldst16_via_apply() { + let mut mem = insn_buf(LDRH); + Rel64Tag::LdSt16AbsLo12Nc + .apply(0, 0xABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC >> 1); +} + +#[test] +fn tag_ldst32_via_apply() { + let mut mem = insn_buf(LDR_W); + Rel64Tag::LdSt32AbsLo12Nc + .apply(0, 0xABC, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xABC >> 2); +} + +#[test] +fn tag_ldst64_via_apply() { + let mut mem = insn_buf(LDR_X); + Rel64Tag::LdSt64AbsLo12Nc + .apply(0, 0xFF8, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF8 >> 3); +} + +#[test] +fn tag_ldst128_via_apply() { + let mut mem = insn_buf(LDR_Q); + Rel64Tag::LdSt128AbsLo12Nc + .apply(0, 0xFF0, &mut mem, 0) + .unwrap(); + assert_eq!(dec_imm12(u32_at(&mem, 0)), 0xFF0 >> 4); +} + +#[test] +fn tag_ldst_unaligned_via_apply() { + // Alignment is enforced through the tag dispatcher too. + let mut mem = [0u8; 4]; + assert!(matches!( + Rel64Tag::LdSt64AbsLo12Nc.apply(0, 0x4, &mut mem, 0), + Err(Rel64Error::InvalidBits(_)) + )); +} + +// ── Contrast with AddAbsLo12Nc (same imm12 field, shift=0) ─────────── + +#[test] +fn ldst8_and_add_produce_identical_imm12() { + // LdSt8 is shift=0, identical formula to AddAbsLo12Nc. + let mut mem_ldst = insn_buf(LDRB); + let mut mem_add = insn_buf(ADD); + ldst8_abs_lo12_nc_reloc(0x1_ABC, &mut mem_ldst, 0).unwrap(); + add_abs_lo12_nc_reloc(0x1_ABC, &mut mem_add, 0).unwrap(); + assert_eq!( + dec_imm12(u32_at(&mem_ldst, 0)), + dec_imm12(u32_at(&mem_add, 0)) + ); +} diff --git a/harm/src/reloc/control.rs b/harm/src/reloc/control.rs new file mode 100644 index 0000000..6d7d063 --- /dev/null +++ b/harm/src/reloc/control.rs @@ -0,0 +1,92 @@ +/* Copyright (C) 2026 Ivan Boldyrev + * + * This document is licensed under the BSD 3-clause license. + */ + +use aarchmrs_types::InstructionCode; + +use super::{Addr, Rel64Error, calc_offset}; +use crate::instructions::control::{BranchCondOffset, BranchOffset, TestBranchOffset}; +use crate::reloc::get_bytes_mut; + +const JUMP_IMM26_OFFSET: u32 = 0u32; +const JUMP_IMM26_WIDTH: u32 = 26u32; + +const TST_BR_IMM14_OFFSET: u32 = 5u32; +const TST_BR_IMM14_WIDTH: u32 = 14u32; + +const COND_BR_IMM19_OFFSET: u32 = 5u32; +const COND_BR_IMM19_WIDTH: u32 = 19u32; + +pub fn jump26_reloc( + base: Addr, + target: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let jump_offset64 = calc_offset(base, target, offset)?; + let jump_offset = BranchOffset::new_i64(jump_offset64)?; + let bytes = get_bytes_mut(mem, offset)?; + let code = InstructionCode(*bytes).unpack(); + + let jump_offset_bits = jump_offset.bits(); + + let code_cleaned = code & !(((1u32 << JUMP_IMM26_WIDTH) - 1) << JUMP_IMM26_OFFSET); + let code_patched = code_cleaned | ((jump_offset_bits as u32) << JUMP_IMM26_OFFSET); + + *bytes = InstructionCode::from_u32(code_patched).0; + + Ok(()) +} + +#[inline] +pub fn call26_reloc( + base: Addr, + target: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + jump26_reloc(base, target, mem, offset) +} + +pub fn tst_br14_reloc( + base: Addr, + target: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let tst_offset64 = calc_offset(base, target, offset)?; + let tst_offset = TestBranchOffset::new_i64(tst_offset64)?; + let bytes = get_bytes_mut(mem, offset)?; + let code = InstructionCode(*bytes).unpack(); + + let jump_offset_bits = tst_offset.bits(); + + let code_cleaned = code & !(((1u32 << TST_BR_IMM14_WIDTH) - 1) << TST_BR_IMM14_OFFSET); + let code_patched = code_cleaned | ((jump_offset_bits as u32) << TST_BR_IMM14_OFFSET); + + *bytes = InstructionCode::from_u32(code_patched).0; + + Ok(()) +} + +pub fn cond_br19_reloc( + base: Addr, + target: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let cond_offset64 = calc_offset(base, target, offset)?; + let cond_offset = BranchCondOffset::new_i64(cond_offset64)?; + let bytes = get_bytes_mut(mem, offset)?; + let code = InstructionCode(*bytes).unpack(); + + let jump_offset_bits = cond_offset.bits(); + + let code_cleaned = code & !(((1u32 << COND_BR_IMM19_WIDTH) - 1) << COND_BR_IMM19_OFFSET); + let code_patched = code_cleaned | ((jump_offset_bits as u32) << COND_BR_IMM19_OFFSET); + + *bytes = InstructionCode::from_u32(code_patched).0; + + Ok(()) +} diff --git a/harm/src/reloc/data.rs b/harm/src/reloc/data.rs new file mode 100644 index 0000000..2e24c2b --- /dev/null +++ b/harm/src/reloc/data.rs @@ -0,0 +1,93 @@ +/* Copyright (C) 2026 Ivan Boldyrev + * + * This document is licensed under the BSD 3-clause license. + */ + +use super::{Addr, Rel64Error, calc_offset, get_bytes_mut}; + +pub fn abs64_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + *bytes = value.to_le_bytes(); + Ok(()) +} + +pub fn abs32_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: u32 = try_unsigned_and_signed::(value)?; + let bytes = get_bytes_mut(mem, offset)?; + *bytes = target.to_le_bytes(); + Ok(()) +} + +pub fn abs16_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: u16 = try_unsigned_and_signed::(value)?; + let bytes = get_bytes_mut(mem, offset)?; + *bytes = target.to_le_bytes(); + Ok(()) +} + +pub fn prel64_reloc( + base: Addr, + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + + *bytes = calc_offset(base, symbol, offset)?.to_le_bytes(); + Ok(()) +} + +pub fn prel32_reloc( + base: Addr, + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + + let delta = calc_offset(base, symbol, offset)?; + let value = try_unsigned_and_signed::(delta)?; + *bytes = value.to_le_bytes(); + Ok(()) +} + +pub fn prel16_reloc( + base: Addr, + symbol: Addr, + mem: &mut [u8], + offset: usize, +) -> Result<(), Rel64Error> { + let bytes = get_bytes_mut(mem, offset)?; + + let delta = calc_offset(base, symbol, offset)?; + let value = try_unsigned_and_signed::(delta)?; + *bytes = value.to_le_bytes(); + Ok(()) +} + +fn try_unsigned_and_signed(value: i64) -> Result +where + T1: TryFrom, + T2: TryFrom, + Rel64Error: From<>::Error>, + T2: AsPrimitive, +{ + Ok(T1::try_from(value).or_else(|_| T2::try_from(value).map(AsPrimitive::as_primitive))?) +} + +// It repeats partially the num-traits crate, but I want the crate to be leaner. +trait AsPrimitive { + fn as_primitive(self) -> Other; +} + +impl AsPrimitive for i32 { + fn as_primitive(self) -> u32 { + self as u32 + } +} + +impl AsPrimitive for i16 { + fn as_primitive(self) -> u16 { + self as u16 + } +} diff --git a/harm/src/reloc/movs.rs b/harm/src/reloc/movs.rs new file mode 100644 index 0000000..3e49a7b --- /dev/null +++ b/harm/src/reloc/movs.rs @@ -0,0 +1,112 @@ +/* Copyright (C) 2026 Ivan Boldyrev + * + * This document is licensed under the BSD 3-clause license. + */ +use aarchmrs_types::InstructionCode; + +use super::Rel64Error; +use crate::reloc::get_bytes_mut; + +const MOV_OPCODE_OFFSET: u32 = 29; +const MOV_OPCODE_WIDTH: u32 = 2; + +const MOV_OPCODE_MOVN: u32 = 0; +const MOV_OPCODE_MOVZ: u32 = 2; + +const MOV_IMM16_OFFSET: u32 = 5; +const MOV_IMM16_WIDTH: u32 = 16; + +pub fn mov_w_abs_g0_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: u16 = value.try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g0nc_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target = value as u16; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g0s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: i16 = value.try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov_signed(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g1_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: u16 = (value >> 16).try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g1nc_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target = (value >> 16) as u16; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g1s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: i16 = (value >> 16).try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov_signed(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g2_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: u16 = (value >> 32).try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g2nc_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target = (value >> 32) as u16; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g2s_reloc(value: i64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target: i16 = (value >> 32).try_into()?; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov_signed(target, bytes); + Ok(()) +} + +pub fn mov_w_abs_g3_reloc(value: u64, mem: &mut [u8], offset: usize) -> Result<(), Rel64Error> { + let target = (value >> 48) as u16; + let bytes = get_bytes_mut(mem, offset)?; + patch_mov(target, bytes); + Ok(()) +} + +fn patch_mov(target: u16, bytes: &mut [u8; 4]) { + const INST_MASK: u32 = ((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET; + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + inst_code |= (target as u32) << MOV_IMM16_OFFSET; + *bytes = InstructionCode::from_u32(inst_code).0; +} + +fn patch_mov_signed(target: i16, bytes: &mut [u8; 4]) { + const INST_MASK: u32 = (((1 << MOV_IMM16_WIDTH) - 1) << MOV_IMM16_OFFSET) + | (((1 << MOV_OPCODE_WIDTH) - 1) << MOV_OPCODE_OFFSET); + + let mut inst_code = InstructionCode(*bytes).unpack(); + inst_code &= !INST_MASK; + if target < 0 { + inst_code |= ((!target as u16) as u32) << MOV_IMM16_OFFSET; + inst_code |= MOV_OPCODE_MOVN << MOV_OPCODE_OFFSET; + } else { + inst_code |= ((target as u16) as u32) << MOV_IMM16_OFFSET; + inst_code |= MOV_OPCODE_MOVZ << MOV_OPCODE_OFFSET; + } + *bytes = InstructionCode::from_u32(inst_code).0; +}