Skip to content

rust: avoid rerunning constructors after WASI startup - #1750

Merged
alexcrichton merged 3 commits into
bytecodealliance:mainfrom
macovedj:ctors-once-after-initialize
Oct 20, 2026
Merged

alexcrichton merged 3 commits into
bytecodealliance:mainfrom
macovedj:ctors-once-after-initialize

Conversation

@macovedj

Copy link
Copy Markdown
Contributor

Since rust-lang/rust#161421, _initialize runs constructors in WASI cdylibs before the first export call, but wit-bindgen’s separate RUN guard remains unset, so run_ctors_once runs them again.

This PR registers an .init_array marker that sets the existing RUN guard when constructors run. Exported functions can then recognize initialization already performed by _initialize or _start

Register a startup marker that sets the existing run-once flag when constructors are invoked by _initialize or _start. Preserve the older toolchain fallback and add a constructor-counting component test covering the first and second export calls.
@alexcrichton

Copy link
Copy Markdown
Member

Thanks! I think it might actually be time though to just go ahead and outright delete this workaround. IIRC this was needed for... something in the past but I have no idea what, and I also have a vague recollection that the need for it was fixed long ago. Mind just deleting all the infrastructure to do this instead?

@macovedj

Copy link
Copy Markdown
Contributor Author

Looks to me like this is the original issue. I started looking into removal, and for wasm32-unknown-unknown, LLD still generates per-export constructor wrappers, including around cabi_realloc. I think we could still remove the workaround, but those builds need replacement startup support first. I’m looking into providing _initialize and suppressing those wrappers in wasm-component-ld. Should be able to have something up over there soon.

@alexcrichton

Copy link
Copy Markdown
Member

Another possible fix might be to limit the workaround to non-wasi targets since I think it's just the wasi targets using _initialize nowadays, but a deeper fix would work too!

@macovedj

Copy link
Copy Markdown
Contributor Author

Ah that would be easier to implement. One other wrinkle though. Removing the work around for wasi targets before rust 1.100 breaks since when cdylibs will start getting crt-reactor.o and _initialize.

The cleanest option I see is a build script that no-ops run_ctors_once once the compiler is new enough to guarantee that, defaulting to the current fallback when detection fails. Alternatively, wasm-component-ld could handle this itself, since it sees the whole link, but then the switch is keyed to the linker version rather than the Rust version.

Unless there's another mechanism I'm missing, I'd guess merging this as is for now and revisiting the deletion once our MSRV reaches a Rust version where _initialize is guaranteed for WASI cdylibs is what makes the most sense. I'd be happy to own a tracking issue if we go that route.

If you have any simpler approaches in mind, I'm happy go for one of those too.

@alexcrichton

Copy link
Copy Markdown
Member

Personally I'd prefer to time-box the workaround here to avoid it getting cemented too much -- I think the best option for now would be to detect the rustc version in a build script and then change this function based on that. Once MSRV is beyond that then this can all be dropped entriely

Detect rustc in the runtime build script and make run_ctors_once a no-op when WASI startup provides _initialize. Retain the existing helper for older Rust and non-WASI targets, including when version detection fails.
@macovedj

Copy link
Copy Markdown
Contributor Author

Updated this to use the build-script/version-gated approach.

The guest runtime build script now emits wasi_has_initialize only for WASI targets when rustc is at least 1.100. With that cfg, run_ctors_once becomes a no-op and relies on _initialize while older WASI compilers and
non-WASI targets retain the existing manual __wasm_call_ctors() fallback. Detection failure also retains the fallback.

I removed the .init_array marker as part of this change, since the explicit compiler gate now distinguishes the cases instead of dynamically detecting whether startup already ran.

Comment thread crates/guest-rust/Cargo.toml Outdated
Read the compiler minor version directly in the runtime build script, following the wasmparser pattern. Preserve the constructor fallback when compiler detection fails.
@alexcrichton
alexcrichton added this pull request to the merge queue Oct 20, 2026
Merged via the queue into bytecodealliance:main with commit de33f02 Oct 20, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants