Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions src/ion/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,48 @@ impl core::ops::IndexMut<VReg> 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` 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<LiveBundleIndex>`
/// 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<u64>,
generation: u64,
}

impl ConflictSet {
/// Empty the set by bumping the generation.
/// Every value below the new generation is now stale.
#[inline]
pub fn clear(&mut self) {
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,
Expand Down Expand Up @@ -484,9 +526,10 @@ pub struct Ctx {
pub(crate) debug_annotations: FxHashMap<ProgPoint, Vec<String>>,
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<LiveBundleIndex>,
// 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,
Expand Down
7 changes: 7 additions & 0 deletions src/ion/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ impl<'a, F: Function> Env<'a, F> {
self.ctx.pregs[reg.index()].allocations.btree
);
let mut first_conflict: Option<ProgPoint> = None;
let mut last_conflict_bundle: Option<LiveBundleIndex> = None;

'ranges: for entry in bundle_ranges {
trace!(" -> range LR {:?}: {:?}", entry.index, entry.range);
Expand Down Expand Up @@ -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(
Expand Down
Loading