From 0f563f786acc21fbca1f7f9a8b2c2f3a4a6f15b3 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sun, 13 Dec 2020 18:05:38 -0800 Subject: [PATCH] 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. --- cranelift/codegen/src/isa/x64/inst/emit.rs | 28 +++++++++++++++++++ .../codegen/src/isa/x64/inst/emit_tests.rs | 11 ++++++++ cranelift/codegen/src/isa/x64/inst/mod.rs | 26 ++++++++++++++++- cranelift/codegen/src/isa/x64/lower.rs | 18 +++++++++++- .../filetests/filetests/isa/x64/tls_elf.clif | 19 +++++++++++++ 5 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 cranelift/filetests/filetests/isa/x64/tls_elf.clif diff --git a/cranelift/codegen/src/isa/x64/inst/emit.rs b/cranelift/codegen/src/isa/x64/inst/emit.rs index 075724d493..e1253b9597 100644 --- a/cranelift/codegen/src/isa/x64/inst/emit.rs +++ b/cranelift/codegen/src/isa/x64/inst/emit.rs @@ -1,5 +1,6 @@ use crate::binemit::{Addend, Reloc}; use crate::ir::immediates::{Ieee32, Ieee64}; +use crate::ir::LibCall; use crate::ir::TrapCode; use crate::isa::x64::inst::args::*; use crate::isa::x64::inst::*; @@ -2988,6 +2989,33 @@ pub(crate) fn emit( Inst::EpiloguePlaceholder => { // 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(); diff --git a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs index 42e38c9cd5..f1e74ae22a 100644 --- a/cranelift/codegen/src/isa/x64/inst/emit_tests.rs +++ b/cranelift/codegen/src/isa/x64/inst/emit_tests.rs @@ -3904,6 +3904,17 @@ fn test_x64_emit() { let trap_code = TrapCode::UnreachableCodeReached; 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! let mut flag_builder = settings::builder(); diff --git a/cranelift/codegen/src/isa/x64/inst/mod.rs b/cranelift/codegen/src/isa/x64/inst/mod.rs index 979c264231..edc7a65109 100644 --- a/cranelift/codegen/src/isa/x64/inst/mod.rs +++ b/cranelift/codegen/src/isa/x64/inst/mod.rs @@ -2,7 +2,9 @@ use crate::binemit::{CodeOffset, StackMap}; 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::CallConv; use crate::machinst::*; use crate::{settings, settings::Flags, CodegenError, CodegenResult}; 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 /// longer flat) and requires knowledge about semantics and initial-value independence anyway. XmmUninitializedValue { dst: Writable }, + + /// 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 { @@ -532,7 +538,8 @@ impl Inst { | Inst::XmmCmpRmR { .. } | Inst::XmmLoadConst { .. } | Inst::XmmMinMaxSeq { .. } - | Inst::XmmUninitializedValue { .. } => None, + | Inst::XmmUninitializedValue { .. } + | Inst::ElfTlsGetAddr { .. } => None, // These use dynamic SSE opcodes. Inst::GprToXmm { op, .. } @@ -1780,6 +1787,10 @@ impl PrettyPrint for Inst { Inst::Hlt => "hlt".into(), 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 { .. } => { // 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(inst: &mut Inst, mapper: &RUM) { | Inst::Ud2 { .. } | Inst::Hlt | Inst::AtomicRmwSeq { .. } + | Inst::ElfTlsGetAddr { .. } | Inst::Fence { .. } => { // 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. diff --git a/cranelift/codegen/src/isa/x64/lower.rs b/cranelift/codegen/src/isa/x64/lower.rs index a25da666b3..da231120bb 100644 --- a/cranelift/codegen/src/isa/x64/lower.rs +++ b/cranelift/codegen/src/isa/x64/lower.rs @@ -12,7 +12,7 @@ use crate::isa::{x64::X64Backend, CallConv}; use crate::machinst::lower::*; use crate::machinst::*; use crate::result::CodegenResult; -use crate::settings::Flags; +use crate::settings::{Flags, TlsModel}; use alloc::boxed::Box; use alloc::vec::Vec; use cranelift_codegen_shared::condcodes::CondCode; @@ -5324,6 +5324,22 @@ fn lower_insn_to_regs>( 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::ImulImm | Opcode::UdivImm diff --git a/cranelift/filetests/filetests/isa/x64/tls_elf.clif b/cranelift/filetests/filetests/isa/x64/tls_elf.clif new file mode 100644 index 0000000000..6136e98e7d --- /dev/null +++ b/cranelift/filetests/filetests/isa/x64/tls_elf.clif @@ -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