use crate::Engine;
use crate::hash_map::HashMap;
use crate::hash_set::HashSet;
use crate::prelude::*;
use std::{any::Any, borrow::Cow, collections::BTreeMap, mem, ops::Range};
use call_graph::CallGraph;
#[cfg(feature = "component-model")]
use wasmtime_environ::component::Translator;
use wasmtime_environ::{
Abi, CompiledFunctionBody, CompiledFunctionsTable, CompiledFunctionsTableBuilder,
CompiledModuleInfo, Compiler, DefinedFuncIndex, FilePos, FinishedObject, FuncKey,
FunctionBodyData, InliningCompiler, IntraModuleInlining, ModuleEnvironment, ModuleTranslation,
ModuleTypes, ModuleTypesBuilder, ObjectKind, PrimaryMap, StaticModuleIndex, Tunables,
};
mod call_graph;
mod scc;
mod stratify;
mod code_builder;
pub use self::code_builder::{CodeBuilder, CodeHint, HashedEngineCompileEnv};
#[cfg(feature = "runtime")]
mod runtime;
pub(crate) fn build_module_artifacts<T: FinishedObject>(
engine: &Engine,
wasm: &[u8],
dwarf_package: Option<&[u8]>,
obj_state: &T::State,
) -> Result<(
T,
Option<(CompiledModuleInfo, CompiledFunctionsTable, ModuleTypes)>,
)> {
let compiler = engine.try_compiler()?;
let tunables = engine.tunables();
let mut parser = wasmparser::Parser::new(0);
let mut validator = wasmparser::Validator::new_with_features(engine.features());
parser.set_features(*validator.features());
let mut types = ModuleTypesBuilder::new(&validator);
let mut translation = ModuleEnvironment::new(
tunables,
&mut validator,
&mut types,
StaticModuleIndex::from_u32(0),
)
.translate(parser, wasm)
.context("failed to parse WebAssembly module")?;
let functions = mem::take(&mut translation.function_body_inputs);
let compile_inputs = CompileInputs::for_module(&types, &translation, functions);
let unlinked_compile_outputs = compile_inputs.compile(engine)?;
let PreLinkOutput {
needs_gc_heap,
compiled_funcs,
indices,
} = unlinked_compile_outputs.pre_link();
translation.module.needs_gc_heap |= needs_gc_heap;
let mut object = compiler.object(ObjectKind::Module)?;
engine.append_compiler_info(&mut object)?;
engine.append_bti(&mut object);
let (mut object, compilation_artifacts) = indices.link_and_append_code(
object,
engine,
compiled_funcs,
std::iter::once(translation).collect(),
dwarf_package,
)?;
let (info, index) = compilation_artifacts.unwrap_as_module_info();
let types = types.finish();
object.serialize_info(&(&info, &index, &types));
let result = T::finish_object(object, obj_state)?;
Ok((result, Some((info, index, types))))
}
#[cfg(feature = "component-model")]
pub(crate) fn build_component_artifacts<T: FinishedObject>(
engine: &Engine,
binary: &[u8],
_dwarf_package: Option<&[u8]>,
unsafe_intrinsics_import: Option<&str>,
obj_state: &T::State,
) -> Result<(T, Option<wasmtime_environ::component::ComponentArtifacts>)> {
use wasmtime_environ::ScopeVec;
use wasmtime_environ::component::{
CompiledComponentInfo, ComponentArtifacts, ComponentTypesBuilder,
};
let compiler = engine.try_compiler()?;
let tunables = engine.tunables();
let scope = ScopeVec::new();
let mut validator = wasmparser::Validator::new_with_features(engine.features());
let mut types = ComponentTypesBuilder::new(&validator);
let mut translator = Translator::new(tunables, &mut validator, &mut types, &scope);
if let Some(name) = unsafe_intrinsics_import {
translator.expose_unsafe_intrinsics(name);
}
let (component, mut module_translations) = translator
.translate(binary)
.context("failed to parse WebAssembly module")?;
let compile_inputs = CompileInputs::for_component(
engine,
&types,
&component,
module_translations.iter_mut().map(|(i, translation)| {
let functions = mem::take(&mut translation.function_body_inputs);
(i, &*translation, functions)
}),
);
let unlinked_compile_outputs = compile_inputs.compile(&engine)?;
let PreLinkOutput {
needs_gc_heap,
compiled_funcs,
indices,
} = unlinked_compile_outputs.pre_link();
for (_, t) in &mut module_translations {
t.module.needs_gc_heap |= needs_gc_heap
}
let mut object = compiler.object(ObjectKind::Component)?;
engine.append_compiler_info(&mut object)?;
engine.append_bti(&mut object);
let (mut object, compilation_artifacts) = indices.link_and_append_code(
object,
engine,
compiled_funcs,
module_translations,
None, )?;
let (types, ty) = types.finish(&component.component);
let info = CompiledComponentInfo {
component: component.component,
};
let artifacts = ComponentArtifacts {
info,
table: compilation_artifacts.table,
ty,
types,
static_modules: compilation_artifacts.modules,
};
object.serialize_info(&artifacts);
let result = T::finish_object(object, obj_state)?;
Ok((result, Some(artifacts)))
}
type CompileInput<'a> = Box<dyn FnOnce(&dyn Compiler) -> Result<CompileOutput<'a>> + Send + 'a>;
struct CompileOutput<'a> {
key: FuncKey,
symbol: String,
function: CompiledFunctionBody,
start_srcloc: FilePos,
translation: Option<&'a ModuleTranslation<'a>>,
func_body: Option<wasmparser::FunctionBody<'a>>,
}
struct InlineHeuristicParams<'a> {
tunables: &'a Tunables,
caller_size: u32,
caller_key: FuncKey,
caller_needs_gc_heap: bool,
callee_size: u32,
callee_key: FuncKey,
callee_needs_gc_heap: bool,
}
#[derive(Default)]
struct CompileInputs<'a> {
inputs: Vec<CompileInput<'a>>,
}
impl<'a> CompileInputs<'a> {
fn push_input(
&mut self,
f: impl FnOnce(&dyn Compiler) -> Result<CompileOutput<'a>> + Send + 'a,
) {
self.inputs.push(Box::new(f));
}
fn for_module(
types: &'a ModuleTypesBuilder,
translation: &'a ModuleTranslation<'a>,
functions: PrimaryMap<DefinedFuncIndex, FunctionBodyData<'a>>,
) -> Self {
let mut ret = CompileInputs { inputs: vec![] };
let module_index = StaticModuleIndex::from_u32(0);
ret.collect_inputs_in_translations(types, [(module_index, translation, functions)]);
ret
}
#[cfg(feature = "component-model")]
fn for_component(
engine: &'a Engine,
types: &'a wasmtime_environ::component::ComponentTypesBuilder,
component: &'a wasmtime_environ::component::ComponentTranslation,
module_translations: impl IntoIterator<
Item = (
StaticModuleIndex,
&'a ModuleTranslation<'a>,
PrimaryMap<DefinedFuncIndex, FunctionBodyData<'a>>,
),
>,
) -> Self {
use wasmtime_environ::Abi;
use wasmtime_environ::component::UnsafeIntrinsic;
let mut ret = CompileInputs { inputs: vec![] };
ret.collect_inputs_in_translations(types.module_types_builder(), module_translations);
let tunables = engine.tunables();
for i in component
.component
.unsafe_intrinsics
.iter()
.enumerate()
.filter_map(|(i, ty)| if ty.is_some() { Some(i) } else { None })
{
let i = u32::try_from(i).unwrap();
let intrinsic = UnsafeIntrinsic::from_u32(i);
for abi in [Abi::Wasm, Abi::Array] {
ret.push_input(move |compiler| {
let symbol = format!(
"unsafe-intrinsics-{}-{}",
match abi {
Abi::Wasm => "wasm-call",
Abi::Array => "array-call",
Abi::Patchable => "patchable-call",
},
intrinsic.name(),
);
Ok(CompileOutput {
key: FuncKey::UnsafeIntrinsic(abi, intrinsic),
function: compiler
.component_compiler()
.compile_intrinsic(tunables, component, types, intrinsic, abi, &symbol)
.with_context(|| format!("failed to compile `{symbol}`"))?,
symbol,
start_srcloc: FilePos::default(),
translation: None,
func_body: None,
})
});
}
}
for (idx, trampoline) in component.trampolines.iter() {
for abi in [Abi::Wasm, Abi::Array] {
ret.push_input(move |compiler| {
let key = FuncKey::ComponentTrampoline(abi, idx);
let symbol = format!(
"component-trampolines[{}]-{}-{}",
idx.as_u32(),
match abi {
Abi::Wasm => "wasm-call",
Abi::Array => "array-call",
Abi::Patchable => "patchable-call",
},
trampoline.symbol_name(),
);
let function = compiler
.component_compiler()
.compile_trampoline(component, types, key, abi, tunables, &symbol)
.with_context(|| format!("failed to compile {symbol}"))?;
Ok(CompileOutput {
key,
function,
symbol,
start_srcloc: FilePos::default(),
translation: None,
func_body: None,
})
});
}
}
if component.component.num_resources > 0 {
if let Some(sig) = types.find_resource_drop_signature() {
ret.push_input(move |compiler| {
let key = FuncKey::ResourceDropTrampoline;
let symbol = "resource_drop_trampoline".to_string();
let function = compiler
.compile_wasm_to_array_trampoline(types[sig].unwrap_func(), key, &symbol)
.with_context(|| format!("failed to compile `{symbol}`"))?;
Ok(CompileOutput {
key,
function,
symbol,
start_srcloc: FilePos::default(),
translation: None,
func_body: None,
})
});
}
}
ret
}
fn clean_symbol(name: &str) -> Cow<'_, str> {
const MAX_SYMBOL_LEN: usize = 96;
let bad_char = |c: char| !c.is_ascii_graphic();
if name.chars().any(bad_char) {
let mut last_char_seen = '\u{0000}';
Cow::Owned(
name.chars()
.map(|c| if bad_char(c) { '?' } else { c })
.filter(|c| {
let skip = last_char_seen == '?' && *c == '?';
last_char_seen = *c;
!skip
})
.take(MAX_SYMBOL_LEN)
.collect::<String>(),
)
} else if name.len() <= MAX_SYMBOL_LEN {
Cow::Borrowed(&name[..])
} else {
Cow::Borrowed(&name[..MAX_SYMBOL_LEN])
}
}
fn collect_inputs_in_translations(
&mut self,
types: &'a ModuleTypesBuilder,
translations: impl IntoIterator<
Item = (
StaticModuleIndex,
&'a ModuleTranslation<'a>,
PrimaryMap<DefinedFuncIndex, FunctionBodyData<'a>>,
),
>,
) {
for (module, translation, functions) in translations {
for (def_func_index, func_body_data) in functions {
self.push_input(move |compiler| {
let key = FuncKey::DefinedWasmFunction(module, def_func_index);
let func_index = translation.module.func_index(def_func_index);
let symbol = match translation
.debuginfo
.name_section
.func_names
.get(&func_index)
{
Some(name) => format!(
"wasm[{}]::function[{}]::{}",
module.as_u32(),
func_index.as_u32(),
Self::clean_symbol(&name)
),
None => format!(
"wasm[{}]::function[{}]",
module.as_u32(),
func_index.as_u32()
),
};
let func_body = func_body_data.body.clone();
let data = func_body.get_binary_reader();
let offset = data.original_position();
let start_srcloc = FilePos::new(u32::try_from(offset).unwrap());
let function = compiler
.compile_function(translation, key, func_body_data, types, &symbol)
.with_context(|| format!("failed to compile: {symbol}"))?;
Ok(CompileOutput {
key,
symbol,
function,
start_srcloc,
translation: Some(translation),
func_body: Some(func_body),
})
});
let func_index = translation.module.func_index(def_func_index);
if translation.module.functions[func_index].is_escaping() {
self.push_input(move |compiler| {
let key = FuncKey::ArrayToWasmTrampoline(module, def_func_index);
let func_index = translation.module.func_index(def_func_index);
let symbol = format!(
"wasm[{}]::array_to_wasm_trampoline[{}]",
module.as_u32(),
func_index.as_u32()
);
let function = compiler
.compile_array_to_wasm_trampoline(translation, types, key, &symbol)
.with_context(|| format!("failed to compile: {symbol}"))?;
Ok(CompileOutput {
key,
symbol,
function,
start_srcloc: FilePos::default(),
translation: None,
func_body: None,
})
});
}
}
}
let mut trampoline_types_seen = HashSet::new();
for (_func_type_index, trampoline_type_index) in types.trampoline_types() {
let is_new = trampoline_types_seen.insert(trampoline_type_index);
if !is_new {
continue;
}
let trampoline_func_ty = types[trampoline_type_index].unwrap_func();
self.push_input(move |compiler| {
let key = FuncKey::WasmToArrayTrampoline(trampoline_type_index);
let symbol = format!(
"signatures[{}]::wasm_to_array_trampoline",
trampoline_type_index.as_u32()
);
let function = compiler
.compile_wasm_to_array_trampoline(trampoline_func_ty, key, &symbol)
.with_context(|| format!("failed to compile: {symbol}"))?;
Ok(CompileOutput {
key,
function,
symbol,
start_srcloc: FilePos::default(),
translation: None,
func_body: None,
})
});
}
}
fn compile(self, engine: &Engine) -> Result<UnlinkedCompileOutputs<'a>> {
let compiler = engine.try_compiler()?;
if self.inputs.len() > 0 && cfg!(miri) {
bail!(
"\
You are attempting to compile a WebAssembly module or component that contains
functions in Miri. Running Cranelift through Miri is known to take quite a long
time and isn't what we want in CI at least. If this is a mistake then you should
ignore this test in Miri with:
#[cfg_attr(miri, ignore)]
If this is not a mistake then try to edit the `pulley_provenance_test` test
which runs Cranelift outside of Miri. If you still feel this is a mistake then
please open an issue or a topic on Zulip to talk about how best to accommodate
the use case.
"
);
}
let mut raw_outputs = if let Some(inlining_compiler) = compiler.inlining_compiler() {
if engine.tunables().inlining {
self.compile_with_inlining(engine, compiler, inlining_compiler)?
} else {
engine.run_maybe_parallel::<_, _, Error, _>(self.inputs, |f| {
let mut compiled = f(compiler)?;
inlining_compiler.finish_compiling(
&mut compiled.function,
compiled.func_body.take(),
&compiled.symbol,
)?;
Ok(compiled)
})?
}
} else {
engine.run_maybe_parallel(self.inputs, |f| f(compiler))?
};
if cfg!(debug_assertions) {
let mut symbols: Vec<_> = raw_outputs.iter().map(|i| &i.symbol).collect();
symbols.sort();
for w in symbols.windows(2) {
assert_ne!(
w[0], w[1],
"should never have duplicate symbols, but found two functions with the symbol `{}`",
w[0]
);
}
}
compile_required_builtins(engine, &mut raw_outputs)?;
let mut outputs: BTreeMap<FuncKey, CompileOutput> = BTreeMap::new();
for output in raw_outputs {
outputs.insert(output.key, output);
}
Ok(UnlinkedCompileOutputs { outputs })
}
fn compile_with_inlining(
self,
engine: &Engine,
compiler: &dyn Compiler,
inlining_compiler: &dyn InliningCompiler,
) -> Result<Vec<CompileOutput<'a>>, Error> {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct OutputIndex(u32);
wasmtime_environ::entity_impl!(OutputIndex);
let mut outputs = PrimaryMap::<OutputIndex, Option<CompileOutput<'_>>>::from(
engine.run_maybe_parallel(self.inputs, |f| f(compiler).map(Some))?,
);
fn is_inlining_function(key: FuncKey) -> bool {
match key {
FuncKey::DefinedWasmFunction(..) => true,
#[cfg(feature = "component-model")]
FuncKey::UnsafeIntrinsic(..) => true,
FuncKey::ArrayToWasmTrampoline(..)
| FuncKey::WasmToArrayTrampoline(..)
| FuncKey::WasmToBuiltinTrampoline(..)
| FuncKey::PatchableToBuiltinTrampoline(..) => false,
#[cfg(feature = "component-model")]
FuncKey::ComponentTrampoline(..) | FuncKey::ResourceDropTrampoline => false,
FuncKey::PulleyHostCall(_) => {
unreachable!("we don't compile artifacts for Pulley host calls")
}
}
}
fn inlining_functions<'a>(
outputs: &'a PrimaryMap<OutputIndex, Option<CompileOutput<'_>>>,
) -> impl Iterator<Item = OutputIndex> + 'a {
outputs.iter().filter_map(|(index, output)| {
if is_inlining_function(output.as_ref().unwrap().key) {
Some(index)
} else {
None
}
})
}
let key_to_output: HashMap<FuncKey, OutputIndex> = inlining_functions(&outputs)
.map(|output_index| {
let output = outputs[output_index].as_ref().unwrap();
(output.key, output_index)
})
.collect();
let call_graph = CallGraph::<OutputIndex>::new(inlining_functions(&outputs), {
let mut func_keys = IndexSet::default();
let outputs = &outputs;
let key_to_output = &key_to_output;
move |output_index, calls| {
debug_assert!(calls.is_empty());
let output = outputs[output_index].as_ref().unwrap();
debug_assert!(is_inlining_function(output.key));
func_keys.clear();
inlining_compiler.calls(&output.function, &mut func_keys)?;
calls.extend(
func_keys
.iter()
.copied()
.filter_map(|key| key_to_output.get(&key)),
);
log::trace!(
"call graph edges for {output_index:?} = {:?}: {calls:?}",
output.key
);
Ok(())
}
})?;
let strata =
stratify::Strata::<OutputIndex>::new(inlining_functions(&outputs), &call_graph);
let mut layer_outputs = vec![];
for layer in strata.layers() {
debug_assert!(layer_outputs.is_empty());
layer_outputs.extend(layer.iter().map(|f| outputs[*f].take().unwrap()));
engine.run_maybe_parallel_mut(
&mut layer_outputs,
|output: &mut CompileOutput<'_>| {
log::trace!("processing inlining for {:?}", output.key);
debug_assert!(is_inlining_function(output.key));
let caller_key = output.key;
let caller_needs_gc_heap =
output.translation.is_some_and(|t| t.module.needs_gc_heap);
let caller = &mut output.function;
let mut caller_size = inlining_compiler.size(caller);
inlining_compiler.inline(caller, &mut |callee_key: FuncKey| {
log::trace!(" --> considering call to {callee_key:?}");
let callee_output_index: OutputIndex = key_to_output[&callee_key];
let callee_output = outputs[callee_output_index].as_ref()?;
debug_assert_eq!(callee_output.key, callee_key);
let callee = &callee_output.function;
let callee_size = inlining_compiler.size(callee);
let callee_needs_gc_heap = callee_output
.translation
.is_some_and(|t| t.module.needs_gc_heap);
if Self::should_inline(InlineHeuristicParams {
tunables: engine.tunables(),
caller_size,
caller_key,
caller_needs_gc_heap,
callee_size,
callee_key,
callee_needs_gc_heap,
}) {
caller_size = caller_size.saturating_add(callee_size);
Some(callee)
} else {
None
}
})
},
)?;
for (f, func) in layer.iter().zip(layer_outputs.drain(..)) {
debug_assert!(outputs[*f].is_none());
outputs[*f] = Some(func);
}
}
engine.run_maybe_parallel(outputs.into(), |output| {
let mut output = output.unwrap();
inlining_compiler.finish_compiling(
&mut output.function,
output.func_body.take(),
&output.symbol,
)?;
Ok(output)
})
}
fn should_inline(
InlineHeuristicParams {
tunables,
caller_size,
caller_key,
caller_needs_gc_heap,
callee_size,
callee_key,
callee_needs_gc_heap,
}: InlineHeuristicParams,
) -> bool {
log::trace!(
"considering inlining:\n\
\tcaller = {caller_key:?}\n\
\t\tsize = {caller_size}\n\
\t\tneeds_gc_heap = {caller_needs_gc_heap}\n\
\tcallee = {callee_key:?}\n\
\t\tsize = {callee_size}\n\
\t\tneeds_gc_heap = {callee_needs_gc_heap}"
);
debug_assert!(
tunables.inlining,
"shouldn't even call this method if we aren't configured for inlining"
);
debug_assert_ne!(caller_key, callee_key, "we never inline recursion");
let sum_size = caller_size.saturating_add(callee_size);
if sum_size > tunables.inlining_sum_size_threshold {
log::trace!(
" --> not inlining: the sum of the caller's and callee's sizes is greater than \
the inlining-sum-size threshold: {callee_size} + {caller_size} > {}",
tunables.inlining_sum_size_threshold
);
return false;
}
if caller_key.abi() == Abi::Array {
log::trace!(" --> not inlining: not inlining into array-abi caller");
return false;
}
match (caller_key, callee_key) {
(
FuncKey::DefinedWasmFunction(caller_module, _),
FuncKey::DefinedWasmFunction(callee_module, _),
) => {
if caller_module == callee_module {
match tunables.inlining_intra_module {
IntraModuleInlining::Yes => {}
IntraModuleInlining::WhenUsingGc
if caller_needs_gc_heap || callee_needs_gc_heap => {}
IntraModuleInlining::WhenUsingGc => {
log::trace!(
" --> not inlining: intra-module call that does not use GC"
);
return false;
}
IntraModuleInlining::No => {
log::trace!(" --> not inlining: intra-module call");
return false;
}
}
}
}
_ => {}
}
if callee_size <= tunables.inlining_small_callee_size {
log::trace!(
" --> inlining: callee's size is less than the small-callee size: \
{callee_size} <= {}",
tunables.inlining_small_callee_size
);
return true;
}
log::trace!(" --> inlining: did not find a reason we should not");
true
}
}
fn compile_required_builtins(engine: &Engine, raw_outputs: &mut Vec<CompileOutput>) -> Result<()> {
let compiler = engine.try_compiler()?;
let mut builtins = HashSet::new();
let mut new_inputs: Vec<CompileInput<'_>> = Vec::new();
let compile_builtin = |key: FuncKey| {
Box::new(move |compiler: &dyn Compiler| {
let symbol = match key {
FuncKey::WasmToBuiltinTrampoline(builtin) => {
format!("wasmtime_builtin_{}", builtin.name())
}
FuncKey::PatchableToBuiltinTrampoline(builtin) => {
format!("wasmtime_patchable_builtin_{}", builtin.name())
}
_ => unreachable!(),
};
let mut function = compiler
.compile_wasm_to_builtin(key, &symbol)
.with_context(|| format!("failed to compile `{symbol}`"))?;
if let Some(compiler) = compiler.inlining_compiler() {
compiler.finish_compiling(&mut function, None, &symbol)?;
}
Ok(CompileOutput {
key,
function,
symbol,
start_srcloc: FilePos::default(),
translation: None,
func_body: None,
})
})
};
for output in raw_outputs.iter() {
for reloc in compiler.compiled_function_relocation_targets(&*output.function.code) {
match reloc {
FuncKey::WasmToBuiltinTrampoline(builtin)
| FuncKey::PatchableToBuiltinTrampoline(builtin) => {
if builtins.insert(builtin) {
new_inputs.push(compile_builtin(reloc));
}
}
_ => {}
}
}
}
raw_outputs.extend(engine.run_maybe_parallel(new_inputs, |c| c(compiler))?);
Ok(())
}
#[derive(Default)]
struct UnlinkedCompileOutputs<'a> {
outputs: BTreeMap<FuncKey, CompileOutput<'a>>,
}
impl UnlinkedCompileOutputs<'_> {
fn pre_link(self) -> PreLinkOutput {
let mut compiled_funcs = vec![];
let mut indices = FunctionIndices::default();
let mut needs_gc_heap = false;
for output in self.outputs.into_values() {
needs_gc_heap |= output.function.needs_gc_heap;
let index = compiled_funcs.len();
compiled_funcs.push((output.symbol, output.key, output.function.code));
if output.start_srcloc != FilePos::none() {
indices
.start_srclocs
.insert(output.key, output.start_srcloc);
}
indices.indices.insert(output.key, index);
}
PreLinkOutput {
needs_gc_heap,
compiled_funcs,
indices,
}
}
}
struct PreLinkOutput {
needs_gc_heap: bool,
compiled_funcs: Vec<(String, FuncKey, Box<dyn Any + Send + Sync>)>,
indices: FunctionIndices,
}
#[derive(Default)]
struct FunctionIndices {
start_srclocs: HashMap<FuncKey, FilePos>,
indices: BTreeMap<FuncKey, usize>,
}
impl FunctionIndices {
fn link_and_append_code<'a>(
self,
mut obj: object::write::Object<'static>,
engine: &'a Engine,
compiled_funcs: Vec<(String, FuncKey, Box<dyn Any + Send + Sync>)>,
translations: PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
dwarf_package_bytes: Option<&[u8]>,
) -> Result<(wasmtime_environ::ObjectBuilder<'a>, Artifacts)> {
let compiler = engine.try_compiler()?;
let tunables = engine.tunables();
let symbol_ids_and_locs = compiler.append_code(
&mut obj,
&compiled_funcs,
&|_caller_index: usize, callee: FuncKey| {
self.indices.get(&callee).copied().unwrap_or_else(|| {
panic!("cannot resolve relocation! no index for callee {callee:?}")
})
},
)?;
if tunables.debug_native {
compiler.append_dwarf(
&mut obj,
&translations,
&|module, func| {
let i = self.indices[&FuncKey::DefinedWasmFunction(module, func)];
let (symbol, _) = symbol_ids_and_locs[i];
let (_, _, compiled_func) = &compiled_funcs[i];
(symbol, &**compiled_func)
},
dwarf_package_bytes,
tunables,
)?;
}
let mut table_builder = CompiledFunctionsTableBuilder::new();
for (key, compiled_func_index) in &self.indices {
let (_, func_loc) = symbol_ids_and_locs[*compiled_func_index];
let src_loc = self
.start_srclocs
.get(key)
.copied()
.unwrap_or_else(FilePos::none);
table_builder.push_func(*key, func_loc, src_loc);
}
let mut obj = wasmtime_environ::ObjectBuilder::new(obj, tunables);
let modules = translations
.into_iter()
.map(|(_, mut translation)| {
if engine.tunables().memory_init_cow {
let align = compiler.page_size_align();
let max_always_allowed = engine.config().memory_guaranteed_dense_image_size;
translation.try_static_init(align, max_always_allowed);
}
if engine.tunables().table_lazy_init {
translation.try_func_table_init();
}
obj.append(translation)
})
.collect::<Result<PrimaryMap<_, _>>>()?;
let artifacts = Artifacts {
modules,
table: table_builder.finish(),
};
Ok((obj, artifacts))
}
}
struct Artifacts {
modules: PrimaryMap<StaticModuleIndex, CompiledModuleInfo>,
table: CompiledFunctionsTable,
}
impl Artifacts {
fn unwrap_as_module_info(self) -> (CompiledModuleInfo, CompiledFunctionsTable) {
assert_eq!(self.modules.len(), 1);
let info = self.modules.into_iter().next().unwrap().1;
let table = self.table;
(info, table)
}
}
fn extend_with_range<T>(dest: &mut Vec<T>, items: impl IntoIterator<Item = T>) -> Range<u32> {
let start = dest.len();
let start = u32::try_from(start).unwrap();
dest.extend(items);
let end = dest.len();
let end = u32::try_from(end).unwrap();
start..end
}