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:
108
lib/cretonne/src/binemit/memorysink.rs
Normal file
108
lib/cretonne/src/binemit/memorysink.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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<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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user