From 2f7057b96f41ef4969fa1472348e303731bc68e1 Mon Sep 17 00:00:00 2001 From: Jakob Stoklund Olesen Date: Mon, 17 Jul 2017 18:13:05 -0700 Subject: [PATCH] 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. --- cranelift/src/filetest/compile.rs | 9 +-- lib/cretonne/src/binemit/memorysink.rs | 108 +++++++++++++++++++++++++ lib/cretonne/src/binemit/mod.rs | 19 +++++ lib/cretonne/src/context.rs | 12 ++- lib/cretonne/src/isa/arm32/mod.rs | 6 +- lib/cretonne/src/isa/arm64/mod.rs | 6 +- lib/cretonne/src/isa/intel/mod.rs | 6 +- lib/cretonne/src/isa/mod.rs | 9 ++- lib/cretonne/src/isa/riscv/mod.rs | 6 +- 9 files changed, 168 insertions(+), 13 deletions(-) create mode 100644 lib/cretonne/src/binemit/memorysink.rs diff --git a/cranelift/src/filetest/compile.rs b/cranelift/src/filetest/compile.rs index 15db119efd..f3766f001f 100644 --- a/cranelift/src/filetest/compile.rs +++ b/cranelift/src/filetest/compile.rs @@ -51,12 +51,9 @@ impl SubTest for TestCompile { // Finally verify that the returned code size matches the emitted bytes. let mut sink = SizeSink { offset: 0 }; - for ebb in comp_ctx.func.layout.ebbs() { - assert_eq!(sink.offset, comp_ctx.func.offsets[ebb]); - for inst in comp_ctx.func.layout.ebb_insts(ebb) { - isa.emit_inst(&comp_ctx.func, inst, &mut sink); - } - } + binemit::emit_function(&comp_ctx.func, + |func, inst, sink| isa.emit_inst(func, inst, sink), + &mut sink); if sink.offset != code_size { return Err(format!("Expected code size {}, got {}", code_size, sink.offset)); diff --git a/lib/cretonne/src/binemit/memorysink.rs b/lib/cretonne/src/binemit/memorysink.rs new file mode 100644 index 0000000000..f3fd5cebba --- /dev/null +++ b/lib/cretonne/src/binemit/memorysink.rs @@ -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); + } +} diff --git a/lib/cretonne/src/binemit/mod.rs b/lib/cretonne/src/binemit/mod.rs index eaa721d510..02076270d0 100644 --- a/lib/cretonne/src/binemit/mod.rs +++ b/lib/cretonne/src/binemit/mod.rs @@ -4,8 +4,10 @@ //! binary machine code. mod relaxation; +mod memorysink; pub use self::relaxation::relax_branches; +pub use self::memorysink::{MemoryCodeSink, RelocSink}; use ir::{Ebb, FuncRef, JumpTable, Function, Inst}; @@ -55,3 +57,20 @@ pub fn bad_encoding(func: &Function, inst: Inst) -> ! { func.encodings[inst], 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(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); + } + + } +} diff --git a/lib/cretonne/src/context.rs b/lib/cretonne/src/context.rs index a87e35cd32..d0288789f6 100644 --- a/lib/cretonne/src/context.rs +++ b/lib/cretonne/src/context.rs @@ -9,7 +9,7 @@ //! contexts concurrently. Typically, you would have one context per compilation thread and only a //! single ISA instance. -use binemit::{CodeOffset, relax_branches}; +use binemit::{CodeOffset, relax_branches, MemoryCodeSink, RelocSink}; use dominator_tree::DominatorTree; use flowgraph::ControlFlowGraph; use ir::Function; @@ -71,6 +71,16 @@ impl Context { 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. /// /// Also check that the dominator tree and control flow graph are consistent with the function. diff --git a/lib/cretonne/src/isa/arm32/mod.rs b/lib/cretonne/src/isa/arm32/mod.rs index d986b9e0e2..5591a2aece 100644 --- a/lib/cretonne/src/isa/arm32/mod.rs +++ b/lib/cretonne/src/isa/arm32/mod.rs @@ -6,7 +6,7 @@ mod binemit; mod enc_tables; mod registers; -use binemit::CodeSink; +use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; @@ -95,6 +95,10 @@ impl TargetIsa for Isa { 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] { &binemit::RELOC_NAMES } diff --git a/lib/cretonne/src/isa/arm64/mod.rs b/lib/cretonne/src/isa/arm64/mod.rs index c8c2de3cf8..316d1957f5 100644 --- a/lib/cretonne/src/isa/arm64/mod.rs +++ b/lib/cretonne/src/isa/arm64/mod.rs @@ -6,7 +6,7 @@ mod binemit; mod enc_tables; mod registers; -use binemit::CodeSink; +use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; use isa::enc_tables::{lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; @@ -88,6 +88,10 @@ impl TargetIsa for Isa { 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] { &binemit::RELOC_NAMES } diff --git a/lib/cretonne/src/isa/intel/mod.rs b/lib/cretonne/src/isa/intel/mod.rs index 88df62c8ac..81e3127811 100644 --- a/lib/cretonne/src/isa/intel/mod.rs +++ b/lib/cretonne/src/isa/intel/mod.rs @@ -6,7 +6,7 @@ mod binemit; mod enc_tables; mod registers; -use binemit::CodeSink; +use binemit::{CodeSink, MemoryCodeSink, emit_function}; use super::super::settings as shared_settings; use isa::enc_tables::{self as shared_enc_tables, lookup_enclist, Encodings}; use isa::Builder as IsaBuilder; @@ -95,6 +95,10 @@ impl TargetIsa for Isa { 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] { &binemit::RELOC_NAMES } diff --git a/lib/cretonne/src/isa/mod.rs b/lib/cretonne/src/isa/mod.rs index edd72b27af..0604c0f271 100644 --- a/lib/cretonne/src/isa/mod.rs +++ b/lib/cretonne/src/isa/mod.rs @@ -44,7 +44,7 @@ pub use isa::constraints::{RecipeConstraints, OperandConstraint, ConstraintKind, pub use isa::encoding::{Encoding, EncInfo}; pub use isa::registers::{RegInfo, RegUnit, RegClass, RegClassIndex, regs_overlap}; -use binemit::CodeSink; +use binemit; use settings; use ir; 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 /// 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. /// diff --git a/lib/cretonne/src/isa/riscv/mod.rs b/lib/cretonne/src/isa/riscv/mod.rs index 246ec13bd1..6be2432a35 100644 --- a/lib/cretonne/src/isa/riscv/mod.rs +++ b/lib/cretonne/src/isa/riscv/mod.rs @@ -7,7 +7,7 @@ mod enc_tables; mod registers; 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::Builder as IsaBuilder; use isa::{TargetIsa, RegInfo, RegClass, EncInfo, Legalize}; @@ -95,6 +95,10 @@ impl TargetIsa for Isa { 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] { &binemit::RELOC_NAMES }