use crate::binemit::{Addend, CodeOffset, Reloc, StackMap};
use crate::ir::function::FunctionParameters;
use crate::ir::{ExternalName, Opcode, RelSourceLoc, SourceLoc, TrapCode};
use crate::isa::unwind::UnwindInst;
use crate::machinst::{
BlockIndex, MachInstLabelUse, TextSectionBuilder, VCodeConstant, VCodeConstants, VCodeInst,
};
use crate::timing;
use crate::trace;
use cranelift_entity::{entity_impl, SecondaryMap};
use smallvec::SmallVec;
use std::convert::TryFrom;
use std::mem;
use std::string::String;
use std::vec::Vec;
#[cfg(feature = "enable-serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "enable-serde")]
pub trait CompilePhase {
type MachSrcLocType: for<'a> Deserialize<'a> + Serialize + core::fmt::Debug + PartialEq + Clone;
type SourceLocType: for<'a> Deserialize<'a> + Serialize + core::fmt::Debug + PartialEq + Clone;
}
#[cfg(not(feature = "enable-serde"))]
pub trait CompilePhase {
type MachSrcLocType: core::fmt::Debug + PartialEq + Clone;
type SourceLocType: core::fmt::Debug + PartialEq + Clone;
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))]
pub struct Stencil;
#[derive(Clone, Debug, PartialEq)]
pub struct Final;
impl CompilePhase for Stencil {
type MachSrcLocType = MachSrcLoc<Stencil>;
type SourceLocType = RelSourceLoc;
}
impl CompilePhase for Final {
type MachSrcLocType = MachSrcLoc<Final>;
type SourceLocType = SourceLoc;
}
pub struct MachBuffer<I: VCodeInst> {
data: SmallVec<[u8; 1024]>,
relocs: SmallVec<[MachReloc; 16]>,
traps: SmallVec<[MachTrap; 16]>,
call_sites: SmallVec<[MachCallSite; 16]>,
srclocs: SmallVec<[MachSrcLoc<Stencil>; 64]>,
stack_maps: SmallVec<[MachStackMap; 8]>,
unwind_info: SmallVec<[(CodeOffset, UnwindInst); 8]>,
cur_srcloc: Option<(CodeOffset, RelSourceLoc)>,
label_offsets: SmallVec<[CodeOffset; 16]>,
label_aliases: SmallVec<[MachLabel; 16]>,
pending_constants: SmallVec<[MachLabelConstant; 16]>,
fixup_records: SmallVec<[MachLabelFixup<I>; 16]>,
island_deadline: CodeOffset,
island_worst_case_size: CodeOffset,
latest_branches: SmallVec<[MachBranch; 4]>,
labels_at_tail: SmallVec<[MachLabel; 4]>,
labels_at_tail_off: CodeOffset,
constant_labels: SecondaryMap<VCodeConstant, MachLabel>,
}
impl MachBufferFinalized<Stencil> {
pub(crate) fn apply_params(self, params: &FunctionParameters) -> MachBufferFinalized<Final> {
MachBufferFinalized {
data: self.data,
relocs: self.relocs,
traps: self.traps,
call_sites: self.call_sites,
srclocs: self
.srclocs
.into_iter()
.map(|srcloc| srcloc.apply_params(params))
.collect(),
stack_maps: self.stack_maps,
unwind_info: self.unwind_info,
}
}
}
#[derive(PartialEq, Debug, Clone)]
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MachBufferFinalized<T: CompilePhase> {
pub(crate) data: SmallVec<[u8; 1024]>,
pub(crate) relocs: SmallVec<[MachReloc; 16]>,
pub(crate) traps: SmallVec<[MachTrap; 16]>,
pub(crate) call_sites: SmallVec<[MachCallSite; 16]>,
pub(crate) srclocs: SmallVec<[T::MachSrcLocType; 64]>,
pub(crate) stack_maps: SmallVec<[MachStackMap; 8]>,
pub unwind_info: SmallVec<[(CodeOffset, UnwindInst); 8]>,
}
const UNKNOWN_LABEL_OFFSET: CodeOffset = 0xffff_ffff;
const UNKNOWN_LABEL: MachLabel = MachLabel(0xffff_ffff);
const LABEL_LIST_THRESHOLD: usize = 100;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MachLabel(u32);
entity_impl!(MachLabel);
impl MachLabel {
pub fn from_block(bindex: BlockIndex) -> MachLabel {
MachLabel(bindex.index() as u32)
}
pub fn get(self) -> u32 {
self.0
}
pub fn to_string(&self) -> String {
format!("label{}", self.0)
}
}
impl Default for MachLabel {
fn default() -> Self {
UNKNOWN_LABEL
}
}
pub enum StackMapExtent {
UpcomingBytes(CodeOffset),
StartedAtOffset(CodeOffset),
}
impl<I: VCodeInst> MachBuffer<I> {
pub fn new() -> MachBuffer<I> {
MachBuffer {
data: SmallVec::new(),
relocs: SmallVec::new(),
traps: SmallVec::new(),
call_sites: SmallVec::new(),
srclocs: SmallVec::new(),
stack_maps: SmallVec::new(),
unwind_info: SmallVec::new(),
cur_srcloc: None,
label_offsets: SmallVec::new(),
label_aliases: SmallVec::new(),
pending_constants: SmallVec::new(),
fixup_records: SmallVec::new(),
island_deadline: UNKNOWN_LABEL_OFFSET,
island_worst_case_size: 0,
latest_branches: SmallVec::new(),
labels_at_tail: SmallVec::new(),
labels_at_tail_off: 0,
constant_labels: SecondaryMap::new(),
}
}
pub fn cur_offset(&self) -> CodeOffset {
self.data.len() as CodeOffset
}
pub fn put1(&mut self, value: u8) {
trace!("MachBuffer: put byte @ {}: {:x}", self.cur_offset(), value);
self.data.push(value);
}
pub fn put2(&mut self, value: u16) {
trace!(
"MachBuffer: put 16-bit word @ {}: {:x}",
self.cur_offset(),
value
);
let bytes = value.to_le_bytes();
self.data.extend_from_slice(&bytes[..]);
}
pub fn put4(&mut self, value: u32) {
trace!(
"MachBuffer: put 32-bit word @ {}: {:x}",
self.cur_offset(),
value
);
let bytes = value.to_le_bytes();
self.data.extend_from_slice(&bytes[..]);
}
pub fn put8(&mut self, value: u64) {
trace!(
"MachBuffer: put 64-bit word @ {}: {:x}",
self.cur_offset(),
value
);
let bytes = value.to_le_bytes();
self.data.extend_from_slice(&bytes[..]);
}
pub fn put_data(&mut self, data: &[u8]) {
trace!(
"MachBuffer: put data @ {}: len {}",
self.cur_offset(),
data.len()
);
self.data.extend_from_slice(data);
}
pub fn get_appended_space(&mut self, len: usize) -> &mut [u8] {
trace!("MachBuffer: put data @ {}: len {}", self.cur_offset(), len);
let off = self.data.len();
let new_len = self.data.len() + len;
self.data.resize(new_len, 0);
&mut self.data[off..]
}
pub fn align_to(&mut self, align_to: CodeOffset) {
trace!("MachBuffer: align to {}", align_to);
assert!(
align_to.is_power_of_two(),
"{} is not a power of two",
align_to
);
while self.cur_offset() & (align_to - 1) != 0 {
self.put1(0);
}
}
pub fn get_label(&mut self) -> MachLabel {
let l = self.label_offsets.len() as u32;
self.label_offsets.push(UNKNOWN_LABEL_OFFSET);
self.label_aliases.push(UNKNOWN_LABEL);
trace!("MachBuffer: new label -> {:?}", MachLabel(l));
MachLabel(l)
}
pub fn reserve_labels_for_blocks(&mut self, blocks: usize) {
trace!("MachBuffer: first {} labels are for blocks", blocks);
debug_assert!(self.label_offsets.is_empty());
self.label_offsets.resize(blocks, UNKNOWN_LABEL_OFFSET);
self.label_aliases.resize(blocks, UNKNOWN_LABEL);
}
pub fn reserve_labels_for_constants(&mut self, constants: &VCodeConstants) {
trace!(
"MachBuffer: next {} labels are for constants",
constants.len()
);
for c in constants.keys() {
self.constant_labels[c] = self.get_label();
}
}
pub fn get_label_for_constant(&self, constant: VCodeConstant) -> MachLabel {
self.constant_labels[constant]
}
pub fn bind_label(&mut self, label: MachLabel) {
trace!(
"MachBuffer: bind label {:?} at offset {}",
label,
self.cur_offset()
);
debug_assert_eq!(self.label_offsets[label.0 as usize], UNKNOWN_LABEL_OFFSET);
debug_assert_eq!(self.label_aliases[label.0 as usize], UNKNOWN_LABEL);
let offset = self.cur_offset();
self.label_offsets[label.0 as usize] = offset;
self.lazily_clear_labels_at_tail();
self.labels_at_tail.push(label);
self.optimize_branches();
}
fn lazily_clear_labels_at_tail(&mut self) {
let offset = self.cur_offset();
if offset > self.labels_at_tail_off {
self.labels_at_tail_off = offset;
self.labels_at_tail.clear();
}
}
pub(crate) fn resolve_label_offset(&self, mut label: MachLabel) -> CodeOffset {
let mut iters = 0;
while self.label_aliases[label.0 as usize] != UNKNOWN_LABEL {
label = self.label_aliases[label.0 as usize];
iters += 1;
assert!(iters < 1_000_000, "Unexpected cycle in label aliases");
}
self.label_offsets[label.0 as usize]
}
pub fn use_label_at_offset(&mut self, offset: CodeOffset, label: MachLabel, kind: I::LabelUse) {
trace!(
"MachBuffer: use_label_at_offset: offset {} label {:?} kind {:?}",
offset,
label,
kind
);
self.fixup_records.push(MachLabelFixup {
label,
offset,
kind,
});
if kind.supports_veneer() {
self.island_worst_case_size += kind.veneer_size();
self.island_worst_case_size &= !(I::LabelUse::ALIGN - 1);
}
let deadline = offset.saturating_add(kind.max_pos_range());
if deadline < self.island_deadline {
self.island_deadline = deadline;
}
}
pub fn add_uncond_branch(&mut self, start: CodeOffset, end: CodeOffset, target: MachLabel) {
assert!(self.cur_offset() == start);
debug_assert!(end > start);
assert!(!self.fixup_records.is_empty());
let fixup = self.fixup_records.len() - 1;
self.lazily_clear_labels_at_tail();
self.latest_branches.push(MachBranch {
start,
end,
target,
fixup,
inverted: None,
labels_at_this_branch: self.labels_at_tail.clone(),
});
}
pub fn add_cond_branch(
&mut self,
start: CodeOffset,
end: CodeOffset,
target: MachLabel,
inverted: &[u8],
) {
assert!(self.cur_offset() == start);
debug_assert!(end > start);
assert!(!self.fixup_records.is_empty());
debug_assert!(inverted.len() == (end - start) as usize);
let fixup = self.fixup_records.len() - 1;
let inverted = Some(SmallVec::from(inverted));
self.lazily_clear_labels_at_tail();
self.latest_branches.push(MachBranch {
start,
end,
target,
fixup,
inverted,
labels_at_this_branch: self.labels_at_tail.clone(),
});
}
fn truncate_last_branch(&mut self) {
self.lazily_clear_labels_at_tail();
let b = self.latest_branches.pop().unwrap();
assert!(b.end == self.cur_offset());
self.data.truncate(b.start as usize);
self.fixup_records.truncate(b.fixup);
while let Some(mut last_srcloc) = self.srclocs.last_mut() {
if last_srcloc.end <= b.start {
break;
}
if last_srcloc.start < b.start {
last_srcloc.end = b.start;
break;
}
self.srclocs.pop();
}
let cur_off = self.cur_offset();
self.labels_at_tail_off = cur_off;
trace!(
"truncate_last_branch: truncated {:?}; off now {}",
b,
cur_off
);
for &l in &self.labels_at_tail {
self.label_offsets[l.0 as usize] = cur_off;
}
self.labels_at_tail
.extend(b.labels_at_this_branch.into_iter());
}
fn optimize_branches(&mut self) {
self.lazily_clear_labels_at_tail();
trace!(
"enter optimize_branches:\n b = {:?}\n l = {:?}\n f = {:?}",
self.latest_branches,
self.labels_at_tail,
self.fixup_records
);
while let Some(b) = self.latest_branches.last() {
let cur_off = self.cur_offset();
trace!("optimize_branches: last branch {:?} at off {}", b, cur_off);
if b.end < cur_off {
break;
}
if b.labels_at_this_branch.len() > LABEL_LIST_THRESHOLD {
break;
}
if self.resolve_label_offset(b.target) == cur_off {
trace!("branch with target == cur off; truncating");
self.truncate_last_branch();
continue;
}
if b.is_uncond() {
if self.resolve_label_offset(b.target) != b.start {
let redirected = b.labels_at_this_branch.len();
for &l in &b.labels_at_this_branch {
trace!(
" -> label at start of branch {:?} redirected to target {:?}",
l,
b.target
);
self.label_aliases[l.0 as usize] = b.target;
}
let mut_b = self.latest_branches.last_mut().unwrap();
mut_b.labels_at_this_branch.clear();
if redirected > 0 {
trace!(" -> after label redirects, restarting loop");
continue;
}
} else {
break;
}
let b = self.latest_branches.last().unwrap();
if self.latest_branches.len() > 1 {
let prev_b = &self.latest_branches[self.latest_branches.len() - 2];
trace!(" -> more than one branch; prev_b = {:?}", prev_b);
if prev_b.is_uncond()
&& prev_b.end == b.start
&& b.labels_at_this_branch.is_empty()
{
trace!(" -> uncond follows another uncond; truncating");
self.truncate_last_branch();
continue;
}
if prev_b.is_cond()
&& prev_b.end == b.start
&& self.resolve_label_offset(prev_b.target) == cur_off
{
trace!(" -> uncond follows a conditional, and conditional's target resolves to current offset");
let target = b.target;
let data = prev_b.inverted.clone().unwrap();
self.truncate_last_branch();
let off_before_edit = self.cur_offset();
let prev_b = self.latest_branches.last_mut().unwrap();
let not_inverted = SmallVec::from(
&self.data[(prev_b.start as usize)..(prev_b.end as usize)],
);
self.data.truncate(prev_b.start as usize);
self.data.extend_from_slice(&data[..]);
prev_b.inverted = Some(not_inverted);
self.fixup_records[prev_b.fixup].label = target;
trace!(" -> reassigning target of condbr to {:?}", target);
prev_b.target = target;
debug_assert_eq!(off_before_edit, self.cur_offset());
continue;
}
}
}
break;
}
self.purge_latest_branches();
trace!(
"leave optimize_branches:\n b = {:?}\n l = {:?}\n f = {:?}",
self.latest_branches,
self.labels_at_tail,
self.fixup_records
);
}
fn purge_latest_branches(&mut self) {
let cur_off = self.cur_offset();
if let Some(l) = self.latest_branches.last() {
if l.end < cur_off {
trace!("purge_latest_branches: removing branch {:?}", l);
self.latest_branches.clear();
}
}
}
pub fn defer_constant(
&mut self,
label: MachLabel,
align: CodeOffset,
data: &[u8],
max_distance: CodeOffset,
) {
trace!(
"defer_constant: eventually emit {} bytes aligned to {} at label {:?}",
data.len(),
align,
label
);
let deadline = self.cur_offset().saturating_add(max_distance);
self.island_worst_case_size += data.len() as CodeOffset;
self.island_worst_case_size =
(self.island_worst_case_size + I::LabelUse::ALIGN - 1) & !(I::LabelUse::ALIGN - 1);
self.pending_constants.push(MachLabelConstant {
label,
align,
data: SmallVec::from(data),
});
if deadline < self.island_deadline {
self.island_deadline = deadline;
}
}
pub fn island_needed(&self, distance: CodeOffset) -> bool {
self.worst_case_end_of_island(distance) > self.island_deadline
}
fn worst_case_end_of_island(&self, distance: CodeOffset) -> CodeOffset {
self.cur_offset()
.saturating_add(distance)
.saturating_add(self.island_worst_case_size)
}
pub fn emit_island(&mut self, distance: CodeOffset) {
self.emit_island_maybe_forced(false, distance);
}
fn emit_island_maybe_forced(&mut self, force_veneers: bool, distance: CodeOffset) {
self.latest_branches.clear();
let forced_threshold = self.worst_case_end_of_island(distance);
self.island_deadline = UNKNOWN_LABEL_OFFSET;
self.island_worst_case_size = 0;
for MachLabelConstant { label, align, data } in mem::take(&mut self.pending_constants) {
self.align_to(align);
self.bind_label(label);
self.put_data(&data[..]);
}
for fixup in mem::take(&mut self.fixup_records) {
trace!("emit_island: fixup {:?}", fixup);
let MachLabelFixup {
label,
offset,
kind,
} = fixup;
let label_offset = self.resolve_label_offset(label);
let start = offset as usize;
let end = (offset + kind.patch_size()) as usize;
if label_offset != UNKNOWN_LABEL_OFFSET {
let veneer_required = if label_offset >= offset {
assert!((label_offset - offset) <= kind.max_pos_range());
false
} else {
(offset - label_offset) > kind.max_neg_range()
};
trace!(
" -> label_offset = {}, known, required = {} (pos {} neg {})",
label_offset,
veneer_required,
kind.max_pos_range(),
kind.max_neg_range()
);
if (force_veneers && kind.supports_veneer()) || veneer_required {
self.emit_veneer(label, offset, kind);
} else {
let slice = &mut self.data[start..end];
trace!("patching in-range!");
kind.patch(slice, offset, label_offset);
}
} else {
if forced_threshold - offset > kind.max_pos_range() {
self.emit_veneer(label, offset, kind);
} else {
self.use_label_at_offset(offset, label, kind);
}
}
}
}
fn emit_veneer(&mut self, label: MachLabel, offset: CodeOffset, kind: I::LabelUse) {
assert!(
kind.supports_veneer(),
"jump beyond the range of {:?} but a veneer isn't supported",
kind,
);
self.align_to(I::LabelUse::ALIGN);
let veneer_offset = self.cur_offset();
trace!("making a veneer at {}", veneer_offset);
let start = offset as usize;
let end = (offset + kind.patch_size()) as usize;
let slice = &mut self.data[start..end];
trace!(
"patching original at offset {} to veneer offset {}",
offset,
veneer_offset
);
kind.patch(slice, offset, veneer_offset);
let veneer_slice = self.get_appended_space(kind.veneer_size() as usize);
let (veneer_fixup_off, veneer_label_use) =
kind.generate_veneer(veneer_slice, veneer_offset);
trace!(
"generated veneer; fixup offset {}, label_use {:?}",
veneer_fixup_off,
veneer_label_use
);
self.use_label_at_offset(veneer_fixup_off, label, veneer_label_use);
}
fn finish_emission_maybe_forcing_veneers(&mut self, force_veneers: bool) {
while !self.pending_constants.is_empty() || !self.fixup_records.is_empty() {
self.emit_island_maybe_forced(force_veneers, u32::MAX);
}
assert!(self.fixup_records.is_empty());
}
pub fn finish(mut self) -> MachBufferFinalized<Stencil> {
let _tt = timing::vcode_emit_finish();
self.optimize_branches();
self.finish_emission_maybe_forcing_veneers(false);
let mut srclocs = self.srclocs;
srclocs.sort_by_key(|entry| entry.start);
MachBufferFinalized {
data: self.data,
relocs: self.relocs,
traps: self.traps,
call_sites: self.call_sites,
srclocs,
stack_maps: self.stack_maps,
unwind_info: self.unwind_info,
}
}
pub fn add_reloc(&mut self, kind: Reloc, name: &ExternalName, addend: Addend) {
let name = name.clone();
self.relocs.push(MachReloc {
offset: self.data.len() as CodeOffset,
kind,
name,
addend,
});
}
pub fn add_trap(&mut self, code: TrapCode) {
self.traps.push(MachTrap {
offset: self.data.len() as CodeOffset,
code,
});
}
pub fn add_call_site(&mut self, 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,
opcode,
});
}
pub fn add_unwind(&mut self, unwind: UnwindInst) {
self.unwind_info.push((self.cur_offset(), unwind));
}
pub fn start_srcloc(&mut self, loc: RelSourceLoc) {
self.cur_srcloc = Some((self.cur_offset(), loc));
}
pub fn end_srcloc(&mut self) {
let (start, loc) = self
.cur_srcloc
.take()
.expect("end_srcloc() called without start_srcloc()");
let end = self.cur_offset();
debug_assert!(end >= start);
if end > start {
self.srclocs.push(MachSrcLoc { start, end, loc });
}
}
pub fn add_stack_map(&mut self, extent: StackMapExtent, stack_map: StackMap) {
let (start, end) = match extent {
StackMapExtent::UpcomingBytes(insn_len) => {
let start_offset = self.cur_offset();
(start_offset, start_offset + insn_len)
}
StackMapExtent::StartedAtOffset(start_offset) => {
let end_offset = self.cur_offset();
debug_assert!(end_offset >= start_offset);
(start_offset, end_offset)
}
};
trace!("Adding stack map for offsets {start:#x}..{end:#x}");
self.stack_maps.push(MachStackMap {
offset: start,
offset_end: end,
stack_map,
});
}
}
impl<T: CompilePhase> MachBufferFinalized<T> {
pub fn get_srclocs_sorted(&self) -> &[T::MachSrcLocType] {
&self.srclocs[..]
}
pub fn total_size(&self) -> CodeOffset {
self.data.len() as CodeOffset
}
pub fn stringify_code_bytes(&self) -> String {
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
}
pub fn data(&self) -> &[u8] {
&self.data[..]
}
pub fn relocs(&self) -> &[MachReloc] {
&self.relocs[..]
}
pub fn traps(&self) -> &[MachTrap] {
&self.traps[..]
}
pub fn stack_maps(&self) -> &[MachStackMap] {
&self.stack_maps[..]
}
pub fn call_sites(&self) -> &[MachCallSite] {
&self.call_sites[..]
}
}
struct MachLabelConstant {
label: MachLabel,
align: CodeOffset,
data: SmallVec<[u8; 16]>,
}
#[derive(Debug)]
struct MachLabelFixup<I: VCodeInst> {
label: MachLabel,
offset: CodeOffset,
kind: I::LabelUse,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MachReloc {
pub offset: CodeOffset,
pub kind: Reloc,
pub name: ExternalName,
pub addend: i64,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MachTrap {
pub offset: CodeOffset,
pub code: TrapCode,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MachCallSite {
pub ret_addr: CodeOffset,
pub opcode: Opcode,
}
#[derive(PartialEq, Debug, Clone)]
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MachSrcLoc<T: CompilePhase> {
pub start: CodeOffset,
pub end: CodeOffset,
pub loc: T::SourceLocType,
}
impl MachSrcLoc<Stencil> {
fn apply_params(self, params: &FunctionParameters) -> MachSrcLoc<Final> {
MachSrcLoc {
start: self.start,
end: self.end,
loc: self.loc.expand(params.base_srcloc()),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "enable-serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MachStackMap {
pub offset: CodeOffset,
pub offset_end: CodeOffset,
pub stack_map: StackMap,
}
#[derive(Clone, Debug)]
struct MachBranch {
start: CodeOffset,
end: CodeOffset,
target: MachLabel,
fixup: usize,
inverted: Option<SmallVec<[u8; 8]>>,
labels_at_this_branch: SmallVec<[MachLabel; 4]>,
}
impl MachBranch {
fn is_cond(&self) -> bool {
self.inverted.is_some()
}
fn is_uncond(&self) -> bool {
self.inverted.is_none()
}
}
pub struct MachTextSectionBuilder<I: VCodeInst> {
buf: MachBuffer<I>,
next_func: usize,
force_veneers: bool,
}
impl<I: VCodeInst> MachTextSectionBuilder<I> {
pub fn new(num_funcs: u32) -> MachTextSectionBuilder<I> {
let mut buf = MachBuffer::new();
buf.reserve_labels_for_blocks(num_funcs as usize);
MachTextSectionBuilder {
buf,
next_func: 0,
force_veneers: false,
}
}
}
impl<I: VCodeInst> TextSectionBuilder for MachTextSectionBuilder<I> {
fn append(&mut self, named: bool, func: &[u8], align: u32) -> u64 {
let size = func.len() as u32;
if self.force_veneers || self.buf.island_needed(size) {
self.buf.emit_island_maybe_forced(self.force_veneers, size);
}
self.buf.align_to(align);
let pos = self.buf.cur_offset();
if named {
self.buf
.bind_label(MachLabel::from_block(BlockIndex::new(self.next_func)));
self.next_func += 1;
}
self.buf.put_data(func);
u64::from(pos)
}
fn resolve_reloc(&mut self, offset: u64, reloc: Reloc, addend: Addend, target: u32) -> bool {
let label = MachLabel::from_block(BlockIndex::new(target as usize));
let offset = u32::try_from(offset).unwrap();
match I::LabelUse::from_reloc(reloc, addend) {
Some(label_use) => {
self.buf.use_label_at_offset(offset, label, label_use);
true
}
None => false,
}
}
fn force_veneers(&mut self) {
self.force_veneers = true;
}
fn finish(&mut self) -> Vec<u8> {
assert_eq!(self.next_func, self.buf.label_offsets.len());
self.buf
.finish_emission_maybe_forcing_veneers(self.force_veneers);
mem::take(&mut self.buf.data).into_vec()
}
}
#[cfg(all(test, feature = "arm64"))]
mod test {
use cranelift_entity::EntityRef as _;
use super::*;
use crate::ir::UserExternalNameRef;
use crate::isa::aarch64::inst::xreg;
use crate::isa::aarch64::inst::{BranchTarget, CondBrKind, EmitInfo, Inst};
use crate::machinst::MachInstEmit;
use crate::settings;
use std::default::Default;
use std::vec::Vec;
fn label(n: u32) -> MachLabel {
MachLabel::from_block(BlockIndex::new(n as usize))
}
fn target(n: u32) -> BranchTarget {
BranchTarget::Label(label(n))
}
#[test]
fn test_elide_jump_to_next() {
let info = EmitInfo::new(settings::Flags::new(settings::builder()));
let mut buf = MachBuffer::new();
let mut state = Default::default();
buf.reserve_labels_for_blocks(2);
buf.bind_label(label(0));
let inst = Inst::Jump { dest: target(1) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(1));
let buf = buf.finish();
assert_eq!(0, buf.total_size());
}
#[test]
fn test_elide_trivial_jump_blocks() {
let info = EmitInfo::new(settings::Flags::new(settings::builder()));
let mut buf = MachBuffer::new();
let mut state = Default::default();
buf.reserve_labels_for_blocks(4);
buf.bind_label(label(0));
let inst = Inst::CondBr {
kind: CondBrKind::NotZero(xreg(0)),
taken: target(1),
not_taken: target(2),
};
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(1));
let inst = Inst::Jump { dest: target(3) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(2));
let inst = Inst::Jump { dest: target(3) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(3));
let buf = buf.finish();
assert_eq!(0, buf.total_size());
}
#[test]
fn test_flip_cond() {
let info = EmitInfo::new(settings::Flags::new(settings::builder()));
let mut buf = MachBuffer::new();
let mut state = Default::default();
buf.reserve_labels_for_blocks(4);
buf.bind_label(label(0));
let inst = Inst::CondBr {
kind: CondBrKind::NotZero(xreg(0)),
taken: target(1),
not_taken: target(2),
};
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(1));
let inst = Inst::Udf {
trap_code: TrapCode::Interrupt,
};
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(2));
let inst = Inst::Nop4;
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(3));
let buf = buf.finish();
let mut buf2 = MachBuffer::new();
let mut state = Default::default();
let inst = Inst::TrapIf {
kind: CondBrKind::NotZero(xreg(0)),
trap_code: TrapCode::Interrupt,
};
inst.emit(&[], &mut buf2, &info, &mut state);
let inst = Inst::Nop4;
inst.emit(&[], &mut buf2, &info, &mut state);
let buf2 = buf2.finish();
assert_eq!(buf.data, buf2.data);
}
#[test]
fn test_island() {
let info = EmitInfo::new(settings::Flags::new(settings::builder()));
let mut buf = MachBuffer::new();
let mut state = Default::default();
buf.reserve_labels_for_blocks(4);
buf.bind_label(label(0));
let inst = Inst::CondBr {
kind: CondBrKind::NotZero(xreg(0)),
taken: target(2),
not_taken: target(3),
};
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(1));
while buf.cur_offset() < 2000000 {
if buf.island_needed(0) {
buf.emit_island(0);
}
let inst = Inst::Nop4;
inst.emit(&[], &mut buf, &info, &mut state);
}
buf.bind_label(label(2));
let inst = Inst::Nop4;
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(3));
let inst = Inst::Nop4;
inst.emit(&[], &mut buf, &info, &mut state);
let buf = buf.finish();
assert_eq!(2000000 + 8, buf.total_size());
let mut buf2 = MachBuffer::new();
let mut state = Default::default();
let inst = Inst::CondBr {
kind: CondBrKind::NotZero(xreg(0)),
taken: BranchTarget::ResolvedOffset((1 << 20) - 4 - 20),
not_taken: BranchTarget::ResolvedOffset(2000000 + 4 - 4),
};
inst.emit(&[], &mut buf2, &info, &mut state);
let buf2 = buf2.finish();
assert_eq!(&buf.data[0..8], &buf2.data[..]);
}
#[test]
fn test_island_backward() {
let info = EmitInfo::new(settings::Flags::new(settings::builder()));
let mut buf = MachBuffer::new();
let mut state = Default::default();
buf.reserve_labels_for_blocks(4);
buf.bind_label(label(0));
let inst = Inst::Nop4;
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(1));
let inst = Inst::Nop4;
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(2));
while buf.cur_offset() < 2000000 {
let inst = Inst::Nop4;
inst.emit(&[], &mut buf, &info, &mut state);
}
buf.bind_label(label(3));
let inst = Inst::CondBr {
kind: CondBrKind::NotZero(xreg(0)),
taken: target(0),
not_taken: target(1),
};
inst.emit(&[], &mut buf, &info, &mut state);
let buf = buf.finish();
assert_eq!(2000000 + 12, buf.total_size());
let mut buf2 = MachBuffer::new();
let mut state = Default::default();
let inst = Inst::CondBr {
kind: CondBrKind::NotZero(xreg(0)),
taken: BranchTarget::ResolvedOffset(8),
not_taken: BranchTarget::ResolvedOffset(4 - (2000000 + 4)),
};
inst.emit(&[], &mut buf2, &info, &mut state);
let inst = Inst::Jump {
dest: BranchTarget::ResolvedOffset(-(2000000 + 8)),
};
inst.emit(&[], &mut buf2, &info, &mut state);
let buf2 = buf2.finish();
assert_eq!(&buf.data[2000000..], &buf2.data[..]);
}
#[test]
fn test_multiple_redirect() {
let info = EmitInfo::new(settings::Flags::new(settings::builder()));
let mut buf = MachBuffer::new();
let mut state = Default::default();
buf.reserve_labels_for_blocks(8);
buf.bind_label(label(0));
let inst = Inst::CondBr {
kind: CondBrKind::Zero(xreg(0)),
taken: target(1),
not_taken: target(2),
};
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(1));
let inst = Inst::Jump { dest: target(3) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(2));
let inst = Inst::Nop4;
inst.emit(&[], &mut buf, &info, &mut state);
inst.emit(&[], &mut buf, &info, &mut state);
let inst = Inst::Jump { dest: target(0) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(3));
let inst = Inst::Jump { dest: target(4) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(4));
let inst = Inst::Jump { dest: target(5) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(5));
let inst = Inst::Jump { dest: target(7) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(6));
let inst = Inst::Nop4;
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(7));
let inst = Inst::Ret { rets: vec![] };
inst.emit(&[], &mut buf, &info, &mut state);
let buf = buf.finish();
let golden_data = vec![
0xa0, 0x00, 0x00, 0xb4, 0x1f, 0x20, 0x03, 0xd5, 0x1f, 0x20, 0x03, 0xd5, 0xfd, 0xff, 0xff, 0x17, 0x1f, 0x20, 0x03, 0xd5, 0xc0, 0x03, 0x5f, 0xd6, ];
assert_eq!(&golden_data[..], &buf.data[..]);
}
#[test]
fn test_handle_branch_cycle() {
let info = EmitInfo::new(settings::Flags::new(settings::builder()));
let mut buf = MachBuffer::new();
let mut state = Default::default();
buf.reserve_labels_for_blocks(5);
buf.bind_label(label(0));
let inst = Inst::Jump { dest: target(1) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(1));
let inst = Inst::Jump { dest: target(2) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(2));
let inst = Inst::Jump { dest: target(3) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(3));
let inst = Inst::Jump { dest: target(4) };
inst.emit(&[], &mut buf, &info, &mut state);
buf.bind_label(label(4));
let inst = Inst::Jump { dest: target(1) };
inst.emit(&[], &mut buf, &info, &mut state);
let buf = buf.finish();
let golden_data = vec![
0x00, 0x00, 0x00, 0x14, ];
assert_eq!(&golden_data[..], &buf.data[..]);
}
#[test]
fn metadata_records() {
let mut buf = MachBuffer::<Inst>::new();
buf.reserve_labels_for_blocks(1);
buf.bind_label(label(0));
buf.put1(1);
buf.add_trap(TrapCode::HeapOutOfBounds);
buf.put1(2);
buf.add_trap(TrapCode::IntegerOverflow);
buf.add_trap(TrapCode::IntegerDivisionByZero);
buf.add_call_site(Opcode::Call);
buf.add_reloc(
Reloc::Abs4,
&ExternalName::User(UserExternalNameRef::new(0)),
0,
);
buf.put1(3);
buf.add_reloc(
Reloc::Abs8,
&ExternalName::User(UserExternalNameRef::new(1)),
1,
);
buf.put1(4);
let buf = buf.finish();
assert_eq!(buf.data(), &[1, 2, 3, 4]);
assert_eq!(
buf.traps()
.iter()
.map(|trap| (trap.offset, trap.code))
.collect::<Vec<_>>(),
vec![
(1, TrapCode::HeapOutOfBounds),
(2, TrapCode::IntegerOverflow),
(2, TrapCode::IntegerDivisionByZero)
]
);
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)]
);
}
}