From 317dc2075b09bddd2322e003d86791d9461243e4 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Fri, 12 Dec 2025 16:40:30 -0800 Subject: [PATCH] Evict larger limits first using spill weights Consider an instruction that could potentially overallocate to a limited range of registers: ``` Use: v0i limit(0..=1), Use: v0i limit(0..=1), Use: v1i limit(0..=7) ``` Both uses of `v0` _must_ fit in registers 0-1 and `v1` should find its home somewhere else in registers 2-7. But fuzzing found a case where this failed. If `v1` was allocated first--say to register 0--and one of the uses of `v0` allocated to the remaining register--register 1--the final use of `v0` had nowhere to go _and_ could not evict either of the previous allocations. This caused `ion` to fail with `TooManyLiveRegs` when in fact a solution was possible. (Why are the identical uses of `v0` allocated separately? Can't they use the same register? I'm not entirely sure, but I think `ion` had split things down to minimal bundles). The fix in this change is to alter the default spill weights assigned to a minimal bundle. Previously, minimal bundles with a fixed constraint received the highest weight, followed by other minimal bundles and then non-minimal bundles. This reserves weights for limits into that order: - minimal with `fixed` constraint - minimal with `limit(0..=0)` constraint - minimal with `limit(0..=1)` constraint - ... - minimal with `limit(0..=255)` constraint - minimal, any other constraint - non-minimal Doing this allows `ion` to evict bundles with higher limits (e.g., `v1i limit(0..=7)` once they become minimal, allowing allocation to continue. Co-authored-by: Chris Fallin Co-authored-by: Rahul Chaphalkar --- src/ion/data_structures.rs | 5 +++-- src/ion/process.rs | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ion/data_structures.rs b/src/ion/data_structures.rs index 99fd7b1f..41937a27 100644 --- a/src/ion/data_structures.rs +++ b/src/ion/data_structures.rs @@ -208,8 +208,9 @@ pub struct LiveBundle { pub const BUNDLE_MAX_SPILL_WEIGHT: u32 = (1 << 28) - 1; pub const MINIMAL_FIXED_BUNDLE_SPILL_WEIGHT: u32 = BUNDLE_MAX_SPILL_WEIGHT; -pub const MINIMAL_BUNDLE_SPILL_WEIGHT: u32 = BUNDLE_MAX_SPILL_WEIGHT - 1; -pub const BUNDLE_MAX_NORMAL_SPILL_WEIGHT: u32 = BUNDLE_MAX_SPILL_WEIGHT - 2; +pub const MINIMAL_LIMITED_BUNDLE_SPILL_WEIGHT: u32 = BUNDLE_MAX_SPILL_WEIGHT - 1; +pub const MINIMAL_BUNDLE_SPILL_WEIGHT: u32 = MINIMAL_LIMITED_BUNDLE_SPILL_WEIGHT - 256; +pub const BUNDLE_MAX_NORMAL_SPILL_WEIGHT: u32 = MINIMAL_BUNDLE_SPILL_WEIGHT - 1; impl LiveBundle { #[inline(always)] diff --git a/src/ion/process.rs b/src/ion/process.rs index 8c5a3d0a..a3cbc05a 100644 --- a/src/ion/process.rs +++ b/src/ion/process.rs @@ -21,6 +21,7 @@ use crate::{ ion::data_structures::{ CodeRange, Use, BUNDLE_MAX_NORMAL_SPILL_WEIGHT, MAX_SPLITS_PER_SPILLSET, MINIMAL_BUNDLE_SPILL_WEIGHT, MINIMAL_FIXED_BUNDLE_SPILL_WEIGHT, + MINIMAL_LIMITED_BUNDLE_SPILL_WEIGHT, }, Allocation, Function, Inst, InstPosition, OperandConstraint, OperandKind, PReg, ProgPoint, RegAllocError, @@ -325,6 +326,9 @@ impl<'a, F: Function> Env<'a, F> { if fixed { trace!(" -> fixed and minimal"); MINIMAL_FIXED_BUNDLE_SPILL_WEIGHT + } else if let Some(limit) = self.ctx.bundles[bundle].limit { + trace!(" -> limited({limit}) and minimal"); + MINIMAL_LIMITED_BUNDLE_SPILL_WEIGHT - u32::from(limit) } else { trace!(" -> non-fixed and minimal"); MINIMAL_BUNDLE_SPILL_WEIGHT