Skip to content

Commit bb17468

Browse files
chore: improve optimizer and simplify function execution
- add stack-constant and fused local-update rewrites - split host and Wasm function storage and execution paths - add the default full feature - recursively discover WAST test files Signed-off-by: Henry <mail@henrygressmann.de>
1 parent e73c921 commit bb17468

31 files changed

Lines changed: 726 additions & 602 deletions

Cargo.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ tinywasm-types = { path = "crates/types", version = "0.11.0-pre.0", default-feat
2626

2727
anyhow = "1.0"
2828
log = "0.4"
29-
owo-colors = { version = "4.3" }
29+
owo-colors = { version = "4.4" }
3030
pretty_env_logger = "0.5"
3131
serde = { version = "1.0", features = ["derive"] }
3232
serde_json = { version = "1.0" }

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ See the [examples](./examples) directory and [documentation](https://docs.rs/tin
4848

4949
## Cargo Features
5050

51+
- **`full`:** Enables `archive`, `parallel-parser`, `parser`, and `validate`. Enabled by default.
5152
- **`std`:** Enables `std` and parsing from files and streams. Enabled by default.
5253
- **`log`:** Enables integration with the `log` crate. Enabled by default.
5354
- **`parser`:** Enables `tinywasm-parser` and top-level parse helpers. Enabled by default.

crates/cli/Cargo.toml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,12 @@ pretty_env_logger.workspace = true
3232
serde = { workspace = true, optional = true }
3333
serde_json = { workspace = true, optional = true }
3434
tinywasm = { workspace = true, features = [
35-
"archive",
3635
"canonicalize-nans",
3736
"debug",
37+
"full",
3838
"guest-debug",
3939
"log",
40-
"parallel-parser",
41-
"parser",
4240
"std",
43-
"validate",
4441
] }
4542
wasm-testsuite = { workspace = true, optional = true }
4643
wast = { workspace = true, optional = true }

crates/cli/src/wast_runner.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -669,17 +669,20 @@ struct TestCase {
669669

670670
fn expand_paths(paths: &[PathBuf]) -> Result<Vec<PathBuf>> {
671671
let mut files = Vec::new();
672-
for path in paths {
672+
let mut pending = paths.to_vec();
673+
while let Some(path) = pending.pop() {
673674
if path.is_dir() {
674-
for entry in std::fs::read_dir(path)? {
675+
for entry in std::fs::read_dir(&path)? {
675676
let entry = entry?;
676677
let path = entry.path();
677-
if path.extension().is_some_and(|ext| ext == "wast") {
678+
if entry.file_type()?.is_dir() {
679+
pending.push(path);
680+
} else if path.extension().is_some_and(|ext| ext == "wast") {
678681
files.push(path);
679682
}
680683
}
681684
} else {
682-
files.push(path.clone());
685+
files.push(path);
683686
}
684687
}
685688
files.sort();
@@ -973,6 +976,18 @@ mod tests {
973976
runner.run_paths(&[path]).unwrap();
974977
}
975978

979+
#[test]
980+
fn recursively_runs_wast_files_and_ignores_other_files() {
981+
let dir = tempfile::tempdir().unwrap();
982+
let nested = dir.path().join("nested");
983+
std::fs::create_dir(&nested).unwrap();
984+
std::fs::write(dir.path().join("README.md"), "not a wast file").unwrap();
985+
std::fs::write(nested.join("simple.wast"), "(module)").unwrap();
986+
987+
let mut runner = WastRunner::new();
988+
runner.run_paths(&[dir.path().to_path_buf()]).unwrap();
989+
}
990+
976991
#[test]
977992
fn runs_module_definition_and_instance_directives() {
978993
let dir = tempfile::tempdir().unwrap();

crates/parser/src/optimize/rewrite.rs

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ fn rewrite(
107107
output.extend([LocalGet32(local), I32Add3]);
108108
read = output.len() - 1;
109109
}
110+
} else {
111+
rewrite!(output, read, [Const32(value)] => stack_const32(op, value));
110112
}
111113
}
112114
raw @ (I32Sub | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr) => {
@@ -117,7 +119,10 @@ fn rewrite(
117119
if rewrite_scalar_const32(&mut output, &mut read, data, op, false)? {
118120
continue;
119121
}
120-
rewrite_sign_extend32(&mut output, &mut read, data, op);
122+
if rewrite_sign_extend32(&mut output, &mut read, data, op) {
123+
continue;
124+
}
125+
rewrite!(output, read, [Const32(value)] => stack_const32(op, value));
121126
}
122127
raw @ (I64Add | I64Mul | I64And | I64Or | I64Xor) => {
123128
let op = int_bin_op(raw).unwrap();
@@ -129,6 +134,8 @@ fn rewrite(
129134
if op == BinOp::IAdd {
130135
rewrite!(output, read, [Const64(index)] => AddConst64(index));
131136
rewrite!(output, read, [I64Add] => I64Add3);
137+
} else {
138+
rewrite!(output, read, [Const64(index)] => BinOpStackConst64(PackedOp::new(op, index)));
132139
}
133140
}
134141
raw @ (I64Sub | I64Shl | I64ShrS | I64ShrU | I64Rotl | I64Rotr) => {
@@ -138,7 +145,10 @@ fn rewrite(
138145
if rewrite_scalar_const64(&mut output, &mut read, data, op, false)? {
139146
continue;
140147
}
141-
rewrite_sign_extend64(&mut output, &mut read, data, op);
148+
if rewrite_sign_extend64(&mut output, &mut read, data, op) {
149+
continue;
150+
}
151+
rewrite!(output, read, [Const64(index)] => BinOpStackConst64(PackedOp::new(op, index)));
142152
}
143153
raw if cmp_op(raw).is_some() => {
144154
let op = cmp_op(raw).unwrap();
@@ -149,23 +159,31 @@ fn rewrite(
149159
let op = float_bin_op(raw).unwrap();
150160
rewrite!(output, read, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b));
151161
rewrite!(output, read, [LocalGet32(local)] => BinOpStackLocal32(op, local));
152-
rewrite_scalar_const32(&mut output, &mut read, data, op, true)?;
162+
if !rewrite_scalar_const32(&mut output, &mut read, data, op, true)? {
163+
rewrite!(output, read, [Const32(value)] => stack_const32(op, value));
164+
}
153165
}
154166
raw @ (F32Sub | F32Div | F32Copysign) => {
155167
let op = float_bin_op(raw).unwrap();
156168
rewrite!(output, read, [LocalGet32(a), LocalGet32(b)] => BinOpLocalLocal32(op, a, b));
157169
rewrite!(output, read, [LocalGet32(local)] => BinOpStackLocal32(op, local));
158-
rewrite_scalar_const32(&mut output, &mut read, data, op, false)?;
170+
if !rewrite_scalar_const32(&mut output, &mut read, data, op, false)? {
171+
rewrite!(output, read, [Const32(value)] => stack_const32(op, value));
172+
}
159173
}
160174
raw @ (F64Add | F64Mul | F64Min | F64Max) => {
161175
let op = float_bin_op(raw).unwrap();
162176
rewrite!(output, read, [LocalGet64(a), LocalGet64(b)] => BinOpLocalLocal64(op, a, b));
163-
rewrite_scalar_const64(&mut output, &mut read, data, op, true)?;
177+
if !rewrite_scalar_const64(&mut output, &mut read, data, op, true)? {
178+
rewrite!(output, read, [Const64(index)] => BinOpStackConst64(PackedOp::new(op, index)));
179+
}
164180
}
165181
raw @ (F64Sub | F64Div | F64Copysign) => {
166182
let op = float_bin_op(raw).unwrap();
167183
rewrite!(output, read, [LocalGet64(a), LocalGet64(b)] => BinOpLocalLocal64(op, a, b));
168-
rewrite_scalar_const64(&mut output, &mut read, data, op, false)?;
184+
if !rewrite_scalar_const64(&mut output, &mut read, data, op, false)? {
185+
rewrite!(output, read, [Const64(index)] => BinOpStackConst64(PackedOp::new(op, index)));
186+
}
169187
}
170188
raw @ (V128And | V128Or | V128Xor | I64x2Add | I64x2Mul | V128AndNot) => {
171189
let op = bin_op_128(raw).unwrap();
@@ -299,6 +317,8 @@ fn local_const32(data: &WasmFunctionData, instruction: Instruction) -> Option<(B
299317
Instruction::AddLocalConst32(arg) => Some((BinOp::IAdd, arg.local, arg.value)),
300318
Instruction::SubLocalConst32(arg) => Some((BinOp::ISub, arg.local, arg.value)),
301319
Instruction::MulLocalConst32(arg) => Some((BinOp::IMul, arg.local, arg.value)),
320+
Instruction::AndLocalConst32(arg) => Some((BinOp::IAnd, arg.local, arg.value)),
321+
Instruction::ShrULocalConst32(arg) => Some((BinOp::IShrU, arg.local, arg.value)),
302322
Instruction::BinOpLocalConst32(packed) => {
303323
let value = data.operand64(packed.index);
304324
Some((packed.op, value.a(), value.b() as i32))
@@ -348,6 +368,8 @@ fn rewrite_scalar_const32(
348368
BinOp::IAdd => Instruction::AddLocalConst32(arg),
349369
BinOp::ISub => Instruction::SubLocalConst32(arg),
350370
BinOp::IMul => Instruction::MulLocalConst32(arg),
371+
BinOp::IAnd => Instruction::AndLocalConst32(arg),
372+
BinOp::IShrU => Instruction::ShrULocalConst32(arg),
351373
_ => Instruction::BinOpLocalConst32(PackedOp::new(
352374
op,
353375
data.push_operand64(Operand64::<(u16, u32)>::new(*local, *value as u32))?,
@@ -362,6 +384,7 @@ fn rewrite_scalar_const32(
362384
match op {
363385
BinOp::IAdd => Instruction::AddLocalConst32(arg),
364386
BinOp::IMul => Instruction::MulLocalConst32(arg),
387+
BinOp::IAnd => Instruction::AndLocalConst32(arg),
365388
_ => Instruction::BinOpLocalConst32(PackedOp::new(
366389
op,
367390
data.push_operand64(Operand64::<(u16, u32)>::new(*local, *value as u32))?,
@@ -380,6 +403,15 @@ fn rewrite_scalar_const32(
380403
Ok(true)
381404
}
382405

406+
fn stack_const32(op: BinOp, value: i32) -> Instruction {
407+
match op {
408+
BinOp::IAnd => Instruction::AndConst32(value),
409+
BinOp::IXor => Instruction::XorConst32(value),
410+
BinOp::IShrU => Instruction::ShrUConst32(value),
411+
_ => Instruction::BinOpStackConst32(op, value),
412+
}
413+
}
414+
383415
fn rewrite_scalar_const64(
384416
output: &mut CompactOutput,
385417
read: &mut usize,
@@ -568,7 +600,7 @@ fn rewrite_store64(
568600
if let [
569601
Instruction::LocalGet32(addr) | Instruction::LocalGet64(addr),
570602
Instruction::LoadLocal64(arg),
571-
Instruction::Const64(one),
603+
Instruction::AddConst64(one),
572604
] = previous
573605
&& { compact_arg.map(Operand64::from) == Some(data.operand64(arg.memory_arg_idx)) }
574606
&& addr == u16::from(arg.local1)
@@ -820,6 +852,12 @@ fn rewrite_local_tee32(
820852
}
821853
if *read > output.block_start {
822854
match output[*read - 1] {
855+
Instruction::AndConst32(value) => {
856+
replace!(output, *read, 1 => Instruction::AndConstTee32(I32LocalArg { value, local: dst }));
857+
}
858+
Instruction::BinOpStackConst32(BinOp::ISub, value) => {
859+
replace!(output, *read, 1 => Instruction::SubConstTee32(I32LocalArg { value, local: dst }));
860+
}
823861
Instruction::LocalGet32(src) if src == dst => replace!(output, *read, 1 => Instruction::LocalGet32(src)),
824862
Instruction::BinOpLocalLocal32(op, left, right) => {
825863
let replacement = if op == BinOp::IAdd {
@@ -886,6 +924,12 @@ fn rewrite_local_tee64(
886924
}
887925
if *read > output.block_start {
888926
match output[*read - 1] {
927+
Instruction::BinOpStackConst64(packed) if packed.op == BinOp::IAnd => {
928+
replace!(output, *read, 1 => Instruction::AndConstTee64(PackedOp::new(dst, packed.index)));
929+
}
930+
Instruction::BinOpStackConst64(packed) if packed.op == BinOp::ISub => {
931+
replace!(output, *read, 1 => Instruction::SubConstTee64(PackedOp::new(dst, packed.index)));
932+
}
889933
Instruction::LocalGet64(src) if src == dst => replace!(output, *read, 1 => Instruction::LocalGet64(src)),
890934
Instruction::BinOpLocalLocal64(op, left, right) => {
891935
let index = data.push_operand64(Operand64::<(u16, u16, u16)>::new(left, right, dst))?;
@@ -1222,6 +1266,34 @@ fn rewrite_conditional(
12221266
replace!(output, *read, 3 => replacement);
12231267
return Ok(());
12241268
}
1269+
if *read >= output.block_start + 3 {
1270+
let update = match [output[*read - 3], output[*read - 2], output[*read - 1]] {
1271+
[Instruction::XorConst32(value), Instruction::LocalTee32(local), Instruction::LocalGet32(cond)]
1272+
if local == cond =>
1273+
{
1274+
Some((BinOp::IXor, value, local))
1275+
}
1276+
[Instruction::ShrUConst32(value), Instruction::LocalTee32(local), Instruction::LocalGet32(cond)]
1277+
if local == cond =>
1278+
{
1279+
Some((BinOp::IShrU, value, local))
1280+
}
1281+
[
1282+
Instruction::BinOpStackConst32(op, value),
1283+
Instruction::LocalTee32(local),
1284+
Instruction::LocalGet32(cond),
1285+
] if local == cond => Some((op, value, local)),
1286+
_ => None,
1287+
};
1288+
if let Some((op, value, local)) = update {
1289+
let replacement = Instruction::BinOpStackConstTeeLocalJump32(PackedOp::new(
1290+
op,
1291+
data.push_target_operand128(Operand128::<LocalUpdateOperand>::new(target, value, local, on_zero))?,
1292+
));
1293+
replace!(output, *read, 3 => replacement);
1294+
return Ok(());
1295+
}
1296+
}
12251297
if *read >= output.block_start + 2 {
12261298
let update = match [output[*read - 2], output[*read - 1]] {
12271299
[Instruction::AndConstTee32(arg), Instruction::LocalGet32(cond)] if arg.local == cond => {

crates/tinywasm/Cargo.toml

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,7 @@ keywords.workspace = true
1111
categories.workspace = true
1212

1313
[package.metadata.docs.rs]
14-
features = [
15-
"std",
16-
"validate",
17-
"parser",
18-
"archive",
19-
"log",
20-
"canonicalize-nans",
21-
"debug",
22-
"guest-debug"
23-
]
14+
features = ["std", "validate", "parser", "archive", "log", "canonicalize-nans", "debug", "guest-debug"]
2415
rustdoc-args = ["--cfg", "docsrs"]
2516

2617
[lib]
@@ -98,16 +89,8 @@ wasm-testsuite.workspace = true
9889
wat.workspace = true
9990

10091
[features]
101-
default = [
102-
"archive",
103-
"canonicalize-nans",
104-
"debug",
105-
"log",
106-
"parallel-parser",
107-
"parser",
108-
"std",
109-
"validate"
110-
]
92+
default = ["full", "std"]
93+
full = ["archive", "parallel-parser", "parser", "validate"]
11194

11295
log = ["dep:log", "tinywasm-parser?/log", "tinywasm-types/log"]
11396
std = ["tinywasm-parser?/std", "tinywasm-types/std"]
@@ -119,10 +102,10 @@ send = []
119102
parser = ["dep:tinywasm-parser"]
120103

121104
# validate WebAssembly while parsing
122-
validate = ["parser", "tinywasm-parser/validate"]
105+
validate = ["tinywasm-parser?/validate"]
123106

124107
# parallelize function parsing/validation across threads (requires std)
125-
parallel-parser = ["parser", "tinywasm-parser?/parallel"]
108+
parallel-parser = ["tinywasm-parser?/parallel"]
126109

127110
# support targets without native atomic CAS
128111
portable-atomic = ["dep:portable-atomic", "tinywasm-parser?/portable-atomic", "tinywasm-types/portable-atomic"]

0 commit comments

Comments
 (0)