Rename the 'cretonne' crate to 'cretonne-codegen'.

This fixes the next part of #287.
This commit is contained in:
Dan Gohman
2018-04-17 08:48:02 -07:00
parent 7767186dd0
commit 24fa169e1f
254 changed files with 265 additions and 264 deletions

View File

@@ -0,0 +1,125 @@
//! 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 super::{Addend, CodeOffset, CodeSink, Reloc};
use ir::{ExternalName, JumpTable, SourceLoc, TrapCode};
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 IR 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,
traps: &'a mut TrapSink,
}
impl<'a> MemoryCodeSink<'a> {
/// Create a new memory code sink that writes a function to the memory pointed to by `data`.
pub fn new<'sink>(
data: *mut u8,
relocs: &'sink mut RelocSink,
traps: &'sink mut TrapSink,
) -> MemoryCodeSink<'sink> {
MemoryCodeSink {
data,
offset: 0,
relocs,
traps,
}
}
}
/// 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, CodeOffset);
/// Add a relocation referencing an external symbol at the current offset.
fn reloc_external(&mut self, CodeOffset, Reloc, &ExternalName, Addend);
/// Add a relocation referencing a jump table.
fn reloc_jt(&mut self, CodeOffset, Reloc, JumpTable);
}
/// A trait for receiving trap codes and offsets.
pub trait TrapSink {
/// Add trap information for a specific offset.
fn trap(&mut self, CodeOffset, SourceLoc, TrapCode);
}
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_offset: CodeOffset) {
let ofs = self.offset();
self.relocs.reloc_ebb(ofs, rel, ebb_offset);
}
fn reloc_external(&mut self, rel: Reloc, name: &ExternalName, addend: Addend) {
let ofs = self.offset();
self.relocs.reloc_external(ofs, rel, name, addend);
}
fn reloc_jt(&mut self, rel: Reloc, jt: JumpTable) {
let ofs = self.offset();
self.relocs.reloc_jt(ofs, rel, jt);
}
fn trap(&mut self, code: TrapCode, srcloc: SourceLoc) {
let ofs = self.offset();
self.traps.trap(ofs, srcloc, code);
}
}

View File

@@ -0,0 +1,121 @@
//! Binary machine code emission.
//!
//! The `binemit` module contains code for translating Cretonne's intermediate representation into
//! binary machine code.
mod memorysink;
mod relaxation;
pub use self::memorysink::{MemoryCodeSink, RelocSink, TrapSink};
pub use self::relaxation::relax_branches;
pub use regalloc::RegDiversions;
use ir::{ExternalName, Function, Inst, JumpTable, SourceLoc, TrapCode};
use std::fmt;
/// Offset in bytes from the beginning of the function.
///
/// Cretonne 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(Debug)]
pub enum Reloc {
/// absolute 4-byte
Abs4,
/// absolute 8-byte
Abs8,
/// x86 PC-relative 4-byte
X86PCRel4,
/// x86 GOT PC-relative 4-byte
X86GOTPCRel4,
/// x86 PLT-relative 4-byte
X86PLTRel4,
/// Arm32 call target
Arm32Call,
/// Arm64 call target
Arm64Call,
/// RISC-V call target
RiscvCall,
}
impl fmt::Display for Reloc {
/// Display trait implementation drops the arch, since its used in contexts where the arch is
/// already unambigious, e.g. cton syntax with isa specified. In other contexts, use Debug.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Reloc::Abs4 => write!(f, "{}", "Abs4"),
Reloc::Abs8 => write!(f, "{}", "Abs8"),
Reloc::X86PCRel4 => write!(f, "{}", "PCRel4"),
Reloc::X86GOTPCRel4 => write!(f, "{}", "GOTPCRel4"),
Reloc::X86PLTRel4 => write!(f, "{}", "PLTRel4"),
Reloc::Arm32Call | Reloc::Arm64Call | Reloc::RiscvCall => write!(f, "{}", "Call"),
}
}
}
/// 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 EBB at the current offset.
fn reloc_ebb(&mut self, Reloc, CodeOffset);
/// Add a relocation referencing an external symbol plus the addend at the current offset.
fn reloc_external(&mut self, Reloc, &ExternalName, Addend);
/// 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);
}
/// Report a bad encoding error.
#[cold]
pub fn bad_encoding(func: &Function, inst: Inst) -> ! {
panic!(
"Bad encoding {} for {}",
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 RegDiversions, &mut CS),
{
let mut divert = RegDiversions::new();
for ebb in func.layout.ebbs() {
divert.clear();
debug_assert_eq!(func.offsets[ebb], sink.offset());
for inst in func.layout.ebb_insts(ebb) {
emit_inst(func, inst, &mut divert, sink);
}
}
}

View File

@@ -0,0 +1,198 @@
//! Branch relaxation and offset computation.
//!
//! # EBB header offsets
//!
//! Before we can generate binary machine code for branch instructions, we need to know the final
//! offsets of all the EBB headers in the function. This information is encoded in the
//! `func.offsets` table.
//!
//! # Branch relaxation
//!
//! Branch relaxation is the process of ensuring that all branches in the function have enough
//! range to encode their destination. It is common to have multiple branch encodings in an ISA.
//! For example, x86 branches can have either an 8-bit or a 32-bit displacement.
//!
//! On RISC architectures, it can happen that conditional branches have a shorter range than
//! unconditional branches:
//!
//! ```cton
//! brz v1, ebb17
//! ```
//!
//! can be transformed into:
//!
//! ```cton
//! brnz v1, ebb23
//! jump ebb17
//! ebb23:
//! ```
use binemit::CodeOffset;
use cursor::{Cursor, FuncCursor};
use ir::{Function, InstructionData, Opcode};
use isa::{EncInfo, TargetIsa};
use iterators::IteratorExtras;
use result::CtonError;
/// Relax branches and compute the final layout of EBB headers in `func`.
///
/// Fill in the `func.offsets` table so the function is ready for binary emission.
pub fn relax_branches(func: &mut Function, isa: &TargetIsa) -> Result<CodeOffset, CtonError> {
let encinfo = isa.encoding_info();
// Clear all offsets so we can recognize EBBs that haven't been visited yet.
func.offsets.clear();
func.offsets.resize(func.dfg.num_ebbs());
// Start by inserting fall through instructions.
fallthroughs(func);
let mut offset = 0;
// The relaxation algorithm iterates to convergence.
let mut go_again = true;
while go_again {
go_again = false;
offset = 0;
// Visit all instructions in layout order
let mut cur = FuncCursor::new(func);
while let Some(ebb) = cur.next_ebb() {
// Record the offset for `ebb` and make sure we iterate until offsets are stable.
if cur.func.offsets[ebb] != offset {
debug_assert!(
cur.func.offsets[ebb] < offset,
"Code shrinking during relaxation"
);
cur.func.offsets[ebb] = offset;
go_again = true;
}
while let Some(inst) = cur.next_inst() {
let enc = cur.func.encodings[inst];
let size = encinfo.bytes(enc);
// See if this might be a branch that is out of range.
if let Some(range) = encinfo.branch_range(enc) {
if let Some(dest) = cur.func.dfg[inst].branch_destination() {
let dest_offset = cur.func.offsets[dest];
// This could be an out-of-range branch.
// Relax it unless the destination offset has not been computed yet.
if !range.contains(offset, dest_offset) &&
(dest_offset != 0 || Some(dest) == cur.func.layout.entry_block())
{
offset += relax_branch(&mut cur, offset, dest_offset, &encinfo, isa);
continue;
}
}
}
offset += size;
}
}
}
Ok(offset)
}
/// Convert `jump` instructions to `fallthrough` instructions where possible and verify that any
/// existing `fallthrough` instructions are correct.
fn fallthroughs(func: &mut Function) {
for (ebb, succ) in func.layout.ebbs().adjacent_pairs() {
let term = func.layout.last_inst(ebb).expect("EBB has no terminator.");
if let InstructionData::Jump {
ref mut opcode,
destination,
..
} = func.dfg[term]
{
match *opcode {
Opcode::Fallthrough => {
// Somebody used a fall-through instruction before the branch relaxation pass.
// Make sure it is correct, i.e. the destination is the layout successor.
debug_assert_eq!(destination, succ, "Illegal fall-through in {}", ebb)
}
Opcode::Jump => {
// If this is a jump to the successor EBB, change it to a fall-through.
if destination == succ {
*opcode = Opcode::Fallthrough;
func.encodings[term] = Default::default();
}
}
_ => {}
}
}
}
}
/// Relax the branch instruction at `pos` so it can cover the range `offset - dest_offset`.
///
/// Return the size of the replacement instructions up to and including the location where `pos` is
/// left.
fn relax_branch(
cur: &mut FuncCursor,
offset: CodeOffset,
dest_offset: CodeOffset,
encinfo: &EncInfo,
isa: &TargetIsa,
) -> CodeOffset {
let inst = cur.current_inst().unwrap();
dbg!(
"Relaxing [{}] {} for {:#x}-{:#x} range",
encinfo.display(cur.func.encodings[inst]),
cur.func.dfg.display_inst(inst, isa),
offset,
dest_offset
);
// Pick the first encoding that can handle the branch range.
let dfg = &cur.func.dfg;
let ctrl_type = dfg.ctrl_typevar(inst);
if let Some(enc) = isa.legal_encodings(cur.func, &dfg[inst], ctrl_type).find(
|&enc| {
let range = encinfo.branch_range(enc).expect("Branch with no range");
if !range.contains(offset, dest_offset) {
dbg!(" trying [{}]: out of range", encinfo.display(enc));
false
} else if encinfo.operand_constraints(enc) !=
encinfo.operand_constraints(cur.func.encodings[inst])
{
// Conservatively give up if the encoding has different constraints
// than the original, so that we don't risk picking a new encoding
// which the existing operands don't satisfy. We can't check for
// validity directly because we don't have a RegDiversions active so
// we don't know which registers are actually in use.
dbg!(" trying [{}]: constraints differ", encinfo.display(enc));
false
} else {
dbg!(" trying [{}]: OK", encinfo.display(enc));
true
}
},
)
{
cur.func.encodings[inst] = enc;
return encinfo.bytes(enc);
}
// Note: On some RISC ISAs, conditional branches have shorter range than unconditional
// branches, so one way of extending the range of a conditional branch is to invert its
// condition and make it branch over an unconditional jump which has the larger range.
//
// Splitting the EBB is problematic this late because there may be register diversions in
// effect across the conditional branch, and they can't survive the control flow edge to a new
// EBB. We have two options for handling that:
//
// 1. Set a flag on the new EBB that indicates it wants the preserve the register diversions of
// its layout predecessor, or
// 2. Use an encoding macro for the branch-over-jump pattern so we don't need to split the EBB.
//
// It seems that 1. would allow us to share code among RISC ISAs that need this.
//
// We can't allow register diversions to survive from the layout predecessor because the layout
// predecessor could contain kill points for some values that are live in this EBB, and
// diversions are not automatically cancelled when the live range of a value ends.
// This assumes solution 2. above:
panic!("No branch in range for {:#x}-{:#x}", offset, dest_offset);
}