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 {
212
     pub symbol_resolution: Duration,
212
     pub symbol_resolution: Duration,
213
     pub atomization: Duration,
213
     pub atomization: Duration,
214
     pub layout: Duration,
214
     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,
215
     pub synth_sections: Duration,
221
     pub synth_sections: Duration,
216
     pub synth_linkedit_finalize: Duration,
222
     pub synth_linkedit_finalize: Duration,
217
     pub synth_linkedit_symbol_plan: Duration,
223
     pub synth_linkedit_symbol_plan: Duration,
@@ -583,8 +589,11 @@ impl Linker {
583
         let phase_started = Instant::now();
589
         let phase_started = Instant::now();
584
         let parsed_relocs = macho::writer::build_parsed_reloc_cache(&layout_inputs)?;
590
         let parsed_relocs = macho::writer::build_parsed_reloc_cache(&layout_inputs)?;
585
         phases.input_parsing += phase_started.elapsed();
591
         phases.input_parsing += phase_started.elapsed();
592
+        let layout_started = Instant::now();
586
         let phase_started = Instant::now();
593
         let phase_started = Instant::now();
587
         let entry_symbol = find_entry_symbol_id(opts, &sym_table)?;
594
         let entry_symbol = find_entry_symbol_id(opts, &sym_table)?;
595
+        phases.layout_entry_lookup = phase_started.elapsed();
596
+        let phase_started = Instant::now();
588
         let dead_strip = opts.dead_strip.then(|| {
597
         let dead_strip = opts.dead_strip.then(|| {
589
             why_live::DeadStripAnalysis::build(
598
             why_live::DeadStripAnalysis::build(
590
                 opts,
599
                 opts,
@@ -594,6 +603,8 @@ impl Linker {
594
                 entry_symbol,
603
                 entry_symbol,
595
             )
604
             )
596
         });
605
         });
606
+        phases.layout_dead_strip = phase_started.elapsed();
607
+        let phase_started = Instant::now();
597
         let icf = (opts.icf_mode == IcfMode::Safe)
608
         let icf = (opts.icf_mode == IcfMode::Safe)
598
             .then(|| {
609
             .then(|| {
599
                 icf::fold_safe(
610
                 icf::fold_safe(
@@ -604,19 +615,24 @@ impl Linker {
604
                 )
615
                 )
605
             })
616
             })
606
             .transpose()?;
617
             .transpose()?;
618
+        phases.layout_icf = phase_started.elapsed();
607
         let kept_atoms = if let Some(icf) = &icf {
619
         let kept_atoms = if let Some(icf) = &icf {
608
             Some(icf.kept_atoms())
620
             Some(icf.kept_atoms())
609
         } else {
621
         } else {
610
             dead_strip.as_ref().map(|analysis| analysis.live_atoms())
622
             dead_strip.as_ref().map(|analysis| analysis.live_atoms())
611
         };
623
         };
612
-        let synthetic_plan = synth::SyntheticPlan::build_filtered(
624
+        let phase_started = Instant::now();
625
+        let synthetic_plan = synth::SyntheticPlan::build_filtered_with_relocs(
613
             &layout_inputs,
626
             &layout_inputs,
614
             &atom_table,
627
             &atom_table,
615
             &mut sym_table,
628
             &mut sym_table,
616
             &inputs.dylibs,
629
             &inputs.dylibs,
617
             kept_atoms,
630
             kept_atoms,
631
+            &parsed_relocs,
618
         )?;
632
         )?;
633
+        phases.layout_synthetic_plan = phase_started.elapsed();
619
         let icf_redirects = icf.as_ref().map(|plan| plan.redirects());
634
         let icf_redirects = icf.as_ref().map(|plan| plan.redirects());
635
+        let phase_started = Instant::now();
620
         let mut layout = Layout::build_with_synthetics_filtered(
636
         let mut layout = Layout::build_with_synthetics_filtered(
621
             opts.kind,
637
             opts.kind,
622
             &layout_inputs,
638
             &layout_inputs,
@@ -625,18 +641,24 @@ impl Linker {
625
             Some(&synthetic_plan),
641
             Some(&synthetic_plan),
626
             kept_atoms,
642
             kept_atoms,
627
         );
643
         );
644
+        phases.layout_build += phase_started.elapsed();
628
         let mut thunk_plan = None;
645
         let mut thunk_plan = None;
629
         let mut thunk_converged = false;
646
         let mut thunk_converged = false;
630
         for _ in 0..THUNK_PLAN_MAX_ITERATIONS {
647
         for _ in 0..THUNK_PLAN_MAX_ITERATIONS {
648
+            let phase_started = Instant::now();
631
             let next_plan = reloc::arm64::plan_thunks(
649
             let next_plan = reloc::arm64::plan_thunks(
632
                 opts,
650
                 opts,
633
-                &layout,
651
+                reloc::arm64::ThunkPlanningContext {
634
-                &layout_inputs,
652
+                    layout: &layout,
635
-                &atom_table,
653
+                    inputs: &layout_inputs,
636
-                &sym_table,
654
+                    atoms: &atom_table,
637
-                Some(&synthetic_plan),
655
+                    sym_table: &sym_table,
638
-                icf_redirects,
656
+                    synthetic_plan: Some(&synthetic_plan),
657
+                    icf_redirects,
658
+                    parsed_relocs: &parsed_relocs,
659
+                },
639
             )?;
660
             )?;
661
+            phases.layout_thunk_plan += phase_started.elapsed();
640
             if next_plan == thunk_plan {
662
             if next_plan == thunk_plan {
641
                 thunk_converged = true;
663
                 thunk_converged = true;
642
                 break;
664
                 break;
@@ -647,6 +669,7 @@ impl Linker {
647
             let split_after_atoms = next_plan
669
             let split_after_atoms = next_plan
648
                 .as_ref()
670
                 .as_ref()
649
                 .map_or_else(Vec::new, |plan| plan.split_after_atoms());
671
                 .map_or_else(Vec::new, |plan| plan.split_after_atoms());
672
+            let phase_started = Instant::now();
650
             layout = Layout::build_with_synthetics_and_extra_filtered(
673
             layout = Layout::build_with_synthetics_and_extra_filtered(
651
                 opts.kind,
674
                 opts.kind,
652
                 &layout_inputs,
675
                 &layout_inputs,
@@ -659,12 +682,13 @@ impl Linker {
659
                     split_after_atoms: &split_after_atoms,
682
                     split_after_atoms: &split_after_atoms,
660
                 },
683
                 },
661
             );
684
             );
685
+            phases.layout_build += phase_started.elapsed();
662
             thunk_plan = next_plan;
686
             thunk_plan = next_plan;
663
         }
687
         }
664
         if !thunk_converged {
688
         if !thunk_converged {
665
             return Err(LinkError::ThunkPlanningDidNotConverge);
689
             return Err(LinkError::ThunkPlanningDidNotConverge);
666
         }
690
         }
667
-        phases.layout = phase_started.elapsed();
691
+        phases.layout = layout_started.elapsed();
668
         let linkedit_context = macho::writer::LinkEditContext {
692
         let linkedit_context = macho::writer::LinkEditContext {
669
             layout_inputs: &layout_inputs,
693
             layout_inputs: &layout_inputs,
670
             atom_table: &atom_table,
694
             atom_table: &atom_table,
@@ -739,6 +763,7 @@ impl Linker {
739
                 thunk_plan: thunk_plan.as_ref(),
763
                 thunk_plan: thunk_plan.as_ref(),
740
                 linkedit: &linkedit,
764
                 linkedit: &linkedit,
741
                 icf_redirects,
765
                 icf_redirects,
766
+                parsed_relocs: &parsed_relocs,
742
             },
767
             },
743
         )?;
768
         )?;
744
         phases.reloc_apply = phase_started.elapsed();
769
         phases.reloc_apply = phase_started.elapsed();
src/macho/writer.rsmodified
@@ -20,7 +20,9 @@ use crate::macho::reader::{
20
     LinkEditDataCmd, LoadCommand, MachHeader64, RpathCmd, Section64Header, Segment64, SymtabCmd,
20
     LinkEditDataCmd, LoadCommand, MachHeader64, RpathCmd, Section64Header, Segment64, SymtabCmd,
21
     HEADER_SIZE,
21
     HEADER_SIZE,
22
 };
22
 };
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
+};
24
 use crate::resolve::InputId;
26
 use crate::resolve::InputId;
25
 use crate::resolve::{Symbol, SymbolId, SymbolTable};
27
 use crate::resolve::{Symbol, SymbolId, SymbolTable};
26
 use crate::section::is_executable;
28
 use crate::section::is_executable;
@@ -53,8 +55,6 @@ pub struct LinkEditContext<'a> {
53
     pub parsed_relocs: &'a ParsedRelocCache,
55
     pub parsed_relocs: &'a ParsedRelocCache,
54
 }
56
 }
55
 
57
 
56
-pub type ParsedRelocCache = HashMap<(InputId, u8), Vec<Reloc>>;
57
-
58
 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
58
 #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
59
 pub struct LinkEditBuildTimings {
59
 pub struct LinkEditBuildTimings {
60
     pub symbol_plan: Duration,
60
     pub symbol_plan: Duration,
src/reloc/arm64.rsmodified
@@ -6,7 +6,7 @@ use crate::atom::{Atom, AtomSection, AtomTable};
6
 use crate::input::ObjectFile;
6
 use crate::input::ObjectFile;
7
 use crate::layout::{ExtraOutputSection, ExtraSectionAnchor, Layout, LayoutInput};
7
 use crate::layout::{ExtraOutputSection, ExtraSectionAnchor, Layout, LayoutInput};
8
 use crate::macho::writer::LinkEditPlan;
8
 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};
10
 use crate::resolve::{InputId, Symbol, SymbolId, SymbolTable};
10
 use crate::resolve::{InputId, Symbol, SymbolId, SymbolTable};
11
 use crate::section::{OutputSection, SectionKind};
11
 use crate::section::{OutputSection, SectionKind};
12
 use crate::symbol::{InputSymbol, SymKind};
12
 use crate::symbol::{InputSymbol, SymKind};
@@ -72,6 +72,7 @@ pub struct ApplyLayoutPlan<'a> {
72
     pub thunk_plan: Option<&'a ThunkPlan>,
72
     pub thunk_plan: Option<&'a ThunkPlan>,
73
     pub linkedit: &'a LinkEditPlan,
73
     pub linkedit: &'a LinkEditPlan,
74
     pub icf_redirects: Option<&'a HashMap<crate::resolve::AtomId, crate::resolve::AtomId>>,
74
     pub icf_redirects: Option<&'a HashMap<crate::resolve::AtomId, crate::resolve::AtomId>>,
75
+    pub parsed_relocs: &'a ParsedRelocCache,
75
 }
76
 }
76
 
77
 
77
 struct InputSectionResolveCtx<'a> {
78
 struct InputSectionResolveCtx<'a> {
@@ -206,36 +207,6 @@ pub fn apply_layout(
206
         .iter()
207
         .iter()
207
         .map(|input| (input.id, input.object))
208
         .map(|input| (input.id, input.object))
208
         .collect();
209
         .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
-
239
     let atom_addrs = atom_address_map(layout);
210
     let atom_addrs = atom_address_map(layout);
240
     let atoms_by_input_section = atoms.by_input_section();
211
     let atoms_by_input_section = atoms.by_input_section();
241
     let section_addrs = input_section_address_map(layout, atoms);
212
     let section_addrs = input_section_address_map(layout, atoms);
@@ -277,7 +248,8 @@ pub fn apply_layout(
277
                 )
248
                 )
278
             })?;
249
             })?;
279
             patch_eh_frame_cie_pointer(&mut placed.data, atom, &resolve)?;
250
             patch_eh_frame_cie_pointer(&mut placed.data, atom, &resolve)?;
280
-            let relocs = reloc_cache
251
+            let relocs = plan
252
+                .parsed_relocs
281
                 .get(&(atom.origin, atom.input_section))
253
                 .get(&(atom.origin, atom.input_section))
282
                 .map(Vec::as_slice)
254
                 .map(Vec::as_slice)
283
                 .unwrap_or(&[]);
255
                 .unwrap_or(&[]);
@@ -305,7 +277,7 @@ pub fn apply_layout(
305
             synthetic_plan,
277
             synthetic_plan,
306
             atoms,
278
             atoms,
307
             &input_map,
279
             &input_map,
308
-            &reloc_cache,
280
+            plan.parsed_relocs,
309
             &resolve,
281
             &resolve,
310
         )?;
282
         )?;
311
         synthesize_got_section(layout, synthetic_plan, &resolve)?;
283
         synthesize_got_section(layout, synthetic_plan, &resolve)?;
@@ -510,53 +482,38 @@ fn synthetic_address_maps(
510
     }
482
     }
511
 }
483
 }
512
 
484
 
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
+
513
 pub fn plan_thunks(
495
 pub fn plan_thunks(
514
     opts: &LinkOptions,
496
     opts: &LinkOptions,
515
-    layout: &Layout,
497
+    ctx: ThunkPlanningContext<'_>,
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>>,
521
 ) -> Result<Option<ThunkPlan>, RelocError> {
498
 ) -> Result<Option<ThunkPlan>, RelocError> {
522
     if opts.thunks == ThunkMode::None {
499
     if opts.thunks == ThunkMode::None {
523
         return Ok(None);
500
         return Ok(None);
524
     }
501
     }
525
 
502
 
503
+    let ThunkPlanningContext {
504
+        layout,
505
+        inputs,
506
+        atoms,
507
+        sym_table,
508
+        synthetic_plan,
509
+        icf_redirects,
510
+        parsed_relocs,
511
+    } = ctx;
512
+
526
     let input_map: HashMap<InputId, &ObjectFile> = inputs
513
     let input_map: HashMap<InputId, &ObjectFile> = inputs
527
         .iter()
514
         .iter()
528
         .map(|input| (input.id, input.object))
515
         .map(|input| (input.id, input.object))
529
         .collect();
516
         .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
-
560
     let atom_addrs = atom_address_map(layout);
517
     let atom_addrs = atom_address_map(layout);
561
     let atom_segments = atom_output_segment_map(layout);
518
     let atom_segments = atom_output_segment_map(layout);
562
     let atoms_by_input_section = atoms.by_input_section();
519
     let atoms_by_input_section = atoms.by_input_section();
@@ -588,7 +545,7 @@ pub fn plan_thunks(
588
         let Some(obj) = input_map.get(&atom.origin) else {
545
         let Some(obj) = input_map.get(&atom.origin) else {
589
             continue;
546
             continue;
590
         };
547
         };
591
-        let relocs = reloc_cache
548
+        let relocs = parsed_relocs
592
             .get(&(atom.origin, atom.input_section))
549
             .get(&(atom.origin, atom.input_section))
593
             .map(Vec::as_slice)
550
             .map(Vec::as_slice)
594
             .unwrap_or(&[]);
551
             .unwrap_or(&[]);
@@ -2739,9 +2696,21 @@ mod tests {
2739
             ..LinkOptions::default()
2696
             ..LinkOptions::default()
2740
         };
2697
         };
2741
         let base_layout = Layout::build(OutputKind::Executable, &inputs, &atoms, 0);
2698
         let base_layout = Layout::build(OutputKind::Executable, &inputs, &atoms, 0);
2742
-        let plan = plan_thunks(&opts, &base_layout, &inputs, &atoms, &sym_table, None, None)
2699
+        let parsed_relocs = crate::macho::writer::build_parsed_reloc_cache(&inputs).unwrap();
2743
-            .unwrap()
2700
+        let plan = plan_thunks(
2744
-            .unwrap();
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();
2745
 
2714
 
2746
         assert_eq!(
2715
         assert_eq!(
2747
             plan.redirect_for(caller1, 0),
2716
             plan.redirect_for(caller1, 0),
@@ -2795,9 +2764,20 @@ mod tests {
2795
             vec!["__text", "__thunks", "__text", "__thunks", "__text"]
2764
             vec!["__text", "__thunks", "__text", "__thunks", "__text"]
2796
         );
2765
         );
2797
 
2766
 
2798
-        let replan = plan_thunks(&opts, &rebuilt, &inputs, &atoms, &sym_table, None, None)
2767
+        let replan = plan_thunks(
2799
-            .unwrap()
2768
+            &opts,
2800
-            .unwrap();
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();
2801
         assert_eq!(
2781
         assert_eq!(
2802
             replan, plan,
2782
             replan, plan,
2803
             "expected thunk planning to converge once the intra-section islands exist"
2783
             "expected thunk planning to converge once the intra-section islands exist"
src/reloc/mod.rsmodified
@@ -17,8 +17,11 @@
17
 
17
 
18
 pub mod arm64;
18
 pub mod arm64;
19
 
19
 
20
+use std::collections::HashMap;
21
+
20
 use crate::macho::constants::*;
22
 use crate::macho::constants::*;
21
 use crate::macho::reader::{u32_le, ReadError};
23
 use crate::macho::reader::{u32_le, ReadError};
24
+use crate::resolve::InputId;
22
 
25
 
23
 /// Size of one `relocation_info` on the wire.
26
 /// Size of one `relocation_info` on the wire.
24
 pub const RAW_RELOC_SIZE: usize = 8;
27
 pub const RAW_RELOC_SIZE: usize = 8;
@@ -185,6 +188,8 @@ pub struct Reloc {
185
     pub subtrahend: Option<Referent>,
188
     pub subtrahend: Option<Referent>,
186
 }
189
 }
187
 
190
 
191
+pub type ParsedRelocCache = HashMap<(InputId, u8), Vec<Reloc>>;
192
+
188
 fn referent_from(raw: &RawRelocation) -> Result<Referent, ReadError> {
193
 fn referent_from(raw: &RawRelocation) -> Result<Referent, ReadError> {
189
     if raw.r_extern {
194
     if raw.r_extern {
190
         Ok(Referent::Symbol(raw.r_symbolnum))
195
         Ok(Referent::Symbol(raw.r_symbolnum))
src/resolve.rsmodified
@@ -63,6 +63,10 @@ impl StringInterner {
63
         Istr(id)
63
         Istr(id)
64
     }
64
     }
65
 
65
 
66
+    pub fn get(&self, s: &str) -> Option<Istr> {
67
+        self.index.get(s).copied().map(Istr)
68
+    }
69
+
66
     pub fn resolve(&self, i: Istr) -> &str {
70
     pub fn resolve(&self, i: Istr) -> &str {
67
         &self.strings[i.0 as usize]
71
         &self.strings[i.0 as usize]
68
     }
72
     }
@@ -554,6 +558,10 @@ impl SymbolTable {
554
         self.by_name.get(&name).copied()
558
         self.by_name.get(&name).copied()
555
     }
559
     }
556
 
560
 
561
+    pub fn lookup_str(&self, name: &str) -> Option<SymbolId> {
562
+        self.interner.get(name).and_then(|name| self.lookup(name))
563
+    }
564
+
557
     pub fn get(&self, id: SymbolId) -> &Symbol {
565
     pub fn get(&self, id: SymbolId) -> &Symbol {
558
         &self.symbols[id.0 as usize]
566
         &self.symbols[id.0 as usize]
559
     }
567
     }
src/synth/mod.rsmodified
@@ -16,7 +16,9 @@ use crate::macho::constants::{
16
     S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS, S_LAZY_SYMBOL_POINTERS,
16
     S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS, S_LAZY_SYMBOL_POINTERS,
17
     S_NON_LAZY_SYMBOL_POINTERS, S_REGULAR, S_SYMBOL_STUBS, S_THREAD_LOCAL_VARIABLE_POINTERS,
17
     S_NON_LAZY_SYMBOL_POINTERS, S_REGULAR, S_SYMBOL_STUBS, S_THREAD_LOCAL_VARIABLE_POINTERS,
18
 };
18
 };
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
+};
20
 use crate::resolve::{
22
 use crate::resolve::{
21
     AtomId, DylibId, DylibInput, InputId, InsertOutcome, Symbol, SymbolId, SymbolTable,
23
     AtomId, DylibId, DylibInput, InputId, InsertOutcome, Symbol, SymbolId, SymbolTable,
22
 };
24
 };
@@ -92,37 +94,25 @@ impl SyntheticPlan {
92
         sym_table: &mut SymbolTable,
94
         sym_table: &mut SymbolTable,
93
         dylibs: &[DylibInput],
95
         dylibs: &[DylibInput],
94
         live_atoms: Option<&HashSet<AtomId>>,
96
         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,
95
     ) -> Result<Self, SynthError> {
109
     ) -> Result<Self, SynthError> {
96
         let input_map: HashMap<InputId, &ObjectFile> = inputs
110
         let input_map: HashMap<InputId, &ObjectFile> = inputs
97
             .iter()
111
             .iter()
98
             .map(|input| (input.id, input.object))
112
             .map(|input| (input.id, input.object))
99
             .collect();
113
             .collect();
100
-        let mut reloc_cache: HashMap<(InputId, u8), Vec<Reloc>> = HashMap::new();
114
+        let input_symbol_index = build_input_symbol_index(inputs, sym_table, parsed_relocs);
101
-        for input in inputs {
115
+        let reloc_index = build_sorted_reloc_index(parsed_relocs);
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
-        }
126
 
116
 
127
         let mut got = GotSection::default();
117
         let mut got = GotSection::default();
128
         let mut stubs = StubsSection::default();
118
         let mut stubs = StubsSection::default();
@@ -141,16 +131,19 @@ impl SyntheticPlan {
141
                 kind: RelocKind::Unsigned,
131
                 kind: RelocKind::Unsigned,
142
                 detail: "missing parsed object".to_string(),
132
                 detail: "missing parsed object".to_string(),
143
             })?;
133
             })?;
144
-            let relocs = reloc_cache
134
+            let relocs = reloc_index
145
                 .get(&(atom.origin, atom.input_section))
135
                 .get(&(atom.origin, atom.input_section))
146
                 .map(Vec::as_slice)
136
                 .map(Vec::as_slice)
147
                 .unwrap_or(&[]);
137
                 .unwrap_or(&[]);
138
+            let input_symbols = input_symbol_index.get(&atom.origin).map(Vec::as_slice);
148
             for reloc in relocs_for_atom(relocs, atom) {
139
             for reloc in relocs_for_atom(relocs, atom) {
149
                 if atom.section == AtomSection::CompactUnwind
140
                 if atom.section == AtomSection::CompactUnwind
150
                     && reloc.kind == RelocKind::Unsigned
141
                     && reloc.kind == RelocKind::Unsigned
151
                     && reloc.offset == atom.input_offset + COMPACT_UNWIND_PERSONALITY_FIELD_OFFSET
142
                     && reloc.offset == atom.input_offset + COMPACT_UNWIND_PERSONALITY_FIELD_OFFSET
152
                 {
143
                 {
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
+                    {
154
                         got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
147
                         got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
155
                     }
148
                     }
156
                     continue;
149
                     continue;
@@ -163,7 +156,8 @@ impl SyntheticPlan {
163
                         if matches!(atom.section, AtomSection::ThreadLocalVariables) {
156
                         if matches!(atom.section, AtomSection::ThreadLocalVariables) {
164
                             continue;
157
                             continue;
165
                         }
158
                         }
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)
167
                         else {
161
                         else {
168
                             continue;
162
                             continue;
169
                         };
163
                         };
@@ -179,7 +173,8 @@ impl SyntheticPlan {
179
                     RelocKind::GotLoadPage21
173
                     RelocKind::GotLoadPage21
180
                     | RelocKind::GotLoadPageOff12
174
                     | RelocKind::GotLoadPageOff12
181
                     | RelocKind::PointerToGot => {
175
                     | 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)
183
                         else {
178
                         else {
184
                             continue;
179
                             continue;
185
                         };
180
                         };
@@ -198,7 +193,8 @@ impl SyntheticPlan {
198
                         }
193
                         }
199
                     }
194
                     }
200
                     RelocKind::Branch26 => {
195
                     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)
202
                         else {
198
                         else {
203
                             continue;
199
                             continue;
204
                         };
200
                         };
@@ -214,7 +210,8 @@ impl SyntheticPlan {
214
                         );
210
                         );
215
                     }
211
                     }
216
                     RelocKind::TlvpLoadPage21 | RelocKind::TlvpLoadPageOff12 => {
212
                     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)
218
                         {
215
                         {
219
                             if tlv_symbol_needs_got(sym_table, symbol_id) {
216
                             if tlv_symbol_needs_got(sym_table, symbol_id) {
220
                                 got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
217
                                 got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
@@ -424,12 +421,99 @@ fn sort_symbol_indexed_entries<T, F>(
424
     }
421
     }
425
 }
422
 }
426
 
423
 
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
+
427
 fn relocs_for_atom<'a>(relocs: &'a [Reloc], atom: &Atom) -> impl Iterator<Item = Reloc> + 'a {
509
 fn relocs_for_atom<'a>(relocs: &'a [Reloc], atom: &Atom) -> impl Iterator<Item = Reloc> + 'a {
428
     let start = atom.input_offset;
510
     let start = atom.input_offset;
429
     let end = atom.input_offset + atom.size;
511
     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| {
431
         let reloc_end = reloc.offset + reloc.width_for_planning();
515
         let reloc_end = reloc.offset + reloc.width_for_planning();
432
-        reloc.offset >= start && reloc_end <= end
516
+        reloc_end <= end
433
     })
517
     })
434
 }
518
 }
435
 
519
 
@@ -455,8 +539,9 @@ fn dylib_import_referent(
455
     obj: &ObjectFile,
539
     obj: &ObjectFile,
456
     referent: Referent,
540
     referent: Referent,
457
     sym_table: &SymbolTable,
541
     sym_table: &SymbolTable,
542
+    input_symbols: Option<&[Option<SymbolId>]>,
458
 ) -> Option<SymbolId> {
543
 ) -> 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)?;
460
     matches!(sym_table.get(symbol_id), Symbol::DylibImport { .. }).then_some(symbol_id)
545
     matches!(sym_table.get(symbol_id), Symbol::DylibImport { .. }).then_some(symbol_id)
461
 }
546
 }
462
 
547
 
@@ -464,10 +549,14 @@ fn symbol_referent_id(
464
     obj: &ObjectFile,
549
     obj: &ObjectFile,
465
     referent: Referent,
550
     referent: Referent,
466
     sym_table: &SymbolTable,
551
     sym_table: &SymbolTable,
552
+    input_symbols: Option<&[Option<SymbolId>]>,
467
 ) -> Option<SymbolId> {
553
 ) -> Option<SymbolId> {
468
     let Referent::Symbol(sym_idx) = referent else {
554
     let Referent::Symbol(sym_idx) = referent else {
469
         return None;
555
         return None;
470
     };
556
     };
557
+    if let Some(symbols) = input_symbols {
558
+        return symbols.get(sym_idx as usize).copied().flatten();
559
+    }
471
     let input_sym = obj.symbols.get(sym_idx as usize)?;
560
     let input_sym = obj.symbols.get(sym_idx as usize)?;
472
     let name = obj.symbol_name(input_sym).ok()?;
561
     let name = obj.symbol_name(input_sym).ok()?;
473
     let (symbol_id, _) = sym_table
562
     let (symbol_id, _) = sym_table
tests/perf_baseline.rsmodified
@@ -39,12 +39,18 @@ fn executable_opts(inputs: Vec<PathBuf>, output: PathBuf) -> LinkOptions {
39
 
39
 
40
 fn assert_profile_basics(name: &str, profile: &LinkProfile) {
40
 fn assert_profile_basics(name: &str, profile: &LinkProfile) {
41
     eprintln!(
41
     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={:?}",
43
         profile.total_wall,
43
         profile.total_wall,
44
         profile.phases.input_parsing,
44
         profile.phases.input_parsing,
45
         profile.phases.symbol_resolution,
45
         profile.phases.symbol_resolution,
46
         profile.phases.atomization,
46
         profile.phases.atomization,
47
         profile.phases.layout,
47
         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,
48
         profile.phases.synth_sections,
54
         profile.phases.synth_sections,
49
         profile.phases.synth_linkedit_finalize,
55
         profile.phases.synth_linkedit_finalize,
50
         profile.phases.synth_linkedit_symbol_plan,
56
         profile.phases.synth_linkedit_symbol_plan,
@@ -67,6 +73,16 @@ fn assert_profile_basics(name: &str, profile: &LinkProfile) {
67
         profile.phases.accounted_total() > Duration::ZERO,
73
         profile.phases.accounted_total() > Duration::ZERO,
68
         "{name}: all phase timings were zero"
74
         "{name}: all phase timings were zero"
69
     );
75
     );
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
+    );
70
     assert!(
86
     assert!(
71
         profile.phases.synth_sections
87
         profile.phases.synth_sections
72
             >= profile.phases.synth_linkedit_finalize + profile.phases.synth_unwind,
88
             >= profile.phases.synth_linkedit_finalize + profile.phases.synth_unwind,