Skip to content

Commit ca6c66a

Browse files
feat: add resumable calls / fuel tracking
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent bfeefda commit ca6c66a

File tree

16 files changed

+651
-61
lines changed

16 files changed

+651
-61
lines changed

crates/tinywasm/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,7 @@ harness=false
116116
[[bench]]
117117
name="tinywasm"
118118
harness=false
119+
120+
[[bench]]
121+
name="tinywasm_modes"
122+
harness=false

crates/tinywasm/benches/tinywasm.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ fn tinywasm_run(module: TinyWasmModule) -> Result<()> {
3434
fn criterion_benchmark(c: &mut Criterion) {
3535
let module = tinywasm_parse().expect("tinywasm_parse");
3636
let _twasm = tinywasm_to_twasm(&module).expect("tinywasm_to_twasm");
37+
let mut group = c.benchmark_group("tinywasm");
38+
group.measurement_time(std::time::Duration::from_secs(10));
3739

38-
// c.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse));
39-
// c.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module)));
40-
// c.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm)));
41-
c.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone())));
40+
// group.bench_function("tinywasm_parse", |b| b.iter(tinywasm_parse));
41+
// group.bench_function("tinywasm_to_twasm", |b| b.iter(|| tinywasm_to_twasm(&module)));
42+
// group.bench_function("tinywasm_from_twasm", |b| b.iter(|| tinywasm_from_twasm(&twasm)));
43+
group.bench_function("tinywasm", |b| b.iter(|| tinywasm_run(module.clone())));
4244
}
4345

4446
criterion_group!(benches, criterion_benchmark);
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
2+
use eyre::Result;
3+
use tinywasm::engine::{Config, FuelPolicy};
4+
use tinywasm::types::TinyWasmModule;
5+
use tinywasm::{Engine, ExecProgress, Extern, FuncContext, FuncHandleTyped, Imports, ModuleInstance, Store};
6+
7+
const WASM: &[u8] = include_bytes!("../../../examples/rust/out/tinywasm.wasm");
8+
const FUEL_PER_ROUND: u32 = 512;
9+
const TIME_BUDGET_PER_ROUND: core::time::Duration = core::time::Duration::from_micros(50);
10+
const BENCH_MEASUREMENT_TIME: core::time::Duration = core::time::Duration::from_secs(10);
11+
12+
fn tinywasm_parse() -> Result<TinyWasmModule> {
13+
let parser = tinywasm_parser::Parser::new();
14+
Ok(parser.parse_module_bytes(WASM)?)
15+
}
16+
17+
fn setup_typed_func(module: TinyWasmModule, engine: Option<Engine>) -> Result<(Store, FuncHandleTyped<(), ()>)> {
18+
let mut store = match engine {
19+
Some(engine) => Store::new(engine),
20+
None => Store::default(),
21+
};
22+
23+
let mut imports = Imports::default();
24+
imports.define("env", "printi32", Extern::typed_func(|_: FuncContext<'_>, _: i32| Ok(())))?;
25+
26+
let instance = ModuleInstance::instantiate(&mut store, module.into(), Some(imports))?;
27+
let func = instance.exported_func::<(), ()>(&store, "hello")?;
28+
Ok((store, func))
29+
}
30+
31+
fn run_call(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> {
32+
func.call(store, ())?;
33+
Ok(())
34+
}
35+
36+
fn run_resume_with_fuel(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> {
37+
let mut execution = func.call_resumable(store, ())?;
38+
loop {
39+
match execution.resume_with_fuel(FUEL_PER_ROUND)? {
40+
ExecProgress::Completed(_) => return Ok(()),
41+
ExecProgress::Suspended => {}
42+
}
43+
}
44+
}
45+
46+
fn run_resume_with_time_budget(store: &mut Store, func: &FuncHandleTyped<(), ()>) -> Result<()> {
47+
let mut execution = func.call_resumable(store, ())?;
48+
loop {
49+
match execution.resume_with_time_budget(TIME_BUDGET_PER_ROUND)? {
50+
ExecProgress::Completed(_) => return Ok(()),
51+
ExecProgress::Suspended => {}
52+
}
53+
}
54+
}
55+
56+
fn criterion_benchmark(c: &mut Criterion) {
57+
let module = tinywasm_parse().expect("tinywasm_parse");
58+
let mut group = c.benchmark_group("tinywasm_modes");
59+
group.measurement_time(BENCH_MEASUREMENT_TIME);
60+
61+
group.bench_function("call", |b| {
62+
b.iter_batched_ref(
63+
|| setup_typed_func(module.clone(), None).expect("setup call"),
64+
|(store, func)| run_call(store, func).expect("run call"),
65+
BatchSize::LargeInput,
66+
)
67+
});
68+
69+
let per_instruction_engine = Engine::new(Config::new().fuel_policy(FuelPolicy::PerInstruction));
70+
group.bench_function("resume_fuel_per_instruction", |b| {
71+
b.iter_batched_ref(
72+
|| {
73+
setup_typed_func(module.clone(), Some(per_instruction_engine.clone()))
74+
.expect("setup fuel per-instruction")
75+
},
76+
|(store, func)| run_resume_with_fuel(store, func).expect("run fuel per-instruction"),
77+
BatchSize::LargeInput,
78+
)
79+
});
80+
81+
let weighted_engine = Engine::new(Config::new().fuel_policy(FuelPolicy::Weighted));
82+
group.bench_function("resume_fuel_weighted", |b| {
83+
b.iter_batched_ref(
84+
|| setup_typed_func(module.clone(), Some(weighted_engine.clone())).expect("setup fuel weighted"),
85+
|(store, func)| run_resume_with_fuel(store, func).expect("run fuel weighted"),
86+
BatchSize::LargeInput,
87+
)
88+
});
89+
90+
group.bench_function("resume_time_budget", |b| {
91+
b.iter_batched_ref(
92+
|| setup_typed_func(module.clone(), None).expect("setup time budget"),
93+
|(store, func)| run_resume_with_time_budget(store, func).expect("run time budget"),
94+
BatchSize::LargeInput,
95+
)
96+
});
97+
98+
group.finish();
99+
}
100+
101+
criterion_group!(benches, criterion_benchmark);
102+
criterion_main!(benches);

crates/tinywasm/src/engine.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,22 @@ pub(crate) struct EngineInner {
3939
// pub(crate) allocator: Box<dyn Allocator + Send + Sync>,
4040
}
4141

42+
/// Fuel accounting policy for budgeted execution.
43+
#[derive(Debug, Clone, Copy)]
44+
#[non_exhaustive]
45+
pub enum FuelPolicy {
46+
/// Charge one fuel unit per retired instruction.
47+
PerInstruction,
48+
/// Charge one fuel unit per instruction plus predefined extra cost for specific operations.
49+
Weighted,
50+
}
51+
52+
impl Default for FuelPolicy {
53+
fn default() -> Self {
54+
Self::PerInstruction
55+
}
56+
}
57+
4258
/// Default initial size for the 32-bit value stack (i32, f32 values).
4359
pub const DEFAULT_VALUE_STACK_32_SIZE: usize = 64 * 1024; // 64k slots
4460

@@ -68,13 +84,21 @@ pub struct Config {
6884
pub stack_ref_size: usize,
6985
/// Initial size of the call stack.
7086
pub call_stack_size: usize,
87+
/// Fuel accounting policy used by budgeted execution.
88+
pub fuel_policy: FuelPolicy,
7189
}
7290

7391
impl Config {
7492
/// Create a new stack configuration with default settings.
7593
pub fn new() -> Self {
7694
Self::default()
7795
}
96+
97+
/// Set the fuel accounting policy for budgeted execution.
98+
pub fn fuel_policy(mut self, fuel_policy: FuelPolicy) -> Self {
99+
self.fuel_policy = fuel_policy;
100+
self
101+
}
78102
}
79103

80104
impl Default for Config {
@@ -85,6 +109,7 @@ impl Default for Config {
85109
stack_128_size: DEFAULT_VALUE_STACK_128_SIZE,
86110
stack_ref_size: DEFAULT_VALUE_STACK_REF_SIZE,
87111
call_stack_size: DEFAULT_CALL_STACK_SIZE,
112+
fuel_policy: FuelPolicy::default(),
88113
}
89114
}
90115
}

0 commit comments

Comments
 (0)