Refactor CompiledModule to separate compile and linking stages (#1831)
* Refactor how relocs are stored and handled * refactor CompiledModule::instantiate and link_module * Refactor DWARF creation: split generation and serialization * Separate DWARF data transform from instantiation * rm LinkContext
This commit is contained in:
@@ -8,15 +8,14 @@ use cranelift_codegen::print_errors::pretty_error;
|
||||
use cranelift_codegen::Context;
|
||||
use cranelift_codegen::{binemit, ir};
|
||||
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
|
||||
use std::collections::HashMap;
|
||||
use wasmtime_debug::{emit_debugsections_image, DebugInfoData};
|
||||
use wasmtime_debug::{emit_dwarf, DebugInfoData, DwarfSection};
|
||||
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
|
||||
use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa};
|
||||
use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex, MemoryIndex, SignatureIndex};
|
||||
use wasmtime_environ::{
|
||||
CacheConfig, CompileError, CompiledFunction, Compiler as _C, ModuleAddressMap,
|
||||
CacheConfig, CompileError, CompiledFunction, Compiler as _C, Module, ModuleAddressMap,
|
||||
ModuleMemoryOffset, ModuleTranslation, ModuleVmctxInfo, Relocation, RelocationTarget,
|
||||
Relocations, Traps, Tunables, VMOffsets,
|
||||
Relocations, Traps, Tunables, VMOffsets, ValueLabelsRanges,
|
||||
};
|
||||
use wasmtime_runtime::{InstantiationError, VMFunctionBody, VMTrampoline};
|
||||
|
||||
@@ -71,15 +70,73 @@ fn _assert_compiler_send_sync() {
|
||||
_assert::<Compiler>();
|
||||
}
|
||||
|
||||
fn transform_dwarf_data(
|
||||
isa: &dyn TargetIsa,
|
||||
module: &Module,
|
||||
debug_data: &DebugInfoData,
|
||||
address_transform: &ModuleAddressMap,
|
||||
value_ranges: &ValueLabelsRanges,
|
||||
stack_slots: PrimaryMap<DefinedFuncIndex, ir::StackSlots>,
|
||||
compilation: &wasmtime_environ::Compilation,
|
||||
) -> Result<Vec<DwarfSection>, SetupError> {
|
||||
let target_config = isa.frontend_config();
|
||||
let ofs = VMOffsets::new(target_config.pointer_bytes(), &module.local);
|
||||
|
||||
let module_vmctx_info = {
|
||||
ModuleVmctxInfo {
|
||||
memory_offset: if ofs.num_imported_memories > 0 {
|
||||
ModuleMemoryOffset::Imported(ofs.vmctx_vmmemory_import(MemoryIndex::new(0)))
|
||||
} else if ofs.num_defined_memories > 0 {
|
||||
ModuleMemoryOffset::Defined(
|
||||
ofs.vmctx_vmmemory_definition_base(DefinedMemoryIndex::new(0)),
|
||||
)
|
||||
} else {
|
||||
ModuleMemoryOffset::None
|
||||
},
|
||||
stack_slots,
|
||||
}
|
||||
};
|
||||
emit_dwarf(
|
||||
isa,
|
||||
debug_data,
|
||||
&address_transform,
|
||||
&module_vmctx_info,
|
||||
&value_ranges,
|
||||
&compilation,
|
||||
)
|
||||
.map_err(SetupError::DebugInfo)
|
||||
}
|
||||
|
||||
fn get_code_range(
|
||||
compilation: &wasmtime_environ::Compilation,
|
||||
finished_functions: &PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
) -> (*const u8, usize) {
|
||||
if finished_functions.is_empty() {
|
||||
return (::std::ptr::null(), 0);
|
||||
}
|
||||
// Assuming all functions in the same code block, looking min/max of its range.
|
||||
let (start, end) = finished_functions.iter().fold::<(usize, usize), _>(
|
||||
(!0, 0),
|
||||
|(start, end), (i, body_ptr)| {
|
||||
let body_ptr = (*body_ptr) as *const u8 as usize;
|
||||
let body_len = compilation.get(i).body.len();
|
||||
(
|
||||
::std::cmp::min(start, body_ptr),
|
||||
::std::cmp::max(end, body_ptr + body_len),
|
||||
)
|
||||
},
|
||||
);
|
||||
(start as *const u8, end - start)
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub struct Compilation {
|
||||
pub code_memory: CodeMemory,
|
||||
pub finished_functions: PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>,
|
||||
pub relocations: Relocations,
|
||||
pub code_range: (*const u8, usize),
|
||||
pub trampolines: PrimaryMap<SignatureIndex, VMTrampoline>,
|
||||
pub trampoline_relocations: HashMap<SignatureIndex, Vec<Relocation>>,
|
||||
pub jt_offsets: PrimaryMap<DefinedFuncIndex, ir::JumpTableOffsets>,
|
||||
pub dbg_image: Option<Vec<u8>>,
|
||||
pub dwarf_sections: Vec<DwarfSection>,
|
||||
pub traps: Traps,
|
||||
pub address_transform: ModuleAddressMap,
|
||||
}
|
||||
@@ -130,15 +187,29 @@ impl Compiler {
|
||||
}
|
||||
.map_err(SetupError::Compile)?;
|
||||
|
||||
let dwarf_sections = if debug_data.is_some() && !compilation.is_empty() {
|
||||
transform_dwarf_data(
|
||||
&*self.isa,
|
||||
&translation.module,
|
||||
debug_data.as_ref().unwrap(),
|
||||
&address_transform,
|
||||
&value_ranges,
|
||||
stack_slots,
|
||||
&compilation,
|
||||
)?
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
|
||||
// Allocate all of the compiled functions into executable memory,
|
||||
// copying over their contents.
|
||||
let finished_functions =
|
||||
allocate_functions(&mut code_memory, &compilation).map_err(|message| {
|
||||
SetupError::Instantiate(InstantiationError::Resource(format!(
|
||||
"failed to allocate memory for functions: {}",
|
||||
message
|
||||
)))
|
||||
})?;
|
||||
let finished_functions = allocate_functions(&mut code_memory, &compilation, &relocations)
|
||||
.map_err(|message| {
|
||||
SetupError::Instantiate(InstantiationError::Resource(format!(
|
||||
"failed to allocate memory for functions: {}",
|
||||
message
|
||||
)))
|
||||
})?;
|
||||
|
||||
// Eagerly generate a entry trampoline for every type signature in the
|
||||
// module. This should be "relatively lightweight" for most modules and
|
||||
@@ -146,9 +217,8 @@ impl Compiler {
|
||||
// tables) have a trampoline when invoked through the wasmtime API.
|
||||
let mut cx = FunctionBuilderContext::new();
|
||||
let mut trampolines = PrimaryMap::new();
|
||||
let mut trampoline_relocations = HashMap::new();
|
||||
for (index, (_, native_sig)) in translation.module.local.signatures.iter() {
|
||||
let (trampoline, relocations) = make_trampoline(
|
||||
for (_, (_, native_sig)) in translation.module.local.signatures.iter() {
|
||||
let trampoline = make_trampoline(
|
||||
&*self.isa,
|
||||
&mut code_memory,
|
||||
&mut cx,
|
||||
@@ -156,66 +226,18 @@ impl Compiler {
|
||||
std::mem::size_of::<u128>(),
|
||||
)?;
|
||||
trampolines.push(trampoline);
|
||||
|
||||
// Typically trampolines do not have relocations, so if one does
|
||||
// show up be sure to log it in case anyone's listening and there's
|
||||
// an accidental bug.
|
||||
if relocations.len() > 0 {
|
||||
log::info!("relocations found in trampoline for {:?}", native_sig);
|
||||
trampoline_relocations.insert(index, relocations);
|
||||
}
|
||||
}
|
||||
|
||||
// Translate debug info (DWARF) only if at least one function is present.
|
||||
let dbg_image = if debug_data.is_some() && !finished_functions.is_empty() {
|
||||
let target_config = self.isa.frontend_config();
|
||||
let ofs = VMOffsets::new(target_config.pointer_bytes(), &translation.module.local);
|
||||
|
||||
let mut funcs = Vec::new();
|
||||
for (i, allocated) in finished_functions.into_iter() {
|
||||
let ptr = (*allocated) as *const u8;
|
||||
let body_len = compilation.get(i).body.len();
|
||||
funcs.push((ptr, body_len));
|
||||
}
|
||||
let module_vmctx_info = {
|
||||
ModuleVmctxInfo {
|
||||
memory_offset: if ofs.num_imported_memories > 0 {
|
||||
ModuleMemoryOffset::Imported(ofs.vmctx_vmmemory_import(MemoryIndex::new(0)))
|
||||
} else if ofs.num_defined_memories > 0 {
|
||||
ModuleMemoryOffset::Defined(
|
||||
ofs.vmctx_vmmemory_definition_base(DefinedMemoryIndex::new(0)),
|
||||
)
|
||||
} else {
|
||||
ModuleMemoryOffset::None
|
||||
},
|
||||
stack_slots,
|
||||
}
|
||||
};
|
||||
let bytes = emit_debugsections_image(
|
||||
&*self.isa,
|
||||
debug_data.as_ref().unwrap(),
|
||||
&module_vmctx_info,
|
||||
&address_transform,
|
||||
&value_ranges,
|
||||
&funcs,
|
||||
&compilation,
|
||||
)
|
||||
.map_err(SetupError::DebugInfo)?;
|
||||
Some(bytes)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let jt_offsets = compilation.get_jt_offsets();
|
||||
let code_range = get_code_range(&compilation, &finished_functions);
|
||||
|
||||
Ok(Compilation {
|
||||
code_memory,
|
||||
finished_functions,
|
||||
relocations,
|
||||
code_range,
|
||||
trampolines,
|
||||
trampoline_relocations,
|
||||
jt_offsets,
|
||||
dbg_image,
|
||||
dwarf_sections,
|
||||
traps,
|
||||
address_transform,
|
||||
})
|
||||
@@ -229,7 +251,7 @@ pub fn make_trampoline(
|
||||
fn_builder_ctx: &mut FunctionBuilderContext,
|
||||
signature: &ir::Signature,
|
||||
value_size: usize,
|
||||
) -> Result<(VMTrampoline, Vec<Relocation>), SetupError> {
|
||||
) -> Result<VMTrampoline, SetupError> {
|
||||
let pointer_type = isa.pointer_type();
|
||||
let mut wrapper_sig = ir::Signature::new(isa.frontend_config().default_call_conv);
|
||||
|
||||
@@ -337,28 +359,29 @@ pub fn make_trampoline(
|
||||
})?;
|
||||
|
||||
let ptr = code_memory
|
||||
.allocate_for_function(&CompiledFunction {
|
||||
body: code_buf,
|
||||
jt_offsets: context.func.jt_offsets,
|
||||
unwind_info,
|
||||
})
|
||||
.allocate_for_function(
|
||||
&CompiledFunction {
|
||||
body: code_buf,
|
||||
jt_offsets: context.func.jt_offsets,
|
||||
unwind_info,
|
||||
},
|
||||
reloc_sink.relocs.iter(),
|
||||
)
|
||||
.map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))?
|
||||
.as_ptr();
|
||||
Ok((
|
||||
unsafe { std::mem::transmute::<*const VMFunctionBody, VMTrampoline>(ptr) },
|
||||
reloc_sink.relocs,
|
||||
))
|
||||
Ok(unsafe { std::mem::transmute::<*const VMFunctionBody, VMTrampoline>(ptr) })
|
||||
}
|
||||
|
||||
fn allocate_functions(
|
||||
code_memory: &mut CodeMemory,
|
||||
compilation: &wasmtime_environ::Compilation,
|
||||
relocations: &Relocations,
|
||||
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
|
||||
if compilation.is_empty() {
|
||||
return Ok(PrimaryMap::new());
|
||||
}
|
||||
|
||||
let fat_ptrs = code_memory.allocate_for_compilation(compilation)?;
|
||||
let fat_ptrs = code_memory.allocate_for_compilation(compilation, relocations)?;
|
||||
|
||||
// Second, create a PrimaryMap from result vector of pointers.
|
||||
let mut result = PrimaryMap::with_capacity(compilation.len());
|
||||
@@ -374,10 +397,17 @@ fn allocate_functions(
|
||||
/// this `RelocSink` just asserts that it doesn't recieve most of them, but
|
||||
/// handles libcall ones.
|
||||
#[derive(Default)]
|
||||
struct RelocSink {
|
||||
pub struct RelocSink {
|
||||
relocs: Vec<Relocation>,
|
||||
}
|
||||
|
||||
impl RelocSink {
|
||||
/// Returns collected relocations.
|
||||
pub fn relocs(&self) -> &[Relocation] {
|
||||
&self.relocs
|
||||
}
|
||||
}
|
||||
|
||||
impl binemit::RelocSink for RelocSink {
|
||||
fn reloc_block(
|
||||
&mut self,
|
||||
|
||||
Reference in New Issue
Block a user