[codegen] Add a pinned register that's entirely under the control of the user;

This commit is contained in:
Benjamin Bouvier
2019-08-28 17:33:45 +02:00
parent d1d2e790b9
commit 660b8b28b8
15 changed files with 229 additions and 30 deletions

View File

@@ -11,6 +11,7 @@ pub struct RegBank {
pub names: Vec<&'static str>, pub names: Vec<&'static str>,
pub prefix: &'static str, pub prefix: &'static str,
pub pressure_tracking: bool, pub pressure_tracking: bool,
pub pinned_reg: Option<u16>,
pub toprcs: Vec<RegClassIndex>, pub toprcs: Vec<RegClassIndex>,
pub classes: Vec<RegClassIndex>, pub classes: Vec<RegClassIndex>,
} }
@@ -23,6 +24,7 @@ impl RegBank {
names: Vec<&'static str>, names: Vec<&'static str>,
prefix: &'static str, prefix: &'static str,
pressure_tracking: bool, pressure_tracking: bool,
pinned_reg: Option<u16>,
) -> Self { ) -> Self {
RegBank { RegBank {
name, name,
@@ -31,6 +33,7 @@ impl RegBank {
names, names,
prefix, prefix,
pressure_tracking, pressure_tracking,
pinned_reg,
toprcs: Vec::new(), toprcs: Vec::new(),
classes: Vec::new(), classes: Vec::new(),
} }
@@ -183,6 +186,7 @@ pub struct RegBankBuilder {
pub names: Vec<&'static str>, pub names: Vec<&'static str>,
pub prefix: &'static str, pub prefix: &'static str,
pub pressure_tracking: Option<bool>, pub pressure_tracking: Option<bool>,
pub pinned_reg: Option<u16>,
} }
impl RegBankBuilder { impl RegBankBuilder {
@@ -193,6 +197,7 @@ impl RegBankBuilder {
names: vec![], names: vec![],
prefix, prefix,
pressure_tracking: None, pressure_tracking: None,
pinned_reg: None,
} }
} }
pub fn units(mut self, units: u8) -> Self { pub fn units(mut self, units: u8) -> Self {
@@ -207,6 +212,11 @@ impl RegBankBuilder {
self.pressure_tracking = Some(track); self.pressure_tracking = Some(track);
self self
} }
pub fn pinned_reg(mut self, unit: u16) -> Self {
assert!(unit < (self.units as u16));
self.pinned_reg = Some(unit);
self
}
} }
pub struct IsaRegsBuilder { pub struct IsaRegsBuilder {
@@ -246,6 +256,7 @@ impl IsaRegsBuilder {
builder builder
.pressure_tracking .pressure_tracking
.expect("Pressure tracking must be explicitly set"), .expect("Pressure tracking must be explicitly set"),
builder.pinned_reg,
)) ))
} }

View File

@@ -56,6 +56,7 @@ fn gen_regclass(isa: &TargetIsa, reg_class: &RegClass, fmt: &mut Formatter) {
fmtln!(fmt, "first: {},", reg_bank.first_unit + reg_class.start); fmtln!(fmt, "first: {},", reg_bank.first_unit + reg_class.start);
fmtln!(fmt, "subclasses: {:#x},", reg_class.subclass_mask()); fmtln!(fmt, "subclasses: {:#x},", reg_class.subclass_mask());
fmtln!(fmt, "mask: [{}],", mask); fmtln!(fmt, "mask: [{}],", mask);
fmtln!(fmt, "pinned_reg: {:?},", reg_bank.pinned_reg);
fmtln!(fmt, "info: &INFO,"); fmtln!(fmt, "info: &INFO,");
}); });
fmtln!(fmt, "};"); fmtln!(fmt, "};");

View File

@@ -372,6 +372,7 @@ pub(crate) fn define(
let fpromote = shared.by_name("fpromote"); let fpromote = shared.by_name("fpromote");
let fsub = shared.by_name("fsub"); let fsub = shared.by_name("fsub");
let func_addr = shared.by_name("func_addr"); let func_addr = shared.by_name("func_addr");
let get_pinned_reg = shared.by_name("get_pinned_reg");
let iadd = shared.by_name("iadd"); let iadd = shared.by_name("iadd");
let iadd_cout = shared.by_name("iadd_cout"); let iadd_cout = shared.by_name("iadd_cout");
let iadd_cin = shared.by_name("iadd_cin"); let iadd_cin = shared.by_name("iadd_cin");
@@ -421,6 +422,7 @@ pub(crate) fn define(
let scalar_to_vector = shared.by_name("scalar_to_vector"); let scalar_to_vector = shared.by_name("scalar_to_vector");
let selectif = shared.by_name("selectif"); let selectif = shared.by_name("selectif");
let sextend = shared.by_name("sextend"); let sextend = shared.by_name("sextend");
let set_pinned_reg = shared.by_name("set_pinned_reg");
let sload16 = shared.by_name("sload16"); let sload16 = shared.by_name("sload16");
let sload16_complex = shared.by_name("sload16_complex"); let sload16_complex = shared.by_name("sload16_complex");
let sload32 = shared.by_name("sload32"); let sload32 = shared.by_name("sload32");
@@ -516,6 +518,7 @@ pub(crate) fn define(
let rec_furm = r.template("furm"); let rec_furm = r.template("furm");
let rec_furm_reg_to_ssa = r.template("furm_reg_to_ssa"); let rec_furm_reg_to_ssa = r.template("furm_reg_to_ssa");
let rec_furmi_rnd = r.template("furmi_rnd"); let rec_furmi_rnd = r.template("furmi_rnd");
let rec_get_pinned_reg = r.recipe("get_pinned_reg");
let rec_got_fnaddr8 = r.template("got_fnaddr8"); let rec_got_fnaddr8 = r.template("got_fnaddr8");
let rec_got_gvaddr8 = r.template("got_gvaddr8"); let rec_got_gvaddr8 = r.template("got_gvaddr8");
let rec_gvaddr4 = r.template("gvaddr4"); let rec_gvaddr4 = r.template("gvaddr4");
@@ -569,6 +572,7 @@ pub(crate) fn define(
let rec_safepoint = r.recipe("safepoint"); let rec_safepoint = r.recipe("safepoint");
let rec_setf_abcd = r.template("setf_abcd"); let rec_setf_abcd = r.template("setf_abcd");
let rec_seti_abcd = r.template("seti_abcd"); let rec_seti_abcd = r.template("seti_abcd");
let rec_set_pinned_reg = r.template("set_pinned_reg");
let rec_spaddr4_id = r.template("spaddr4_id"); let rec_spaddr4_id = r.template("spaddr4_id");
let rec_spaddr8_id = r.template("spaddr8_id"); let rec_spaddr8_id = r.template("spaddr8_id");
let rec_spillSib32 = r.template("spillSib32"); let rec_spillSib32 = r.template("spillSib32");
@@ -619,6 +623,13 @@ pub(crate) fn define(
// Definitions. // Definitions.
let mut e = PerCpuModeEncodings::new(); let mut e = PerCpuModeEncodings::new();
// The pinned reg is fixed to a certain value entirely user-controlled, so it generates nothing!
e.enc64_rec(get_pinned_reg.bind(I64), rec_get_pinned_reg, 0);
e.enc_x86_64(
set_pinned_reg.bind(I64),
rec_set_pinned_reg.opcodes(vec![0x89]).rex().w(),
);
e.enc_i32_i64(iadd, rec_rr.opcodes(vec![0x01])); e.enc_i32_i64(iadd, rec_rr.opcodes(vec![0x01]));
e.enc_i32_i64(iadd_cout, rec_rr.opcodes(vec![0x01])); e.enc_i32_i64(iadd_cout, rec_rr.opcodes(vec![0x01]));
e.enc_i32_i64(iadd_cin, rec_rin.opcodes(vec![0x11])); e.enc_i32_i64(iadd_cin, rec_rin.opcodes(vec![0x11]));

View File

@@ -351,6 +351,7 @@ pub(crate) fn define<'shared>(
let reg_rax = Register::new(gpr, regs.regunit_by_name(gpr, "rax")); let reg_rax = Register::new(gpr, regs.regunit_by_name(gpr, "rax"));
let reg_rcx = Register::new(gpr, regs.regunit_by_name(gpr, "rcx")); let reg_rcx = Register::new(gpr, regs.regunit_by_name(gpr, "rcx"));
let reg_rdx = Register::new(gpr, regs.regunit_by_name(gpr, "rdx")); let reg_rdx = Register::new(gpr, regs.regunit_by_name(gpr, "rdx"));
let reg_r15 = Register::new(gpr, regs.regunit_by_name(gpr, "r15"));
// Stack operand with a 32-bit signed displacement from either RBP or RSP. // Stack operand with a 32-bit signed displacement from either RBP or RSP.
let stack_gpr32 = Stack::new(gpr); let stack_gpr32 = Stack::new(gpr);
@@ -428,6 +429,25 @@ pub(crate) fn define<'shared>(
.emit(""), .emit(""),
); );
recipes.add_recipe(
EncodingRecipeBuilder::new("get_pinned_reg", f_nullary, 0)
.operands_out(vec![reg_r15])
.emit(""),
);
// umr with a fixed register output that's r15.
recipes.add_template_recipe(
EncodingRecipeBuilder::new("set_pinned_reg", f_unary, 1)
.operands_in(vec![gpr])
.clobbers_flags(false)
.emit(
r#"
let r15 = RU::r15.into();
{{PUT_OP}}(bits, rex2(r15, in_reg0), sink);
modrm_rr(r15, in_reg0, sink);
"#,
),
);
// No-op fills, created by late-stage redundant-fill removal. // No-op fills, created by late-stage redundant-fill removal.
recipes.add_recipe( recipes.add_recipe(
EncodingRecipeBuilder::new("fillnull", f_unary, 0) EncodingRecipeBuilder::new("fillnull", f_unary, 0)

View File

@@ -6,7 +6,8 @@ pub fn define() -> IsaRegs {
let builder = RegBankBuilder::new("IntRegs", "r") let builder = RegBankBuilder::new("IntRegs", "r")
.units(16) .units(16)
.names(vec!["rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi"]) .names(vec!["rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi"])
.track_pressure(true); .track_pressure(true)
.pinned_reg(15);
let int_regs = regs.add_bank(builder); let int_regs = regs.add_bank(builder);
let builder = RegBankBuilder::new("FloatRegs", "xmm") let builder = RegBankBuilder::new("FloatRegs", "xmm")

View File

@@ -964,6 +964,34 @@ pub(crate) fn define(
.operands_out(vec![addr]), .operands_out(vec![addr]),
); );
// Note this instruction is marked as having other side-effects, so GVN won't try to hoist it,
// which would result in it being subject to spilling. While not hoisting would generally hurt
// performance, since a computed value used many times may need to be regenerated before each
// use, it is not the case here: this instruction doesn't generate any code. That's because,
// by definition the pinned register is never used by the register allocator, but is written to
// and read explicitly and exclusively by set_pinned_reg and get_pinned_reg.
ig.push(
Inst::new(
"get_pinned_reg",
r#"
Gets the content of the pinned register, when it's enabled.
"#,
)
.operands_out(vec![addr])
.other_side_effects(true),
);
ig.push(
Inst::new(
"set_pinned_reg",
r#"
Sets the content of the pinned register, when it's enabled.
"#,
)
.operands_in(vec![addr])
.other_side_effects(true),
);
let TableOffset = &TypeVar::new( let TableOffset = &TypeVar::new(
"TableOffset", "TableOffset",
"An unsigned table offset", "An unsigned table offset",

View File

@@ -84,6 +84,17 @@ pub fn define() -> SettingGroup {
false, false,
); );
settings.add_bool(
"enable_pinned_reg",
r#"Enable the use of the pinned register.
This register is excluded from register allocation, and is completely under the control of
the end-user. It is possible to read it via the get_pinned_reg instruction, and to set it
with the set_pinned_reg instruction.
"#,
false,
);
settings.add_bool("enable_simd", "Enable the use of SIMD instructions.", false); settings.add_bool("enable_simd", "Enable the use of SIMD instructions.", false);
settings.add_bool( settings.add_bool(

View File

@@ -154,6 +154,12 @@ pub struct RegClassData {
/// The global `RegInfo` instance containing this register class. /// The global `RegInfo` instance containing this register class.
pub info: &'static RegInfo, pub info: &'static RegInfo,
/// The "pinned" register of the associated register bank.
///
/// This register must be non-volatile (callee-preserved) and must not be the fixed
/// output register of any instruction.
pub pinned_reg: Option<RegUnit>,
} }
impl RegClassData { impl RegClassData {
@@ -201,6 +207,15 @@ impl RegClassData {
pub fn contains(&self, regunit: RegUnit) -> bool { pub fn contains(&self, regunit: RegUnit) -> bool {
self.mask[(regunit / 32) as usize] & (1u32 << (regunit % 32)) != 0 self.mask[(regunit / 32) as usize] & (1u32 << (regunit % 32)) != 0
} }
/// If the pinned register is used, is the given regunit the pinned register of this class?
#[inline]
pub fn is_pinned_reg(&self, enabled: bool, regunit: RegUnit) -> bool {
enabled
&& self
.pinned_reg
.map_or(false, |pinned_reg| pinned_reg == regunit)
}
} }
impl fmt::Display for RegClassData { impl fmt::Display for RegClassData {

View File

@@ -89,9 +89,8 @@ impl ArgAssigner for Args {
let reg = FPR.unit(self.fpr_used); let reg = FPR.unit(self.fpr_used);
self.fpr_used += 1; self.fpr_used += 1;
return ArgumentLoc::Reg(reg).into(); return ArgumentLoc::Reg(reg).into();
} else {
return ValueConversion::VectorSplit.into();
} }
return ValueConversion::VectorSplit.into();
} }
// Large integers and booleans are broken down to fit in a register. // Large integers and booleans are broken down to fit in a register.
@@ -229,7 +228,7 @@ pub fn regclass_for_abi_type(ty: ir::Type) -> RegClass {
} }
/// Get the set of allocatable registers for `func`. /// Get the set of allocatable registers for `func`.
pub fn allocatable_registers(_func: &ir::Function, triple: &Triple) -> RegisterSet { pub fn allocatable_registers(triple: &Triple, flags: &shared_settings::Flags) -> RegisterSet {
let mut regs = RegisterSet::new(); let mut regs = RegisterSet::new();
regs.take(GPR, RU::rsp as RegUnit); regs.take(GPR, RU::rsp as RegUnit);
regs.take(GPR, RU::rbp as RegUnit); regs.take(GPR, RU::rbp as RegUnit);
@@ -240,6 +239,15 @@ pub fn allocatable_registers(_func: &ir::Function, triple: &Triple) -> RegisterS
regs.take(GPR, GPR.unit(i)); regs.take(GPR, GPR.unit(i));
regs.take(FPR, FPR.unit(i)); regs.take(FPR, FPR.unit(i));
} }
if flags.enable_pinned_reg() {
unimplemented!("Pinned register not implemented on x86-32.");
}
} else {
// Choose r15 as the pinned register on 64-bits: it is non-volatile on native ABIs and
// isn't the fixed output register of any instruction.
if flags.enable_pinned_reg() {
regs.take(GPR, RU::r15 as RegUnit);
}
} }
regs regs

View File

@@ -119,8 +119,8 @@ impl TargetIsa for Isa {
abi::regclass_for_abi_type(ty) abi::regclass_for_abi_type(ty)
} }
fn allocatable_registers(&self, func: &ir::Function) -> regalloc::RegisterSet { fn allocatable_registers(&self, _func: &ir::Function) -> regalloc::RegisterSet {
abi::allocatable_registers(func, &self.triple) abi::allocatable_registers(&self.triple, &self.shared_flags)
} }
#[cfg(feature = "testing_hooks")] #[cfg(feature = "testing_hooks")]

View File

@@ -106,6 +106,8 @@ struct Context<'a> {
// Pristine set of registers that the allocator can use. // Pristine set of registers that the allocator can use.
// This set remains immutable, we make clones. // This set remains immutable, we make clones.
usable_regs: RegisterSet, usable_regs: RegisterSet,
uses_pinned_reg: bool,
} }
impl Coloring { impl Coloring {
@@ -137,6 +139,7 @@ impl Coloring {
debug!("Coloring for:\n{}", func.display(isa)); debug!("Coloring for:\n{}", func.display(isa));
let mut ctx = Context { let mut ctx = Context {
usable_regs: isa.allocatable_registers(func), usable_regs: isa.allocatable_registers(func),
uses_pinned_reg: isa.flags().enable_pinned_reg(),
cur: EncCursor::new(func, isa), cur: EncCursor::new(func, isa),
reginfo: isa.register_info(), reginfo: isa.register_info(),
encinfo: isa.encoding_info(), encinfo: isa.encoding_info(),
@@ -151,6 +154,12 @@ impl Coloring {
} }
impl<'a> Context<'a> { impl<'a> Context<'a> {
/// Is the pinned register usage enabled, and is this register the pinned register?
#[inline]
fn is_pinned_reg(&self, rc: RegClass, reg: RegUnit) -> bool {
rc.is_pinned_reg(self.uses_pinned_reg, reg)
}
/// Run the coloring algorithm. /// Run the coloring algorithm.
fn run(&mut self, tracker: &mut LiveValueTracker) { fn run(&mut self, tracker: &mut LiveValueTracker) {
self.cur self.cur
@@ -425,9 +434,11 @@ impl<'a> Context<'a> {
// Program the solver with register constraints for the input side. // Program the solver with register constraints for the input side.
self.solver.reset(&regs.input); self.solver.reset(&regs.input);
if let Some(constraints) = constraints { if let Some(constraints) = constraints {
self.program_input_constraints(inst, constraints.ins); self.program_input_constraints(inst, constraints.ins);
} }
let call_sig = self.cur.func.dfg.call_signature(inst); let call_sig = self.cur.func.dfg.call_signature(inst);
if let Some(sig) = call_sig { if let Some(sig) = call_sig {
self.program_input_abi(inst, AbiParams::Parameters(sig)); self.program_input_abi(inst, AbiParams::Parameters(sig));
@@ -457,6 +468,7 @@ impl<'a> Context<'a> {
if self.solver.has_fixed_input_conflicts() { if self.solver.has_fixed_input_conflicts() {
self.divert_fixed_input_conflicts(tracker.live()); self.divert_fixed_input_conflicts(tracker.live());
} }
self.solver.inputs_done(); self.solver.inputs_done();
// Update the live value tracker with this instruction. // Update the live value tracker with this instruction.
@@ -467,6 +479,13 @@ impl<'a> Context<'a> {
if let Affinity::Reg(rci) = lv.affinity { if let Affinity::Reg(rci) = lv.affinity {
let rc = self.reginfo.rc(rci); let rc = self.reginfo.rc(rci);
let reg = self.divert.reg(lv.value, &self.cur.func.locations); let reg = self.divert.reg(lv.value, &self.cur.func.locations);
if self.is_pinned_reg(rc, reg) {
// Don't kill the pinned reg, either in the local or global register sets.
debug_assert!(lv.is_local, "pinned register SSA value can't be global");
continue;
}
debug!( debug!(
" kill {} in {} ({} {})", " kill {} in {} ({} {})",
lv.value, lv.value,
@@ -506,6 +525,7 @@ impl<'a> Context<'a> {
); );
} }
} }
if let Some(sig) = call_sig { if let Some(sig) = call_sig {
self.program_output_abi( self.program_output_abi(
sig, sig,
@@ -515,6 +535,7 @@ impl<'a> Context<'a> {
&regs.global, &regs.global,
); );
} }
if let Some(constraints) = constraints { if let Some(constraints) = constraints {
self.program_output_constraints( self.program_output_constraints(
inst, inst,
@@ -596,16 +617,28 @@ impl<'a> Context<'a> {
if let Affinity::Reg(rci) = lv.affinity { if let Affinity::Reg(rci) = lv.affinity {
let rc = self.reginfo.rc(rci); let rc = self.reginfo.rc(rci);
let reg = loc.unwrap_reg();
debug_assert!(
!self.is_pinned_reg(rc, reg)
|| self.cur.func.dfg[inst].opcode() == Opcode::GetPinnedReg,
"pinned register may not be part of outputs for '{}'.",
self.cur.func.dfg[inst].opcode()
);
if self.is_pinned_reg(rc, reg) {
continue;
}
// Remove the dead defs. // Remove the dead defs.
if lv.endpoint == inst { if lv.endpoint == inst {
regs.input.free(rc, loc.unwrap_reg()); regs.input.free(rc, reg);
debug_assert!(lv.is_local); debug_assert!(lv.is_local);
} }
// Track globals in their undiverted locations. // Track globals in their undiverted locations.
if !lv.is_local && !replace_global_defines { if !lv.is_local && !replace_global_defines {
regs.global.take(rc, loc.unwrap_reg()); regs.global.take(rc, reg);
} }
} }
} }
@@ -626,14 +659,35 @@ impl<'a> Context<'a> {
// already in a register. // already in a register.
let cur_reg = self.divert.reg(value, &self.cur.func.locations); let cur_reg = self.divert.reg(value, &self.cur.func.locations);
match op.kind { match op.kind {
ConstraintKind::FixedReg(regunit) | ConstraintKind::FixedTied(regunit) => { ConstraintKind::FixedReg(regunit) => {
// Add the fixed constraint even if `cur_reg == regunit`. // Add the fixed constraint even if `cur_reg == regunit`.
// It is possible that we will want to convert the value to a variable later, // It is possible that we will want to convert the value to a variable later,
// and this identity assignment prevents that from happening. // and this identity assignment prevents that from happening.
self.solver self.solver
.reassign_in(value, op.regclass, cur_reg, regunit); .reassign_in(value, op.regclass, cur_reg, regunit);
} }
ConstraintKind::Reg | ConstraintKind::Tied(_) => { ConstraintKind::FixedTied(regunit) => {
// The pinned register may not be part of a fixed tied requirement. If this
// becomes the case, then it must be changed to a different register.
debug_assert!(
!self.is_pinned_reg(op.regclass, regunit),
"see comment above"
);
// See comment right above.
self.solver
.reassign_in(value, op.regclass, cur_reg, regunit);
}
ConstraintKind::Tied(_) => {
if self.is_pinned_reg(op.regclass, cur_reg) {
// Divert the pinned register; it shouldn't be reused for a tied input.
if self.solver.can_add_var(op.regclass, cur_reg) {
self.solver.add_var(value, op.regclass, cur_reg);
}
} else if !op.regclass.contains(cur_reg) {
self.solver.add_var(value, op.regclass, cur_reg);
}
}
ConstraintKind::Reg => {
if !op.regclass.contains(cur_reg) { if !op.regclass.contains(cur_reg) {
self.solver.add_var(value, op.regclass, cur_reg); self.solver.add_var(value, op.regclass, cur_reg);
} }
@@ -664,10 +718,13 @@ impl<'a> Context<'a> {
match op.kind { match op.kind {
ConstraintKind::Reg | ConstraintKind::Tied(_) => { ConstraintKind::Reg | ConstraintKind::Tied(_) => {
let cur_reg = self.divert.reg(value, &self.cur.func.locations); let cur_reg = self.divert.reg(value, &self.cur.func.locations);
// This is the opposite condition of `program_input_constraints()`.
if op.regclass.contains(cur_reg) { // This is the opposite condition of `program_input_constraints()`. The pinned
// register mustn't be added back as a variable.
if op.regclass.contains(cur_reg) && !self.is_pinned_reg(op.regclass, cur_reg) {
// This code runs after calling `solver.inputs_done()` so we must identify // This code runs after calling `solver.inputs_done()` so we must identify
// the new variable as killed or live-through. // the new variable as killed or live-through. Always special-case the
// pinned register as a through variable.
let ctx = self.liveness.context(&self.cur.func.layout); let ctx = self.liveness.context(&self.cur.func.layout);
if self.liveness[value].killed_at(inst, ctx.order.pp_ebb(inst), ctx) { if self.liveness[value].killed_at(inst, ctx.order.pp_ebb(inst), ctx) {
self.solver.add_killed_var(value, op.regclass, cur_reg); self.solver.add_killed_var(value, op.regclass, cur_reg);
@@ -778,7 +835,7 @@ impl<'a> Context<'a> {
if pred(lr, self.liveness.context(&self.cur.func.layout)) { if pred(lr, self.liveness.context(&self.cur.func.layout)) {
if let Affinity::Reg(rci) = lr.affinity { if let Affinity::Reg(rci) = lr.affinity {
let rc = self.reginfo.rc(rci); let rc = self.reginfo.rc(rci);
// Stack diversions should not be possible here. The only live transiently // Stack diversions should not be possible here. They only live transiently
// during `shuffle_inputs()`. // during `shuffle_inputs()`.
self.solver.reassign_in( self.solver.reassign_in(
value, value,
@@ -797,8 +854,8 @@ impl<'a> Context<'a> {
} }
} }
// Find existing live values that conflict with the fixed input register constraints programmed /// Find existing live values that conflict with the fixed input register constraints programmed
// into the constraint solver. Convert them to solver variables so they can be diverted. /// into the constraint solver. Convert them to solver variables so they can be diverted.
fn divert_fixed_input_conflicts(&mut self, live: &[LiveValue]) { fn divert_fixed_input_conflicts(&mut self, live: &[LiveValue]) {
for lv in live { for lv in live {
if let Affinity::Reg(rci) = lv.affinity { if let Affinity::Reg(rci) = lv.affinity {
@@ -886,7 +943,9 @@ impl<'a> Context<'a> {
reg: RegUnit, reg: RegUnit,
throughs: &[LiveValue], throughs: &[LiveValue],
) { ) {
if !self.solver.add_fixed_output(rc, reg) { // Pinned register is already unavailable in the solver, since it is copied in the
// available registers on entry.
if !self.is_pinned_reg(rc, reg) && !self.solver.add_fixed_output(rc, reg) {
// The fixed output conflicts with some of the live-through registers. // The fixed output conflicts with some of the live-through registers.
for lv in throughs { for lv in throughs {
if let Affinity::Reg(rci) = lv.affinity { if let Affinity::Reg(rci) = lv.affinity {
@@ -929,12 +988,12 @@ impl<'a> Context<'a> {
// Find the input operand we're tied to. // Find the input operand we're tied to.
// The solver doesn't care about the output value. // The solver doesn't care about the output value.
let arg = self.cur.func.dfg.inst_args(inst)[num as usize]; let arg = self.cur.func.dfg.inst_args(inst)[num as usize];
if let Some(reg) = self.solver.add_tied_input( let reg = self.divert.reg(arg, &self.cur.func.locations);
arg,
op.regclass, if let Some(reg) =
self.divert.reg(arg, &self.cur.func.locations), self.solver
!lv.is_local, .add_tied_input(arg, op.regclass, reg, !lv.is_local)
) { {
// The value we're tied to has been assigned to a fixed register. // The value we're tied to has been assigned to a fixed register.
// We need to make sure that fixed output register is compatible with the // We need to make sure that fixed output register is compatible with the
// global register set. // global register set.
@@ -1000,7 +1059,7 @@ impl<'a> Context<'a> {
let toprc2 = self.reginfo.toprc(rci); let toprc2 = self.reginfo.toprc(rci);
let reg2 = self.divert.reg(lv.value, &self.cur.func.locations); let reg2 = self.divert.reg(lv.value, &self.cur.func.locations);
if rc.contains(reg2) if rc.contains(reg2)
&& self.solver.can_add_var(lv.value, toprc2, reg2) && self.solver.can_add_var(toprc2, reg2)
&& !self.is_live_on_outgoing_edge(lv.value) && !self.is_live_on_outgoing_edge(lv.value)
{ {
self.solver.add_through_var(lv.value, toprc2, reg2); self.solver.add_through_var(lv.value, toprc2, reg2);
@@ -1061,8 +1120,15 @@ impl<'a> Context<'a> {
for m in self.solver.moves() { for m in self.solver.moves() {
match *m { match *m {
Reg { Reg {
value, from, to, .. value,
from,
to,
rc,
} => { } => {
debug_assert!(
!self.is_pinned_reg(rc, to),
"pinned register used in a regmove"
);
self.divert.regmove(value, from, to); self.divert.regmove(value, from, to);
self.cur.ins().regmove(value, from, to); self.cur.ins().regmove(value, from, to);
} }
@@ -1086,8 +1152,12 @@ impl<'a> Context<'a> {
value, value,
from_slot, from_slot,
to, to,
.. rc,
} => { } => {
debug_assert!(
!self.is_pinned_reg(rc, to),
"pinned register used in a regfill"
);
// These slots are single use, so mark `ss` as available again. // These slots are single use, so mark `ss` as available again.
let ss = slot[from_slot].take().expect("Using unallocated slot"); let ss = slot[from_slot].take().expect("Using unallocated slot");
self.divert.regfill(value, ss, to); self.divert.regfill(value, ss, to);

View File

@@ -268,6 +268,7 @@ mod tests {
subclasses: 0, subclasses: 0,
mask: [0xf0000000, 0x0000000f, 0], mask: [0xf0000000, 0x0000000f, 0],
info: &INFO, info: &INFO,
pinned_reg: None,
}; };
const DPR: RegClass = &RegClassData { const DPR: RegClass = &RegClassData {
@@ -280,6 +281,7 @@ mod tests {
subclasses: 0, subclasses: 0,
mask: [0x50000000, 0x0000000a, 0], mask: [0x50000000, 0x0000000a, 0],
info: &INFO, info: &INFO,
pinned_reg: None,
}; };
const INFO: RegInfo = RegInfo { const INFO: RegInfo = RegInfo {

View File

@@ -877,6 +877,7 @@ impl Solver {
let d = v.iter(&self.regs_in, &self.regs_out, global_regs).len(); let d = v.iter(&self.regs_in, &self.regs_out, global_regs).len();
v.domain = cmp::min(d, u16::MAX as usize) as u16; v.domain = cmp::min(d, u16::MAX as usize) as u16;
} }
// Solve for vars with small domains first to increase the chance of finding a solution. // Solve for vars with small domains first to increase the chance of finding a solution.
// //
// Also consider this case: // Also consider this case:
@@ -949,9 +950,8 @@ impl Solver {
// live registers be diverted. We need to make it a non-global value. // live registers be diverted. We need to make it a non-global value.
if v.is_global && gregs.iter(rc).next().is_none() { if v.is_global && gregs.iter(rc).next().is_none() {
return Err(SolverError::Global(v.value)); return Err(SolverError::Global(v.value));
} else {
return Err(SolverError::Divert(rc));
} }
return Err(SolverError::Divert(rc));
} }
}; };
@@ -976,7 +976,7 @@ impl Solver {
} }
/// Check if `value` can be added as a variable to help find a solution. /// Check if `value` can be added as a variable to help find a solution.
pub fn can_add_var(&mut self, _value: Value, constraint: RegClass, from: RegUnit) -> bool { pub fn can_add_var(&mut self, constraint: RegClass, from: RegUnit) -> bool {
!self.regs_in.is_avail(constraint, from) !self.regs_in.is_avail(constraint, from)
} }
} }
@@ -1030,7 +1030,7 @@ impl Solver {
let mut avail = regs.clone(); let mut avail = regs.clone();
let mut i = 0; let mut i = 0;
while i < self.moves.len() + self.fills.len() { while i < self.moves.len() + self.fills.len() {
// Don't even look at the fills until we've spent all the moves. Deferring these let's // Don't even look at the fills until we've spent all the moves. Deferring these lets
// us potentially reuse the claimed registers to resolve multiple cycles. // us potentially reuse the claimed registers to resolve multiple cycles.
if i >= self.moves.len() { if i >= self.moves.len() {
self.moves.append(&mut self.fills); self.moves.append(&mut self.fills);

View File

@@ -388,6 +388,7 @@ mod tests {
avoid_div_traps = false\n\ avoid_div_traps = false\n\
enable_float = true\n\ enable_float = true\n\
enable_nan_canonicalization = false\n\ enable_nan_canonicalization = false\n\
enable_pinned_reg = false\n\
enable_simd = false\n\ enable_simd = false\n\
enable_atomics = true\n\ enable_atomics = true\n\
enable_safepoints = false\n\ enable_safepoints = false\n\

View File

@@ -676,6 +676,26 @@ impl<'a> Verifier<'a> {
self.verify_value_list(inst, args, errors)?; self.verify_value_list(inst, args, errors)?;
} }
NullAry {
opcode: Opcode::GetPinnedReg,
}
| Unary {
opcode: Opcode::SetPinnedReg,
..
} => {
if let Some(isa) = &self.isa {
if !isa.flags().enable_pinned_reg() {
return fatal!(
errors,
inst,
"GetPinnedReg/SetPinnedReg cannot be used without enable_pinned_reg"
);
}
} else {
return fatal!(errors, inst, "GetPinnedReg/SetPinnedReg need an ISA!");
}
}
// Exhaustive list so we can't forget to add new formats // Exhaustive list so we can't forget to add new formats
Unary { .. } Unary { .. }
| UnaryImm { .. } | UnaryImm { .. }