Files
wasmtime/cranelift/filetests/src/test_unwind.rs
Jamey Sharp 04b30acad9 Misc cleanups (#5014)
* Replace resize+copy_from_slice with extend_from_slice

Vec::resize initializes the new space, which is wasted effort if we're
just going to call `copy_from_slice` on it immediately afterward. Using
`extend_from_slice` is simpler, and very slightly faster.

If the new size were bigger than the buffer we're copying from, then it
would make sense to initialize the excess. But it isn't: it's always
exactly the same size.

* Move helpers from Context to CompiledCode

These methods only use information from Context::compiled_code, so they
should live on CompiledCode instead.

* Remove an unnecessary #[cfg_attr]

There are other uses of `#[allow(clippy::too_many_arguments)]` in this
file, so apparently it doesn't need to be guarded by the "cargo-clippy"
feature.

* Fix a few comments

Two of these were wrong/misleading:

- `FunctionBuilder::new` does not clear the provided func_ctx. It does
  debug-assert that the context is already clear, but I don't think
  that's worth a comment.

- `switch_to_block` does not "create values for the arguments." That's
  done by the combination of `append_block_params_for_function_params`
  and `declare_wasm_parameters`.

* wasmtime-cranelift: Misc cleanups

The main change is to use the `CompiledCode` reference we already had
instead of getting it out of `Context` repeatedly. This removes a bunch
of `unwrap()` calls.

* wasmtime-cranelift: Factor out uncached compile
2022-10-05 10:35:59 -07:00

571 lines
20 KiB
Rust

//! Test command for verifying the unwind emitted for each function.
//!
//! The `unwind` test command runs each function through the full code generator pipeline.
#![cfg_attr(feature = "cargo-clippy", allow(clippy::cast_ptr_alignment))]
use crate::subtest::{run_filecheck, Context, SubTest};
use cranelift_codegen::{self, ir, isa::unwind::UnwindInfo};
use cranelift_reader::TestCommand;
use gimli::{
write::{Address, EhFrame, EndianVec, FrameTable},
LittleEndian,
};
use std::borrow::Cow;
struct TestUnwind;
pub fn subtest(parsed: &TestCommand) -> anyhow::Result<Box<dyn SubTest>> {
assert_eq!(parsed.command, "unwind");
if !parsed.options.is_empty() {
anyhow::bail!("No options allowed on {}", parsed);
}
Ok(Box::new(TestUnwind))
}
impl SubTest for TestUnwind {
fn name(&self) -> &'static str {
"unwind"
}
fn is_mutating(&self) -> bool {
false
}
fn needs_isa(&self) -> bool {
true
}
fn run(&self, func: Cow<ir::Function>, context: &Context) -> anyhow::Result<()> {
let isa = context.isa.expect("unwind needs an ISA");
let mut comp_ctx = cranelift_codegen::Context::for_function(func.into_owned());
let code = comp_ctx.compile(isa).expect("failed to compile function");
let mut text = String::new();
match code.create_unwind_info(isa).expect("unwind info") {
Some(UnwindInfo::WindowsX64(info)) => {
let mut mem = vec![0; info.emit_size()];
info.emit(&mut mem);
windowsx64::dump(&mut text, &mem);
}
Some(UnwindInfo::SystemV(info)) => {
let mut table = FrameTable::default();
let cie = isa
.create_systemv_cie()
.expect("the ISA should support a System V CIE");
let cie_id = table.add_cie(cie);
table.add_fde(cie_id, info.to_fde(Address::Constant(0)));
let mut eh_frame = EhFrame(EndianVec::new(LittleEndian));
table.write_eh_frame(&mut eh_frame).unwrap();
systemv::dump(&mut text, &eh_frame.0.into_vec(), isa.pointer_bytes())
}
Some(ui) => {
anyhow::bail!("Unexpected unwind info type: {:?}", ui);
}
None => {}
}
run_filecheck(&text, context)
}
}
mod windowsx64 {
use std::fmt::Write;
pub fn dump<W: Write>(text: &mut W, mem: &[u8]) {
let info = UnwindInfo::from_slice(mem);
writeln!(text, " version: {}", info.version).unwrap();
writeln!(text, " flags: {}", info.flags).unwrap();
writeln!(text, " prologue size: {}", info.prologue_size).unwrap();
writeln!(text, " frame register: {}", info.frame_register).unwrap();
writeln!(
text,
"frame register offset: {}",
info.frame_register_offset
)
.unwrap();
writeln!(text, " unwind codes: {}", info.unwind_codes.len()).unwrap();
for code in info.unwind_codes.iter().rev() {
writeln!(text).unwrap();
writeln!(text, " offset: {}", code.offset).unwrap();
writeln!(text, " op: {:?}", code.op).unwrap();
writeln!(text, " info: {}", code.info).unwrap();
match code.value {
UnwindValue::None => {}
UnwindValue::U16(v) => {
writeln!(text, " value: {} (u16)", v).unwrap()
}
UnwindValue::U32(v) => {
writeln!(text, " value: {} (u32)", v).unwrap()
}
};
}
}
#[derive(Debug)]
struct UnwindInfo {
version: u8,
flags: u8,
prologue_size: u8,
#[allow(dead_code)]
unwind_code_count_raw: u8,
frame_register: u8,
frame_register_offset: u8,
unwind_codes: Vec<UnwindCode>,
}
impl UnwindInfo {
fn from_slice(mem: &[u8]) -> Self {
let version_and_flags = mem[0];
let prologue_size = mem[1];
let unwind_code_count_raw = mem[2];
let frame_register_and_offset = mem[3];
let mut unwind_codes = Vec::new();
let mut i = 0;
while i < unwind_code_count_raw {
let code = UnwindCode::from_slice(&mem[(4 + (i * 2) as usize)..]);
i += match &code.value {
UnwindValue::None => 1,
UnwindValue::U16(_) => 2,
UnwindValue::U32(_) => 3,
};
unwind_codes.push(code);
}
Self {
version: version_and_flags & 0x3,
flags: (version_and_flags & 0xF8) >> 3,
prologue_size,
unwind_code_count_raw,
frame_register: frame_register_and_offset & 0xF,
frame_register_offset: (frame_register_and_offset & 0xF0) >> 4,
unwind_codes,
}
}
}
#[derive(Debug)]
struct UnwindCode {
offset: u8,
op: UnwindOperation,
info: u8,
value: UnwindValue,
}
impl UnwindCode {
fn from_slice(mem: &[u8]) -> Self {
let offset = mem[0];
let op_and_info = mem[1];
let op = UnwindOperation::from(op_and_info & 0xF);
let info = (op_and_info & 0xF0) >> 4;
let unwind_le_bytes = |bytes| match (bytes, &mem[2..]) {
(2, &[b0, b1, ..]) => UnwindValue::U16(u16::from_le_bytes([b0, b1])),
(4, &[b0, b1, b2, b3, ..]) => {
UnwindValue::U32(u32::from_le_bytes([b0, b1, b2, b3]))
}
(_, _) => panic!("not enough bytes to unwind value"),
};
let value = match (&op, info) {
(UnwindOperation::LargeStackAlloc, 0) => unwind_le_bytes(2),
(UnwindOperation::LargeStackAlloc, 1) => unwind_le_bytes(4),
(UnwindOperation::LargeStackAlloc, _) => {
panic!("unexpected stack alloc info value")
}
(UnwindOperation::SaveNonvolatileRegister, _) => unwind_le_bytes(2),
(UnwindOperation::SaveNonvolatileRegisterFar, _) => unwind_le_bytes(4),
(UnwindOperation::SaveXmm128, _) => unwind_le_bytes(2),
(UnwindOperation::SaveXmm128Far, _) => unwind_le_bytes(4),
_ => UnwindValue::None,
};
Self {
offset,
op,
info,
value,
}
}
}
#[derive(Debug)]
enum UnwindOperation {
PushNonvolatileRegister = 0,
LargeStackAlloc = 1,
SmallStackAlloc = 2,
SetFramePointer = 3,
SaveNonvolatileRegister = 4,
SaveNonvolatileRegisterFar = 5,
SaveXmm128 = 8,
SaveXmm128Far = 9,
PushMachineFrame = 10,
}
impl From<u8> for UnwindOperation {
fn from(value: u8) -> Self {
// The numerical value is specified as part of the Windows x64 ABI
match value {
0 => Self::PushNonvolatileRegister,
1 => Self::LargeStackAlloc,
2 => Self::SmallStackAlloc,
3 => Self::SetFramePointer,
4 => Self::SaveNonvolatileRegister,
5 => Self::SaveNonvolatileRegisterFar,
8 => Self::SaveXmm128,
9 => Self::SaveXmm128Far,
10 => Self::PushMachineFrame,
_ => panic!("unsupported unwind operation"),
}
}
}
#[derive(Debug)]
enum UnwindValue {
None,
U16(u16),
U32(u32),
}
}
mod systemv {
fn register_name<'a>(register: gimli::Register) -> std::borrow::Cow<'a, str> {
Cow::Owned(format!("r{}", register.0))
}
pub fn dump<W: Write>(text: &mut W, bytes: &[u8], address_size: u8) {
let mut eh_frame = gimli::EhFrame::new(bytes, gimli::LittleEndian);
eh_frame.set_address_size(address_size);
let bases = gimli::BaseAddresses::default();
dump_eh_frame(text, &eh_frame, &bases, &register_name).unwrap();
}
// Remainder copied from https://github.com/gimli-rs/gimli/blob/1e49ffc9af4ec64a1b7316924d73c933dd7157c5/examples/dwarfdump.rs
use gimli::UnwindSection;
use std::borrow::Cow;
use std::collections::HashMap;
use std::fmt::{self, Debug, Write};
use std::result;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Error {
GimliError(gimli::Error),
IoError,
}
impl fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> ::std::result::Result<(), fmt::Error> {
Debug::fmt(self, f)
}
}
impl From<gimli::Error> for Error {
fn from(err: gimli::Error) -> Self {
Self::GimliError(err)
}
}
impl From<fmt::Error> for Error {
fn from(_: fmt::Error) -> Self {
Self::IoError
}
}
pub(super) type Result<T> = result::Result<T, Error>;
pub(super) trait Reader: gimli::Reader<Offset = usize> + Send + Sync {}
impl<'input, Endian> Reader for gimli::EndianSlice<'input, Endian> where
Endian: gimli::Endianity + Send + Sync
{
}
pub(super) fn dump_eh_frame<R: Reader, W: Write>(
w: &mut W,
eh_frame: &gimli::EhFrame<R>,
bases: &gimli::BaseAddresses,
register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>,
) -> Result<()> {
let mut cies = HashMap::new();
let mut entries = eh_frame.entries(bases);
loop {
match entries.next()? {
None => return Ok(()),
Some(gimli::CieOrFde::Cie(cie)) => {
writeln!(w, "{:#010x}: CIE", cie.offset())?;
writeln!(w, " length: {:#010x}", cie.entry_len())?;
// TODO: CIE_id
writeln!(w, " version: {:#04x}", cie.version())?;
// TODO: augmentation
writeln!(w, " code_align: {}", cie.code_alignment_factor())?;
writeln!(w, " data_align: {}", cie.data_alignment_factor())?;
writeln!(w, " ra_register: {:#x}", cie.return_address_register().0)?;
if let Some(encoding) = cie.lsda_encoding() {
writeln!(w, " lsda_encoding: {:#02x}", encoding.0)?;
}
if let Some((encoding, personality)) = cie.personality_with_encoding() {
write!(w, " personality: {:#02x} ", encoding.0)?;
dump_pointer(w, personality)?;
writeln!(w)?;
}
if let Some(encoding) = cie.fde_address_encoding() {
writeln!(w, " fde_encoding: {:#02x}", encoding.0)?;
}
dump_cfi_instructions(
w,
cie.instructions(eh_frame, bases),
true,
register_name,
)?;
writeln!(w)?;
}
Some(gimli::CieOrFde::Fde(partial)) => {
let mut offset = None;
let fde = partial.parse(|_, bases, o| {
offset = Some(o);
cies.entry(o)
.or_insert_with(|| eh_frame.cie_from_offset(bases, o))
.clone()
})?;
writeln!(w)?;
writeln!(w, "{:#010x}: FDE", fde.offset())?;
writeln!(w, " length: {:#010x}", fde.entry_len())?;
writeln!(w, " CIE_pointer: {:#010x}", offset.unwrap().0)?;
// TODO: symbolicate the start address like the canonical dwarfdump does.
writeln!(w, " start_addr: {:#018x}", fde.initial_address())?;
writeln!(
w,
" range_size: {:#018x} (end_addr = {:#018x})",
fde.len(),
fde.initial_address() + fde.len()
)?;
if let Some(lsda) = fde.lsda() {
write!(w, " lsda: ")?;
dump_pointer(w, lsda)?;
writeln!(w)?;
}
dump_cfi_instructions(
w,
fde.instructions(eh_frame, bases),
false,
register_name,
)?;
writeln!(w)?;
}
}
}
}
fn dump_pointer<W: Write>(w: &mut W, p: gimli::Pointer) -> Result<()> {
match p {
gimli::Pointer::Direct(p) => {
write!(w, "{:#018x}", p)?;
}
gimli::Pointer::Indirect(p) => {
write!(w, "({:#018x})", p)?;
}
}
Ok(())
}
#[allow(clippy::unneeded_field_pattern)]
fn dump_cfi_instructions<R: Reader, W: Write>(
w: &mut W,
mut insns: gimli::CallFrameInstructionIter<R>,
is_initial: bool,
register_name: &dyn Fn(gimli::Register) -> Cow<'static, str>,
) -> Result<()> {
use gimli::CallFrameInstruction::*;
// TODO: we need to actually evaluate these instructions as we iterate them
// so we can print the initialized state for CIEs, and each unwind row's
// registers for FDEs.
//
// TODO: We should print DWARF expressions for the CFI instructions that
// embed DWARF expressions within themselves.
if !is_initial {
writeln!(w, " Instructions:")?;
}
loop {
match insns.next() {
Err(e) => {
writeln!(w, "Failed to decode CFI instruction: {}", e)?;
return Ok(());
}
Ok(None) => {
if is_initial {
writeln!(w, " Instructions: Init State:")?;
}
return Ok(());
}
Ok(Some(op)) => match op {
SetLoc { address } => {
writeln!(w, " DW_CFA_set_loc ({:#x})", address)?;
}
AdvanceLoc { delta } => {
writeln!(w, " DW_CFA_advance_loc ({})", delta)?;
}
DefCfa { register, offset } => {
writeln!(
w,
" DW_CFA_def_cfa ({}, {})",
register_name(register),
offset
)?;
}
DefCfaSf {
register,
factored_offset,
} => {
writeln!(
w,
" DW_CFA_def_cfa_sf ({}, {})",
register_name(register),
factored_offset
)?;
}
DefCfaRegister { register } => {
writeln!(
w,
" DW_CFA_def_cfa_register ({})",
register_name(register)
)?;
}
DefCfaOffset { offset } => {
writeln!(w, " DW_CFA_def_cfa_offset ({})", offset)?;
}
DefCfaOffsetSf { factored_offset } => {
writeln!(
w,
" DW_CFA_def_cfa_offset_sf ({})",
factored_offset
)?;
}
DefCfaExpression { expression: _ } => {
writeln!(w, " DW_CFA_def_cfa_expression (...)")?;
}
Undefined { register } => {
writeln!(
w,
" DW_CFA_undefined ({})",
register_name(register)
)?;
}
SameValue { register } => {
writeln!(
w,
" DW_CFA_same_value ({})",
register_name(register)
)?;
}
Offset {
register,
factored_offset,
} => {
writeln!(
w,
" DW_CFA_offset ({}, {})",
register_name(register),
factored_offset
)?;
}
OffsetExtendedSf {
register,
factored_offset,
} => {
writeln!(
w,
" DW_CFA_offset_extended_sf ({}, {})",
register_name(register),
factored_offset
)?;
}
ValOffset {
register,
factored_offset,
} => {
writeln!(
w,
" DW_CFA_val_offset ({}, {})",
register_name(register),
factored_offset
)?;
}
ValOffsetSf {
register,
factored_offset,
} => {
writeln!(
w,
" DW_CFA_val_offset_sf ({}, {})",
register_name(register),
factored_offset
)?;
}
Register {
dest_register,
src_register,
} => {
writeln!(
w,
" DW_CFA_register ({}, {})",
register_name(dest_register),
register_name(src_register)
)?;
}
Expression {
register,
expression: _,
} => {
writeln!(
w,
" DW_CFA_expression ({}, ...)",
register_name(register)
)?;
}
ValExpression {
register,
expression: _,
} => {
writeln!(
w,
" DW_CFA_val_expression ({}, ...)",
register_name(register)
)?;
}
Restore { register } => {
writeln!(
w,
" DW_CFA_restore ({})",
register_name(register)
)?;
}
RememberState => {
writeln!(w, " DW_CFA_remember_state")?;
}
RestoreState => {
writeln!(w, " DW_CFA_restore_state")?;
}
ArgsSize { size } => {
writeln!(w, " DW_CFA_GNU_args_size ({})", size)?;
}
Nop => {
writeln!(w, " DW_CFA_nop")?;
}
},
}
}
}
}