Skip to content

Commit 8ec2ca1

Browse files
michael-weigeltMichael Weigelt
andauthored
feat: [EXC-1767] Enable tail_call in wasmtime. (#2826)
Enables the wasmtime feature [tail_call](https://github.com/WebAssembly/tail-call/blob/master/proposals/tail-call/Overview.md). Benchmarks show that using `return_call` instead of `call` can improve performance significantly, but the improvement depends on the depth of the call stack. In short, with reasonable assumptions about call depth, the improvement is about 1.5x, so the cost for `return_call` and `return_call_indirect` are adjusted downward to incentivize the use of this optimization by the toolchains. --------- Co-authored-by: Michael Weigelt <michael.weigelt@dfinity.com>
1 parent 6f1be0f commit 8ec2ca1

File tree

6 files changed

+54
-12
lines changed

6 files changed

+54
-12
lines changed

rs/embedders/src/wasm_utils/instrumentation.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,13 @@ pub fn instruction_to_cost(i: &Operator, mem_type: WasmMemoryType) -> u64 {
427427

428428
// Call instructions are of cost 20. Validated in benchmarks.
429429
// The cost is adjusted to 5 and 10 after benchmarking with real canisters.
430-
Operator::Call { .. } | Operator::ReturnCall { .. } => 5,
431-
Operator::CallIndirect { .. } | Operator::ReturnCallIndirect { .. } => 10,
430+
Operator::Call { .. } => 5,
431+
Operator::CallIndirect { .. } => 10,
432+
433+
// ReturnCall instructions are on average approx. 1.5 times faster than Call
434+
// instructions (shown by relative benchmarks).
435+
Operator::ReturnCall { .. } => 3,
436+
Operator::ReturnCallIndirect { .. } => 6,
432437

433438
// Return, drop, unreachable and nop instructions are of cost 1.
434439
Operator::Return { .. } | Operator::Drop | Operator::Unreachable | Operator::Nop => 1,

rs/embedders/src/wasm_utils/validation.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,8 +1449,7 @@ pub fn wasmtime_validation_config(embedders_config: &EmbeddersConfig) -> wasmtim
14491449
config.wasm_reference_types(true);
14501450
// The relaxed SIMD instructions are disable for determinism.
14511451
config.wasm_relaxed_simd(false);
1452-
// Tail calls may be enabled in the future.
1453-
config.wasm_tail_call(false);
1452+
config.wasm_tail_call(true);
14541453
// WebAssembly extended-const proposal is disabled.
14551454
config.wasm_extended_const(false);
14561455

rs/embedders/src/wasmtime_embedder/wasmtime_embedder_tests.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -170,16 +170,10 @@ fn test_wasmtime_system_api() {
170170

171171
#[test]
172172
fn test_initial_wasmtime_config() {
173-
// The following proposals should be disabled: tail_call, simd, relaxed_simd,
173+
// The following proposals should be disabled: simd, relaxed_simd,
174174
// threads, multi_memory, exceptions, extended_const, component_model,
175175
// function_references, memory_control, gc
176176
for (proposal, _url, wat, expected_err_msg) in [
177-
(
178-
"tail_call",
179-
"https://github.com/WebAssembly/tail-call/",
180-
"(module (func $f1 return_call $f2) (func $f2))",
181-
"tail calls support is not enabled",
182-
),
183177
(
184178
"relaxed_simd",
185179
"https://github.com/WebAssembly/relaxed-simd/",

rs/execution_environment/benches/lib/src/wat_builder.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ impl Block {
8787
if code.contains("$empty_return_call") {
8888
self.import("(func $empty_return_call (result i32) return_call $empty)");
8989
}
90+
if code.contains("$recursive_call") {
91+
self.import(
92+
&RECURSIVE
93+
.replace("<NAME>", "$recursive_call")
94+
.replace("<RETURN>", ""),
95+
);
96+
}
97+
if code.contains("$recursive_return_call") {
98+
self.import(
99+
&RECURSIVE
100+
.replace("<NAME>", "$recursive_return_call")
101+
.replace("<RETURN>", "return_"),
102+
);
103+
}
90104
if code.contains("$result_i32") || code.contains("table.get") || code.contains("table.size")
91105
{
92106
self.import("(type $result_i32 (func (result i32)))")
@@ -213,3 +227,18 @@ pub fn src_type(op: &str) -> &'static str {
213227
// Fallback to the destination type, i.e. for `i64.eqz` returns `i64`.
214228
dst_type(op)
215229
}
230+
231+
const RECURSIVE: &str = r#"
232+
(func <NAME> (param $n i32) (result i32)
233+
(i32.eqz (local.get $n))
234+
(if (result i32)
235+
(then
236+
(local.get $n)
237+
)
238+
(else
239+
(i32.sub (local.get $n) (i32.const 1))
240+
(<RETURN>call <NAME>)
241+
)
242+
)
243+
)
244+
"#;

rs/execution_environment/benches/wasm_instructions/basic.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,21 @@ pub fn benchmarks() -> Vec<Benchmark> {
479479
"ctrlop/call_indirect",
480480
"(global.set $x_i32 (call_indirect (type $result_i32) (i32.const 7)))",
481481
));
482+
// This benchmark inevitably contains many more instructions than the one we wish to
483+
// benchmark, so the absolute values of the results are not very interesting.
484+
// What matters is the comparison with the 'recursive_call*' results below, which
485+
// shows that 'return_call' is at least as efficient as 'call'. The deeper the call-
486+
// stack, the larger the improvement. In the call-depth range 1-10000, 'return_call'
487+
// is approximately 1.5 times faster on average than 'call'.
488+
benchmarks.extend(benchmark_with_confirmation(
489+
"ctrlop/recursive_return_call*",
490+
"(global.set $x_i32 (call $recursive_return_call (i32.const 10)))",
491+
));
492+
// This is a reference for the 'return_call' benchmark above.
493+
benchmarks.extend(benchmark_with_confirmation(
494+
"ctrlop/recursive_call*",
495+
"(global.set $x_i32 (call $recursive_call (i32.const 10)))",
496+
));
482497

483498
benchmarks
484499
}

rs/execution_environment/benches/wasm_instructions/wasm_coverage.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ PROPOSAL_TO_OP_URL="https://raw.githubusercontent.com/bytecodealliance/wasm-tool
1616
# The file with Wasm operator to Wasm instruction mapping.
1717
OP_TO_INSTR_URL="https://raw.githubusercontent.com/bytecodealliance/wasm-tools/main/crates/wast/src/core/expr.rs"
1818
# The Wasm proposal to exclude from the coverage, i.e. unsupported Wasm proposals.
19-
EXCLUDE_PROPOSALS="tail_call|relaxed_simd|threads|multi_memory|exceptions|memory64|extended_const|component_model|function_references|memory_control|gc|shared_everything_threads"
19+
EXCLUDE_PROPOSALS="relaxed_simd|threads|multi_memory|exceptions|memory64|extended_const|component_model|function_references|memory_control|gc|shared_everything_threads"
2020

2121
# Extract `instruction_to_cost` function from the file.
2222
instruction_to_cost=$(sed -n '/pub fn instruction_to_cost/,/^[}]/{p}' "${INSTRUCTION_TO_COST_FILE}")

0 commit comments

Comments
 (0)