Rename the 'cretonne' crate to 'cretonne-codegen'.
This fixes the next part of #287.
This commit is contained in:
125
lib/codegen/src/binemit/memorysink.rs
Normal file
125
lib/codegen/src/binemit/memorysink.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
121
lib/codegen/src/binemit/mod.rs
Normal file
121
lib/codegen/src/binemit/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
198
lib/codegen/src/binemit/relaxation.rs
Normal file
198
lib/codegen/src/binemit/relaxation.rs
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user