Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added `WasmValue::ty` and `WasmValue::matches_type`
- Added `ValueLane` for mapping WebAssembly value types to their physical 32-bit, 64-bit, or 128-bit storage lane.
- Added a `validate` feature to `tinywasm` and `tinywasm-parser` (enabled by default) to optionally skip wasmparser validation for faster parsing of trusted modules.
- Added optional parse-time operand deduplication to reduce precompiled module and `.twasm` archive size.

### Changed

Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

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

33 changes: 21 additions & 12 deletions crates/parser/src/conversion.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::validation::{FuncValidator, FuncValidatorAllocations, ValidatorResources};
#[cfg(feature = "validate")]
use crate::visit::process_operators_and_validate;
use crate::{Result, module::FunctionCode, visit::process_operators};
use crate::{ParserOptions, Result, module::FunctionCode, visit::process_operators};
use alloc::{boxed::Box, format, vec::Vec};
use tinywasm_types::*;
use wasmparser::{CompositeInnerType, OperatorsReader, OperatorsReaderAllocations, UnpackedIndex};
Expand Down Expand Up @@ -149,6 +149,7 @@ pub(crate) fn convert_module_code(
reader_allocs: OperatorsReaderAllocations,
metadata: &crate::visit::ModuleMetadata,
ty_idx: u32,
options: &ParserOptions,
) -> Result<(FunctionCode, Option<FuncValidatorAllocations>, OperatorsReaderAllocations)> {
let locals_reader = func.get_locals_reader()?;
#[cfg(feature = "validate")]
Expand Down Expand Up @@ -194,32 +195,40 @@ pub(crate) fn convert_module_code(
let (body, data, validator_allocs, reader_allocs) = process_operators_and_validate(
validator,
func,
local_types,
local_addr_map,
(local_types, local_addr_map),
metadata,
ty_idx,
reader_allocs,
options.deduplicate_operands(),
)?;
(body, data, Some(validator_allocs), reader_allocs)
}
None => {
let (body, data, reader_allocs) =
process_operators(func, local_types, local_addr_map, metadata, ty_idx, reader_allocs)?;
let (body, data, reader_allocs) = process_operators(
func,
(local_types, local_addr_map),
metadata,
ty_idx,
reader_allocs,
options.deduplicate_operands(),
)?;
(body, data, None, reader_allocs)
}
};
#[cfg(not(feature = "validate"))]
let (body, data, validator_allocs, reader_allocs) = {
let _ = validator;
let (body, data, reader_allocs) =
process_operators(func, local_types, local_addr_map, metadata, ty_idx, reader_allocs)?;
let (body, data, reader_allocs) = process_operators(
func,
(local_types, local_addr_map),
metadata,
ty_idx,
reader_allocs,
options.deduplicate_operands(),
)?;
(body, data, None, reader_allocs)
};
Ok((
FunctionCode { instructions: body, data, locals: local_counts, uses_local_memory: false },
validator_allocs,
reader_allocs,
))
Ok((FunctionCode { instructions: body, data, locals: local_counts }, validator_allocs, reader_allocs))
}

pub(crate) fn convert_rec_group(ty: wasmparser::RecGroup, group_start: u32, types: &mut Vec<SubType>) -> Result<u32> {
Expand Down
16 changes: 16 additions & 0 deletions crates/parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ pub struct ParserOptions {
pub optimize_local_memory_allocation: bool,
/// Whether to run the peephole rewrite optimizer.
pub optimize_rewrite: bool,
/// Whether to deduplicate immutable function operands while parsing.
///
/// This uses more parse CPU to reduce precompiled module and archive size.
pub deduplicate_operands: bool,

#[cfg(parallel_parser)]
/// Number of threads to use for parallel parsing.
Expand All @@ -83,6 +87,7 @@ impl Default for ParserOptions {
validation: cfg!(feature = "validate"),
optimize_local_memory_allocation: true,
optimize_rewrite: true,
deduplicate_operands: false,
#[cfg(parallel_parser)]
parser_threads: None,
}
Expand Down Expand Up @@ -134,6 +139,17 @@ impl ParserOptions {
self.optimize_rewrite
}

/// Enable or disable parse-time deduplication of immutable function operands.
pub const fn with_operand_deduplication(mut self, enabled: bool) -> Self {
self.deduplicate_operands = enabled;
self
}

/// Returns whether immutable function operands are deduplicated while parsing.
pub const fn deduplicate_operands(&self) -> bool {
self.deduplicate_operands
}

#[cfg(parallel_parser)]
/// Set the number of threads for parallel parsing.
///
Expand Down
155 changes: 25 additions & 130 deletions crates/parser/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,17 @@ pub(crate) mod visit {
) => {
fn $visit(&mut self, memarg: wasmparser::MemArg $(, $lane: $ty)?) -> Self::Output {
let address = self.metadata.memory_size(memarg.memory)?;
let memory_arg_idx = self.push_operand(MemoryArg::new(memarg.offset, memarg.memory))?;
lowering_ops!(@emit self address(address) [$($input),*] => [$($output),*]
Instruction::$instr(MemoryArg::new(memarg.offset, memarg.memory) $(, $lane)?).into())
lowering_ops!(@memory_instruction $instr memory_arg_idx $(, $lane)?))
}
};
(@memory_instruction $instr:ident $memory_arg_idx:ident) => {
Instruction::$instr($memory_arg_idx)
};
(@memory_instruction $instr:ident $memory_arg_idx:ident, $lane:ident) => {
Instruction::$instr(tinywasm_types::MemoryLaneArg { memory_arg_idx: $memory_arg_idx, lane: $lane })
};
(@global $inputs:tt => $outputs:tt $($operator:tt)*) => {
lowering_ops!(@resolved global_size $inputs => $outputs $($operator)*);
};
Expand Down Expand Up @@ -191,6 +198,20 @@ pub(crate) mod optimize {
($instructions:ident, $read:ident, $consumed:expr => $out:expr) => {
replace!($instructions, $read, $consumed => [$out]);
};
($instructions:ident, *$read:ident, $consumed:expr => [$($out:expr),+ $(,)?]) => {{
const {
assert!($consumed >= 1 && $consumed <= 3);
assert!([$(stringify!($out)),+].len() <= $consumed + 1);
}
let replacements = [$($out),+];
let start = *$read - $consumed;
$instructions[start..start + replacements.len()].copy_from_slice(&replacements);
$instructions.truncate(start + replacements.len());
*$read = $instructions.len() - 1;
}};
($instructions:ident, *$read:ident, $consumed:expr => $out:expr) => {
replace!($instructions, *$read, $consumed => [$out])
};
}

macro_rules! rewrite {
Expand All @@ -201,16 +222,13 @@ pub(crate) mod optimize {
};
($instructions:ident, $read:ident, [$($pattern:pat),+] $(if ($($guard:tt)+))? => $body:block $(,)?) => {{
const CONSUMED: usize = [$(stringify!($pattern)),+].len();
if !$instructions.tail_rewritten
&& $read < $instructions.len()
&& $read >= $instructions.block_start + CONSUMED
{
if $read >= $instructions.block_start + CONSUMED {
let previous: [Instruction; CONSUMED] = $instructions[$read - CONSUMED..$read].try_into().unwrap();
if let [$($pattern),+] = previous $(
&& $($guard)+
)? {
$instructions.tail_rewritten = true;
$body
continue;
}
}
}};
Expand All @@ -221,128 +239,5 @@ pub(crate) mod optimize {
};
}

macro_rules! define_local_source_resolver {
(
$name:ident,
get = $get:ident,
tee = $tee:ident,
set = $set:ident,
binop_local_local_tee = $lltee:ident,
binop_local_local_set = $llset:ident,
binop_local_const_tee = $lctee:ident,
binop_local_const_set = $lcset:ident
$(, load_local_tee = $loadtee:ident, load_local_set = $loadset:ident)?
) => {
fn $name(instr: Instruction) -> Option<(Option<Instruction>, u16)> {
Some(match instr {
Instruction::$get(local) => (None, local),
Instruction::$tee(local) => (Some(Instruction::$set(local)), local),
Instruction::$lltee(op, a, b, local) => (Some(Instruction::$llset(op, a, b, local)), local),
Instruction::$lctee(op, src, c, local) => (Some(Instruction::$lcset(op, src, c, local)), local),
$(Instruction::$loadtee(memarg, addr, local) => (Some(Instruction::$loadset(memarg, addr, local)), local.into()),)?
_ => return None,
})
}
};
}

macro_rules! fold_local_binop {
(
$instrs:ident, $read:expr, $dst:expr,
source = $source:ident,
op = $op:ident,
const = $const:ident,
local_local = $local_local:ident,
local_const = $local_const:expr
) => {{
if !$instrs.tail_rewritten
&& $read < $instrs.len()
&& $read >= $instrs.block_start + 3
&& let [lhs_src, rhs_src, raw_op] = [$instrs[$read - 3], $instrs[$read - 2], $instrs[$read - 1]]
&& let Some((lhs_instr, lhs)) = $source(lhs_src)
&& let Some(op) = $op(raw_op)
{
if let Some((rhs_instr, rhs)) = $source(rhs_src) {
if rhs_instr.is_none() || rhs != lhs {
$instrs.tail_rewritten = true;
$instrs.truncate($read - 3);
$instrs.extend(lhs_instr);
$instrs.extend(rhs_instr);
$instrs.push(Instruction::$local_local(op, lhs, rhs, $dst));
$read = $instrs.len() - 1;
}
} else if let Some(imm) = $const(rhs_src, raw_op) {
$instrs.tail_rewritten = true;
$instrs.truncate($read - 3);
$instrs.extend(lhs_instr);
$instrs.push($local_const($dst, lhs, op, imm));
$read = $instrs.len() - 1;
}
}
}};
}

macro_rules! rewrite_local_set_direct {
(
$instrs:ident, $read:ident, $dst:expr,
get = $get:ident,
copy = $copy:ident,
binop_local_local = $ll:ident,
binop_local_local_set = $llset:ident,
binop_local_const = $lc:ident,
binop_local_const_set = $lcset:expr
$(, const_instr = $const_instr:ident, set_local_const = $set_local_const:ident)?
) => {{
rewrite!($instrs, $read, [$get(src)] if (src != $dst) => Instruction::$copy(src, $dst));
if !$instrs.tail_rewritten
&& $read < $instrs.len()
&& $read > $instrs.block_start
&& let Instruction::$get(src) = $instrs[$read - 1]
&& src == $dst
{
$instrs.tail_rewritten = true;
$instrs.truncate($read - 1);
$read = $instrs.len();
}
$(rewrite!($instrs, $read, [$const_instr(c)] => Instruction::$set_local_const($dst, c));)?
rewrite!($instrs, $read, [$ll(op, a, b)] => Instruction::$llset(op, a, b, $dst));
rewrite!($instrs, $read, [$lc(op, src, c)] => { replace!($instrs, $read, 1 => $lcset($dst, src, op, c)); });
}};
}

macro_rules! rewrite_local_tee_direct {
(
$instrs:ident, $read:ident, $dst:expr,
get = $get:ident,
binop_local_local = $ll:ident,
binop_local_local_tee = $lltee:ident,
binop_local_const = $lc:ident,
binop_local_const_tee = $lctee:ident
) => {{
rewrite!($instrs, $read, [$get(src)] if (src == $dst) => Instruction::$get(src));
rewrite!($instrs, $read, [$ll(op, a, b)] => Instruction::$lltee(op, a, b, $dst));
rewrite!($instrs, $read, [$lc(op, src, c)] => Instruction::$lctee(op, src, c, $dst));
}};
}

macro_rules! rewrite_drop_tee_direct {
(
$instrs:ident, $read:ident,
tee = $tee:ident,
set = $set:ident,
binop_local_local_tee = $lltee:ident,
binop_local_local_set = $llset:ident,
binop_local_const_tee = $lctee:ident,
binop_local_const_set = $lcset:ident
) => {{
rewrite!($instrs, $read, [$tee(local)] => Instruction::$set(local));
rewrite!($instrs, $read, [$lltee(op, a, b, dst)] => Instruction::$llset(op, a, b, dst));
rewrite!($instrs, $read, [$lctee(op, src, c, dst)] => Instruction::$lcset(op, src, c, dst));
}};
}

pub(crate) use {
define_local_source_resolver, fold_local_binop, replace, rewrite, rewrite_drop_tee_direct,
rewrite_local_set_direct, rewrite_local_tee_direct,
};
pub(crate) use {replace, rewrite};
}
Loading
Loading