fortrangoingonforty/afs-ld / 1d9f402

Browse files

Speed synthetic planning

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
1d9f402d4ed10ae203a7d88bf28cdfeb14fe94dc
Parents
b81b24f
Tree
9ff8a82

7 changed files

StatusFile+-
M src/lib.rs 33 8
M src/macho/writer.rs 3 3
M src/reloc/arm64.rs 56 76
M src/reloc/mod.rs 5 0
M src/resolve.rs 8 0
M src/synth/mod.rs 125 36
M tests/perf_baseline.rs 17 1
src/lib.rsmodified
@@ -212,6 +212,12 @@ pub struct LinkPhaseTimings {
212212
     pub symbol_resolution: Duration,
213213
     pub atomization: Duration,
214214
     pub layout: Duration,
215
+    pub layout_entry_lookup: Duration,
216
+    pub layout_dead_strip: Duration,
217
+    pub layout_icf: Duration,
218
+    pub layout_synthetic_plan: Duration,
219
+    pub layout_build: Duration,
220
+    pub layout_thunk_plan: Duration,
215221
     pub synth_sections: Duration,
216222
     pub synth_linkedit_finalize: Duration,
217223
     pub synth_linkedit_symbol_plan: Duration,
@@ -583,8 +589,11 @@ impl Linker {
583589
         let phase_started = Instant::now();
584590
         let parsed_relocs = macho::writer::build_parsed_reloc_cache(&layout_inputs)?;
585591
         phases.input_parsing += phase_started.elapsed();
592
+        let layout_started = Instant::now();
586593
         let phase_started = Instant::now();
587594
         let entry_symbol = find_entry_symbol_id(opts, &sym_table)?;
595
+        phases.layout_entry_lookup = phase_started.elapsed();
596
+        let phase_started = Instant::now();
588597
         let dead_strip = opts.dead_strip.then(|| {
589598
             why_live::DeadStripAnalysis::build(
590599
                 opts,
@@ -594,6 +603,8 @@ impl Linker {
594603
                 entry_symbol,
595604
             )
596605
         });
606
+        phases.layout_dead_strip = phase_started.elapsed();
607
+        let phase_started = Instant::now();
597608
         let icf = (opts.icf_mode == IcfMode::Safe)
598609
             .then(|| {
599610
                 icf::fold_safe(
@@ -604,19 +615,24 @@ impl Linker {
604615
                 )
605616
             })
606617
             .transpose()?;
618
+        phases.layout_icf = phase_started.elapsed();
607619
         let kept_atoms = if let Some(icf) = &icf {
608620
             Some(icf.kept_atoms())
609621
         } else {
610622
             dead_strip.as_ref().map(|analysis| analysis.live_atoms())
611623
         };
612
-        let synthetic_plan = synth::SyntheticPlan::build_filtered(
624
+        let phase_started = Instant::now();
625
+        let synthetic_plan = synth::SyntheticPlan::build_filtered_with_relocs(
613626
             &layout_inputs,
614627
             &atom_table,
615628
             &mut sym_table,
616629
             &inputs.dylibs,
617630
             kept_atoms,
631
+            &parsed_relocs,
618632
         )?;
633
+        phases.layout_synthetic_plan = phase_started.elapsed();
619634
         let icf_redirects = icf.as_ref().map(|plan| plan.redirects());
635
+        let phase_started = Instant::now();
620636
         let mut layout = Layout::build_with_synthetics_filtered(
621637
             opts.kind,
622638
             &layout_inputs,
@@ -625,18 +641,24 @@ impl Linker {
625641
             Some(&synthetic_plan),
626642
             kept_atoms,
627643
         );
644
+        phases.layout_build += phase_started.elapsed();
628645
         let mut thunk_plan = None;
629646
         let mut thunk_converged = false;
630647
         for _ in 0..THUNK_PLAN_MAX_ITERATIONS {
648
+            let phase_started = Instant::now();
631649
             let next_plan = reloc::arm64::plan_thunks(
632650
                 opts,
633
-                &layout,
634
-                &layout_inputs,
635
-                &atom_table,
636
-                &sym_table,
637
-                Some(&synthetic_plan),
638
-                icf_redirects,
651
+                reloc::arm64::ThunkPlanningContext {
652
+                    layout: &layout,
653
+                    inputs: &layout_inputs,
654
+                    atoms: &atom_table,
655
+                    sym_table: &sym_table,
656
+                    synthetic_plan: Some(&synthetic_plan),
657
+                    icf_redirects,
658
+                    parsed_relocs: &parsed_relocs,
659
+                },
639660
             )?;
661
+            phases.layout_thunk_plan += phase_started.elapsed();
640662
             if next_plan == thunk_plan {
641663
                 thunk_converged = true;
642664
                 break;
@@ -647,6 +669,7 @@ impl Linker {
647669
             let split_after_atoms = next_plan
648670
                 .as_ref()
649671
                 .map_or_else(Vec::new, |plan| plan.split_after_atoms());
672
+            let phase_started = Instant::now();
650673
             layout = Layout::build_with_synthetics_and_extra_filtered(
651674
                 opts.kind,
652675
                 &layout_inputs,
@@ -659,12 +682,13 @@ impl Linker {
659682
                     split_after_atoms: &split_after_atoms,
660683
                 },
661684
             );
685
+            phases.layout_build += phase_started.elapsed();
662686
             thunk_plan = next_plan;
663687
         }
664688
         if !thunk_converged {
665689
             return Err(LinkError::ThunkPlanningDidNotConverge);
666690
         }
667
-        phases.layout = phase_started.elapsed();
691
+        phases.layout = layout_started.elapsed();
668692
         let linkedit_context = macho::writer::LinkEditContext {
669693
             layout_inputs: &layout_inputs,
670694
             atom_table: &atom_table,
@@ -739,6 +763,7 @@ impl Linker {
739763
                 thunk_plan: thunk_plan.as_ref(),
740764
                 linkedit: &linkedit,
741765
                 icf_redirects,
766
+                parsed_relocs: &parsed_relocs,
742767
             },
743768
         )?;
744769
         phases.reloc_apply = phase_started.elapsed();
src/macho/writer.rsmodified
@@ -20,7 +20,9 @@ use crate::macho::reader::{
2020
     LinkEditDataCmd, LoadCommand, MachHeader64, RpathCmd, Section64Header, Segment64, SymtabCmd,
2121
     HEADER_SIZE,
2222
 };
23
-use crate::reloc::{parse_raw_relocs, parse_relocs, Referent, Reloc, RelocKind, RelocLength};
23
+use crate::reloc::{
24
+    parse_raw_relocs, parse_relocs, ParsedRelocCache, Referent, Reloc, RelocKind, RelocLength,
25
+};
2426
 use crate::resolve::InputId;
2527
 use crate::resolve::{Symbol, SymbolId, SymbolTable};
2628
 use crate::section::is_executable;
@@ -53,8 +55,6 @@ pub struct LinkEditContext<'a> {
5355
     pub parsed_relocs: &'a ParsedRelocCache,
5456
 }
5557
 
56
-pub type ParsedRelocCache = HashMap<(InputId, u8), Vec<Reloc>>;
57
-
5858
 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
5959
 pub struct LinkEditBuildTimings {
6060
     pub symbol_plan: Duration,
src/reloc/arm64.rsmodified
@@ -6,7 +6,7 @@ use crate::atom::{Atom, AtomSection, AtomTable};
66
 use crate::input::ObjectFile;
77
 use crate::layout::{ExtraOutputSection, ExtraSectionAnchor, Layout, LayoutInput};
88
 use crate::macho::writer::LinkEditPlan;
9
-use crate::reloc::{parse_raw_relocs, parse_relocs, Referent, Reloc, RelocKind, RelocLength};
9
+use crate::reloc::{ParsedRelocCache, Referent, Reloc, RelocKind, RelocLength};
1010
 use crate::resolve::{InputId, Symbol, SymbolId, SymbolTable};
1111
 use crate::section::{OutputSection, SectionKind};
1212
 use crate::symbol::{InputSymbol, SymKind};
@@ -72,6 +72,7 @@ pub struct ApplyLayoutPlan<'a> {
7272
     pub thunk_plan: Option<&'a ThunkPlan>,
7373
     pub linkedit: &'a LinkEditPlan,
7474
     pub icf_redirects: Option<&'a HashMap<crate::resolve::AtomId, crate::resolve::AtomId>>,
75
+    pub parsed_relocs: &'a ParsedRelocCache,
7576
 }
7677
 
7778
 struct InputSectionResolveCtx<'a> {
@@ -206,36 +207,6 @@ pub fn apply_layout(
206207
         .iter()
207208
         .map(|input| (input.id, input.object))
208209
         .collect();
209
-    let mut reloc_cache: HashMap<(InputId, u8), Vec<Reloc>> = HashMap::new();
210
-    for input in inputs {
211
-        for (sect_idx, section) in input.object.sections.iter().enumerate() {
212
-            let relocs = if section.nreloc == 0 {
213
-                Vec::new()
214
-            } else {
215
-                let raws =
216
-                    parse_raw_relocs(&section.raw_relocs, 0, section.nreloc).map_err(|err| {
217
-                        RelocError {
218
-                            input: input.object.path.clone(),
219
-                            atom: crate::resolve::AtomId(0),
220
-                            atom_offset: 0,
221
-                            kind: RelocKind::Unsigned,
222
-                            referent: format!("section {},{}", section.segname, section.sectname),
223
-                            detail: err.to_string(),
224
-                        }
225
-                    })?;
226
-                parse_relocs(&raws).map_err(|err| RelocError {
227
-                    input: input.object.path.clone(),
228
-                    atom: crate::resolve::AtomId(0),
229
-                    atom_offset: 0,
230
-                    kind: RelocKind::Unsigned,
231
-                    referent: format!("section {},{}", section.segname, section.sectname),
232
-                    detail: err.to_string(),
233
-                })?
234
-            };
235
-            reloc_cache.insert((input.id, (sect_idx + 1) as u8), relocs);
236
-        }
237
-    }
238
-
239210
     let atom_addrs = atom_address_map(layout);
240211
     let atoms_by_input_section = atoms.by_input_section();
241212
     let section_addrs = input_section_address_map(layout, atoms);
@@ -277,7 +248,8 @@ pub fn apply_layout(
277248
                 )
278249
             })?;
279250
             patch_eh_frame_cie_pointer(&mut placed.data, atom, &resolve)?;
280
-            let relocs = reloc_cache
251
+            let relocs = plan
252
+                .parsed_relocs
281253
                 .get(&(atom.origin, atom.input_section))
282254
                 .map(Vec::as_slice)
283255
                 .unwrap_or(&[]);
@@ -305,7 +277,7 @@ pub fn apply_layout(
305277
             synthetic_plan,
306278
             atoms,
307279
             &input_map,
308
-            &reloc_cache,
280
+            plan.parsed_relocs,
309281
             &resolve,
310282
         )?;
311283
         synthesize_got_section(layout, synthetic_plan, &resolve)?;
@@ -510,53 +482,38 @@ fn synthetic_address_maps(
510482
     }
511483
 }
512484
 
485
+pub struct ThunkPlanningContext<'a> {
486
+    pub layout: &'a Layout,
487
+    pub inputs: &'a [LayoutInput<'a>],
488
+    pub atoms: &'a AtomTable,
489
+    pub sym_table: &'a SymbolTable,
490
+    pub synthetic_plan: Option<&'a SyntheticPlan>,
491
+    pub icf_redirects: Option<&'a HashMap<crate::resolve::AtomId, crate::resolve::AtomId>>,
492
+    pub parsed_relocs: &'a ParsedRelocCache,
493
+}
494
+
513495
 pub fn plan_thunks(
514496
     opts: &LinkOptions,
515
-    layout: &Layout,
516
-    inputs: &[LayoutInput<'_>],
517
-    atoms: &AtomTable,
518
-    sym_table: &SymbolTable,
519
-    synthetic_plan: Option<&SyntheticPlan>,
520
-    icf_redirects: Option<&HashMap<crate::resolve::AtomId, crate::resolve::AtomId>>,
497
+    ctx: ThunkPlanningContext<'_>,
521498
 ) -> Result<Option<ThunkPlan>, RelocError> {
522499
     if opts.thunks == ThunkMode::None {
523500
         return Ok(None);
524501
     }
525502
 
503
+    let ThunkPlanningContext {
504
+        layout,
505
+        inputs,
506
+        atoms,
507
+        sym_table,
508
+        synthetic_plan,
509
+        icf_redirects,
510
+        parsed_relocs,
511
+    } = ctx;
512
+
526513
     let input_map: HashMap<InputId, &ObjectFile> = inputs
527514
         .iter()
528515
         .map(|input| (input.id, input.object))
529516
         .collect();
530
-    let mut reloc_cache: HashMap<(InputId, u8), Vec<Reloc>> = HashMap::new();
531
-    for input in inputs {
532
-        for (sect_idx, section) in input.object.sections.iter().enumerate() {
533
-            let relocs = if section.nreloc == 0 {
534
-                Vec::new()
535
-            } else {
536
-                let raws =
537
-                    parse_raw_relocs(&section.raw_relocs, 0, section.nreloc).map_err(|err| {
538
-                        RelocError {
539
-                            input: input.object.path.clone(),
540
-                            atom: crate::resolve::AtomId(0),
541
-                            atom_offset: 0,
542
-                            kind: RelocKind::Unsigned,
543
-                            referent: format!("section {},{}", section.segname, section.sectname),
544
-                            detail: err.to_string(),
545
-                        }
546
-                    })?;
547
-                parse_relocs(&raws).map_err(|err| RelocError {
548
-                    input: input.object.path.clone(),
549
-                    atom: crate::resolve::AtomId(0),
550
-                    atom_offset: 0,
551
-                    kind: RelocKind::Unsigned,
552
-                    referent: format!("section {},{}", section.segname, section.sectname),
553
-                    detail: err.to_string(),
554
-                })?
555
-            };
556
-            reloc_cache.insert((input.id, (sect_idx + 1) as u8), relocs);
557
-        }
558
-    }
559
-
560517
     let atom_addrs = atom_address_map(layout);
561518
     let atom_segments = atom_output_segment_map(layout);
562519
     let atoms_by_input_section = atoms.by_input_section();
@@ -588,7 +545,7 @@ pub fn plan_thunks(
588545
         let Some(obj) = input_map.get(&atom.origin) else {
589546
             continue;
590547
         };
591
-        let relocs = reloc_cache
548
+        let relocs = parsed_relocs
592549
             .get(&(atom.origin, atom.input_section))
593550
             .map(Vec::as_slice)
594551
             .unwrap_or(&[]);
@@ -2739,9 +2696,21 @@ mod tests {
27392696
             ..LinkOptions::default()
27402697
         };
27412698
         let base_layout = Layout::build(OutputKind::Executable, &inputs, &atoms, 0);
2742
-        let plan = plan_thunks(&opts, &base_layout, &inputs, &atoms, &sym_table, None, None)
2743
-            .unwrap()
2744
-            .unwrap();
2699
+        let parsed_relocs = crate::macho::writer::build_parsed_reloc_cache(&inputs).unwrap();
2700
+        let plan = plan_thunks(
2701
+            &opts,
2702
+            ThunkPlanningContext {
2703
+                layout: &base_layout,
2704
+                inputs: &inputs,
2705
+                atoms: &atoms,
2706
+                sym_table: &sym_table,
2707
+                synthetic_plan: None,
2708
+                icf_redirects: None,
2709
+                parsed_relocs: &parsed_relocs,
2710
+            },
2711
+        )
2712
+        .unwrap()
2713
+        .unwrap();
27452714
 
27462715
         assert_eq!(
27472716
             plan.redirect_for(caller1, 0),
@@ -2795,9 +2764,20 @@ mod tests {
27952764
             vec!["__text", "__thunks", "__text", "__thunks", "__text"]
27962765
         );
27972766
 
2798
-        let replan = plan_thunks(&opts, &rebuilt, &inputs, &atoms, &sym_table, None, None)
2799
-            .unwrap()
2800
-            .unwrap();
2767
+        let replan = plan_thunks(
2768
+            &opts,
2769
+            ThunkPlanningContext {
2770
+                layout: &rebuilt,
2771
+                inputs: &inputs,
2772
+                atoms: &atoms,
2773
+                sym_table: &sym_table,
2774
+                synthetic_plan: None,
2775
+                icf_redirects: None,
2776
+                parsed_relocs: &parsed_relocs,
2777
+            },
2778
+        )
2779
+        .unwrap()
2780
+        .unwrap();
28012781
         assert_eq!(
28022782
             replan, plan,
28032783
             "expected thunk planning to converge once the intra-section islands exist"
src/reloc/mod.rsmodified
@@ -17,8 +17,11 @@
1717
 
1818
 pub mod arm64;
1919
 
20
+use std::collections::HashMap;
21
+
2022
 use crate::macho::constants::*;
2123
 use crate::macho::reader::{u32_le, ReadError};
24
+use crate::resolve::InputId;
2225
 
2326
 /// Size of one `relocation_info` on the wire.
2427
 pub const RAW_RELOC_SIZE: usize = 8;
@@ -185,6 +188,8 @@ pub struct Reloc {
185188
     pub subtrahend: Option<Referent>,
186189
 }
187190
 
191
+pub type ParsedRelocCache = HashMap<(InputId, u8), Vec<Reloc>>;
192
+
188193
 fn referent_from(raw: &RawRelocation) -> Result<Referent, ReadError> {
189194
     if raw.r_extern {
190195
         Ok(Referent::Symbol(raw.r_symbolnum))
src/resolve.rsmodified
@@ -63,6 +63,10 @@ impl StringInterner {
6363
         Istr(id)
6464
     }
6565
 
66
+    pub fn get(&self, s: &str) -> Option<Istr> {
67
+        self.index.get(s).copied().map(Istr)
68
+    }
69
+
6670
     pub fn resolve(&self, i: Istr) -> &str {
6771
         &self.strings[i.0 as usize]
6872
     }
@@ -554,6 +558,10 @@ impl SymbolTable {
554558
         self.by_name.get(&name).copied()
555559
     }
556560
 
561
+    pub fn lookup_str(&self, name: &str) -> Option<SymbolId> {
562
+        self.interner.get(name).and_then(|name| self.lookup(name))
563
+    }
564
+
557565
     pub fn get(&self, id: SymbolId) -> &Symbol {
558566
         &self.symbols[id.0 as usize]
559567
     }
src/synth/mod.rsmodified
@@ -16,7 +16,9 @@ use crate::macho::constants::{
1616
     S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS, S_LAZY_SYMBOL_POINTERS,
1717
     S_NON_LAZY_SYMBOL_POINTERS, S_REGULAR, S_SYMBOL_STUBS, S_THREAD_LOCAL_VARIABLE_POINTERS,
1818
 };
19
-use crate::reloc::{parse_raw_relocs, parse_relocs, Referent, Reloc, RelocKind, RelocLength};
19
+use crate::reloc::{
20
+    parse_raw_relocs, parse_relocs, ParsedRelocCache, Referent, Reloc, RelocKind, RelocLength,
21
+};
2022
 use crate::resolve::{
2123
     AtomId, DylibId, DylibInput, InputId, InsertOutcome, Symbol, SymbolId, SymbolTable,
2224
 };
@@ -92,37 +94,25 @@ impl SyntheticPlan {
9294
         sym_table: &mut SymbolTable,
9395
         dylibs: &[DylibInput],
9496
         live_atoms: Option<&HashSet<AtomId>>,
97
+    ) -> Result<Self, SynthError> {
98
+        let reloc_cache = build_synthetic_reloc_cache(inputs)?;
99
+        Self::build_filtered_with_relocs(inputs, atoms, sym_table, dylibs, live_atoms, &reloc_cache)
100
+    }
101
+
102
+    pub fn build_filtered_with_relocs(
103
+        inputs: &[LayoutInput<'_>],
104
+        atoms: &AtomTable,
105
+        sym_table: &mut SymbolTable,
106
+        dylibs: &[DylibInput],
107
+        live_atoms: Option<&HashSet<AtomId>>,
108
+        parsed_relocs: &ParsedRelocCache,
95109
     ) -> Result<Self, SynthError> {
96110
         let input_map: HashMap<InputId, &ObjectFile> = inputs
97111
             .iter()
98112
             .map(|input| (input.id, input.object))
99113
             .collect();
100
-        let mut reloc_cache: HashMap<(InputId, u8), Vec<Reloc>> = HashMap::new();
101
-        for input in inputs {
102
-            for (sect_idx, section) in input.object.sections.iter().enumerate() {
103
-                let relocs = if section.nreloc == 0 {
104
-                    Vec::new()
105
-                } else {
106
-                    let raws = parse_raw_relocs(&section.raw_relocs, 0, section.nreloc).map_err(
107
-                        |err| SynthError {
108
-                            input: input.object.path.clone(),
109
-                            atom: crate::resolve::AtomId(0),
110
-                            reloc_offset: 0,
111
-                            kind: RelocKind::Unsigned,
112
-                            detail: err.to_string(),
113
-                        },
114
-                    )?;
115
-                    parse_relocs(&raws).map_err(|err| SynthError {
116
-                        input: input.object.path.clone(),
117
-                        atom: crate::resolve::AtomId(0),
118
-                        reloc_offset: 0,
119
-                        kind: RelocKind::Unsigned,
120
-                        detail: err.to_string(),
121
-                    })?
122
-                };
123
-                reloc_cache.insert((input.id, (sect_idx + 1) as u8), relocs);
124
-            }
125
-        }
114
+        let input_symbol_index = build_input_symbol_index(inputs, sym_table, parsed_relocs);
115
+        let reloc_index = build_sorted_reloc_index(parsed_relocs);
126116
 
127117
         let mut got = GotSection::default();
128118
         let mut stubs = StubsSection::default();
@@ -141,16 +131,19 @@ impl SyntheticPlan {
141131
                 kind: RelocKind::Unsigned,
142132
                 detail: "missing parsed object".to_string(),
143133
             })?;
144
-            let relocs = reloc_cache
134
+            let relocs = reloc_index
145135
                 .get(&(atom.origin, atom.input_section))
146136
                 .map(Vec::as_slice)
147137
                 .unwrap_or(&[]);
138
+            let input_symbols = input_symbol_index.get(&atom.origin).map(Vec::as_slice);
148139
             for reloc in relocs_for_atom(relocs, atom) {
149140
                 if atom.section == AtomSection::CompactUnwind
150141
                     && reloc.kind == RelocKind::Unsigned
151142
                     && reloc.offset == atom.input_offset + COMPACT_UNWIND_PERSONALITY_FIELD_OFFSET
152143
                 {
153
-                    if let Some(symbol_id) = dylib_import_referent(obj, reloc.referent, sym_table) {
144
+                    if let Some(symbol_id) =
145
+                        dylib_import_referent(obj, reloc.referent, sym_table, input_symbols)
146
+                    {
154147
                         got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
155148
                     }
156149
                     continue;
@@ -163,7 +156,8 @@ impl SyntheticPlan {
163156
                         if matches!(atom.section, AtomSection::ThreadLocalVariables) {
164157
                             continue;
165158
                         }
166
-                        let Some(symbol_id) = dylib_import_referent(obj, reloc.referent, sym_table)
159
+                        let Some(symbol_id) =
160
+                            dylib_import_referent(obj, reloc.referent, sym_table, input_symbols)
167161
                         else {
168162
                             continue;
169163
                         };
@@ -179,7 +173,8 @@ impl SyntheticPlan {
179173
                     RelocKind::GotLoadPage21
180174
                     | RelocKind::GotLoadPageOff12
181175
                     | RelocKind::PointerToGot => {
182
-                        let Some(symbol_id) = symbol_referent_id(obj, reloc.referent, sym_table)
176
+                        let Some(symbol_id) =
177
+                            symbol_referent_id(obj, reloc.referent, sym_table, input_symbols)
183178
                         else {
184179
                             continue;
185180
                         };
@@ -198,7 +193,8 @@ impl SyntheticPlan {
198193
                         }
199194
                     }
200195
                     RelocKind::Branch26 => {
201
-                        let Some(symbol_id) = dylib_import_referent(obj, reloc.referent, sym_table)
196
+                        let Some(symbol_id) =
197
+                            dylib_import_referent(obj, reloc.referent, sym_table, input_symbols)
202198
                         else {
203199
                             continue;
204200
                         };
@@ -214,7 +210,8 @@ impl SyntheticPlan {
214210
                         );
215211
                     }
216212
                     RelocKind::TlvpLoadPage21 | RelocKind::TlvpLoadPageOff12 => {
217
-                        if let Some(symbol_id) = symbol_referent_id(obj, reloc.referent, sym_table)
213
+                        if let Some(symbol_id) =
214
+                            symbol_referent_id(obj, reloc.referent, sym_table, input_symbols)
218215
                         {
219216
                             if tlv_symbol_needs_got(sym_table, symbol_id) {
220217
                                 got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
@@ -424,12 +421,99 @@ fn sort_symbol_indexed_entries<T, F>(
424421
     }
425422
 }
426423
 
424
+fn build_synthetic_reloc_cache(inputs: &[LayoutInput<'_>]) -> Result<ParsedRelocCache, SynthError> {
425
+    let mut reloc_cache = ParsedRelocCache::new();
426
+    for input in inputs {
427
+        for (sect_idx, section) in input.object.sections.iter().enumerate() {
428
+            let relocs = if section.nreloc == 0 {
429
+                Vec::new()
430
+            } else {
431
+                let raws =
432
+                    parse_raw_relocs(&section.raw_relocs, 0, section.nreloc).map_err(|err| {
433
+                        SynthError {
434
+                            input: input.object.path.clone(),
435
+                            atom: crate::resolve::AtomId(0),
436
+                            reloc_offset: 0,
437
+                            kind: RelocKind::Unsigned,
438
+                            detail: err.to_string(),
439
+                        }
440
+                    })?;
441
+                parse_relocs(&raws).map_err(|err| SynthError {
442
+                    input: input.object.path.clone(),
443
+                    atom: crate::resolve::AtomId(0),
444
+                    reloc_offset: 0,
445
+                    kind: RelocKind::Unsigned,
446
+                    detail: err.to_string(),
447
+                })?
448
+            };
449
+            reloc_cache.insert((input.id, (sect_idx + 1) as u8), relocs);
450
+        }
451
+    }
452
+    Ok(reloc_cache)
453
+}
454
+
455
+fn build_input_symbol_index(
456
+    inputs: &[LayoutInput<'_>],
457
+    sym_table: &SymbolTable,
458
+    parsed_relocs: &ParsedRelocCache,
459
+) -> HashMap<InputId, Vec<Option<SymbolId>>> {
460
+    let mut referenced = HashMap::<InputId, HashSet<u32>>::new();
461
+    for ((input_id, _), relocs) in parsed_relocs {
462
+        let referenced = referenced.entry(*input_id).or_default();
463
+        for reloc in relocs {
464
+            if let Referent::Symbol(sym_idx) = reloc.referent {
465
+                referenced.insert(sym_idx);
466
+            }
467
+            if let Some(Referent::Symbol(sym_idx)) = reloc.subtrahend {
468
+                referenced.insert(sym_idx);
469
+            }
470
+        }
471
+    }
472
+
473
+    let mut index = HashMap::new();
474
+    for input in inputs {
475
+        let mut symbols = vec![None; input.object.symbols.len()];
476
+        if let Some(referenced) = referenced.get(&input.id) {
477
+            for sym_idx in referenced {
478
+                let Some(input_sym) = input.object.symbols.get(*sym_idx as usize) else {
479
+                    continue;
480
+                };
481
+                let Some(slot) = symbols.get_mut(*sym_idx as usize) else {
482
+                    continue;
483
+                };
484
+                *slot = input
485
+                    .object
486
+                    .symbol_name(input_sym)
487
+                    .ok()
488
+                    .and_then(|name| sym_table.lookup_str(name));
489
+            }
490
+        }
491
+        index.insert(input.id, symbols);
492
+    }
493
+    index
494
+}
495
+
496
+fn build_sorted_reloc_index(parsed_relocs: &ParsedRelocCache) -> ParsedRelocCache {
497
+    let mut index = ParsedRelocCache::new();
498
+    for (key, relocs) in parsed_relocs {
499
+        if relocs.is_empty() {
500
+            continue;
501
+        }
502
+        let mut sorted = relocs.clone();
503
+        sorted.sort_by_key(|reloc| reloc.offset);
504
+        index.insert(*key, sorted);
505
+    }
506
+    index
507
+}
508
+
427509
 fn relocs_for_atom<'a>(relocs: &'a [Reloc], atom: &Atom) -> impl Iterator<Item = Reloc> + 'a {
428510
     let start = atom.input_offset;
429511
     let end = atom.input_offset + atom.size;
430
-    relocs.iter().copied().filter(move |reloc| {
512
+    let first = relocs.partition_point(|reloc| reloc.offset < start);
513
+    let last = relocs.partition_point(|reloc| reloc.offset < end);
514
+    relocs[first..last].iter().copied().filter(move |reloc| {
431515
         let reloc_end = reloc.offset + reloc.width_for_planning();
432
-        reloc.offset >= start && reloc_end <= end
516
+        reloc_end <= end
433517
     })
434518
 }
435519
 
@@ -455,8 +539,9 @@ fn dylib_import_referent(
455539
     obj: &ObjectFile,
456540
     referent: Referent,
457541
     sym_table: &SymbolTable,
542
+    input_symbols: Option<&[Option<SymbolId>]>,
458543
 ) -> Option<SymbolId> {
459
-    let symbol_id = symbol_referent_id(obj, referent, sym_table)?;
544
+    let symbol_id = symbol_referent_id(obj, referent, sym_table, input_symbols)?;
460545
     matches!(sym_table.get(symbol_id), Symbol::DylibImport { .. }).then_some(symbol_id)
461546
 }
462547
 
@@ -464,10 +549,14 @@ fn symbol_referent_id(
464549
     obj: &ObjectFile,
465550
     referent: Referent,
466551
     sym_table: &SymbolTable,
552
+    input_symbols: Option<&[Option<SymbolId>]>,
467553
 ) -> Option<SymbolId> {
468554
     let Referent::Symbol(sym_idx) = referent else {
469555
         return None;
470556
     };
557
+    if let Some(symbols) = input_symbols {
558
+        return symbols.get(sym_idx as usize).copied().flatten();
559
+    }
471560
     let input_sym = obj.symbols.get(sym_idx as usize)?;
472561
     let name = obj.symbol_name(input_sym).ok()?;
473562
     let (symbol_id, _) = sym_table
tests/perf_baseline.rsmodified
@@ -39,12 +39,18 @@ fn executable_opts(inputs: Vec<PathBuf>, output: PathBuf) -> LinkOptions {
3939
 
4040
 fn assert_profile_basics(name: &str, profile: &LinkProfile) {
4141
     eprintln!(
42
-        "{name}: total={:?} parse={:?} resolve={:?} atomize={:?} layout={:?} synth={:?} (linkedit={:?}: symbols={:?} [locals={:?} globals={:?} strtab={:?}] dyld={:?} metadata={:?} codesig={:?}; unwind={:?}) reloc={:?} write={:?}",
42
+        "{name}: total={:?} parse={:?} resolve={:?} atomize={:?} layout={:?} (entry={:?} dead={:?} icf={:?} synth_plan={:?} build={:?} thunks={:?}) synth={:?} (linkedit={:?}: symbols={:?} [locals={:?} globals={:?} strtab={:?}] dyld={:?} metadata={:?} codesig={:?}; unwind={:?}) reloc={:?} write={:?}",
4343
         profile.total_wall,
4444
         profile.phases.input_parsing,
4545
         profile.phases.symbol_resolution,
4646
         profile.phases.atomization,
4747
         profile.phases.layout,
48
+        profile.phases.layout_entry_lookup,
49
+        profile.phases.layout_dead_strip,
50
+        profile.phases.layout_icf,
51
+        profile.phases.layout_synthetic_plan,
52
+        profile.phases.layout_build,
53
+        profile.phases.layout_thunk_plan,
4854
         profile.phases.synth_sections,
4955
         profile.phases.synth_linkedit_finalize,
5056
         profile.phases.synth_linkedit_symbol_plan,
@@ -67,6 +73,16 @@ fn assert_profile_basics(name: &str, profile: &LinkProfile) {
6773
         profile.phases.accounted_total() > Duration::ZERO,
6874
         "{name}: all phase timings were zero"
6975
     );
76
+    assert!(
77
+        profile.phases.layout
78
+            >= profile.phases.layout_entry_lookup
79
+                + profile.phases.layout_dead_strip
80
+                + profile.phases.layout_icf
81
+                + profile.phases.layout_synthetic_plan
82
+                + profile.phases.layout_build
83
+                + profile.phases.layout_thunk_plan,
84
+        "{name}: layout subphases exceeded layout total"
85
+    );
7086
     assert!(
7187
         profile.phases.synth_sections
7288
             >= profile.phases.synth_linkedit_finalize + profile.phases.synth_unwind,