Refactor unwind generation in Cranelift.

This commit makes the following changes to unwind information generation in
Cranelift:

* Remove frame layout change implementation in favor of processing the prologue
  and epilogue instructions when unwind information is requested.  This also
  means this work is no longer performed for Windows, which didn't utilize it.
  It also helps simplify the prologue and epilogue generation code.

* Remove the unwind sink implementation that required each unwind information
  to be represented in final form. For FDEs, this meant writing a
  complete frame table per function, which wastes 20 bytes or so for each
  function with duplicate CIEs.  This also enables Cranelift users to collect the
  unwind information and write it as a single frame table.

* For System V calling convention, the unwind information is no longer stored
  in code memory (it's only a requirement for Windows ABI to do so).  This allows
  for more compact code memory for modules with a lot of functions.

* Deletes some duplicate code relating to frame table generation.  Users can
  now simply use gimli to create a frame table from each function's unwind
  information.

Fixes #1181.
This commit is contained in:
Peter Huene
2020-03-30 19:48:02 -07:00
parent 7da6101732
commit f7e9f86ba9
42 changed files with 2678 additions and 3161 deletions

View File

@@ -1,139 +0,0 @@
use std::collections::HashMap;
use wasmtime_environ::entity::EntityRef;
use wasmtime_environ::isa::{CallConv, TargetIsa};
use wasmtime_environ::wasm::DefinedFuncIndex;
use wasmtime_environ::{FrameLayoutChange, FrameLayouts};
use gimli::write::{
Address, CallFrameInstruction, CommonInformationEntry as CIEEntry, Error,
FrameDescriptionEntry as FDEEntry, FrameTable,
};
use gimli::{Encoding, Format, Register, X86_64};
fn to_cfi(
isa: &dyn TargetIsa,
change: &FrameLayoutChange,
cfa_def_reg: &mut Register,
cfa_def_offset: &mut i32,
) -> Option<CallFrameInstruction> {
Some(match change {
FrameLayoutChange::CallFrameAddressAt { reg, offset } => {
let mapped = match isa.map_dwarf_register(*reg) {
Ok(r) => Register(r),
Err(_) => return None,
};
let offset = (*offset) as i32;
if mapped != *cfa_def_reg && offset != *cfa_def_offset {
*cfa_def_reg = mapped;
*cfa_def_offset = offset;
CallFrameInstruction::Cfa(mapped, offset)
} else if offset != *cfa_def_offset {
*cfa_def_offset = offset;
CallFrameInstruction::CfaOffset(offset)
} else if mapped != *cfa_def_reg {
*cfa_def_reg = mapped;
CallFrameInstruction::CfaRegister(mapped)
} else {
return None;
}
}
FrameLayoutChange::RegAt { reg, cfa_offset } => {
assert!(cfa_offset % -8 == 0);
let cfa_offset = *cfa_offset as i32;
let mapped = match isa.map_dwarf_register(*reg) {
Ok(r) => Register(r),
Err(_) => return None,
};
CallFrameInstruction::Offset(mapped, cfa_offset)
}
FrameLayoutChange::ReturnAddressAt { cfa_offset } => {
assert!(cfa_offset % -8 == 0);
let cfa_offset = *cfa_offset as i32;
CallFrameInstruction::Offset(X86_64::RA, cfa_offset)
}
FrameLayoutChange::Preserve => CallFrameInstruction::RememberState,
FrameLayoutChange::Restore => CallFrameInstruction::RestoreState,
})
}
pub fn get_debug_frame_bytes(
funcs: &[(*const u8, usize)],
isa: &dyn TargetIsa,
layouts: &FrameLayouts,
) -> Result<Option<FrameTable>, Error> {
// FIXME Only x86-64 at this moment.
if isa.name() != "x86" || isa.pointer_bits() != 64 {
return Ok(None);
}
let address_size = isa.pointer_bytes();
let encoding = Encoding {
format: Format::Dwarf64,
version: 4,
address_size,
};
let mut frames = FrameTable::default();
let mut cached_cies = HashMap::new();
for (i, f) in funcs.into_iter().enumerate() {
let layout = &layouts[DefinedFuncIndex::new(i)];
// FIXME Can only process functions with SystemV-like prologue.
if layout.call_conv != CallConv::Fast
&& layout.call_conv != CallConv::Cold
&& layout.call_conv != CallConv::SystemV
{
continue;
}
// Caching CIE with similar initial_commands.
let (cie_id, mut cfa_def_reg, mut cfa_def_offset) = {
use std::collections::hash_map::Entry;
match cached_cies.entry(&layout.initial_commands) {
Entry::Occupied(o) => *o.get(),
Entry::Vacant(v) => {
// cfa_def_reg and cfa_def_offset initialized with some random values.
let mut cfa_def_reg = X86_64::RA;
let mut cfa_def_offset = 0i32;
// TODO adjust code_alignment_factor and data_alignment_factor based on ISA.
let mut cie = CIEEntry::new(
encoding,
/* code_alignment_factor = */ 1,
/* data_alignment_factor = */ -8,
/* return_address_register = */ X86_64::RA,
);
for cmd in layout.initial_commands.iter() {
if let Some(instr) = to_cfi(isa, cmd, &mut cfa_def_reg, &mut cfa_def_offset)
{
cie.add_instruction(instr);
}
}
let cie_id = frames.add_cie(cie);
*v.insert((cie_id, cfa_def_reg, cfa_def_offset))
}
}
};
let f_len = f.1 as u32;
let mut fde = FDEEntry::new(
Address::Symbol {
symbol: i,
addend: 0,
},
f_len,
);
for (offset, cmd) in layout.commands.into_iter() {
if let Some(instr) = to_cfi(isa, cmd, &mut cfa_def_reg, &mut cfa_def_offset) {
fde.add_instruction(*offset as u32, instr);
}
}
frames.add_fde(cie_id, fde);
}
Ok(Some(frames))
}

View File

@@ -2,19 +2,18 @@
#![allow(clippy::cast_ptr_alignment)]
use crate::frame::get_debug_frame_bytes;
use anyhow::Error;
use faerie::{Artifact, Decl};
use gimli::write::{Address, FrameTable};
use more_asserts::assert_gt;
use target_lexicon::BinaryFormat;
use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::{FrameLayouts, ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
use wasmtime_environ::isa::{unwind::UnwindInfo, TargetIsa};
use wasmtime_environ::{Compilation, ModuleAddressMap, ModuleVmctxInfo, ValueLabelsRanges};
pub use crate::read_debuginfo::{read_debuginfo, DebugInfoData, WasmFileInfo};
pub use crate::transform::transform_dwarf;
pub use crate::write_debuginfo::{emit_dwarf, ResolvedSymbol, SymbolResolver};
mod frame;
mod gc;
mod read_debuginfo;
mod transform;
@@ -28,6 +27,29 @@ impl SymbolResolver for FunctionRelocResolver {
}
}
fn create_frame_table<'a>(
isa: &dyn TargetIsa,
infos: impl Iterator<Item = &'a Option<UnwindInfo>>,
) -> Option<FrameTable> {
let mut table = FrameTable::default();
let cie_id = table.add_cie(isa.create_systemv_cie()?);
for (i, info) in infos.enumerate() {
if let Some(UnwindInfo::SystemV(info)) = info {
table.add_fde(
cie_id,
info.to_fde(Address::Symbol {
symbol: i,
addend: 0,
}),
);
}
}
Some(table)
}
pub fn emit_debugsections(
obj: &mut Artifact,
vmctx_info: &ModuleVmctxInfo,
@@ -35,21 +57,13 @@ pub fn emit_debugsections(
debuginfo_data: &DebugInfoData,
at: &ModuleAddressMap,
ranges: &ValueLabelsRanges,
frame_layouts: &FrameLayouts,
compilation: &Compilation,
) -> Result<(), Error> {
let resolver = FunctionRelocResolver {};
let dwarf = transform_dwarf(isa, debuginfo_data, at, vmctx_info, ranges)?;
let frame_table = create_frame_table(isa, compilation.into_iter().map(|f| &f.unwind_info));
let max = at.values().map(|v| v.body_len).fold(0, usize::max);
let mut funcs_bodies = Vec::with_capacity(max as usize);
funcs_bodies.resize(max as usize, 0);
let funcs = at
.values()
.map(|v| (::std::ptr::null(), v.body_len))
.collect::<Vec<(*const u8, usize)>>();
let frames = get_debug_frame_bytes(&funcs, isa, frame_layouts)?;
emit_dwarf(obj, dwarf, &resolver, frames)?;
emit_dwarf(obj, dwarf, &resolver, frame_table)?;
Ok(())
}
@@ -70,8 +84,8 @@ pub fn emit_debugsections_image(
vmctx_info: &ModuleVmctxInfo,
at: &ModuleAddressMap,
ranges: &ValueLabelsRanges,
frame_layouts: &FrameLayouts,
funcs: &[(*const u8, usize)],
compilation: &Compilation,
) -> Result<Vec<u8>, Error> {
let func_offsets = &funcs
.iter()
@@ -93,8 +107,8 @@ pub fn emit_debugsections_image(
let body = unsafe { std::slice::from_raw_parts(segment_body.0, segment_body.1) };
obj.declare_with("all", Decl::function(), body.to_vec())?;
let frames = get_debug_frame_bytes(funcs, isa, frame_layouts)?;
emit_dwarf(&mut obj, dwarf, &resolver, frames)?;
let frame_table = create_frame_table(isa, compilation.into_iter().map(|f| &f.unwind_info));
emit_dwarf(&mut obj, dwarf, &resolver, frame_table)?;
// LLDB is too "magical" about mach-o, generating elf
let mut bytes = obj.emit_as(BinaryFormat::Elf)?;