@@ -4172,69 +4172,154 @@ lnk_icf_small_sort(U64 *keys, U32 *vals, U64 n)
41724172// provably-redundant re-key requests (the worklist dirties CLASSES, set semantics), so it visits the
41734173// same dirty sets -> identical partition -> byte-identical output.
41744174//
4175- // Duplicate (C,T) edges (multiple relocs from C to the same T) are deduped: cands are iterated in
4176- // ascending index and each cand's relocs are drained before the next, so per-target appends arrive in
4177- // non-decreasing C -- a duplicate is exactly "tail of T's bucket == C" (last_ref[] in the count pass,
4178- // bucket-tail check in the fill pass; identical predicates under identical iteration order).
4179- // Filtering/dedup only removes provably-redundant re-key requests (the worklist dirties CLASSES, set
4180- // semantics), so it visits the same dirty sets -> identical partition -> byte-identical output.
4181- internal void
4182- lnk_icf_build_reverse_index (Arena * arena , U64 cand_count , LNK_ICFCand * cands , U8 * rt_iscand , U64 * rt_target ,
4183- U32 * active , U64 active_count ,
4184- U32 * * out_rev_off , U32 * * out_rev_adj )
4175+ // PARALLEL BUILD: the old serial builder (two full scans over every active cand's relocs + a per-target
4176+ // last_ref/cursor dedup) was the dominant serial cost of the whole worklist tail (~0.4-0.9s wall at
4177+ // editor scale). Instead: parallel count/fill of the filtered (target, referrer) edge list over
4178+ // reloc-weighted cand ranges (dups kept), one parallel stable radix sort by target, then a serial
4179+ // compaction pass. Identical result:
4180+ // - per-target referrer order: edges are emitted in ascending referrer ci (workers own contiguous
4181+ // cand ranges, concatenated in order), and the radix sort is stable -> per-target ascending ci,
4182+ // exactly the old iteration order;
4183+ // - dedup: duplicate (C,T) pairs only arise within one cand's reloc list (C is unique per cand), so
4184+ // they are adjacent after the stable sort; dropping equal-neighbor pairs removes exactly the pairs
4185+ // last_ref/cursor dropped.
4186+ typedef struct LNK_ICFRevIdxTask
41854187{
4186- Temp scratch = scratch_begin (& arena , 1 );
4187- U8 * is_active = push_array (scratch .arena , U8 , cand_count ? cand_count : 1 );
4188- for EachIndex (i , active_count ) { is_active [active [i ]] = 1 ; }
4189- U32 * last_ref = push_array (scratch .arena , U32 , cand_count ? cand_count : 1 ); // ci+1 of last referrer counted per target; 0 = none
4188+ LNK_ICFCand * cands ;
4189+ U8 * rt_iscand ;
4190+ U64 * rt_target ;
4191+ U8 * is_active ;
4192+ U32 * active ;
4193+ Rng1U64 * act_ranges ; // [W+1] even split over active_count (is_active scatter)
4194+ Rng1U64 * cand_ranges ; // [W+1] reloc-weighted split over cand_count (count/fill)
4195+ U64 * counts ; // [W] per-worker filtered edge counts
4196+ U64 * offsets ; // [W] exclusive prefix of counts
4197+ U64 * ek ; // [edge] key: target cand idx
4198+ U32 * ev ; // [edge] val: referrer cand idx
4199+ } LNK_ICFRevIdxTask ;
41904200
4191- U32 * rev_off = push_array (arena , U32 , cand_count + 1 );
4192- U64 raw_iscand = 0 ; // instrumentation: unfiltered edge count (old rev_adj size); debug-log only, so
4193- // it is counted in a separate debug-gated pass -- the hot count pass skips
4194- // inactive referrer rows at the CAND level (no per-reloc rt_iscand loads at all
4195- // for the ~vast majority of cands that are already inactive at handoff).
4196- if (lnk_get_log_status (LNK_Log_Debug )) {
4197- for EachIndex (ci , cand_count ) {
4198- LNK_ICFCand * c = & cands [ci ];
4199- for EachIndex (j , c -> reloc_count ) { raw_iscand += rt_iscand [(U64 )c -> reloc_first + j ]; }
4201+ internal
4202+ THREAD_POOL_TASK_FUNC (lnk_icf_revidx_active_task )
4203+ {
4204+ LNK_ICFRevIdxTask * t = raw_task ;
4205+ Rng1U64 r = t -> act_ranges [task_id ];
4206+ for EachInRange (i , r ) { t -> is_active [t -> active [i ]] = 1 ; }
4207+ }
4208+
4209+ internal
4210+ THREAD_POOL_TASK_FUNC (lnk_icf_revidx_count_task )
4211+ {
4212+ LNK_ICFRevIdxTask * t = raw_task ;
4213+ Rng1U64 r = t -> cand_ranges [task_id ];
4214+ U64 n = 0 ;
4215+ for EachInRange (ci , r ) {
4216+ if (!t -> is_active [ci ]) { continue ; }
4217+ LNK_ICFCand * c = & t -> cands [ci ];
4218+ for EachIndex (j , c -> reloc_count ) {
4219+ U64 idx = (U64 )c -> reloc_first + j ;
4220+ if (!t -> rt_iscand [idx ]) { continue ; }
4221+ if (!t -> is_active [t -> rt_target [idx ]]) { continue ; }
4222+ n += 1 ;
42004223 }
42014224 }
4202- for EachIndex (ci , cand_count ) {
4203- if (!is_active [ci ]) { continue ; }
4204- LNK_ICFCand * c = & cands [ci ];
4225+ t -> counts [task_id ] = n ;
4226+ }
4227+
4228+ internal
4229+ THREAD_POOL_TASK_FUNC (lnk_icf_revidx_fill_task )
4230+ {
4231+ LNK_ICFRevIdxTask * t = raw_task ;
4232+ Rng1U64 r = t -> cand_ranges [task_id ];
4233+ U64 out = t -> offsets [task_id ];
4234+ for EachInRange (ci , r ) {
4235+ if (!t -> is_active [ci ]) { continue ; }
4236+ LNK_ICFCand * c = & t -> cands [ci ];
42054237 for EachIndex (j , c -> reloc_count ) {
42064238 U64 idx = (U64 )c -> reloc_first + j ;
4207- if (!rt_iscand [idx ]) { continue ; }
4208- U32 tg = (U32 )rt_target [idx ];
4209- if (!is_active [tg ]) { continue ; }
4210- if (last_ref [tg ] == (U32 )ci + 1 ) { continue ; } // duplicate (C,T)
4211- last_ref [tg ] = (U32 )ci + 1 ;
4212- rev_off [tg + 1 ] += 1 ;
4239+ if (!t -> rt_iscand [idx ]) { continue ; }
4240+ U64 tg = t -> rt_target [idx ];
4241+ if (!t -> is_active [tg ]) { continue ; }
4242+ t -> ek [out ] = tg ; t -> ev [out ] = (U32 )ci ; out += 1 ;
42134243 }
42144244 }
4245+ }
4246+
4247+ internal void
4248+ lnk_icf_build_reverse_index (TP_Context * tp , Arena * arena , U64 cand_count , LNK_ICFCand * cands , U8 * rt_iscand , U64 * rt_target ,
4249+ U32 * active , U64 active_count ,
4250+ U32 * * out_rev_off , U32 * * out_rev_adj )
4251+ {
4252+ Temp scratch = scratch_begin (& arena , 1 );
4253+ U64 W = tp -> worker_count ? tp -> worker_count : 1 ;
4254+ U64 total_relocs = cand_count ? ((U64 )cands [cand_count - 1 ].reloc_first + cands [cand_count - 1 ].reloc_count ) : 0 ;
4255+
4256+ LNK_ICFRevIdxTask task = {0 };
4257+ task .cands = cands ;
4258+ task .rt_iscand = rt_iscand ;
4259+ task .rt_target = rt_target ;
4260+ task .active = active ;
4261+ task .is_active = push_array (scratch .arena , U8 , cand_count ? cand_count : 1 );
4262+
4263+ // active-bit scatter (disjoint ranges; all stores are the constant 1)
4264+ task .act_ranges = tp_divide_work (scratch .arena , active_count , W );
4265+ tp_for_parallel (tp , 0 , W , lnk_icf_revidx_active_task , & task );
4266+
4267+ // reloc-weighted cand ranges: split at the cand whose reloc_first crosses w/W of total_relocs
4268+ // (reloc_first is monotone). Splitting at cand boundaries keeps each cand's (potentially
4269+ // duplicated) targets inside ONE worker's contiguous, in-order output range.
4270+ task .cand_ranges = push_array_no_zero (scratch .arena , Rng1U64 , W + 1 );
4271+ {
4272+ U64 prev = 0 ;
4273+ for (U64 w = 1 ; w <= W ; w += 1 ) {
4274+ U64 goal = (total_relocs * w ) / W ;
4275+ U64 lo = prev , hi = cand_count ;
4276+ while (lo < hi ) { U64 mid = (lo + hi ) >> 1 ; if ((U64 )cands [mid ].reloc_first < goal ) { lo = mid + 1 ; } else { hi = mid ; } }
4277+ task .cand_ranges [w - 1 ] = rng_1u64 (prev , lo );
4278+ prev = lo ;
4279+ }
4280+ task .cand_ranges [W - 1 ].max = cand_count ; // last range absorbs the tail
4281+ task .cand_ranges [W ] = rng_1u64 (cand_count , cand_count );
4282+ }
4283+
4284+ task .counts = push_array (scratch .arena , U64 , W );
4285+ tp_for_parallel (tp , 0 , W , lnk_icf_revidx_count_task , & task );
4286+ task .offsets = offsets_from_counts_array_u64 (scratch .arena , task .counts , W );
4287+ U64 edge_raw = task .offsets [W - 1 ] + task .counts [W - 1 ];
4288+
4289+ task .ek = push_array_no_zero (scratch .arena , U64 , edge_raw ? edge_raw : 1 );
4290+ task .ev = push_array_no_zero (scratch .arena , U32 , edge_raw ? edge_raw : 1 );
4291+ tp_for_parallel (tp , 0 , W , lnk_icf_revidx_fill_task , & task );
4292+
4293+ // stable parallel radix sort by target (ties keep input order = ascending referrer ci)
4294+ lnk_radix_sort_u64_pairs (tp , scratch .arena , edge_raw , task .ek , task .ev );
4295+
4296+ // serial compaction: drop adjacent duplicate (target, referrer) pairs, count per target, emit CSR.
4297+ // rev_adj is written strictly sequentially (edges are sorted by target).
4298+ U32 * rev_off = push_array (arena , U32 , cand_count + 1 );
4299+ U64 edge_count = 0 ;
4300+ for (U64 e = 0 ; e < edge_raw ; e += 1 ) {
4301+ if (e > 0 && task .ek [e ] == task .ek [e - 1 ] && task .ev [e ] == task .ev [e - 1 ]) { continue ; } // duplicate (C,T)
4302+ rev_off [task .ek [e ] + 1 ] += 1 ;
4303+ edge_count += 1 ;
4304+ }
42154305 for EachIndex (t , cand_count ) { rev_off [t + 1 ] += rev_off [t ]; }
4216- U64 edge_count = rev_off [cand_count ];
42174306 U32 * rev_adj = push_array_no_zero (arena , U32 , edge_count ? edge_count : 1 );
4218- U32 * cursor = push_array (scratch .arena , U32 , cand_count ? cand_count : 1 );
4219- for EachIndex (ci , cand_count ) {
4220- if (!is_active [ci ]) { continue ; }
4221- LNK_ICFCand * c = & cands [ci ];
4222- for EachIndex (j , c -> reloc_count ) {
4223- U64 idx = (U64 )c -> reloc_first + j ;
4224- if (!rt_iscand [idx ]) { continue ; }
4225- U32 tg = (U32 )rt_target [idx ];
4226- if (!is_active [tg ]) { continue ; }
4227- U32 n = cursor [tg ];
4228- if (n > 0 && rev_adj [rev_off [tg ] + n - 1 ] == (U32 )ci ) { continue ; } // duplicate (C,T)
4229- rev_adj [rev_off [tg ] + n ] = (U32 )ci ;
4230- cursor [tg ] = n + 1 ;
4307+ {
4308+ U64 out = 0 ;
4309+ for (U64 e = 0 ; e < edge_raw ; e += 1 ) {
4310+ if (e > 0 && task .ek [e ] == task .ek [e - 1 ] && task .ev [e ] == task .ev [e - 1 ]) { continue ; }
4311+ rev_adj [out ++ ] = task .ev [e ];
42314312 }
42324313 }
42334314 scratch_end (scratch );
42344315 if (lnk_get_log_status (LNK_Log_Debug )) {
4235- U64 total_relocs = cand_count ? ((U64 )cands [cand_count - 1 ].reloc_first + cands [cand_count - 1 ].reloc_count ) : 0 ;
4236- lnk_log (LNK_Log_Debug , "/OPT:ICF worklist rev-index: relocs=%llu iscand=%llu kept=%llu (active-rows+dedup); rev_adj %llu -> %llu bytes" ,
4237- total_relocs , raw_iscand , edge_count , raw_iscand * sizeof (U32 ), edge_count * sizeof (U32 ));
4316+ U64 raw_iscand = 0 ; // instrumentation: unfiltered edge count (old rev_adj size); debug-log only
4317+ for EachIndex (ci , cand_count ) {
4318+ LNK_ICFCand * c = & cands [ci ];
4319+ for EachIndex (j , c -> reloc_count ) { raw_iscand += rt_iscand [(U64 )c -> reloc_first + j ]; }
4320+ }
4321+ lnk_log (LNK_Log_Debug , "/OPT:ICF worklist rev-index: relocs=%llu iscand=%llu filtered=%llu kept=%llu (active-rows+dedup); rev_adj %llu -> %llu bytes" ,
4322+ total_relocs , raw_iscand , edge_raw , edge_count , raw_iscand * sizeof (U32 ), edge_count * sizeof (U32 ));
42384323 }
42394324 * out_rev_off = rev_off ;
42404325 * out_rev_adj = rev_adj ;
@@ -4703,7 +4788,7 @@ lnk_opt_icf(TP_Context *tp, Arena *perm, LNK_SymbolTable *symtab, LNK_Config *co
47034788 U32 * active_prev = rs .active2 ;
47044789 U64 n_prev = rs .round_n ;
47054790 U32 * rev_off = 0 , * rev_adj = 0 ;
4706- lnk_icf_build_reverse_index (arena , cand_count , cands , rt_iscand , rt_target , active_prev , n_prev , & rev_off , & rev_adj );
4791+ lnk_icf_build_reverse_index (tp , arena , cand_count , cands , rt_iscand , rt_target , active_prev , n_prev , & rev_off , & rev_adj );
47074792 U64 revidx_end_us = now_time_us ();
47084793
47094794 // SEED: members of the classes that SPLIT in the region's final round. active_prev is grouped by
0 commit comments