Skip to content

Commit fe43e48

Browse files
fix: remove _start fallback for instantiation
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent 089f5dd commit fe43e48

7 files changed

Lines changed: 51 additions & 33 deletions

File tree

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ jobs:
106106
if: matrix.target == ''
107107

108108
- name: Run tests (${{ matrix.target }})
109-
uses: houseabsolute/actions-rust-cross@fe6ede73c7f0a50412ec05994e2aa5a7bf635eac # v1.0.7
109+
uses: houseabsolute/actions-rust-cross@21b0f18dc621b25bfae556ff2791fca4173121e8 # v1.0.8
110110
with:
111111
command: test
112112
target: ${{ matrix.target }}

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Changed
11+
12+
- `instantiate` / `start` / `start_func` no longer fall back to `_start` if the module has no start section. This is technically a breaking change, but it is more consistent with the WebAssembly spec and prevents accidental execution of `_start` in wasi modules that don't have a start section. Call the `_start` function explicitly if you want to run it.
13+
814
## [0.9.1] - 2026-06-29
915

1016
### Added

crates/cli/src/cmd/run.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use eyre::{Result, bail};
1+
use eyre::Result;
22
use tinywasm::types::ExportType;
33
use tinywasm::{ModuleInstance, Store};
44

@@ -34,13 +34,17 @@ pub fn run(args: RunArgs) -> Result<()> {
3434
Ok(())
3535
}
3636
None => {
37-
if instance.start_func(&store)?.is_none() {
38-
bail!(
39-
"module has no start function or `_start` export; use `tinywasm inspect {module_path}` or `tinywasm run --invoke <export> {module_path}`"
40-
)
37+
if instance.start_func(&store)?.is_some() {
38+
let _ = instance.start(&mut store)?;
39+
return Ok(());
4140
}
4241

43-
let _ = instance.start(&mut store)?;
42+
let start = instance.func_untyped(&store, "_start").map_err(|_| {
43+
eyre::eyre!(
44+
"module has no start function or `_start` export. Use `tinywasm inspect {module_path}` or `tinywasm run --invoke <export> {module_path}`"
45+
)
46+
})?;
47+
start.call(&mut store, &[])?;
4448
Ok(())
4549
}
4650
}

crates/tinywasm/src/instance.rs

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,8 @@ impl ModuleInstance {
604604
/// # use tinywasm::{ModuleInstance, Store};
605605
/// # let wasm = wat::parse_str(r#"
606606
/// # (module
607-
/// # (func (export "_start"))
607+
/// # (func $start)
608+
/// # (start $start)
608609
/// # )
609610
/// # "#).expect("valid wat");
610611
/// # let module = tinywasm::parse_bytes(&wasm)?;
@@ -619,21 +620,8 @@ impl ModuleInstance {
619620
pub fn start_func(&self, store: &Store) -> Result<Option<Function>> {
620621
self.validate_store(store)?;
621622

622-
let func_addr = match self.0.func_start {
623-
Some(func_index) => func_index,
624-
None => {
625-
// Alternatively, check for a _start function in the exports.
626-
let Some(ExternVal::Func(func_addr)) = self.export_addr("_start") else {
627-
return Ok(None);
628-
};
629-
630-
return Ok(Some(Function {
631-
item: crate::StoreItem::new(self.0.store_id, func_addr),
632-
module_addr: self.id(),
633-
addr: func_addr,
634-
ty: store.state.get_func(func_addr).ty().clone(),
635-
}));
636-
}
623+
let Some(func_addr) = self.0.func_start else {
624+
return Ok(None);
637625
};
638626

639627
let func_addr = self.resolve_func_addr(func_addr);
@@ -656,9 +644,10 @@ impl ModuleInstance {
656644
/// # let wasm = wat::parse_str(r#"
657645
/// # (module
658646
/// # (global $g (mut i32) (i32.const 0))
659-
/// # (func (export "_start")
647+
/// # (func $start
660648
/// # i32.const 7
661649
/// # global.set $g)
650+
/// # (start $start)
662651
/// # (export "g" (global $g)))
663652
/// # "#).expect("valid wat");
664653
/// # let module = tinywasm::parse_bytes(&wasm)?;

crates/tinywasm/src/interpreter/executor.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
4949
}
5050
}
5151

52+
#[allow(clippy::redundant_closure_call)] // we wrap these in closures to get better perf traces
5253
#[inline(always)]
5354
fn exec(&mut self) -> Result<Option<()>, Trap> {
5455
macro_rules! stack_op {
@@ -264,7 +265,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> {
264265
}};
265266
}
266267

267-
let next = match self.func.instructions.get(self.cf.instr_ptr as usize) {
268+
let next = match self.func.instructions.get(self.cf.instr_ptr) {
268269
Some(instr) => instr,
269270
None => {
270271
cold_path();
@@ -1654,12 +1655,6 @@ impl<'store> Executor<'store, true> {
16541655
}
16551656
}
16561657

1657-
#[cold]
1658-
#[inline(never)]
1659-
fn instruction_pointer_out_of_bounds(instr_ptr: usize, instruction_count: usize) -> ! {
1660-
panic!("Instruction pointer out of bounds: {instr_ptr} ({instruction_count} instructions)")
1661-
}
1662-
16631658
#[inline(always)]
16641659
fn cmp_i32(lhs: i32, rhs: i32, op: CmpOp) -> bool {
16651660
match op {

crates/tinywasm/tests/internal_refs.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,15 @@ fn export_func_type_index_mismatch_fixture_would_break_old_lookup() -> Result<()
169169
}
170170

171171
#[test]
172-
fn start_prefers_exported_start_without_re_resolving_store_addr() -> Result<()> {
172+
fn start_resolves_module_func_index_to_store_addr() -> Result<()> {
173173
let wasm = wat::parse_str(
174174
r#"
175175
(module
176176
(global (export "g") (mut i32) (i32.const 0))
177-
(func (export "_start")
177+
(func $start
178178
i32.const 1
179179
global.set 0)
180+
(start $start)
180181
)
181182
"#,
182183
)?;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use eyre::Result;
2+
use tinywasm::{ModuleInstance, Store};
3+
4+
#[test]
5+
fn exported_wasi_start_is_not_run_during_instantiation() -> Result<()> {
6+
let wasm = wat::parse_str(
7+
r#"
8+
(module
9+
(table 1 funcref)
10+
(func $_start (export "_start")
11+
unreachable)
12+
(elem (i32.const 0) func $_start))
13+
"#,
14+
)?;
15+
let module = tinywasm::parse_bytes(&wasm)?;
16+
let mut store = Store::default();
17+
18+
let instance = ModuleInstance::instantiate(&mut store, &module, None)?;
19+
20+
assert!(instance.start_func(&store)?.is_none());
21+
assert!(instance.func::<(), ()>(&store, "_start")?.call(&mut store, ()).is_err());
22+
Ok(())
23+
}

0 commit comments

Comments
 (0)