Skip to content

Commit

Permalink
Merge pull request #76 from dfoxfranke/master
Browse files Browse the repository at this point in the history
Use .init_array on all WASM targets, not just WASI/Emscripten
  • Loading branch information
dtolnay authored Jan 7, 2025
2 parents 7a2a06e + 06c226f commit b131245
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 16 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
75 changes: 63 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -137,6 +168,8 @@ pub struct Registry {
pub struct Node {
pub value: &'static dyn ErasedNode,
pub next: UnsafeCell<Option<&'static Node>>,
#[cfg(target_family = "wasm")]
pub initialized: core::sync::atomic::AtomicBool,
}

// The `value` is Sync, and `next` is only mutated during submit, which is prior
Expand Down Expand Up @@ -188,6 +221,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 {
Expand Down Expand Up @@ -418,7 +464,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)]
Expand All @@ -435,6 +481,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")]
Expand All @@ -450,21 +498,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",
Expand Down

0 comments on commit b131245

Please sign in to comment.