-
Notifications
You must be signed in to change notification settings - Fork 50
Limit split count per original bundle with fallback 1-to-N split. #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
cfallin
merged 2 commits into
bytecodealliance:main
from
cfallin:only-a-little-bit-of-splitting
Jun 27, 2022
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,18 +15,18 @@ | |
| use super::{ | ||
| spill_weight_from_constraint, Env, LiveBundleIndex, LiveBundleVec, LiveRangeFlag, | ||
| LiveRangeIndex, LiveRangeKey, LiveRangeList, LiveRangeListEntry, PRegIndex, RegTraversalIter, | ||
| Requirement, SpillWeight, UseList, | ||
| Requirement, SpillWeight, UseList, VRegIndex, | ||
| }; | ||
| use crate::{ | ||
| ion::data_structures::{ | ||
| CodeRange, BUNDLE_MAX_NORMAL_SPILL_WEIGHT, MINIMAL_BUNDLE_SPILL_WEIGHT, | ||
| MINIMAL_FIXED_BUNDLE_SPILL_WEIGHT, | ||
| CodeRange, BUNDLE_MAX_NORMAL_SPILL_WEIGHT, MAX_SPLITS_PER_SPILLSET, | ||
| MINIMAL_BUNDLE_SPILL_WEIGHT, MINIMAL_FIXED_BUNDLE_SPILL_WEIGHT, | ||
| }, | ||
| Allocation, Function, Inst, InstPosition, OperandConstraint, OperandKind, PReg, ProgPoint, | ||
| RegAllocError, | ||
| }; | ||
| use fxhash::FxHashSet; | ||
| use smallvec::smallvec; | ||
| use smallvec::{smallvec, SmallVec}; | ||
| use std::fmt::Debug; | ||
|
|
||
| #[derive(Clone, Debug, PartialEq, Eq)] | ||
|
|
@@ -410,6 +410,16 @@ impl<'a, F: Function> Env<'a, F> { | |
|
|
||
| let spillset = self.bundles[bundle.index()].spillset; | ||
|
|
||
| // Have we reached the maximum split count? If so, fall back | ||
| // to a "minimal bundles and spill bundle" setup for this | ||
| // bundle. See the doc-comment on | ||
| // `split_into_minimal_bundles()` above for more. | ||
| if self.spillsets[spillset.index()].splits >= MAX_SPLITS_PER_SPILLSET { | ||
| self.split_into_minimal_bundles(bundle, reg_hint); | ||
| return; | ||
| } | ||
| self.spillsets[spillset.index()].splits += 1; | ||
|
|
||
| debug_assert!(!self.bundles[bundle.index()].ranges.is_empty()); | ||
| // Split point *at* start is OK; this means we peel off | ||
| // exactly one use to create a minimal bundle. | ||
|
|
@@ -511,6 +521,7 @@ impl<'a, F: Function> Env<'a, F> { | |
| self.bundles[bundle.index()] | ||
| .ranges | ||
| .truncate(last_lr_in_old_bundle_idx + 1); | ||
| self.bundles[bundle.index()].ranges.shrink_to_fit(); | ||
|
|
||
| // If the first entry in `new_lr_list` is a LR that is split | ||
| // down the middle, replace it with a new LR and chop off the | ||
|
|
@@ -537,6 +548,7 @@ impl<'a, F: Function> Env<'a, F> { | |
| .collect(); | ||
| self.ranges[new_lr.index()].uses = rest_uses; | ||
| self.ranges[orig_lr.index()].uses.truncate(first_use); | ||
| self.ranges[orig_lr.index()].uses.shrink_to_fit(); | ||
| self.recompute_range_properties(orig_lr); | ||
| self.recompute_range_properties(new_lr); | ||
| new_lr_list[0].index = new_lr; | ||
|
|
@@ -728,6 +740,247 @@ impl<'a, F: Function> Env<'a, F> { | |
| } | ||
| } | ||
|
|
||
| /// Splits the given bundle into minimal bundles per Use, falling | ||
| /// back onto the spill bundle. This must work for any bundle no | ||
| /// matter how many conflicts. | ||
| /// | ||
| /// This is meant to solve a quadratic-cost problem that exists | ||
| /// with "normal" splitting as implemented above. With that | ||
| /// procedure, , splitting a bundle produces two | ||
| /// halves. Furthermore, it has cost linear in the length of the | ||
| /// bundle, because the resulting half-bundles have their | ||
| /// requirements recomputed with a new scan, and because we copy | ||
| /// half the use-list over to the tail end sub-bundle. | ||
| /// | ||
| /// This works fine when a bundle has a handful of splits overall, | ||
| /// but not when an input has a systematic pattern of conflicts | ||
| /// that will require O(|bundle|) splits (e.g., every Use is | ||
| /// constrained to a different fixed register than the last | ||
| /// one). In such a case, we get quadratic behavior. | ||
| /// | ||
| /// This method implements a direct split into minimal bundles | ||
| /// along the whole length of the bundle, putting the regions | ||
| /// without uses in the spill bundle. We do this once the number | ||
| /// of splits in an original bundle (tracked by spillset) reaches | ||
| /// a pre-determined limit. | ||
| /// | ||
| /// This basically approximates what a non-splitting allocator | ||
| /// would do: it "spills" the whole bundle to possibly a | ||
| /// stackslot, or a second-chance register allocation at best, via | ||
| /// the spill bundle; and then does minimal reservations of | ||
| /// registers just at uses/defs and moves the "spilled" value | ||
| /// into/out of them immediately. | ||
| pub fn split_into_minimal_bundles(&mut self, bundle: LiveBundleIndex, reg_hint: PReg) { | ||
| let mut removed_lrs: FxHashSet<LiveRangeIndex> = FxHashSet::default(); | ||
| let mut removed_lrs_vregs: FxHashSet<VRegIndex> = FxHashSet::default(); | ||
| let mut new_lrs: SmallVec<[(VRegIndex, LiveRangeIndex); 16]> = smallvec![]; | ||
| let mut new_bundles: SmallVec<[LiveBundleIndex; 16]> = smallvec![]; | ||
|
|
||
| let spill = self | ||
| .get_or_create_spill_bundle(bundle, /* create_if_absent = */ true) | ||
| .unwrap(); | ||
|
|
||
| trace!( | ||
| "Splitting bundle {:?} into minimal bundles with reg hint {}", | ||
| bundle, | ||
| reg_hint | ||
| ); | ||
|
|
||
| let mut last_lr: Option<LiveRangeIndex> = None; | ||
| let mut last_bundle: Option<LiveBundleIndex> = None; | ||
| let mut last_inst: Option<Inst> = None; | ||
| let mut last_vreg: Option<VRegIndex> = None; | ||
|
|
||
| for entry_idx in 0..self.bundles[bundle.index()].ranges.len() { | ||
| // Iterate manually; don't borrow `self`. | ||
| let entry = self.bundles[bundle.index()].ranges[entry_idx]; | ||
|
|
||
| removed_lrs.insert(entry.index); | ||
| let vreg = self.ranges[entry.index.index()].vreg; | ||
| removed_lrs_vregs.insert(vreg); | ||
| trace!(" -> removing old LR {:?} for vreg {:?}", entry.index, vreg); | ||
|
|
||
| let mut last_live_pos = entry.range.from; | ||
| for use_idx in 0..self.ranges[entry.index.index()].uses.len() { | ||
| let u = self.ranges[entry.index.index()].uses[use_idx]; | ||
|
Comment on lines
+804
to
+805
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could also do the |
||
| trace!(" -> use {:?} (last_live_pos {:?})", u, last_live_pos); | ||
|
|
||
| // If we just created a LR for this inst at the last | ||
| // pos, add this use to the same LR. | ||
| if Some(u.pos.inst()) == last_inst && Some(vreg) == last_vreg { | ||
| self.ranges[last_lr.unwrap().index()].uses.push(u); | ||
| trace!(" -> appended to last LR {:?}", last_lr.unwrap()); | ||
| continue; | ||
| } | ||
|
|
||
| // If the last bundle was at the same inst, add a new | ||
| // LR to the same bundle; otherwise, create a LR and a | ||
| // new bundle. | ||
| if Some(u.pos.inst()) == last_inst { | ||
| let cr = CodeRange { | ||
| from: u.pos, | ||
| to: ProgPoint::before(u.pos.inst().next()), | ||
| }; | ||
| let lr = self.create_liverange(cr); | ||
| new_lrs.push((vreg, lr)); | ||
| self.ranges[lr.index()].uses.push(u); | ||
| self.ranges[lr.index()].vreg = vreg; | ||
|
|
||
| trace!( | ||
| " -> created new LR {:?} but adding to existing bundle {:?}", | ||
| lr, | ||
| last_bundle.unwrap() | ||
| ); | ||
| // Edit the previous LR to end mid-inst. | ||
| self.bundles[last_bundle.unwrap().index()] | ||
| .ranges | ||
| .last_mut() | ||
| .unwrap() | ||
| .range | ||
| .to = u.pos; | ||
| self.ranges[last_lr.unwrap().index()].range.to = u.pos; | ||
| // Add this LR to the bundle. | ||
| self.bundles[last_bundle.unwrap().index()] | ||
| .ranges | ||
| .push(LiveRangeListEntry { | ||
| range: cr, | ||
| index: lr, | ||
| }); | ||
| self.ranges[lr.index()].bundle = last_bundle.unwrap(); | ||
| last_live_pos = ProgPoint::before(u.pos.inst().next()); | ||
| continue; | ||
| } | ||
|
|
||
| // Otherwise, create a new LR. | ||
| let pos = ProgPoint::before(u.pos.inst()); | ||
| let cr = CodeRange { | ||
| from: pos, | ||
| to: ProgPoint::before(u.pos.inst().next()), | ||
| }; | ||
| let lr = self.create_liverange(cr); | ||
| new_lrs.push((vreg, lr)); | ||
| self.ranges[lr.index()].uses.push(u); | ||
| self.ranges[lr.index()].vreg = vreg; | ||
|
|
||
| // Create a new bundle that contains only this LR. | ||
| let new_bundle = self.create_bundle(); | ||
| self.ranges[lr.index()].bundle = new_bundle; | ||
| self.bundles[new_bundle.index()].spillset = self.bundles[bundle.index()].spillset; | ||
| self.bundles[new_bundle.index()] | ||
| .ranges | ||
| .push(LiveRangeListEntry { | ||
| range: cr, | ||
| index: lr, | ||
| }); | ||
| new_bundles.push(new_bundle); | ||
|
|
||
| // If this use was a Def, set the StartsAtDef flag for | ||
| // the new LR. (N.B.: *not* Mod, only Def, because Mod | ||
| // needs an input. This flag specifically indicates | ||
| // that the LR does not require the value to be moved | ||
| // into location at start because it (re)defines the | ||
| // value.) | ||
| if u.operand.kind() == OperandKind::Def { | ||
| self.ranges[lr.index()].set_flag(LiveRangeFlag::StartsAtDef); | ||
| } | ||
|
|
||
| trace!( | ||
| " -> created new LR {:?} range {:?} with new bundle {:?} for this use", | ||
| lr, | ||
| cr, | ||
| new_bundle | ||
| ); | ||
|
|
||
| // If there was any intervening range in the LR not | ||
| // covered by the minimal new LR above, add it to the | ||
| // spillset. | ||
| if pos > last_live_pos { | ||
| let cr = CodeRange { | ||
| from: last_live_pos, | ||
| to: pos, | ||
| }; | ||
| let spill_lr = self.create_liverange(cr); | ||
| self.ranges[spill_lr.index()].vreg = vreg; | ||
| self.ranges[spill_lr.index()].bundle = spill; | ||
| new_lrs.push((vreg, spill_lr)); | ||
| self.bundles[spill.index()].ranges.push(LiveRangeListEntry { | ||
| range: cr, | ||
| index: spill_lr, | ||
| }); | ||
| self.ranges[spill_lr.index()].bundle = spill; | ||
| trace!( | ||
| " -> put intervening range {:?} in new LR {:?} in spill bundle {:?}", | ||
| cr, | ||
| spill_lr, | ||
| spill | ||
| ); | ||
| } | ||
| last_live_pos = ProgPoint::before(u.pos.inst().next()); | ||
|
|
||
| last_lr = Some(lr); | ||
| last_bundle = Some(new_bundle); | ||
| last_inst = Some(u.pos.inst()); | ||
| last_vreg = Some(vreg); | ||
| } | ||
|
|
||
| // Clear the use-list from the original LR. | ||
| self.ranges[entry.index.index()].uses = Default::default(); | ||
|
|
||
| // If there is space from the last use to the end of the | ||
| // LR, put that in the spill bundle too. | ||
| if entry.range.to > last_live_pos { | ||
| let cr = CodeRange { | ||
| from: last_live_pos, | ||
| to: entry.range.to, | ||
| }; | ||
| let spill_lr = self.create_liverange(cr); | ||
| self.ranges[spill_lr.index()].vreg = vreg; | ||
| self.ranges[spill_lr.index()].bundle = spill; | ||
| new_lrs.push((vreg, spill_lr)); | ||
| self.bundles[spill.index()].ranges.push(LiveRangeListEntry { | ||
| range: cr, | ||
| index: spill_lr, | ||
| }); | ||
| self.ranges[spill_lr.index()].bundle = spill; | ||
| trace!( | ||
| " -> put trailing range {:?} in new LR {:?} in spill bundle {:?}", | ||
| cr, | ||
| spill_lr, | ||
| spill | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // Clear the LR list in the original bundle. | ||
| self.bundles[bundle.index()].ranges.clear(); | ||
| self.bundles[bundle.index()].ranges.shrink_to_fit(); | ||
|
|
||
| // Remove all of the removed LRs from respective vregs' lists. | ||
| for vreg in removed_lrs_vregs { | ||
| self.vregs[vreg.index()] | ||
| .ranges | ||
| .retain(|entry| !removed_lrs.contains(&entry.index)); | ||
| } | ||
|
|
||
| // Add the new LRs to their respective vreg lists. | ||
| for (vreg, lr) in new_lrs { | ||
| let range = self.ranges[lr.index()].range; | ||
| let entry = LiveRangeListEntry { range, index: lr }; | ||
| self.vregs[vreg.index()].ranges.push(entry); | ||
| } | ||
|
|
||
| // Recompute bundle properties for all new bundles and enqueue | ||
| // them. | ||
| for bundle in new_bundles { | ||
| if self.bundles[bundle.index()].ranges.len() > 0 { | ||
| self.recompute_bundle_properties(bundle); | ||
| let prio = self.bundles[bundle.index()].prio; | ||
| self.allocation_queue | ||
| .insert(bundle, prio as usize, reg_hint); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn process_bundle( | ||
| &mut self, | ||
| bundle: LiveBundleIndex, | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random stylistic comment but you can
impl<T> Index<MyLocalType> for [T]which could help change all the indexing here toself.bundles[bundle](or evenself[bundle]) rather thanself.bundles[bundle.index()]There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I briefly played with this but it seems we would need wrapper types around the
Vec<T>to make this work properly. I'd love to have this eventually but it's a bigger refactor (all use-sites across the allocator) than I want to bite off right now; I'll file an issue!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#62