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