#![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, sync::Arc};
use core::hint::cold_path;
use core::ops::{Deref, Range};
const MEM_PAGE_SIZE: u64 = 65536;
const MAX_MEMORY_SIZE: u64 = 4294967296;
const fn max_page_count(page_size: u64) -> u64 {
MAX_MEMORY_SIZE / page_size
}
#[cfg(feature = "log")]
#[allow(clippy::single_component_path_imports, unused_imports)]
use log;
#[cfg(not(feature = "log"))]
#[allow(unused_imports, unused_macros)]
pub(crate) mod log {
macro_rules! debug ( ($($tt:tt)*) => {{}} );
macro_rules! info ( ($($tt:tt)*) => {{}} );
macro_rules! error ( ($($tt:tt)*) => {{}} );
pub(crate) use debug;
pub(crate) use error;
pub(crate) use info;
}
mod instructions;
mod value;
pub use instructions::*;
pub use value::*;
#[cfg(feature = "archive")]
pub mod archive;
#[cfg(not(feature = "archive"))]
pub mod archive {
#[derive(Debug)]
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(Arc<ModuleInner>);
impl From<ModuleInner> for Module {
fn from(inner: ModuleInner) -> Self {
Self(Arc::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<[Arc<WasmFunction>]>,
pub func_types: Arc<[Arc<FuncType>]>,
pub exports: Arc<[Export]>,
pub globals: Box<[Global]>,
pub table_types: Box<[TableType]>,
pub memory_types: Box<[MemoryType]>,
pub imports: Box<[Import]>,
pub data: Box<[Data]>,
pub elements: Box<[Element]>,
pub local_memory_allocation: LocalMemoryAllocation,
}
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.func_types.get(*type_idx as usize)?)),
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)),
}?;
Some(ModuleImport { module: import.module.as_ref(), name: import.name.as_ref(), ty })
})
}
pub fn exports(&self) -> impl Iterator<Item = ModuleExport<'_>> {
fn imported_func_type(module: &ModuleInner, function_index: usize) -> Option<&FuncType> {
let mut seen = 0usize;
for import in module.imports.iter() {
if let ImportKind::Function(type_idx) = import.kind {
if seen == function_index {
return module.func_types.get(type_idx as usize).map(|ty| &**ty);
}
seen += 1;
}
}
None
}
fn imported_global_type(module: &Module, global_index: usize) -> Option<&GlobalType> {
let mut seen = 0usize;
for import in module.imports.iter() {
if let ImportKind::Global(global_ty) = &import.kind {
if seen == global_index {
return Some(global_ty);
}
seen += 1;
}
}
None
}
self.0.exports.iter().filter_map(move |export| {
let imports = self.0.imports.iter();
let idx = export.index as usize;
let ty = match export.kind {
ExternalKind::Func => {
let imported_funcs =
imports.filter(|import| matches!(import.kind, ImportKind::Function(_))).count();
if idx < imported_funcs {
ExportType::Func(imported_func_type(&self.0, idx)?)
} else {
let local_idx = idx - imported_funcs;
ExportType::Func(&self.0.funcs.get(local_idx)?.ty)
}
}
ExternalKind::Table => ExportType::Table(self.0.table_types.get(idx)?),
ExternalKind::Memory => ExportType::Memory(self.0.memory_types.get(idx)?),
ExternalKind::Global => {
let imported_globals =
imports.filter(|import| matches!(import.kind, ImportKind::Global(_))).count();
if idx < imported_globals {
ExportType::Global(imported_global_type(self, idx)?)
} else {
let local_idx = idx - imported_globals;
ExportType::Global(&self.0.globals.get(local_idx)?.ty)
}
}
};
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),
}
pub enum ExportType<'a> {
Func(&'a FuncType),
Table(&'a TableType),
Memory(&'a MemoryType),
Global(&'a GlobalType),
}
#[derive(Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub enum LocalMemoryAllocation {
#[default]
Skip,
Lazy,
Eager,
}
#[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,
}
pub type Addr = u32;
pub type FuncAddr = Addr;
pub type TableAddr = Addr;
pub type MemAddr = Addr;
pub type GlobalAddr = Addr;
pub type ElemAddr = Addr;
pub type DataAddr = Addr;
pub type ExternAddr = Addr;
pub type ConstIdx = Addr;
pub type TypeAddr = Addr;
pub type LocalAddr = u16; pub type ModuleInstanceAddr = Addr;
#[derive(Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum ExternVal {
Func(FuncAddr),
Table(TableAddr),
Memory(MemAddr),
Global(GlobalAddr),
}
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,
}
}
#[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),
}
}
}
#[derive(Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct FuncType {
data: Box<[WasmType]>,
param_count: u16,
}
impl FuncType {
pub fn new(params: &[WasmType], results: &[WasmType]) -> Self {
let param_count = params.len() as u16;
let data: Box<[WasmType]> = params.iter().cloned().chain(results.iter().cloned()).collect();
Self { data, param_count }
}
pub fn params(&self) -> &[WasmType] {
&self.data[..self.param_count as usize]
}
pub fn results(&self) -> &[WasmType] {
&self.data[self.param_count as usize..]
}
}
#[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 ty {
WasmType::I32 | WasmType::F32 | WasmType::RefExtern | WasmType::RefFunc => counts.c32 += 1,
WasmType::I64 | WasmType::F64 => counts.c64 += 1,
WasmType::V128 => 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,
pub ty: Arc<FuncType>,
}
#[derive(Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct WasmFunctionData {
pub v128_constants: Box<[[u8; 16]]>,
pub branch_table_targets: Box<[u32]>,
}
impl WasmFunctionData {
#[inline(always)]
pub fn v128_const(&self, idx: ConstIdx) -> [u8; 16] {
let Some(val) = self.v128_constants.get(idx as usize) else {
cold_path();
unreachable!("invalid v128 constant index");
};
*val
}
}
#[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(Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct TableType {
pub element_type: WasmType,
pub size_initial: u32,
pub size_max: Option<u32>,
}
impl TableType {
pub fn empty() -> Self {
Self { element_type: WasmType::RefFunc, size_initial: 0, size_max: None }
}
pub fn new(element_type: WasmType, size_initial: u32, size_max: Option<u32>) -> Self {
Self { element_type, size_initial, size_max }
}
}
#[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.page_size()) }
}
#[inline]
pub const fn page_size(&self) -> u64 {
if let Some(page_size) = self.page_size { page_size } else { MEM_PAGE_SIZE }
}
#[inline]
pub const fn initial_size(&self) -> u64 {
self.page_count_initial * self.page_size()
}
#[inline]
pub const fn max_size(&self) -> u64 {
self.page_count_max() * self.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, 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),
}
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,
}
}
}
#[derive(Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[cfg_attr(feature = "archive", derive(serde::Serialize, serde::Deserialize))]
pub struct Data {
pub data: Box<[u8]>,
pub range: Range<usize>,
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<usize>,
pub ty: WasmType,
}
#[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]>),
}