Path: blob/main/crates/cranelift/src/compiler.rs
3067 views
use crate::TRAP_INTERNAL_ASSERT;1use crate::debug::DwarfSectionRelocTarget;2use crate::func_environ::FuncEnvironment;3use crate::translate::FuncTranslator;4use crate::{BuiltinFunctionSignatures, builder::LinkOptions, wasm_call_signature};5use crate::{CompiledFunction, ModuleTextBuilder, array_call_signature};6use cranelift_codegen::binemit::CodeOffset;7use cranelift_codegen::inline::InlineCommand;8use cranelift_codegen::ir::condcodes::IntCC;9use cranelift_codegen::ir::{self, InstBuilder, MemFlags, UserExternalName, UserFuncName, Value};10use cranelift_codegen::isa::CallConv;11use cranelift_codegen::isa::{12OwnedTargetIsa, TargetIsa,13unwind::{UnwindInfo, UnwindInfoKind},14};15use cranelift_codegen::print_errors::pretty_error;16use cranelift_codegen::{17CompiledCode, Context, FinalizedMachCallSite, MachBufferDebugTagList, MachBufferFrameLayout,18MachDebugTagPos,19};20use cranelift_entity::PrimaryMap;21use cranelift_frontend::FunctionBuilder;22use object::write::{Object, StandardSegment, SymbolId};23use object::{RelocationEncoding, RelocationFlags, RelocationKind, SectionKind};24use std::any::Any;25use std::borrow::Cow;26use std::cmp;27use std::collections::HashMap;28use std::mem;29use std::ops::Range;30use std::path;31use std::sync::{Arc, Mutex};32use wasmparser::{FuncValidatorAllocations, FunctionBody};33use wasmtime_environ::error::{Context as _, Result};34use wasmtime_environ::obj::{ELF_WASMTIME_EXCEPTIONS, ELF_WASMTIME_FRAMES};35use wasmtime_environ::{36Abi, AddressMapSection, BuiltinFunctionIndex, CacheStore, CompileError, CompiledFunctionBody,37DefinedFuncIndex, FlagValue, FrameInstPos, FrameStackShape, FrameStateSlotBuilder,38FrameTableBuilder, FuncKey, FunctionBodyData, FunctionLoc, HostCall, InliningCompiler,39ModuleTranslation, ModuleTypesBuilder, PtrSize, StackMapSection, StaticModuleIndex,40TrapEncodingBuilder, TrapSentinel, TripleExt, Tunables, WasmFuncType, WasmValType, prelude::*,41};42use wasmtime_unwinder::ExceptionTableBuilder;4344#[cfg(feature = "component-model")]45mod component;4647struct IncrementalCacheContext {48#[cfg(feature = "incremental-cache")]49cache_store: Arc<dyn CacheStore>,50num_hits: usize,51num_cached: usize,52}5354struct CompilerContext {55func_translator: FuncTranslator,56codegen_context: Context,57incremental_cache_ctx: Option<IncrementalCacheContext>,58validator_allocations: FuncValidatorAllocations,59debug_slot_descriptor: Option<FrameStateSlotBuilder>,60abi: Option<Abi>,61}6263impl Default for CompilerContext {64fn default() -> Self {65Self {66func_translator: FuncTranslator::new(),67codegen_context: Context::new(),68incremental_cache_ctx: None,69validator_allocations: Default::default(),70debug_slot_descriptor: None,71abi: None,72}73}74}7576/// A compiler that compiles a WebAssembly module with Compiler, translating77/// the Wasm to Compiler IR, optimizing it and then translating to assembly.78pub struct Compiler {79tunables: Tunables,80contexts: Mutex<Vec<CompilerContext>>,81isa: OwnedTargetIsa,82emit_debug_checks: bool,83linkopts: LinkOptions,84cache_store: Option<Arc<dyn CacheStore>>,85clif_dir: Option<path::PathBuf>,86#[cfg(feature = "wmemcheck")]87pub(crate) wmemcheck: bool,88}8990impl Drop for Compiler {91fn drop(&mut self) {92if self.cache_store.is_none() {93return;94}9596let mut num_hits = 0;97let mut num_cached = 0;98for ctx in self.contexts.lock().unwrap().iter() {99if let Some(ref cache_ctx) = ctx.incremental_cache_ctx {100num_hits += cache_ctx.num_hits;101num_cached += cache_ctx.num_cached;102}103}104105let total = num_hits + num_cached;106if num_hits + num_cached > 0 {107log::trace!(108"Incremental compilation cache stats: {}/{} = {}% (hits/lookup)\ncached: {}",109num_hits,110total,111(num_hits as f32) / (total as f32) * 100.0,112num_cached113);114}115}116}117118impl Compiler {119pub fn new(120tunables: Tunables,121isa: OwnedTargetIsa,122cache_store: Option<Arc<dyn CacheStore>>,123emit_debug_checks: bool,124linkopts: LinkOptions,125clif_dir: Option<path::PathBuf>,126wmemcheck: bool,127) -> Compiler {128let _ = wmemcheck;129Compiler {130contexts: Default::default(),131tunables,132isa,133emit_debug_checks,134linkopts,135cache_store,136clif_dir,137#[cfg(feature = "wmemcheck")]138wmemcheck,139}140}141142/// Perform an indirect call from Cranelift-generated code to native code in143/// Wasmtime itself.144///145/// For native platforms this is a simple `call_indirect` instruction but146/// for the Pulley backend this is special as it's transitioning from147/// Cranelift-generated bytecode to native code on the host. That requires a148/// special opcode in the interpreter and is modeled slightly differently in149/// Cranelift IR.150fn call_indirect_host(151&self,152builder: &mut FunctionBuilder<'_>,153hostcall: impl Into<HostCall>,154sig: ir::SigRef,155addr: Value,156args: &[Value],157) -> ir::Inst {158let signature = &builder.func.dfg.signatures[sig];159160// When calling the host we should always be using the platform's161// default calling convention since it'll be calling Rust code in162// Wasmtime itself.163assert_eq!(signature.call_conv, self.isa.default_call_conv());164165// If this target is actually pulley then the goal is to emit the custom166// `call_indirect_host` pulley opcode. That's encoded in Cranelift as a167// `call` instruction where the name is `colocated: false`. This will168// force a pulley-specific relocation to get emitted in addition to169// using the `call_indirect_host` instruction.170if self.isa.triple().is_pulley() {171let mut new_signature = signature.clone();172new_signature173.params174.insert(0, ir::AbiParam::new(self.isa.pointer_type()));175let new_sig = builder.func.import_signature(new_signature);176let key = FuncKey::PulleyHostCall(hostcall.into());177let (namespace, index) = key.into_raw_parts();178let name = ir::ExternalName::User(179builder180.func181.declare_imported_user_function(ir::UserExternalName { namespace, index }),182);183let func = builder.func.import_function(ir::ExtFuncData {184name,185signature: new_sig,186// This is the signal that a special `call_indirect_host`187// opcode is used to jump from pulley to the host.188colocated: false,189patchable: false,190});191let mut raw_args = vec![addr];192raw_args.extend_from_slice(args);193return builder.ins().call(func, &raw_args);194}195196builder.ins().call_indirect(sig, addr, args)197}198}199200fn box_dyn_any_compiled_function(f: CompiledFunction) -> Box<dyn Any + Send + Sync> {201let b = box_dyn_any(f);202debug_assert!(b.is::<CompiledFunction>());203b204}205206fn box_dyn_any_compiler_context(ctx: Option<CompilerContext>) -> Box<dyn Any + Send + Sync> {207let b = box_dyn_any(ctx);208debug_assert!(b.is::<Option<CompilerContext>>());209b210}211212fn box_dyn_any(x: impl Any + Send + Sync) -> Box<dyn Any + Send + Sync> {213log::trace!(214"making Box<dyn Any + Send + Sync> of {}",215std::any::type_name_of_val(&x)216);217let b = Box::new(x);218let r: &(dyn Any + Sync + Send) = &*b;219log::trace!(" --> {r:#p}");220b221}222223impl wasmtime_environ::Compiler for Compiler {224fn inlining_compiler(&self) -> Option<&dyn wasmtime_environ::InliningCompiler> {225Some(self)226}227228fn compile_function(229&self,230translation: &ModuleTranslation<'_>,231key: FuncKey,232input: FunctionBodyData<'_>,233types: &ModuleTypesBuilder,234symbol: &str,235) -> Result<CompiledFunctionBody, CompileError> {236log::trace!("compiling Wasm function: {key:?} = {symbol:?}");237238let isa = &*self.isa;239let module = &translation.module;240241let (module_index, def_func_index) = key.unwrap_defined_wasm_function();242debug_assert_eq!(translation.module_index(), module_index);243244let func_index = module.func_index(def_func_index);245let sig = translation.module.functions[func_index]246.signature247.unwrap_module_type_index();248let wasm_func_ty = types[sig].unwrap_func();249250let mut compiler = self.function_compiler();251252let context = &mut compiler.cx.codegen_context;253context.func.signature = wasm_call_signature(isa, wasm_func_ty, &self.tunables);254let (namespace, index) = key.into_raw_parts();255context.func.name = UserFuncName::User(UserExternalName { namespace, index });256257if self.tunables.debug_native {258context.func.collect_debug_info();259}260261let mut func_env = FuncEnvironment::new(self, translation, types, wasm_func_ty, key);262263// The `stack_limit` global value below is the implementation of stack264// overflow checks in Wasmtime.265//266// The Wasm spec defines that stack overflows will raise a trap, and267// there's also an added constraint where as an embedder you frequently268// are running host-provided code called from wasm. WebAssembly and269// native code currently share the same call stack, so Wasmtime needs to270// make sure that host-provided code will have enough call-stack271// available to it.272//273// The way that stack overflow is handled here is by adding a prologue274// check to all functions for how much native stack is remaining. The275// `VMContext` pointer is the first argument to all functions, and the276// first field of this structure is `*const VMStoreContext` and the277// third field of that is the stack limit. Note that the stack limit in278// this case means "if the stack pointer goes below this, trap". Each279// function which consumes stack space or isn't a leaf function starts280// off by loading the stack limit, checking it against the stack281// pointer, and optionally traps.282//283// This manual check allows the embedder to give wasm a relatively284// precise amount of stack allocation. Using this scheme we reserve a285// chunk of stack for wasm code relative from where wasm code was286// called. This ensures that native code called by wasm should have287// native stack space to run, and the numbers of stack spaces here288// should all be configurable for various embeddings.289//290// Note that this check is independent of each thread's stack guard page291// here. If the stack guard page is reached that's still considered an292// abort for the whole program since the runtime limits configured by293// the embedder should cause wasm to trap before it reaches that294// (ensuring the host has enough space as well for its functionality).295if !isa.triple().is_pulley() {296let vmctx = context297.func298.create_global_value(ir::GlobalValueData::VMContext);299let interrupts_ptr = context.func.create_global_value(ir::GlobalValueData::Load {300base: vmctx,301offset: i32::from(func_env.offsets.ptr.vmctx_store_context()).into(),302global_type: isa.pointer_type(),303flags: MemFlags::trusted().with_readonly(),304});305let stack_limit = context.func.create_global_value(ir::GlobalValueData::Load {306base: interrupts_ptr,307offset: i32::from(func_env.offsets.ptr.vmstore_context_stack_limit()).into(),308global_type: isa.pointer_type(),309flags: MemFlags::trusted(),310});311if self.tunables.signals_based_traps {312context.func.stack_limit = Some(stack_limit);313} else {314func_env.stack_limit_at_function_entry = Some(stack_limit);315}316}317let FunctionBodyData { validator, body } = input;318let mut validator =319validator.into_validator(mem::take(&mut compiler.cx.validator_allocations));320compiler.cx.func_translator.translate_body(321&mut validator,322body.clone(),323&mut context.func,324&mut func_env,325)?;326327if self.tunables.inlining {328compiler329.cx330.codegen_context331.legalize(isa)332.map_err(|e| CompileError::Codegen(e.to_string()))?;333}334335let needs_gc_heap = func_env.needs_gc_heap();336337if let Some((_, slot_builder)) = func_env.state_slot {338compiler.cx.debug_slot_descriptor = Some(slot_builder);339}340341let timing = cranelift_codegen::timing::take_current();342log::debug!("`{symbol}` translated to CLIF in {:?}", timing.total());343log::trace!("`{symbol}` timing info\n{timing}");344345Ok(CompiledFunctionBody {346code: box_dyn_any_compiler_context(Some(compiler.cx)),347needs_gc_heap,348})349}350351fn compile_array_to_wasm_trampoline(352&self,353translation: &ModuleTranslation<'_>,354types: &ModuleTypesBuilder,355key: FuncKey,356symbol: &str,357) -> Result<CompiledFunctionBody, CompileError> {358let (module_index, def_func_index) = key.unwrap_array_to_wasm_trampoline();359let func_index = translation.module.func_index(def_func_index);360let sig = translation.module.functions[func_index]361.signature362.unwrap_module_type_index();363self.array_to_wasm_trampoline(364key,365FuncKey::DefinedWasmFunction(module_index, def_func_index),366types[sig].unwrap_func(),367symbol,368self.isa.pointer_bytes().vmctx_store_context().into(),369wasmtime_environ::VMCONTEXT_MAGIC,370)371}372373fn compile_wasm_to_array_trampoline(374&self,375wasm_func_ty: &WasmFuncType,376key: FuncKey,377symbol: &str,378) -> Result<CompiledFunctionBody, CompileError> {379log::trace!("compiling wasm-to-array trampoline: {key:?} = {symbol:?}");380381let isa = &*self.isa;382let pointer_type = isa.pointer_type();383let wasm_call_sig = wasm_call_signature(isa, wasm_func_ty, &self.tunables);384let array_call_sig = array_call_signature(isa);385386let mut compiler = self.function_compiler();387let func = ir::Function::with_name_signature(key_to_name(key), wasm_call_sig);388let (mut builder, block0) = compiler.builder(func);389390let args = builder.func.dfg.block_params(block0).to_vec();391let callee_vmctx = args[0];392let caller_vmctx = args[1];393394// We are exiting Wasm, so save our PC and FP.395//396// Assert that the caller vmctx really is a core Wasm vmctx, since397// that's what we are assuming with our offsets below.398self.debug_assert_vmctx_kind(399&mut builder,400caller_vmctx,401wasmtime_environ::VMCONTEXT_MAGIC,402);403let ptr = isa.pointer_bytes();404let vm_store_context = builder.ins().load(405pointer_type,406MemFlags::trusted(),407caller_vmctx,408i32::from(ptr.vmcontext_store_context()),409);410save_last_wasm_exit_fp_and_pc(&mut builder, pointer_type, &ptr, vm_store_context);411412// Spill all wasm arguments to the stack in `ValRaw` slots.413let (args_base, args_len) =414self.allocate_stack_array_and_spill_args(wasm_func_ty, &mut builder, &args[2..]);415let args_len = builder.ins().iconst(pointer_type, i64::from(args_len));416417// Load the actual callee out of the418// `VMArrayCallHostFuncContext::host_func`.419let ptr_size = isa.pointer_bytes();420let callee = builder.ins().load(421pointer_type,422MemFlags::trusted(),423callee_vmctx,424ptr_size.vmarray_call_host_func_context_func_ref() + ptr_size.vm_func_ref_array_call(),425);426427// Do an indirect call to the callee.428let callee_signature = builder.func.import_signature(array_call_sig);429let call = self.call_indirect_host(430&mut builder,431HostCall::ArrayCall,432callee_signature,433callee,434&[callee_vmctx, caller_vmctx, args_base, args_len],435);436let succeeded = builder.func.dfg.inst_results(call)[0];437self.raise_if_host_trapped(&mut builder, caller_vmctx, succeeded);438let results =439self.load_values_from_array(wasm_func_ty.returns(), &mut builder, args_base, args_len);440builder.ins().return_(&results);441builder.finalize();442443Ok(CompiledFunctionBody {444code: box_dyn_any_compiler_context(Some(compiler.cx)),445needs_gc_heap: false,446})447}448449fn append_code(450&self,451obj: &mut Object<'static>,452funcs: &[(String, FuncKey, Box<dyn Any + Send + Sync>)],453resolve_reloc: &dyn Fn(usize, FuncKey) -> usize,454) -> Result<Vec<(SymbolId, FunctionLoc)>> {455log::trace!(456"appending functions to object file: {:#?}",457funcs.iter().map(|(sym, _, _)| sym).collect::<Vec<_>>()458);459460let mut builder =461ModuleTextBuilder::new(obj, self, self.isa.text_section_builder(funcs.len()));462if self.linkopts.force_jump_veneers {463builder.force_veneers();464}465let mut addrs = AddressMapSection::default();466let mut traps = TrapEncodingBuilder::default();467let mut stack_maps = StackMapSection::default();468let mut exception_tables = ExceptionTableBuilder::default();469let mut frame_tables = FrameTableBuilder::default();470471let funcs = funcs472.iter()473.map(|(sym, key, func)| {474debug_assert!(!func.is::<Option<CompilerContext>>());475debug_assert!(func.is::<CompiledFunction>());476let func = func.downcast_ref::<CompiledFunction>().unwrap();477(sym, *key, func)478})479.collect::<Vec<_>>();480481let mut frame_descriptors = HashMap::new();482if self.tunables.debug_guest {483for (_, key, func) in &funcs {484frame_descriptors.insert(485*key,486func.debug_slot_descriptor487.as_ref()488.map(|builder| builder.serialize())489.unwrap_or_else(|| vec![]),490);491}492}493494let mut breakpoint_table = Vec::new();495let mut nop_units = None;496497let mut ret = Vec::with_capacity(funcs.len());498for (i, (sym, _key, func)) in funcs.iter().enumerate() {499let (sym_id, range) = builder.append_func(&sym, func, |idx| resolve_reloc(i, idx));500log::trace!("symbol id {sym_id:?} = {sym:?}");501502if self.tunables.generate_address_map {503let addr = func.address_map();504addrs.push(range.clone(), &addr.instructions);505}506507clif_to_env_stack_maps(508&mut stack_maps,509range.clone(),510func.buffer.user_stack_maps(),511);512513traps.push(range.clone(), &func.traps().collect::<Vec<_>>());514clif_to_env_exception_tables(515&mut exception_tables,516range.clone(),517func.buffer.call_sites(),518)?;519if self.tunables.debug_guest520&& let Some(frame_layout) = func.buffer.frame_layout()521{522clif_to_env_frame_tables(523&mut frame_tables,524range.clone(),525func.buffer.debug_tags(),526frame_layout,527&frame_descriptors,528)?;529}530if self.tunables.debug_guest {531clif_to_env_breakpoints(532range.clone(),533func.breakpoint_patches(),534&mut breakpoint_table,535)?;536nop_units.get_or_insert_with(|| func.buffer.nop_units.clone());537}538builder.append_padding(self.linkopts.padding_between_functions);539540let info = FunctionLoc {541start: u32::try_from(range.start).unwrap(),542length: u32::try_from(range.end - range.start).unwrap(),543};544ret.push((sym_id, info));545}546547// Sort breakpoints by Wasm PC now. Note that the same Wasm PC548// may appear in multiple functions (due to inlining) so it is549// only now that we can aggregate all appearances of a PC550// together for breakpoint metadata indexed by that PC.551breakpoint_table.sort_by_key(|(wasm_pc, _text_range)| *wasm_pc);552553builder.finish(|text| {554if !breakpoint_table.is_empty() {555let nop_units = nop_units.as_ref().unwrap();556let fill_with_nops = |mut slice: &mut [u8]| {557while !slice.is_empty() {558let nop_unit = nop_units559.iter()560.rev()561.find(|u| u.len() <= slice.len())562.expect("no NOP is small enough for remaining slice");563let (nop_sized_chunk, rest) = slice.split_at_mut(nop_unit.len());564nop_sized_chunk.copy_from_slice(&nop_unit);565slice = rest;566}567};568569for (wasm_pc, text_range) in &breakpoint_table {570let start = usize::try_from(text_range.start).unwrap();571let end = usize::try_from(text_range.end).unwrap();572let text = &mut text[start..end];573frame_tables.add_breakpoint_patch(*wasm_pc, text_range.start, text);574fill_with_nops(text);575}576}577});578579if self.tunables.generate_address_map {580addrs.append_to(obj);581}582stack_maps.append_to(obj);583traps.append_to(obj);584585let exception_section = obj.add_section(586obj.segment_name(StandardSegment::Data).to_vec(),587ELF_WASMTIME_EXCEPTIONS.as_bytes().to_vec(),588SectionKind::ReadOnlyData,589);590exception_tables.serialize(|bytes| {591obj.append_section_data(exception_section, bytes, 1);592});593594if self.tunables.debug_guest {595let frame_table_section = obj.add_section(596obj.segment_name(StandardSegment::Data).to_vec(),597ELF_WASMTIME_FRAMES.as_bytes().to_vec(),598SectionKind::ReadOnlyData,599);600frame_tables.serialize(|bytes| {601obj.append_section_data(frame_table_section, bytes, 1);602});603}604605Ok(ret)606}607608fn triple(&self) -> &target_lexicon::Triple {609self.isa.triple()610}611612fn flags(&self) -> Vec<(&'static str, FlagValue<'static>)> {613crate::clif_flags_to_wasmtime(self.isa.flags().iter())614}615616fn isa_flags(&self) -> Vec<(&'static str, FlagValue<'static>)> {617crate::clif_flags_to_wasmtime(self.isa.isa_flags())618}619620fn is_branch_protection_enabled(&self) -> bool {621self.isa.is_branch_protection_enabled()622}623624#[cfg(feature = "component-model")]625fn component_compiler(&self) -> &dyn wasmtime_environ::component::ComponentCompiler {626self627}628629fn append_dwarf<'a>(630&self,631obj: &mut Object<'_>,632translations: &'a PrimaryMap<StaticModuleIndex, ModuleTranslation<'a>>,633get_func: &'a dyn Fn(634StaticModuleIndex,635DefinedFuncIndex,636) -> (SymbolId, &'a (dyn Any + Send + Sync)),637dwarf_package_bytes: Option<&'a [u8]>,638tunables: &'a Tunables,639) -> Result<()> {640log::trace!("appending DWARF debug info");641642let get_func = move |m, f| {643let (sym, any) = get_func(m, f);644log::trace!("get_func({m:?}, {f:?}) -> ({sym:?}, {any:#p})");645debug_assert!(!any.is::<Option<CompilerContext>>());646debug_assert!(any.is::<CompiledFunction>());647(648sym,649any.downcast_ref::<CompiledFunction>().unwrap().metadata(),650)651};652653let mut compilation = crate::debug::Compilation::new(654&*self.isa,655translations,656&get_func,657dwarf_package_bytes,658tunables,659);660let dwarf_sections = crate::debug::emit_dwarf(&*self.isa, &mut compilation)661.with_context(|| "failed to emit DWARF debug information")?;662663let (debug_bodies, debug_relocs): (Vec<_>, Vec<_>) = dwarf_sections664.iter()665.map(|s| ((s.name, &s.body), (s.name, &s.relocs)))666.unzip();667let mut dwarf_sections_ids = HashMap::new();668for (name, body) in debug_bodies {669let segment = obj.segment_name(StandardSegment::Debug).to_vec();670let section_id = obj.add_section(segment, name.as_bytes().to_vec(), SectionKind::Debug);671dwarf_sections_ids.insert(name, section_id);672obj.append_section_data(section_id, &body, 1);673}674675// Write all debug data relocations.676for (name, relocs) in debug_relocs {677let section_id = *dwarf_sections_ids.get(name).unwrap();678for reloc in relocs {679let target_symbol = match reloc.target {680DwarfSectionRelocTarget::Func(id) => compilation.symbol_id(id),681DwarfSectionRelocTarget::Section(name) => {682obj.section_symbol(dwarf_sections_ids[name])683}684};685obj.add_relocation(686section_id,687object::write::Relocation {688offset: u64::from(reloc.offset),689symbol: target_symbol,690addend: i64::from(reloc.addend),691flags: RelocationFlags::Generic {692size: reloc.size << 3,693kind: RelocationKind::Absolute,694encoding: RelocationEncoding::Generic,695},696},697)?;698}699}700701Ok(())702}703704fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {705self.isa.create_systemv_cie()706}707708fn compile_wasm_to_builtin(709&self,710key: FuncKey,711symbol: &str,712) -> Result<CompiledFunctionBody, CompileError> {713log::trace!("compiling wasm-to-builtin trampoline: {key:?} = {symbol:?}");714715let isa = &*self.isa;716let ptr_size = isa.pointer_bytes();717let pointer_type = isa.pointer_type();718let sigs = BuiltinFunctionSignatures::new(self);719720let (builtin_func_index, wasm_sig) = match key {721FuncKey::WasmToBuiltinTrampoline(builtin) => (builtin, sigs.wasm_signature(builtin)),722FuncKey::PatchableToBuiltinTrampoline(builtin) => {723let mut sig = sigs.wasm_signature(builtin);724// Patchable functions cannot return anything. We725// raise any errors that occur below so this is fine.726sig.returns.clear();727sig.call_conv = CallConv::PreserveAll;728(builtin, sig)729}730_ => unreachable!(),731};732let host_sig = sigs.host_signature(builtin_func_index);733734let mut compiler = self.function_compiler();735let func = ir::Function::with_name_signature(key_to_name(key), wasm_sig.clone());736let (mut builder, block0) = compiler.builder(func);737let vmctx = builder.block_params(block0)[0];738739// Debug-assert that this is the right kind of vmctx, and then740// additionally perform the "routine of the exit trampoline" of saving741// fp/pc/etc.742self.debug_assert_vmctx_kind(&mut builder, vmctx, wasmtime_environ::VMCONTEXT_MAGIC);743let vm_store_context = builder.ins().load(744pointer_type,745MemFlags::trusted(),746vmctx,747ptr_size.vmcontext_store_context(),748);749save_last_wasm_exit_fp_and_pc(&mut builder, pointer_type, &ptr_size, vm_store_context);750751// Now it's time to delegate to the actual builtin. Forward all our own752// arguments to the libcall itself.753let args = builder.block_params(block0).to_vec();754let call = self.call_builtin(&mut builder, vmctx, &args, builtin_func_index, host_sig);755let results = builder.func.dfg.inst_results(call).to_vec();756757// Libcalls do not explicitly jump/raise on traps but instead return a758// code indicating whether they trapped or not. This means that it's the759// responsibility of the trampoline to check for an trapping return760// value and raise a trap as appropriate. With the `results` above check761// what `index` is and for each libcall that has a trapping return value762// process it here.763match builtin_func_index.trap_sentinel() {764Some(TrapSentinel::Falsy) => {765self.raise_if_host_trapped(&mut builder, vmctx, results[0]);766}767Some(TrapSentinel::NegativeTwo) => {768let ty = builder.func.dfg.value_type(results[0]);769let trapped = builder.ins().iconst(ty, -2);770let succeeded = builder.ins().icmp(IntCC::NotEqual, results[0], trapped);771self.raise_if_host_trapped(&mut builder, vmctx, succeeded);772}773Some(TrapSentinel::Negative) => {774let ty = builder.func.dfg.value_type(results[0]);775let zero = builder.ins().iconst(ty, 0);776let succeeded =777builder778.ins()779.icmp(IntCC::SignedGreaterThanOrEqual, results[0], zero);780self.raise_if_host_trapped(&mut builder, vmctx, succeeded);781}782Some(TrapSentinel::NegativeOne) => {783let ty = builder.func.dfg.value_type(results[0]);784let minus_one = builder.ins().iconst(ty, -1);785let succeeded = builder.ins().icmp(IntCC::NotEqual, results[0], minus_one);786self.raise_if_host_trapped(&mut builder, vmctx, succeeded);787}788None => {}789}790791// And finally, return all the results of this libcall.792if !wasm_sig.returns.is_empty() {793builder.ins().return_(&results);794} else {795builder.ins().return_(&[]);796}797builder.finalize();798799Ok(CompiledFunctionBody {800code: box_dyn_any_compiler_context(Some(compiler.cx)),801needs_gc_heap: false,802})803}804805fn compiled_function_relocation_targets<'a>(806&'a self,807func: &'a dyn Any,808) -> Box<dyn Iterator<Item = FuncKey> + 'a> {809debug_assert!(!func.is::<Option<CompilerContext>>());810debug_assert!(func.is::<CompiledFunction>());811let func = func.downcast_ref::<CompiledFunction>().unwrap();812Box::new(func.relocations().map(|r| r.reloc_target))813}814}815816impl InliningCompiler for Compiler {817fn calls(&self, func_body: &CompiledFunctionBody, calls: &mut IndexSet<FuncKey>) -> Result<()> {818debug_assert!(!func_body.code.is::<CompiledFunction>());819debug_assert!(func_body.code.is::<Option<CompilerContext>>());820let cx = func_body821.code822.downcast_ref::<Option<CompilerContext>>()823.unwrap()824.as_ref()825.unwrap();826let func = &cx.codegen_context.func;827calls.extend(828func.params829.user_named_funcs()830.values()831.map(|name| FuncKey::from_raw_parts(name.namespace, name.index))832.filter(|key| match key {833FuncKey::DefinedWasmFunction(..) => true,834#[cfg(feature = "component-model")]835FuncKey::UnsafeIntrinsic(..) => true,836_ => false,837}),838);839Ok(())840}841842fn size(&self, func_body: &CompiledFunctionBody) -> u32 {843debug_assert!(!func_body.code.is::<CompiledFunction>());844debug_assert!(func_body.code.is::<Option<CompilerContext>>());845let cx = func_body846.code847.downcast_ref::<Option<CompilerContext>>()848.unwrap()849.as_ref()850.unwrap();851let func = &cx.codegen_context.func;852let size = func.dfg.values().len();853u32::try_from(size).unwrap()854}855856fn inline<'a>(857&self,858func_body: &mut CompiledFunctionBody,859get_callee: &'a mut dyn FnMut(FuncKey) -> Option<&'a CompiledFunctionBody>,860) -> Result<()> {861debug_assert!(!func_body.code.is::<CompiledFunction>());862debug_assert!(func_body.code.is::<Option<CompilerContext>>());863let code = func_body864.code865.downcast_mut::<Option<CompilerContext>>()866.unwrap();867let cx = code.as_mut().unwrap();868869cx.codegen_context.inline(Inliner(get_callee))?;870return Ok(());871872struct Inliner<'a>(&'a mut dyn FnMut(FuncKey) -> Option<&'a CompiledFunctionBody>);873874impl cranelift_codegen::inline::Inline for Inliner<'_> {875fn inline(876&mut self,877caller: &ir::Function,878_call_inst: ir::Inst,879_call_opcode: ir::Opcode,880callee: ir::FuncRef,881_call_args: &[ir::Value],882) -> InlineCommand<'_> {883let callee = &caller.dfg.ext_funcs[callee].name;884let callee = match callee {885ir::ExternalName::User(callee) => *callee,886ir::ExternalName::TestCase(_)887| ir::ExternalName::LibCall(_)888| ir::ExternalName::KnownSymbol(_) => return InlineCommand::KeepCall,889};890let callee = &caller.params.user_named_funcs()[callee];891let callee = FuncKey::from_raw_parts(callee.namespace, callee.index);892match callee {893FuncKey::DefinedWasmFunction(..) => {}894#[cfg(feature = "component-model")]895FuncKey::UnsafeIntrinsic(..) => {}896_ => return InlineCommand::KeepCall,897}898match (self.0)(callee) {899None => InlineCommand::KeepCall,900Some(func_body) => {901debug_assert!(!func_body.code.is::<CompiledFunction>());902debug_assert!(func_body.code.is::<Option<CompilerContext>>());903let cx = func_body904.code905.downcast_ref::<Option<CompilerContext>>()906.unwrap();907InlineCommand::Inline {908callee: Cow::Borrowed(&cx.as_ref().unwrap().codegen_context.func),909// We've already visited the callee for inlining910// due to our bottom-up approach, no need to911// visit it again.912visit_callee: false,913}914}915}916}917}918}919920fn finish_compiling(921&self,922func_body: &mut CompiledFunctionBody,923input: Option<wasmparser::FunctionBody<'_>>,924symbol: &str,925) -> Result<()> {926log::trace!("finish compiling {symbol:?}");927debug_assert!(!func_body.code.is::<CompiledFunction>());928debug_assert!(func_body.code.is::<Option<CompilerContext>>());929let cx = func_body930.code931.downcast_mut::<Option<CompilerContext>>()932.unwrap()933.take()934.unwrap();935let compiler = FunctionCompiler { compiler: self, cx };936937let symbol = match compiler.cx.abi {938None => Cow::Borrowed(symbol),939Some(Abi::Wasm) => Cow::Owned(format!("{symbol}_wasm_call")),940Some(Abi::Array) => Cow::Owned(format!("{symbol}_array_call")),941Some(Abi::Patchable) => Cow::Owned(format!("{symbol}_patchable_call")),942};943944let compiled_func = if let Some(input) = input {945compiler.finish_with_info(Some((&input, &self.tunables)), &symbol)?946} else {947compiler.finish(&symbol)?948};949950let timing = cranelift_codegen::timing::take_current();951log::debug!("`{symbol}` compiled in {:?}", timing.total());952log::trace!("`{symbol}` timing info\n{timing}");953954func_body.code = box_dyn_any_compiled_function(compiled_func);955Ok(())956}957}958959#[cfg(feature = "incremental-cache")]960mod incremental_cache {961use super::*;962963struct CraneliftCacheStore(Arc<dyn CacheStore>);964965impl cranelift_codegen::incremental_cache::CacheKvStore for CraneliftCacheStore {966fn get(&self, key: &[u8]) -> Option<std::borrow::Cow<'_, [u8]>> {967self.0.get(key)968}969fn insert(&mut self, key: &[u8], val: Vec<u8>) {970self.0.insert(key, val);971}972}973974pub(super) fn compile_maybe_cached<'a>(975context: &'a mut Context,976isa: &dyn TargetIsa,977cache_ctx: Option<&mut IncrementalCacheContext>,978) -> Result<CompiledCode, CompileError> {979let cache_ctx = match cache_ctx {980Some(ctx) => ctx,981None => return compile_uncached(context, isa),982};983984let mut cache_store = CraneliftCacheStore(cache_ctx.cache_store.clone());985let (_compiled_code, from_cache) = context986.compile_with_cache(isa, &mut cache_store, &mut Default::default())987.map_err(|error| CompileError::Codegen(pretty_error(&error.func, error.inner)))?;988989if from_cache {990cache_ctx.num_hits += 1;991} else {992cache_ctx.num_cached += 1;993}994995Ok(context.take_compiled_code().unwrap())996}997}998999#[cfg(feature = "incremental-cache")]1000use incremental_cache::*;10011002#[cfg(not(feature = "incremental-cache"))]1003fn compile_maybe_cached<'a>(1004context: &'a mut Context,1005isa: &dyn TargetIsa,1006_cache_ctx: Option<&mut IncrementalCacheContext>,1007) -> Result<CompiledCode, CompileError> {1008compile_uncached(context, isa)1009}10101011fn compile_uncached<'a>(1012context: &'a mut Context,1013isa: &dyn TargetIsa,1014) -> Result<CompiledCode, CompileError> {1015context1016.compile(isa, &mut Default::default())1017.map_err(|error| CompileError::Codegen(pretty_error(&error.func, error.inner)))?;1018Ok(context.take_compiled_code().unwrap())1019}10201021impl Compiler {1022/// This function will allocate a stack slot suitable for storing both the1023/// arguments and return values of the function, and then the arguments will1024/// all be stored in this block.1025///1026/// `block0` must be the entry block of the function and `ty` must be the1027/// Wasm function type of the trampoline.1028///1029/// The stack slot pointer is returned in addition to the size, in units of1030/// `ValRaw`, of the stack slot.1031fn allocate_stack_array_and_spill_args(1032&self,1033ty: &WasmFuncType,1034builder: &mut FunctionBuilder,1035args: &[ir::Value],1036) -> (Value, u32) {1037let isa = &*self.isa;1038let pointer_type = isa.pointer_type();10391040// Compute the size of the values vector.1041let value_size = mem::size_of::<u128>();1042let values_vec_len = cmp::max(ty.params().len(), ty.returns().len());1043let values_vec_byte_size = u32::try_from(value_size * values_vec_len).unwrap();1044let values_vec_len = u32::try_from(values_vec_len).unwrap();10451046let slot = builder.func.create_sized_stack_slot(ir::StackSlotData::new(1047ir::StackSlotKind::ExplicitSlot,1048values_vec_byte_size,10494,1050));1051let values_vec_ptr = builder.ins().stack_addr(pointer_type, slot, 0);10521053{1054let values_vec_len = builder1055.ins()1056.iconst(ir::types::I32, i64::from(values_vec_len));1057self.store_values_to_array(builder, ty.params(), args, values_vec_ptr, values_vec_len);1058}10591060(values_vec_ptr, values_vec_len)1061}10621063/// Store values to an array in the array calling convention.1064///1065/// Used either to store arguments to the array when calling a function1066/// using the array calling convention, or used to store results to the1067/// array when implementing a function that exposes the array calling1068/// convention.1069fn store_values_to_array(1070&self,1071builder: &mut FunctionBuilder,1072types: &[WasmValType],1073values: &[Value],1074values_vec_ptr: Value,1075values_vec_capacity: Value,1076) {1077debug_assert_eq!(types.len(), values.len());1078self.debug_assert_enough_capacity_for_length(builder, types.len(), values_vec_capacity);10791080// Note that loads and stores are unconditionally done in the1081// little-endian format rather than the host's native-endianness,1082// despite this load/store being unrelated to execution in Wasm itself.1083// For more details on this see the `ValRaw` type in1084// `wasmtime::runtime::vm`.1085let flags = ir::MemFlags::new()1086.with_notrap()1087.with_endianness(ir::Endianness::Little);10881089let value_size = mem::size_of::<u128>();1090for (i, val) in values.iter().copied().enumerate() {1091crate::unbarriered_store_type_at_offset(1092&mut builder.cursor(),1093flags,1094values_vec_ptr,1095i32::try_from(i * value_size).unwrap(),1096val,1097);1098}1099}11001101/// Used for loading the values of an array-call host function's value1102/// array.1103///1104/// This can be used to load arguments out of the array if the trampoline we1105/// are building exposes the array calling convention, or it can be used to1106/// load results out of the array if the trampoline we are building calls a1107/// function that uses the array calling convention.1108fn load_values_from_array(1109&self,1110types: &[WasmValType],1111builder: &mut FunctionBuilder,1112values_vec_ptr: Value,1113values_vec_capacity: Value,1114) -> Vec<ir::Value> {1115let isa = &*self.isa;1116let value_size = mem::size_of::<u128>();11171118self.debug_assert_enough_capacity_for_length(builder, types.len(), values_vec_capacity);11191120// Note that this is little-endian like `store_values_to_array` above,1121// see notes there for more information.1122let flags = MemFlags::new()1123.with_notrap()1124.with_endianness(ir::Endianness::Little);11251126let mut results = Vec::new();1127for (i, ty) in types.iter().enumerate() {1128results.push(crate::unbarriered_load_type_at_offset(1129isa,1130&mut builder.cursor(),1131*ty,1132flags,1133values_vec_ptr,1134i32::try_from(i * value_size).unwrap(),1135));1136}1137results1138}11391140fn function_compiler(&self) -> FunctionCompiler<'_> {1141let saved_context = self.contexts.lock().unwrap().pop();1142FunctionCompiler {1143compiler: self,1144cx: saved_context1145.map(|mut ctx| {1146ctx.codegen_context.clear();1147ctx1148})1149.unwrap_or_else(|| CompilerContext {1150#[cfg(feature = "incremental-cache")]1151incremental_cache_ctx: self.cache_store.as_ref().map(|cache_store| {1152IncrementalCacheContext {1153cache_store: cache_store.clone(),1154num_hits: 0,1155num_cached: 0,1156}1157}),1158..Default::default()1159}),1160}1161}11621163/// Invokes the `raise` libcall in `vmctx` if the `succeeded` value1164/// indicates if a trap happened.1165///1166/// This helper is used when the host returns back to WebAssembly. The host1167/// returns a `bool` indicating whether the call succeeded. If the call1168/// failed then Cranelift needs to unwind back to the original invocation1169/// point. The unwind right now is then implemented in Wasmtime with an1170/// exceptional resume, one day this might be implemented differently with1171/// an unwind inside of Cranelift.1172///1173/// Additionally in the future for pulley this will emit a special trap1174/// opcode for Pulley itself to cease interpretation and exit the1175/// interpreter.1176pub fn raise_if_host_trapped(1177&self,1178builder: &mut FunctionBuilder<'_>,1179vmctx: ir::Value,1180succeeded: ir::Value,1181) {1182let trapped_block = builder.create_block();1183let continuation_block = builder.create_block();1184builder.set_cold_block(trapped_block);1185builder1186.ins()1187.brif(succeeded, continuation_block, &[], trapped_block, &[]);11881189builder.seal_block(trapped_block);1190builder.seal_block(continuation_block);11911192builder.switch_to_block(trapped_block);1193let sigs = BuiltinFunctionSignatures::new(self);1194let sig = sigs.host_signature(BuiltinFunctionIndex::raise());1195self.call_builtin(builder, vmctx, &[vmctx], BuiltinFunctionIndex::raise(), sig);1196builder.ins().trap(TRAP_INTERNAL_ASSERT);11971198builder.switch_to_block(continuation_block);1199}12001201/// Helper to load the core `builtin` from `vmctx` and invoke it with1202/// `args`.1203fn call_builtin(1204&self,1205builder: &mut FunctionBuilder<'_>,1206vmctx: ir::Value,1207args: &[ir::Value],1208builtin: BuiltinFunctionIndex,1209sig: ir::Signature,1210) -> ir::Inst {1211let isa = &*self.isa;1212let ptr_size = isa.pointer_bytes();1213let pointer_type = isa.pointer_type();12141215// Builtins are stored in an array in all `VMContext`s. First load the1216// base pointer of the array and then load the entry of the array that1217// corresponds to this builtin.1218let mem_flags = ir::MemFlags::trusted().with_readonly();1219let array_addr = builder.ins().load(1220pointer_type,1221mem_flags,1222vmctx,1223i32::from(ptr_size.vmcontext_builtin_functions()),1224);1225let body_offset = i32::try_from(builtin.index() * pointer_type.bytes()).unwrap();1226let func_addr = builder1227.ins()1228.load(pointer_type, mem_flags, array_addr, body_offset);12291230let sig = builder.func.import_signature(sig);1231self.call_indirect_host(builder, builtin, sig, func_addr, args)1232}12331234pub fn isa(&self) -> &dyn TargetIsa {1235&*self.isa1236}12371238pub fn tunables(&self) -> &Tunables {1239&self.tunables1240}12411242fn debug_assert_enough_capacity_for_length(1243&self,1244builder: &mut FunctionBuilder,1245length: usize,1246capacity: ir::Value,1247) {1248if !self.emit_debug_checks {1249return;1250}1251let enough_capacity = builder.ins().icmp_imm(1252ir::condcodes::IntCC::UnsignedGreaterThanOrEqual,1253capacity,1254ir::immediates::Imm64::new(length.try_into().unwrap()),1255);1256builder.ins().trapz(enough_capacity, TRAP_INTERNAL_ASSERT);1257}12581259fn debug_assert_vmctx_kind(1260&self,1261builder: &mut FunctionBuilder,1262vmctx: ir::Value,1263expected_vmctx_magic: u32,1264) {1265if !self.emit_debug_checks {1266return;1267}1268let magic = builder.ins().load(1269ir::types::I32,1270MemFlags::trusted().with_endianness(self.isa.endianness()),1271vmctx,12720,1273);1274let is_expected_vmctx = builder.ins().icmp_imm(1275ir::condcodes::IntCC::Equal,1276magic,1277i64::from(expected_vmctx_magic),1278);1279builder.ins().trapz(is_expected_vmctx, TRAP_INTERNAL_ASSERT);1280}12811282fn array_to_wasm_trampoline(1283&self,1284trampoline_key: FuncKey,1285callee_key: FuncKey,1286callee_sig: &WasmFuncType,1287symbol: &str,1288vm_store_context_offset: u32,1289expected_vmctx_magic: u32,1290) -> Result<CompiledFunctionBody, CompileError> {1291log::trace!("compiling array-to-wasm trampoline: {trampoline_key:?} = {symbol:?}");12921293let isa = &*self.isa;1294let pointer_type = isa.pointer_type();1295let wasm_call_sig = wasm_call_signature(isa, callee_sig, &self.tunables);1296let array_call_sig = array_call_signature(isa);12971298let mut compiler = self.function_compiler();1299let func = ir::Function::with_name_signature(key_to_name(trampoline_key), array_call_sig);1300let (mut builder, block0) = compiler.builder(func);13011302let try_call_block = builder.create_block();1303builder.ins().jump(try_call_block, []);1304builder.switch_to_block(try_call_block);13051306let (vmctx, caller_vmctx, values_vec_ptr, values_vec_len) = {1307let params = builder.func.dfg.block_params(block0);1308(params[0], params[1], params[2], params[3])1309};13101311// First load the actual arguments out of the array.1312let mut args = self.load_values_from_array(1313callee_sig.params(),1314&mut builder,1315values_vec_ptr,1316values_vec_len,1317);1318args.insert(0, caller_vmctx);1319args.insert(0, vmctx);13201321// Just before we enter Wasm, save our context information.1322//1323// Assert that we were really given a core Wasm vmctx, since that's1324// what we are assuming with our offsets below.1325self.debug_assert_vmctx_kind(&mut builder, vmctx, expected_vmctx_magic);1326save_last_wasm_entry_context(1327&mut builder,1328pointer_type,1329&self.isa.pointer_bytes(),1330vm_store_context_offset,1331vmctx,1332try_call_block,1333);13341335// Create the invocation of wasm, which is notably done with a1336// `try_call` with an exception handler that's used to handle traps.1337let normal_return = builder.create_block();1338let exceptional_return = builder.create_block();1339let normal_return_values = wasm_call_sig1340.returns1341.iter()1342.map(|ty| {1343builder1344.func1345.dfg1346.append_block_param(normal_return, ty.value_type)1347})1348.collect::<Vec<_>>();13491350// Then call the Wasm function with those arguments.1351let signature = builder.func.import_signature(wasm_call_sig.clone());1352let callee = {1353let (namespace, index) = callee_key.into_raw_parts();1354let name = ir::ExternalName::User(1355builder1356.func1357.declare_imported_user_function(ir::UserExternalName { namespace, index }),1358);1359builder.func.dfg.ext_funcs.push(ir::ExtFuncData {1360name,1361signature,1362colocated: true,1363patchable: false,1364})1365};13661367let dfg = &mut builder.func.dfg;1368let exception_table = dfg.exception_tables.push(ir::ExceptionTableData::new(1369signature,1370ir::BlockCall::new(1371normal_return,1372(0..wasm_call_sig.returns.len())1373.map(|i| ir::BlockArg::TryCallRet(i.try_into().unwrap())),1374&mut dfg.value_lists,1375),1376[ir::ExceptionTableItem::Default(ir::BlockCall::new(1377exceptional_return,1378None,1379&mut dfg.value_lists,1380))],1381));1382builder.ins().try_call(callee, &args, exception_table);13831384builder.seal_block(try_call_block);1385builder.seal_block(normal_return);1386builder.seal_block(exceptional_return);13871388// On the normal return path store all the results in the array we were1389// provided and return "true" for "returned successfully".1390builder.switch_to_block(normal_return);1391self.store_values_to_array(1392&mut builder,1393callee_sig.returns(),1394&normal_return_values,1395values_vec_ptr,1396values_vec_len,1397);1398let true_return = builder.ins().iconst(ir::types::I8, 1);1399builder.ins().return_(&[true_return]);14001401// On the exceptional return path just return "false" for "did not1402// succeed". Note that register restoration is part of the `try_call`1403// and handler implementation.1404builder.switch_to_block(exceptional_return);1405let false_return = builder.ins().iconst(ir::types::I8, 0);1406builder.ins().return_(&[false_return]);14071408builder.finalize();14091410Ok(CompiledFunctionBody {1411code: box_dyn_any_compiler_context(Some(compiler.cx)),1412needs_gc_heap: false,1413})1414}1415}14161417struct FunctionCompiler<'a> {1418compiler: &'a Compiler,1419cx: CompilerContext,1420}14211422impl FunctionCompiler<'_> {1423fn builder(&mut self, func: ir::Function) -> (FunctionBuilder<'_>, ir::Block) {1424self.cx.codegen_context.func = func;1425let mut builder = FunctionBuilder::new(1426&mut self.cx.codegen_context.func,1427self.cx.func_translator.context(),1428);14291430let block0 = builder.create_block();1431builder.append_block_params_for_function_params(block0);1432builder.switch_to_block(block0);1433builder.ensure_inserted_block();1434builder.seal_block(block0);1435(builder, block0)1436}14371438fn finish(self, symbol: &str) -> Result<CompiledFunction, CompileError> {1439self.finish_with_info(None, symbol)1440}14411442fn finish_with_info(1443mut self,1444body_and_tunables: Option<(&FunctionBody<'_>, &Tunables)>,1445symbol: &str,1446) -> Result<CompiledFunction, CompileError> {1447let context = &mut self.cx.codegen_context;1448let isa = &*self.compiler.isa;14491450// Run compilation, but don't propagate the error just yet. This'll1451// mutate `context` and the IR contained within (optionally) but it may1452// fail if the backend has a bug in it. Use `context` after this1453// finishes to optionally emit CLIF and then after that's done actually1454// propagate the error if one happened.1455let compilation_result =1456compile_maybe_cached(context, isa, self.cx.incremental_cache_ctx.as_mut());14571458if let Some(path) = &self.compiler.clif_dir {1459use std::io::Write;14601461let mut path = path.join(symbol.replace(":", "-"));1462path.set_extension("clif");14631464let mut output = std::fs::File::create(path).unwrap();1465write!(1466output,1467";; Intermediate Representation of function <{symbol}>:\n",1468)1469.unwrap();1470write!(output, "{}", context.func.display()).unwrap();1471}14721473let compiled_code = compilation_result?;14741475// Give wasm functions, user defined code, a "preferred" alignment1476// instead of the minimum alignment as this can help perf in niche1477// situations.1478let preferred_alignment = if body_and_tunables.is_some() {1479self.compiler.isa.function_alignment().preferred1480} else {148111482};14831484let alignment = compiled_code.buffer.alignment.max(preferred_alignment);1485let mut compiled_function = CompiledFunction::new(1486compiled_code.buffer.clone(),1487context.func.params.user_named_funcs().clone(),1488alignment,1489);14901491if let Some((body, tunables)) = body_and_tunables {1492let data = body.get_binary_reader();1493let offset = data.original_position();1494let len = data.bytes_remaining();1495compiled_function.set_address_map(1496offset.try_into().unwrap(),1497len.try_into().unwrap(),1498tunables.generate_address_map,1499);1500}15011502if isa.flags().unwind_info() {1503let unwind = compiled_code1504.create_unwind_info(isa)1505.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;15061507if let Some(unwind_info) = unwind {1508compiled_function.set_unwind_info(unwind_info);1509}1510}15111512if let Some(builder) = self.cx.debug_slot_descriptor.take() {1513compiled_function.debug_slot_descriptor = Some(builder);1514}15151516if body_and_tunables1517.map(|(_, t)| t.debug_native)1518.unwrap_or(false)1519{1520compiled_function.set_value_labels_ranges(compiled_code.value_labels_ranges.clone());15211522// DWARF debugging needs the CFA-based unwind information even on Windows.1523if !matches!(1524compiled_function.metadata().unwind_info,1525Some(UnwindInfo::SystemV(_))1526) {1527let cfa_unwind = compiled_code1528.create_unwind_info_of_kind(isa, UnwindInfoKind::SystemV)1529.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;15301531if let Some(UnwindInfo::SystemV(cfa_unwind_info)) = cfa_unwind {1532compiled_function.set_cfa_unwind_info(cfa_unwind_info);1533}1534}1535}15361537self.compiler.contexts.lock().unwrap().push(self.cx);15381539Ok(compiled_function)1540}1541}15421543/// Convert from Cranelift's representation of a stack map to Wasmtime's1544/// compiler-agnostic representation.1545///1546/// Here `section` is the wasmtime data section being created and `range` is the1547/// range of the function being added. The `clif_stack_maps` entry is the raw1548/// listing of stack maps from Cranelift.1549fn clif_to_env_stack_maps(1550section: &mut StackMapSection,1551range: Range<u64>,1552clif_stack_maps: &[(CodeOffset, u32, ir::UserStackMap)],1553) {1554for (offset, frame_size, stack_map) in clif_stack_maps {1555let mut frame_offsets = Vec::new();1556for (ty, frame_offset) in stack_map.entries() {1557assert_eq!(ty, ir::types::I32);1558frame_offsets.push(frame_offset);1559}1560let code_offset = range.start + u64::from(*offset);1561assert!(code_offset < range.end);1562section.push(code_offset, *frame_size, frame_offsets.into_iter());1563}1564}15651566/// Convert from Cranelift's representation of exception handler1567/// metadata to Wasmtime's compiler-agnostic representation.1568///1569/// Here `builder` is the wasmtime-unwinder exception section being1570/// created and `range` is the range of the function being added. The1571/// `call_sites` iterator is the raw iterator over callsite metadata1572/// (including exception handlers) from Cranelift.1573fn clif_to_env_exception_tables<'a>(1574builder: &mut ExceptionTableBuilder,1575range: Range<u64>,1576call_sites: impl Iterator<Item = FinalizedMachCallSite<'a>>,1577) -> Result<()> {1578builder.add_func(CodeOffset::try_from(range.start).unwrap(), call_sites)1579}15801581/// Convert from Cranelift's representation of frame state slots and1582/// debug tags to Wasmtime's serialized metadata.1583fn clif_to_env_frame_tables<'a>(1584builder: &mut FrameTableBuilder,1585range: Range<u64>,1586tag_sites: impl Iterator<Item = MachBufferDebugTagList<'a>>,1587frame_layout: &MachBufferFrameLayout,1588frame_descriptors: &HashMap<FuncKey, Vec<u8>>,1589) -> Result<()> {1590let mut frame_descriptor_indices = HashMap::new();1591for tag_site in tag_sites {1592// Split into frames; each has three debug tags.1593let mut frames = vec![];1594for frame_tags in tag_site.tags.chunks_exact(3) {1595let &[1596ir::DebugTag::StackSlot(slot),1597ir::DebugTag::User(wasm_pc),1598ir::DebugTag::User(stack_shape),1599] = frame_tags1600else {1601panic!("Invalid tags");1602};16031604let func_key = frame_layout.stackslots[slot]1605.key1606.expect("Key must be present on stackslot used as state slot")1607.bits();1608let func_key = FuncKey::from_raw_u64(func_key);1609let frame_descriptor = *frame_descriptor_indices.entry(slot).or_insert_with(|| {1610let slot_to_fp_offset =1611frame_layout.frame_to_fp_offset - frame_layout.stackslots[slot].offset;1612let descriptor = frame_descriptors1613.get(&func_key)1614.expect("frame descriptor not present for FuncKey");1615builder.add_frame_descriptor(slot_to_fp_offset, &descriptor)1616});16171618frames.push((1619wasm_pc,1620frame_descriptor,1621FrameStackShape::from_raw(stack_shape),1622));1623}16241625let native_pc_in_code_section = u32::try_from(range.start)1626.unwrap()1627.checked_add(tag_site.offset)1628.unwrap();1629let pos = match tag_site.pos {1630MachDebugTagPos::Post => FrameInstPos::Post,1631MachDebugTagPos::Pre => FrameInstPos::Pre,1632};1633builder.add_program_point(native_pc_in_code_section, pos, &frames);1634}16351636Ok(())1637}16381639/// Convert from Cranelift's representation of breakpoint patches to1640/// Wasmtime's serialized metadata.1641fn clif_to_env_breakpoints(1642range: Range<u64>,1643breakpoint_patches: impl Iterator<Item = (u32, Range<u32>)>,1644patch_table: &mut Vec<(u32, Range<u32>)>,1645) -> Result<()> {1646patch_table.extend(breakpoint_patches.map(|(wasm_pc, offset_range)| {1647let start = offset_range.start + u32::try_from(range.start).unwrap();1648let end = offset_range.end + u32::try_from(range.start).unwrap();1649(wasm_pc, start..end)1650}));1651Ok(())1652}16531654fn save_last_wasm_entry_context(1655builder: &mut FunctionBuilder,1656pointer_type: ir::Type,1657ptr_size: &dyn PtrSize,1658vm_store_context_offset: u32,1659vmctx: Value,1660block: ir::Block,1661) {1662// First we need to get the `VMStoreContext`.1663let vm_store_context = builder.ins().load(1664pointer_type,1665MemFlags::trusted(),1666vmctx,1667i32::try_from(vm_store_context_offset).unwrap(),1668);16691670// Save the current fp/sp of the entry trampoline into the `VMStoreContext`.1671let fp = builder.ins().get_frame_pointer(pointer_type);1672builder.ins().store(1673MemFlags::trusted(),1674fp,1675vm_store_context,1676ptr_size.vmstore_context_last_wasm_entry_fp(),1677);1678let sp = builder.ins().get_stack_pointer(pointer_type);1679builder.ins().store(1680MemFlags::trusted(),1681sp,1682vm_store_context,1683ptr_size.vmstore_context_last_wasm_entry_sp(),1684);16851686// Also save the address of this function's exception handler. This is used1687// as a resumption point for traps, for example.1688let trap_handler = builder1689.ins()1690.get_exception_handler_address(pointer_type, block, 0);1691builder.ins().store(1692MemFlags::trusted(),1693trap_handler,1694vm_store_context,1695ptr_size.vmstore_context_last_wasm_entry_trap_handler(),1696);1697}16981699fn save_last_wasm_exit_fp_and_pc(1700builder: &mut FunctionBuilder,1701pointer_type: ir::Type,1702ptr: &impl PtrSize,1703limits: Value,1704) {1705// Save the trampoline FP to the limits. Exception unwind needs1706// this so that it can know the SP (bottom of frame) for the very1707// last Wasm frame.1708let trampoline_fp = builder.ins().get_frame_pointer(pointer_type);1709builder.ins().store(1710MemFlags::trusted(),1711trampoline_fp,1712limits,1713ptr.vmstore_context_last_wasm_exit_trampoline_fp(),1714);17151716// Finally save the Wasm return address to the limits.1717let wasm_pc = builder.ins().get_return_address(pointer_type);1718builder.ins().store(1719MemFlags::trusted(),1720wasm_pc,1721limits,1722ptr.vmstore_context_last_wasm_exit_pc(),1723);1724}17251726fn key_to_name(key: FuncKey) -> ir::UserFuncName {1727let (namespace, index) = key.into_raw_parts();1728ir::UserFuncName::User(ir::UserExternalName { namespace, index })1729}173017311732