Skip to main content

tinywasm_types/
lib.rs

1#![doc(test(
2    no_crate_inject,
3    attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables))
4))]
5#![warn(rust_2018_idioms, unreachable_pub)]
6#![no_std]
7#![deny(unsafe_code)]
8
9//! Types used by [`tinywasm`](https://docs.rs/tinywasm) and [`tinywasm_parser`](https://docs.rs/tinywasm_parser).
10
11extern crate alloc;
12use alloc::{boxed::Box, sync::Arc};
13use core::ops::{Deref, Range};
14
15// Memory defaults
16const MEM_PAGE_SIZE: u64 = 65536;
17const MAX_MEMORY_SIZE: u64 = 4294967296;
18
19const fn max_page_count(page_size: u64) -> u64 {
20    MAX_MEMORY_SIZE / page_size
21}
22
23mod instructions;
24mod reference;
25mod value;
26pub use instructions::*;
27pub use reference::*;
28pub use value::*;
29
30#[cfg(feature = "archive")]
31pub mod archive;
32
33#[cfg(not(feature = "archive"))]
34pub mod archive {
35    #[derive(Debug, PartialEq, Eq)]
36    pub enum TwasmError {}
37    impl core::fmt::Display for TwasmError {
38        fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
39            Err(core::fmt::Error)
40        }
41    }
42    impl core::error::Error for TwasmError {}
43}
44
45/// A `TinyWasm` WebAssembly Module
46///
47/// This is the internal representation of a WebAssembly module in `TinyWasm`.
48/// Modules produced by the parser are validated by default, but validation can be
49/// disabled for trusted input. Do not trust modules or archives from third parties.
50#[derive(Clone, Default, PartialEq)]
51#[cfg_attr(feature = "debug", derive(Debug))]
52#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
53pub struct Module(Arc<ModuleInner>);
54
55impl From<ModuleInner> for Module {
56    fn from(inner: ModuleInner) -> Self {
57        Self(Arc::new(inner))
58    }
59}
60
61impl Deref for Module {
62    type Target = ModuleInner;
63
64    fn deref(&self) -> &ModuleInner {
65        &self.0
66    }
67}
68
69#[doc(hidden)]
70#[derive(Clone, Default, PartialEq)]
71#[cfg_attr(feature = "debug", derive(Debug))]
72#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
73pub struct ModuleInner {
74    /// Optional address of the start function
75    ///
76    /// Corresponds to the `start` section of the original WebAssembly module.
77    pub start_func: Option<FuncAddr>,
78
79    /// Optimized WebAssembly functions
80    ///
81    /// Contains data from to the `code`, `func`, and `type` sections of the original WebAssembly module.
82    pub funcs: Box<[Arc<WasmFunction>]>,
83
84    /// A vector of type definitions, indexed by `TypeAddr`
85    ///
86    /// Corresponds to the `type` section of the original WebAssembly module.
87    pub func_types: Arc<[Arc<FuncType>]>,
88
89    /// Function index to type index mapping in module index space, including imports.
90    pub func_type_idxs: Arc<[u32]>,
91
92    /// Exported items of the WebAssembly module.
93    ///
94    /// Corresponds to the `export` section of the original WebAssembly module.
95    pub exports: Arc<[Export]>,
96
97    /// Global components of the WebAssembly module.
98    ///
99    /// Corresponds to the `global` section of the original WebAssembly module.
100    pub globals: Box<[Global]>,
101
102    /// Table components of the WebAssembly module used to initialize tables.
103    ///
104    /// Corresponds to the `table` section of the original WebAssembly module.
105    pub table_types: Box<[TableType]>,
106
107    /// Memory components of the WebAssembly module used to initialize memories.
108    ///
109    /// Corresponds to the `memory` section of the original WebAssembly module.
110    pub memory_types: Box<[MemoryType]>,
111
112    /// Imports of the WebAssembly module.
113    ///
114    /// Corresponds to the `import` section of the original WebAssembly module.
115    pub imports: Box<[Import]>,
116
117    /// Data segments of the WebAssembly module.
118    ///
119    /// Corresponds to the `data` section of the original WebAssembly module.
120    pub data: Box<[Data]>,
121
122    /// Element segments of the WebAssembly module.
123    ///
124    /// Corresponds to the `elem` section of the original WebAssembly module.
125    pub elements: Box<[Element]>,
126
127    /// How instantiation should prepare the module's local memories.
128    pub local_memory_allocation: LocalMemoryAllocation,
129}
130
131impl Module {
132    /// Returns an iterator over the module's import descriptors.
133    ///
134    /// The returned data mirrors the module's import section and preserves order.
135    pub fn imports(&self) -> impl Iterator<Item = ModuleImport<'_>> {
136        self.0.imports.iter().filter_map(|import| {
137            let ty = match &import.kind {
138                ImportKind::Function(type_idx) => Some(ImportType::Func(self.0.func_types.get(*type_idx as usize)?)),
139                ImportKind::Table(table_ty) => Some(ImportType::Table(table_ty)),
140                ImportKind::Memory(memory_ty) => Some(ImportType::Memory(memory_ty)),
141                ImportKind::Global(global_ty) => Some(ImportType::Global(global_ty)),
142            }?;
143
144            Some(ModuleImport { module: import.module.as_ref(), name: import.name.as_ref(), ty })
145        })
146    }
147
148    /// Returns an iterator over the module's export descriptors.
149    ///
150    /// The returned data mirrors the module's export section and preserves order.
151    pub fn exports(&self) -> impl Iterator<Item = ModuleExport<'_>> {
152        fn imported_count(module: &ModuleInner, kind: ExternalKind) -> usize {
153            module
154                .imports
155                .iter()
156                .filter(|import| {
157                    matches!(
158                        (kind, &import.kind),
159                        (ExternalKind::Func, ImportKind::Function(_))
160                            | (ExternalKind::Table, ImportKind::Table(_))
161                            | (ExternalKind::Memory, ImportKind::Memory(_))
162                            | (ExternalKind::Global, ImportKind::Global(_))
163                    )
164                })
165                .count()
166        }
167
168        fn imported_type(module: &ModuleInner, kind: ExternalKind, index: usize) -> Option<ExportType<'_>> {
169            let mut imports = module.imports.iter().filter(|import| {
170                matches!(
171                    (kind, &import.kind),
172                    (ExternalKind::Func, ImportKind::Function(_))
173                        | (ExternalKind::Table, ImportKind::Table(_))
174                        | (ExternalKind::Memory, ImportKind::Memory(_))
175                        | (ExternalKind::Global, ImportKind::Global(_))
176                )
177            });
178            let import = imports.nth(index)?;
179
180            match &import.kind {
181                ImportKind::Function(type_idx) => Some(ExportType::Func(module.func_types.get(*type_idx as usize)?)),
182                ImportKind::Table(table_ty) => Some(ExportType::Table(table_ty)),
183                ImportKind::Memory(memory_ty) => Some(ExportType::Memory(memory_ty)),
184                ImportKind::Global(global_ty) => Some(ExportType::Global(global_ty)),
185            }
186        }
187
188        self.0.exports.iter().filter_map(move |export| {
189            let idx = export.index as usize;
190            let ty = match export.kind {
191                ExternalKind::Func => {
192                    let imported_funcs = imported_count(&self.0, ExternalKind::Func);
193                    if idx < imported_funcs {
194                        imported_type(&self.0, ExternalKind::Func, idx)?
195                    } else {
196                        ExportType::Func(&self.0.funcs.get(idx - imported_funcs)?.ty)
197                    }
198                }
199                ExternalKind::Table => {
200                    let imported_tables = imported_count(&self.0, ExternalKind::Table);
201                    if idx < imported_tables {
202                        imported_type(&self.0, ExternalKind::Table, idx)?
203                    } else {
204                        ExportType::Table(self.0.table_types.get(idx - imported_tables)?)
205                    }
206                }
207                ExternalKind::Memory => {
208                    let imported_memories = imported_count(&self.0, ExternalKind::Memory);
209                    if idx < imported_memories {
210                        imported_type(&self.0, ExternalKind::Memory, idx)?
211                    } else {
212                        ExportType::Memory(self.0.memory_types.get(idx - imported_memories)?)
213                    }
214                }
215                ExternalKind::Global => {
216                    let imported_globals = imported_count(&self.0, ExternalKind::Global);
217                    if idx < imported_globals {
218                        imported_type(&self.0, ExternalKind::Global, idx)?
219                    } else {
220                        ExportType::Global(&self.0.globals.get(idx - imported_globals)?.ty)
221                    }
222                }
223            };
224
225            Some(ModuleExport { name: export.name.as_ref(), ty })
226        })
227    }
228}
229
230/// A module export descriptor.
231pub struct ModuleExport<'a> {
232    /// Export name.
233    pub name: &'a str,
234    /// Export type.
235    pub ty: ExportType<'a>,
236}
237
238/// A module import descriptor.
239pub struct ModuleImport<'a> {
240    /// Importing module name.
241    pub module: &'a str,
242    /// Import name.
243    pub name: &'a str,
244    /// Import type.
245    pub ty: ImportType<'a>,
246}
247
248/// Imported entity type.
249pub enum ImportType<'a> {
250    /// Imported function type.
251    Func(&'a FuncType),
252    /// Imported table type.
253    Table(&'a TableType),
254    /// Imported memory type.
255    Memory(&'a MemoryType),
256    /// Imported global type.
257    Global(&'a GlobalType),
258}
259
260/// Exported entity type.
261pub enum ExportType<'a> {
262    /// Exported function type.
263    Func(&'a FuncType),
264    /// Exported table type.
265    Table(&'a TableType),
266    /// Exported memory type.
267    Memory(&'a MemoryType),
268    /// Exported global type.
269    Global(&'a GlobalType),
270}
271
272/// How instantiation should prepare local memories declared by the module.
273#[derive(Clone, Copy, PartialEq, Eq, Default)]
274#[cfg_attr(feature = "debug", derive(Debug))]
275#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
276pub enum LocalMemoryAllocation {
277    /// The module's local memories are unobservable and can be skipped entirely.
278    #[default]
279    Skip,
280    /// The module's local memories may be observed through exports, but can be delayed until first use.
281    Lazy,
282    /// The module's local memories must be allocated during instantiation.
283    Eager,
284}
285
286/// A WebAssembly External Kind.
287///
288/// See <https://webassembly.github.io/spec/core/syntax/types.html#external-types>
289#[derive(Clone, Copy, PartialEq, Eq)]
290#[cfg_attr(feature = "debug", derive(Debug))]
291#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
292pub enum ExternalKind {
293    /// A WebAssembly Function.
294    Func,
295    /// A WebAssembly Table.
296    Table,
297    /// A WebAssembly Memory.
298    Memory,
299    /// A WebAssembly Global.
300    Global,
301}
302
303/// A WebAssembly Address.
304///
305/// These are indexes into the respective stores.
306///
307/// See <https://webassembly.github.io/spec/core/exec/runtime.html#addresses>
308pub type Addr = u32;
309
310// aliases for clarity
311pub type FuncAddr = Addr;
312pub type TableAddr = Addr;
313pub type MemAddr = Addr;
314pub type GlobalAddr = Addr;
315pub type ElemAddr = Addr;
316pub type DataAddr = Addr;
317pub type ExternAddr = Addr;
318pub type ConstIdx = Addr;
319
320// additional internal addresses
321pub type TypeAddr = Addr;
322pub type LocalAddr = u16; // there can't be more than 50.000 locals in a function
323pub type ModuleInstanceAddr = Addr;
324
325/// A WebAssembly External Value.
326///
327/// See <https://webassembly.github.io/spec/core/exec/runtime.html#external-values>
328#[derive(Clone)]
329#[cfg_attr(feature = "debug", derive(Debug))]
330pub enum ExternVal {
331    Func(FuncAddr),
332    Table(TableAddr),
333    Memory(MemAddr),
334    Global(GlobalAddr),
335}
336
337impl ExternVal {
338    #[inline]
339    pub const fn kind(&self) -> ExternalKind {
340        match self {
341            Self::Func(_) => ExternalKind::Func,
342            Self::Table(_) => ExternalKind::Table,
343            Self::Memory(_) => ExternalKind::Memory,
344            Self::Global(_) => ExternalKind::Global,
345        }
346    }
347
348    #[inline]
349    pub const fn new(kind: ExternalKind, addr: Addr) -> Self {
350        match kind {
351            ExternalKind::Func => Self::Func(addr),
352            ExternalKind::Table => Self::Table(addr),
353            ExternalKind::Memory => Self::Memory(addr),
354            ExternalKind::Global => Self::Global(addr),
355        }
356    }
357}
358
359/// The type of a WebAssembly Function.
360///
361/// See <https://webassembly.github.io/spec/core/syntax/types.html#function-types>
362#[derive(Clone, PartialEq, Eq, Default)]
363#[cfg_attr(feature = "debug", derive(Debug))]
364#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
365pub struct FuncType {
366    data: Box<[WasmType]>,
367    param_count: u16,
368}
369
370impl FuncType {
371    /// Create a new function type.
372    pub fn new(params: &[WasmType], results: &[WasmType]) -> Self {
373        let data: Box<[WasmType]> = params.iter().cloned().chain(results.iter().cloned()).collect();
374        Self { data, param_count: params.len() as u16 }
375    }
376
377    /// Get the parameter types of this function type.
378    pub fn params(&self) -> &[WasmType] {
379        &self.data[..self.param_count as usize]
380    }
381
382    /// Get the result types of this function type.
383    pub fn results(&self) -> &[WasmType] {
384        &self.data[self.param_count as usize..]
385    }
386}
387
388#[derive(Default, Clone, Copy, PartialEq, Eq)]
389#[cfg_attr(feature = "debug", derive(Debug))]
390#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
391pub struct ValueCounts {
392    pub c32: u16,
393    pub c64: u16,
394    pub c128: u16,
395}
396
397impl ValueCounts {
398    #[inline]
399    pub fn is_empty(&self) -> bool {
400        self.c32 == 0 && self.c64 == 0 && self.c128 == 0
401    }
402}
403
404impl<'a> FromIterator<&'a WasmType> for ValueCounts {
405    #[inline]
406    fn from_iter<I: IntoIterator<Item = &'a WasmType>>(iter: I) -> Self {
407        let mut counts = Self::default();
408
409        for ty in iter {
410            match ty {
411                WasmType::I32 | WasmType::F32 | WasmType::RefExtern | WasmType::RefFunc => counts.c32 += 1,
412                WasmType::I64 | WasmType::F64 => counts.c64 += 1,
413                WasmType::V128 => counts.c128 += 1,
414            }
415        }
416        counts
417    }
418}
419
420#[derive(Clone, PartialEq, Default)]
421#[cfg_attr(feature = "debug", derive(Debug))]
422#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
423pub struct WasmFunction {
424    pub instructions: Box<[Instruction]>,
425    pub data: WasmFunctionData,
426    pub locals: ValueCounts,
427    pub params: ValueCounts,
428    pub results: ValueCounts,
429    pub ty: Arc<FuncType>,
430}
431
432#[derive(Clone, PartialEq, Eq, Default)]
433#[cfg_attr(feature = "debug", derive(Debug))]
434#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
435pub struct WasmFunctionData {
436    pub v128_constants: Box<[[u8; 16]]>,
437    pub branch_table_targets: Box<[u32]>,
438}
439
440impl WasmFunctionData {
441    /// Panics if `idx` is out of bounds.
442    #[inline(always)]
443    pub fn v128_const(&self, idx: ConstIdx) -> [u8; 16] {
444        *self.v128_constants.get(idx as usize).unwrap_or_else(|| unreachable!("invalid v128 constant index: {idx}"))
445    }
446}
447
448/// A WebAssembly Module Export
449#[derive(Clone, PartialEq, Eq)]
450#[cfg_attr(feature = "debug", derive(Debug))]
451#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
452pub struct Export {
453    /// The name of the export.
454    pub name: Box<str>,
455    /// The kind of the export.
456    pub kind: ExternalKind,
457    /// The index of the exported item.
458    pub index: u32,
459}
460
461#[derive(Clone, PartialEq)]
462#[cfg_attr(feature = "debug", derive(Debug))]
463#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
464pub struct Global {
465    pub ty: GlobalType,
466    pub init: Box<[ConstInstruction]>,
467}
468
469#[derive(Clone, Copy, PartialEq, Eq)]
470#[cfg_attr(feature = "debug", derive(Debug))]
471#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
472pub struct GlobalType {
473    pub mutable: bool,
474    pub ty: WasmType,
475}
476
477impl GlobalType {
478    /// Create a new global type.
479    pub const fn new(ty: WasmType, mutable: bool) -> Self {
480        Self { mutable, ty }
481    }
482
483    /// Set a different value type.
484    pub const fn with_ty(mut self, ty: WasmType) -> Self {
485        self.ty = ty;
486        self
487    }
488
489    /// Set global mutability.
490    pub const fn with_mutable(mut self, mutable: bool) -> Self {
491        self.mutable = mutable;
492        self
493    }
494}
495
496impl Default for GlobalType {
497    fn default() -> Self {
498        Self::new(WasmType::I32, false)
499    }
500}
501
502#[derive(Copy, Clone, PartialEq, Eq)]
503#[cfg_attr(feature = "debug", derive(Debug))]
504#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
505pub struct TableType {
506    arch: MemoryArch,
507    pub element_type: WasmType,
508    pub size_initial: u64,
509    pub size_max: Option<u64>,
510}
511
512impl TableType {
513    pub const fn empty() -> Self {
514        Self::new(WasmType::RefFunc, 0, None)
515    }
516
517    /// Create a table with 32-bit indices.
518    pub const fn new(element_type: WasmType, size_initial: u64, size_max: Option<u64>) -> Self {
519        Self { arch: MemoryArch::I32, element_type, size_initial, size_max }
520    }
521
522    /// Create a table with 64-bit indices.
523    pub const fn new64(element_type: WasmType, size_initial: u64, size_max: Option<u64>) -> Self {
524        Self { arch: MemoryArch::I64, element_type, size_initial, size_max }
525    }
526
527    #[inline]
528    pub const fn arch(&self) -> MemoryArch {
529        self.arch
530    }
531}
532
533/// Represents a memory's type.
534#[derive(Copy, Clone, PartialEq, Eq)]
535#[cfg_attr(feature = "debug", derive(Debug))]
536#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
537pub struct MemoryType {
538    arch: MemoryArch,
539    page_count_initial: u64,
540    page_count_max: Option<u64>,
541    page_size: Option<u64>,
542}
543
544impl MemoryType {
545    /// Create a new memory type.
546    pub const fn new(
547        arch: MemoryArch,
548        page_count_initial: u64,
549        page_count_max: Option<u64>,
550        page_size: Option<u64>,
551    ) -> Self {
552        Self { arch, page_count_initial, page_count_max, page_size }
553    }
554
555    #[inline]
556    pub const fn arch(&self) -> MemoryArch {
557        self.arch
558    }
559
560    #[inline]
561    pub const fn page_count_initial(&self) -> u64 {
562        self.page_count_initial
563    }
564
565    #[inline]
566    pub const fn page_count_max(&self) -> u64 {
567        if let Some(page_count_max) = self.page_count_max { page_count_max } else { max_page_count(self.page_size()) }
568    }
569
570    #[inline]
571    pub const fn page_size(&self) -> u64 {
572        if let Some(page_size) = self.page_size { page_size } else { MEM_PAGE_SIZE }
573    }
574
575    #[inline]
576    pub const fn initial_size(&self) -> u64 {
577        self.page_count_initial * self.page_size()
578    }
579
580    #[inline]
581    pub const fn max_size(&self) -> u64 {
582        self.page_count_max() * self.page_size()
583    }
584
585    /// Set a different memory architecture.
586    pub const fn with_arch(mut self, arch: MemoryArch) -> Self {
587        self.arch = arch;
588        self
589    }
590
591    /// Set a different initial page count.
592    pub const fn with_page_count_initial(mut self, page_count_initial: u64) -> Self {
593        self.page_count_initial = page_count_initial;
594        self
595    }
596
597    /// Set a different maximum page count.
598    pub const fn with_page_count_max(mut self, page_count_max: Option<u64>) -> Self {
599        self.page_count_max = page_count_max;
600        self
601    }
602
603    /// Set a different page size.
604    pub const fn with_page_size(mut self, page_size: Option<u64>) -> Self {
605        self.page_size = page_size;
606        self
607    }
608}
609
610impl Default for MemoryType {
611    fn default() -> Self {
612        Self::new(MemoryArch::I32, 0, None, None)
613    }
614}
615
616#[derive(Copy, Clone, PartialEq, Eq, Hash)]
617#[cfg_attr(feature = "debug", derive(Debug))]
618#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
619pub enum MemoryArch {
620    I32,
621    I64,
622}
623
624#[derive(Clone, PartialEq, Eq)]
625#[cfg_attr(feature = "debug", derive(Debug))]
626#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
627pub struct Import {
628    pub module: Box<str>,
629    pub name: Box<str>,
630    pub kind: ImportKind,
631}
632
633#[derive(Clone, PartialEq, Eq)]
634#[cfg_attr(feature = "debug", derive(Debug))]
635#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
636pub enum ImportKind {
637    Function(TypeAddr),
638    Table(TableType),
639    Memory(MemoryType),
640    Global(GlobalType),
641}
642
643impl From<&ImportKind> for ExternalKind {
644    fn from(kind: &ImportKind) -> Self {
645        match kind {
646            ImportKind::Function(_) => Self::Func,
647            ImportKind::Table(_) => Self::Table,
648            ImportKind::Memory(_) => Self::Memory,
649            ImportKind::Global(_) => Self::Global,
650        }
651    }
652}
653
654#[derive(Clone, PartialEq)]
655#[cfg_attr(feature = "debug", derive(Debug))]
656#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
657pub struct Data {
658    pub data: Box<[u8]>,
659    pub range: Range<usize>,
660    pub kind: DataKind,
661}
662
663#[derive(Clone, PartialEq)]
664#[cfg_attr(feature = "debug", derive(Debug))]
665#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
666pub enum DataKind {
667    Active { mem: MemAddr, offset: Box<[ConstInstruction]> },
668    Passive,
669}
670
671#[derive(Clone, PartialEq)]
672#[cfg_attr(feature = "debug", derive(Debug))]
673#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
674pub struct Element {
675    pub kind: ElementKind,
676    pub items: Box<[ElementItem]>,
677    pub range: Range<usize>,
678    pub ty: WasmType,
679}
680
681#[derive(Clone, PartialEq)]
682#[cfg_attr(feature = "debug", derive(Debug))]
683#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
684pub enum ElementKind {
685    Passive,
686    Active { table: TableAddr, offset: Box<[ConstInstruction]> },
687    Declared,
688}
689
690#[derive(Clone, PartialEq)]
691#[cfg_attr(feature = "debug", derive(Debug))]
692#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
693pub enum ElementItem {
694    Func(FuncAddr),
695    Expr(Box<[ConstInstruction]>),
696}