[codegen] Add a pinned register that's entirely under the control of the user;
This commit is contained in:
@@ -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,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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, "};");
|
||||||
|
|||||||
@@ -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]));
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")]
|
||||||
|
|||||||
@@ -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(®s.input);
|
self.solver.reset(®s.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> {
|
|||||||
®s.global,
|
®s.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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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\
|
||||||
|
|||||||
@@ -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 { .. }
|
||||||
|
|||||||
Reference in New Issue
Block a user