Even if the trace log level is disabled, the presence of the trace! macro still has a significant impact on performance because it is present in the inner loops of the allocator. Removing the trace! calls at compile-time reduces instruction count by ~7%.
218 lines
8.3 KiB
Rust
218 lines
8.3 KiB
Rust
/*
|
|
* This file was initially derived from the files
|
|
* `js/src/jit/BacktrackingAllocator.h` and
|
|
* `js/src/jit/BacktrackingAllocator.cpp` in Mozilla Firefox, and was
|
|
* originally licensed under the Mozilla Public License 2.0. We
|
|
* subsequently relicensed it to Apache-2.0 WITH LLVM-exception (see
|
|
* https://github.com/bytecodealliance/regalloc2/issues/7).
|
|
*
|
|
* Since the initial port, the design has been substantially evolved
|
|
* and optimized.
|
|
*/
|
|
|
|
//! Spillslot allocation.
|
|
|
|
use super::{
|
|
AllocRegResult, Env, LiveRangeKey, LiveRangeSet, PReg, PRegIndex, RegClass, RegTraversalIter,
|
|
SpillSetIndex, SpillSlotData, SpillSlotIndex, SpillSlotList,
|
|
};
|
|
use crate::{Allocation, Function, SpillSlot};
|
|
|
|
impl<'a, F: Function> Env<'a, F> {
|
|
pub fn try_allocating_regs_for_spilled_bundles(&mut self) {
|
|
trace!("allocating regs for spilled bundles");
|
|
for i in 0..self.spilled_bundles.len() {
|
|
let bundle = self.spilled_bundles[i]; // don't borrow self
|
|
|
|
let class = self.spillsets[self.bundles[bundle.index()].spillset.index()].class;
|
|
let hint = self.spillsets[self.bundles[bundle.index()].spillset.index()].reg_hint;
|
|
|
|
// This may be an empty-range bundle whose ranges are not
|
|
// sorted; sort all range-lists again here.
|
|
self.bundles[bundle.index()]
|
|
.ranges
|
|
.sort_unstable_by_key(|entry| entry.range.from);
|
|
|
|
let mut success = false;
|
|
self.stats.spill_bundle_reg_probes += 1;
|
|
for preg in
|
|
RegTraversalIter::new(self.env, class, hint, PReg::invalid(), bundle.index(), None)
|
|
{
|
|
trace!("trying bundle {:?} to preg {:?}", bundle, preg);
|
|
let preg_idx = PRegIndex::new(preg.index());
|
|
if let AllocRegResult::Allocated(_) =
|
|
self.try_to_allocate_bundle_to_reg(bundle, preg_idx, None)
|
|
{
|
|
self.stats.spill_bundle_reg_success += 1;
|
|
success = true;
|
|
break;
|
|
}
|
|
}
|
|
if !success {
|
|
trace!(
|
|
"spilling bundle {:?}: marking spillset {:?} as required",
|
|
bundle,
|
|
self.bundles[bundle.index()].spillset
|
|
);
|
|
self.spillsets[self.bundles[bundle.index()].spillset.index()].required = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn spillslot_can_fit_spillset(
|
|
&mut self,
|
|
spillslot: SpillSlotIndex,
|
|
spillset: SpillSetIndex,
|
|
) -> bool {
|
|
for &vreg in &self.spillsets[spillset.index()].vregs {
|
|
for entry in &self.vregs[vreg.index()].ranges {
|
|
if self.spillslots[spillslot.index()]
|
|
.ranges
|
|
.btree
|
|
.contains_key(&LiveRangeKey::from_range(&entry.range))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
pub fn allocate_spillset_to_spillslot(
|
|
&mut self,
|
|
spillset: SpillSetIndex,
|
|
spillslot: SpillSlotIndex,
|
|
) {
|
|
self.spillsets[spillset.index()].slot = spillslot;
|
|
for i in 0..self.spillsets[spillset.index()].vregs.len() {
|
|
// don't borrow self
|
|
let vreg = self.spillsets[spillset.index()].vregs[i];
|
|
trace!(
|
|
"spillslot {:?} alloc'ed to spillset {:?}: vreg {:?}",
|
|
spillslot,
|
|
spillset,
|
|
vreg,
|
|
);
|
|
for entry in &self.vregs[vreg.index()].ranges {
|
|
trace!(
|
|
"spillslot {:?} getting range {:?} from LR {:?} from vreg {:?}",
|
|
spillslot,
|
|
entry.range,
|
|
entry.index,
|
|
vreg,
|
|
);
|
|
self.spillslots[spillslot.index()]
|
|
.ranges
|
|
.btree
|
|
.insert(LiveRangeKey::from_range(&entry.range), entry.index);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn allocate_spillslots(&mut self) {
|
|
for spillset in 0..self.spillsets.len() {
|
|
trace!("allocate spillslot: {}", spillset);
|
|
let spillset = SpillSetIndex::new(spillset);
|
|
if !self.spillsets[spillset.index()].required {
|
|
continue;
|
|
}
|
|
// Get or create the spillslot list for this size.
|
|
let size = self.spillsets[spillset.index()].size as usize;
|
|
if size >= self.slots_by_size.len() {
|
|
self.slots_by_size.resize(
|
|
size + 1,
|
|
SpillSlotList {
|
|
first_spillslot: SpillSlotIndex::invalid(),
|
|
last_spillslot: SpillSlotIndex::invalid(),
|
|
},
|
|
);
|
|
}
|
|
// Try a few existing spillslots.
|
|
let mut spillslot_iter = self.slots_by_size[size].first_spillslot;
|
|
let mut first_slot = SpillSlotIndex::invalid();
|
|
let mut prev = SpillSlotIndex::invalid();
|
|
let mut success = false;
|
|
for _attempt in 0..10 {
|
|
if spillslot_iter.is_invalid() {
|
|
break;
|
|
}
|
|
if spillslot_iter == first_slot {
|
|
// We've started looking at slots we placed at the end; end search.
|
|
break;
|
|
}
|
|
if first_slot.is_invalid() {
|
|
first_slot = spillslot_iter;
|
|
}
|
|
|
|
if self.spillslot_can_fit_spillset(spillslot_iter, spillset) {
|
|
self.allocate_spillset_to_spillslot(spillset, spillslot_iter);
|
|
success = true;
|
|
break;
|
|
}
|
|
// Remove the slot and place it at the end of the respective list.
|
|
let next = self.spillslots[spillslot_iter.index()].next_spillslot;
|
|
if prev.is_valid() {
|
|
self.spillslots[prev.index()].next_spillslot = next;
|
|
} else {
|
|
self.slots_by_size[size].first_spillslot = next;
|
|
}
|
|
if !next.is_valid() {
|
|
self.slots_by_size[size].last_spillslot = prev;
|
|
}
|
|
|
|
let last = self.slots_by_size[size].last_spillslot;
|
|
if last.is_valid() {
|
|
self.spillslots[last.index()].next_spillslot = spillslot_iter;
|
|
} else {
|
|
self.slots_by_size[size].first_spillslot = spillslot_iter;
|
|
}
|
|
self.slots_by_size[size].last_spillslot = spillslot_iter;
|
|
|
|
prev = spillslot_iter;
|
|
spillslot_iter = next;
|
|
}
|
|
|
|
if !success {
|
|
// Allocate a new spillslot.
|
|
let spillslot = SpillSlotIndex::new(self.spillslots.len());
|
|
let next = self.slots_by_size[size].first_spillslot;
|
|
self.spillslots.push(SpillSlotData {
|
|
ranges: LiveRangeSet::new(),
|
|
next_spillslot: next,
|
|
alloc: Allocation::none(),
|
|
class: self.spillsets[spillset.index()].class,
|
|
});
|
|
self.slots_by_size[size].first_spillslot = spillslot;
|
|
if !next.is_valid() {
|
|
self.slots_by_size[size].last_spillslot = spillslot;
|
|
}
|
|
|
|
self.allocate_spillset_to_spillslot(spillset, spillslot);
|
|
}
|
|
}
|
|
|
|
// Assign actual slot indices to spillslots.
|
|
for i in 0..self.spillslots.len() {
|
|
self.spillslots[i].alloc = self.allocate_spillslot(self.spillslots[i].class);
|
|
}
|
|
|
|
trace!("spillslot allocator done");
|
|
}
|
|
|
|
pub fn allocate_spillslot(&mut self, class: RegClass) -> Allocation {
|
|
let size = self.func.spillslot_size(class) as u32;
|
|
let mut offset = self.num_spillslots;
|
|
// Align up to `size`.
|
|
debug_assert!(size.is_power_of_two());
|
|
offset = (offset + size - 1) & !(size - 1);
|
|
let slot = if self.func.multi_spillslot_named_by_last_slot() {
|
|
offset + size - 1
|
|
} else {
|
|
offset
|
|
};
|
|
offset += size;
|
|
self.num_spillslots = offset;
|
|
Allocation::stack(SpillSlot::new(slot as usize, class))
|
|
}
|
|
}
|