Clobbers: use a more efficient bitmask representation in API. (#58)

* Clobbers: use a more efficient bitmask representation in API.

Currently, the `Function` trait requires a `&[PReg]` for the
clobber-list for a given instruction. In most cases where clobbers are
used, the list may be long: e.g., ABIs specify a fixed set of registers
that are clobbered and there may be ~half of all registers in this list.
What's more, the list can't be shared for e.g. all calls of a given ABI,
because actual return-values (defs) can't be clobbers. So we need to
allocate space for long, sometimes-slightly-different lists; this is
inefficient for the embedder and for us.

It's much more efficient to use a bitmask to represent a set of physical
registers. With current data structure bitpacking limitations, we can
support at most 128 physical registers; this means we can use a `u128`
bitmask. This also allows e.g. an embedder to start with a constant for
a given ABI, and mask out bits for actual return-value registers on call
instructions.

This PR makes that change, for minor but positive performance impact.

* Review comments.
This commit is contained in:
Chris Fallin
2022-06-27 12:27:19 -07:00
committed by GitHub
parent 06b3baf9f9
commit 9733cb2227
6 changed files with 94 additions and 13 deletions

View File

@@ -123,13 +123,13 @@ impl PReg {
/// all physical registers. Allows one to maintain an array of data for
/// all PRegs and index it efficiently.
#[inline(always)]
pub fn index(self) -> usize {
pub const fn index(self) -> usize {
self.bits as usize
}
/// Construct a PReg from the value returned from `.index()`.
#[inline(always)]
pub fn from_index(index: usize) -> Self {
pub const fn from_index(index: usize) -> Self {
PReg {
bits: (index & (Self::NUM_INDEX - 1)) as u8,
}
@@ -165,6 +165,78 @@ impl std::fmt::Display for PReg {
}
}
/// A physical register set. Used to represent clobbers
/// efficiently.
///
/// The set is `Copy` and is guaranteed to have constant, and small,
/// size, as it is based on a bitset internally.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct PRegSet {
bits: u128,
}
impl PRegSet {
/// Create an empty set.
pub const fn empty() -> Self {
Self { bits: 0 }
}
/// Add a physical register (PReg) to the set, returning the new value.
pub const fn with(self, reg: PReg) -> Self {
let bit = reg.index();
debug_assert!(bit < 128);
Self {
bits: self.bits | (1u128 << bit),
}
}
/// Add a physical register (PReg) to the set.
pub fn add(&mut self, reg: PReg) {
let bit = reg.index();
debug_assert!(bit < 128);
self.bits |= 1u128 << bit;
}
/// Remove a physical register (PReg) from the set.
pub fn remove(&mut self, reg: PReg) {
let bit = reg.index();
debug_assert!(bit < 128);
self.bits &= !(1u128 << bit);
}
/// Add all of the registers in one set to this one, mutating in
/// place.
pub fn union_from(&mut self, other: PRegSet) {
self.bits |= other.bits;
}
}
impl IntoIterator for PRegSet {
type Item = PReg;
type IntoIter = PRegSetIter;
fn into_iter(self) -> PRegSetIter {
PRegSetIter { bits: self.bits }
}
}
pub struct PRegSetIter {
bits: u128,
}
impl Iterator for PRegSetIter {
type Item = PReg;
fn next(&mut self) -> Option<PReg> {
if self.bits == 0 {
None
} else {
let index = self.bits.trailing_zeros();
self.bits &= !(1u128 << index);
Some(PReg::from_index(index as usize))
}
}
}
/// A virtual register. Contains a virtual register number and a
/// class.
///
@@ -198,7 +270,7 @@ impl VReg {
}
#[inline(always)]
pub fn vreg(self) -> usize {
pub const fn vreg(self) -> usize {
let vreg = (self.bits >> 1) as usize;
vreg
}
@@ -972,7 +1044,14 @@ pub trait Function {
/// fixed physical registers written by an instruction but not
/// used as a vreg output, or fixed physical registers used as
/// temps within an instruction out of necessity.
fn inst_clobbers(&self, insn: Inst) -> &[PReg];
///
/// Note that it is legal for a register to be both a clobber and
/// an actual def (via pinned vreg or via operand constrained to
/// the reg). This is for convenience: e.g., a call instruction
/// might have a constant clobber set determined by the ABI, but
/// some of those clobbered registers are sometimes return
/// value(s).
fn inst_clobbers(&self, insn: Inst) -> PRegSet;
/// Get the number of `VReg` in use in this function.
fn num_vregs(&self) -> usize;