Relax adrp-add hints
- SHA
faaa755af0a1d10c8caa4decbc17a18c07dffd65- Parents
-
1e80916 - Tree
b509fee
faaa755
faaa755af0a1d10c8caa4decbc17a18c07dffd651e80916
b509fee| Status | File | + | - |
|---|---|---|---|
| M |
src/args.rs
|
10 | 0 |
| M |
src/lib.rs
|
11 | 0 |
| M |
src/loh.rs
|
178 | 0 |
| M |
src/macho/writer.rs
|
4 | 0 |
| M |
src/main.rs
|
1 | 0 |
| M |
tests/cli_diagnostics.rs
|
38 | 0 |
| M |
tests/linker_run.rs
|
189 | 0 |
| M |
tests/snapshots/help.txt
|
1 | 0 |
src/args.rsmodified@@ -33,6 +33,7 @@ const KNOWN_FLAGS: &[&str] = &[ | ||
| 33 | 33 | "-unexported_symbol", |
| 34 | 34 | "-S", |
| 35 | 35 | "-no_uuid", |
| 36 | + "-no_loh", | |
| 36 | 37 | "-dead_strip", |
| 37 | 38 | "-icf=safe", |
| 38 | 39 | "-icf=none", |
@@ -312,6 +313,9 @@ pub fn parse(argv: &[String]) -> Result<LinkOptions, ArgsError> { | ||
| 312 | 313 | "-no_uuid" => { |
| 313 | 314 | opts.emit_uuid = false; |
| 314 | 315 | } |
| 316 | + "-no_loh" => { | |
| 317 | + opts.no_loh = true; | |
| 318 | + } | |
| 315 | 319 | "-dead_strip" => { |
| 316 | 320 | opts.dead_strip = true; |
| 317 | 321 | } |
@@ -466,6 +470,12 @@ mod tests { | ||
| 466 | 470 | assert!(!opts.emit_uuid); |
| 467 | 471 | } |
| 468 | 472 | |
| 473 | + #[test] | |
| 474 | + fn no_loh_flag_is_recorded() { | |
| 475 | + let opts = parse(&argv(&["-no_loh", "foo.o"])).unwrap(); | |
| 476 | + assert!(opts.no_loh); | |
| 477 | + } | |
| 478 | + | |
| 469 | 479 | #[test] |
| 470 | 480 | fn dead_strip_icf_and_fixup_chain_flags_are_recorded() { |
| 471 | 481 | let opts = parse(&argv(&[ |
src/lib.rsmodified@@ -103,6 +103,7 @@ pub struct LinkOptions { | ||
| 103 | 103 | pub strip_debug: bool, |
| 104 | 104 | pub emit_uuid: bool, |
| 105 | 105 | pub dead_strip: bool, |
| 106 | + pub no_loh: bool, | |
| 106 | 107 | pub icf_mode: IcfMode, |
| 107 | 108 | pub fixup_chains: bool, |
| 108 | 109 | pub all_load: bool, |
@@ -152,6 +153,7 @@ impl Default for LinkOptions { | ||
| 152 | 153 | strip_debug: false, |
| 153 | 154 | emit_uuid: true, |
| 154 | 155 | dead_strip: false, |
| 156 | + no_loh: false, | |
| 155 | 157 | icf_mode: IcfMode::None, |
| 156 | 158 | fixup_chains: false, |
| 157 | 159 | all_load: false, |
@@ -179,6 +181,7 @@ pub enum LinkError { | ||
| 179 | 181 | Synth(synth::SynthError), |
| 180 | 182 | Unwind(synth::unwind::UnwindError), |
| 181 | 183 | Icf(IcfError), |
| 184 | + Loh(loh::LohError), | |
| 182 | 185 | DuplicateSymbols(String), |
| 183 | 186 | UndefinedSymbols(String), |
| 184 | 187 | UnsupportedArch(String), |
@@ -205,6 +208,7 @@ impl std::fmt::Display for LinkError { | ||
| 205 | 208 | LinkError::Synth(e) => write!(f, "{e}"), |
| 206 | 209 | LinkError::Unwind(e) => write!(f, "{e}"), |
| 207 | 210 | LinkError::Icf(e) => write!(f, "{e}"), |
| 211 | + LinkError::Loh(e) => write!(f, "{e}"), | |
| 208 | 212 | LinkError::DuplicateSymbols(msg) | LinkError::UndefinedSymbols(msg) => { |
| 209 | 213 | write!(f, "{msg}") |
| 210 | 214 | } |
@@ -304,6 +308,12 @@ impl From<IcfError> for LinkError { | ||
| 304 | 308 | } |
| 305 | 309 | } |
| 306 | 310 | |
| 311 | +impl From<loh::LohError> for LinkError { | |
| 312 | + fn from(value: loh::LohError) -> Self { | |
| 313 | + LinkError::Loh(value) | |
| 314 | + } | |
| 315 | +} | |
| 316 | + | |
| 307 | 317 | /// The linker itself. Sprint 0 only validates that inputs exist; later sprints |
| 308 | 318 | /// grow this into the full pipeline described in `.docs/overview.md`. |
| 309 | 319 | pub struct Linker; |
@@ -570,6 +580,7 @@ impl Linker { | ||
| 570 | 580 | &linkedit, |
| 571 | 581 | icf.as_ref().map(|plan| plan.redirects()), |
| 572 | 582 | )?; |
| 583 | + loh::relax_layout(&mut layout, &linkedit, !opts.no_loh)?; | |
| 573 | 584 | let folded_symbols = icf |
| 574 | 585 | .as_ref() |
| 575 | 586 | .map(|plan| plan.folded_symbols(&atom_table, &sym_table, &layout_inputs)) |
src/loh.rsmodified@@ -4,13 +4,19 @@ | ||
| 4 | 4 | //! args...)` records. The args are file offsets of the participating |
| 5 | 5 | //! instructions. |
| 6 | 6 | |
| 7 | +use std::collections::HashSet; | |
| 8 | +use std::fmt; | |
| 9 | + | |
| 10 | +use crate::layout::Layout; | |
| 7 | 11 | use crate::leb::{read_uleb, write_uleb}; |
| 8 | 12 | use crate::macho::reader::ReadError; |
| 13 | +use crate::macho::writer::LinkEditPlan; | |
| 9 | 14 | |
| 10 | 15 | pub const LOH_ARM64_ADRP_LDR: u32 = 2; |
| 11 | 16 | pub const LOH_ARM64_ADRP_LDR_GOT_LDR: u32 = 4; |
| 12 | 17 | pub const LOH_ARM64_ADRP_ADD: u32 = 7; |
| 13 | 18 | pub const LOH_ARM64_ADRP_LDR_GOT: u32 = 8; |
| 19 | +const NOP: u32 = 0xd503_201f; | |
| 14 | 20 | |
| 15 | 21 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 16 | 22 | pub struct LohEntry { |
@@ -18,6 +24,32 @@ pub struct LohEntry { | ||
| 18 | 24 | pub args: Vec<u32>, |
| 19 | 25 | } |
| 20 | 26 | |
| 27 | +#[derive(Debug, Clone, PartialEq, Eq)] | |
| 28 | +pub struct LohError(String); | |
| 29 | + | |
| 30 | +impl fmt::Display for LohError { | |
| 31 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| 32 | + write!(f, "LOH relaxation error: {}", self.0) | |
| 33 | + } | |
| 34 | +} | |
| 35 | + | |
| 36 | +impl std::error::Error for LohError {} | |
| 37 | + | |
| 38 | +impl From<ReadError> for LohError { | |
| 39 | + fn from(value: ReadError) -> Self { | |
| 40 | + Self(value.to_string()) | |
| 41 | + } | |
| 42 | +} | |
| 43 | + | |
| 44 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
| 45 | +struct LocatedWord { | |
| 46 | + section_idx: usize, | |
| 47 | + atom_idx: usize, | |
| 48 | + word_off: usize, | |
| 49 | + addr: u64, | |
| 50 | + insn: u32, | |
| 51 | +} | |
| 52 | + | |
| 21 | 53 | pub fn parse_loh_blob(bytes: &[u8]) -> Result<Vec<LohEntry>, ReadError> { |
| 22 | 54 | let mut out = Vec::new(); |
| 23 | 55 | let mut cursor = 0usize; |
@@ -64,6 +96,140 @@ pub fn write_loh_blob(entries: &[LohEntry]) -> Vec<u8> { | ||
| 64 | 96 | out |
| 65 | 97 | } |
| 66 | 98 | |
| 99 | +pub fn relax_layout( | |
| 100 | + layout: &mut Layout, | |
| 101 | + linkedit: &LinkEditPlan, | |
| 102 | + enabled: bool, | |
| 103 | +) -> Result<(), LohError> { | |
| 104 | + if !enabled || linkedit.loh.is_none() || linkedit.loh_bytes().is_empty() { | |
| 105 | + return Ok(()); | |
| 106 | + } | |
| 107 | + | |
| 108 | + let entries = parse_loh_blob(linkedit.loh_bytes())?; | |
| 109 | + let mut rewritten = HashSet::new(); | |
| 110 | + for entry in entries { | |
| 111 | + if entry.kind != LOH_ARM64_ADRP_ADD || entry.args.len() != 2 { | |
| 112 | + continue; | |
| 113 | + } | |
| 114 | + | |
| 115 | + let adrp_off = entry.args[0] as u64; | |
| 116 | + let add_off = entry.args[1] as u64; | |
| 117 | + if !rewritten.insert(adrp_off) || !rewritten.insert(add_off) { | |
| 118 | + continue; | |
| 119 | + } | |
| 120 | + | |
| 121 | + let adrp = locate_word(layout, adrp_off)?; | |
| 122 | + let add = locate_word(layout, add_off)?; | |
| 123 | + let Some(target) = decode_adrp_add_target(adrp.insn, add.insn, adrp.addr) else { | |
| 124 | + continue; | |
| 125 | + }; | |
| 126 | + let Some(adr) = encode_adr(target, adrp.addr, (adrp.insn & 0x1f) as u8) else { | |
| 127 | + continue; | |
| 128 | + }; | |
| 129 | + | |
| 130 | + write_word(layout, adrp, adr)?; | |
| 131 | + write_word(layout, add, NOP)?; | |
| 132 | + } | |
| 133 | + Ok(()) | |
| 134 | +} | |
| 135 | + | |
| 136 | +fn locate_word(layout: &Layout, file_offset: u64) -> Result<LocatedWord, LohError> { | |
| 137 | + for (section_idx, section) in layout.sections.iter().enumerate() { | |
| 138 | + for (atom_idx, atom) in section.atoms.iter().enumerate() { | |
| 139 | + let start = section.file_off + atom.offset; | |
| 140 | + let end = start + atom.data.len() as u64; | |
| 141 | + if !(start <= file_offset && file_offset + 4 <= end) { | |
| 142 | + continue; | |
| 143 | + } | |
| 144 | + let word_off = (file_offset - start) as usize; | |
| 145 | + let bytes = atom.data.get(word_off..word_off + 4).ok_or_else(|| { | |
| 146 | + LohError(format!( | |
| 147 | + "instruction read OOB at file offset 0x{file_offset:x}" | |
| 148 | + )) | |
| 149 | + })?; | |
| 150 | + return Ok(LocatedWord { | |
| 151 | + section_idx, | |
| 152 | + atom_idx, | |
| 153 | + word_off, | |
| 154 | + addr: section.addr + atom.offset + word_off as u64, | |
| 155 | + insn: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]), | |
| 156 | + }); | |
| 157 | + } | |
| 158 | + } | |
| 159 | + Err(LohError(format!( | |
| 160 | + "LOH instruction offset 0x{file_offset:x} did not resolve to an output atom" | |
| 161 | + ))) | |
| 162 | +} | |
| 163 | + | |
| 164 | +fn write_word(layout: &mut Layout, word: LocatedWord, insn: u32) -> Result<(), LohError> { | |
| 165 | + let atom = &mut layout.sections[word.section_idx].atoms[word.atom_idx]; | |
| 166 | + let bytes = atom | |
| 167 | + .data | |
| 168 | + .get_mut(word.word_off..word.word_off + 4) | |
| 169 | + .ok_or_else(|| { | |
| 170 | + LohError(format!( | |
| 171 | + "instruction write OOB at section {} atom {} word {}", | |
| 172 | + word.section_idx, word.atom_idx, word.word_off | |
| 173 | + )) | |
| 174 | + })?; | |
| 175 | + bytes.copy_from_slice(&insn.to_le_bytes()); | |
| 176 | + Ok(()) | |
| 177 | +} | |
| 178 | + | |
| 179 | +fn decode_adrp_add_target(adrp: u32, add: u32, place: u64) -> Option<u64> { | |
| 180 | + if !is_adrp(adrp) || !is_add_imm_64(add) { | |
| 181 | + return None; | |
| 182 | + } | |
| 183 | + let rd = (adrp & 0x1f) as u8; | |
| 184 | + let add_rd = (add & 0x1f) as u8; | |
| 185 | + let add_rn = ((add >> 5) & 0x1f) as u8; | |
| 186 | + if rd == 31 || add_rd != rd || add_rn != rd { | |
| 187 | + return None; | |
| 188 | + } | |
| 189 | + let adrp_immlo = ((adrp >> 29) & 0x3) as i64; | |
| 190 | + let adrp_immhi = ((adrp >> 5) & 0x7ffff) as i64; | |
| 191 | + let adrp_pages = sign_extend_21((adrp_immhi << 2) | adrp_immlo); | |
| 192 | + let adrp_base = ((place as i64) & !0xfff) + (adrp_pages << 12); | |
| 193 | + let low = ((add >> 10) & 0xfff) as u64; | |
| 194 | + Some((adrp_base as u64) + low) | |
| 195 | +} | |
| 196 | + | |
| 197 | +fn encode_adr(target: u64, place: u64, reg: u8) -> Option<u32> { | |
| 198 | + if reg == 31 { | |
| 199 | + return None; | |
| 200 | + } | |
| 201 | + let delta = (target as i64).wrapping_sub(place as i64); | |
| 202 | + if !fits_signed(delta, 21) { | |
| 203 | + return None; | |
| 204 | + } | |
| 205 | + let encoded = (delta as u32) & 0x1f_ffff; | |
| 206 | + let immlo = encoded & 0x3; | |
| 207 | + let immhi = (encoded >> 2) & 0x7ffff; | |
| 208 | + Some(0x1000_0000 | (immlo << 29) | (immhi << 5) | reg as u32) | |
| 209 | +} | |
| 210 | + | |
| 211 | +fn is_adrp(insn: u32) -> bool { | |
| 212 | + (insn & 0x9f00_0000) == 0x9000_0000 | |
| 213 | +} | |
| 214 | + | |
| 215 | +fn is_add_imm_64(insn: u32) -> bool { | |
| 216 | + (insn & 0xffc0_0000) == 0x9100_0000 | |
| 217 | +} | |
| 218 | + | |
| 219 | +fn fits_signed(value: i64, bits: u32) -> bool { | |
| 220 | + let min = -(1i64 << (bits - 1)); | |
| 221 | + let max = (1i64 << (bits - 1)) - 1; | |
| 222 | + (min..=max).contains(&value) | |
| 223 | +} | |
| 224 | + | |
| 225 | +fn sign_extend_21(value: i64) -> i64 { | |
| 226 | + if value & (1 << 20) != 0 { | |
| 227 | + value | !0x1f_ffff | |
| 228 | + } else { | |
| 229 | + value | |
| 230 | + } | |
| 231 | +} | |
| 232 | + | |
| 67 | 233 | #[cfg(test)] |
| 68 | 234 | mod tests { |
| 69 | 235 | use super::*; |
@@ -101,4 +267,16 @@ mod tests { | ||
| 101 | 267 | }] |
| 102 | 268 | ); |
| 103 | 269 | } |
| 270 | + | |
| 271 | + #[test] | |
| 272 | + fn encode_adr_round_trips_small_delta() { | |
| 273 | + let place = 0x1_0000_1000; | |
| 274 | + let target = place + 0x48; | |
| 275 | + let adr = encode_adr(target, place, 9).unwrap(); | |
| 276 | + assert_eq!(adr & 0x1f, 9); | |
| 277 | + let immlo = ((adr >> 29) & 0x3) as i64; | |
| 278 | + let immhi = ((adr >> 5) & 0x7ffff) as i64; | |
| 279 | + let delta = sign_extend_21((immhi << 2) | immlo); | |
| 280 | + assert_eq!(place.wrapping_add_signed(delta), target); | |
| 281 | + } | |
| 104 | 282 | } |
src/macho/writer.rsmodified@@ -748,6 +748,10 @@ impl LinkEditPlan { | ||
| 748 | 748 | pub fn lazy_bind_offset(&self, symbol: SymbolId) -> Option<u32> { |
| 749 | 749 | self.lazy_bind_offsets.get(&symbol).copied() |
| 750 | 750 | } |
| 751 | + | |
| 752 | + pub fn loh_bytes(&self) -> &[u8] { | |
| 753 | + &self.loh_bytes | |
| 754 | + } | |
| 751 | 755 | } |
| 752 | 756 | |
| 753 | 757 | fn build_linkedit_plan( |
src/main.rsmodified@@ -36,6 +36,7 @@ Options: | ||
| 36 | 36 | -x Strip local symbols |
| 37 | 37 | -S Strip debug symbols (currently a no-op warning) |
| 38 | 38 | -no_uuid Omit LC_UUID |
| 39 | + -no_loh Skip LOH relaxation | |
| 39 | 40 | -dead_strip Dead-strip unreferenced code/data |
| 40 | 41 | -icf=safe | -icf=none | -icf=all |
| 41 | 42 | Configure identical code folding (`all` currently errors) |
tests/cli_diagnostics.rsmodified@@ -216,6 +216,44 @@ fn no_uuid_flag_omits_uuid_load_command() { | ||
| 216 | 216 | let _ = fs::remove_file(out_path); |
| 217 | 217 | } |
| 218 | 218 | |
| 219 | +#[test] | |
| 220 | +fn no_loh_flag_links_successfully() { | |
| 221 | + if !have_xcrun() { | |
| 222 | + eprintln!("skipping: xcrun as unavailable"); | |
| 223 | + return; | |
| 224 | + } | |
| 225 | + | |
| 226 | + let exe = env!("CARGO_BIN_EXE_afs-ld"); | |
| 227 | + let obj = match assemble_minimal_main("no-loh-main.o") { | |
| 228 | + Ok(obj) => obj, | |
| 229 | + Err(e) => { | |
| 230 | + eprintln!("skipping: assemble failed: {e}"); | |
| 231 | + return; | |
| 232 | + } | |
| 233 | + }; | |
| 234 | + let out_path = scratch("no-loh.out"); | |
| 235 | + let out = Command::new(exe) | |
| 236 | + .arg("-no_loh") | |
| 237 | + .arg("-o") | |
| 238 | + .arg(&out_path) | |
| 239 | + .arg(&obj) | |
| 240 | + .output() | |
| 241 | + .expect("afs-ld should run"); | |
| 242 | + assert!( | |
| 243 | + out.status.success(), | |
| 244 | + "-no_loh link should succeed:\nstderr:\n{}", | |
| 245 | + String::from_utf8_lossy(&out.stderr) | |
| 246 | + ); | |
| 247 | + assert!( | |
| 248 | + out_path.is_file(), | |
| 249 | + "expected -no_loh link to produce {}", | |
| 250 | + out_path.display() | |
| 251 | + ); | |
| 252 | + | |
| 253 | + let _ = fs::remove_file(obj); | |
| 254 | + let _ = fs::remove_file(out_path); | |
| 255 | +} | |
| 256 | + | |
| 219 | 257 | #[test] |
| 220 | 258 | fn strip_debug_flag_warns_but_links_successfully() { |
| 221 | 259 | if !have_xcrun() { |
tests/linker_run.rsmodified@@ -1807,6 +1807,25 @@ fn read_insn(bytes: &[u8], start: usize) -> Result<u32, String> { | ||
| 1807 | 1807 | Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]])) |
| 1808 | 1808 | } |
| 1809 | 1809 | |
| 1810 | +fn is_adr(insn: u32) -> bool { | |
| 1811 | + (insn & 0x9f00_0000) == 0x1000_0000 | |
| 1812 | +} | |
| 1813 | + | |
| 1814 | +fn is_adrp(insn: u32) -> bool { | |
| 1815 | + (insn & 0x9f00_0000) == 0x9000_0000 | |
| 1816 | +} | |
| 1817 | + | |
| 1818 | +fn is_add_imm_64(insn: u32) -> bool { | |
| 1819 | + (insn & 0xffc0_0000) == 0x9100_0000 | |
| 1820 | +} | |
| 1821 | + | |
| 1822 | +fn decode_adr_target(insn: u32, place: u64) -> u64 { | |
| 1823 | + let immlo = ((insn >> 29) & 0x3) as i64; | |
| 1824 | + let immhi = ((insn >> 5) & 0x7ffff) as i64; | |
| 1825 | + let delta = sign_extend_21((immhi << 2) | immlo); | |
| 1826 | + place.wrapping_add_signed(delta) | |
| 1827 | +} | |
| 1828 | + | |
| 1810 | 1829 | fn sign_extend_26(value: i64) -> i64 { |
| 1811 | 1830 | if value & (1 << 25) != 0 { |
| 1812 | 1831 | value | !0x03ff_ffff |
@@ -1985,6 +2004,176 @@ fn linker_run_preserves_loh_payloads_from_input_objects() { | ||
| 1985 | 2004 | } |
| 1986 | 2005 | } |
| 1987 | 2006 | |
| 2007 | +#[test] | |
| 2008 | +fn linker_run_relaxes_adrp_add_loh_when_target_is_near() { | |
| 2009 | + if !have_xcrun() { | |
| 2010 | + eprintln!("skipping: xcrun unavailable"); | |
| 2011 | + return; | |
| 2012 | + } | |
| 2013 | + | |
| 2014 | + let obj = scratch("loh-relax-near.o"); | |
| 2015 | + let out = scratch("loh-relax-near.out"); | |
| 2016 | + let src = r#" | |
| 2017 | + .section __TEXT,__text,regular,pure_instructions | |
| 2018 | + .globl _main | |
| 2019 | + .globl _target | |
| 2020 | + _main: | |
| 2021 | + Lloh0: | |
| 2022 | + adrp x0, _target@PAGE | |
| 2023 | + Lloh1: | |
| 2024 | + add x0, x0, _target@PAGEOFF | |
| 2025 | + mov w0, #0 | |
| 2026 | + ret | |
| 2027 | + _target: | |
| 2028 | + ret | |
| 2029 | + .loh AdrpAdd Lloh0, Lloh1 | |
| 2030 | + .subsections_via_symbols | |
| 2031 | + "#; | |
| 2032 | + if let Err(e) = assemble(src, &obj) { | |
| 2033 | + eprintln!("skipping: assemble failed: {e}"); | |
| 2034 | + return; | |
| 2035 | + } | |
| 2036 | + | |
| 2037 | + let opts = LinkOptions { | |
| 2038 | + inputs: vec![obj.clone()], | |
| 2039 | + output: Some(out.clone()), | |
| 2040 | + kind: OutputKind::Executable, | |
| 2041 | + ..LinkOptions::default() | |
| 2042 | + }; | |
| 2043 | + Linker::run(&opts).unwrap(); | |
| 2044 | + | |
| 2045 | + let bytes = fs::read(&out).unwrap(); | |
| 2046 | + let (text_addr, text) = output_section(&bytes, "__TEXT", "__text").unwrap(); | |
| 2047 | + let target_addr = symbol_values(&bytes)["_target"]; | |
| 2048 | + let first = read_insn(&text, 0).unwrap(); | |
| 2049 | + let second = read_insn(&text, 4).unwrap(); | |
| 2050 | + assert!( | |
| 2051 | + is_adr(first), | |
| 2052 | + "expected ADR after LOH relaxation, got {first:#010x}" | |
| 2053 | + ); | |
| 2054 | + assert_eq!(second, 0xd503_201f, "expected NOP after LOH relaxation"); | |
| 2055 | + assert_eq!(decode_adr_target(first, text_addr), target_addr); | |
| 2056 | + | |
| 2057 | + let _ = fs::remove_file(obj); | |
| 2058 | + let _ = fs::remove_file(out); | |
| 2059 | +} | |
| 2060 | + | |
| 2061 | +#[test] | |
| 2062 | +fn linker_run_keeps_adrp_add_loh_when_target_is_far() { | |
| 2063 | + if !have_xcrun() { | |
| 2064 | + eprintln!("skipping: xcrun unavailable"); | |
| 2065 | + return; | |
| 2066 | + } | |
| 2067 | + | |
| 2068 | + let obj = scratch("loh-relax-far.o"); | |
| 2069 | + let out = scratch("loh-relax-far.out"); | |
| 2070 | + let src = r#" | |
| 2071 | + .section __TEXT,__text,regular,pure_instructions | |
| 2072 | + .globl _main | |
| 2073 | + .globl _target | |
| 2074 | + _main: | |
| 2075 | + Lloh0: | |
| 2076 | + adrp x0, _target@PAGE | |
| 2077 | + Lloh1: | |
| 2078 | + add x0, x0, _target@PAGEOFF | |
| 2079 | + mov w0, #0 | |
| 2080 | + ret | |
| 2081 | + .space 0x200000 | |
| 2082 | + _target: | |
| 2083 | + ret | |
| 2084 | + .loh AdrpAdd Lloh0, Lloh1 | |
| 2085 | + .subsections_via_symbols | |
| 2086 | + "#; | |
| 2087 | + if let Err(e) = assemble(src, &obj) { | |
| 2088 | + eprintln!("skipping: assemble failed: {e}"); | |
| 2089 | + return; | |
| 2090 | + } | |
| 2091 | + | |
| 2092 | + let opts = LinkOptions { | |
| 2093 | + inputs: vec![obj.clone()], | |
| 2094 | + output: Some(out.clone()), | |
| 2095 | + kind: OutputKind::Executable, | |
| 2096 | + ..LinkOptions::default() | |
| 2097 | + }; | |
| 2098 | + Linker::run(&opts).unwrap(); | |
| 2099 | + | |
| 2100 | + let bytes = fs::read(&out).unwrap(); | |
| 2101 | + let (text_addr, text) = output_section(&bytes, "__TEXT", "__text").unwrap(); | |
| 2102 | + let target_addr = symbol_values(&bytes)["_target"]; | |
| 2103 | + let first = read_insn(&text, 0).unwrap(); | |
| 2104 | + let second = read_insn(&text, 4).unwrap(); | |
| 2105 | + assert!( | |
| 2106 | + is_adrp(first), | |
| 2107 | + "far LOH pair should stay ADRP, got {first:#010x}" | |
| 2108 | + ); | |
| 2109 | + assert!( | |
| 2110 | + is_add_imm_64(second), | |
| 2111 | + "far LOH pair should keep ADD immediate, got {second:#010x}" | |
| 2112 | + ); | |
| 2113 | + assert_eq!( | |
| 2114 | + decode_page_reference(&text, text_addr, 0, &PageRefKind::Add).unwrap(), | |
| 2115 | + target_addr | |
| 2116 | + ); | |
| 2117 | + | |
| 2118 | + let _ = fs::remove_file(obj); | |
| 2119 | + let _ = fs::remove_file(out); | |
| 2120 | +} | |
| 2121 | + | |
| 2122 | +#[test] | |
| 2123 | +fn linker_run_no_loh_keeps_adrp_add_unrelaxed() { | |
| 2124 | + if !have_xcrun() { | |
| 2125 | + eprintln!("skipping: xcrun unavailable"); | |
| 2126 | + return; | |
| 2127 | + } | |
| 2128 | + | |
| 2129 | + let obj = scratch("loh-no-relax.o"); | |
| 2130 | + let out = scratch("loh-no-relax.out"); | |
| 2131 | + let src = r#" | |
| 2132 | + .section __TEXT,__text,regular,pure_instructions | |
| 2133 | + .globl _main | |
| 2134 | + .globl _target | |
| 2135 | + _main: | |
| 2136 | + Lloh0: | |
| 2137 | + adrp x0, _target@PAGE | |
| 2138 | + Lloh1: | |
| 2139 | + add x0, x0, _target@PAGEOFF | |
| 2140 | + mov w0, #0 | |
| 2141 | + ret | |
| 2142 | + _target: | |
| 2143 | + ret | |
| 2144 | + .loh AdrpAdd Lloh0, Lloh1 | |
| 2145 | + .subsections_via_symbols | |
| 2146 | + "#; | |
| 2147 | + if let Err(e) = assemble(src, &obj) { | |
| 2148 | + eprintln!("skipping: assemble failed: {e}"); | |
| 2149 | + return; | |
| 2150 | + } | |
| 2151 | + | |
| 2152 | + let opts = LinkOptions { | |
| 2153 | + inputs: vec![obj.clone()], | |
| 2154 | + output: Some(out.clone()), | |
| 2155 | + kind: OutputKind::Executable, | |
| 2156 | + no_loh: true, | |
| 2157 | + ..LinkOptions::default() | |
| 2158 | + }; | |
| 2159 | + Linker::run(&opts).unwrap(); | |
| 2160 | + | |
| 2161 | + let bytes = fs::read(&out).unwrap(); | |
| 2162 | + let (text_addr, text) = output_section(&bytes, "__TEXT", "__text").unwrap(); | |
| 2163 | + let target_addr = symbol_values(&bytes)["_target"]; | |
| 2164 | + let first = read_insn(&text, 0).unwrap(); | |
| 2165 | + let second = read_insn(&text, 4).unwrap(); | |
| 2166 | + assert!(is_adrp(first), "expected ADRP when -no_loh is set"); | |
| 2167 | + assert!(is_add_imm_64(second), "expected ADD when -no_loh is set"); | |
| 2168 | + assert_eq!( | |
| 2169 | + decode_page_reference(&text, text_addr, 0, &PageRefKind::Add).unwrap(), | |
| 2170 | + target_addr | |
| 2171 | + ); | |
| 2172 | + | |
| 2173 | + let _ = fs::remove_file(obj); | |
| 2174 | + let _ = fs::remove_file(out); | |
| 2175 | +} | |
| 2176 | + | |
| 1988 | 2177 | #[test] |
| 1989 | 2178 | fn linker_run_emits_minimal_dylib_from_real_object() { |
| 1990 | 2179 | if !have_xcrun() { |
tests/snapshots/help.txtmodified@@ -30,6 +30,7 @@ Options: | ||
| 30 | 30 | -x Strip local symbols |
| 31 | 31 | -S Strip debug symbols (currently a no-op warning) |
| 32 | 32 | -no_uuid Omit LC_UUID |
| 33 | + -no_loh Skip LOH relaxation | |
| 33 | 34 | -dead_strip Dead-strip unreferenced code/data |
| 34 | 35 | -icf=safe | -icf=none | -icf=all |
| 35 | 36 | Configure identical code folding (`all` currently errors) |