Path: blob/main/crates/environ/src/compile/module_environ.rs
3050 views
use crate::error::{Result, bail};1use crate::module::{2FuncRefIndex, Initializer, MemoryInitialization, MemoryInitializer, Module, TableSegment,3TableSegmentElements,4};5use crate::prelude::*;6use crate::{7ConstExpr, ConstOp, DataIndex, DefinedFuncIndex, ElemIndex, EngineOrModuleTypeIndex,8EntityIndex, EntityType, FuncIndex, FuncKey, GlobalIndex, IndexType, InitMemory, MemoryIndex,9ModuleInternedTypeIndex, ModuleTypesBuilder, PrimaryMap, SizeOverflow, StaticMemoryInitializer,10StaticModuleIndex, TableIndex, TableInitialValue, Tag, TagIndex, Tunables, TypeConvert,11TypeIndex, WasmError, WasmHeapTopType, WasmHeapType, WasmResult, WasmValType,12WasmparserTypeConverter,13};14use cranelift_entity::SecondaryMap;15use cranelift_entity::packed_option::ReservedValue;16use std::borrow::Cow;17use std::collections::HashMap;18use std::mem;19use std::path::PathBuf;20use std::sync::Arc;21use wasmparser::{22CustomSectionReader, DataKind, ElementItems, ElementKind, Encoding, ExternalKind,23FuncToValidate, FunctionBody, KnownCustom, NameSectionReader, Naming, Parser, Payload, TypeRef,24Validator, ValidatorResources, types::Types,25};2627/// Object containing the standalone environment information.28pub struct ModuleEnvironment<'a, 'data> {29/// The current module being translated30result: ModuleTranslation<'data>,3132/// Intern'd types for this entire translation, shared by all modules.33types: &'a mut ModuleTypesBuilder,3435// Various bits and pieces of configuration36validator: &'a mut Validator,37tunables: &'a Tunables,38}3940/// The result of translating via `ModuleEnvironment`.41///42/// Function bodies are not yet translated, and data initializers have not yet43/// been copied out of the original buffer.44pub struct ModuleTranslation<'data> {45/// Module information.46pub module: Module,4748/// The input wasm binary.49///50/// This can be useful, for example, when modules are parsed from a51/// component and the embedder wants access to the raw wasm modules52/// themselves.53pub wasm: &'data [u8],5455/// References to the function bodies.56pub function_body_inputs: PrimaryMap<DefinedFuncIndex, FunctionBodyData<'data>>,5758/// For each imported function, the single statically-known function that59/// always satisfies that import, if any.60///61/// This is used to turn what would otherwise be indirect calls through the62/// imports table into direct calls, when possible.63///64/// When filled in, this only ever contains65/// `FuncKey::DefinedWasmFunction(..)`s and `FuncKey::Intrinsic(..)`s.66pub known_imported_functions: SecondaryMap<FuncIndex, Option<FuncKey>>,6768/// A list of type signatures which are considered exported from this69/// module, or those that can possibly be called. This list is sorted, and70/// trampolines for each of these signatures are required.71pub exported_signatures: Vec<ModuleInternedTypeIndex>,7273/// DWARF debug information, if enabled, parsed from the module.74pub debuginfo: DebugInfoData<'data>,7576/// Set if debuginfo was found but it was not parsed due to `Tunables`77/// configuration.78pub has_unparsed_debuginfo: bool,7980/// List of data segments found in this module which should be concatenated81/// together for the final compiled artifact.82///83/// These data segments, when concatenated, are indexed by the84/// `MemoryInitializer` type.85pub data: Vec<Cow<'data, [u8]>>,8687/// The desired alignment of `data` in the final data section of the object88/// file that we'll emit.89///90/// Note that this is 1 by default but `MemoryInitialization::Static` might91/// switch this to a higher alignment to facilitate mmap-ing data from92/// an object file into a linear memory.93pub data_align: Option<u64>,9495/// Total size of all data pushed onto `data` so far.96total_data: u32,9798/// List of passive element segments found in this module which will get99/// concatenated for the final artifact.100pub passive_data: Vec<&'data [u8]>,101102/// Total size of all passive data pushed into `passive_data` so far.103total_passive_data: u32,104105/// When we're parsing the code section this will be incremented so we know106/// which function is currently being defined.107code_index: u32,108109/// The type information of the current module made available at the end of the110/// validation process.111types: Option<Types>,112}113114impl<'data> ModuleTranslation<'data> {115/// Create a new translation for the module with the given index.116pub fn new(module_index: StaticModuleIndex) -> Self {117Self {118module: Module::new(module_index),119wasm: &[],120function_body_inputs: PrimaryMap::default(),121known_imported_functions: SecondaryMap::default(),122exported_signatures: Vec::default(),123debuginfo: DebugInfoData::default(),124has_unparsed_debuginfo: false,125data: Vec::default(),126data_align: None,127total_data: 0,128passive_data: Vec::default(),129total_passive_data: 0,130code_index: 0,131types: None,132}133}134135/// Returns a reference to the type information of the current module.136pub fn get_types(&self) -> &Types {137self.types138.as_ref()139.expect("module type information to be available")140}141142/// Get this translation's module's index.143pub fn module_index(&self) -> StaticModuleIndex {144self.module.module_index145}146}147148/// Contains function data: byte code and its offset in the module.149pub struct FunctionBodyData<'a> {150/// The body of the function, containing code and locals.151pub body: FunctionBody<'a>,152/// Validator for the function body153pub validator: FuncToValidate<ValidatorResources>,154}155156#[derive(Debug, Default)]157#[expect(missing_docs, reason = "self-describing fields")]158pub struct DebugInfoData<'a> {159pub dwarf: Dwarf<'a>,160pub name_section: NameSection<'a>,161pub wasm_file: WasmFileInfo,162pub debug_loc: gimli::DebugLoc<Reader<'a>>,163pub debug_loclists: gimli::DebugLocLists<Reader<'a>>,164pub debug_ranges: gimli::DebugRanges<Reader<'a>>,165pub debug_rnglists: gimli::DebugRngLists<Reader<'a>>,166pub debug_cu_index: gimli::DebugCuIndex<Reader<'a>>,167pub debug_tu_index: gimli::DebugTuIndex<Reader<'a>>,168}169170#[expect(missing_docs, reason = "self-describing")]171pub type Dwarf<'input> = gimli::Dwarf<Reader<'input>>;172173type Reader<'input> = gimli::EndianSlice<'input, gimli::LittleEndian>;174175#[derive(Debug, Default)]176#[expect(missing_docs, reason = "self-describing fields")]177pub struct NameSection<'a> {178pub module_name: Option<&'a str>,179pub func_names: HashMap<FuncIndex, &'a str>,180pub locals_names: HashMap<FuncIndex, HashMap<u32, &'a str>>,181}182183#[derive(Debug, Default)]184#[expect(missing_docs, reason = "self-describing fields")]185pub struct WasmFileInfo {186pub path: Option<PathBuf>,187pub code_section_offset: u64,188pub imported_func_count: u32,189pub funcs: Vec<FunctionMetadata>,190}191192#[derive(Debug)]193#[expect(missing_docs, reason = "self-describing fields")]194pub struct FunctionMetadata {195pub params: Box<[WasmValType]>,196pub locals: Box<[(u32, WasmValType)]>,197}198199impl<'a, 'data> ModuleEnvironment<'a, 'data> {200/// Allocates the environment data structures.201pub fn new(202tunables: &'a Tunables,203validator: &'a mut Validator,204types: &'a mut ModuleTypesBuilder,205module_index: StaticModuleIndex,206) -> Self {207Self {208result: ModuleTranslation::new(module_index),209types,210tunables,211validator,212}213}214215/// Translate a wasm module using this environment.216///217/// This function will translate the `data` provided with `parser`,218/// validating everything along the way with this environment's validator.219///220/// The result of translation, [`ModuleTranslation`], contains everything221/// necessary to compile functions afterwards as well as learn type222/// information about the module at runtime.223pub fn translate(224mut self,225parser: Parser,226data: &'data [u8],227) -> Result<ModuleTranslation<'data>> {228self.result.wasm = data;229230for payload in parser.parse_all(data) {231self.translate_payload(payload?)?;232}233234Ok(self.result)235}236237fn translate_payload(&mut self, payload: Payload<'data>) -> Result<()> {238match payload {239Payload::Version {240num,241encoding,242range,243} => {244self.validator.version(num, encoding, &range)?;245match encoding {246Encoding::Module => {}247Encoding::Component => {248bail!("expected a WebAssembly module but was given a WebAssembly component")249}250}251}252253Payload::End(offset) => {254self.result.types = Some(self.validator.end(offset)?);255256// With the `escaped_funcs` set of functions finished257// we can calculate the set of signatures that are exported as258// the set of exported functions' signatures.259self.result.exported_signatures = self260.result261.module262.functions263.iter()264.filter_map(|(_, func)| {265if func.is_escaping() {266Some(func.signature.unwrap_module_type_index())267} else {268None269}270})271.collect();272self.result.exported_signatures.sort_unstable();273self.result.exported_signatures.dedup();274}275276Payload::TypeSection(types) => {277self.validator.type_section(&types)?;278279let count = self.validator.types(0).unwrap().core_type_count_in_module();280log::trace!("interning {count} Wasm types");281282let capacity = usize::try_from(count).unwrap();283self.result.module.types.reserve(capacity);284self.types.reserve_wasm_signatures(capacity);285286// Iterate over each *rec group* -- not type -- defined in the287// types section. Rec groups are the unit of canonicalization288// and therefore the unit at which we need to process at a289// time. `wasmparser` has already done the hard work of290// de-duplicating and canonicalizing the rec groups within the291// module for us, we just need to translate them into our data292// structures. Note that, if the Wasm defines duplicate rec293// groups, we need copy the duplicates over (shallowly) as well,294// so that our types index space doesn't have holes.295let mut type_index = 0;296while type_index < count {297let validator_types = self.validator.types(0).unwrap();298299// Get the rec group for the current type index, which is300// always the first type defined in a rec group.301log::trace!("looking up wasmparser type for index {type_index}");302let core_type_id = validator_types.core_type_at_in_module(type_index);303log::trace!(304" --> {core_type_id:?} = {:?}",305validator_types[core_type_id],306);307let rec_group_id = validator_types.rec_group_id_of(core_type_id);308debug_assert_eq!(309validator_types310.rec_group_elements(rec_group_id)311.position(|id| id == core_type_id),312Some(0)313);314315// Intern the rec group and then fill in this module's types316// index space.317let interned = self.types.intern_rec_group(validator_types, rec_group_id)?;318let elems = self.types.rec_group_elements(interned);319let len = elems.len();320self.result.module.types.reserve(len);321for ty in elems {322self.result.module.types.push(ty.into());323}324325// Advance `type_index` to the start of the next rec group.326type_index += u32::try_from(len).unwrap();327}328}329330Payload::ImportSection(imports) => {331self.validator.import_section(&imports)?;332333let cnt = usize::try_from(imports.count()).unwrap();334self.result.module.initializers.reserve(cnt);335336for entry in imports.into_imports() {337let import = entry?;338let ty = match import.ty {339TypeRef::Func(index) => {340let index = TypeIndex::from_u32(index);341let interned_index = self.result.module.types[index];342self.result.module.num_imported_funcs += 1;343self.result.debuginfo.wasm_file.imported_func_count += 1;344EntityType::Function(interned_index)345}346TypeRef::Memory(ty) => {347self.result.module.num_imported_memories += 1;348EntityType::Memory(ty.into())349}350TypeRef::Global(ty) => {351self.result.module.num_imported_globals += 1;352EntityType::Global(self.convert_global_type(&ty)?)353}354TypeRef::Table(ty) => {355self.result.module.num_imported_tables += 1;356EntityType::Table(self.convert_table_type(&ty)?)357}358TypeRef::Tag(ty) => {359let index = TypeIndex::from_u32(ty.func_type_idx);360let signature = self.result.module.types[index];361let exception = self.types.define_exception_type_for_tag(362signature.unwrap_module_type_index(),363);364let tag = Tag {365signature,366exception: EngineOrModuleTypeIndex::Module(exception),367};368self.result.module.num_imported_tags += 1;369EntityType::Tag(tag)370}371TypeRef::FuncExact(_) => {372bail!("custom-descriptors proposal not implemented yet");373}374};375self.declare_import(import.module, import.name, ty);376}377}378379Payload::FunctionSection(functions) => {380self.validator.function_section(&functions)?;381382let cnt = usize::try_from(functions.count()).unwrap();383self.result.module.functions.reserve_exact(cnt);384385for entry in functions {386let sigindex = entry?;387let ty = TypeIndex::from_u32(sigindex);388let interned_index = self.result.module.types[ty];389self.result.module.push_function(interned_index);390}391}392393Payload::TableSection(tables) => {394self.validator.table_section(&tables)?;395let cnt = usize::try_from(tables.count()).unwrap();396self.result.module.tables.reserve_exact(cnt);397398for entry in tables {399let wasmparser::Table { ty, init } = entry?;400let table = self.convert_table_type(&ty)?;401self.result.module.needs_gc_heap |= table.ref_type.is_vmgcref_type();402self.result.module.tables.push(table);403let init = match init {404wasmparser::TableInit::RefNull => TableInitialValue::Null {405precomputed: Vec::new(),406},407wasmparser::TableInit::Expr(expr) => {408let (init, escaped) = ConstExpr::from_wasmparser(self, expr)?;409for f in escaped {410self.flag_func_escaped(f);411}412TableInitialValue::Expr(init)413}414};415self.result416.module417.table_initialization418.initial_values419.push(init);420}421}422423Payload::MemorySection(memories) => {424self.validator.memory_section(&memories)?;425426let cnt = usize::try_from(memories.count()).unwrap();427self.result.module.memories.reserve_exact(cnt);428429for entry in memories {430let memory = entry?;431self.result.module.memories.push(memory.into());432}433}434435Payload::TagSection(tags) => {436self.validator.tag_section(&tags)?;437438for entry in tags {439let sigindex = entry?.func_type_idx;440let ty = TypeIndex::from_u32(sigindex);441let interned_index = self.result.module.types[ty];442let exception = self443.types444.define_exception_type_for_tag(interned_index.unwrap_module_type_index());445self.result.module.push_tag(interned_index, exception);446}447}448449Payload::GlobalSection(globals) => {450self.validator.global_section(&globals)?;451452let cnt = usize::try_from(globals.count()).unwrap();453self.result.module.globals.reserve_exact(cnt);454455for entry in globals {456let wasmparser::Global { ty, init_expr } = entry?;457let (initializer, escaped) = ConstExpr::from_wasmparser(self, init_expr)?;458for f in escaped {459self.flag_func_escaped(f);460}461let ty = self.convert_global_type(&ty)?;462self.result.module.globals.push(ty);463self.result.module.global_initializers.push(initializer);464}465}466467Payload::ExportSection(exports) => {468self.validator.export_section(&exports)?;469470let cnt = usize::try_from(exports.count()).unwrap();471self.result.module.exports.reserve(cnt);472473for entry in exports {474let wasmparser::Export { name, kind, index } = entry?;475let entity = match kind {476ExternalKind::Func | ExternalKind::FuncExact => {477let index = FuncIndex::from_u32(index);478self.flag_func_escaped(index);479EntityIndex::Function(index)480}481ExternalKind::Table => EntityIndex::Table(TableIndex::from_u32(index)),482ExternalKind::Memory => EntityIndex::Memory(MemoryIndex::from_u32(index)),483ExternalKind::Global => EntityIndex::Global(GlobalIndex::from_u32(index)),484ExternalKind::Tag => EntityIndex::Tag(TagIndex::from_u32(index)),485};486self.result487.module488.exports489.insert(String::from(name), entity);490}491}492493Payload::StartSection { func, range } => {494self.validator.start_section(func, &range)?;495496let func_index = FuncIndex::from_u32(func);497self.flag_func_escaped(func_index);498debug_assert!(self.result.module.start_func.is_none());499self.result.module.start_func = Some(func_index);500}501502Payload::ElementSection(elements) => {503self.validator.element_section(&elements)?;504505for (index, entry) in elements.into_iter().enumerate() {506let wasmparser::Element {507kind,508items,509range: _,510} = entry?;511512// Build up a list of `FuncIndex` corresponding to all the513// entries listed in this segment. Note that it's not514// possible to create anything other than a `ref.null515// extern` for externref segments, so those just get516// translated to the reserved value of `FuncIndex`.517let elements = match items {518ElementItems::Functions(funcs) => {519let mut elems =520Vec::with_capacity(usize::try_from(funcs.count()).unwrap());521for func in funcs {522let func = FuncIndex::from_u32(func?);523self.flag_func_escaped(func);524elems.push(func);525}526TableSegmentElements::Functions(elems.into())527}528ElementItems::Expressions(_ty, items) => {529let mut exprs =530Vec::with_capacity(usize::try_from(items.count()).unwrap());531for expr in items {532let (expr, escaped) = ConstExpr::from_wasmparser(self, expr?)?;533exprs.push(expr);534for func in escaped {535self.flag_func_escaped(func);536}537}538TableSegmentElements::Expressions(exprs.into())539}540};541542match kind {543ElementKind::Active {544table_index,545offset_expr,546} => {547let table_index = TableIndex::from_u32(table_index.unwrap_or(0));548let (offset, escaped) = ConstExpr::from_wasmparser(self, offset_expr)?;549debug_assert!(escaped.is_empty());550551self.result552.module553.table_initialization554.segments555.push(TableSegment {556table_index,557offset,558elements,559});560}561562ElementKind::Passive => {563let elem_index = ElemIndex::from_u32(index as u32);564let index = self.result.module.passive_elements.len();565self.result.module.passive_elements.push(elements);566self.result567.module568.passive_elements_map569.insert(elem_index, index);570}571572ElementKind::Declared => {}573}574}575}576577Payload::CodeSectionStart { count, range, .. } => {578self.validator.code_section_start(&range)?;579let cnt = usize::try_from(count).unwrap();580self.result.function_body_inputs.reserve_exact(cnt);581self.result.debuginfo.wasm_file.code_section_offset = range.start as u64;582}583584Payload::CodeSectionEntry(body) => {585let validator = self.validator.code_section_entry(&body)?;586let func_index =587self.result.code_index + self.result.module.num_imported_funcs as u32;588let func_index = FuncIndex::from_u32(func_index);589590if self.tunables.debug_native {591let sig_index = self.result.module.functions[func_index]592.signature593.unwrap_module_type_index();594let sig = self.types[sig_index].unwrap_func();595let mut locals = Vec::new();596for pair in body.get_locals_reader()? {597let (cnt, ty) = pair?;598let ty = self.convert_valtype(ty)?;599locals.push((cnt, ty));600}601self.result602.debuginfo603.wasm_file604.funcs605.push(FunctionMetadata {606locals: locals.into_boxed_slice(),607params: sig.params().into(),608});609}610if self.tunables.debug_guest {611// All functions are potentially reachable and612// callable by the guest debugger, so they must613// all be flagged as escaping.614self.flag_func_escaped(func_index);615}616self.result617.function_body_inputs618.push(FunctionBodyData { validator, body });619self.result.code_index += 1;620}621622Payload::DataSection(data) => {623self.validator.data_section(&data)?;624625let initializers = match &mut self.result.module.memory_initialization {626MemoryInitialization::Segmented(i) => i,627_ => unreachable!(),628};629630let cnt = usize::try_from(data.count()).unwrap();631initializers.reserve_exact(cnt);632self.result.data.reserve_exact(cnt);633634for (index, entry) in data.into_iter().enumerate() {635let wasmparser::Data {636kind,637data,638range: _,639} = entry?;640let mk_range = |total: &mut u32| -> Result<_, WasmError> {641let range = u32::try_from(data.len())642.ok()643.and_then(|size| {644let start = *total;645let end = start.checked_add(size)?;646Some(start..end)647})648.ok_or_else(|| {649WasmError::Unsupported(format!(650"more than 4 gigabytes of data in wasm module",651))652})?;653*total += range.end - range.start;654Ok(range)655};656match kind {657DataKind::Active {658memory_index,659offset_expr,660} => {661let range = mk_range(&mut self.result.total_data)?;662let memory_index = MemoryIndex::from_u32(memory_index);663let (offset, escaped) = ConstExpr::from_wasmparser(self, offset_expr)?;664debug_assert!(escaped.is_empty());665666let initializers = match &mut self.result.module.memory_initialization {667MemoryInitialization::Segmented(i) => i,668_ => unreachable!(),669};670initializers.push(MemoryInitializer {671memory_index,672offset,673data: range,674});675self.result.data.push(data.into());676}677DataKind::Passive => {678let data_index = DataIndex::from_u32(index as u32);679let range = mk_range(&mut self.result.total_passive_data)?;680self.result.passive_data.push(data);681self.result682.module683.passive_data_map684.insert(data_index, range);685}686}687}688}689690Payload::DataCountSection { count, range } => {691self.validator.data_count_section(count, &range)?;692693// Note: the count passed in here is the *total* segment count694// There is no way to reserve for just the passive segments as695// they are discovered when iterating the data section entries696// Given that the total segment count might be much larger than697// the passive count, do not reserve anything here.698}699700Payload::CustomSection(s)701if s.name() == "webidl-bindings" || s.name() == "wasm-interface-types" =>702{703bail!(704"\705Support for interface types has temporarily been removed from `wasmtime`.706707For more information about this temporary change you can read on the issue online:708709https://github.com/bytecodealliance/wasmtime/issues/1271710711and for re-adding support for interface types you can see this issue:712713https://github.com/bytecodealliance/wasmtime/issues/677714"715)716}717718Payload::CustomSection(s) => {719self.register_custom_section(&s);720}721722// It's expected that validation will probably reject other723// payloads such as `UnknownSection` or those related to the724// component model. If, however, something gets past validation then725// that's a bug in Wasmtime as we forgot to implement something.726other => {727self.validator.payload(&other)?;728panic!("unimplemented section in wasm file {other:?}");729}730}731Ok(())732}733734fn register_custom_section(&mut self, section: &CustomSectionReader<'data>) {735match section.as_known() {736KnownCustom::Name(name) => {737let result = self.name_section(name);738if let Err(e) = result {739log::warn!("failed to parse name section {e:?}");740}741}742_ => {743let name = section.name().trim_end_matches(".dwo");744if name.starts_with(".debug_") {745self.dwarf_section(name, section);746}747}748}749}750751fn dwarf_section(&mut self, name: &str, section: &CustomSectionReader<'data>) {752if !self.tunables.debug_native && !self.tunables.parse_wasm_debuginfo {753self.result.has_unparsed_debuginfo = true;754return;755}756let info = &mut self.result.debuginfo;757let dwarf = &mut info.dwarf;758let endian = gimli::LittleEndian;759let data = section.data();760let slice = gimli::EndianSlice::new(data, endian);761762match name {763// `gimli::Dwarf` fields.764".debug_abbrev" => dwarf.debug_abbrev = gimli::DebugAbbrev::new(data, endian),765".debug_addr" => dwarf.debug_addr = gimli::DebugAddr::from(slice),766".debug_info" => {767dwarf.debug_info = gimli::DebugInfo::new(data, endian);768}769".debug_line" => dwarf.debug_line = gimli::DebugLine::new(data, endian),770".debug_line_str" => dwarf.debug_line_str = gimli::DebugLineStr::from(slice),771".debug_str" => dwarf.debug_str = gimli::DebugStr::new(data, endian),772".debug_str_offsets" => dwarf.debug_str_offsets = gimli::DebugStrOffsets::from(slice),773".debug_str_sup" => {774let mut dwarf_sup: Dwarf<'data> = Default::default();775dwarf_sup.debug_str = gimli::DebugStr::from(slice);776dwarf.sup = Some(Arc::new(dwarf_sup));777}778".debug_types" => dwarf.debug_types = gimli::DebugTypes::from(slice),779780// Additional fields.781".debug_loc" => info.debug_loc = gimli::DebugLoc::from(slice),782".debug_loclists" => info.debug_loclists = gimli::DebugLocLists::from(slice),783".debug_ranges" => info.debug_ranges = gimli::DebugRanges::new(data, endian),784".debug_rnglists" => info.debug_rnglists = gimli::DebugRngLists::new(data, endian),785786// DWARF package fields787".debug_cu_index" => info.debug_cu_index = gimli::DebugCuIndex::new(data, endian),788".debug_tu_index" => info.debug_tu_index = gimli::DebugTuIndex::new(data, endian),789790// We don't use these at the moment.791".debug_aranges" | ".debug_pubnames" | ".debug_pubtypes" => return,792other => {793log::warn!("unknown debug section `{other}`");794return;795}796}797798dwarf.ranges = gimli::RangeLists::new(info.debug_ranges, info.debug_rnglists);799dwarf.locations = gimli::LocationLists::new(info.debug_loc, info.debug_loclists);800}801802/// Declares a new import with the `module` and `field` names, importing the803/// `ty` specified.804///805/// Note that this method is somewhat tricky due to the implementation of806/// the module linking proposal. In the module linking proposal two-level807/// imports are recast as single-level imports of instances. That recasting808/// happens here by recording an import of an instance for the first time809/// we see a two-level import.810///811/// When the module linking proposal is disabled, however, disregard this812/// logic and instead work directly with two-level imports since no813/// instances are defined.814fn declare_import(&mut self, module: &'data str, field: &'data str, ty: EntityType) {815let index = self.push_type(ty);816self.result.module.initializers.push(Initializer::Import {817name: module.to_owned(),818field: field.to_owned(),819index,820});821}822823fn push_type(&mut self, ty: EntityType) -> EntityIndex {824match ty {825EntityType::Function(ty) => EntityIndex::Function({826let func_index = self827.result828.module829.push_function(ty.unwrap_module_type_index());830// Imported functions can escape; in fact, they've already done831// so to get here.832self.flag_func_escaped(func_index);833func_index834}),835EntityType::Table(ty) => EntityIndex::Table(self.result.module.tables.push(ty)),836EntityType::Memory(ty) => EntityIndex::Memory(self.result.module.memories.push(ty)),837EntityType::Global(ty) => EntityIndex::Global(self.result.module.globals.push(ty)),838EntityType::Tag(ty) => EntityIndex::Tag(self.result.module.tags.push(ty)),839}840}841842fn flag_func_escaped(&mut self, func: FuncIndex) {843let ty = &mut self.result.module.functions[func];844// If this was already assigned a funcref index no need to re-assign it.845if ty.is_escaping() {846return;847}848let index = self.result.module.num_escaped_funcs as u32;849ty.func_ref = FuncRefIndex::from_u32(index);850self.result.module.num_escaped_funcs += 1;851}852853/// Parses the Name section of the wasm module.854fn name_section(&mut self, names: NameSectionReader<'data>) -> WasmResult<()> {855for subsection in names {856match subsection? {857wasmparser::Name::Function(names) => {858for name in names {859let Naming { index, name } = name?;860// Skip this naming if it's naming a function that861// doesn't actually exist.862if (index as usize) >= self.result.module.functions.len() {863continue;864}865866// Store the name unconditionally, regardless of867// whether we're parsing debuginfo, since function868// names are almost always present in the869// final compilation artifact.870let index = FuncIndex::from_u32(index);871self.result872.debuginfo873.name_section874.func_names875.insert(index, name);876}877}878wasmparser::Name::Module { name, .. } => {879self.result.module.name = Some(name.to_string());880if self.tunables.debug_native {881self.result.debuginfo.name_section.module_name = Some(name);882}883}884wasmparser::Name::Local(reader) => {885if !self.tunables.debug_native {886continue;887}888for f in reader {889let f = f?;890// Skip this naming if it's naming a function that891// doesn't actually exist.892if (f.index as usize) >= self.result.module.functions.len() {893continue;894}895for name in f.names {896let Naming { index, name } = name?;897898self.result899.debuginfo900.name_section901.locals_names902.entry(FuncIndex::from_u32(f.index))903.or_insert(HashMap::new())904.insert(index, name);905}906}907}908wasmparser::Name::Label(_)909| wasmparser::Name::Type(_)910| wasmparser::Name::Table(_)911| wasmparser::Name::Global(_)912| wasmparser::Name::Memory(_)913| wasmparser::Name::Element(_)914| wasmparser::Name::Data(_)915| wasmparser::Name::Tag(_)916| wasmparser::Name::Field(_)917| wasmparser::Name::Unknown { .. } => {}918}919}920Ok(())921}922}923924impl TypeConvert for ModuleEnvironment<'_, '_> {925fn lookup_heap_type(&self, index: wasmparser::UnpackedIndex) -> WasmHeapType {926WasmparserTypeConverter::new(&self.types, |idx| {927self.result.module.types[idx].unwrap_module_type_index()928})929.lookup_heap_type(index)930}931932fn lookup_type_index(&self, index: wasmparser::UnpackedIndex) -> EngineOrModuleTypeIndex {933WasmparserTypeConverter::new(&self.types, |idx| {934self.result.module.types[idx].unwrap_module_type_index()935})936.lookup_type_index(index)937}938}939940impl ModuleTranslation<'_> {941/// Attempts to convert segmented memory initialization into static942/// initialization for the module that this translation represents.943///944/// If this module's memory initialization is not compatible with paged945/// initialization then this won't change anything. Otherwise if it is946/// compatible then the `memory_initialization` field will be updated.947///948/// Takes a `page_size` argument in order to ensure that all949/// initialization is page-aligned for mmap-ability, and950/// `max_image_size_always_allowed` to control how we decide951/// whether to use static init.952///953/// We will try to avoid generating very sparse images, which are954/// possible if e.g. a module has an initializer at offset 0 and a955/// very high offset (say, 1 GiB). To avoid this, we use a dual956/// condition: we always allow images less than957/// `max_image_size_always_allowed`, and the embedder of Wasmtime958/// can set this if desired to ensure that static init should959/// always be done if the size of the module or its heaps is960/// otherwise bounded by the system. We also allow images with961/// static init data bigger than that, but only if it is "dense",962/// defined as having at least half (50%) of its pages with some963/// data.964///965/// We could do something slightly better by building a dense part966/// and keeping a sparse list of outlier/leftover segments (see967/// issue #3820). This would also allow mostly-static init of968/// modules that have some dynamically-placed data segments. But,969/// for now, this is sufficient to allow a system that "knows what970/// it's doing" to always get static init.971pub fn try_static_init(&mut self, page_size: u64, max_image_size_always_allowed: u64) {972// This method only attempts to transform a `Segmented` memory init973// into a `Static` one, no other state.974if !self.module.memory_initialization.is_segmented() {975return;976}977978// First a dry run of memory initialization is performed. This979// collects information about the extent of memory initialized for each980// memory as well as the size of all data segments being copied in.981struct Memory {982data_size: u64,983min_addr: u64,984max_addr: u64,985// The `usize` here is a pointer into `self.data` which is the list986// of data segments corresponding to what was found in the original987// wasm module.988segments: Vec<(usize, StaticMemoryInitializer)>,989}990let mut info = PrimaryMap::with_capacity(self.module.memories.len());991for _ in 0..self.module.memories.len() {992info.push(Memory {993data_size: 0,994min_addr: u64::MAX,995max_addr: 0,996segments: Vec::new(),997});998}9991000struct InitMemoryAtCompileTime<'a> {1001module: &'a Module,1002info: &'a mut PrimaryMap<MemoryIndex, Memory>,1003idx: usize,1004}1005impl InitMemory for InitMemoryAtCompileTime<'_> {1006fn memory_size_in_bytes(1007&mut self,1008memory_index: MemoryIndex,1009) -> Result<u64, SizeOverflow> {1010self.module.memories[memory_index].minimum_byte_size()1011}10121013fn eval_offset(&mut self, memory_index: MemoryIndex, expr: &ConstExpr) -> Option<u64> {1014match (expr.ops(), self.module.memories[memory_index].idx_type) {1015(&[ConstOp::I32Const(offset)], IndexType::I32) => {1016Some(offset.cast_unsigned().into())1017}1018(&[ConstOp::I64Const(offset)], IndexType::I64) => Some(offset.cast_unsigned()),1019_ => None,1020}1021}10221023fn write(&mut self, memory: MemoryIndex, init: &StaticMemoryInitializer) -> bool {1024// Currently `Static` only applies to locally-defined memories,1025// so if a data segment references an imported memory then1026// transitioning to a `Static` memory initializer is not1027// possible.1028if self.module.defined_memory_index(memory).is_none() {1029return false;1030};1031let info = &mut self.info[memory];1032let data_len = u64::from(init.data.end - init.data.start);1033if data_len > 0 {1034info.data_size += data_len;1035info.min_addr = info.min_addr.min(init.offset);1036info.max_addr = info.max_addr.max(init.offset + data_len);1037info.segments.push((self.idx, init.clone()));1038}1039self.idx += 1;1040true1041}1042}1043let ok = self1044.module1045.memory_initialization1046.init_memory(&mut InitMemoryAtCompileTime {1047idx: 0,1048module: &self.module,1049info: &mut info,1050});1051if !ok {1052return;1053}10541055// Validate that the memory information collected is indeed valid for1056// static memory initialization.1057for (i, info) in info.iter().filter(|(_, info)| info.data_size > 0) {1058let image_size = info.max_addr - info.min_addr;10591060// Simplify things for now by bailing out entirely if any memory has1061// a page size smaller than the host's page size. This fixes a case1062// where currently initializers are created in host-page-size units1063// of length which means that a larger-than-the-entire-memory1064// initializer can be created. This can be handled technically but1065// would require some more changes to help fix the assert elsewhere1066// that this protects against.1067if self.module.memories[i].page_size() < page_size {1068return;1069}10701071// If the range of memory being initialized is less than twice the1072// total size of the data itself then it's assumed that static1073// initialization is ok. This means we'll at most double memory1074// consumption during the memory image creation process, which is1075// currently assumed to "probably be ok" but this will likely need1076// tweaks over time.1077if image_size < info.data_size.saturating_mul(2) {1078continue;1079}10801081// If the memory initialization image is larger than the size of all1082// data, then we still allow memory initialization if the image will1083// be of a relatively modest size, such as 1MB here.1084if image_size < max_image_size_always_allowed {1085continue;1086}10871088// At this point memory initialization is concluded to be too1089// expensive to do at compile time so it's entirely deferred to1090// happen at runtime.1091return;1092}10931094// Here's where we've now committed to changing to static memory. The1095// memory initialization image is built here from the page data and then1096// it's converted to a single initializer.1097let data = mem::replace(&mut self.data, Vec::new());1098let mut map = PrimaryMap::with_capacity(info.len());1099let mut module_data_size = 0u32;1100for (memory, info) in info.iter() {1101// Create the in-memory `image` which is the initialized contents of1102// this linear memory.1103let extent = if info.segments.len() > 0 {1104(info.max_addr - info.min_addr) as usize1105} else {110601107};1108let mut image = Vec::with_capacity(extent);1109for (idx, init) in info.segments.iter() {1110let data = &data[*idx];1111assert_eq!(data.len(), init.data.len());1112let offset = usize::try_from(init.offset - info.min_addr).unwrap();1113if image.len() < offset {1114image.resize(offset, 0u8);1115image.extend_from_slice(data);1116} else {1117image.splice(1118offset..(offset + data.len()).min(image.len()),1119data.iter().copied(),1120);1121}1122}1123assert_eq!(image.len(), extent);1124assert_eq!(image.capacity(), extent);1125let mut offset = if info.segments.len() > 0 {1126info.min_addr1127} else {112801129};11301131// Chop off trailing zeros from the image as memory is already1132// zero-initialized. Note that `i` is the position of a nonzero1133// entry here, so to not lose it we truncate to `i + 1`.1134if let Some(i) = image.iter().rposition(|i| *i != 0) {1135image.truncate(i + 1);1136}11371138// Also chop off leading zeros, if any.1139if let Some(i) = image.iter().position(|i| *i != 0) {1140offset += i as u64;1141image.drain(..i);1142}1143let mut len = u64::try_from(image.len()).unwrap();11441145// The goal is to enable mapping this image directly into memory, so1146// the offset into linear memory must be a multiple of the page1147// size. If that's not already the case then the image is padded at1148// the front and back with extra zeros as necessary1149if offset % page_size != 0 {1150let zero_padding = offset % page_size;1151self.data.push(vec![0; zero_padding as usize].into());1152offset -= zero_padding;1153len += zero_padding;1154}1155self.data.push(image.into());1156if len % page_size != 0 {1157let zero_padding = page_size - (len % page_size);1158self.data.push(vec![0; zero_padding as usize].into());1159len += zero_padding;1160}11611162// Offset/length should now always be page-aligned.1163assert!(offset % page_size == 0);1164assert!(len % page_size == 0);11651166// Create the `StaticMemoryInitializer` which describes this image,1167// only needed if the image is actually present and has a nonzero1168// length. The `offset` has been calculates above, originally1169// sourced from `info.min_addr`. The `data` field is the extent1170// within the final data segment we'll emit to an ELF image, which1171// is the concatenation of `self.data`, so here it's the size of1172// the section-so-far plus the current segment we're appending.1173let len = u32::try_from(len).unwrap();1174let init = if len > 0 {1175Some(StaticMemoryInitializer {1176offset,1177data: module_data_size..module_data_size + len,1178})1179} else {1180None1181};1182let idx = map.push(init);1183assert_eq!(idx, memory);1184module_data_size += len;1185}1186self.data_align = Some(page_size);1187self.module.memory_initialization = MemoryInitialization::Static { map };1188}11891190/// Attempts to convert the module's table initializers to1191/// FuncTable form where possible. This enables lazy table1192/// initialization later by providing a one-to-one map of initial1193/// table values, without having to parse all segments.1194pub fn try_func_table_init(&mut self) {1195// This should be large enough to support very large Wasm1196// modules with huge funcref tables, but small enough to avoid1197// OOMs or DoS on truly sparse tables.1198const MAX_FUNC_TABLE_SIZE: u64 = 1024 * 1024;11991200// First convert any element-initialized tables to images of just that1201// single function if the minimum size of the table allows doing so.1202for ((_, init), (_, table)) in self1203.module1204.table_initialization1205.initial_values1206.iter_mut()1207.zip(1208self.module1209.tables1210.iter()1211.skip(self.module.num_imported_tables),1212)1213{1214let table_size = table.limits.min;1215if table_size > MAX_FUNC_TABLE_SIZE {1216continue;1217}1218if let TableInitialValue::Expr(expr) = init {1219if let [ConstOp::RefFunc(f)] = expr.ops() {1220*init = TableInitialValue::Null {1221precomputed: vec![*f; table_size as usize],1222};1223}1224}1225}12261227let mut segments = mem::take(&mut self.module.table_initialization.segments)1228.into_iter()1229.peekable();12301231// The goal of this loop is to interpret a table segment and apply it1232// "statically" to a local table. This will iterate over segments and1233// apply them one-by-one to each table.1234//1235// If any segment can't be applied, however, then this loop exits and1236// all remaining segments are placed back into the segment list. This is1237// because segments are supposed to be initialized one-at-a-time which1238// means that intermediate state is visible with respect to traps. If1239// anything isn't statically known to not trap it's pessimistically1240// assumed to trap meaning all further segment initializers must be1241// applied manually at instantiation time.1242while let Some(segment) = segments.peek() {1243let defined_index = match self.module.defined_table_index(segment.table_index) {1244Some(index) => index,1245// Skip imported tables: we can't provide a preconstructed1246// table for them, because their values depend on the1247// imported table overlaid with whatever segments we have.1248None => break,1249};12501251// If the base of this segment is dynamic, then we can't1252// include it in the statically-built array of initial1253// contents.1254let offset = match segment.offset.ops() {1255&[ConstOp::I32Const(offset)] => u64::from(offset.cast_unsigned()),1256&[ConstOp::I64Const(offset)] => offset.cast_unsigned(),1257_ => break,1258};12591260// Get the end of this segment. If out-of-bounds, or too1261// large for our dense table representation, then skip the1262// segment.1263let top = match offset.checked_add(segment.elements.len()) {1264Some(top) => top,1265None => break,1266};1267let table_size = self.module.tables[segment.table_index].limits.min;1268if top > table_size || top > MAX_FUNC_TABLE_SIZE {1269break;1270}12711272match self.module.tables[segment.table_index]1273.ref_type1274.heap_type1275.top()1276{1277WasmHeapTopType::Func => {}1278// If this is not a funcref table, then we can't support a1279// pre-computed table of function indices. Technically this1280// initializer won't trap so we could continue processing1281// segments, but that's left as a future optimization if1282// necessary.1283WasmHeapTopType::Any1284| WasmHeapTopType::Extern1285| WasmHeapTopType::Cont1286| WasmHeapTopType::Exn => break,1287}12881289// Function indices can be optimized here, but fully general1290// expressions are deferred to get evaluated at runtime.1291let function_elements = match &segment.elements {1292TableSegmentElements::Functions(indices) => indices,1293TableSegmentElements::Expressions(_) => break,1294};12951296let precomputed =1297match &mut self.module.table_initialization.initial_values[defined_index] {1298TableInitialValue::Null { precomputed } => precomputed,12991300// If this table is still listed as an initial value here1301// then that means the initial size of the table doesn't1302// support a precomputed function list, so skip this.1303// Technically this won't trap so it's possible to process1304// further initializers, but that's left as a future1305// optimization.1306TableInitialValue::Expr(_) => break,1307};13081309// At this point we're committing to pre-initializing the table1310// with the `segment` that's being iterated over. This segment is1311// applied to the `precomputed` list for the table by ensuring1312// it's large enough to hold the segment and then copying the1313// segment into the precomputed list.1314if precomputed.len() < top as usize {1315precomputed.resize(top as usize, FuncIndex::reserved_value());1316}1317let dst = &mut precomputed[offset as usize..top as usize];1318dst.copy_from_slice(&function_elements);13191320// advance the iterator to see the next segment1321let _ = segments.next();1322}1323self.module.table_initialization.segments = segments.collect();1324}1325}132613271328