bytecodealliance / bytecodealliance/componentize-dotnet

Feature Request: Support for jco transpilation / V8 runtime

Open
#103 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
147
Forks
18
PR merge metrics
No merged PRs in 30d

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

```csharp
// 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

```bash
dotnet build
# Output: bin/Debug/net10.0/wasi-wasm/native/MyPlugin.wasm
```

### 3. Transpile with jco

```bash
npx @bytecodealliance/jco transpile MyPlugin.wasm -o jco-output --name mymodule --tla-compat
```

### 4. Try to use in Node.js

```javascript
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:

```wat
(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:
1. Instantiates the WASM module
2. Sets `_initialized = true` after instantiation
3. 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:
1. Doesn't happen correctly
2. Happens but the table isn't shared properly between the shim and core modules

## Workarounds Attempted (None Successful)

1. **Manual `_initialize()` call** - Added explicit call in jco output; no effect
2. **Manual `InitializeModules()` call** - Crashes because `_initialize` already calls it
3. **Different jco flags** - `--instantiation sync`, `--tla-compat`, etc.
4. **Removing `[ThreadStatic]`** - Fixed build issues but not runtime

## Questions for the Team

1. Is jco/V8 support planned for componentize-dotnet?
2. Is this a known limitation of the NativeAOT-LLVM approach?
3. Are there any workarounds to make the indirect call table work in V8?
4. Would a different compilation approach (e.g., Mono interpreter) work better with jco?

## Use Case

We're building a plugin system for [Signal K](https://signalk.org/) 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](https://github.com/bytecodealliance/componentize-dotnet) - States "works with Wasmtime and WAMR"
- [jco repository](https://github.com/bytecodealliance/jco)
- [Our Signal K WASM implementation](https://github.com/dirkwa/signalk-server/tree/WASM_WASIX) - 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`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.