Add a Context::emit_to_memory function.

This function will emit the binary machine code into contiguous raw
memory while sending relocations to a RelocSink.

Add a MemoryCodeSink for generating machine code directly into memory
efficiently. Allow the TargetIsa to provide emit_function
implementations that are specialized to the MemoryCodeSink type to avoid
needless small virtual callbacks to put1() et etc.
This commit is contained in:
Jakob Stoklund Olesen
2017-07-17 18:13:05 -07:00
parent 9dc92eb8b3
commit 2f7057b96f
9 changed files with 168 additions and 13 deletions

View File

@@ -51,12 +51,9 @@ impl SubTest for TestCompile {
// Finally verify that the returned code size matches the emitted bytes. // Finally verify that the returned code size matches the emitted bytes.
let mut sink = SizeSink { offset: 0 }; let mut sink = SizeSink { offset: 0 };
for ebb in comp_ctx.func.layout.ebbs() { binemit::emit_function(&comp_ctx.func,
assert_eq!(sink.offset, comp_ctx.func.offsets[ebb]); |func, inst, sink| isa.emit_inst(func, inst, sink),
for inst in comp_ctx.func.layout.ebb_insts(ebb) { &mut sink);
isa.emit_inst(&comp_ctx.func, inst, &mut sink);
}
}
if sink.offset != code_size { if sink.offset != code_size {
return Err(format!("Expected code size {}, got {}", code_size, sink.offset)); return Err(format!("Expected code size {}, got {}", code_size, sink.offset));

View File

@@ -0,0 +1,108 @@
//! Code sink that writes binary machine code into contiguous memory.
//!
//! The `CodeSink` trait is the most general way of extracting binary machine code from Cretonne,
//! and it is implemented by things like the `test binemit` file test driver to generate
//! hexadecimal machine code. The `CodeSink` has some undesirable performance properties because of
//! the dual abstraction: `TargetIsa` is a trait object implemented by each supported ISA, so it
//! can't have any generic functions that could be specialized for each `CodeSink` implementation.
//! This results in many virtual function callbacks (one per `put*` call) when
//! `TargetIsa::emit_inst()` is used.
//!
//! The `MemoryCodeSink` type fixes the performance problem because it is a type known to
//! `TargetIsa` so it can specialize its machine code generation for the type. The trade-off is
//! that a `MemoryCodeSink` will always write binary machine code to raw memory. It forwards any
//! relocations to a `RelocSink` trait object. Relocations are less frequent than the
//! `CodeSink::put*` methods, so the performance impact of the virtual callbacks is less severe.
use ir::{Ebb, FuncRef, JumpTable};
use super::{CodeSink, CodeOffset, Reloc};
use std::ptr::write_unaligned;
/// A `CodeSink` that writes binary machine code directly into memory.
///
/// A `MemoryCodeSink` object should be used when emitting a Cretonne IL function into executable
/// memory. It writes machine code directly to a raw pointer without any bounds checking, so make
/// sure to allocate enough memory for the whole function. The number of bytes required is returned
/// by the `Context::compile()` function.
///
/// Any relocations in the function are forwarded to the `RelocSink` trait object.
///
/// Note that `MemoryCodeSink` writes multi-byte values in the native byte order of the host. This
/// is not the right thing to do for cross compilation.
pub struct MemoryCodeSink<'a> {
data: *mut u8,
offset: isize,
relocs: &'a mut RelocSink,
}
impl<'a> MemoryCodeSink<'a> {
/// Create a new memory code sink that writes a function to the memory pointed to by `data`.
pub fn new(data: *mut u8, relocs: &mut RelocSink) -> MemoryCodeSink {
MemoryCodeSink {
data,
offset: 0,
relocs,
}
}
}
/// A trait for receiving relocations for code that is emitted directly into memory.
pub trait RelocSink {
/// Add a relocation referencing an EBB at the current offset.
fn reloc_ebb(&mut self, CodeOffset, Reloc, Ebb);
/// Add a relocation referencing an external function at the current offset.
fn reloc_func(&mut self, CodeOffset, Reloc, FuncRef);
/// Add a relocation referencing a jump table.
fn reloc_jt(&mut self, CodeOffset, Reloc, JumpTable);
}
impl<'a> CodeSink for MemoryCodeSink<'a> {
fn offset(&self) -> CodeOffset {
self.offset as CodeOffset
}
fn put1(&mut self, x: u8) {
unsafe {
write_unaligned(self.data.offset(self.offset), x);
}
self.offset += 1;
}
fn put2(&mut self, x: u16) {
unsafe {
write_unaligned(self.data.offset(self.offset) as *mut u16, x);
}
self.offset += 2;
}
fn put4(&mut self, x: u32) {
unsafe {
write_unaligned(self.data.offset(self.offset) as *mut u32, x);
}
self.offset += 4;
}
fn put8(&mut self, x: u64) {
unsafe {
write_unaligned(self.data.offset(self.offset) as *mut u64, x);
}
self.offset += 8;
}
fn reloc_ebb(&mut self, rel: Reloc, ebb: Ebb) {
let ofs = self.offset();
self.relocs.reloc_ebb(ofs, rel, ebb);
}
fn reloc_func(&mut self, rel: Reloc, func: FuncRef) {
let ofs = self.offset();
self.relocs.reloc_func(ofs, rel, func);
}
fn reloc_jt(&mut self, rel: Reloc, jt: JumpTable) {
let ofs = self.offset();
self.relocs.reloc_jt(ofs, rel, jt);
}
}

View File

@@ -4,8 +4,10 @@
//! binary machine code. //! binary machine code.
mod relaxation; mod relaxation;
mod memorysink;
pub use self::relaxation::relax_branches; pub use self::relaxation::relax_branches;
pub use self::memorysink::{MemoryCodeSink, RelocSink};
use ir::{Ebb, FuncRef, JumpTable, Function, Inst}; use ir::{Ebb, FuncRef, JumpTable, Function, Inst};
@@ -55,3 +57,20 @@ pub fn bad_encoding(func: &Function, inst: Inst) -> ! {
func.encodings[inst], func.encodings[inst],
func.dfg.display_inst(inst, None)); func.dfg.display_inst(inst, None));
} }
/// 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)
where CS: CodeSink,
EI: Fn(&Function, Inst, &mut CS)
{
for ebb in func.layout.ebbs() {
assert_eq!(func.offsets[ebb], sink.offset());
for inst in func.layout.ebb_insts(ebb) {
emit_inst(func, inst, sink);
}
}
}

View File

@@ -9,7 +9,7 @@
//! contexts concurrently. Typically, you would have one context per compilation thread and only a //! contexts concurrently. Typically, you would have one context per compilation thread and only a
//! single ISA instance. //! single ISA instance.
use binemit::{CodeOffset, relax_branches}; use binemit::{CodeOffset, relax_branches, MemoryCodeSink, RelocSink};
use dominator_tree::DominatorTree; use dominator_tree::DominatorTree;
use flowgraph::ControlFlowGraph; use flowgraph::ControlFlowGraph;
use ir::Function; use ir::Function;
@@ -71,6 +71,16 @@ impl Context {
self.relax_branches(isa) self.relax_branches(isa)
} }
/// Emit machine code directly into raw memory.
///
/// Write all of the function's machine code to the memory at `mem`. The size of the machine
/// code is returned by `compile` above.
///
/// The machine code is not relocated. Instead, any relocations are emitted into `relocs`.
pub fn emit_to_memory(&self, mem: *mut u8, relocs: &mut RelocSink, isa: &TargetIsa) {
isa.emit_function(&self.func, &mut MemoryCodeSink::new(mem, relocs));
}
/// Run the verifier on the function. /// Run the verifier on the function.
/// ///
/// Also check that the dominator tree and control flow graph are consistent with the function. /// Also check that the dominator tree and control flow graph are consistent with the function.

View File

@@ -6,7 +6,7 @@ mod binemit;
mod enc_tables; mod enc_tables;
mod registers; mod registers;
use binemit::CodeSink; use binemit::{CodeSink, MemoryCodeSink, emit_function};
use super::super::settings as shared_settings; use super::super::settings as shared_settings;
use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings};
use isa::Builder as IsaBuilder; use isa::Builder as IsaBuilder;
@@ -95,6 +95,10 @@ impl TargetIsa for Isa {
binemit::emit_inst(func, inst, sink) binemit::emit_inst(func, inst, sink)
} }
fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) {
emit_function(func, binemit::emit_inst, sink)
}
fn reloc_names(&self) -> &'static [&'static str] { fn reloc_names(&self) -> &'static [&'static str] {
&binemit::RELOC_NAMES &binemit::RELOC_NAMES
} }

View File

@@ -6,7 +6,7 @@ mod binemit;
mod enc_tables; mod enc_tables;
mod registers; mod registers;
use binemit::CodeSink; use binemit::{CodeSink, MemoryCodeSink, emit_function};
use super::super::settings as shared_settings; use super::super::settings as shared_settings;
use isa::enc_tables::{lookup_enclist, Encodings}; use isa::enc_tables::{lookup_enclist, Encodings};
use isa::Builder as IsaBuilder; use isa::Builder as IsaBuilder;
@@ -88,6 +88,10 @@ impl TargetIsa for Isa {
binemit::emit_inst(func, inst, sink) binemit::emit_inst(func, inst, sink)
} }
fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) {
emit_function(func, binemit::emit_inst, sink)
}
fn reloc_names(&self) -> &'static [&'static str] { fn reloc_names(&self) -> &'static [&'static str] {
&binemit::RELOC_NAMES &binemit::RELOC_NAMES
} }

View File

@@ -6,7 +6,7 @@ mod binemit;
mod enc_tables; mod enc_tables;
mod registers; mod registers;
use binemit::CodeSink; use binemit::{CodeSink, MemoryCodeSink, emit_function};
use super::super::settings as shared_settings; use super::super::settings as shared_settings;
use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings};
use isa::Builder as IsaBuilder; use isa::Builder as IsaBuilder;
@@ -95,6 +95,10 @@ impl TargetIsa for Isa {
binemit::emit_inst(func, inst, sink) binemit::emit_inst(func, inst, sink)
} }
fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) {
emit_function(func, binemit::emit_inst, sink)
}
fn reloc_names(&self) -> &'static [&'static str] { fn reloc_names(&self) -> &'static [&'static str] {
&binemit::RELOC_NAMES &binemit::RELOC_NAMES
} }

View File

@@ -44,7 +44,7 @@ pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind,
pub use isa::encoding::{Encoding, EncInfo}; pub use isa::encoding::{Encoding, EncInfo};
pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex, regs_overlap}; pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex, regs_overlap};
use binemit::CodeSink; use binemit;
use settings; use settings;
use ir; use ir;
use regalloc; use regalloc;
@@ -215,7 +215,12 @@ pub trait TargetIsa {
/// ///
/// Note that this will call `put*` methods on the trait object via its vtable which is not the /// Note that this will call `put*` methods on the trait object via its vtable which is not the
/// fastest way of emitting code. /// fastest way of emitting code.
fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut CodeSink); fn emit_inst(&self, func: &ir::Function, inst: ir::Inst, sink: &mut binemit::CodeSink);
/// Emit a whole function into memory.
///
/// This is more performant than calling `emit_inst` for each instruction.
fn emit_function(&self, func: &ir::Function, sink: &mut binemit::MemoryCodeSink);
/// Get a static array of names associated with relocations in this ISA. /// Get a static array of names associated with relocations in this ISA.
/// ///

View File

@@ -7,7 +7,7 @@ mod enc_tables;
mod registers; mod registers;
use super::super::settings as shared_settings; use super::super::settings as shared_settings;
use binemit::CodeSink; use binemit::{CodeSink, MemoryCodeSink, emit_function};
use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings};
use isa::Builder as IsaBuilder; use isa::Builder as IsaBuilder;
use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize};
@@ -95,6 +95,10 @@ impl TargetIsa for Isa {
binemit::emit_inst(func, inst, sink) binemit::emit_inst(func, inst, sink)
} }
fn emit_function(&self, func: &ir::Function, sink: &mut MemoryCodeSink) {
emit_function(func, binemit::emit_inst, sink)
}
fn reloc_names(&self) -> &'static [&'static str] { fn reloc_names(&self) -> &'static [&'static str] {
&binemit::RELOC_NAMES &binemit::RELOC_NAMES
} }