#![doc(test(
no_crate_inject,
attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_assignments, unused_variables))
))]
#![warn(rust_2018_idioms, unreachable_pub)]
#![no_std]
#![deny(unsafe_code)]
extern crate alloc;
use alloc::boxed::Box;
use core::ops::{Deref, Range};
#[cfg(all(not(feature = "portable-atomic"), not(target_has_atomic = "ptr")))]
compile_error!(
"this target does not support atomic operations, enable the `portable-atomic` feature to use `portable-atomic`"
);
#[cfg(not(feature = "portable-atomic"))]
pub use alloc::sync::Arc as Shared;
#[cfg(not(feature = "portable-atomic"))]
pub use alloc::sync::Weak as WeakShared;
#[cfg(feature = "portable-atomic")]
pub use portable_atomic_util::Arc as Shared;
#[cfg(feature = "portable-atomic")]
pub use portable_atomic_util::Weak as WeakShared;
const MEM_PAGE_SIZE: u64 = 65536;
const fn max_page_count(arch: MemoryArch, page_size: u64) -> u64 {
match arch {
MemoryArch::I32 => (1u64 << 32) / page_size,
MemoryArch::I64 => u64::MAX / page_size,
}
}
mod instructions;
mod operands;
mod reference;
mod types;
mod value;
pub use instructions::*;
pub use operands::*;
pub use reference::*;
pub use types::*;
pub use value::*;
#[cfg(feature = "archive")]
pub mod archive;
#[cfg(not(feature = "archive"))]
pub mod archive {
#[derive(Debug, PartialEq, Eq)]
pub enum TwasmError {}
impl core::fmt::Display for TwasmError {
fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Err(core::fmt::Error)
}
}
impl core::error::Error for TwasmError {}
}
#[derive(Clone, Default, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct Module(Shared<ModuleInner>);
impl From<ModuleInner> for Module {
fn from(inner: ModuleInner) -> Self {
Self(Shared::new(inner))
}
}
impl Deref for Module {
type Target = ModuleInner;
fn deref(&self) -> &ModuleInner {
&self.0
}
}
#[doc(hidden)]
#[derive(Clone, Default, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct ModuleInner {
pub start_func: Option<FuncAddr>,
pub funcs: Box<[Shared<WasmFunction>]>,
pub types: TypeSection,
pub func_type_idxs: Box<[TypeAddr]>,
pub exports: Shared<[Export]>,
pub globals: Box<[Global]>,
pub tables: Box<[TableDefinition]>,
pub memory_types: Box<[MemoryType]>,
pub skip_local_memory_allocation: bool,
pub tags: Box<[TagType]>,
pub imports: Box<[Import]>,
pub data: Box<[Data]>,
pub elements: Box<[Element]>,
}
impl Module {
pub fn imports(&self) -> impl Iterator<Item = ModuleImport<'_>> {
self.0.imports.iter().filter_map(|import| {
let ty = match &import.kind {
ImportKind::Function(type_idx) => Some(ImportType::Func(self.0.types.get(*type_idx)?.as_func()?)),
ImportKind::Table(table_ty) => Some(ImportType::Table(table_ty)),
ImportKind::Memory(memory_ty) => Some(ImportType::Memory(memory_ty)),
ImportKind::Global(global_ty) => Some(ImportType::Global(global_ty)),
ImportKind::Tag(tag_ty) => Some(ImportType::Tag(self.0.types.get(tag_ty.type_idx)?.as_func()?)),
}?;
Some(ModuleImport { module: import.module.as_ref(), name: import.name.as_ref(), ty })
})
}
pub fn exports(&self) -> impl Iterator<Item = ModuleExport<'_>> {
fn imported_count(module: &ModuleInner, kind: ExternalKind) -> usize {
module
.imports
.iter()
.filter(|import| {
matches!(
(kind, &import.kind),
(ExternalKind::Func, ImportKind::Function(_))
| (ExternalKind::Table, ImportKind::Table(_))
| (ExternalKind::Memory, ImportKind::Memory(_))
| (ExternalKind::Global, ImportKind::Global(_))
| (ExternalKind::Tag, ImportKind::Tag(_))
)
})
.count()
}
fn imported_type(module: &ModuleInner, kind: ExternalKind, index: usize) -> Option<ExportType<'_>> {
let mut imports = module.imports.iter().filter(|import| {
matches!(
(kind, &import.kind),
(ExternalKind::Func, ImportKind::Function(_))
| (ExternalKind::Table, ImportKind::Table(_))
| (ExternalKind::Memory, ImportKind::Memory(_))
| (ExternalKind::Global, ImportKind::Global(_))
| (ExternalKind::Tag, ImportKind::Tag(_))
)
});
let import = imports.nth(index)?;
match &import.kind {
ImportKind::Function(type_idx) => Some(ExportType::Func(module.types.get(*type_idx)?.as_func()?)),
ImportKind::Table(table_ty) => Some(ExportType::Table(table_ty)),
ImportKind::Memory(memory_ty) => Some(ExportType::Memory(memory_ty)),
ImportKind::Global(global_ty) => Some(ExportType::Global(global_ty)),
ImportKind::Tag(tag_ty) => Some(ExportType::Tag(module.types.get(tag_ty.type_idx)?.as_func()?)),
}
}
self.0.exports.iter().filter_map(move |export| {
let idx = export.index as usize;
let ty = match export.kind {
ExternalKind::Func => {
let imported_funcs = imported_count(&self.0, ExternalKind::Func);
if idx < imported_funcs {
imported_type(&self.0, ExternalKind::Func, idx)?
} else {
let type_idx = *self.0.func_type_idxs.get(idx)?;
ExportType::Func(self.0.types.get(type_idx)?.as_func()?)
}
}
ExternalKind::Table => {
let imported_tables = imported_count(&self.0, ExternalKind::Table);
if idx < imported_tables {
imported_type(&self.0, ExternalKind::Table, idx)?
} else {
ExportType::Table(&self.0.tables.get(idx - imported_tables)?.ty)
}
}
ExternalKind::Memory => {
let imported_memories = imported_count(&self.0, ExternalKind::Memory);
if idx < imported_memories {
imported_type(&self.0, ExternalKind::Memory, idx)?
} else {
ExportType::Memory(self.0.memory_types.get(idx - imported_memories)?)
}
}
ExternalKind::Global => {
let imported_globals = imported_count(&self.0, ExternalKind::Global);
if idx < imported_globals {
imported_type(&self.0, ExternalKind::Global, idx)?
} else {
ExportType::Global(&self.0.globals.get(idx - imported_globals)?.ty)
}
}
ExternalKind::Tag => {
let imported_tags = imported_count(&self.0, ExternalKind::Tag);
if idx < imported_tags {
imported_type(&self.0, ExternalKind::Tag, idx)?
} else {
let tag_ty = self.0.tags.get(idx - imported_tags)?;
ExportType::Tag(self.0.types.get(tag_ty.type_idx)?.as_func()?)
}
}
};
Some(ModuleExport { name: export.name.as_ref(), ty })
})
}
}
pub struct ModuleExport<'a> {
pub name: &'a str,
pub ty: ExportType<'a>,
}
pub struct ModuleImport<'a> {
pub module: &'a str,
pub name: &'a str,
pub ty: ImportType<'a>,
}
pub enum ImportType<'a> {
Func(&'a FuncType),
Table(&'a TableType),
Memory(&'a MemoryType),
Global(&'a GlobalType),
Tag(&'a FuncType),
}
pub enum ExportType<'a> {
Func(&'a FuncType),
Table(&'a TableType),
Memory(&'a MemoryType),
Global(&'a GlobalType),
Tag(&'a FuncType),
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub enum ExternalKind {
Func,
Table,
Memory,
Global,
Tag,
}
pub type Addr = u32;
pub type FuncAddr = Addr;
pub type TableAddr = Addr;
pub type MemAddr = Addr;
pub type GlobalAddr = Addr;
pub type TagAddr = Addr;
pub type ElemAddr = Addr;
pub type DataAddr = Addr;
pub type ExternAddr = Addr;
pub type ConstIdx = Addr;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "archive", serde(transparent))]
pub struct ModuleFuncIdx(u32);
impl ModuleFuncIdx {
#[inline]
pub const fn new(index: u32) -> Self {
Self(index)
}
#[inline]
pub const fn index(self) -> u32 {
self.0
}
}
pub type TypeAddr = Addr;
pub type LocalAddr = u16; pub type ModuleInstanceId = Addr;
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum ExternVal {
Func(FuncAddr),
Table(TableAddr),
Memory(MemAddr),
Global(GlobalAddr),
Tag(TagAddr),
}
impl ExternVal {
#[inline]
pub const fn kind(&self) -> ExternalKind {
match self {
Self::Func(_) => ExternalKind::Func,
Self::Table(_) => ExternalKind::Table,
Self::Memory(_) => ExternalKind::Memory,
Self::Global(_) => ExternalKind::Global,
Self::Tag(_) => ExternalKind::Tag,
}
}
#[inline]
pub const fn new(kind: ExternalKind, addr: Addr) -> Self {
match kind {
ExternalKind::Func => Self::Func(addr),
ExternalKind::Table => Self::Table(addr),
ExternalKind::Memory => Self::Memory(addr),
ExternalKind::Global => Self::Global(addr),
ExternalKind::Tag => Self::Tag(addr),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum ValueLane {
S32,
S64,
S128,
}
impl ValueLane {
pub fn select<T>(self, s32: T, s64: T, s128: T) -> T {
match self {
Self::S32 => s32,
Self::S64 => s64,
Self::S128 => s128,
}
}
}
impl From<&WasmType> for ValueLane {
fn from(ty: &WasmType) -> Self {
match ty {
WasmType::I32 | WasmType::F32 | WasmType::Ref(_) => Self::S32,
WasmType::I64 | WasmType::F64 => Self::S64,
WasmType::V128 => Self::S128,
}
}
}
#[derive(Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct ValueCounts {
pub c32: u16,
pub c64: u16,
pub c128: u16,
}
impl ValueCounts {
#[inline]
pub fn is_empty(&self) -> bool {
self.c32 == 0 && self.c64 == 0 && self.c128 == 0
}
}
impl<'a> FromIterator<&'a WasmType> for ValueCounts {
#[inline]
fn from_iter<I: IntoIterator<Item = &'a WasmType>>(iter: I) -> Self {
let mut counts = Self::default();
for ty in iter {
match ValueLane::from(ty) {
ValueLane::S32 => counts.c32 += 1,
ValueLane::S64 => counts.c64 += 1,
ValueLane::S128 => counts.c128 += 1,
}
}
counts
}
}
#[derive(Clone, PartialEq, Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct WasmFunction {
pub instructions: Box<[Instruction]>,
pub data: WasmFunctionData,
pub locals: ValueCounts,
pub params: ValueCounts,
pub results: ValueCounts,
}
#[derive(Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct WasmFunctionData {
pub operands64: Box<[Operand64]>,
pub operands128: Box<[Operand128]>,
pub branch_table_targets: Box<[u32]>,
pub exception_handlers: Box<[ExceptionHandler]>,
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct Export {
pub name: Box<str>,
pub kind: ExternalKind,
pub index: u32,
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct Global {
pub ty: GlobalType,
pub init: Box<[ConstInstruction]>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct GlobalType {
pub mutable: bool,
pub ty: WasmType,
}
impl GlobalType {
pub const fn new(ty: WasmType, mutable: bool) -> Self {
Self { mutable, ty }
}
pub const fn with_ty(mut self, ty: WasmType) -> Self {
self.ty = ty;
self
}
pub const fn with_mutable(mut self, mutable: bool) -> Self {
self.mutable = mutable;
self
}
}
impl Default for GlobalType {
fn default() -> Self {
Self::new(WasmType::I32, false)
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct TableType {
arch: MemoryArch,
pub element_type: RefType,
pub size_initial: u64,
pub size_max: Option<u64>,
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct TableDefinition {
pub ty: TableType,
pub init: Option<Box<[ConstInstruction]>>,
}
impl TableType {
pub const fn empty() -> Self {
Self::new(RefType::FUNCREF, 0, None)
}
pub const fn new(element_type: RefType, size_initial: u64, size_max: Option<u64>) -> Self {
Self { arch: MemoryArch::I32, element_type, size_initial, size_max }
}
pub const fn new64(element_type: RefType, size_initial: u64, size_max: Option<u64>) -> Self {
Self { arch: MemoryArch::I64, element_type, size_initial, size_max }
}
#[inline]
pub const fn arch(&self) -> MemoryArch {
self.arch
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct MemoryType {
arch: MemoryArch,
page_count_initial: u64,
page_count_max: Option<u64>,
page_size: Option<u64>,
}
impl MemoryType {
pub const fn new(
arch: MemoryArch,
page_count_initial: u64,
page_count_max: Option<u64>,
page_size: Option<u64>,
) -> Self {
Self { arch, page_count_initial, page_count_max, page_size }
}
#[inline]
pub const fn arch(&self) -> MemoryArch {
self.arch
}
#[inline]
pub const fn page_count_initial(&self) -> u64 {
self.page_count_initial
}
#[inline]
pub const fn page_count_max(&self) -> u64 {
if let Some(page_count_max) = self.page_count_max {
page_count_max
} else {
max_page_count(self.arch, self.page_size())
}
}
#[inline]
pub const fn page_count_max_declared(&self) -> Option<u64> {
self.page_count_max
}
#[inline]
pub const fn page_size(&self) -> u64 {
if let Some(page_size) = self.page_size { page_size } else { MEM_PAGE_SIZE }
}
pub const fn with_arch(mut self, arch: MemoryArch) -> Self {
self.arch = arch;
self
}
pub const fn with_page_count_initial(mut self, page_count_initial: u64) -> Self {
self.page_count_initial = page_count_initial;
self
}
pub const fn with_page_count_max(mut self, page_count_max: Option<u64>) -> Self {
self.page_count_max = page_count_max;
self
}
pub const fn with_page_size(mut self, page_size: Option<u64>) -> Self {
self.page_size = page_size;
self
}
}
impl Default for MemoryType {
fn default() -> Self {
Self::new(MemoryArch::I32, 0, None, None)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub enum MemoryArch {
I32,
I64,
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct TagType {
pub type_idx: TypeAddr,
}
impl TagType {
pub const fn new(type_idx: TypeAddr) -> Self {
Self { type_idx }
}
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct Import {
pub module: Box<str>,
pub name: Box<str>,
pub kind: ImportKind,
}
#[derive(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub enum ImportKind {
Function(TypeAddr),
Table(TableType),
Memory(MemoryType),
Global(GlobalType),
Tag(TagType),
}
impl From<&ImportKind> for ExternalKind {
fn from(kind: &ImportKind) -> Self {
match kind {
ImportKind::Function(_) => Self::Func,
ImportKind::Table(_) => Self::Table,
ImportKind::Memory(_) => Self::Memory,
ImportKind::Global(_) => Self::Global,
ImportKind::Tag(_) => Self::Tag,
}
}
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct Data {
pub data: Shared<[u8]>,
pub range: Range<u64>,
pub kind: DataKind,
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub enum DataKind {
Active { mem: MemAddr, offset: Box<[ConstInstruction]> },
Passive,
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct Element {
pub kind: ElementKind,
pub items: Box<[ElementItem]>,
pub range: Range<u64>,
pub ty: RefType,
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub enum ElementKind {
Passive,
Active { table: TableAddr, offset: Box<[ConstInstruction]> },
Declared,
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub enum ElementItem {
Func(FuncAddr),
Expr(Box<[ConstInstruction]>),
}