wasmer/lib/clif-backend/src/resolver.rs

502 lines
20 KiB
Rust
Raw Normal View History

use crate::{
cache::BackendCache,
libcalls,
relocation::{
ExternalRelocation, LibCall, LocalRelocation, LocalTrapSink, Reloc, RelocSink,
RelocationType, TrapSink, VmCall, VmCallKind,
},
signal::HandlerData,
trampoline::Trampolines,
};
2019-01-10 22:59:57 -05:00
use byteorder::{ByteOrder, LittleEndian};
use cranelift_codegen::{
binemit::{Stackmap, StackmapSink},
2020-02-19 16:37:43 -08:00
ir, isa, CodegenError, Context, ValueLabelsRanges,
};
use rayon::prelude::*;
use std::{
mem,
ptr::{write_unaligned, NonNull},
sync::Arc,
};
use wasmer_runtime_core::{
self,
backend::{
sys::{Memory, Protect},
SigRegistry,
},
cache::Error as CacheError,
codegen::WasmSpan,
error::{CompileError, CompileResult},
module::ModuleInfo,
structures::{Map, SliceMap, TypedIndex},
types::{FuncSig, LocalFuncIndex, SigIndex},
2019-01-08 21:57:28 -05:00
vm, vmcalls,
};
extern "C" {
#[cfg(not(target_os = "windows"))]
pub fn __rust_probestack();
#[cfg(all(target_os = "windows", target_pointer_width = "64"))]
pub fn __chkstk();
}
2019-02-20 16:41:41 -08:00
fn lookup_func(
map: &SliceMap<LocalFuncIndex, usize>,
memory: &Memory,
local_func_index: LocalFuncIndex,
) -> Option<NonNull<vm::Func>> {
2019-02-19 15:36:22 -08:00
let offset = *map.get(local_func_index)?;
let ptr = unsafe { memory.as_ptr().add(offset) };
NonNull::new(ptr).map(|nonnull| nonnull.cast())
}
#[allow(dead_code)]
pub struct FuncResolverBuilder {
2019-02-19 15:36:22 -08:00
map: Map<LocalFuncIndex, usize>,
memory: Memory,
local_relocs: Map<LocalFuncIndex, Box<[LocalRelocation]>>,
external_relocs: Map<LocalFuncIndex, Box<[ExternalRelocation]>>,
2019-01-23 00:31:58 -06:00
import_len: usize,
}
pub struct NoopStackmapSink {}
impl StackmapSink for NoopStackmapSink {
fn add_stackmap(&mut self, _: u32, _: Stackmap) {}
}
impl FuncResolverBuilder {
pub fn new_from_backend_cache(
backend_cache: BackendCache,
mut code: Memory,
info: &ModuleInfo,
2019-02-19 09:58:01 -08:00
) -> Result<(Self, Arc<Trampolines>, HandlerData), CacheError> {
unsafe {
code.protect(.., Protect::ReadWrite)
.map_err(|e| CacheError::Unknown(e.to_string()))?;
}
let handler_data =
HandlerData::new(backend_cache.trap_sink, code.as_ptr() as _, code.size());
Ok((
Self {
2019-02-19 15:36:22 -08:00
map: backend_cache.offsets,
memory: code,
local_relocs: Map::new(),
external_relocs: backend_cache.external_relocs,
import_len: info.imported_functions.len(),
},
2019-02-20 16:41:41 -08:00
Arc::new(Trampolines::from_trampoline_cache(
backend_cache.trampolines,
)),
handler_data,
))
}
2019-01-08 21:57:28 -05:00
pub fn new(
2019-08-08 16:46:52 -06:00
isa: &dyn isa::TargetIsa,
function_bodies: Map<LocalFuncIndex, (ir::Function, WasmSpan)>,
info: &ModuleInfo,
2020-02-11 09:52:21 -08:00
) -> CompileResult<(
Self,
Option<wasmer_runtime_core::codegen::DebugMetadata>,
HandlerData,
)> {
2019-02-25 12:03:48 -08:00
let num_func_bodies = function_bodies.len();
let mut local_relocs = Map::with_capacity(num_func_bodies);
let mut external_relocs = Map::with_capacity(num_func_bodies);
2019-01-18 16:45:30 -08:00
let mut trap_sink = TrapSink::new();
2020-02-12 17:09:30 -08:00
let generate_debug_info = info.generate_debug_info;
let fb = function_bodies.iter().collect::<Vec<(_, _)>>();
2020-02-11 09:52:21 -08:00
#[cfg(feature = "generate-debug-information")]
use wasm_debug::types::CompiledFunctionData;
#[cfg(not(feature = "generate-debug-information"))]
type CompiledFunctionData = ();
/// Data about the the compiled machine code.
type CompileMetadata = (
LocalFuncIndex,
Option<(CompiledFunctionData, ValueLabelsRanges, Vec<Option<i32>>)>,
RelocSink,
LocalTrapSink,
);
/// Compiled machine code and information about it
type CompileData = (Vec<u8>, CompileMetadata);
let compiled_functions: Result<Vec<CompileData>, CompileError> = fb
2020-02-11 09:52:21 -08:00
.par_iter()
.map_init(
|| Context::new(),
|ctx, (lfi, (func, _loc))| {
2020-02-11 09:52:21 -08:00
let mut code_buf = Vec::new();
ctx.func = func.to_owned();
let mut reloc_sink = RelocSink::new();
let mut local_trap_sink = LocalTrapSink::new();
let mut stackmap_sink = NoopStackmapSink {};
ctx.compile_and_emit(
isa,
&mut code_buf,
&mut reloc_sink,
&mut local_trap_sink,
&mut stackmap_sink,
)
2020-02-19 16:37:43 -08:00
.map_err(|e| match e {
CodegenError::Verifier(v) => CompileError::InternalError {
msg: format!("Verifier error: {}", v),
},
_ => CompileError::InternalError { msg: e.to_string() },
})?;
2020-02-11 09:52:21 -08:00
#[cfg(feature = "generate-debug-information")]
2020-02-12 17:09:30 -08:00
let debug_entry = if generate_debug_info {
let func = &ctx.func;
let encinfo = isa.encoding_info();
let mut blocks = func.layout.blocks().collect::<Vec<_>>();
blocks.sort_by_key(|block| func.offsets[*block]);
let instructions = blocks
2020-02-12 17:09:30 -08:00
.into_iter()
.flat_map(|block| {
func.inst_offsets(block, &encinfo)
2020-02-12 17:09:30 -08:00
.map(|(offset, inst, length)| {
let srcloc = func.srclocs[inst];
let val = srcloc.bits();
wasm_debug::types::CompiledInstructionData {
2020-02-19 15:46:43 -08:00
srcloc: wasm_debug::types::SourceLoc::new(val),
code_offset: offset as usize,
code_len: length as usize,
2020-02-12 17:09:30 -08:00
}
})
})
.collect::<Vec<_>>();
let stack_slots = ctx
.func
.stack_slots
.iter()
.map(|(_, ssd)| ssd.offset)
.collect::<Vec<Option<i32>>>();
let labels_ranges = ctx.build_value_labels_ranges(isa).unwrap_or_default();
let entry = CompiledFunctionData {
instructions,
start_srcloc: wasm_debug::types::SourceLoc::new(_loc.start()),
end_srcloc: wasm_debug::types::SourceLoc::new(_loc.end()),
2020-02-12 17:09:30 -08:00
// this not being 0 breaks inst-level debugging
2020-02-19 15:46:43 -08:00
body_offset: 0,
body_len: code_buf.len(),
2020-02-12 17:09:30 -08:00
};
Some((entry, labels_ranges, stack_slots))
} else {
None
2020-02-11 09:52:21 -08:00
};
#[cfg(not(feature = "generate-debug-information"))]
let debug_entry = None;
2020-02-11 09:52:21 -08:00
ctx.clear();
2020-02-12 17:09:30 -08:00
Ok((code_buf, (*lfi, debug_entry, reloc_sink, local_trap_sink)))
2020-02-11 09:52:21 -08:00
},
)
.collect();
2020-02-12 17:09:30 -08:00
let mut debug_metadata = if generate_debug_info {
Some(wasmer_runtime_core::codegen::DebugMetadata {
func_info: Map::new(),
inst_info: Map::new(),
2020-02-12 17:09:30 -08:00
pointers: vec![],
stack_slot_offsets: Map::new(),
2020-02-12 17:09:30 -08:00
})
} else {
None
2020-02-11 09:52:21 -08:00
};
let mut compiled_functions = compiled_functions?;
compiled_functions.sort_by(|a, b| (a.1).0.cmp(&(b.1).0));
let compiled_functions = compiled_functions;
let mut total_size = 0;
2019-02-25 12:03:48 -08:00
// We separate into two iterators, one iterable and one into iterable
2020-02-20 13:06:50 -08:00
let (code_bufs, sinks): (Vec<Vec<u8>>, Vec<CompileMetadata>) =
compiled_functions.into_iter().unzip();
for (code_buf, (_, _debug_info, reloc_sink, mut local_trap_sink)) in
2020-02-12 17:09:30 -08:00
code_bufs.iter().zip(sinks.into_iter())
2019-02-25 12:03:48 -08:00
{
2020-02-11 09:52:21 -08:00
let rounded_size = round_up(code_buf.len(), mem::size_of::<usize>());
#[cfg(feature = "generate-debug-information")]
{
if let Some(ref mut dbg_metadata) = debug_metadata {
let (entry, vlr, stackslots) = _debug_info.unwrap();
dbg_metadata.func_info.push(entry);
let new_vlr = vlr
.into_iter()
.map(|(k, v)| {
(
wasm_debug::types::ValueLabel::from_u32(k.as_u32()),
v.into_iter()
.map(|item| wasm_debug::types::ValueLocRange {
start: item.start,
end: item.end,
loc: match item.loc {
cranelift_codegen::ir::ValueLoc::Unassigned => {
wasm_debug::types::ValueLoc::Unassigned
}
cranelift_codegen::ir::ValueLoc::Reg(ru) => {
wasm_debug::types::ValueLoc::Reg(ru)
}
cranelift_codegen::ir::ValueLoc::Stack(st) => {
wasm_debug::types::ValueLoc::Stack(
wasm_debug::types::StackSlot::from_u32(
st.as_u32(),
),
)
}
},
})
.collect::<Vec<wasm_debug::types::ValueLocRange>>(),
)
})
.collect::<wasm_debug::types::ValueLabelsRangesInner>();
dbg_metadata.inst_info.push(new_vlr);
dbg_metadata.stack_slot_offsets.push(stackslots);
}
2020-02-12 17:09:30 -08:00
}
2020-02-11 09:52:21 -08:00
2019-01-18 16:45:30 -08:00
// Clear the local trap sink and consolidate all trap info
// into a single location.
trap_sink.drain_local(total_size, &mut local_trap_sink);
// Round up each function's size to pointer alignment.
2020-02-11 09:52:21 -08:00
total_size += rounded_size;
local_relocs.push(reloc_sink.local_relocs.into_boxed_slice());
external_relocs.push(reloc_sink.external_relocs.into_boxed_slice());
}
let mut memory = Memory::with_size(total_size)
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
unsafe {
memory
.protect(.., Protect::ReadWrite)
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
}
// Normally, excess memory due to alignment and page-rounding would
// be filled with null-bytes. On x86 (and x86_64),
// "\x00\x00" disassembles to "add byte ptr [eax],al".
//
// If the instruction pointer falls out of its designated area,
// it would be better if it would immediately crash instead of
// continuing on and causing non-local issues.
//
// "\xCC" disassembles to "int3", which will immediately cause
// an interrupt that we can catch if we want.
for i in unsafe { memory.as_slice_mut() } {
*i = 0xCC;
}
2019-02-25 12:03:48 -08:00
let mut map = Map::with_capacity(num_func_bodies);
let mut previous_end = 0;
2019-02-25 12:03:48 -08:00
for compiled in code_bufs.iter() {
2020-02-11 09:52:21 -08:00
let length = round_up(compiled.len(), mem::size_of::<usize>());
2020-02-12 17:09:30 -08:00
if let Some(ref mut dbg_metadata) = debug_metadata {
dbg_metadata.pointers.push((
(memory.as_ptr() as usize + previous_end) as *const u8,
length,
));
}
2020-02-11 09:52:21 -08:00
let new_end = previous_end + length;
unsafe {
2019-01-08 21:57:28 -05:00
memory.as_slice_mut()[previous_end..previous_end + compiled.len()]
.copy_from_slice(&compiled[..]);
}
map.push(previous_end);
previous_end = new_end;
}
2019-02-20 16:41:41 -08:00
let handler_data =
HandlerData::new(Arc::new(trap_sink), memory.as_ptr() as _, memory.size());
2019-01-18 16:45:30 -08:00
let mut func_resolver_builder = Self {
2019-02-19 15:36:22 -08:00
map,
memory,
local_relocs,
external_relocs,
import_len: info.imported_functions.len(),
};
func_resolver_builder.relocate_locals();
2020-02-12 17:09:30 -08:00
Ok((func_resolver_builder, debug_metadata, handler_data))
}
fn relocate_locals(&mut self) {
for (index, relocs) in self.local_relocs.iter() {
for ref reloc in relocs.iter() {
let local_func_index = LocalFuncIndex::new(reloc.target.index() - self.import_len);
2019-02-20 16:41:41 -08:00
let target_func_address = lookup_func(&self.map, &self.memory, local_func_index)
.unwrap()
.as_ptr() as usize;
// We need the address of the current function
// because these calls are relative.
2019-02-20 16:41:41 -08:00
let func_addr = lookup_func(&self.map, &self.memory, index)
.unwrap()
.as_ptr() as usize;
unsafe {
let reloc_address = func_addr + reloc.offset as usize;
let reloc_delta = target_func_address
.wrapping_sub(reloc_address)
.wrapping_add(reloc.addend as usize);
write_unaligned(reloc_address as *mut u32, reloc_delta as u32);
}
}
}
}
pub fn finalize(
mut self,
2019-02-22 16:20:26 -08:00
signatures: &SliceMap<SigIndex, FuncSig>,
2019-02-19 09:58:01 -08:00
trampolines: Arc<Trampolines>,
handler_data: HandlerData,
) -> CompileResult<(FuncResolver, BackendCache)> {
for (index, relocs) in self.external_relocs.iter() {
for ref reloc in relocs.iter() {
let target_func_address: isize = match reloc.target {
RelocationType::LibCall(libcall) => match libcall {
LibCall::CeilF32 => libcalls::ceilf32 as isize,
LibCall::FloorF32 => libcalls::floorf32 as isize,
LibCall::TruncF32 => libcalls::truncf32 as isize,
LibCall::NearestF32 => libcalls::nearbyintf32 as isize,
LibCall::CeilF64 => libcalls::ceilf64 as isize,
LibCall::FloorF64 => libcalls::floorf64 as isize,
LibCall::TruncF64 => libcalls::truncf64 as isize,
LibCall::NearestF64 => libcalls::nearbyintf64 as isize,
#[cfg(all(target_pointer_width = "64", target_os = "windows"))]
LibCall::Probestack => __chkstk as isize,
#[cfg(not(target_os = "windows"))]
LibCall::Probestack => __rust_probestack as isize,
},
RelocationType::Intrinsic(ref name) => Err(CompileError::InternalError {
msg: format!("unexpected intrinsic: {}", name),
})?,
2019-01-17 13:09:05 -08:00
RelocationType::VmCall(vmcall) => match vmcall {
VmCall::Local(kind) => match kind {
VmCallKind::StaticMemoryGrow | VmCallKind::SharedStaticMemoryGrow => {
vmcalls::local_static_memory_grow as _
2019-09-09 13:50:30 +02:00
}
VmCallKind::StaticMemorySize | VmCallKind::SharedStaticMemorySize => {
vmcalls::local_static_memory_size as _
2019-09-09 13:50:30 +02:00
}
VmCallKind::DynamicMemoryGrow => {
vmcalls::local_dynamic_memory_grow as _
}
VmCallKind::DynamicMemorySize => {
vmcalls::local_dynamic_memory_size as _
}
},
VmCall::Import(kind) => match kind {
VmCallKind::StaticMemoryGrow | VmCallKind::SharedStaticMemoryGrow => {
vmcalls::imported_static_memory_grow as _
}
VmCallKind::StaticMemorySize | VmCallKind::SharedStaticMemorySize => {
vmcalls::imported_static_memory_size as _
}
VmCallKind::DynamicMemoryGrow => {
vmcalls::imported_dynamic_memory_grow as _
}
VmCallKind::DynamicMemorySize => {
vmcalls::imported_dynamic_memory_size as _
}
},
2019-01-17 13:09:05 -08:00
},
RelocationType::Signature(sig_index) => {
let signature = SigRegistry.lookup_signature_ref(&signatures[sig_index]);
let sig_index = SigRegistry.lookup_sig_index(signature);
sig_index.index() as _
}
};
// We need the address of the current function
// because some of these calls are relative.
2019-02-20 16:41:41 -08:00
let func_addr = lookup_func(&self.map, &self.memory, index)
.unwrap()
.as_ptr() as usize;
// Determine relocation type and apply relocation.
match reloc.reloc {
2019-01-09 20:32:02 -05:00
Reloc::Abs8 => {
let ptr_to_write = (target_func_address as u64)
.checked_add(reloc.addend as u64)
.unwrap();
2019-02-19 15:36:22 -08:00
let empty_space_offset = self.map[index] + reloc.offset as usize;
2019-01-09 20:32:02 -05:00
let ptr_slice = unsafe {
2019-02-19 15:36:22 -08:00
&mut self.memory.as_slice_mut()
2019-01-10 22:59:57 -05:00
[empty_space_offset..empty_space_offset + 8]
2019-01-09 20:32:02 -05:00
};
2019-01-10 22:59:57 -05:00
LittleEndian::write_u64(ptr_slice, ptr_to_write);
}
Reloc::X86PCRel4 | Reloc::X86CallPCRel4 => unsafe {
let reloc_address = (func_addr as usize) + reloc.offset as usize;
let reloc_delta = target_func_address
.wrapping_sub(reloc_address as isize)
.wrapping_add(reloc.addend as isize);
write_unaligned(reloc_address as *mut u32, reloc_delta as u32);
},
}
}
}
unsafe {
2019-02-19 15:36:22 -08:00
self.memory
.protect(.., Protect::ReadExec)
.map_err(|e| CompileError::InternalError { msg: e.to_string() })?;
}
2019-02-19 09:58:01 -08:00
let backend_cache = BackendCache {
external_relocs: self.external_relocs.clone(),
2019-02-19 15:36:22 -08:00
offsets: self.map.clone(),
2019-02-19 09:58:01 -08:00
trap_sink: handler_data.trap_data,
trampolines: trampolines.to_trampoline_cache(),
};
2019-02-20 16:41:41 -08:00
Ok((
FuncResolver {
map: self.map,
memory: Arc::new(self.memory),
},
backend_cache,
))
}
}
2019-02-19 09:58:01 -08:00
unsafe impl Sync for FuncResolver {}
unsafe impl Send for FuncResolver {}
/// Resolves a function index to a function address.
pub struct FuncResolver {
2019-01-16 10:26:10 -08:00
map: Map<LocalFuncIndex, usize>,
2019-02-19 15:36:22 -08:00
pub(crate) memory: Arc<Memory>,
}
impl FuncResolver {
pub fn lookup(&self, index: LocalFuncIndex) -> Option<NonNull<vm::Func>> {
2019-02-19 15:36:22 -08:00
lookup_func(&self.map, &self.memory, index)
}
}
#[inline]
fn round_up(n: usize, multiple: usize) -> usize {
(n + multiple - 1) & !(multiple - 1)
2019-01-10 22:59:57 -05:00
}