Add cranelift-object
This commit is contained in:
584
cranelift/object/src/backend.rs
Normal file
584
cranelift/object/src/backend.rs
Normal file
@@ -0,0 +1,584 @@
|
||||
//! Defines `ObjectBackend`.
|
||||
|
||||
use crate::traps::{ObjectTrapSink, ObjectTrapSite};
|
||||
use cranelift_codegen::binemit::{
|
||||
Addend, CodeOffset, NullStackmapSink, NullTrapSink, Reloc, RelocSink,
|
||||
};
|
||||
use cranelift_codegen::entity::SecondaryMap;
|
||||
use cranelift_codegen::isa::TargetIsa;
|
||||
use cranelift_codegen::{self, binemit, ir};
|
||||
use cranelift_module::{
|
||||
Backend, DataContext, DataDescription, DataId, FuncId, Init, Linkage, ModuleNamespace,
|
||||
ModuleResult,
|
||||
};
|
||||
use object::write::{Object, Relocation, SectionId, StandardSection, Symbol, SymbolId};
|
||||
use object::{RelocationEncoding, RelocationKind, SymbolKind, SymbolScope};
|
||||
use std::collections::HashMap;
|
||||
use target_lexicon::PointerWidth;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Setting to enable collection of traps. Setting this to `Enabled` in
|
||||
/// `ObjectBuilder` means that `ObjectProduct` will contains trap sites.
|
||||
pub enum ObjectTrapCollection {
|
||||
/// `ObjectProduct::traps` will be empty
|
||||
Disabled,
|
||||
/// `ObjectProduct::traps` will contain trap sites
|
||||
Enabled,
|
||||
}
|
||||
|
||||
/// A builder for `ObjectBackend`.
|
||||
pub struct ObjectBuilder {
|
||||
isa: Box<dyn TargetIsa>,
|
||||
name: String,
|
||||
collect_traps: ObjectTrapCollection,
|
||||
libcall_names: Box<dyn Fn(ir::LibCall) -> String>,
|
||||
function_alignment: u64,
|
||||
}
|
||||
|
||||
impl ObjectBuilder {
|
||||
/// Create a new `ObjectBuilder` using the given Cranelift target, that
|
||||
/// can be passed to
|
||||
/// [`Module::new`](cranelift_module/struct.Module.html#method.new].
|
||||
///
|
||||
/// `collect_traps` setting determines whether trap information is collected in the
|
||||
/// `ObjectProduct`.
|
||||
///
|
||||
/// The `libcall_names` function provides a way to translate `cranelift_codegen`'s `ir::LibCall`
|
||||
/// enum to symbols. LibCalls are inserted in the IR as part of the legalization for certain
|
||||
/// floating point instructions, and for stack probes. If you don't know what to use for this
|
||||
/// argument, use `cranelift_module::default_libcall_names()`.
|
||||
pub fn new(
|
||||
isa: Box<dyn TargetIsa>,
|
||||
name: String,
|
||||
collect_traps: ObjectTrapCollection,
|
||||
libcall_names: Box<dyn Fn(ir::LibCall) -> String>,
|
||||
) -> ModuleResult<Self> {
|
||||
Ok(Self {
|
||||
isa,
|
||||
name,
|
||||
collect_traps,
|
||||
libcall_names,
|
||||
function_alignment: 1,
|
||||
})
|
||||
}
|
||||
|
||||
/// Set the alignment used for functions.
|
||||
pub fn function_alignment(&mut self, alignment: u64) -> &mut Self {
|
||||
self.function_alignment = alignment;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A `ObjectBackend` implements `Backend` and emits ".o" files using the `object` library.
|
||||
///
|
||||
/// See the `ObjectBuilder` for a convenient way to construct `ObjectBackend` instances.
|
||||
pub struct ObjectBackend {
|
||||
isa: Box<dyn TargetIsa>,
|
||||
object: Object,
|
||||
functions: SecondaryMap<FuncId, Option<SymbolId>>,
|
||||
data_objects: SecondaryMap<DataId, Option<SymbolId>>,
|
||||
traps: SecondaryMap<FuncId, Vec<ObjectTrapSite>>,
|
||||
libcalls: HashMap<ir::LibCall, SymbolId>,
|
||||
libcall_names: Box<dyn Fn(ir::LibCall) -> String>,
|
||||
collect_traps: ObjectTrapCollection,
|
||||
function_alignment: u64,
|
||||
}
|
||||
|
||||
impl Backend for ObjectBackend {
|
||||
type Builder = ObjectBuilder;
|
||||
|
||||
type CompiledFunction = ObjectCompiledFunction;
|
||||
type CompiledData = ObjectCompiledData;
|
||||
|
||||
// There's no need to return individual artifacts; we're writing them into
|
||||
// the output file instead.
|
||||
type FinalizedFunction = ();
|
||||
type FinalizedData = ();
|
||||
|
||||
type Product = ObjectProduct;
|
||||
|
||||
/// Create a new `ObjectBackend` using the given Cranelift target.
|
||||
fn new(builder: ObjectBuilder) -> Self {
|
||||
let triple = builder.isa.triple();
|
||||
let mut object = Object::new(triple.binary_format, triple.architecture);
|
||||
object.add_file_symbol(builder.name.as_bytes().to_vec());
|
||||
Self {
|
||||
isa: builder.isa,
|
||||
object,
|
||||
functions: SecondaryMap::new(),
|
||||
data_objects: SecondaryMap::new(),
|
||||
traps: SecondaryMap::new(),
|
||||
libcalls: HashMap::new(),
|
||||
libcall_names: builder.libcall_names,
|
||||
collect_traps: builder.collect_traps,
|
||||
function_alignment: builder.function_alignment,
|
||||
}
|
||||
}
|
||||
|
||||
fn isa(&self) -> &dyn TargetIsa {
|
||||
&*self.isa
|
||||
}
|
||||
|
||||
fn declare_function(&mut self, id: FuncId, name: &str, linkage: Linkage) {
|
||||
let (scope, weak) = translate_linkage(linkage);
|
||||
|
||||
if let Some(function) = self.functions[id] {
|
||||
let symbol = self.object.symbol_mut(function);
|
||||
symbol.scope = scope;
|
||||
symbol.weak = weak;
|
||||
} else {
|
||||
let symbol_id = self.object.add_symbol(Symbol {
|
||||
name: name.as_bytes().to_vec(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
kind: SymbolKind::Text,
|
||||
scope,
|
||||
weak,
|
||||
section: None,
|
||||
});
|
||||
self.functions[id] = Some(symbol_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn declare_data(
|
||||
&mut self,
|
||||
id: DataId,
|
||||
name: &str,
|
||||
linkage: Linkage,
|
||||
_writable: bool,
|
||||
_align: Option<u8>,
|
||||
) {
|
||||
let (scope, weak) = translate_linkage(linkage);
|
||||
|
||||
if let Some(data) = self.data_objects[id] {
|
||||
let symbol = self.object.symbol_mut(data);
|
||||
symbol.scope = scope;
|
||||
symbol.weak = weak;
|
||||
} else {
|
||||
let symbol_id = self.object.add_symbol(Symbol {
|
||||
name: name.as_bytes().to_vec(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
kind: SymbolKind::Data,
|
||||
scope,
|
||||
weak,
|
||||
section: None,
|
||||
});
|
||||
self.data_objects[id] = Some(symbol_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn define_function(
|
||||
&mut self,
|
||||
func_id: FuncId,
|
||||
_name: &str,
|
||||
ctx: &cranelift_codegen::Context,
|
||||
_namespace: &ModuleNamespace<Self>,
|
||||
code_size: u32,
|
||||
) -> ModuleResult<ObjectCompiledFunction> {
|
||||
let mut code: Vec<u8> = vec![0; code_size as usize];
|
||||
let mut reloc_sink = ObjectRelocSink::default();
|
||||
let mut trap_sink = ObjectTrapSink::default();
|
||||
let mut stackmap_sink = NullStackmapSink {};
|
||||
|
||||
if let ObjectTrapCollection::Enabled = self.collect_traps {
|
||||
unsafe {
|
||||
ctx.emit_to_memory(
|
||||
&*self.isa,
|
||||
code.as_mut_ptr(),
|
||||
&mut reloc_sink,
|
||||
&mut trap_sink,
|
||||
&mut stackmap_sink,
|
||||
)
|
||||
};
|
||||
} else {
|
||||
let mut trap_sink = NullTrapSink {};
|
||||
unsafe {
|
||||
ctx.emit_to_memory(
|
||||
&*self.isa,
|
||||
code.as_mut_ptr(),
|
||||
&mut reloc_sink,
|
||||
&mut trap_sink,
|
||||
&mut stackmap_sink,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
let symbol = self.functions[func_id].unwrap();
|
||||
let section = self.object.section_id(StandardSection::Text);
|
||||
let offset = self
|
||||
.object
|
||||
.add_symbol_data(symbol, section, &code, self.function_alignment);
|
||||
self.traps[func_id] = trap_sink.sites;
|
||||
Ok(ObjectCompiledFunction {
|
||||
offset,
|
||||
size: code_size,
|
||||
section,
|
||||
relocs: reloc_sink.relocs,
|
||||
})
|
||||
}
|
||||
|
||||
fn define_data(
|
||||
&mut self,
|
||||
data_id: DataId,
|
||||
_name: &str,
|
||||
writable: bool,
|
||||
align: Option<u8>,
|
||||
data_ctx: &DataContext,
|
||||
_namespace: &ModuleNamespace<Self>,
|
||||
) -> ModuleResult<ObjectCompiledData> {
|
||||
let &DataDescription {
|
||||
ref init,
|
||||
ref function_decls,
|
||||
ref data_decls,
|
||||
ref function_relocs,
|
||||
ref data_relocs,
|
||||
} = data_ctx.description();
|
||||
|
||||
let size = init.size();
|
||||
let mut data = Vec::with_capacity(size);
|
||||
match *init {
|
||||
Init::Uninitialized => {
|
||||
panic!("data is not initialized yet");
|
||||
}
|
||||
Init::Zeros { .. } => {
|
||||
data.resize(size, 0);
|
||||
}
|
||||
Init::Bytes { ref contents } => {
|
||||
data.extend_from_slice(contents);
|
||||
}
|
||||
}
|
||||
|
||||
let reloc_size = match self.isa.triple().pointer_width().unwrap() {
|
||||
PointerWidth::U16 => 16,
|
||||
PointerWidth::U32 => 32,
|
||||
PointerWidth::U64 => 64,
|
||||
};
|
||||
let mut relocs = Vec::new();
|
||||
for &(offset, id) in function_relocs {
|
||||
relocs.push(RelocRecord {
|
||||
offset,
|
||||
name: function_decls[id].clone(),
|
||||
kind: RelocationKind::Absolute,
|
||||
encoding: RelocationEncoding::Generic,
|
||||
size: reloc_size,
|
||||
addend: 0,
|
||||
});
|
||||
}
|
||||
for &(offset, id, addend) in data_relocs {
|
||||
relocs.push(RelocRecord {
|
||||
offset,
|
||||
name: data_decls[id].clone(),
|
||||
kind: RelocationKind::Absolute,
|
||||
encoding: RelocationEncoding::Generic,
|
||||
size: reloc_size,
|
||||
addend,
|
||||
});
|
||||
}
|
||||
|
||||
let symbol = self.data_objects[data_id].unwrap();
|
||||
let section = self.object.section_id(if writable {
|
||||
StandardSection::Data
|
||||
} else if relocs.is_empty() {
|
||||
StandardSection::ReadOnlyData
|
||||
} else {
|
||||
StandardSection::ReadOnlyDataWithRel
|
||||
});
|
||||
let offset =
|
||||
self.object
|
||||
.add_symbol_data(symbol, section, &data, u64::from(align.unwrap_or(1)));
|
||||
Ok(ObjectCompiledData {
|
||||
offset,
|
||||
section,
|
||||
relocs,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_data_funcaddr(
|
||||
&mut self,
|
||||
_data: &mut ObjectCompiledData,
|
||||
_offset: usize,
|
||||
_what: ir::FuncRef,
|
||||
) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn write_data_dataaddr(
|
||||
&mut self,
|
||||
_data: &mut ObjectCompiledData,
|
||||
_offset: usize,
|
||||
_what: ir::GlobalValue,
|
||||
_usize: binemit::Addend,
|
||||
) {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn finalize_function(
|
||||
&mut self,
|
||||
_id: FuncId,
|
||||
func: &ObjectCompiledFunction,
|
||||
namespace: &ModuleNamespace<Self>,
|
||||
) {
|
||||
for &RelocRecord {
|
||||
offset,
|
||||
ref name,
|
||||
kind,
|
||||
encoding,
|
||||
size,
|
||||
addend,
|
||||
} in &func.relocs
|
||||
{
|
||||
let offset = func.offset + offset as u64;
|
||||
let symbol = self.get_symbol(namespace, name);
|
||||
self.object
|
||||
.add_relocation(
|
||||
func.section,
|
||||
Relocation {
|
||||
offset,
|
||||
size,
|
||||
kind,
|
||||
encoding,
|
||||
symbol,
|
||||
addend,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_finalized_function(&self, _func: &ObjectCompiledFunction) {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
fn finalize_data(
|
||||
&mut self,
|
||||
_id: DataId,
|
||||
data: &ObjectCompiledData,
|
||||
namespace: &ModuleNamespace<Self>,
|
||||
) {
|
||||
for &RelocRecord {
|
||||
offset,
|
||||
ref name,
|
||||
kind,
|
||||
encoding,
|
||||
size,
|
||||
addend,
|
||||
} in &data.relocs
|
||||
{
|
||||
let offset = data.offset + offset as u64;
|
||||
let symbol = self.get_symbol(namespace, name);
|
||||
self.object
|
||||
.add_relocation(
|
||||
data.section,
|
||||
Relocation {
|
||||
offset,
|
||||
size,
|
||||
kind,
|
||||
encoding,
|
||||
symbol,
|
||||
addend,
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_finalized_data(&self, _data: &ObjectCompiledData) {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
fn publish(&mut self) {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
fn finish(self) -> ObjectProduct {
|
||||
ObjectProduct {
|
||||
object: self.object,
|
||||
functions: self.functions,
|
||||
data_objects: self.data_objects,
|
||||
traps: self.traps,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectBackend {
|
||||
// This should only be called during finalization because it creates
|
||||
// symbols for missing libcalls.
|
||||
fn get_symbol(
|
||||
&mut self,
|
||||
namespace: &ModuleNamespace<Self>,
|
||||
name: &ir::ExternalName,
|
||||
) -> SymbolId {
|
||||
match *name {
|
||||
ir::ExternalName::User { .. } => {
|
||||
if namespace.is_function(name) {
|
||||
let id = namespace.get_function_id(name);
|
||||
self.functions[id].unwrap()
|
||||
} else {
|
||||
let id = namespace.get_data_id(name);
|
||||
self.data_objects[id].unwrap()
|
||||
}
|
||||
}
|
||||
ir::ExternalName::LibCall(ref libcall) => {
|
||||
let name = (self.libcall_names)(*libcall);
|
||||
if let Some(symbol) = self.object.symbol_id(name.as_bytes()) {
|
||||
symbol
|
||||
} else if let Some(symbol) = self.libcalls.get(libcall) {
|
||||
*symbol
|
||||
} else {
|
||||
let symbol = self.object.add_symbol(Symbol {
|
||||
name: name.as_bytes().to_vec(),
|
||||
value: 0,
|
||||
size: 0,
|
||||
kind: SymbolKind::Text,
|
||||
scope: SymbolScope::Unknown,
|
||||
weak: false,
|
||||
section: None,
|
||||
});
|
||||
self.libcalls.insert(*libcall, symbol);
|
||||
symbol
|
||||
}
|
||||
}
|
||||
_ => panic!("invalid ExternalName {}", name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_linkage(linkage: Linkage) -> (SymbolScope, bool) {
|
||||
let scope = match linkage {
|
||||
Linkage::Import => SymbolScope::Unknown,
|
||||
Linkage::Local => SymbolScope::Compilation,
|
||||
Linkage::Export | Linkage::Preemptible => SymbolScope::Dynamic,
|
||||
};
|
||||
// TODO: this matches rustc_codegen_cranelift, but may be wrong.
|
||||
let weak = linkage == Linkage::Preemptible;
|
||||
(scope, weak)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ObjectCompiledFunction {
|
||||
offset: u64,
|
||||
size: u32,
|
||||
section: SectionId,
|
||||
relocs: Vec<RelocRecord>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ObjectCompiledData {
|
||||
offset: u64,
|
||||
section: SectionId,
|
||||
relocs: Vec<RelocRecord>,
|
||||
}
|
||||
|
||||
/// This is the output of `Module`'s
|
||||
/// [`finish`](../cranelift_module/struct.Module.html#method.finish) function.
|
||||
/// It contains the generated `Object` and other information produced during
|
||||
/// compilation.
|
||||
pub struct ObjectProduct {
|
||||
/// Object artifact with all functions and data from the module defined.
|
||||
pub object: Object,
|
||||
/// Symbol IDs for functions (both declared and defined).
|
||||
pub functions: SecondaryMap<FuncId, Option<SymbolId>>,
|
||||
/// Symbol IDs for data objects (both declared and defined).
|
||||
pub data_objects: SecondaryMap<DataId, Option<SymbolId>>,
|
||||
/// Trap sites for defined functions.
|
||||
pub traps: SecondaryMap<FuncId, Vec<ObjectTrapSite>>,
|
||||
}
|
||||
|
||||
impl ObjectProduct {
|
||||
/// Return the `SymbolId` for the given function.
|
||||
#[inline]
|
||||
pub fn function_symbol(&self, id: FuncId) -> SymbolId {
|
||||
self.functions[id].unwrap()
|
||||
}
|
||||
|
||||
/// Return the `SymbolId` for the given data object.
|
||||
#[inline]
|
||||
pub fn data_symbol(&self, id: DataId) -> SymbolId {
|
||||
self.data_objects[id].unwrap()
|
||||
}
|
||||
|
||||
/// Write the object bytes in memory.
|
||||
#[inline]
|
||||
pub fn emit(self) -> Result<Vec<u8>, String> {
|
||||
self.object.write()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RelocRecord {
|
||||
offset: CodeOffset,
|
||||
name: ir::ExternalName,
|
||||
kind: RelocationKind,
|
||||
encoding: RelocationEncoding,
|
||||
size: u8,
|
||||
addend: Addend,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ObjectRelocSink {
|
||||
relocs: Vec<RelocRecord>,
|
||||
}
|
||||
|
||||
impl RelocSink for ObjectRelocSink {
|
||||
fn reloc_ebb(&mut self, _offset: CodeOffset, _reloc: Reloc, _ebb_offset: CodeOffset) {
|
||||
unimplemented!();
|
||||
}
|
||||
|
||||
fn reloc_external(
|
||||
&mut self,
|
||||
offset: CodeOffset,
|
||||
reloc: Reloc,
|
||||
name: &ir::ExternalName,
|
||||
addend: Addend,
|
||||
) {
|
||||
let (kind, encoding, size) = match reloc {
|
||||
Reloc::Abs4 => (RelocationKind::Absolute, RelocationEncoding::Generic, 32),
|
||||
Reloc::Abs8 => (RelocationKind::Absolute, RelocationEncoding::Generic, 64),
|
||||
Reloc::X86PCRel4 => (RelocationKind::Relative, RelocationEncoding::Generic, 32),
|
||||
Reloc::X86CallPCRel4 => (RelocationKind::Relative, RelocationEncoding::X86Branch, 32),
|
||||
// TODO: Get Cranelift to tell us when we can use
|
||||
// R_X86_64_GOTPCRELX/R_X86_64_REX_GOTPCRELX.
|
||||
Reloc::X86CallPLTRel4 => (
|
||||
RelocationKind::PltRelative,
|
||||
RelocationEncoding::X86Branch,
|
||||
32,
|
||||
),
|
||||
Reloc::X86GOTPCRel4 => (RelocationKind::GotRelative, RelocationEncoding::Generic, 32),
|
||||
// FIXME
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
self.relocs.push(RelocRecord {
|
||||
offset,
|
||||
name: name.clone(),
|
||||
kind,
|
||||
encoding,
|
||||
size,
|
||||
addend,
|
||||
});
|
||||
}
|
||||
|
||||
fn reloc_jt(&mut self, _offset: CodeOffset, reloc: Reloc, _jt: ir::JumpTable) {
|
||||
match reloc {
|
||||
Reloc::X86PCRelRodata4 => {
|
||||
// Not necessary to record this unless we are going to split apart code and its
|
||||
// jumptbl/rodata.
|
||||
}
|
||||
_ => {
|
||||
panic!("Unhandled reloc");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reloc_constant(&mut self, _offset: CodeOffset, reloc: Reloc, _jt: ir::ConstantOffset) {
|
||||
match reloc {
|
||||
Reloc::X86PCRelRodata4 => {
|
||||
// Not necessary to record this unless we are going to split apart code and its
|
||||
// jumptbl/rodata.
|
||||
}
|
||||
_ => {
|
||||
panic!("Unhandled reloc");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user