Merge pull request #3680 from bjorn3/remove_code_sink

Remove the CodeSink interface in favor of MachBufferFinalized
This commit is contained in:
Chris Fallin
2022-01-12 10:47:23 -08:00
committed by GitHub
30 changed files with 342 additions and 899 deletions

View File

@@ -1,189 +0,0 @@
//! Code sink that writes binary machine code into contiguous memory.
//!
//! The `CodeSink` trait is the most general way of extracting binary machine code from Cranelift,
//! 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, CodeInfo, CodeOffset, CodeSink, Reloc};
use crate::binemit::stack_map::StackMap;
use crate::ir::{ExternalName, Opcode, SourceLoc, TrapCode};
use core::ptr::write_unaligned;
/// A `CodeSink` that writes binary machine code directly into memory.
///
/// A `MemoryCodeSink` object should be used when emitting a Cranelift 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> {
/// Pointer to start of sink's preallocated memory.
data: *mut u8,
/// Offset is isize because its major consumer needs it in that form.
offset: isize,
relocs: &'a mut dyn RelocSink,
traps: &'a mut dyn TrapSink,
/// Information about the generated code and read-only data.
pub info: CodeInfo,
}
impl<'a> MemoryCodeSink<'a> {
/// Create a new memory code sink that writes a function to the memory pointed to by `data`.
///
/// # Safety
///
/// This function is unsafe since `MemoryCodeSink` does not perform bounds checking on the
/// memory buffer, and it can't guarantee that the `data` pointer is valid.
pub unsafe fn new(
data: *mut u8,
relocs: &'a mut dyn RelocSink,
traps: &'a mut dyn TrapSink,
) -> Self {
Self {
data,
offset: 0,
info: CodeInfo { total_size: 0 },
relocs,
traps,
}
}
}
/// A trait for receiving relocations for code that is emitted directly into memory.
pub trait RelocSink {
/// Add a relocation referencing an external symbol at the current offset.
fn reloc_external(
&mut self,
_: CodeOffset,
_: SourceLoc,
_: Reloc,
_: &ExternalName,
_: Addend,
);
/// Track a call site whose return address is the given CodeOffset, for the given opcode. Does
/// nothing in general, only useful for certain embedders (SpiderMonkey).
fn add_call_site(&mut self, _: Opcode, _: CodeOffset, _: SourceLoc) {}
}
/// A trait for receiving trap codes and offsets.
///
/// If you don't need information about possible traps, you can use the
/// [`NullTrapSink`](NullTrapSink) implementation.
pub trait TrapSink {
/// Add trap information for a specific offset.
fn trap(&mut self, _: CodeOffset, _: SourceLoc, _: TrapCode);
}
impl<'a> MemoryCodeSink<'a> {
fn write<T>(&mut self, x: T) {
unsafe {
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
write_unaligned(self.data.offset(self.offset) as *mut T, x);
self.offset += core::mem::size_of::<T>() as isize;
}
}
}
impl<'a> CodeSink for MemoryCodeSink<'a> {
fn offset(&self) -> CodeOffset {
self.offset as CodeOffset
}
fn put1(&mut self, x: u8) {
self.write(x);
}
fn put2(&mut self, x: u16) {
self.write(x);
}
fn put4(&mut self, x: u32) {
self.write(x);
}
fn put8(&mut self, x: u64) {
self.write(x);
}
fn reloc_external(
&mut self,
srcloc: SourceLoc,
rel: Reloc,
name: &ExternalName,
addend: Addend,
) {
let ofs = self.offset();
self.relocs.reloc_external(ofs, srcloc, rel, name, addend);
}
fn trap(&mut self, code: TrapCode, srcloc: SourceLoc) {
let ofs = self.offset();
self.traps.trap(ofs, srcloc, code);
}
fn end_codegen(&mut self) {
self.info.total_size = self.offset();
}
fn add_call_site(&mut self, opcode: Opcode, loc: SourceLoc) {
debug_assert!(
opcode.is_call(),
"adding call site info for a non-call instruction."
);
let ret_addr = self.offset();
self.relocs.add_call_site(opcode, ret_addr, loc);
}
}
/// A `RelocSink` implementation that does nothing, which is convenient when
/// compiling code that does not relocate anything.
#[derive(Default)]
pub struct NullRelocSink {}
impl RelocSink for NullRelocSink {
fn reloc_external(
&mut self,
_: CodeOffset,
_: SourceLoc,
_: Reloc,
_: &ExternalName,
_: Addend,
) {
}
}
/// A `TrapSink` implementation that does nothing, which is convenient when
/// compiling code that does not rely on trapping semantics.
#[derive(Default)]
pub struct NullTrapSink {}
impl TrapSink for NullTrapSink {
fn trap(&mut self, _offset: CodeOffset, _srcloc: SourceLoc, _code: TrapCode) {}
}
/// A trait for emitting stack maps.
pub trait StackMapSink {
/// Output a bitmap of the stack representing the live reference variables at this code offset.
fn add_stack_map(&mut self, _: CodeOffset, _: StackMap);
}
/// Placeholder StackMapSink that does nothing.
pub struct NullStackMapSink {}
impl StackMapSink for NullStackMapSink {
fn add_stack_map(&mut self, _: CodeOffset, _: StackMap) {}
}

View File

@@ -3,15 +3,9 @@
//! The `binemit` module contains code for translating Cranelift's intermediate representation into
//! binary machine code.
mod memorysink;
mod stack_map;
pub use self::memorysink::{
MemoryCodeSink, NullRelocSink, NullStackMapSink, NullTrapSink, RelocSink, StackMapSink,
TrapSink,
};
pub use self::stack_map::StackMap;
use crate::ir::{ExternalName, Opcode, SourceLoc, TrapCode};
use core::fmt;
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
@@ -99,38 +93,3 @@ pub struct CodeInfo {
/// Number of bytes in total.
pub total_size: CodeOffset,
}
/// 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 external symbol plus the addend at the current offset.
fn reloc_external(&mut self, _: SourceLoc, _: Reloc, _: &ExternalName, _: Addend);
/// Add trap information for the current offset.
fn trap(&mut self, _: TrapCode, _: SourceLoc);
/// Read-only data output is complete, we're done.
fn end_codegen(&mut self);
/// Add a call site for a call with the given opcode, returning at the current offset.
fn add_call_site(&mut self, _: Opcode, _: SourceLoc) {
// Default implementation doesn't need to do anything.
}
}

View File

@@ -9,7 +9,7 @@
//! contexts concurrently. Typically, you would have one context per compilation thread and only a
//! single ISA instance.
use crate::binemit::{CodeInfo, MemoryCodeSink, RelocSink, StackMapSink, TrapSink};
use crate::binemit::CodeInfo;
use crate::dce::do_dce;
use crate::dominator_tree::DominatorTree;
use crate::flowgraph::ControlFlowGraph;
@@ -18,7 +18,7 @@ use crate::isa::TargetIsa;
use crate::legalizer::simple_legalize;
use crate::licm::do_licm;
use crate::loop_analysis::LoopAnalysis;
use crate::machinst::{MachCompileResult, MachStackMap};
use crate::machinst::MachCompileResult;
use crate::nan_canonicalization::do_nan_canonicalization;
use crate::remove_constant_phis::do_remove_constant_phis;
use crate::result::CodegenResult;
@@ -111,16 +111,11 @@ impl Context {
&mut self,
isa: &dyn TargetIsa,
mem: &mut Vec<u8>,
relocs: &mut dyn RelocSink,
traps: &mut dyn TrapSink,
stack_maps: &mut dyn StackMapSink,
) -> CodegenResult<()> {
let info = self.compile(isa)?;
let old_len = mem.len();
mem.resize(old_len + info.total_size as usize, 0);
let new_info = unsafe {
self.emit_to_memory(mem.as_mut_ptr().add(old_len), relocs, traps, stack_maps)
};
let new_info = unsafe { self.emit_to_memory(mem.as_mut_ptr().add(old_len)) };
debug_assert!(new_info == info);
Ok(())
}
@@ -186,32 +181,18 @@ impl Context {
/// and it can't guarantee that the `mem` pointer is valid.
///
/// Returns information about the emitted code and data.
pub unsafe fn emit_to_memory(
&self,
mem: *mut u8,
relocs: &mut dyn RelocSink,
traps: &mut dyn TrapSink,
stack_maps: &mut dyn StackMapSink,
) -> CodeInfo {
#[deny(unsafe_op_in_unsafe_fn)]
pub unsafe fn emit_to_memory(&self, mem: *mut u8) -> CodeInfo {
let _tt = timing::binemit();
let mut sink = MemoryCodeSink::new(mem, relocs, traps);
let result = self
.mach_compile_result
.as_ref()
.expect("only using mach backend now");
result.buffer.emit(&mut sink);
let info = sink.info;
// New backends do not emit StackMaps through the `CodeSink` because its interface
// requires `Value`s; instead, the `StackMap` objects are directly accessible via
// `result.buffer.stack_maps()`.
for &MachStackMap {
offset_end,
ref stack_map,
..
} in result.buffer.stack_maps()
{
stack_maps.add_stack_map(offset_end, stack_map.clone());
}
let info = result.code_info();
let mem = unsafe { std::slice::from_raw_parts_mut(mem, info.total_size as usize) };
mem.copy_from_slice(result.buffer.data());
info
}

View File

@@ -1,7 +1,6 @@
use crate::ir::types::*;
use crate::ir::TrapCode;
use crate::isa::aarch64::inst::*;
use crate::isa::test_utils;
use crate::isa::CallConv;
use crate::settings;
@@ -6523,12 +6522,10 @@ fn test_aarch64_binemit() {
let actual_printing = insn.show_rru(Some(&rru));
assert_eq!(expected_printing, actual_printing);
let mut sink = test_utils::TestCodeSink::new();
let mut buffer = MachBuffer::new();
insn.emit(&mut buffer, &emit_info, &mut Default::default());
let buffer = buffer.finish();
buffer.emit(&mut sink);
let actual_encoding = &sink.stringify();
let actual_encoding = &buffer.stringify_code_bytes();
assert_eq!(expected_encoding, actual_encoding);
}
}

View File

@@ -130,7 +130,7 @@ impl TargetIsa for AArch64Backend {
Some(UnwindInfo::SystemV(
crate::isa::unwind::systemv::create_unwind_info_from_insts(
&result.buffer.unwind_info[..],
result.buffer.data.len(),
result.buffer.data().len(),
&mapper,
)?,
))
@@ -216,7 +216,7 @@ mod test {
isa_flags,
);
let buffer = backend.compile_function(&mut func, false).unwrap().buffer;
let code = &buffer.data[..];
let code = buffer.data();
// mov x1, #0x1234
// add w0, w0, w1
@@ -271,7 +271,7 @@ mod test {
let result = backend
.compile_function(&mut func, /* want_disasm = */ false)
.unwrap();
let code = &result.buffer.data[..];
let code = result.buffer.data();
// mov x1, #0x1234 // #4660
// add w0, w0, w1

View File

@@ -1,5 +1,4 @@
use crate::isa::arm32::inst::*;
use crate::isa::test_utils;
use crate::settings;
use alloc::vec::Vec;
@@ -1948,12 +1947,10 @@ fn test_arm32_emit() {
// Check the printed text is as expected.
let actual_printing = insn.show_rru(Some(&rru));
assert_eq!(expected_printing, actual_printing);
let mut sink = test_utils::TestCodeSink::new();
let mut buffer = MachBuffer::new();
insn.emit(&mut buffer, &flags, &mut Default::default());
let buffer = buffer.finish();
buffer.emit(&mut sink);
let actual_encoding = &sink.stringify();
let actual_encoding = &buffer.stringify_code_bytes();
assert_eq!(expected_encoding, actual_encoding, "{}", expected_printing);
}
}

View File

@@ -76,9 +76,6 @@ pub mod unwind;
mod call_conv;
#[cfg(test)]
mod test_utils;
/// Returns a builder that can create a corresponding `TargetIsa`
/// or `Err(LookupError::SupportDisabled)` if not enabled.
macro_rules! isa_builder {

View File

@@ -1,7 +1,6 @@
use crate::ir::MemFlags;
use crate::isa::s390x::inst::*;
use crate::isa::s390x::settings as s390x_settings;
use crate::isa::test_utils;
use crate::settings;
use alloc::vec::Vec;
@@ -8112,12 +8111,10 @@ fn test_s390x_binemit() {
let actual_printing = insn.show_rru(Some(&rru));
assert_eq!(expected_printing, actual_printing);
let mut sink = test_utils::TestCodeSink::new();
let mut buffer = MachBuffer::new();
insn.emit(&mut buffer, &emit_info, &mut Default::default());
let buffer = buffer.finish();
buffer.emit(&mut sink);
let actual_encoding = &sink.stringify();
let actual_encoding = &buffer.stringify_code_bytes();
assert_eq!(expected_encoding, actual_encoding);
}
}

View File

@@ -136,7 +136,7 @@ impl TargetIsa for S390xBackend {
Some(UnwindInfo::SystemV(
crate::isa::unwind::systemv::create_unwind_info_from_insts(
&result.buffer.unwind_info[..],
result.buffer.data.len(),
result.buffer.data().len(),
&mapper,
)?,
))
@@ -225,7 +225,7 @@ mod test {
let result = backend
.compile_function(&mut func, /* want_disasm = */ false)
.unwrap();
let code = &result.buffer.data[..];
let code = result.buffer.data();
// ahi %r2, 0x1234
// br %r14
@@ -277,7 +277,7 @@ mod test {
let result = backend
.compile_function(&mut func, /* want_disasm = */ false)
.unwrap();
let code = &result.buffer.data[..];
let code = result.buffer.data();
// FIXME: the branching logic should be optimized more

View File

@@ -1,74 +0,0 @@
// This is unused when no platforms with the new backend are enabled.
#![allow(dead_code)]
use crate::binemit::{Addend, CodeOffset, CodeSink, Reloc};
use crate::ir::{ExternalName, Opcode, SourceLoc, TrapCode};
use alloc::vec::Vec;
use std::string::String;
pub struct TestCodeSink {
bytes: Vec<u8>,
}
impl TestCodeSink {
/// Create a new TestCodeSink.
pub fn new() -> TestCodeSink {
TestCodeSink { bytes: vec![] }
}
/// Return the code emitted to this sink as a hex string.
pub fn stringify(&self) -> String {
// This is pretty lame, but whatever ..
use std::fmt::Write;
let mut s = String::with_capacity(self.bytes.len() * 2);
for b in &self.bytes {
write!(&mut s, "{:02X}", b).unwrap();
}
s
}
}
impl CodeSink for TestCodeSink {
fn offset(&self) -> CodeOffset {
self.bytes.len() as CodeOffset
}
fn put1(&mut self, x: u8) {
self.bytes.push(x);
}
fn put2(&mut self, x: u16) {
self.bytes.push((x >> 0) as u8);
self.bytes.push((x >> 8) as u8);
}
fn put4(&mut self, mut x: u32) {
for _ in 0..4 {
self.bytes.push(x as u8);
x >>= 8;
}
}
fn put8(&mut self, mut x: u64) {
for _ in 0..8 {
self.bytes.push(x as u8);
x >>= 8;
}
}
fn reloc_external(
&mut self,
_srcloc: SourceLoc,
_rel: Reloc,
_name: &ExternalName,
_addend: Addend,
) {
}
fn trap(&mut self, _code: TrapCode, _srcloc: SourceLoc) {}
fn end_codegen(&mut self) {}
fn add_call_site(&mut self, _opcode: Opcode, _srcloc: SourceLoc) {}
}

View File

@@ -13,7 +13,6 @@
//! -- isa::x64::inst::emit_tests::test_x64_emit
use super::*;
use crate::isa::test_utils;
use crate::isa::x64;
use alloc::vec::Vec;
@@ -4460,7 +4459,6 @@ fn test_x64_emit() {
// Check the printed text is as expected.
let actual_printing = insn.show_rru(Some(&rru));
assert_eq!(expected_printing, actual_printing);
let mut sink = test_utils::TestCodeSink::new();
let mut buffer = MachBuffer::new();
insn.emit(&mut buffer, &emit_info, &mut Default::default());
@@ -4470,8 +4468,7 @@ fn test_x64_emit() {
buffer.bind_label(label);
let buffer = buffer.finish();
buffer.emit(&mut sink);
let actual_encoding = &sink.stringify();
let actual_encoding = &buffer.stringify_code_bytes();
assert_eq!(expected_encoding, actual_encoding, "{}", expected_printing);
}
}

View File

@@ -122,7 +122,7 @@ impl TargetIsa for X64Backend {
Some(UnwindInfo::SystemV(
crate::isa::unwind::systemv::create_unwind_info_from_insts(
&result.buffer.unwind_info[..],
result.buffer.data.len(),
result.buffer.data().len(),
&mapper,
)?,
))

View File

@@ -88,7 +88,7 @@ pub mod verifier;
pub mod write;
pub use crate::entity::packed_option;
pub use crate::machinst::buffer::MachSrcLoc;
pub use crate::machinst::buffer::{MachCallSite, MachReloc, MachSrcLoc, MachStackMap, MachTrap};
pub use crate::machinst::TextSectionBuilder;
mod bitset;

View File

@@ -140,7 +140,7 @@
//! Given these invariants, we argue why each optimization preserves execution
//! semantics below (grep for "Preserves execution semantics").
use crate::binemit::{Addend, CodeOffset, CodeSink, Reloc, StackMap};
use crate::binemit::{Addend, CodeOffset, Reloc, StackMap};
use crate::ir::{ExternalName, Opcode, SourceLoc, TrapCode};
use crate::isa::unwind::UnwindInst;
use crate::machinst::{
@@ -233,7 +233,7 @@ pub struct MachBuffer<I: VCodeInst> {
/// without fixups. This allows the type to be independent of the backend.
pub struct MachBufferFinalized {
/// The buffer contents, as raw bytes.
pub data: SmallVec<[u8; 1024]>,
data: SmallVec<[u8; 1024]>,
/// Any relocations referring to this code. Note that only *external*
/// relocations are tracked here; references to labels within the buffer are
/// resolved before emission.
@@ -1350,6 +1350,10 @@ impl<I: VCodeInst> MachBuffer<I> {
/// Add a call-site record at the current offset.
pub fn add_call_site(&mut self, srcloc: SourceLoc, opcode: Opcode) {
debug_assert!(
opcode.is_call(),
"adding call site info for a non-call instruction."
);
self.call_sites.push(MachCallSite {
ret_addr: self.data.len() as CodeOffset,
srcloc,
@@ -1420,8 +1424,19 @@ impl MachBufferFinalized {
self.data.len() as CodeOffset
}
/// Emit this buffer to the given CodeSink.
pub fn emit<CS: CodeSink>(&self, sink: &mut CS) {
/// Return the code in this mach buffer as a hex string for testing purposes.
pub fn stringify_code_bytes(&self) -> String {
// This is pretty lame, but whatever ..
use std::fmt::Write;
let mut s = String::with_capacity(self.data.len() * 2);
for b in &self.data {
write!(&mut s, "{:02X}", b).unwrap();
}
s
}
/// Get the code bytes.
pub fn data(&self) -> &[u8] {
// N.B.: we emit every section into the .text section as far as
// the `CodeSink` is concerned; we do not bother to segregate
// the contents into the actual program text, the jumptable and the
@@ -1433,40 +1448,28 @@ impl MachBufferFinalized {
// add this designation and segregate the output; take care, however,
// to add the appropriate relocations in this case.
let mut next_reloc = 0;
let mut next_trap = 0;
let mut next_call_site = 0;
for (idx, byte) in self.data.iter().enumerate() {
while next_reloc < self.relocs.len()
&& self.relocs[next_reloc].offset == idx as CodeOffset
{
let reloc = &self.relocs[next_reloc];
sink.reloc_external(reloc.srcloc, reloc.kind, &reloc.name, reloc.addend);
next_reloc += 1;
}
while next_trap < self.traps.len() && self.traps[next_trap].offset == idx as CodeOffset
{
let trap = &self.traps[next_trap];
sink.trap(trap.code, trap.srcloc);
next_trap += 1;
}
while next_call_site < self.call_sites.len()
&& self.call_sites[next_call_site].ret_addr == idx as CodeOffset
{
let call_site = &self.call_sites[next_call_site];
sink.add_call_site(call_site.opcode, call_site.srcloc);
next_call_site += 1;
}
sink.put1(*byte);
}
&self.data[..]
}
sink.end_codegen();
/// Get the list of external relocations for this code.
pub fn relocs(&self) -> &[MachReloc] {
&self.relocs[..]
}
/// Get the list of trap records for this code.
pub fn traps(&self) -> &[MachTrap] {
&self.traps[..]
}
/// Get the stack map metadata for this code.
pub fn stack_maps(&self) -> &[MachStackMap] {
&self.stack_maps[..]
}
/// Get the list of call sites for this code.
pub fn call_sites(&self) -> &[MachCallSite] {
&self.call_sites[..]
}
}
/// A constant that is deferred to the next constant-pool opportunity.
@@ -1496,39 +1499,42 @@ struct MachLabelFixup<I: VCodeInst> {
}
/// A relocation resulting from a compilation.
struct MachReloc {
#[derive(Clone, Debug)]
pub struct MachReloc {
/// The offset at which the relocation applies, *relative to the
/// containing section*.
offset: CodeOffset,
pub offset: CodeOffset,
/// The original source location.
srcloc: SourceLoc,
pub srcloc: SourceLoc,
/// The kind of relocation.
kind: Reloc,
pub kind: Reloc,
/// The external symbol / name to which this relocation refers.
name: ExternalName,
pub name: ExternalName,
/// The addend to add to the symbol value.
addend: i64,
pub addend: i64,
}
/// A trap record resulting from a compilation.
struct MachTrap {
#[derive(Clone, Debug)]
pub struct MachTrap {
/// The offset at which the trap instruction occurs, *relative to the
/// containing section*.
offset: CodeOffset,
pub offset: CodeOffset,
/// The original source location.
srcloc: SourceLoc,
pub srcloc: SourceLoc,
/// The trap code.
code: TrapCode,
pub code: TrapCode,
}
/// A call site record resulting from a compilation.
struct MachCallSite {
#[derive(Clone, Debug)]
pub struct MachCallSite {
/// The offset of the call's return address, *relative to the containing section*.
ret_addr: CodeOffset,
pub ret_addr: CodeOffset,
/// The original source location.
srcloc: SourceLoc,
pub srcloc: SourceLoc,
/// The call's opcode.
opcode: Opcode,
pub opcode: Opcode,
}
/// A source-location mapping resulting from a compilation.
@@ -2060,54 +2066,31 @@ mod test {
let buf = buf.finish();
#[derive(Default)]
struct TestCodeSink {
offset: CodeOffset,
traps: Vec<(CodeOffset, TrapCode)>,
callsites: Vec<(CodeOffset, Opcode)>,
relocs: Vec<(CodeOffset, Reloc)>,
}
impl CodeSink for TestCodeSink {
fn offset(&self) -> CodeOffset {
self.offset
}
fn put1(&mut self, _: u8) {
self.offset += 1;
}
fn put2(&mut self, _: u16) {
self.offset += 2;
}
fn put4(&mut self, _: u32) {
self.offset += 4;
}
fn put8(&mut self, _: u64) {
self.offset += 8;
}
fn reloc_external(&mut self, _: SourceLoc, r: Reloc, _: &ExternalName, _: Addend) {
self.relocs.push((self.offset, r));
}
fn trap(&mut self, t: TrapCode, _: SourceLoc) {
self.traps.push((self.offset, t));
}
fn end_codegen(&mut self) {}
fn add_call_site(&mut self, op: Opcode, _: SourceLoc) {
self.callsites.push((self.offset, op));
}
}
let mut sink = TestCodeSink::default();
buf.emit(&mut sink);
assert_eq!(sink.offset, 4);
assert_eq!(buf.data(), &[1, 2, 3, 4]);
assert_eq!(
sink.traps,
buf.traps()
.iter()
.map(|trap| (trap.offset, trap.code))
.collect::<Vec<_>>(),
vec![
(1, TrapCode::HeapOutOfBounds),
(2, TrapCode::IntegerOverflow),
(2, TrapCode::IntegerDivisionByZero)
]
);
assert_eq!(sink.callsites, vec![(2, Opcode::Call),]);
assert_eq!(sink.relocs, vec![(2, Reloc::Abs4), (3, Reloc::Abs8)]);
assert_eq!(
buf.call_sites()
.iter()
.map(|call_site| (call_site.ret_addr, call_site.opcode))
.collect::<Vec<_>>(),
vec![(2, Opcode::Call)]
);
assert_eq!(
buf.relocs()
.iter()
.map(|reloc| (reloc.offset, reloc.kind))
.collect::<Vec<_>>(),
vec![(2, Reloc::Abs4), (3, Reloc::Abs8)]
);
}
}