Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Added support for the WebAssembly function-references proposal
- Added support for the WebAssembly garbage-collection proposal
- Added `WasmValue::ty` and `WasmValue::matches_type`
- Added a `validate` feature to `tinywasm` and `tinywasm-parser` (enabled by default) to optionally skip wasmparser validation for faster parsing of trusted modules.

### Changed

- Function types are now stored separately and resolved through `Function::ty(&Store)`.
- Module types now use one dense recursive type space, while function types are resolved through `Function::ty(&Store)`.

### Fixed

Expand Down
32 changes: 16 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,10 @@ categories = ["compilers", "embedded", "no-std", "virtualization", "wasm"]

[workspace.dependencies]
tinywasm = { path = "crates/tinywasm", version = "0.11.0-pre.0", default-features = false }
tinywasm-cli = { path = "crates/cli", version = "0.11.0-pre.0", default-features = false }
tinywasm-parser = { path = "crates/parser", version = "0.11.0-pre.0", default-features = false }
tinywasm-types = { path = "crates/types", version = "0.11.0-pre.0", default-features = false }

eyre = "0.6"
indexmap = "2.14"
log = "0.4"
owo-colors = { version = "4.3" }
pretty_env_logger = "0.5"
Expand Down
13 changes: 12 additions & 1 deletion crates/cli/src/wast_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,9 @@ enum ExpectedValue {
RefNull,
RefFunc,
RefExtern,
RefAny,
RefEq,
RefI31,
}

impl ExpectedValue {
Expand All @@ -800,6 +803,9 @@ impl ExpectedValue {
Self::RefNull => matches!(value, WasmValue::Ref(RefValue::Null)),
Self::RefFunc => matches!(value, WasmValue::Ref(RefValue::Func(_))),
Self::RefExtern => matches!(value, WasmValue::Ref(RefValue::Extern(_))),
Self::RefAny => matches!(value, WasmValue::Ref(RefValue::Any(_))),
Self::RefEq => matches!(value, WasmValue::Ref(RefValue::Any(value)) if value.as_i31().is_some()),
Self::RefI31 => matches!(value, WasmValue::Ref(RefValue::Any(value)) if value.as_i31().is_some()),
}
}
}
Expand All @@ -817,7 +823,9 @@ fn wastret2tinywasmvalues(ret: wast::WastRet) -> Result<Vec<ExpectedValue>> {
}

fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result<ExpectedValue> {
use wast::core::WastRetCore::{F32, F64, I32, I64, RefExtern, RefFunc, RefNull, V128};
use wast::core::WastRetCore::{
F32, F64, I32, I64, RefAny, RefEq, RefExtern, RefFunc, RefI31, RefI31Shared, RefNull, V128,
};
Ok(match ret {
F32(f) => ExpectedValue::Exact(nanpattern2tinywasmvalue(f)?),
F64(f) => ExpectedValue::Exact(nanpattern2tinywasmvalue(f)?),
Expand All @@ -832,6 +840,9 @@ fn wastretcore2tinywasmvalue(ret: wast::core::WastRetCore) -> Result<ExpectedVal
RefFunc(v) => {
bail!("unsupported arg type: reffunc: {:?}", v);
}
RefAny => ExpectedValue::RefAny,
RefEq => ExpectedValue::RefEq,
RefI31 | RefI31Shared => ExpectedValue::RefI31,
a => {
bail!("unsupported arg type {:?}", a);
}
Expand Down
127 changes: 105 additions & 22 deletions crates/parser/src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ use crate::validation::{FuncValidator, FuncValidatorAllocations, ValidatorResour
#[cfg(feature = "validate")]
use crate::visit::process_operators_and_validate;
use crate::{Result, module::FunctionCode, visit::process_operators};
use alloc::{boxed::Box, format, string::ToString, vec::Vec};
use alloc::{boxed::Box, format, vec::Vec};
use tinywasm_types::*;
use wasmparser::{CompositeInnerType, OperatorsReader, OperatorsReaderAllocations};
use wasmparser::{CompositeInnerType, OperatorsReader, OperatorsReaderAllocations, UnpackedIndex};

pub(crate) fn convert_module_element(element: wasmparser::Element<'_>) -> Result<tinywasm_types::Element> {
let kind = match element.kind {
Expand Down Expand Up @@ -208,39 +208,98 @@ pub(crate) fn convert_module_code(
))
}

pub(crate) fn convert_module_type(ty: wasmparser::RecGroup) -> Result<FuncType> {
let mut types = ty.types();
// TODO(wasm3): Preserve recursive groups and non-function composite types instead of flattening singleton funcs.
if types.len() != 1 {
return Err(crate::ParseError::UnsupportedOperator(
"Expected exactly one type in the type section".to_string(),
));
pub(crate) fn convert_rec_group(ty: wasmparser::RecGroup, group_start: u32, types: &mut Vec<SubType>) -> Result<u32> {
let group_len = u32::try_from(ty.types().len())
.map_err(|_| crate::ParseError::Other("recursive type group is too large".into()))?;
types.reserve(group_len as usize);
for ty in ty.into_types() {
let composite = &ty.composite_type;
if composite.shared {
return Err(crate::ParseError::UnsupportedOperator("shared composite types are unsupported".into()));
}
if composite.descriptor_idx.is_some() || composite.describes_idx.is_some() {
return Err(crate::ParseError::UnsupportedOperator("descriptor types are unsupported".into()));
}

let supertype =
ty.supertype_idx.map(|idx| convert_type_index(idx.unpack(), group_start, group_len)).transpose()?;
let composite = match &composite.inner {
CompositeInnerType::Func(ty) => {
let params = ty
.params()
.iter()
.map(|ty| convert_valtype_in_group(ty, group_start, group_len))
.collect::<Result<Vec<_>>>()?;
let results = ty
.results()
.iter()
.map(|ty| convert_valtype_in_group(ty, group_start, group_len))
.collect::<Result<Vec<_>>>()?;
CompositeType::Func(FuncType::new(&params, &results))
}
CompositeInnerType::Struct(ty) => CompositeType::Struct(StructType {
fields: ty
.fields
.iter()
.map(|field| convert_field_type(field, group_start, group_len))
.collect::<Result<_>>()?,
}),
CompositeInnerType::Array(ty) => {
CompositeType::Array(ArrayType { field: convert_field_type(&ty.0, group_start, group_len)? })
}
CompositeInnerType::Cont(_) => {
return Err(crate::ParseError::UnsupportedOperator("continuation types are unsupported".into()));
}
};
types.push(SubType { is_final: ty.is_final, supertype, composite });
}
Ok(group_len)
}

fn convert_type_index(index: UnpackedIndex, group_start: u32, group_len: u32) -> Result<TypeAddr> {
match index {
UnpackedIndex::Module(index) => Ok(index),
UnpackedIndex::RecGroup(index) if index < group_len => {
group_start.checked_add(index).ok_or_else(|| crate::ParseError::Other("type index is too large".into()))
}
UnpackedIndex::RecGroup(index) => {
Err(crate::ParseError::Other(format!("recursive group type index out of bounds: {index}")))
}
_ => Err(crate::ParseError::UnsupportedOperator(format!("unsupported canonical type index: {index}"))),
}
}

let ty = types.next().unwrap();
let CompositeInnerType::Func(ty) = &ty.composite_type.inner else {
return Err(crate::ParseError::UnsupportedOperator(format!(
"Unsupported non-function type in type section: {}",
ty.composite_type
)));
fn convert_field_type(field: &wasmparser::FieldType, group_start: u32, group_len: u32) -> Result<FieldType> {
let storage = match &field.element_type {
wasmparser::StorageType::I8 => StorageType::I8,
wasmparser::StorageType::I16 => StorageType::I16,
wasmparser::StorageType::Val(ty) => StorageType::Value(convert_valtype_in_group(ty, group_start, group_len)?),
};
let params = ty.params().iter().map(convert_valtype).collect::<Result<Vec<_>>>()?;
let results = ty.results().iter().map(convert_valtype).collect::<Result<Vec<_>>>()?;
Ok(FuncType::new(&params, &results))
Ok(FieldType { storage, mutable: field.mutable })
}

pub(crate) fn convert_ref_type(ty: wasmparser::RefType) -> Result<RefType> {
convert_heap_type(ty.heap_type(), ty.is_nullable())
}

pub(crate) fn convert_valtype(valtype: &wasmparser::ValType) -> Result<WasmType> {
convert_valtype_with_group(valtype, None)
}

fn convert_valtype_in_group(valtype: &wasmparser::ValType, group_start: u32, group_len: u32) -> Result<WasmType> {
convert_valtype_with_group(valtype, Some((group_start, group_len)))
}

fn convert_valtype_with_group(valtype: &wasmparser::ValType, group: Option<(u32, u32)>) -> Result<WasmType> {
match valtype {
wasmparser::ValType::I32 => Ok(WasmType::I32),
wasmparser::ValType::I64 => Ok(WasmType::I64),
wasmparser::ValType::F32 => Ok(WasmType::F32),
wasmparser::ValType::F64 => Ok(WasmType::F64),
wasmparser::ValType::V128 => Ok(WasmType::V128),
wasmparser::ValType::Ref(r) => Ok(WasmType::Ref(convert_ref_type(*r)?)),
wasmparser::ValType::Ref(r) => {
Ok(WasmType::Ref(convert_heap_type_with_group(r.heap_type(), r.is_nullable(), group)?))
}
}
}

Expand All @@ -265,6 +324,9 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result<Box<[C
wasmparser::Operator::RefFunc { function_index } => {
ConstInstruction::Ref(RefValue::Func(FuncRef::new(function_index)))
}
wasmparser::Operator::RefI31 => ConstInstruction::RefI31,
wasmparser::Operator::AnyConvertExtern => ConstInstruction::AnyConvertExtern,
wasmparser::Operator::ExternConvertAny => ConstInstruction::ExternConvertAny,
wasmparser::Operator::I32Const { value } => ConstInstruction::I32Const(value),
wasmparser::Operator::I64Const { value } => ConstInstruction::I64Const(value),
wasmparser::Operator::F32Const { value } => ConstInstruction::F32Const(f32::from_bits(value.bits())),
Expand Down Expand Up @@ -294,6 +356,14 @@ pub(crate) fn process_const_operators(ops: OperatorsReader<'_>) -> Result<Box<[C
}

pub(crate) fn convert_heap_type(heap: wasmparser::HeapType, nullable: bool) -> Result<RefType> {
convert_heap_type_with_group(heap, nullable, None)
}

fn convert_heap_type_with_group(
heap: wasmparser::HeapType,
nullable: bool,
group: Option<(u32, u32)>,
) -> Result<RefType> {
match heap {
wasmparser::HeapType::Abstract { shared: false, ty } => Ok(RefType::new_abstract(
nullable,
Expand All @@ -316,9 +386,22 @@ pub(crate) fn convert_heap_type(heap: wasmparser::HeapType, nullable: bool) -> R
},
)),
wasmparser::HeapType::Concrete(index) => {
let index = index.as_module_index().ok_or_else(|| {
crate::ParseError::UnsupportedOperator(format!("Unsupported non-module heap type index: {index:?}"))
})?;
let index = match index {
UnpackedIndex::Module(index) => index,
index @ UnpackedIndex::RecGroup(_) => {
let (group_start, group_len) = group.ok_or_else(|| {
crate::ParseError::UnsupportedOperator(format!(
"recursive-group heap type outside a type group: {index}"
))
})?;
convert_type_index(index, group_start, group_len)?
}
index => {
return Err(crate::ParseError::UnsupportedOperator(format!(
"unsupported canonical heap type index: {index}"
)));
}
};
RefType::new_concrete(nullable, index)
.ok_or_else(|| crate::ParseError::Other(format!("heap type index is too large: {index}")))
}
Expand Down
1 change: 1 addition & 0 deletions crates/parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ impl Parser {
| WasmFeatures::BULK_MEMORY_OPT
| WasmFeatures::RELAXED_SIMD
| WasmFeatures::GC_TYPES
| WasmFeatures::GC
| WasmFeatures::REFERENCE_TYPES
| WasmFeatures::MUTABLE_GLOBAL
| WasmFeatures::MULTI_VALUE
Expand Down
22 changes: 22 additions & 0 deletions crates/parser/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,27 @@ pub(crate) mod visit {
$(lowering_ops!(@effect $inputs => $outputs $visit);)*
lowering_ops!($($rest)*);
};
(unsupported $args:tt { $($visit:ident),* $(,)? } $($rest:tt)*) => {
$(lowering_ops!(@unsupported $args $visit);)*
lowering_ops!($($rest)*);
};
(heap $nullable:literal $inputs:tt => $outputs:tt {
$($visit:ident => $instr:ident),* $(,)?
} $($rest:tt)*) => {
$(
fn $visit(&mut self, heap_type: wasmparser::HeapType) -> Self::Output {
let ty = convert_heap_type(heap_type, $nullable)?;
lowering_ops!(@emit self fixed $inputs => $outputs Instruction::$instr(ty))
}
)*
lowering_ops!($($rest)*);
};

(@unsupported [$($argty:ty),*] $visit:ident) => {
fn $visit(&mut self $(, _: $argty)*) -> Self::Output {
Err(crate::ParseError::UnsupportedOperator(stringify!($visit).to_string()))
}
};

(@fixed [$($input:ident),*] => [$($output:ident),*]
$visit:ident $(($($arg:ident: $ty:ty),+))? => $instr:ident
Expand Down Expand Up @@ -128,6 +149,7 @@ pub(crate) mod visit {
(@@relaxed_simd $($rest:tt)* ) => {};
(@@tail_call $($rest:tt)* ) => {};
(@@function_references $($rest:tt)* ) => {};
(@@gc $($rest:tt)* ) => {};

(@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => {
fn $visit(&mut self $($(,_: $argty)*)?) -> Self::Output {
Expand Down
Loading
Loading