Add ELF TLS support in new x64 backend.
This follows the implementation in the legacy x86 backend, including hardcoded sequence that is compatible with what the linker expects. We could potentially do better here, but it is likely not necessary. Thanks to @bjorn3 for a bugfix to an earlier version of this.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
use crate::binemit::{Addend, Reloc};
|
use crate::binemit::{Addend, Reloc};
|
||||||
use crate::ir::immediates::{Ieee32, Ieee64};
|
use crate::ir::immediates::{Ieee32, Ieee64};
|
||||||
|
use crate::ir::LibCall;
|
||||||
use crate::ir::TrapCode;
|
use crate::ir::TrapCode;
|
||||||
use crate::isa::x64::inst::args::*;
|
use crate::isa::x64::inst::args::*;
|
||||||
use crate::isa::x64::inst::*;
|
use crate::isa::x64::inst::*;
|
||||||
@@ -2988,6 +2989,33 @@ pub(crate) fn emit(
|
|||||||
Inst::EpiloguePlaceholder => {
|
Inst::EpiloguePlaceholder => {
|
||||||
// Generate no code.
|
// Generate no code.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Inst::ElfTlsGetAddr { ref symbol } => {
|
||||||
|
// N.B.: Must be exactly this byte sequence; the linker requires it,
|
||||||
|
// because it must know how to rewrite the bytes.
|
||||||
|
|
||||||
|
// data16 lea gv@tlsgd(%rip),%rdi
|
||||||
|
sink.put1(0x66); // data16
|
||||||
|
sink.put1(0b01001000); // REX.W
|
||||||
|
sink.put1(0x8d); // LEA
|
||||||
|
sink.put1(0x3d); // ModRM byte
|
||||||
|
emit_reloc(sink, state, Reloc::ElfX86_64TlsGd, symbol, -4);
|
||||||
|
sink.put4(0); // offset
|
||||||
|
|
||||||
|
// data16 data16 callq __tls_get_addr-4
|
||||||
|
sink.put1(0x66); // data16
|
||||||
|
sink.put1(0x66); // data16
|
||||||
|
sink.put1(0b01001000); // REX.W
|
||||||
|
sink.put1(0xe8); // CALL
|
||||||
|
emit_reloc(
|
||||||
|
sink,
|
||||||
|
state,
|
||||||
|
Reloc::X86CallPLTRel4,
|
||||||
|
&ExternalName::LibCall(LibCall::ElfTlsGetAddr),
|
||||||
|
-4,
|
||||||
|
);
|
||||||
|
sink.put4(0); // offset
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.clear_post_insn();
|
state.clear_post_insn();
|
||||||
|
|||||||
@@ -3904,6 +3904,17 @@ fn test_x64_emit() {
|
|||||||
let trap_code = TrapCode::UnreachableCodeReached;
|
let trap_code = TrapCode::UnreachableCodeReached;
|
||||||
insns.push((Inst::Ud2 { trap_code }, "0F0B", "ud2 unreachable"));
|
insns.push((Inst::Ud2 { trap_code }, "0F0B", "ud2 unreachable"));
|
||||||
|
|
||||||
|
insns.push((
|
||||||
|
Inst::ElfTlsGetAddr {
|
||||||
|
symbol: ExternalName::User {
|
||||||
|
namespace: 0,
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"66488D3D00000000666648E800000000",
|
||||||
|
"elf_tls_get_addr User { namespace: 0, index: 0 }",
|
||||||
|
));
|
||||||
|
|
||||||
// ========================================================
|
// ========================================================
|
||||||
// Actually run the tests!
|
// Actually run the tests!
|
||||||
let mut flag_builder = settings::builder();
|
let mut flag_builder = settings::builder();
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
use crate::binemit::{CodeOffset, StackMap};
|
use crate::binemit::{CodeOffset, StackMap};
|
||||||
use crate::ir::{types, ExternalName, Opcode, SourceLoc, TrapCode, Type};
|
use crate::ir::{types, ExternalName, Opcode, SourceLoc, TrapCode, Type};
|
||||||
|
use crate::isa::x64::abi::X64ABIMachineSpec;
|
||||||
use crate::isa::x64::settings as x64_settings;
|
use crate::isa::x64::settings as x64_settings;
|
||||||
|
use crate::isa::CallConv;
|
||||||
use crate::machinst::*;
|
use crate::machinst::*;
|
||||||
use crate::{settings, settings::Flags, CodegenError, CodegenResult};
|
use crate::{settings, settings::Flags, CodegenError, CodegenResult};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
@@ -474,6 +476,10 @@ pub enum Inst {
|
|||||||
/// reports its own `def`s/`use`s/`mod`s; this adds complexity (the instruction list is no
|
/// reports its own `def`s/`use`s/`mod`s; this adds complexity (the instruction list is no
|
||||||
/// longer flat) and requires knowledge about semantics and initial-value independence anyway.
|
/// longer flat) and requires knowledge about semantics and initial-value independence anyway.
|
||||||
XmmUninitializedValue { dst: Writable<Reg> },
|
XmmUninitializedValue { dst: Writable<Reg> },
|
||||||
|
|
||||||
|
/// A call to the `ElfTlsGetAddr` libcall. Returns address
|
||||||
|
/// of TLS symbol in rax.
|
||||||
|
ElfTlsGetAddr { symbol: ExternalName },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn low32_will_sign_extend_to_64(x: u64) -> bool {
|
pub(crate) fn low32_will_sign_extend_to_64(x: u64) -> bool {
|
||||||
@@ -532,7 +538,8 @@ impl Inst {
|
|||||||
| Inst::XmmCmpRmR { .. }
|
| Inst::XmmCmpRmR { .. }
|
||||||
| Inst::XmmLoadConst { .. }
|
| Inst::XmmLoadConst { .. }
|
||||||
| Inst::XmmMinMaxSeq { .. }
|
| Inst::XmmMinMaxSeq { .. }
|
||||||
| Inst::XmmUninitializedValue { .. } => None,
|
| Inst::XmmUninitializedValue { .. }
|
||||||
|
| Inst::ElfTlsGetAddr { .. } => None,
|
||||||
|
|
||||||
// These use dynamic SSE opcodes.
|
// These use dynamic SSE opcodes.
|
||||||
Inst::GprToXmm { op, .. }
|
Inst::GprToXmm { op, .. }
|
||||||
@@ -1780,6 +1787,10 @@ impl PrettyPrint for Inst {
|
|||||||
Inst::Hlt => "hlt".into(),
|
Inst::Hlt => "hlt".into(),
|
||||||
|
|
||||||
Inst::Ud2 { trap_code } => format!("ud2 {}", trap_code),
|
Inst::Ud2 { trap_code } => format!("ud2 {}", trap_code),
|
||||||
|
|
||||||
|
Inst::ElfTlsGetAddr { ref symbol } => {
|
||||||
|
format!("elf_tls_get_addr {:?}", symbol)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2039,6 +2050,18 @@ fn x64_get_regs(inst: &Inst, collector: &mut RegUsageCollector) {
|
|||||||
| Inst::Fence { .. } => {
|
| Inst::Fence { .. } => {
|
||||||
// No registers are used.
|
// No registers are used.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Inst::ElfTlsGetAddr { .. } => {
|
||||||
|
// All caller-saves are clobbered.
|
||||||
|
//
|
||||||
|
// We use the SysV calling convention here because the
|
||||||
|
// pseudoinstruction (and relocation that it emits) is specific to
|
||||||
|
// ELF systems; other x86-64 targets with other conventions (i.e.,
|
||||||
|
// Windows) use different TLS strategies.
|
||||||
|
for reg in X64ABIMachineSpec::get_regs_clobbered_by_call(CallConv::SystemV) {
|
||||||
|
collector.add_def(reg);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2425,6 +2448,7 @@ fn x64_map_regs<RUM: RegUsageMapper>(inst: &mut Inst, mapper: &RUM) {
|
|||||||
| Inst::Ud2 { .. }
|
| Inst::Ud2 { .. }
|
||||||
| Inst::Hlt
|
| Inst::Hlt
|
||||||
| Inst::AtomicRmwSeq { .. }
|
| Inst::AtomicRmwSeq { .. }
|
||||||
|
| Inst::ElfTlsGetAddr { .. }
|
||||||
| Inst::Fence { .. } => {
|
| Inst::Fence { .. } => {
|
||||||
// Instruction doesn't explicitly mention any regs, so it can't have any virtual
|
// Instruction doesn't explicitly mention any regs, so it can't have any virtual
|
||||||
// regs that we'd need to remap. Hence no action required.
|
// regs that we'd need to remap. Hence no action required.
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::isa::{x64::X64Backend, CallConv};
|
|||||||
use crate::machinst::lower::*;
|
use crate::machinst::lower::*;
|
||||||
use crate::machinst::*;
|
use crate::machinst::*;
|
||||||
use crate::result::CodegenResult;
|
use crate::result::CodegenResult;
|
||||||
use crate::settings::Flags;
|
use crate::settings::{Flags, TlsModel};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use cranelift_codegen_shared::condcodes::CondCode;
|
use cranelift_codegen_shared::condcodes::CondCode;
|
||||||
@@ -5324,6 +5324,22 @@ fn lower_insn_to_regs<C: LowerCtx<I = Inst>>(
|
|||||||
ctx.emit(Inst::gen_move(dst_hi, src.regs()[1], types::I64));
|
ctx.emit(Inst::gen_move(dst_hi, src.regs()[1], types::I64));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Opcode::TlsValue => match flags.tls_model() {
|
||||||
|
TlsModel::ElfGd => {
|
||||||
|
let dst = get_output_reg(ctx, outputs[0]).only_reg().unwrap();
|
||||||
|
let (name, _, _) = ctx.symbol_value(insn).unwrap();
|
||||||
|
let symbol = name.clone();
|
||||||
|
ctx.emit(Inst::ElfTlsGetAddr { symbol });
|
||||||
|
ctx.emit(Inst::gen_move(dst, regs::rax(), types::I64));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
todo!(
|
||||||
|
"Unimplemented TLS model in x64 backend: {:?}",
|
||||||
|
flags.tls_model()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
Opcode::IaddImm
|
Opcode::IaddImm
|
||||||
| Opcode::ImulImm
|
| Opcode::ImulImm
|
||||||
| Opcode::UdivImm
|
| Opcode::UdivImm
|
||||||
|
|||||||
19
cranelift/filetests/filetests/isa/x64/tls_elf.clif
Normal file
19
cranelift/filetests/filetests/isa/x64/tls_elf.clif
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
test compile
|
||||||
|
set tls_model=elf_gd
|
||||||
|
target x86_64
|
||||||
|
feature "experimental_x64"
|
||||||
|
|
||||||
|
function u0:0(i32) -> i64 {
|
||||||
|
gv0 = symbol colocated tls u1:0
|
||||||
|
|
||||||
|
block0(v0: i32):
|
||||||
|
v1 = global_value.i64 gv0
|
||||||
|
return v1
|
||||||
|
}
|
||||||
|
|
||||||
|
; check: pushq %rbp
|
||||||
|
; nextln: movq %rsp, %rbp
|
||||||
|
; nextln: elf_tls_get_addr User { namespace: 1, index: 0 }
|
||||||
|
; nextln: movq %rbp, %rsp
|
||||||
|
; nextln: popq %rbp
|
||||||
|
; nextln: ret
|
||||||
Reference in New Issue
Block a user