From 9db00237a41df1a662849437f5d819cb480389bc Mon Sep 17 00:00:00 2001 From: Daniel Fox Franke Date: Mon, 6 Jan 2025 16:21:14 -0500 Subject: [PATCH 1/3] Use .init_array on all WASM targets, not just WASI/Emscripten --- Cargo.toml | 2 +- src/lib.rs | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e0bb786..26236eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0" repository = "/~https://github.com/dtolnay/inventory" rust-version = "1.62" -[target.'cfg(any(target_os = "emscripten", target_os = "wasi"))'.dependencies] +[target.'cfg(target_family = "wasm")'.dependencies] rustversion = "1.0" [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index a4bceeb..b73d533 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -418,7 +418,7 @@ macro_rules! submit { } // Not public API. -#[cfg(any(target_os = "emscripten", target_os = "wasi"))] +#[cfg(target_family = "wasm")] #[doc(hidden)] pub mod __private { #[doc(hidden)] @@ -450,21 +450,24 @@ macro_rules! __do_submit { // 'I'=C init, 'C'=C++ init, 'P'=Pre-terminators and 'T'=Terminators $($used)+ #[cfg_attr( - any( - target_os = "linux", - target_os = "android", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "haiku", - target_os = "illumos", - target_os = "netbsd", - target_os = "openbsd", - target_os = "none", + all( + not(target_family = "wasm"), + any( + target_os = "linux", + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "haiku", + target_os = "illumos", + target_os = "netbsd", + target_os = "openbsd", + target_os = "none", + ) ), link_section = ".init_array", )] #[cfg_attr( - any(target_os = "emscripten", target_os = "wasi"), + target_family = "wasm", $crate::__private::attr( any(all(stable, since(1.85)), since(2024-12-18)), link_section = ".init_array", From 4ca062aeb2bd1345bbf4bda3e4273540fc5e9636 Mon Sep 17 00:00:00 2001 From: Daniel Fox Franke Date: Tue, 7 Jan 2025 10:28:01 -0500 Subject: [PATCH 2/3] Ensure that constructors are idempotent on WASM --- src/lib.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index b73d533..33d42c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,6 +137,8 @@ pub struct Registry { pub struct Node { pub value: &'static dyn ErasedNode, pub next: UnsafeCell>, + #[cfg(target_family = "wasm")] + pub initialized: core::sync::atomic::AtomicBool, } // The `value` is Sync, and `next` is only mutated during submit, which is prior @@ -188,6 +190,19 @@ impl Registry { // SAFETY: requires type of *new.value matches the $ty surrounding the // declaration of this registry in inventory::collect macro. unsafe fn submit(&'static self, new: &'static Node) { + // The WASM linker uses an unreliable heuristic to determine whether a + // module is a "command-style" linkage, for which it will insert a call + // to `__wasm_call_ctors` at the top of every exported function. It + // expects that the embedder will call into such modules only once per + // instantiation. If this heuristic goes wrong, we can end up having our + // constructors invoked multiple times, which without this safeguard + // would lead to our registry's linked list becoming circular. On + // non-WASM platforms, this check is unnecessary, so we skip it. + #[cfg(target_family = "wasm")] + if new.initialized.swap(true, Ordering::Relaxed) { + return; + } + let mut head = self.head.load(Ordering::Relaxed); loop { unsafe { @@ -435,6 +450,8 @@ macro_rules! __do_submit { static __INVENTORY: $crate::Node = $crate::Node { value: &{ $($value)* }, next: $crate::core::cell::UnsafeCell::new($crate::core::option::Option::None), + #[cfg(target_family = "wasm")] + initialized: $crate::core::sync::atomic::AtomicBool::new(false), }; #[cfg_attr(any(target_os = "linux", target_os = "android"), link_section = ".text.startup")] From 06c226f7c37d09a77f37ccfbe065c1f5e8d61ad7 Mon Sep 17 00:00:00 2001 From: Daniel Fox Franke Date: Tue, 7 Jan 2025 11:14:29 -0500 Subject: [PATCH 3/3] Document WebAssembly support --- README.md | 6 +++--- src/lib.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8dc2941..d417828 100644 --- a/README.md +++ b/README.md @@ -102,9 +102,9 @@ registered at the time that dlopen occurs. [`ctor`]: /~https://github.com/mmastrac/rust-ctor -Platform support includes Linux, macOS, iOS, FreeBSD, Android, Windows, and a -few others. Beyond this, other platforms will simply find that no plugins have -been registered. +Platform support includes Linux, macOS, iOS, FreeBSD, Android, Windows, +WebAssembly, and a few others. Beyond this, other platforms will simply find +that no plugins have been registered. For a different approach to plugin registration that *does not* involve life-before-main, see the [`linkme`] crate. diff --git a/src/lib.rs b/src/lib.rs index 33d42c9..5d9e323 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -101,6 +101,37 @@ //! //! There is no guarantee about the order that plugins of the same type are //! visited by the iterator. They may be visited in any order. +//! +//! ## WebAssembly and constructors +//! +//! `inventory` supports all WebAssembly targets, including +//! `wasm*-unknown-unknown`. However, in unusual circumstances, ensuring that +//! constructors run may require some extra effort. The WASM linker will +//! generate a function `extern "C" unsafe fn __wasm_call_ctors()` which calls +//! all constructors when invoked; this function will *not* be exported from the +//! module unless you do so explicitly. Depending on the result of a heuristic, +//! the linker may or may not insert a call to this function from the beginning +//! of every function that your module exports. Specifically, it regards a +//! module as having "command-style linkage" if: +//! +//! * it is not relocatable; +//! * it is not a position-independent executable; +//! * and it does not call `__wasm_call_ctors`, directly or indirectly, from any +//! exported function. +//! +//! The linker expects that the embedder will call into a command-style module +//! only once per instantiation. Violation of this expectation can result in +//! `__wasm_call_ctors` being called multiple times. This is dangerous in +//! general, but safe and mostly harmless in the case of constructors generated +//! by `inventory`, which ensure their own idempotence. +//! +//! If you are building a module which relies on constructors and may be called +//! into multiple times per instance, you should export `__wasm_call_ctors` (or +//! a wrapper around it) and ensure that the embedder calls it immediately after +//! instantiation. Even though `inventory` may work fine without this, it is +//! still good practice, because it avoids unnecessary overhead from repeated +//! constructor invocation. It also can prevent unsoundness if some of your +//! constructors are generated by other crates or other programming languages. #![doc(html_root_url = "https://docs.rs/inventory/0.3.16")] #![no_std]