Merge pull request #1570 from cfallin/fix-long-range-aarch64-call

Fix long-range (non-colocated) aarch64 calls to not use Arm64Call reloc, and fix simplejit to use new long-distance call.
This commit is contained in:
Chris Fallin
2020-05-05 10:45:55 -07:00
committed by GitHub
10 changed files with 114 additions and 23 deletions

View File

@@ -7,6 +7,7 @@
use crate::ir::{ArgumentLoc, ExternalName, SigRef, Type}; use crate::ir::{ArgumentLoc, ExternalName, SigRef, Type};
use crate::isa::{CallConv, RegInfo, RegUnit}; use crate::isa::{CallConv, RegInfo, RegUnit};
use crate::machinst::RelocDistance;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::fmt; use core::fmt;
use core::str::FromStr; use core::str::FromStr;
@@ -366,6 +367,16 @@ pub struct ExtFuncData {
/// Will this function be defined nearby, such that it will always be a certain distance away, /// Will this function be defined nearby, such that it will always be a certain distance away,
/// after linking? If so, references to it can avoid going through a GOT or PLT. Note that /// after linking? If so, references to it can avoid going through a GOT or PLT. Note that
/// symbols meant to be preemptible cannot be considered colocated. /// symbols meant to be preemptible cannot be considered colocated.
///
/// If `true`, some backends may use relocation forms that have limited range. The exact
/// distance depends on the code model in use. Currently on AArch64, for example, Cranelift
/// uses a custom code model supporting up to +/- 128MB displacements. If it is unknown how
/// far away the target will be, it is best not to set the `colocated` flag; in general, this
/// flag is best used when the target is known to be in the same unit of code generation, such
/// as a Wasm module.
///
/// See the documentation for [`RelocDistance`](machinst::RelocDistance) for more details. A
/// `colocated` flag value of `true` implies `RelocDistance::Near`.
pub colocated: bool, pub colocated: bool,
} }
@@ -378,6 +389,17 @@ impl fmt::Display for ExtFuncData {
} }
} }
impl ExtFuncData {
/// Return an estimate of the distance to the referred-to function symbol.
pub fn reloc_distance(&self) -> RelocDistance {
if self.colocated {
RelocDistance::Near
} else {
RelocDistance::Far
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -3,6 +3,7 @@
use crate::ir::immediates::{Imm64, Offset32}; use crate::ir::immediates::{Imm64, Offset32};
use crate::ir::{ExternalName, GlobalValue, Type}; use crate::ir::{ExternalName, GlobalValue, Type};
use crate::isa::TargetIsa; use crate::isa::TargetIsa;
use crate::machinst::RelocDistance;
use core::fmt; use core::fmt;
/// Information about a global value declaration. /// Information about a global value declaration.
@@ -62,6 +63,10 @@ pub enum GlobalValueData {
/// Will this symbol be defined nearby, such that it will always be a certain distance /// Will this symbol be defined nearby, such that it will always be a certain distance
/// away, after linking? If so, references to it can avoid going through a GOT. Note that /// away, after linking? If so, references to it can avoid going through a GOT. Note that
/// symbols meant to be preemptible cannot be colocated. /// symbols meant to be preemptible cannot be colocated.
///
/// If `true`, some backends may use relocation forms that have limited range: for example,
/// a +/- 2^27-byte range on AArch64. See the documentation for
/// [`RelocDistance`](machinst::RelocDistance) for more details.
colocated: bool, colocated: bool,
/// Does this symbol refer to a thread local storage value? /// Does this symbol refer to a thread local storage value?
@@ -85,6 +90,20 @@ impl GlobalValueData {
Self::IAddImm { global_type, .. } | Self::Load { global_type, .. } => global_type, Self::IAddImm { global_type, .. } | Self::Load { global_type, .. } => global_type,
} }
} }
/// If this global references a symbol, return an estimate of the relocation distance,
/// based on the `colocated` flag.
pub fn maybe_reloc_distance(&self) -> Option<RelocDistance> {
match self {
&GlobalValueData::Symbol {
colocated: true, ..
} => Some(RelocDistance::Near),
&GlobalValueData::Symbol {
colocated: false, ..
} => Some(RelocDistance::Far),
_ => None,
}
}
} }
impl fmt::Display for GlobalValueData { impl fmt::Display for GlobalValueData {

View File

@@ -1061,7 +1061,7 @@ impl ABIBody for AArch64ABIBody {
} }
enum CallDest { enum CallDest {
ExtName(ir::ExternalName), ExtName(ir::ExternalName, RelocDistance),
Reg(Reg), Reg(Reg),
} }
@@ -1102,6 +1102,7 @@ impl AArch64ABICall {
pub fn from_func( pub fn from_func(
sig: &ir::Signature, sig: &ir::Signature,
extname: &ir::ExternalName, extname: &ir::ExternalName,
dist: RelocDistance,
loc: ir::SourceLoc, loc: ir::SourceLoc,
) -> AArch64ABICall { ) -> AArch64ABICall {
let sig = ABISig::from_func_sig(sig); let sig = ABISig::from_func_sig(sig);
@@ -1110,7 +1111,7 @@ impl AArch64ABICall {
sig, sig,
uses, uses,
defs, defs,
dest: CallDest::ExtName(extname.clone()), dest: CallDest::ExtName(extname.clone(), dist),
loc, loc,
opcode: ir::Opcode::Call, opcode: ir::Opcode::Call,
} }
@@ -1207,13 +1208,28 @@ impl ABICall for AArch64ABICall {
fn gen_call(&self) -> Vec<Inst> { fn gen_call(&self) -> Vec<Inst> {
let (uses, defs) = (self.uses.clone(), self.defs.clone()); let (uses, defs) = (self.uses.clone(), self.defs.clone());
match &self.dest { match &self.dest {
&CallDest::ExtName(ref name) => vec![Inst::Call { &CallDest::ExtName(ref name, RelocDistance::Near) => vec![Inst::Call {
dest: name.clone(), dest: name.clone(),
uses, uses,
defs, defs,
loc: self.loc, loc: self.loc,
opcode: self.opcode, opcode: self.opcode,
}], }],
&CallDest::ExtName(ref name, RelocDistance::Far) => vec![
Inst::LoadExtName {
rd: writable_spilltmp_reg(),
name: name.clone(),
offset: 0,
srcloc: self.loc,
},
Inst::CallInd {
rn: spilltmp_reg(),
uses,
defs,
loc: self.loc,
opcode: self.opcode,
},
],
&CallDest::Reg(reg) => vec![Inst::CallInd { &CallDest::Reg(reg) => vec![Inst::CallInd {
rn: reg, rn: reg,
uses, uses,

View File

@@ -612,7 +612,10 @@ pub enum Inst {
cond: Cond, cond: Cond,
}, },
/// A machine call instruction. /// A machine call instruction. N.B.: this allows only a +/- 128MB offset (it uses a relocation
/// of type `Reloc::Arm64Call`); if the destination distance is not `RelocDistance::Near`, the
/// code should use a `LoadExtName` / `CallInd` sequence instead, allowing an arbitrary 64-bit
/// target.
Call { Call {
dest: ExternalName, dest: ExternalName,
uses: Set<Reg>, uses: Set<Reg>,

View File

@@ -1233,7 +1233,8 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(ctx: &mut C, insn: IRIns
Opcode::FuncAddr => { Opcode::FuncAddr => {
let rd = output_to_reg(ctx, outputs[0]); let rd = output_to_reg(ctx, outputs[0]);
let extname = ctx.call_target(insn).unwrap().clone(); let (extname, _) = ctx.call_target(insn).unwrap();
let extname = extname.clone();
let loc = ctx.srcloc(insn); let loc = ctx.srcloc(insn);
ctx.emit(Inst::LoadExtName { ctx.emit(Inst::LoadExtName {
rd, rd,
@@ -1249,7 +1250,7 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(ctx: &mut C, insn: IRIns
Opcode::SymbolValue => { Opcode::SymbolValue => {
let rd = output_to_reg(ctx, outputs[0]); let rd = output_to_reg(ctx, outputs[0]);
let (extname, offset) = ctx.symbol_value(insn).unwrap(); let (extname, _, offset) = ctx.symbol_value(insn).unwrap();
let extname = extname.clone(); let extname = extname.clone();
let loc = ctx.srcloc(insn); let loc = ctx.srcloc(insn);
ctx.emit(Inst::LoadExtName { ctx.emit(Inst::LoadExtName {
@@ -1264,12 +1265,15 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(ctx: &mut C, insn: IRIns
let loc = ctx.srcloc(insn); let loc = ctx.srcloc(insn);
let (abi, inputs) = match op { let (abi, inputs) = match op {
Opcode::Call => { Opcode::Call => {
let extname = ctx.call_target(insn).unwrap(); let (extname, dist) = ctx.call_target(insn).unwrap();
let extname = extname.clone(); let extname = extname.clone();
let sig = ctx.call_sig(insn).unwrap(); let sig = ctx.call_sig(insn).unwrap();
assert!(inputs.len() == sig.params.len()); assert!(inputs.len() == sig.params.len());
assert!(outputs.len() == sig.returns.len()); assert!(outputs.len() == sig.returns.len());
(AArch64ABICall::from_func(sig, &extname, loc), &inputs[..]) (
AArch64ABICall::from_func(sig, &extname, dist, loc),
&inputs[..],
)
} }
Opcode::CallIndirect => { Opcode::CallIndirect => {
let ptr = input_to_reg(ctx, inputs[0], NarrowValueMode::ZeroExtend64); let ptr = input_to_reg(ctx, inputs[0], NarrowValueMode::ZeroExtend64);

View File

@@ -67,12 +67,15 @@ pub trait LowerCtx {
fn bb_param(&self, bb: Block, idx: usize) -> Reg; fn bb_param(&self, bb: Block, idx: usize) -> Reg;
/// Get the register for a return value. /// Get the register for a return value.
fn retval(&self, idx: usize) -> Writable<Reg>; fn retval(&self, idx: usize) -> Writable<Reg>;
/// Get the target for a call instruction, as an `ExternalName`. /// Get the target for a call instruction, as an `ExternalName`. Returns a tuple
fn call_target<'b>(&'b self, ir_inst: Inst) -> Option<&'b ExternalName>; /// providing this name and the "relocation distance", i.e., whether the backend
/// can assume the target will be "nearby" (within some small offset) or an
/// arbitrary address. (This comes from the `colocated` bit in the CLIF.)
fn call_target<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, RelocDistance)>;
/// Get the signature for a call or call-indirect instruction. /// Get the signature for a call or call-indirect instruction.
fn call_sig<'b>(&'b self, ir_inst: Inst) -> Option<&'b Signature>; fn call_sig<'b>(&'b self, ir_inst: Inst) -> Option<&'b Signature>;
/// Get the symbol name and offset for a symbol_value instruction. /// Get the symbol name, relocation distance estimate, and offset for a symbol_value instruction.
fn symbol_value<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, i64)>; fn symbol_value<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, RelocDistance, i64)>;
/// Returns the memory flags of a given memory access. /// Returns the memory flags of a given memory access.
fn memflags(&self, ir_inst: Inst) -> Option<MemFlags>; fn memflags(&self, ir_inst: Inst) -> Option<MemFlags>;
/// Get the source location for a given instruction. /// Get the source location for a given instruction.
@@ -122,6 +125,18 @@ pub struct Lower<'func, I: VCodeInst> {
next_vreg: u32, next_vreg: u32,
} }
/// Notion of "relocation distance". This gives an estimate of how far away a symbol will be from a
/// reference.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RelocDistance {
/// Target of relocation is "nearby". The threshold for this is fuzzy but should be interpreted
/// as approximately "within the compiled output of one module"; e.g., within AArch64's +/-
/// 128MB offset. If unsure, use `Far` instead.
Near,
/// Target of relocation could be anywhere in the address space.
Far,
}
fn alloc_vreg( fn alloc_vreg(
value_regs: &mut SecondaryMap<Value, Reg>, value_regs: &mut SecondaryMap<Value, Reg>,
regclass: RegClass, regclass: RegClass,
@@ -647,13 +662,17 @@ impl<'func, I: VCodeInst> LowerCtx for Lower<'func, I> {
Writable::from_reg(self.retval_regs[idx].0) Writable::from_reg(self.retval_regs[idx].0)
} }
/// Get the target for a call instruction, as an `ExternalName`. /// Get the target for a call instruction, as an `ExternalName`. Returns a tuple
fn call_target<'b>(&'b self, ir_inst: Inst) -> Option<&'b ExternalName> { /// providing this name and the "relocation distance", i.e., whether the backend
/// can assume the target will be "nearby" (within some small offset) or an
/// arbitrary address. (This comes from the `colocated` bit in the CLIF.)
fn call_target<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, RelocDistance)> {
match &self.f.dfg[ir_inst] { match &self.f.dfg[ir_inst] {
&InstructionData::Call { func_ref, .. } &InstructionData::Call { func_ref, .. }
| &InstructionData::FuncAddr { func_ref, .. } => { | &InstructionData::FuncAddr { func_ref, .. } => {
let funcdata = &self.f.dfg.ext_funcs[func_ref]; let funcdata = &self.f.dfg.ext_funcs[func_ref];
Some(&funcdata.name) let dist = funcdata.reloc_distance();
Some((&funcdata.name, dist))
} }
_ => None, _ => None,
} }
@@ -670,8 +689,8 @@ impl<'func, I: VCodeInst> LowerCtx for Lower<'func, I> {
} }
} }
/// Get the symbol name and offset for a symbol_value instruction. /// Get the symbol name, relocation distance estimate, and offset for a symbol_value instruction.
fn symbol_value<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, i64)> { fn symbol_value<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, RelocDistance, i64)> {
match &self.f.dfg[ir_inst] { match &self.f.dfg[ir_inst] {
&InstructionData::UnaryGlobalValue { global_value, .. } => { &InstructionData::UnaryGlobalValue { global_value, .. } => {
let gvdata = &self.f.global_values[global_value]; let gvdata = &self.f.global_values[global_value];
@@ -682,7 +701,8 @@ impl<'func, I: VCodeInst> LowerCtx for Lower<'func, I> {
.. ..
} => { } => {
let offset = offset.bits(); let offset = offset.bits();
Some((name, offset)) let dist = gvdata.maybe_reloc_distance().unwrap();
Some((name, dist, offset))
} }
_ => None, _ => None,
} }

View File

@@ -11,7 +11,8 @@ block0(v0: i64):
; check: stp fp, lr, [sp, #-16]! ; check: stp fp, lr, [sp, #-16]!
; nextln: mov fp, sp ; nextln: mov fp, sp
; nextln: bl 0 ; nextln: ldr x15, 8 ; b 12 ; data
; nextln: blr x15
; nextln: mov sp, fp ; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16 ; nextln: ldp fp, lr, [sp], #16
; nextln: ret ; nextln: ret

View File

@@ -45,7 +45,8 @@ block0(v0: i64):
; nextln: subs xzr, sp, x0 ; nextln: subs xzr, sp, x0
; nextln: b.hs 8 ; nextln: b.hs 8
; nextln: udf ; nextln: udf
; nextln: bl 0 ; nextln: ldr x15
; nextln: blr x15
; nextln: mov sp, fp ; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16 ; nextln: ldp fp, lr, [sp], #16
; nextln: ret ; nextln: ret
@@ -68,7 +69,8 @@ block0(v0: i64):
; nextln: subs xzr, sp, x15 ; nextln: subs xzr, sp, x15
; nextln: b.hs 8 ; nextln: b.hs 8
; nextln: udf ; nextln: udf
; nextln: bl 0 ; nextln: ldr x15
; nextln: blr x15
; nextln: mov sp, fp ; nextln: mov sp, fp
; nextln: ldp fp, lr, [sp], #16 ; nextln: ldp fp, lr, [sp], #16
; nextln: ret ; nextln: ret

View File

@@ -5,6 +5,7 @@ use cranelift_codegen::binemit::{
Addend, CodeOffset, Reloc, RelocSink, Stackmap, StackmapSink, TrapSink, Addend, CodeOffset, Reloc, RelocSink, Stackmap, StackmapSink, TrapSink,
}; };
use cranelift_codegen::isa::TargetIsa; use cranelift_codegen::isa::TargetIsa;
use cranelift_codegen::settings::Configurable;
use cranelift_codegen::{self, ir, settings}; use cranelift_codegen::{self, ir, settings};
use cranelift_module::{ use cranelift_module::{
Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleNamespace, Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleNamespace,
@@ -40,7 +41,11 @@ impl SimpleJITBuilder {
/// floating point instructions, and for stack probes. If you don't know what to use for this /// floating point instructions, and for stack probes. If you don't know what to use for this
/// argument, use `cranelift_module::default_libcall_names()`. /// argument, use `cranelift_module::default_libcall_names()`.
pub fn new(libcall_names: Box<dyn Fn(ir::LibCall) -> String>) -> Self { pub fn new(libcall_names: Box<dyn Fn(ir::LibCall) -> String>) -> Self {
let flag_builder = settings::builder(); let mut flag_builder = settings::builder();
// On at least AArch64, "colocated" calls use shorter-range relocations,
// which might not reach all definitions; we can't handle that here, so
// we require long-range relocation types.
flag_builder.set("use_colocated_libcalls", "false").unwrap();
let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| { let isa_builder = cranelift_native::builder().unwrap_or_else(|msg| {
panic!("host machine is not supported: {}", msg); panic!("host machine is not supported: {}", msg);
}); });

View File

@@ -153,7 +153,6 @@ fn switch_error() {
} }
#[test] #[test]
#[cfg_attr(target_arch = "aarch64", should_panic)] // FIXME(#1521)
fn libcall_function() { fn libcall_function() {
let mut module: Module<SimpleJITBackend> = let mut module: Module<SimpleJITBackend> =
Module::new(SimpleJITBuilder::new(default_libcall_names())); Module::new(SimpleJITBuilder::new(default_libcall_names()));