//! Data structures for representing decoded wasm modules.12use crate::prelude::*;3use crate::*;4use alloc::collections::BTreeMap;5use core::ops::Range;6use cranelift_entity::{EntityRef, packed_option::ReservedValue};7use serde_derive::{Deserialize, Serialize};89/// A WebAssembly linear memory initializer.10#[derive(Clone, Debug, Serialize, Deserialize)]11pub struct MemoryInitializer {12/// The index of a linear memory to initialize.13pub memory_index: MemoryIndex,14/// The base offset to start this segment at.15pub offset: ConstExpr,16/// The range of the data to write within the linear memory.17///18/// This range indexes into a separately stored data section which will be19/// provided with the compiled module's code as well.20pub data: Range<u32>,21}2223/// Similar to the above `MemoryInitializer` but only used when memory24/// initializers are statically known to be valid.25#[derive(Clone, Debug, Serialize, Deserialize)]26pub struct StaticMemoryInitializer {27/// The 64-bit offset, in bytes, of where this initializer starts.28pub offset: u64,2930/// The range of data to write at `offset`, where these indices are indexes31/// into the compiled wasm module's data section.32pub data: Range<u32>,33}3435/// The type of WebAssembly linear memory initialization to use for a module.36#[derive(Debug, Serialize, Deserialize)]37pub enum MemoryInitialization {38/// Memory initialization is segmented.39///40/// Segmented initialization can be used for any module, but it is required41/// if:42///43/// * A data segment referenced an imported memory.44/// * A data segment uses a global base.45///46/// Segmented initialization is performed by processing the complete set of47/// data segments when the module is instantiated.48///49/// This is the default memory initialization type.50Segmented(Vec<MemoryInitializer>),5152/// Memory initialization is statically known and involves a single `memcpy`53/// or otherwise simply making the defined data visible.54///55/// To be statically initialized everything must reference a defined memory56/// and all data segments have a statically known in-bounds base (no57/// globals).58///59/// This form of memory initialization is a more optimized version of60/// `Segmented` where memory can be initialized with one of a few methods:61///62/// * First it could be initialized with a single `memcpy` of data from the63/// module to the linear memory.64/// * Otherwise techniques like `mmap` are also possible to make this data,65/// which might reside in a compiled module on disk, available immediately66/// in a linear memory's address space.67///68/// To facilitate the latter of these techniques the `try_static_init`69/// function below, which creates this variant, takes a host page size70/// argument which can page-align everything to make mmap-ing possible.71Static {72/// The initialization contents for each linear memory.73///74/// This array has, for each module's own linear memory, the contents75/// necessary to initialize it. If the memory has a `None` value then no76/// initialization is necessary (it's zero-filled). Otherwise with77/// `Some` the first element of the tuple is the offset in memory to78/// start the initialization and the `Range` is the range within the79/// final data section of the compiled module of bytes to copy into the80/// memory.81///82/// The offset, range base, and range end are all guaranteed to be page83/// aligned to the page size passed in to `try_static_init`.84map: PrimaryMap<MemoryIndex, Option<StaticMemoryInitializer>>,85},86}8788impl Default for MemoryInitialization {89fn default() -> Self {90Self::Segmented(Vec::new())91}92}9394impl MemoryInitialization {95/// Returns whether this initialization is of the form96/// `MemoryInitialization::Segmented`.97pub fn is_segmented(&self) -> bool {98match self {99MemoryInitialization::Segmented(_) => true,100_ => false,101}102}103104/// Performs the memory initialization steps for this set of initializers.105///106/// This will perform wasm initialization in compliance with the wasm spec107/// and how data segments are processed. This doesn't need to necessarily108/// only be called as part of initialization, however, as it's structured to109/// allow learning about memory ahead-of-time at compile time possibly.110///111/// This function will return true if all memory initializers are processed112/// successfully. If any initializer hits an error or, for example, a113/// global value is needed but `None` is returned, then false will be114/// returned. At compile-time this typically means that the "error" in115/// question needs to be deferred to runtime, and at runtime this means116/// that an invalid initializer has been found and a trap should be117/// generated.118pub fn init_memory(&self, state: &mut dyn InitMemory) -> bool {119let initializers = match self {120// Fall through below to the segmented memory one-by-one121// initialization.122MemoryInitialization::Segmented(list) => list,123124// If previously switched to static initialization then pass through125// all those parameters here to the `write` callback.126//127// Note that existence of `Static` already guarantees that all128// indices are in-bounds.129MemoryInitialization::Static { map } => {130for (index, init) in map {131if let Some(init) = init {132let result = state.write(index, init);133if !result {134return result;135}136}137}138return true;139}140};141142for initializer in initializers {143let &MemoryInitializer {144memory_index,145ref offset,146ref data,147} = initializer;148149// First up determine the start/end range and verify that they're150// in-bounds for the initial size of the memory at `memory_index`.151// Note that this can bail if we don't have access to globals yet152// (e.g. this is a task happening before instantiation at153// compile-time).154let start = match state.eval_offset(memory_index, offset) {155Some(start) => start,156None => return false,157};158let len = u64::try_from(data.len()).unwrap();159let end = match start.checked_add(len) {160Some(end) => end,161None => return false,162};163164match state.memory_size_in_bytes(memory_index) {165Ok(max) => {166if end > max {167return false;168}169}170171// Note that computing the minimum can overflow if the page size172// is the default 64KiB and the memory's minimum size in pages173// is `1 << 48`, the maximum number of minimum pages for 64-bit174// memories. We don't return `false` to signal an error here and175// instead defer the error to runtime, when it will be176// impossible to allocate that much memory anyways.177Err(_) => {}178}179180// The limits of the data segment have been validated at this point181// so the `write` callback is called with the range of data being182// written. Any erroneous result is propagated upwards.183let init = StaticMemoryInitializer {184offset: start,185data: data.clone(),186};187let result = state.write(memory_index, &init);188if !result {189return result;190}191}192193return true;194}195}196197/// The various callbacks provided here are used to drive the smaller bits of198/// memory initialization.199pub trait InitMemory {200/// Returns the size, in bytes, of the memory specified. For compile-time201/// purposes this would be the memory type's minimum size.202fn memory_size_in_bytes(&mut self, memory_index: MemoryIndex) -> Result<u64, SizeOverflow>;203204/// Returns the value of the constant expression, as a `u64`. Note that205/// this may involve zero-extending a 32-bit global to a 64-bit number. May206/// return `None` to indicate that the expression involves a value which is207/// not available yet.208fn eval_offset(&mut self, memory_index: MemoryIndex, expr: &ConstExpr) -> Option<u64>;209210/// A callback used to actually write data. This indicates that the211/// specified memory must receive the specified range of data at the212/// specified offset. This can return false on failure.213fn write(&mut self, memory_index: MemoryIndex, init: &StaticMemoryInitializer) -> bool;214}215216/// Table initialization data for all tables in the module.217#[derive(Debug, Default, Serialize, Deserialize)]218pub struct TableInitialization {219/// Initial values for tables defined within the module itself.220///221/// This contains the initial values and initializers for tables defined222/// within a wasm, so excluding imported tables. This initializer can223/// represent null-initialized tables, element-initialized tables (e.g. with224/// the function-references proposal), or precomputed images of table225/// initialization. For example table initializers to a table that are all226/// in-bounds will get removed from `segment` and moved into227/// `initial_values` here.228pub initial_values: PrimaryMap<DefinedTableIndex, TableInitialValue>,229230/// Element segments present in the initial wasm module which are executed231/// at instantiation time.232///233/// These element segments are iterated over during instantiation to apply234/// any segments that weren't already moved into `initial_values` above.235pub segments: Vec<TableSegment>,236}237238/// Initial value for all elements in a table.239#[derive(Clone, Debug, Serialize, Deserialize)]240pub enum TableInitialValue {241/// Initialize each table element to null, optionally setting some elements242/// to non-null given the precomputed image.243Null {244/// A precomputed image of table initializers for this table.245///246/// This image is constructed during `try_func_table_init` and247/// null-initialized elements are represented with248/// `FuncIndex::reserved_value()`. Note that this image is empty by249/// default and may not encompass the entire span of the table in which250/// case the elements are initialized to null.251precomputed: Vec<FuncIndex>,252},253/// An arbitrary const expression.254Expr(ConstExpr),255}256257/// A WebAssembly table initializer segment.258#[derive(Clone, Debug, Serialize, Deserialize)]259pub struct TableSegment {260/// The index of a table to initialize.261pub table_index: TableIndex,262/// The base offset to start this segment at.263pub offset: ConstExpr,264/// The values to write into the table elements.265pub elements: TableSegmentElements,266}267268/// Elements of a table segment, either a list of functions or list of arbitrary269/// expressions.270#[derive(Clone, Debug, Serialize, Deserialize)]271pub enum TableSegmentElements {272/// A sequential list of functions where `FuncIndex::reserved_value()`273/// indicates a null function.274Functions(Box<[FuncIndex]>),275/// Arbitrary expressions, aka either functions, null or a load of a global.276Expressions(Box<[ConstExpr]>),277}278279impl TableSegmentElements {280/// Returns the number of elements in this segment.281pub fn len(&self) -> u64 {282match self {283Self::Functions(s) => u64::try_from(s.len()).unwrap(),284Self::Expressions(s) => u64::try_from(s.len()).unwrap(),285}286}287}288289/// A translated WebAssembly module, excluding the function bodies and290/// memory initializers.291#[derive(Default, Debug, Serialize, Deserialize)]292pub struct Module {293/// The name of this wasm module, often found in the wasm file.294pub name: Option<String>,295296/// All import records, in the order they are declared in the module.297pub initializers: Vec<Initializer>,298299/// Exported entities.300pub exports: IndexMap<String, EntityIndex>,301302/// The module "start" function, if present.303pub start_func: Option<FuncIndex>,304305/// WebAssembly table initialization data, per table.306pub table_initialization: TableInitialization,307308/// WebAssembly linear memory initializer.309pub memory_initialization: MemoryInitialization,310311/// WebAssembly passive elements.312pub passive_elements: Vec<TableSegmentElements>,313314/// The map from passive element index (element segment index space) to index in `passive_elements`.315pub passive_elements_map: BTreeMap<ElemIndex, usize>,316317/// The map from passive data index (data segment index space) to index in `passive_data`.318pub passive_data_map: BTreeMap<DataIndex, Range<u32>>,319320/// Types declared in the wasm module.321pub types: PrimaryMap<TypeIndex, EngineOrModuleTypeIndex>,322323/// Number of imported or aliased functions in the module.324pub num_imported_funcs: usize,325326/// Number of imported or aliased tables in the module.327pub num_imported_tables: usize,328329/// Number of imported or aliased memories in the module.330pub num_imported_memories: usize,331332/// Number of imported or aliased globals in the module.333pub num_imported_globals: usize,334335/// Number of imported or aliased tags in the module.336pub num_imported_tags: usize,337338/// Does this module need a GC heap to run?339pub needs_gc_heap: bool,340341/// Number of functions that "escape" from this module may need to have a342/// `VMFuncRef` constructed for them.343///344/// This is also the number of functions in the `functions` array below with345/// an `func_ref` index (and is the maximum func_ref index).346pub num_escaped_funcs: usize,347348/// Types of functions, imported and local.349pub functions: PrimaryMap<FuncIndex, FunctionType>,350351/// WebAssembly tables.352pub tables: PrimaryMap<TableIndex, Table>,353354/// WebAssembly linear memory plans.355pub memories: PrimaryMap<MemoryIndex, Memory>,356357/// WebAssembly global variables.358pub globals: PrimaryMap<GlobalIndex, Global>,359360/// WebAssembly global initializers for locally-defined globals.361pub global_initializers: PrimaryMap<DefinedGlobalIndex, ConstExpr>,362363/// WebAssembly exception and control tags.364pub tags: PrimaryMap<TagIndex, Tag>,365}366367/// Initialization routines for creating an instance, encompassing imports,368/// modules, instances, aliases, etc.369#[derive(Debug, Serialize, Deserialize)]370pub enum Initializer {371/// An imported item is required to be provided.372Import {373/// Name of this import374name: String,375/// The field name projection of this import376field: String,377/// Where this import will be placed, which also has type information378/// about the import.379index: EntityIndex,380},381}382383impl Module {384/// Allocates the module data structures.385pub fn new() -> Self {386Module::default()387}388389/// Convert a `DefinedFuncIndex` into a `FuncIndex`.390#[inline]391pub fn func_index(&self, defined_func: DefinedFuncIndex) -> FuncIndex {392FuncIndex::new(self.num_imported_funcs + defined_func.index())393}394395/// Convert a `FuncIndex` into a `DefinedFuncIndex`. Returns None if the396/// index is an imported function.397#[inline]398pub fn defined_func_index(&self, func: FuncIndex) -> Option<DefinedFuncIndex> {399if func.index() < self.num_imported_funcs {400None401} else {402Some(DefinedFuncIndex::new(403func.index() - self.num_imported_funcs,404))405}406}407408/// Test whether the given function index is for an imported function.409#[inline]410pub fn is_imported_function(&self, index: FuncIndex) -> bool {411index.index() < self.num_imported_funcs412}413414/// Convert a `DefinedTableIndex` into a `TableIndex`.415#[inline]416pub fn table_index(&self, defined_table: DefinedTableIndex) -> TableIndex {417TableIndex::new(self.num_imported_tables + defined_table.index())418}419420/// Convert a `TableIndex` into a `DefinedTableIndex`. Returns None if the421/// index is an imported table.422#[inline]423pub fn defined_table_index(&self, table: TableIndex) -> Option<DefinedTableIndex> {424if table.index() < self.num_imported_tables {425None426} else {427Some(DefinedTableIndex::new(428table.index() - self.num_imported_tables,429))430}431}432433/// Test whether the given table index is for an imported table.434#[inline]435pub fn is_imported_table(&self, index: TableIndex) -> bool {436index.index() < self.num_imported_tables437}438439/// Convert a `DefinedMemoryIndex` into a `MemoryIndex`.440#[inline]441pub fn memory_index(&self, defined_memory: DefinedMemoryIndex) -> MemoryIndex {442MemoryIndex::new(self.num_imported_memories + defined_memory.index())443}444445/// Convert a `MemoryIndex` into a `DefinedMemoryIndex`. Returns None if the446/// index is an imported memory.447#[inline]448pub fn defined_memory_index(&self, memory: MemoryIndex) -> Option<DefinedMemoryIndex> {449if memory.index() < self.num_imported_memories {450None451} else {452Some(DefinedMemoryIndex::new(453memory.index() - self.num_imported_memories,454))455}456}457458/// Convert a `DefinedMemoryIndex` into an `OwnedMemoryIndex`. Returns None459/// if the index is an imported memory.460#[inline]461pub fn owned_memory_index(&self, memory: DefinedMemoryIndex) -> OwnedMemoryIndex {462assert!(463memory.index() < self.memories.len(),464"non-shared memory must have an owned index"465);466467// Once we know that the memory index is not greater than the number of468// plans, we can iterate through the plans up to the memory index and469// count how many are not shared (i.e., owned).470let owned_memory_index = self471.memories472.iter()473.skip(self.num_imported_memories)474.take(memory.index())475.filter(|(_, mp)| !mp.shared)476.count();477OwnedMemoryIndex::new(owned_memory_index)478}479480/// Test whether the given memory index is for an imported memory.481#[inline]482pub fn is_imported_memory(&self, index: MemoryIndex) -> bool {483index.index() < self.num_imported_memories484}485486/// Convert a `DefinedGlobalIndex` into a `GlobalIndex`.487#[inline]488pub fn global_index(&self, defined_global: DefinedGlobalIndex) -> GlobalIndex {489GlobalIndex::new(self.num_imported_globals + defined_global.index())490}491492/// Convert a `GlobalIndex` into a `DefinedGlobalIndex`. Returns None if the493/// index is an imported global.494#[inline]495pub fn defined_global_index(&self, global: GlobalIndex) -> Option<DefinedGlobalIndex> {496if global.index() < self.num_imported_globals {497None498} else {499Some(DefinedGlobalIndex::new(500global.index() - self.num_imported_globals,501))502}503}504505/// Test whether the given global index is for an imported global.506#[inline]507pub fn is_imported_global(&self, index: GlobalIndex) -> bool {508index.index() < self.num_imported_globals509}510511/// Test whether the given tag index is for an imported tag.512#[inline]513pub fn is_imported_tag(&self, index: TagIndex) -> bool {514index.index() < self.num_imported_tags515}516517/// Convert a `DefinedTagIndex` into a `TagIndex`.518#[inline]519pub fn tag_index(&self, defined_tag: DefinedTagIndex) -> TagIndex {520TagIndex::new(self.num_imported_tags + defined_tag.index())521}522523/// Convert a `TagIndex` into a `DefinedTagIndex`. Returns None if the524/// index is an imported tag.525#[inline]526pub fn defined_tag_index(&self, tag: TagIndex) -> Option<DefinedTagIndex> {527if tag.index() < self.num_imported_tags {528None529} else {530Some(DefinedTagIndex::new(tag.index() - self.num_imported_tags))531}532}533534/// Returns an iterator of all the imports in this module, along with their535/// module name, field name, and type that's being imported.536pub fn imports(&self) -> impl ExactSizeIterator<Item = (&str, &str, EntityType)> {537self.initializers.iter().map(move |i| match i {538Initializer::Import { name, field, index } => {539(name.as_str(), field.as_str(), self.type_of(*index))540}541})542}543544/// Get this module's `i`th import.545pub fn import(&self, i: usize) -> Option<(&str, &str, EntityType)> {546match self.initializers.get(i)? {547Initializer::Import { name, field, index } => Some((name, field, self.type_of(*index))),548}549}550551/// Returns the type of an item based on its index552pub fn type_of(&self, index: EntityIndex) -> EntityType {553match index {554EntityIndex::Global(i) => EntityType::Global(self.globals[i]),555EntityIndex::Table(i) => EntityType::Table(self.tables[i]),556EntityIndex::Memory(i) => EntityType::Memory(self.memories[i]),557EntityIndex::Function(i) => EntityType::Function(self.functions[i].signature),558EntityIndex::Tag(i) => EntityType::Tag(self.tags[i]),559}560}561562/// Appends a new tag to this module with the given type information.563pub fn push_tag(564&mut self,565signature: impl Into<EngineOrModuleTypeIndex>,566exception: impl Into<EngineOrModuleTypeIndex>,567) -> TagIndex {568let signature = signature.into();569let exception = exception.into();570self.tags.push(Tag {571signature,572exception,573})574}575576/// Appends a new function to this module with the given type information,577/// used for functions that either don't escape or aren't certain whether578/// they escape yet.579pub fn push_function(&mut self, signature: impl Into<EngineOrModuleTypeIndex>) -> FuncIndex {580let signature = signature.into();581self.functions.push(FunctionType {582signature,583func_ref: FuncRefIndex::reserved_value(),584})585}586587/// Returns an iterator over all of the defined function indices in this588/// module.589pub fn defined_func_indices(&self) -> impl Iterator<Item = DefinedFuncIndex> + use<> {590(0..self.functions.len() - self.num_imported_funcs).map(|i| DefinedFuncIndex::new(i))591}592593/// Returns the number of tables defined by this module itself: all tables594/// minus imported tables.595pub fn num_defined_tables(&self) -> usize {596self.tables.len() - self.num_imported_tables597}598599/// Returns the number of memories defined by this module itself: all600/// memories minus imported memories.601pub fn num_defined_memories(&self) -> usize {602self.memories.len() - self.num_imported_memories603}604605/// Returns the number of globals defined by this module itself: all606/// globals minus imported globals.607pub fn num_defined_globals(&self) -> usize {608self.globals.len() - self.num_imported_globals609}610611/// Returns the number of tags defined by this module itself: all tags612/// minus imported tags.613pub fn num_defined_tags(&self) -> usize {614self.tags.len() - self.num_imported_tags615}616}617618impl TypeTrace for Module {619fn trace<F, E>(&self, func: &mut F) -> Result<(), E>620where621F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,622{623// NB: Do not `..` elide unmodified fields so that we get compile errors624// when adding new fields that might need re-canonicalization.625let Self {626name: _,627initializers: _,628exports: _,629start_func: _,630table_initialization: _,631memory_initialization: _,632passive_elements: _,633passive_elements_map: _,634passive_data_map: _,635types,636num_imported_funcs: _,637num_imported_tables: _,638num_imported_memories: _,639num_imported_globals: _,640num_imported_tags: _,641num_escaped_funcs: _,642needs_gc_heap: _,643functions,644tables,645memories: _,646globals,647global_initializers: _,648tags,649} = self;650651for t in types.values().copied() {652func(t)?;653}654for f in functions.values() {655f.trace(func)?;656}657for t in tables.values() {658t.trace(func)?;659}660for g in globals.values() {661g.trace(func)?;662}663for t in tags.values() {664t.trace(func)?;665}666Ok(())667}668669fn trace_mut<F, E>(&mut self, func: &mut F) -> Result<(), E>670where671F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,672{673// NB: Do not `..` elide unmodified fields so that we get compile errors674// when adding new fields that might need re-canonicalization.675let Self {676name: _,677initializers: _,678exports: _,679start_func: _,680table_initialization: _,681memory_initialization: _,682passive_elements: _,683passive_elements_map: _,684passive_data_map: _,685types,686num_imported_funcs: _,687num_imported_tables: _,688num_imported_memories: _,689num_imported_globals: _,690num_imported_tags: _,691num_escaped_funcs: _,692needs_gc_heap: _,693functions,694tables,695memories: _,696globals,697global_initializers: _,698tags,699} = self;700701for t in types.values_mut() {702func(t)?;703}704for f in functions.values_mut() {705f.trace_mut(func)?;706}707for t in tables.values_mut() {708t.trace_mut(func)?;709}710for g in globals.values_mut() {711g.trace_mut(func)?;712}713for t in tags.values_mut() {714t.trace_mut(func)?;715}716Ok(())717}718}719720/// Type information about functions in a wasm module.721#[derive(Debug, Serialize, Deserialize)]722pub struct FunctionType {723/// The type of this function, indexed into the module-wide type tables for724/// a module compilation.725pub signature: EngineOrModuleTypeIndex,726/// The index into the funcref table, if present. Note that this is727/// `reserved_value()` if the function does not escape from a module.728pub func_ref: FuncRefIndex,729}730731impl TypeTrace for FunctionType {732fn trace<F, E>(&self, func: &mut F) -> Result<(), E>733where734F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,735{736func(self.signature)737}738739fn trace_mut<F, E>(&mut self, func: &mut F) -> Result<(), E>740where741F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,742{743func(&mut self.signature)744}745}746747impl FunctionType {748/// Returns whether this function's type is one that "escapes" the current749/// module, meaning that the function is exported, used in `ref.func`, used750/// in a table, etc.751pub fn is_escaping(&self) -> bool {752!self.func_ref.is_reserved_value()753}754}755756/// Index into the funcref table within a VMContext for a function.757#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)]758pub struct FuncRefIndex(u32);759cranelift_entity::entity_impl!(FuncRefIndex);760761762