219 lines
7.7 KiB
Rust
219 lines
7.7 KiB
Rust
//! Binary machine code emission.
|
|
//!
|
|
//! The `binemit` module contains code for translating Cranelift's intermediate representation into
|
|
//! binary machine code.
|
|
|
|
mod memorysink;
|
|
mod stack_map;
|
|
|
|
pub use self::memorysink::{
|
|
MemoryCodeSink, NullRelocSink, NullStackMapSink, NullTrapSink, RelocSink, StackMapSink,
|
|
TrapSink,
|
|
};
|
|
pub use self::stack_map::StackMap;
|
|
use crate::ir::entities::Value;
|
|
use crate::ir::{
|
|
ConstantOffset, ExternalName, Function, Inst, JumpTable, Opcode, SourceLoc, TrapCode,
|
|
};
|
|
use crate::isa::TargetIsa;
|
|
use core::fmt;
|
|
#[cfg(feature = "enable-serde")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
/// Offset in bytes from the beginning of the function.
|
|
///
|
|
/// Cranelift can be used as a cross compiler, so we don't want to use a type like `usize` which
|
|
/// depends on the *host* platform, not the *target* platform.
|
|
pub type CodeOffset = u32;
|
|
|
|
/// Addend to add to the symbol value.
|
|
pub type Addend = i64;
|
|
|
|
/// Relocation kinds for every ISA
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
|
|
pub enum Reloc {
|
|
/// absolute 4-byte
|
|
Abs4,
|
|
/// absolute 8-byte
|
|
Abs8,
|
|
/// x86 PC-relative 4-byte
|
|
X86PCRel4,
|
|
/// x86 PC-relative 4-byte offset to trailing rodata
|
|
X86PCRelRodata4,
|
|
/// x86 call to PC-relative 4-byte
|
|
X86CallPCRel4,
|
|
/// x86 call to PLT-relative 4-byte
|
|
X86CallPLTRel4,
|
|
/// x86 GOT PC-relative 4-byte
|
|
X86GOTPCRel4,
|
|
/// Arm32 call target
|
|
Arm32Call,
|
|
/// Arm64 call target. Encoded as bottom 26 bits of instruction. This
|
|
/// value is sign-extended, multiplied by 4, and added to the PC of
|
|
/// the call instruction to form the destination address.
|
|
Arm64Call,
|
|
/// s390x PC-relative 4-byte offset
|
|
S390xPCRel32Dbl,
|
|
|
|
/// Elf x86_64 32 bit signed PC relative offset to two GOT entries for GD symbol.
|
|
ElfX86_64TlsGd,
|
|
|
|
/// Mach-O x86_64 32 bit signed PC relative offset to a `__thread_vars` entry.
|
|
MachOX86_64Tlv,
|
|
|
|
/// AArch64 TLS GD
|
|
/// Set an ADRP immediate field to the top 21 bits of the final address. Checks for overflow.
|
|
/// This is equivalent to `R_AARCH64_TLSGD_ADR_PAGE21` in the [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#relocations-for-thread-local-storage)
|
|
Aarch64TlsGdAdrPage21,
|
|
|
|
/// AArch64 TLS GD
|
|
/// Set the add immediate field to the low 12 bits of the final address. Does not check for overflow.
|
|
/// This is equivalent to `R_AARCH64_TLSGD_ADD_LO12_NC` in the [aaelf64](https://github.com/ARM-software/abi-aa/blob/2bcab1e3b22d55170c563c3c7940134089176746/aaelf64/aaelf64.rst#relocations-for-thread-local-storage)
|
|
Aarch64TlsGdAddLo12Nc,
|
|
}
|
|
|
|
impl fmt::Display for Reloc {
|
|
/// Display trait implementation drops the arch, since its used in contexts where the arch is
|
|
/// already unambiguous, e.g. clif syntax with isa specified. In other contexts, use Debug.
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
match *self {
|
|
Self::Abs4 => write!(f, "Abs4"),
|
|
Self::Abs8 => write!(f, "Abs8"),
|
|
Self::S390xPCRel32Dbl => write!(f, "PCRel32Dbl"),
|
|
Self::X86PCRel4 => write!(f, "PCRel4"),
|
|
Self::X86PCRelRodata4 => write!(f, "PCRelRodata4"),
|
|
Self::X86CallPCRel4 => write!(f, "CallPCRel4"),
|
|
Self::X86CallPLTRel4 => write!(f, "CallPLTRel4"),
|
|
Self::X86GOTPCRel4 => write!(f, "GOTPCRel4"),
|
|
Self::Arm32Call | Self::Arm64Call => write!(f, "Call"),
|
|
|
|
Self::ElfX86_64TlsGd => write!(f, "ElfX86_64TlsGd"),
|
|
Self::MachOX86_64Tlv => write!(f, "MachOX86_64Tlv"),
|
|
Self::Aarch64TlsGdAdrPage21 => write!(f, "Aarch64TlsGdAdrPage21"),
|
|
Self::Aarch64TlsGdAddLo12Nc => write!(f, "Aarch64TlsGdAddLo12Nc"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Container for information about a vector of compiled code and its supporting read-only data.
|
|
///
|
|
/// The code starts at offset 0 and is followed optionally by relocatable jump tables and copyable
|
|
/// (raw binary) read-only data. Any padding between sections is always part of the section that
|
|
/// precedes the boundary between the sections.
|
|
#[derive(PartialEq)]
|
|
pub struct CodeInfo {
|
|
/// Number of bytes of machine code (the code starts at offset 0).
|
|
pub code_size: CodeOffset,
|
|
|
|
/// Number of bytes of jumptables.
|
|
pub jumptables_size: CodeOffset,
|
|
|
|
/// Number of bytes of rodata.
|
|
pub rodata_size: CodeOffset,
|
|
|
|
/// Number of bytes in total.
|
|
pub total_size: CodeOffset,
|
|
}
|
|
|
|
impl CodeInfo {
|
|
/// Offset of any relocatable jump tables, or equal to rodata if there are no jump tables.
|
|
pub fn jumptables(&self) -> CodeOffset {
|
|
self.code_size
|
|
}
|
|
|
|
/// Offset of any copyable read-only data, or equal to total_size if there are no rodata.
|
|
pub fn rodata(&self) -> CodeOffset {
|
|
self.code_size + self.jumptables_size
|
|
}
|
|
}
|
|
|
|
/// Abstract interface for adding bytes to the code segment.
|
|
///
|
|
/// A `CodeSink` will receive all of the machine code for a function. It also accepts relocations
|
|
/// which are locations in the code section that need to be fixed up when linking.
|
|
pub trait CodeSink {
|
|
/// Get the current position.
|
|
fn offset(&self) -> CodeOffset;
|
|
|
|
/// Add 1 byte to the code section.
|
|
fn put1(&mut self, _: u8);
|
|
|
|
/// Add 2 bytes to the code section.
|
|
fn put2(&mut self, _: u16);
|
|
|
|
/// Add 4 bytes to the code section.
|
|
fn put4(&mut self, _: u32);
|
|
|
|
/// Add 8 bytes to the code section.
|
|
fn put8(&mut self, _: u64);
|
|
|
|
/// Add a relocation referencing an external symbol plus the addend at the current offset.
|
|
fn reloc_external(&mut self, _: SourceLoc, _: Reloc, _: &ExternalName, _: Addend);
|
|
|
|
/// Add a relocation referencing a constant.
|
|
fn reloc_constant(&mut self, _: Reloc, _: ConstantOffset);
|
|
|
|
/// Add a relocation referencing a jump table.
|
|
fn reloc_jt(&mut self, _: Reloc, _: JumpTable);
|
|
|
|
/// Add trap information for the current offset.
|
|
fn trap(&mut self, _: TrapCode, _: SourceLoc);
|
|
|
|
/// Machine code output is complete, jump table data may follow.
|
|
fn begin_jumptables(&mut self);
|
|
|
|
/// Jump table output is complete, raw read-only data may follow.
|
|
fn begin_rodata(&mut self);
|
|
|
|
/// Read-only data output is complete, we're done.
|
|
fn end_codegen(&mut self);
|
|
|
|
/// Add a stack map at the current code offset.
|
|
fn add_stack_map(&mut self, _: &[Value], _: &Function, _: &dyn TargetIsa);
|
|
|
|
/// Add a call site for a call with the given opcode, returning at the current offset.
|
|
fn add_call_site(&mut self, _: Opcode, _: SourceLoc) {
|
|
// Default implementation doesn't need to do anything.
|
|
}
|
|
}
|
|
|
|
/// Emit a function to `sink`, given an instruction emitter function.
|
|
///
|
|
/// This function is called from the `TargetIsa::emit_function()` implementations with the
|
|
/// appropriate instruction emitter.
|
|
pub fn emit_function<CS, EI>(func: &Function, emit_inst: EI, sink: &mut CS, isa: &dyn TargetIsa)
|
|
where
|
|
CS: CodeSink,
|
|
EI: Fn(&Function, Inst, &mut CS, &dyn TargetIsa),
|
|
{
|
|
for block in func.layout.blocks() {
|
|
debug_assert_eq!(func.offsets[block], sink.offset());
|
|
for inst in func.layout.block_insts(block) {
|
|
emit_inst(func, inst, sink, isa);
|
|
}
|
|
}
|
|
|
|
sink.begin_jumptables();
|
|
|
|
// Output jump tables.
|
|
for (jt, jt_data) in func.jump_tables.iter() {
|
|
let jt_offset = func.jt_offsets[jt];
|
|
for block in jt_data.iter() {
|
|
let rel_offset: i32 = func.offsets[*block] as i32 - jt_offset as i32;
|
|
sink.put4(rel_offset as u32)
|
|
}
|
|
}
|
|
|
|
sink.begin_rodata();
|
|
|
|
// Output constants.
|
|
for (_, constant_data) in func.dfg.constants.iter() {
|
|
for byte in constant_data.iter() {
|
|
sink.put1(*byte)
|
|
}
|
|
}
|
|
|
|
sink.end_codegen();
|
|
}
|