From 59b9a1515b95db0789104a6e0319cf8ac50a3d66 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 6 Jul 2026 09:14:07 -0700 Subject: [PATCH] Make `Ctx` be `Send + Sync` so Cranelift and Wasmtime can reuse it --- src/ion/data_structures.rs | 137 +++++++++++++++++++++++++++++++++++-- src/ion/liveranges.rs | 4 +- src/ion/merge.rs | 2 +- src/ion/process.rs | 19 ++--- 4 files changed, 146 insertions(+), 16 deletions(-) diff --git a/src/ion/data_structures.rs b/src/ion/data_structures.rs index 41937a27..ba036a11 100644 --- a/src/ion/data_structures.rs +++ b/src/ion/data_structures.rs @@ -444,7 +444,71 @@ impl core::ops::IndexMut for VRegs { } } -#[derive(Default)] +/// An unsafe wrapper around a `T` that is always `Send + Sync`, regardless +/// whether `T` is or not. +#[repr(transparent)] +pub(crate) struct UnsafeSendSync { + inner: T, +} + +impl<'a, T> IntoIterator for &'a UnsafeSendSync +where + &'a T: IntoIterator, +{ + type Item = <&'a T as IntoIterator>::Item; + + type IntoIter = <&'a T as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + (&self.inner).into_iter() + } +} + +impl<'a, T> IntoIterator for &'a mut UnsafeSendSync +where + &'a mut T: IntoIterator, +{ + type Item = <&'a mut T as IntoIterator>::Item; + + type IntoIter = <&'a mut T as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + (&mut self.inner).into_iter() + } +} + +// SAFETY: upheld by the invariants callers of `UnsafeSendSync::new` must uphold +// in turn. +unsafe impl Send for UnsafeSendSync {} +unsafe impl Sync for UnsafeSendSync {} + +impl Deref for UnsafeSendSync { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for UnsafeSendSync { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl UnsafeSendSync { + /// Create a new `UnsafeSendSync`. + /// + /// # Safety + /// + /// The result is `Send` and `Sync` regardless whether `T: Send + Sync`. You + /// must ensure that the result is only used in a manner that is compatible + /// with whether `T` is `Send + Sync`. + pub unsafe fn new(inner: T) -> Self { + Self { inner } + } +} + pub struct Ctx { pub(crate) cfginfo: CFGInfo, pub(crate) cfginfo_ctx: CFGInfoCtx, @@ -453,10 +517,10 @@ pub struct Ctx { pub(crate) blockparam_outs: Vec, pub(crate) blockparam_ins: Vec, - pub(crate) ranges: LiveRanges, - pub(crate) bundles: LiveBundles, + pub(crate) ranges: UnsafeSendSync, + pub(crate) bundles: UnsafeSendSync, pub(crate) spillsets: SpillSets, - pub(crate) vregs: VRegs, + pub(crate) vregs: UnsafeSendSync, pub(crate) pregs: Vec, pub(crate) allocation_queue: PrioQueue, @@ -503,7 +567,70 @@ pub struct Ctx { pub(crate) scratch_removed_lrs_vregs: FxHashSet, pub(crate) scratch_workqueue_set: FxHashSet, - pub(crate) scratch_bump: Bump, + pub(crate) scratch_bump: UnsafeSendSync, +} + +const _ASSERT_SEND_SYNC: () = { + const fn assert_send_sync() {} + assert_send_sync::(); +}; + +impl Default for Ctx { + fn default() -> Self { + Self { + // SAFETY: These collections use a `Bump` allocator. `Bump` is not + // `Send + Sync` because it is a newtype of `Rc`, + // which leads to two problems: + // + // 1. `bumpalo::Bump` is `Send` but it is _not_ `Sync` (it internally uses a + // `Cell`), and + // + // 2. `Rc` is neither `Send` nor `Sync`. + // + // Nonetheless, we only ever touch a `Bump` (and every clone of it!) + // from a single thread at a time: all clones of a given `Bump` live + // inside one `Ctx`; a `Ctx` is created, mutated, and dropped by a + // single thread at a time; and the returned `Output` is + // `Bump`-free, so no clone escapes the `Ctx`. The `Bump` is + // therefore never shared between threads and its `Rc` clones are + // never split across threads, so treating `Bump` as `Send + Sync` + // is sound for our usage. + ranges: unsafe { UnsafeSendSync::new(Default::default()) }, + bundles: unsafe { UnsafeSendSync::new(Default::default()) }, + scratch_bump: unsafe { UnsafeSendSync::new(Default::default()) }, + vregs: unsafe { UnsafeSendSync::new(Default::default()) }, + + cfginfo: Default::default(), + cfginfo_ctx: Default::default(), + liveins: Default::default(), + liveouts: Default::default(), + blockparam_outs: Default::default(), + blockparam_ins: Default::default(), + spillsets: Default::default(), + pregs: Default::default(), + allocation_queue: Default::default(), + spilled_bundles: Default::default(), + spillslots: Default::default(), + slots_by_class: Default::default(), + extra_spillslots_by_class: Default::default(), + preferred_victim_by_class: Default::default(), + multi_fixed_reg_fixups: Default::default(), + allocated_bundle_count: Default::default(), + debug_annotations: Default::default(), + annotations_enabled: Default::default(), + conflict_set: Default::default(), + output: Default::default(), + scratch_conflicts: Default::default(), + scratch_bundle: Default::default(), + scratch_vreg_ranges: Default::default(), + scratch_spillset_pool: Default::default(), + scratch_workqueue: Default::default(), + scratch_operand_rewrites: Default::default(), + scratch_removed_lrs: Default::default(), + scratch_removed_lrs_vregs: Default::default(), + scratch_workqueue_set: Default::default(), + } + } } impl Ctx { diff --git a/src/ion/liveranges.rs b/src/ion/liveranges.rs index 449f2626..09040f6c 100644 --- a/src/ion/liveranges.rs +++ b/src/ion/liveranges.rs @@ -125,7 +125,7 @@ impl<'a, F: Function> Env<'a, F> { self.ctx.vregs.add( VReg::new(idx, RegClass::Int), VRegData { - ranges: LiveRangeList::new_in(self.ctx.bump()), + ranges: LiveRangeList::new_in(self.ctx.scratch_bump.clone()), blockparam: Block::invalid(), // We'll learn the RegClass as we scan the code. class: None, @@ -196,7 +196,7 @@ impl<'a, F: Function> Env<'a, F> { { // Is not contiguous with previously-added (immediately // following) range; create a new range. - let lr = self.ctx.ranges.add(range, self.ctx.bump()); + let lr = self.ctx.ranges.add(range, self.ctx.scratch_bump.clone()); self.ranges[lr].vreg = vreg; self.vregs[vreg] .ranges diff --git a/src/ion/merge.rs b/src/ion/merge.rs index f6e87d68..ff33ddc0 100644 --- a/src/ion/merge.rs +++ b/src/ion/merge.rs @@ -265,7 +265,7 @@ impl<'a, F: Function> Env<'a, F> { continue; } - let bundle = self.ctx.bundles.add(self.ctx.bump()); + let bundle = self.ctx.bundles.add(self.ctx.scratch_bump.clone()); let mut range = self.vregs[vreg].ranges.first().unwrap().range; self.bundles[bundle].ranges = self.vregs[vreg].ranges.clone(); diff --git a/src/ion/process.rs b/src/ion/process.rs index 94009191..a6e5db90 100644 --- a/src/ion/process.rs +++ b/src/ion/process.rs @@ -399,7 +399,7 @@ impl<'a, F: Function> Env<'a, F> { if idx.is_valid() { Some(idx) } else if create_if_absent { - let idx = self.ctx.bundles.add(self.ctx.bump()); + let idx = self.ctx.bundles.add(self.ctx.scratch_bump.clone()); self.ctx.spillsets[ssidx].spill_bundle = idx; self.ctx.bundles[idx].spillset = ssidx; self.ctx.spilled_bundles.push(idx); @@ -592,7 +592,7 @@ impl<'a, F: Function> Env<'a, F> { from: split_at, to: new_lr_list[0].range.to, }, - self.ctx.bump(), + self.ctx.scratch_bump.clone(), ); self.ctx.ranges[new_lr].vreg = self.ranges[orig_lr].vreg; trace!(" -> splitting LR {:?} into {:?}", orig_lr, new_lr); @@ -634,7 +634,7 @@ impl<'a, F: Function> Env<'a, F> { }); } - let new_bundle = self.ctx.bundles.add(self.ctx.bump()); + let new_bundle = self.ctx.bundles.add(self.ctx.scratch_bump.clone()); trace!(" -> creating new bundle {:?}", new_bundle); self.ctx.bundles[new_bundle].spillset = spillset; for entry in &new_lr_list { @@ -683,7 +683,7 @@ impl<'a, F: Function> Env<'a, F> { from: split, to: end, }; - let empty_lr = self.ctx.ranges.add(range, self.ctx.bump()); + let empty_lr = self.ctx.ranges.add(range, self.ctx.scratch_bump.clone()); self.ctx.bundles[spill].ranges.push(LiveRangeListEntry { range, index: empty_lr, @@ -748,7 +748,7 @@ impl<'a, F: Function> Env<'a, F> { from: start, to: split, }; - let empty_lr = self.ctx.ranges.add(range, self.ctx.bump()); + let empty_lr = self.ctx.ranges.add(range, self.ctx.scratch_bump.clone()); self.ctx.bundles[spill].ranges.push(LiveRangeListEntry { range, index: empty_lr, @@ -881,13 +881,13 @@ impl<'a, F: Function> Env<'a, F> { // Create a new LR. let cr = minimal_range_for_use(&u); - let lr = self.ctx.ranges.add(cr, self.ctx.bump()); + let lr = self.ctx.ranges.add(cr, self.ctx.scratch_bump.clone()); new_lrs.push((vreg, lr)); self.ctx.ranges[lr].uses.push(u); self.ctx.ranges[lr].vreg = vreg; // Create a new bundle that contains only this LR. - let new_bundle = self.ctx.bundles.add(self.ctx.bump()); + let new_bundle = self.ctx.bundles.add(self.ctx.scratch_bump.clone()); self.ctx.ranges[lr].bundle = new_bundle; self.ctx.bundles[new_bundle].spillset = spillset; self.ctx.bundles[new_bundle] @@ -915,7 +915,10 @@ impl<'a, F: Function> Env<'a, F> { // Make one entry in the spill bundle that covers the whole range. // TODO: it might be worth tracking enough state to only create this LR when there is // open space in the original LR. - let spill_lr = self.ctx.ranges.add(spill_range, self.ctx.bump()); + let spill_lr = self + .ctx + .ranges + .add(spill_range, self.ctx.scratch_bump.clone()); self.ctx.ranges[spill_lr].vreg = vreg; self.ctx.ranges[spill_lr].bundle = spill; self.ctx.ranges[spill_lr].uses.extend(spill_uses.drain(..));