Skip to content

Conversation

@dirkwa
Copy link

@dirkwa dirkwa commented Jan 6, 2026

Summary

This PR adds initialization guard infrastructure to the C# wit-bindgen code generator for reactor (library) components. It ensures __wasm_call_ctors is called before any exported function executes.

Problem

When building .NET WASM components as libraries (reactors without a _start entry point), calling exports directly fails with:

wasmtime:
wasm trap: uninitialized element wasm backtrace: 0: InitializeGlobalTablesForModule

jco/Node.js:
RuntimeError: null function or function signature mismatch at InitializeGlobalTablesForModule

Root Cause Analysis

The issue involves a circular dependency in the .NET NativeAOT-LLVM runtime:

  1. Reactor components have no _start to trigger runtime initialization
  2. When exports are called, RhpReversePInvoke attempts to initialize the runtime
  3. But RhpReversePInvoke itself requires initialized function tables
  4. This causes a crash during initialization, not before it

This PR's Contribution

This PR adds the correct initialization pattern at the wit-bindgen layer:

internal static class WasmInteropInitializer
{
    private static volatile bool _initialized;
    private static readonly object _lock = new object();

    [DllImport("*", EntryPoint = "__wasm_call_ctors")]
    private static extern void WasmCallCtors();

    internal static void EnsureInitialized()
    {
        if (_initialized) return;
        lock (_lock)
        {
            if (_initialized) return;
            WasmCallCtors();
            _initialized = true;
        }
    }
}

Each generated export function calls EnsureInitialized() first:

[UnmanagedCallersOnly(EntryPoint = "test:minimal/plugin@1.0.0#get-id")]
public static unsafe nint wasmExportGetId() {
    MinimalWorld.WasmInteropInitializer.EnsureInitialized();
    // ... rest of export code
}

Limitations

This fix alone does not fully resolve the issue. The complete solution requires changes in:

  • NativeAOT-LLVM runtime: To properly handle reactor initialization where RhpReversePInvoke is called before tables are populated
  • Or componentize-dotnet/wasm-tools adapter: To ensure _initialize is called before any exports

This PR establishes the correct pattern so that when the runtime-level fix is implemented, the initialization will be properly triggered.

Testing

  • Verified generated code includes WasmInteropInitializer class
  • Verified EnsureInitialized() is called in all export functions
  • Confirmed the crash now occurs during initialization (progress from "no init attempted")

Related Issues

Both issues report the same root cause - .NET reactor components crash when exports are called directly because runtime initialization hasn't occurred properly.

Adds infrastructure to call runtime initialization before exported
functions execute. This addresses the "uninitialized element" trap
that occurs when calling exports on standalone .NET library components.

Note: This fix adds the correct initialization call pattern, but full
resolution requires additional changes in the NativeAOT-LLVM runtime
or componentize-dotnet adapter layer to handle the circular dependency
where RhpReversePInvoke needs initialized tables before initialization
can complete.

Changes:
- Add `has_exports` flag to track when exports are generated
- Generate `WasmInteropInitializer` class with thread-safe init guard
- Call `EnsureInitialized()` at the start of every export function
- Initializer calls `__wasm_call_ctors` to trigger runtime init

Related: bytecodealliance/componentize-dotnet#103
Related: bytecodealliance/jco#1173
@jsturtevant
Copy link
Collaborator

I will need to look a bit closer later, but this looks to be similar to #777. fyi @yowl

@jsturtevant
Copy link
Collaborator

jsturtevant commented Jan 7, 2026

@dirkwa looking at ci failures it seems some of the examples need to bring in the proper imports for this new functionality or it needs to be added to the import list in the binding generator

@yowl
Copy link
Collaborator

yowl commented Jan 9, 2026

Is there a repo or a stack trace?

@dirkwa
Copy link
Author

dirkwa commented Jan 9, 2026

Is there a repo or a stack trace?

See here:

bytecodealliance/jco#1173 (comment)

@yowl
Copy link
Collaborator

yowl commented Jan 9, 2026

So would this be fixed by moving to wasi SDK 29 ?

@yowl
Copy link
Collaborator

yowl commented Jan 9, 2026

Note that NAOT-LLVM upgraded to wasi sdk 29 today, so the nugets for that should be out in a few hours.

@dirkwa
Copy link
Author

dirkwa commented Jan 9, 2026

So would this be fixed by moving to wasi SDK 29 ?

I hope so, if the commit will be accepted by the maintainers.

Nevertheless this PR has a reason.

If you look at the full bytecodealliance/jco#1173 there is my test-repo to locally verify it. With the workaround in the comment I was able to sucessfully build it.

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.

3 participants