Skip to content

Commit e06a50f

Browse files
committed
Fix long-range (non-colocated) aarch64 calls to not use Arm64Call reloc, and fix simplejit to use it.
Previously, every call was lowered on AArch64 to a `call` instruction, which takes a signed 26-bit PC-relative offset. Including the 2-bit left shift, this gives a range of +/- 128 MB. Longer-distance offsets would cause an impossible relocation record to be emitted (or rather, a record that a more sophisticated linker would fix up by inserting a shim/veneer). This commit adds a notion of "relocation distance" in the MachInst backends, and provides this information for every call target and symbol reference. The intent is that backends on architectures like AArch64, where there are different offset sizes / addressing strategies to choose from, can either emit a regular call or a load-64-bit-constant / call-indirect sequence, as necessary. This avoids the need to implement complex linking behavior. The MachInst driver code provides this information based on the "colocated" bit in the CLIF symbol references, which appears to have been designed for this purpose, or at least a similar one. Combined with the `use_colocated_libcalls` setting, this allows client code to ensure that library calls can link to library code at any location in the address space. Separately, the `simplejit` example did not handle `Arm64Call`; rather than doing so, it appears all that is necessary to get its tests to pass is to set the `use_colocated_libcalls` flag to false, to make use of the above change. This fixes the `libcall_function` unit-test in this crate.
1 parent 6ef106f commit e06a50f

File tree

10 files changed

+114
-23
lines changed

10 files changed

+114
-23
lines changed

cranelift/codegen/src/ir/extfunc.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
use crate::ir::{ArgumentLoc, ExternalName, SigRef, Type};
99
use crate::isa::{CallConv, RegInfo, RegUnit};
10+
use crate::machinst::RelocDistance;
1011
use alloc::vec::Vec;
1112
use core::fmt;
1213
use core::str::FromStr;
@@ -366,6 +367,16 @@ pub struct ExtFuncData {
366367
/// Will this function be defined nearby, such that it will always be a certain distance away,
367368
/// after linking? If so, references to it can avoid going through a GOT or PLT. Note that
368369
/// symbols meant to be preemptible cannot be considered colocated.
370+
///
371+
/// If `true`, some backends may use relocation forms that have limited range. The exact
372+
/// distance depends on the code model in use. Currently on AArch64, for example, Cranelift
373+
/// uses a custom code model supporting up to +/- 128MB displacements. If it is unknown how
374+
/// far away the target will be, it is best not to set the `colocated` flag; in general, this
375+
/// flag is best used when the target is known to be in the same unit of code generation, such
376+
/// as a Wasm module.
377+
///
378+
/// See the documentation for [`RelocDistance`](machinst::RelocDistance) for more details. A
379+
/// `colocated` flag value of `true` implies `RelocDistance::Near`.
369380
pub colocated: bool,
370381
}
371382

@@ -378,6 +389,17 @@ impl fmt::Display for ExtFuncData {
378389
}
379390
}
380391

392+
impl ExtFuncData {
393+
/// Return an estimate of the distance to the referred-to function symbol.
394+
pub fn reloc_distance(&self) -> RelocDistance {
395+
if self.colocated {
396+
RelocDistance::Near
397+
} else {
398+
RelocDistance::Far
399+
}
400+
}
401+
}
402+
381403
#[cfg(test)]
382404
mod tests {
383405
use super::*;

cranelift/codegen/src/ir/globalvalue.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use crate::ir::immediates::{Imm64, Offset32};
44
use crate::ir::{ExternalName, GlobalValue, Type};
55
use crate::isa::TargetIsa;
6+
use crate::machinst::RelocDistance;
67
use core::fmt;
78

89
/// Information about a global value declaration.
@@ -62,6 +63,10 @@ pub enum GlobalValueData {
6263
/// Will this symbol be defined nearby, such that it will always be a certain distance
6364
/// away, after linking? If so, references to it can avoid going through a GOT. Note that
6465
/// symbols meant to be preemptible cannot be colocated.
66+
///
67+
/// If `true`, some backends may use relocation forms that have limited range: for example,
68+
/// a +/- 2^27-byte range on AArch64. See the documentation for
69+
/// [`RelocDistance`](machinst::RelocDistance) for more details.
6570
colocated: bool,
6671

6772
/// Does this symbol refer to a thread local storage value?
@@ -85,6 +90,20 @@ impl GlobalValueData {
8590
Self::IAddImm { global_type, .. } | Self::Load { global_type, .. } => global_type,
8691
}
8792
}
93+
94+
/// IF this global references a symbol, return an estimate of the relocation distance,
95+
/// based on the `colocated` flag.
96+
pub fn maybe_reloc_distance(&self) -> Option<RelocDistance> {
97+
match self {
98+
&GlobalValueData::Symbol {
99+
colocated: true, ..
100+
} => Some(RelocDistance::Near),
101+
&GlobalValueData::Symbol {
102+
colocated: false, ..
103+
} => Some(RelocDistance::Far),
104+
_ => None,
105+
}
106+
}
88107
}
89108

90109
impl fmt::Display for GlobalValueData {

cranelift/codegen/src/isa/aarch64/abi.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,7 @@ impl ABIBody for AArch64ABIBody {
10611061
}
10621062

10631063
enum CallDest {
1064-
ExtName(ir::ExternalName),
1064+
ExtName(ir::ExternalName, RelocDistance),
10651065
Reg(Reg),
10661066
}
10671067

@@ -1102,6 +1102,7 @@ impl AArch64ABICall {
11021102
pub fn from_func(
11031103
sig: &ir::Signature,
11041104
extname: &ir::ExternalName,
1105+
dist: RelocDistance,
11051106
loc: ir::SourceLoc,
11061107
) -> AArch64ABICall {
11071108
let sig = ABISig::from_func_sig(sig);
@@ -1110,7 +1111,7 @@ impl AArch64ABICall {
11101111
sig,
11111112
uses,
11121113
defs,
1113-
dest: CallDest::ExtName(extname.clone()),
1114+
dest: CallDest::ExtName(extname.clone(), dist),
11141115
loc,
11151116
opcode: ir::Opcode::Call,
11161117
}
@@ -1207,13 +1208,28 @@ impl ABICall for AArch64ABICall {
12071208
fn gen_call(&self) -> Vec<Inst> {
12081209
let (uses, defs) = (self.uses.clone(), self.defs.clone());
12091210
match &self.dest {
1210-
&CallDest::ExtName(ref name) => vec![Inst::Call {
1211+
&CallDest::ExtName(ref name, RelocDistance::Near) => vec![Inst::Call {
12111212
dest: name.clone(),
12121213
uses,
12131214
defs,
12141215
loc: self.loc,
12151216
opcode: self.opcode,
12161217
}],
1218+
&CallDest::ExtName(ref name, RelocDistance::Far) => vec![
1219+
Inst::LoadExtName {
1220+
rd: writable_spilltmp_reg(),
1221+
name: name.clone(),
1222+
offset: 0,
1223+
srcloc: self.loc,
1224+
},
1225+
Inst::CallInd {
1226+
rn: spilltmp_reg(),
1227+
uses,
1228+
defs,
1229+
loc: self.loc,
1230+
opcode: self.opcode,
1231+
},
1232+
],
12171233
&CallDest::Reg(reg) => vec![Inst::CallInd {
12181234
rn: reg,
12191235
uses,

cranelift/codegen/src/isa/aarch64/inst/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,10 @@ pub enum Inst {
612612
cond: Cond,
613613
},
614614

615-
/// A machine call instruction.
615+
/// A machine call instruction. N.B.: this allows only a +/- 128MB offset (it uses a relocation
616+
/// of type `Reloc::Arm64Call`); if the destination distance is not `RelocDistance::Near`, the
617+
/// code should use a `LoadExtName` / `CallInd` sequence instead, allowing an arbitrary 64-bit
618+
/// target.
616619
Call {
617620
dest: ExternalName,
618621
uses: Set<Reg>,

cranelift/codegen/src/isa/aarch64/lower_inst.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,7 +1233,8 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(ctx: &mut C, insn: IRIns
12331233

12341234
Opcode::FuncAddr => {
12351235
let rd = output_to_reg(ctx, outputs[0]);
1236-
let extname = ctx.call_target(insn).unwrap().clone();
1236+
let (extname, _) = ctx.call_target(insn).unwrap();
1237+
let extname = extname.clone();
12371238
let loc = ctx.srcloc(insn);
12381239
ctx.emit(Inst::LoadExtName {
12391240
rd,
@@ -1249,7 +1250,7 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(ctx: &mut C, insn: IRIns
12491250

12501251
Opcode::SymbolValue => {
12511252
let rd = output_to_reg(ctx, outputs[0]);
1252-
let (extname, offset) = ctx.symbol_value(insn).unwrap();
1253+
let (extname, _, offset) = ctx.symbol_value(insn).unwrap();
12531254
let extname = extname.clone();
12541255
let loc = ctx.srcloc(insn);
12551256
ctx.emit(Inst::LoadExtName {
@@ -1264,12 +1265,15 @@ pub(crate) fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(ctx: &mut C, insn: IRIns
12641265
let loc = ctx.srcloc(insn);
12651266
let (abi, inputs) = match op {
12661267
Opcode::Call => {
1267-
let extname = ctx.call_target(insn).unwrap();
1268+
let (extname, dist) = ctx.call_target(insn).unwrap();
12681269
let extname = extname.clone();
12691270
let sig = ctx.call_sig(insn).unwrap();
12701271
assert!(inputs.len() == sig.params.len());
12711272
assert!(outputs.len() == sig.returns.len());
1272-
(AArch64ABICall::from_func(sig, &extname, loc), &inputs[..])
1273+
(
1274+
AArch64ABICall::from_func(sig, &extname, dist, loc),
1275+
&inputs[..],
1276+
)
12731277
}
12741278
Opcode::CallIndirect => {
12751279
let ptr = input_to_reg(ctx, inputs[0], NarrowValueMode::ZeroExtend64);

cranelift/codegen/src/machinst/lower.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,15 @@ pub trait LowerCtx {
6767
fn bb_param(&self, bb: Block, idx: usize) -> Reg;
6868
/// Get the register for a return value.
6969
fn retval(&self, idx: usize) -> Writable<Reg>;
70-
/// Get the target for a call instruction, as an `ExternalName`.
71-
fn call_target<'b>(&'b self, ir_inst: Inst) -> Option<&'b ExternalName>;
70+
/// Get the target for a call instruction, as an `ExternalName`. Returns a tuple
71+
/// providing this name and the "relocation distance", i.e., whether the backend
72+
/// can assume the target will be "nearby" (within some small offset) or an
73+
/// arbitrary address. (This comes from the `colocated` bit in the CLIF.)
74+
fn call_target<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, RelocDistance)>;
7275
/// Get the signature for a call or call-indirect instruction.
7376
fn call_sig<'b>(&'b self, ir_inst: Inst) -> Option<&'b Signature>;
74-
/// Get the symbol name and offset for a symbol_value instruction.
75-
fn symbol_value<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, i64)>;
77+
/// Get the symbol name, relocation distance estimate, and offset for a symbol_value instruction.
78+
fn symbol_value<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, RelocDistance, i64)>;
7679
/// Returns the memory flags of a given memory access.
7780
fn memflags(&self, ir_inst: Inst) -> Option<MemFlags>;
7881
/// Get the source location for a given instruction.
@@ -122,6 +125,18 @@ pub struct Lower<'func, I: VCodeInst> {
122125
next_vreg: u32,
123126
}
124127

128+
/// Notion of "relocation distance". This gives an estimate of how far away a symbol will be from a
129+
/// reference.
130+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
131+
pub enum RelocDistance {
132+
/// Target of relocation is "nearby". The threshold for this is fuzzy but should be interpreted
133+
/// as approximately "within the compiled output of one module"; e.g., within AArch64's +/-
134+
/// 128MB offset. If unsure, use `Far` instead.
135+
Near,
136+
/// Target of relocation could be anywhere in the address space.
137+
Far,
138+
}
139+
125140
fn alloc_vreg(
126141
value_regs: &mut SecondaryMap<Value, Reg>,
127142
regclass: RegClass,
@@ -647,13 +662,17 @@ impl<'func, I: VCodeInst> LowerCtx for Lower<'func, I> {
647662
Writable::from_reg(self.retval_regs[idx].0)
648663
}
649664

650-
/// Get the target for a call instruction, as an `ExternalName`.
651-
fn call_target<'b>(&'b self, ir_inst: Inst) -> Option<&'b ExternalName> {
665+
/// Get the target for a call instruction, as an `ExternalName`. Returns a tuple
666+
/// providing this name and the "relocation distance", i.e., whether the backend
667+
/// can assume the target will be "nearby" (within some small offset) or an
668+
/// arbitrary address. (This comes from the `colocated` bit in the CLIF.)
669+
fn call_target<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, RelocDistance)> {
652670
match &self.f.dfg[ir_inst] {
653671
&InstructionData::Call { func_ref, .. }
654672
| &InstructionData::FuncAddr { func_ref, .. } => {
655673
let funcdata = &self.f.dfg.ext_funcs[func_ref];
656-
Some(&funcdata.name)
674+
let dist = funcdata.reloc_distance();
675+
Some((&funcdata.name, dist))
657676
}
658677
_ => None,
659678
}
@@ -670,8 +689,8 @@ impl<'func, I: VCodeInst> LowerCtx for Lower<'func, I> {
670689
}
671690
}
672691

673-
/// Get the symbol name and offset for a symbol_value instruction.
674-
fn symbol_value<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, i64)> {
692+
/// Get the symbol name, relocation distance estimate, and offset for a symbol_value instruction.
693+
fn symbol_value<'b>(&'b self, ir_inst: Inst) -> Option<(&'b ExternalName, RelocDistance, i64)> {
675694
match &self.f.dfg[ir_inst] {
676695
&InstructionData::UnaryGlobalValue { global_value, .. } => {
677696
let gvdata = &self.f.global_values[global_value];
@@ -682,7 +701,8 @@ impl<'func, I: VCodeInst> LowerCtx for Lower<'func, I> {
682701
..
683702
} => {
684703
let offset = offset.bits();
685-
Some((name, offset))
704+
let dist = gvdata.maybe_reloc_distance().unwrap();
705+
Some((name, dist, offset))
686706
}
687707
_ => None,
688708
}

cranelift/filetests/filetests/vcode/aarch64/call.clif

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ block0(v0: i64):
1111

1212
; check: stp fp, lr, [sp, #-16]!
1313
; nextln: mov fp, sp
14-
; nextln: bl 0
14+
; nextln: ldr x15, 8 ; b 12 ; data
15+
; nextln: blr x15
1516
; nextln: mov sp, fp
1617
; nextln: ldp fp, lr, [sp], #16
1718
; nextln: ret

cranelift/filetests/filetests/vcode/aarch64/stack-limit.clif

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ block0(v0: i64):
4545
; nextln: subs xzr, sp, x0
4646
; nextln: b.hs 8
4747
; nextln: udf
48-
; nextln: bl 0
48+
; nextln: ldr x15
49+
; nextln: blr x15
4950
; nextln: mov sp, fp
5051
; nextln: ldp fp, lr, [sp], #16
5152
; nextln: ret
@@ -68,7 +69,8 @@ block0(v0: i64):
6869
; nextln: subs xzr, sp, x15
6970
; nextln: b.hs 8
7071
; nextln: udf
71-
; nextln: bl 0
72+
; nextln: ldr x15
73+
; nextln: blr x15
7274
; nextln: mov sp, fp
7375
; nextln: ldp fp, lr, [sp], #16
7476
; nextln: ret

cranelift/simplejit/src/backend.rs

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

cranelift/simplejit/tests/basic.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ fn switch_error() {
153153
}
154154

155155
#[test]
156-
#[cfg_attr(target_arch = "aarch64", should_panic)] // FIXME(#1521)
157156
fn libcall_function() {
158157
let mut module: Module<SimpleJITBackend> =
159158
Module::new(SimpleJITBuilder::new(default_libcall_names()));

0 commit comments

Comments
 (0)