Skip to content

Commit 49c54b6

Browse files
committed
Misc usability and functionality enhancements:
- Support preferred and non-preferred subsets of a register class. This allows allocating, e.g., caller-saved registers before callee-saved registers. - Allow branch blockparam args to start an a certain offset in branch operands; this allows branches to have other operands too (e.g., conditional-branch inputs). - Allow `OperandOrAllocation` to be constructed from an `Allocation` and `OperandKind` as well (i.e., an allocation with an use/def bit).
1 parent 414f3f8 commit 49c54b6

File tree

3 files changed

+104
-44
lines changed

3 files changed

+104
-44
lines changed

src/fuzzing/func.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ impl Function for Func {
116116
self.insts[insn.index()].op == InstOpcode::Branch
117117
}
118118

119+
fn branch_blockparam_arg_offset(&self, _: Block, _: Inst) -> usize {
120+
// Branch blockparam args always start at zero for this
121+
// Function implementation.
122+
0
123+
}
124+
119125
fn is_safepoint(&self, insn: Inst) -> bool {
120126
self.insts[insn.index()].is_safepoint
121127
}
@@ -576,12 +582,16 @@ impl std::fmt::Debug for Func {
576582
pub fn machine_env() -> MachineEnv {
577583
// Reg 31 is the scratch reg.
578584
let regs: Vec<PReg> = (0..31).map(|i| PReg::new(i, RegClass::Int)).collect();
579-
let regs_by_class: Vec<Vec<PReg>> = vec![regs.clone(), vec![]];
585+
let preferred_regs_by_class: Vec<Vec<PReg>> =
586+
vec![regs.iter().cloned().take(24).collect(), vec![]];
587+
let non_preferred_regs_by_class: Vec<Vec<PReg>> =
588+
vec![regs.iter().cloned().skip(24).collect(), vec![]];
580589
let scratch_by_class: Vec<PReg> =
581590
vec![PReg::new(31, RegClass::Int), PReg::new(0, RegClass::Float)];
582591
MachineEnv {
583592
regs,
584-
regs_by_class,
593+
preferred_regs_by_class,
594+
non_preferred_regs_by_class,
585595
scratch_by_class,
586596
}
587597
}

src/ion/mod.rs

Lines changed: 67 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,69 @@ pub struct Stats {
560560
edits_count: usize,
561561
}
562562

563+
/// This iterator represents a traversal through all allocatable
564+
/// registers of a given class, in a certain order designed to
565+
/// minimize allocation contention.
566+
///
567+
/// The order in which we try registers is somewhat complex:
568+
/// - First, if there is a hint, we try that.
569+
/// - Then, we try registers in a traversal order that is based on an
570+
/// "offset" (usually the bundle index) spreading pressure evenly
571+
/// among registers to reduce commitment-map contention.
572+
/// - Within that scan, we try registers in two groups: first,
573+
/// prferred registers; then, non-preferred registers. (In normal
574+
/// usage, these consist of caller-save and callee-save registers
575+
/// respectively, to minimize clobber-saves; but they need not.)
576+
struct RegTraversalIter<'a> {
577+
env: &'a MachineEnv,
578+
class: usize,
579+
hint_reg: Option<PReg>,
580+
pref_idx: usize,
581+
non_pref_idx: usize,
582+
offset: usize,
583+
}
584+
585+
impl<'a> RegTraversalIter<'a> {
586+
pub fn new(
587+
env: &'a MachineEnv,
588+
class: RegClass,
589+
hint_reg: Option<PReg>,
590+
offset: usize,
591+
) -> Self {
592+
Self {
593+
env,
594+
class: class as u8 as usize,
595+
hint_reg,
596+
pref_idx: 0,
597+
non_pref_idx: 0,
598+
offset,
599+
}
600+
}
601+
}
602+
603+
impl<'a> std::iter::Iterator for RegTraversalIter<'a> {
604+
type Item = PReg;
605+
606+
fn next(&mut self) -> Option<PReg> {
607+
if let Some(preg) = self.hint_reg.take() {
608+
return Some(preg);
609+
}
610+
if self.pref_idx < self.env.preferred_regs_by_class[self.class].len() {
611+
let arr = &self.env.preferred_regs_by_class[self.class][..];
612+
let r = arr[(self.pref_idx + self.offset) % arr.len()];
613+
self.pref_idx += 1;
614+
return Some(r);
615+
}
616+
if self.non_pref_idx < self.env.non_preferred_regs_by_class[self.class].len() {
617+
let arr = &self.env.non_preferred_regs_by_class[self.class][..];
618+
let r = arr[(self.non_pref_idx + self.offset) % arr.len()];
619+
self.non_pref_idx += 1;
620+
return Some(r);
621+
}
622+
None
623+
}
624+
}
625+
563626
impl<'a, F: Function> Env<'a, F> {
564627
pub(crate) fn new(func: &'a F, env: &'a MachineEnv, cfginfo: CFGInfo) -> Self {
565628
Self {
@@ -987,7 +1050,7 @@ impl<'a, F: Function> Env<'a, F> {
9871050
// return), create blockparam_out entries.
9881051
if self.func.is_branch(insns.last()) {
9891052
let operands = self.func.inst_operands(insns.last());
990-
let mut i = 0;
1053+
let mut i = self.func.branch_blockparam_arg_offset(block, insns.last());
9911054
for &succ in self.func.block_succs(block) {
9921055
for &blockparam in self.func.block_params(succ) {
9931056
let from_vreg = VRegIndex::new(operands[i].vreg().vreg());
@@ -2671,12 +2734,7 @@ impl<'a, F: Function> Env<'a, F> {
26712734
Requirement::Register(class) => {
26722735
// Scan all pregs and attempt to allocate.
26732736
let mut lowest_cost_conflict_set: Option<LiveBundleVec> = None;
2674-
let n_regs = self.env.regs_by_class[class as u8 as usize].len();
2675-
let loop_count = if hint_reg.is_some() {
2676-
n_regs + 1
2677-
} else {
2678-
n_regs
2679-
};
2737+
26802738
// Heuristic: start the scan for an available
26812739
// register at an offset influenced both by our
26822740
// location in the code and by the bundle we're
@@ -2688,35 +2746,8 @@ impl<'a, F: Function> Env<'a, F> {
26882746
.inst
26892747
.index()
26902748
+ bundle.index();
2691-
for i in 0..loop_count {
2692-
// The order in which we try registers is somewhat complex:
2693-
// - First, if there is a hint, we try that.
2694-
// - Then, we try registers in a traversal
2695-
// order that is based on the bundle index,
2696-
// spreading pressure evenly among registers
2697-
// to reduce commitment-map
2698-
// contention. (TODO: account for
2699-
// caller-save vs. callee-saves here too.)
2700-
// Note that we avoid retrying the hint_reg;
2701-
// this is why the loop count is n_regs + 1
2702-
// if there is a hint reg, because we always
2703-
// skip one iteration.
2704-
let preg = match (i, hint_reg) {
2705-
(0, Some(hint_reg)) => hint_reg,
2706-
(i, Some(hint_reg)) => {
2707-
let reg = self.env.regs_by_class[class as u8 as usize]
2708-
[(i - 1 + scan_offset) % n_regs];
2709-
if reg == hint_reg {
2710-
continue;
2711-
}
2712-
reg
2713-
}
2714-
(i, None) => {
2715-
self.env.regs_by_class[class as u8 as usize]
2716-
[(i + scan_offset) % n_regs]
2717-
}
2718-
};
27192749

2750+
for preg in RegTraversalIter::new(self.env, class, hint_reg, scan_offset) {
27202751
self.stats.process_bundle_reg_probes_any += 1;
27212752
let preg_idx = PRegIndex::new(preg.index());
27222753
match self.try_to_allocate_bundle_to_reg(bundle, preg_idx) {
@@ -2828,10 +2859,7 @@ impl<'a, F: Function> Env<'a, F> {
28282859
let class = any_vreg.class();
28292860
let mut success = false;
28302861
self.stats.spill_bundle_reg_probes += 1;
2831-
let nregs = self.env.regs_by_class[class as u8 as usize].len();
2832-
for i in 0..nregs {
2833-
let i = (i + bundle.index()) % nregs;
2834-
let preg = self.env.regs_by_class[class as u8 as usize][i]; // don't borrow self
2862+
for preg in RegTraversalIter::new(self.env, class, None, bundle.index()) {
28352863
let preg_idx = PRegIndex::new(preg.index());
28362864
if let AllocRegResult::Allocated(_) =
28372865
self.try_to_allocate_bundle_to_reg(bundle, preg_idx)

src/lib.rs

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,15 @@ impl OperandOrAllocation {
579579
debug_assert!(alloc.bits() >> 29 >= 5);
580580
Self { bits: alloc.bits() }
581581
}
582+
pub fn from_alloc_and_kind(alloc: Allocation, kind: OperandKind) -> Self {
583+
debug_assert!(alloc.bits() >> 29 >= 5);
584+
let bits = alloc.bits()
585+
| match kind {
586+
OperandKind::Def => 0,
587+
OperandKind::Use => 1 << 28,
588+
};
589+
Self { bits }
590+
}
582591
pub fn is_operand(&self) -> bool {
583592
(self.bits >> 29) <= 4
584593
}
@@ -659,10 +668,22 @@ pub trait Function {
659668
fn is_ret(&self, insn: Inst) -> bool;
660669

661670
/// Determine whether an instruction is the end-of-block
662-
/// branch. If so, its operands *must* be the block parameters for
663-
/// each of its block's `block_succs` successor blocks, in order.
671+
/// branch. If so, its operands at the indices given by
672+
/// `branch_blockparam_arg_offset()` below *must* be the block
673+
/// parameters for each of its block's `block_succs` successor
674+
/// blocks, in order.
664675
fn is_branch(&self, insn: Inst) -> bool;
665676

677+
/// If `insn` is a branch at the end of `block`, returns the
678+
/// operand index at which outgoing blockparam arguments are
679+
/// found. Starting at this index, blockparam arguments for each
680+
/// successor block's blockparams, in order, must be found.
681+
///
682+
/// It is an error if `self.inst_operands(insn).len() -
683+
/// self.branch_blockparam_arg_offset(insn)` is not exactly equal
684+
/// to the sum of blockparam counts for all successor blocks.
685+
fn branch_blockparam_arg_offset(&self, block: Block, insn: Inst) -> usize;
686+
666687
/// Determine whether an instruction is a safepoint and requires a stackmap.
667688
fn is_safepoint(&self, _: Inst) -> bool {
668689
false
@@ -842,7 +863,8 @@ pub enum Edit {
842863
#[derive(Clone, Debug)]
843864
pub struct MachineEnv {
844865
regs: Vec<PReg>,
845-
regs_by_class: Vec<Vec<PReg>>,
866+
preferred_regs_by_class: Vec<Vec<PReg>>,
867+
non_preferred_regs_by_class: Vec<Vec<PReg>>,
846868
scratch_by_class: Vec<PReg>,
847869
}
848870

0 commit comments

Comments
 (0)