From b07552890931199fcbcb2146e6d73786c6ab6afb Mon Sep 17 00:00:00 2001 From: Michael Weigelt Date: Fri, 5 Jun 2026 17:35:57 +0000 Subject: [PATCH 1/3] initial change --- src/ion/data_structures.rs | 54 +++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/src/ion/data_structures.rs b/src/ion/data_structures.rs index 41937a27..41b8ad30 100644 --- a/src/ion/data_structures.rs +++ b/src/ion/data_structures.rs @@ -444,6 +444,53 @@ impl core::ops::IndexMut for VRegs { } } +/// A dedup set of `LiveBundleIndex` values that avoids hashing. +/// +/// Each bundle index is mapped to a slot in a dense array holding the +/// generation at which it was last inserted. `clear` simply bumps the +/// current generation (O(1) in the common case), and `insert` is a +/// single bounds-checked compare-and-store. This is a drop-in +/// replacement for the previous `FxHashSet` that is +/// much cheaper when a bundle conflicts with many others (e.g. a +/// function with many locals). +#[derive(Clone, Debug, Default)] +pub struct ConflictSet { + // Generation at which each bundle was last inserted. The value `0` + // means "never inserted"; `generation` is therefore always >= 1 + // after the first `clear`. + stamps: Vec, + generation: u32, +} + +impl ConflictSet { + /// Empty the set. O(1) except on the rare generation wraparound. + #[inline] + pub fn clear(&mut self) { + self.generation = self.generation.wrapping_add(1); + if self.generation == 0 { + // Wrapped around; reset stamps so stale entries don't read + // as present, and skip the reserved `0` generation. + self.stamps.iter_mut().for_each(|s| *s = 0); + self.generation = 1; + } + } + + /// Insert `bundle`. Returns `true` if it was not already present. + #[inline] + pub fn insert(&mut self, bundle: LiveBundleIndex) -> bool { + let idx = bundle.index(); + if idx >= self.stamps.len() { + self.stamps.resize(idx + 1, 0); + } + if self.stamps[idx] == self.generation { + false + } else { + self.stamps[idx] = self.generation; + true + } + } +} + #[derive(Default)] pub struct Ctx { pub(crate) cfginfo: CFGInfo, @@ -484,9 +531,10 @@ pub struct Ctx { pub(crate) debug_annotations: FxHashMap>, pub(crate) annotations_enabled: bool, - // Cached allocation for `try_to_allocate_bundle_to_reg` to avoid allocating - // a new HashSet on every call. - pub(crate) conflict_set: FxHashSet, + // Scratch dedup set for `try_to_allocate_bundle_to_reg`, reused + // across calls. Uses generation stamping to avoid hashing and to + // make clearing O(1). + pub(crate) conflict_set: ConflictSet, // Output: pub output: Output, From ad389a2e5f88fdc8290b57d44036fa92582f26c9 Mon Sep 17 00:00:00 2001 From: Michael Weigelt Date: Fri, 5 Jun 2026 17:41:31 +0000 Subject: [PATCH 2/3] same-as-last bundle --- src/ion/process.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ion/process.rs b/src/ion/process.rs index 94009191..35f8bb0c 100644 --- a/src/ion/process.rs +++ b/src/ion/process.rs @@ -94,6 +94,7 @@ impl<'a, F: Function> Env<'a, F> { self.ctx.pregs[reg.index()].allocations.btree ); let mut first_conflict: Option = None; + let mut last_conflict_bundle: Option = None; 'ranges: for entry in bundle_ranges { trace!(" -> range LR {:?}: {:?}", entry.index, entry.range); @@ -158,6 +159,12 @@ impl<'a, F: Function> Env<'a, F> { // conflicts list. let conflict_bundle = self.ctx.ranges[*preg_range].bundle; trace!(" -> conflict bundle {:?}", conflict_bundle); + // Adjacent preg ranges very often belong to the same + // bundle; skip the dedup-set lookup on repeats. + if last_conflict_bundle == Some(conflict_bundle) { + continue 'alloc; + } + last_conflict_bundle = Some(conflict_bundle); if self.ctx.conflict_set.insert(conflict_bundle) { conflicts.push(conflict_bundle); max_conflict_weight = core::cmp::max( From 14c22e7e38a66f0d18138d7f6afa40967387bd62 Mon Sep 17 00:00:00 2001 From: Michael Weigelt Date: Tue, 9 Jun 2026 13:32:41 +0000 Subject: [PATCH 3/3] u64 --- src/ion/data_structures.rs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/ion/data_structures.rs b/src/ion/data_structures.rs index 41b8ad30..87912140 100644 --- a/src/ion/data_structures.rs +++ b/src/ion/data_structures.rs @@ -447,32 +447,27 @@ impl core::ops::IndexMut for VRegs { /// A dedup set of `LiveBundleIndex` values that avoids hashing. /// /// Each bundle index is mapped to a slot in a dense array holding the -/// generation at which it was last inserted. `clear` simply bumps the -/// current generation (O(1) in the common case), and `insert` is a -/// single bounds-checked compare-and-store. This is a drop-in -/// replacement for the previous `FxHashSet` that is -/// much cheaper when a bundle conflicts with many others (e.g. a -/// function with many locals). +/// generation at which it was last inserted. `clear` bumps the current +/// generation and `insert` updates the stamp for the given bundle and +/// resizes the array if necessary. +/// This is a drop-in replacement for the previous `FxHashSet` +/// that is much cheaper when a bundle conflicts with many others (e.g. for +/// functions with many locals). #[derive(Clone, Debug, Default)] pub struct ConflictSet { // Generation at which each bundle was last inserted. The value `0` // means "never inserted"; `generation` is therefore always >= 1 // after the first `clear`. - stamps: Vec, - generation: u32, + stamps: Vec, + generation: u64, } impl ConflictSet { - /// Empty the set. O(1) except on the rare generation wraparound. + /// Empty the set by bumping the generation. + /// Every value below the new generation is now stale. #[inline] pub fn clear(&mut self) { - self.generation = self.generation.wrapping_add(1); - if self.generation == 0 { - // Wrapped around; reset stamps so stale entries don't read - // as present, and skip the reserved `0` generation. - self.stamps.iter_mut().for_each(|s| *s = 0); - self.generation = 1; - } + self.generation += 1; } /// Insert `bundle`. Returns `true` if it was not already present.