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,37 +1,33 @@
//! Memory management for executable code.
use crate::function_table::FunctionTable;
use crate::unwind::UnwindRegistry;
use region;
use std::mem::ManuallyDrop;
use std::{cmp, mem};
use wasmtime_environ::{Compilation, CompiledFunction};
use wasmtime_environ::{
isa::{unwind::UnwindInfo, TargetIsa},
Compilation, CompiledFunction,
};
use wasmtime_runtime::{Mmap, VMFunctionBody};
struct CodeMemoryEntry {
mmap: ManuallyDrop<Mmap>,
table: ManuallyDrop<FunctionTable>,
registry: ManuallyDrop<UnwindRegistry>,
}
impl CodeMemoryEntry {
fn new() -> Self {
Self {
mmap: ManuallyDrop::new(Mmap::new()),
table: ManuallyDrop::new(FunctionTable::new()),
}
}
fn with_capacity(cap: usize) -> Result<Self, String> {
Ok(Self {
mmap: ManuallyDrop::new(Mmap::with_at_least(cap)?),
table: ManuallyDrop::new(FunctionTable::new()),
})
let mmap = ManuallyDrop::new(Mmap::with_at_least(cap)?);
let registry = ManuallyDrop::new(UnwindRegistry::new(mmap.as_ptr() as usize));
Ok(Self { mmap, registry })
}
}
impl Drop for CodeMemoryEntry {
fn drop(&mut self) {
unsafe {
// Table needs to be freed before mmap.
ManuallyDrop::drop(&mut self.table);
// The registry needs to be dropped before the mmap
ManuallyDrop::drop(&mut self.registry);
ManuallyDrop::drop(&mut self.mmap);
}
}
@@ -39,7 +35,7 @@ impl Drop for CodeMemoryEntry {
/// Memory manager for executable code.
pub struct CodeMemory {
current: CodeMemoryEntry,
current: Option<CodeMemoryEntry>,
entries: Vec<CodeMemoryEntry>,
position: usize,
published: usize,
@@ -54,7 +50,7 @@ impl CodeMemory {
/// Create a new `CodeMemory` instance.
pub fn new() -> Self {
Self {
current: CodeMemoryEntry::new(),
current: None,
entries: Vec::new(),
position: 0,
published: 0,
@@ -70,16 +66,14 @@ impl CodeMemory {
) -> Result<&mut [VMFunctionBody], String> {
let size = Self::function_allocation_size(func);
let (buf, table, start) = self.allocate(size)?;
let (buf, registry, start) = self.allocate(size)?;
let (_, _, _, vmfunc) = Self::copy_function(func, start as u32, buf, table);
let (_, _, vmfunc) = Self::copy_function(func, start as u32, buf, registry);
Ok(vmfunc)
}
/// Allocate a continuous memory block for a compilation.
///
/// Allocates memory for both the function bodies as well as function unwind data.
pub fn allocate_for_compilation(
&mut self,
compilation: &Compilation,
@@ -88,33 +82,35 @@ impl CodeMemory {
.into_iter()
.fold(0, |acc, func| acc + Self::function_allocation_size(func));
let (mut buf, mut table, start) = self.allocate(total_len)?;
let (mut buf, registry, start) = self.allocate(total_len)?;
let mut result = Vec::with_capacity(compilation.len());
let mut start = start as u32;
for func in compilation.into_iter() {
let (next_start, next_buf, next_table, vmfunc) =
Self::copy_function(func, start, buf, table);
let (next_start, next_buf, vmfunc) = Self::copy_function(func, start, buf, registry);
result.push(vmfunc);
start = next_start;
buf = next_buf;
table = next_table;
}
Ok(result.into_boxed_slice())
}
/// Make all allocated memory executable.
pub fn publish(&mut self) {
pub fn publish(&mut self, isa: &dyn TargetIsa) {
self.push_current(0)
.expect("failed to push current memory map");
for CodeMemoryEntry { mmap: m, table: t } in &mut self.entries[self.published..] {
for CodeMemoryEntry {
mmap: m,
registry: r,
} in &mut self.entries[self.published..]
{
// Remove write access to the pages due to the relocation fixups.
t.publish(m.as_ptr() as u64)
.expect("failed to publish function table");
r.publish(isa)
.expect("failed to publish function unwind registry");
if !m.is_empty() {
unsafe {
@@ -139,73 +135,79 @@ impl CodeMemory {
/// * The offset within the current mmap that the slice starts at
///
/// TODO: Add an alignment flag.
fn allocate(&mut self, size: usize) -> Result<(&mut [u8], &mut FunctionTable, usize), String> {
if self.current.mmap.len() - self.position < size {
fn allocate(&mut self, size: usize) -> Result<(&mut [u8], &mut UnwindRegistry, usize), String> {
assert!(size > 0);
if match &self.current {
Some(e) => e.mmap.len() - self.position < size,
None => true,
} {
self.push_current(cmp::max(0x10000, size))?;
}
let old_position = self.position;
self.position += size;
let e = self.current.as_mut().unwrap();
Ok((
&mut self.current.mmap.as_mut_slice()[old_position..self.position],
&mut self.current.table,
&mut e.mmap.as_mut_slice()[old_position..self.position],
&mut e.registry,
old_position,
))
}
/// Calculates the allocation size of the given compiled function.
fn function_allocation_size(func: &CompiledFunction) -> usize {
if func.unwind_info.is_empty() {
func.body.len()
} else {
// Account for necessary unwind information alignment padding (32-bit)
((func.body.len() + 3) & !3) + func.unwind_info.len()
match &func.unwind_info {
Some(UnwindInfo::WindowsX64(info)) => {
// Windows unwind information is required to be emitted into code memory
// This is because it must be a positive relative offset from the start of the memory
// Account for necessary unwind information alignment padding (32-bit alignment)
((func.body.len() + 3) & !3) + info.emit_size()
}
_ => func.body.len(),
}
}
/// Copies the data of the compiled function to the given buffer.
///
/// This will also add the function to the current function table.
/// This will also add the function to the current unwind registry.
fn copy_function<'a>(
func: &CompiledFunction,
func_start: u32,
buf: &'a mut [u8],
table: &'a mut FunctionTable,
) -> (
u32,
&'a mut [u8],
&'a mut FunctionTable,
&'a mut [VMFunctionBody],
) {
let func_end = func_start + (func.body.len() as u32);
registry: &mut UnwindRegistry,
) -> (u32, &'a mut [u8], &'a mut [VMFunctionBody]) {
let func_len = func.body.len();
let mut func_end = func_start + (func_len as u32);
let (body, remainder) = buf.split_at_mut(func.body.len());
let (body, mut remainder) = buf.split_at_mut(func_len);
body.copy_from_slice(&func.body);
let vmfunc = Self::view_as_mut_vmfunc_slice(body);
if func.unwind_info.is_empty() {
return (func_end, remainder, table, vmfunc);
if let Some(UnwindInfo::WindowsX64(info)) = &func.unwind_info {
// Windows unwind information is written following the function body
// Keep unwind information 32-bit aligned (round up to the nearest 4 byte boundary)
let unwind_start = (func_end + 3) & !3;
let unwind_size = info.emit_size();
let padding = (unwind_start - func_end) as usize;
let (slice, r) = remainder.split_at_mut(padding + unwind_size);
info.emit(&mut slice[padding..]);
func_end = unwind_start + (unwind_size as u32);
remainder = r;
}
// Keep unwind information 32-bit aligned (round up to the nearest 4 byte boundary)
let padding = ((func.body.len() + 3) & !3) - func.body.len();
let (unwind, remainder) = remainder.split_at_mut(padding + func.unwind_info.len());
let mut relocs = Vec::new();
func.unwind_info
.serialize(&mut unwind[padding..], &mut relocs);
if let Some(info) = &func.unwind_info {
registry
.register(func_start, func_len as u32, info)
.expect("failed to register unwind information");
}
let unwind_start = func_end + (padding as u32);
let unwind_end = unwind_start + (func.unwind_info.len() as u32);
relocs.iter_mut().for_each(move |r| {
r.offset += unwind_start;
r.addend += func_start;
});
table.add_function(func_start, func_end, unwind_start, &relocs);
(unwind_end, remainder, table, vmfunc)
(func_end, remainder, vmfunc)
}
/// Convert mut a slice from u8 to VMFunctionBody.
@@ -215,21 +217,19 @@ impl CodeMemory {
unsafe { &mut *body_ptr }
}
/// Pushes the current Mmap (and function table) and allocates a new Mmap of the given size.
/// Pushes the current entry and allocates a new one with the given size.
fn push_current(&mut self, new_size: usize) -> Result<(), String> {
let previous = mem::replace(
&mut self.current,
if new_size == 0 {
CodeMemoryEntry::new()
None
} else {
CodeMemoryEntry::with_capacity(cmp::max(0x10000, new_size))?
Some(CodeMemoryEntry::with_capacity(cmp::max(0x10000, new_size))?)
},
);
if !previous.mmap.is_empty() {
self.entries.push(previous);
} else {
assert_eq!(previous.table.len(), 0);
if let Some(e) = previous {
self.entries.push(e);
}
self.position = 0;