Split based on first conflict of lowest-weight conflict, not first conflict. Also stop scanning PRegs when max bundle weight in conflict bundle list exceeds current best option.
This commit is contained in:
282
src/ion/mod.rs
282
src/ion/mod.rs
@@ -24,7 +24,10 @@
|
|||||||
|
|
||||||
- Split heuristics:
|
- Split heuristics:
|
||||||
- Loop depth at split point? Split before entering more nested loop
|
- Loop depth at split point? Split before entering more nested loop
|
||||||
- Split at earliest vs latest conflict -- study more
|
- In general, consider 'weight' of split point as if it were
|
||||||
|
another use.
|
||||||
|
|
||||||
|
- Add weight to bundles according to progmoves
|
||||||
|
|
||||||
- Reduced spilling when spillslot is still "clean":
|
- Reduced spilling when spillslot is still "clean":
|
||||||
- When we allocate spillsets, use the whole bundle of a given
|
- When we allocate spillsets, use the whole bundle of a given
|
||||||
@@ -44,12 +47,6 @@
|
|||||||
scan in a single range while resolving moves; in-edge makes
|
scan in a single range while resolving moves; in-edge makes
|
||||||
dirty.
|
dirty.
|
||||||
|
|
||||||
- Add weight to bundles according to progmoves
|
|
||||||
|
|
||||||
- Efficiency improvements:
|
|
||||||
- Record 'cheapest evict bundle so far' and stop scanning if
|
|
||||||
total evict cost exceeds that
|
|
||||||
|
|
||||||
- Avoid requiring two scratch regs:
|
- Avoid requiring two scratch regs:
|
||||||
- Require machine impl to be able to (i) push a reg, (ii) pop a
|
- Require machine impl to be able to (i) push a reg, (ii) pop a
|
||||||
reg; then generate a balanced pair of push/pop, using the stack
|
reg; then generate a balanced pair of push/pop, using the stack
|
||||||
@@ -611,6 +608,7 @@ enum AllocRegResult {
|
|||||||
Allocated(Allocation),
|
Allocated(Allocation),
|
||||||
Conflict(LiveBundleVec),
|
Conflict(LiveBundleVec),
|
||||||
ConflictWithFixed,
|
ConflictWithFixed,
|
||||||
|
ConflictHighCost,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
@@ -2425,9 +2423,14 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
bundle: LiveBundleIndex,
|
bundle: LiveBundleIndex,
|
||||||
reg: PRegIndex,
|
reg: PRegIndex,
|
||||||
|
// if the max bundle weight in the conflict set exceeds this
|
||||||
|
// cost (if provided), just return
|
||||||
|
// `AllocRegResult::ConflictHighCost`.
|
||||||
|
max_allowable_cost: Option<u32>,
|
||||||
) -> AllocRegResult {
|
) -> AllocRegResult {
|
||||||
log::debug!("try_to_allocate_bundle_to_reg: {:?} -> {:?}", bundle, reg);
|
log::debug!("try_to_allocate_bundle_to_reg: {:?} -> {:?}", bundle, reg);
|
||||||
let mut conflicts = smallvec![];
|
let mut conflicts = smallvec![];
|
||||||
|
let mut max_conflict_weight = 0;
|
||||||
// Traverse the BTreeMap in order by requesting the whole
|
// Traverse the BTreeMap in order by requesting the whole
|
||||||
// range spanned by the bundle and iterating over that
|
// range spanned by the bundle and iterating over that
|
||||||
// concurrently with our ranges. Because our ranges are in
|
// concurrently with our ranges. Because our ranges are in
|
||||||
@@ -2499,6 +2502,15 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
log::debug!(" -> conflict bundle {:?}", conflict_bundle);
|
log::debug!(" -> conflict bundle {:?}", conflict_bundle);
|
||||||
if !conflicts.iter().any(|b| *b == conflict_bundle) {
|
if !conflicts.iter().any(|b| *b == conflict_bundle) {
|
||||||
conflicts.push(conflict_bundle);
|
conflicts.push(conflict_bundle);
|
||||||
|
max_conflict_weight = std::cmp::max(
|
||||||
|
max_conflict_weight,
|
||||||
|
self.bundles[conflict_bundle.index()].cached_spill_weight(),
|
||||||
|
);
|
||||||
|
if max_allowable_cost.is_some()
|
||||||
|
&& max_conflict_weight > max_allowable_cost.unwrap()
|
||||||
|
{
|
||||||
|
return AllocRegResult::ConflictHighCost;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::debug!(" -> conflict with fixed reservation");
|
log::debug!(" -> conflict with fixed reservation");
|
||||||
@@ -2898,146 +2910,158 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
log::debug!("attempt {}, req {:?}", attempts, req);
|
log::debug!("attempt {}, req {:?}", attempts, req);
|
||||||
debug_assert!(attempts < 100 * self.func.insts());
|
debug_assert!(attempts < 100 * self.func.insts());
|
||||||
|
|
||||||
let (conflicting_bundles, latest_first_conflict_point, latest_first_conflict_reg) =
|
let (conflicting_bundles, first_conflict_point, first_conflict_reg) = match req {
|
||||||
match req {
|
Requirement::Fixed(preg) => {
|
||||||
Requirement::Fixed(preg) => {
|
let preg_idx = PRegIndex::new(preg.index());
|
||||||
|
self.stats.process_bundle_reg_probes_fixed += 1;
|
||||||
|
log::debug!("trying fixed reg {:?}", preg_idx);
|
||||||
|
match self.try_to_allocate_bundle_to_reg(bundle, preg_idx, None) {
|
||||||
|
AllocRegResult::Allocated(alloc) => {
|
||||||
|
self.stats.process_bundle_reg_success_fixed += 1;
|
||||||
|
log::debug!(" -> allocated to fixed {:?}", preg_idx);
|
||||||
|
self.spillsets[self.bundles[bundle.index()].spillset.index()]
|
||||||
|
.reg_hint = alloc.as_reg().unwrap();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
AllocRegResult::Conflict(bundles) => {
|
||||||
|
log::debug!(" -> conflict with bundles {:?}", bundles);
|
||||||
|
let first_bundle = bundles[0];
|
||||||
|
(
|
||||||
|
bundles,
|
||||||
|
self.bundles[first_bundle.index()].ranges[0].range.from,
|
||||||
|
preg,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AllocRegResult::ConflictWithFixed => {
|
||||||
|
log::debug!(" -> conflict with fixed alloc");
|
||||||
|
// Empty conflicts set: there's nothing we can
|
||||||
|
// evict, because fixed conflicts cannot be moved.
|
||||||
|
(
|
||||||
|
smallvec![],
|
||||||
|
ProgPoint::before(Inst::new(0)),
|
||||||
|
PReg::invalid(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AllocRegResult::ConflictHighCost => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Requirement::Register(class) => {
|
||||||
|
// Scan all pregs and attempt to allocate.
|
||||||
|
let mut lowest_cost_conflict_set: Option<LiveBundleVec> = None;
|
||||||
|
let mut lowest_cost_conflict_cost: Option<u32> = None;
|
||||||
|
let mut lowest_cost_conflict_point = ProgPoint::before(Inst::new(0));
|
||||||
|
let mut lowest_cost_conflict_reg = PReg::invalid();
|
||||||
|
|
||||||
|
// Heuristic: start the scan for an available
|
||||||
|
// register at an offset influenced both by our
|
||||||
|
// location in the code and by the bundle we're
|
||||||
|
// considering. This has the effect of spreading
|
||||||
|
// demand more evenly across registers.
|
||||||
|
let scan_offset = self.ranges
|
||||||
|
[self.bundles[bundle.index()].ranges[0].index.index()]
|
||||||
|
.range
|
||||||
|
.from
|
||||||
|
.inst()
|
||||||
|
.index()
|
||||||
|
+ bundle.index();
|
||||||
|
|
||||||
|
self.stats.process_bundle_reg_probe_start_any += 1;
|
||||||
|
for preg in RegTraversalIter::new(
|
||||||
|
self.env,
|
||||||
|
class,
|
||||||
|
hint_reg,
|
||||||
|
PReg::invalid(),
|
||||||
|
scan_offset,
|
||||||
|
) {
|
||||||
|
self.stats.process_bundle_reg_probes_any += 1;
|
||||||
let preg_idx = PRegIndex::new(preg.index());
|
let preg_idx = PRegIndex::new(preg.index());
|
||||||
self.stats.process_bundle_reg_probes_fixed += 1;
|
log::debug!("trying preg {:?}", preg_idx);
|
||||||
log::debug!("trying fixed reg {:?}", preg_idx);
|
|
||||||
match self.try_to_allocate_bundle_to_reg(bundle, preg_idx) {
|
match self.try_to_allocate_bundle_to_reg(
|
||||||
|
bundle,
|
||||||
|
preg_idx,
|
||||||
|
lowest_cost_conflict_cost,
|
||||||
|
) {
|
||||||
AllocRegResult::Allocated(alloc) => {
|
AllocRegResult::Allocated(alloc) => {
|
||||||
self.stats.process_bundle_reg_success_fixed += 1;
|
self.stats.process_bundle_reg_success_any += 1;
|
||||||
log::debug!(" -> allocated to fixed {:?}", preg_idx);
|
log::debug!(" -> allocated to any {:?}", preg_idx);
|
||||||
self.spillsets[self.bundles[bundle.index()].spillset.index()]
|
self.spillsets[self.bundles[bundle.index()].spillset.index()]
|
||||||
.reg_hint = alloc.as_reg().unwrap();
|
.reg_hint = alloc.as_reg().unwrap();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
AllocRegResult::Conflict(bundles) => {
|
AllocRegResult::Conflict(bundles) => {
|
||||||
log::debug!(" -> conflict with bundles {:?}", bundles);
|
log::debug!(" -> conflict with bundles {:?}", bundles);
|
||||||
let first_bundle = bundles[0];
|
|
||||||
(
|
let first_conflict_point =
|
||||||
bundles,
|
self.bundles[bundles[0].index()].ranges[0].range.from;
|
||||||
self.bundles[first_bundle.index()].ranges[0].range.from,
|
|
||||||
preg,
|
let cost = self.maximum_spill_weight_in_bundle_set(&bundles);
|
||||||
)
|
|
||||||
|
if lowest_cost_conflict_cost.is_none() {
|
||||||
|
lowest_cost_conflict_cost = Some(cost);
|
||||||
|
lowest_cost_conflict_set = Some(bundles);
|
||||||
|
lowest_cost_conflict_point = first_conflict_point;
|
||||||
|
lowest_cost_conflict_reg = preg;
|
||||||
|
} else if cost < lowest_cost_conflict_cost.unwrap() {
|
||||||
|
lowest_cost_conflict_cost = Some(cost);
|
||||||
|
lowest_cost_conflict_set = Some(bundles);
|
||||||
|
lowest_cost_conflict_point = first_conflict_point;
|
||||||
|
lowest_cost_conflict_reg = preg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
AllocRegResult::ConflictWithFixed => {
|
AllocRegResult::ConflictWithFixed => {
|
||||||
log::debug!(" -> conflict with fixed alloc");
|
log::debug!(" -> conflict with fixed alloc");
|
||||||
// Empty conflicts set: there's nothing we can
|
// Simply don't consider as an option.
|
||||||
// evict, because fixed conflicts cannot be moved.
|
}
|
||||||
(
|
AllocRegResult::ConflictHighCost => {
|
||||||
smallvec![],
|
// Simply don't consider -- we already have
|
||||||
ProgPoint::before(Inst::new(0)),
|
// a lower-cost conflict bundle option
|
||||||
PReg::invalid(),
|
// to evict.
|
||||||
)
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Requirement::Register(class) => {
|
|
||||||
// Scan all pregs and attempt to allocate.
|
|
||||||
let mut lowest_cost_conflict_set: Option<LiveBundleVec> = None;
|
|
||||||
let mut latest_first_conflict_point = ProgPoint::before(Inst::new(0));
|
|
||||||
let mut latest_first_conflict_reg = PReg::invalid();
|
|
||||||
|
|
||||||
// Heuristic: start the scan for an available
|
// Otherwise, we *require* a register, but didn't fit into
|
||||||
// register at an offset influenced both by our
|
// any with current bundle assignments. Hence, we will need
|
||||||
// location in the code and by the bundle we're
|
// to either split or attempt to evict some bundles. Return
|
||||||
// considering. This has the effect of spreading
|
// the conflicting bundles to evict and retry. Empty list
|
||||||
// demand more evenly across registers.
|
// means nothing to try (due to fixed conflict) so we must
|
||||||
let scan_offset = self.ranges
|
// split instead.
|
||||||
[self.bundles[bundle.index()].ranges[0].index.index()]
|
(
|
||||||
.range
|
lowest_cost_conflict_set.unwrap_or(smallvec![]),
|
||||||
.from
|
lowest_cost_conflict_point,
|
||||||
.inst()
|
lowest_cost_conflict_reg,
|
||||||
.index()
|
)
|
||||||
+ bundle.index();
|
}
|
||||||
|
|
||||||
self.stats.process_bundle_reg_probe_start_any += 1;
|
Requirement::Stack(_) => {
|
||||||
for preg in RegTraversalIter::new(
|
// If we must be on the stack, put ourselves on
|
||||||
self.env,
|
// the spillset's list immediately.
|
||||||
class,
|
self.spillsets[self.bundles[bundle.index()].spillset.index()]
|
||||||
hint_reg,
|
.bundles
|
||||||
PReg::invalid(),
|
.push(bundle);
|
||||||
scan_offset,
|
return Ok(());
|
||||||
) {
|
}
|
||||||
self.stats.process_bundle_reg_probes_any += 1;
|
|
||||||
let preg_idx = PRegIndex::new(preg.index());
|
|
||||||
log::debug!("trying preg {:?}", preg_idx);
|
|
||||||
match self.try_to_allocate_bundle_to_reg(bundle, preg_idx) {
|
|
||||||
AllocRegResult::Allocated(alloc) => {
|
|
||||||
self.stats.process_bundle_reg_success_any += 1;
|
|
||||||
log::debug!(" -> allocated to any {:?}", preg_idx);
|
|
||||||
self.spillsets[self.bundles[bundle.index()].spillset.index()]
|
|
||||||
.reg_hint = alloc.as_reg().unwrap();
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
AllocRegResult::Conflict(bundles) => {
|
|
||||||
log::debug!(" -> conflict with bundles {:?}", bundles);
|
|
||||||
|
|
||||||
let first_conflict_point =
|
Requirement::Any(_) | Requirement::Unknown => {
|
||||||
self.bundles[bundles[0].index()].ranges[0].range.from;
|
// If a register is not *required*, spill now (we'll retry
|
||||||
if first_conflict_point > latest_first_conflict_point {
|
// allocation on spilled bundles later).
|
||||||
latest_first_conflict_point = first_conflict_point;
|
log::debug!("spilling bundle {:?} to spilled_bundles list", bundle);
|
||||||
latest_first_conflict_reg = preg;
|
self.spilled_bundles.push(bundle);
|
||||||
}
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if lowest_cost_conflict_set.is_none() {
|
Requirement::Conflict => {
|
||||||
lowest_cost_conflict_set = Some(bundles);
|
break;
|
||||||
} else if self.maximum_spill_weight_in_bundle_set(&bundles)
|
}
|
||||||
< self.maximum_spill_weight_in_bundle_set(
|
};
|
||||||
lowest_cost_conflict_set.as_ref().unwrap(),
|
|
||||||
)
|
|
||||||
{
|
|
||||||
lowest_cost_conflict_set = Some(bundles);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AllocRegResult::ConflictWithFixed => {
|
|
||||||
log::debug!(" -> conflict with fixed alloc");
|
|
||||||
// Simply don't consider as an option.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we *require* a register, but didn't fit into
|
|
||||||
// any with current bundle assignments. Hence, we will need
|
|
||||||
// to either split or attempt to evict some bundles. Return
|
|
||||||
// the conflicting bundles to evict and retry. Empty list
|
|
||||||
// means nothing to try (due to fixed conflict) so we must
|
|
||||||
// split instead.
|
|
||||||
(
|
|
||||||
lowest_cost_conflict_set.unwrap_or(smallvec![]),
|
|
||||||
latest_first_conflict_point,
|
|
||||||
latest_first_conflict_reg,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Requirement::Stack(_) => {
|
|
||||||
// If we must be on the stack, put ourselves on
|
|
||||||
// the spillset's list immediately.
|
|
||||||
self.spillsets[self.bundles[bundle.index()].spillset.index()]
|
|
||||||
.bundles
|
|
||||||
.push(bundle);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Requirement::Any(_) | Requirement::Unknown => {
|
|
||||||
// If a register is not *required*, spill now (we'll retry
|
|
||||||
// allocation on spilled bundles later).
|
|
||||||
log::debug!("spilling bundle {:?} to spilled_bundles list", bundle);
|
|
||||||
self.spilled_bundles.push(bundle);
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Requirement::Conflict => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
log::debug!(" -> conflict set {:?}", conflicting_bundles);
|
log::debug!(" -> conflict set {:?}", conflicting_bundles);
|
||||||
log::debug!(
|
log::debug!(
|
||||||
" -> latest first conflict {:?} with reg {:?}",
|
" -> first conflict {:?} with reg {:?}",
|
||||||
latest_first_conflict_point,
|
first_conflict_point,
|
||||||
latest_first_conflict_reg
|
first_conflict_reg
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we have already tried evictions once before and are
|
// If we have already tried evictions once before and are
|
||||||
@@ -3053,8 +3077,8 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let bundle_start = self.bundles[bundle.index()].ranges[0].range.from;
|
let bundle_start = self.bundles[bundle.index()].ranges[0].range.from;
|
||||||
split_at_point = std::cmp::max(latest_first_conflict_point, bundle_start);
|
split_at_point = std::cmp::max(first_conflict_point, bundle_start);
|
||||||
requeue_with_reg = latest_first_conflict_reg;
|
requeue_with_reg = first_conflict_reg;
|
||||||
|
|
||||||
// If the maximum spill weight in the conflicting-bundles set is >= this bundle's spill
|
// If the maximum spill weight in the conflicting-bundles set is >= this bundle's spill
|
||||||
// weight, then don't evict.
|
// weight, then don't evict.
|
||||||
@@ -3145,7 +3169,7 @@ impl<'a, F: Function> Env<'a, F> {
|
|||||||
log::debug!("trying bundle {:?} to preg {:?}", bundle, preg);
|
log::debug!("trying bundle {:?} to preg {:?}", bundle, preg);
|
||||||
let preg_idx = PRegIndex::new(preg.index());
|
let preg_idx = PRegIndex::new(preg.index());
|
||||||
if let AllocRegResult::Allocated(_) =
|
if let AllocRegResult::Allocated(_) =
|
||||||
self.try_to_allocate_bundle_to_reg(bundle, preg_idx)
|
self.try_to_allocate_bundle_to_reg(bundle, preg_idx, None)
|
||||||
{
|
{
|
||||||
self.stats.spill_bundle_reg_success += 1;
|
self.stats.spill_bundle_reg_success += 1;
|
||||||
success = true;
|
success = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user