Repository navigation
Feature Request: Support for jco transpilation / V8 runtime #103
Description
Feature Request: Support for jco transpilation / V8 runtime
Summary
Components built with componentize-dotnet cannot be executed in Node.js via jco transpilation. The component builds and transpiles successfully, but calling any exported function results in RuntimeError: null function or function signature mismatch.
Environment
- componentize-dotnet: 0.7.0-preview00010
- .NET SDK: 10.0.100-preview.7
- jco: 1.10.0
- Node.js: 22.x
- OS: Windows 11 / Raspberry Pi OS (both tested)
Reproduction Steps
1. Create a minimal .NET WASI component
// PluginImpl.cs
using SignalkPluginWorld.wit.exports.signalk.plugin.v1_0_0;
namespace SignalkPluginWorld.wit.exports.signalk.plugin.v1_0_0;
public class PluginImpl : IPlugin
{
public static string PluginId() => "test-plugin";
public static string PluginName() => "Test Plugin";
public static string PluginSchema() => "{}";
public static int PluginStart(string config) => 0;
public static int PluginStop() => 0;
}2. Build the component
dotnet build
# Output: bin/Debug/net10.0/wasi-wasm/native/MyPlugin.wasm3. Transpile with jco
npx @bytecodealliance/jco transpile MyPlugin.wasm -o jco-output --name mymodule --tla-compat4. Try to use in Node.js
import * as module from './jco-output/mymodule.js';
await module.$init;
console.log('Init complete');
// This crashes:
const id = module.plugin.pluginId();Expected Behavior
After $init completes, calling pluginId() should return the string "test-plugin".
Actual Behavior
RuntimeError: null function or function signature mismatch
at pluginId (wasm://wasm/00a1b2c3:wasm-function[1234]:0x12345)
at pluginId (file:///path/to/jco-output/mymodule.js:6220:28)
Technical Analysis
What the WASM exports
Using jco print on the compiled component, we can see:
(export "_initialize" (func $_initialize))
(export "InitializeModules" (func $...))
(export "signalk:plugin/plugin@1.0.0#plugin-id" (func $...))
(export "signalk:plugin/plugin@1.0.0#plugin-name" (func $...))
...
(table (;0;) 3983 3983 funcref)The component:
- Exports
_initialize(WASI reactor initialization) - Exports
InitializeModules(.NET runtime initialization) - Has a function table with 3983 entries
- Exports the expected WIT functions
What jco generates
The generated JavaScript correctly:
- Instantiates the WASM module
- Sets
_initialized = trueafter instantiation - Assigns function references:
plugin100PluginId = exports1['signalk:plugin/plugin@1.0.0#plugin-id']
Where it fails
When pluginId() is called, it invokes the WASM function which internally uses call_indirect to dispatch through the function table. In V8, the table entries are null, causing the crash.
Why it works in Wasmtime
In Wasmtime, the _initialize function properly populates the indirect call table. In V8 (via jco transpilation), this initialization either:
- Doesn't happen correctly
- Happens but the table isn't shared properly between the shim and core modules
Workarounds Attempted (None Successful)
- Manual
_initialize()call - Added explicit call in jco output; no effect - Manual
InitializeModules()call - Crashes because_initializealready calls it - Different jco flags -
--instantiation sync,--tla-compat, etc. - Removing
[ThreadStatic]- Fixed build issues but not runtime
Questions for the Team
- Is jco/V8 support planned for componentize-dotnet?
- Is this a known limitation of the NativeAOT-LLVM approach?
- Are there any workarounds to make the indirect call table work in V8?
- Would a different compilation approach (e.g., Mono interpreter) work better with jco?
Use Case
We're building a plugin system for Signal K marine data server. The server runs on Node.js and we want to support WASM plugins in multiple languages. Currently:
- AssemblyScript: Works perfectly
- Rust: Works perfectly
- C#/.NET: Builds but can't execute
Adding .NET support would greatly benefit the enterprise/marine industry developers who prefer C#.
Related Links
- componentize-dotnet README - States "works with Wasmtime and WAMR"
- jco repository
- Our Signal K WASM implementation - in development
Complete Reproduction Repository
A complete working example demonstrating this issue is available at:
https://github.com/dirkwa/signalk-server/tree/WASM_WASIX/examples/wasm-plugins/anchor-watch-dotnet
Labels suggestion: enhancement, runtime-support, jco
Activity
Merge pull request #4 from dirkwa/WASM_WASIX
dirkwa commentedon Jan 6, 2026
Update: Issue also affects wasmtime (not jco/V8 specific)
After extensive testing, I discovered this is not a jco/V8-specific issue - the component also fails in wasmtime itself with the same root cause.
Test Results
null function or function signature mismatchnull function or function signature mismatchwasm trap: uninitialized elementwasm trap: uninitialized elementwasm trap: uninitialized elementNote: wasmtime 23.0.2 is the version documented as the target in componentize-dotnet's README.
Wasmtime Error Details
Implications
This suggests the bug is in componentize-dotnet / .NET NativeAOT WASM code generation, not in how jco transpiles or how V8 handles the component. The indirect call table elements are not being properly initialized in the generated WASM output.
The component passes
wasm-tools validate, so it's structurally valid - the issue is with the runtime initialization code generated by NativeAOT.Test Harness
I created a Rust test harness using the wasmtime crate to verify this. The harness uses
wasmtime::component::bindgen!to generate bindings from the WIT file, then instantiates and calls the component - same error occurs.Repository with full reproduction: https://github.com/dirkwa/jco-issue-1173
This may need to be escalated to dotnet/runtime since the issue appears to be in the NativeAOT-LLVM WASI-WASM code generation.
fix(csharp): Add runtime initialization guard for reactor components
fix(csharp): Add runtime initialization guard for reactor components