Skip to content

Add crate for host-side .net component bindings - #1946

Merged
ludfjig merged 7 commits into
mainfrom
component-wire-format
Nov 4, 2026
Merged

ludfjig merged 7 commits into
mainfrom
component-wire-format

Conversation

@ludfjig

@ludfjig ludfjig commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Adds new crate hyperlight-component-csharp which generates c# host bindings from wit. The intended usage is a .NET hyperlight-wasm library, so .net hosts don't need to write rust.

The crate is in rust and not .net because it reuses the component type elaboration from hyperlight-component-util, the same one the rust bindings use. Both generators resolve types and name wire functions the same way. In the future the remaining core (encoding) could be shared too, so they cannot drift.

breakign changes:

  • host_bindgen! and guest_bindgen! encode values little-endian (instead of native endian)
  • Functions are named <instance>#<function>, such as wasi:http/types@0.2.0#[method]fields.get, rather than r#wasi::r#http::[method]fields.get. The old name dropped the interface and version, so functions in different interfaces or versions of one package collided. For example, wasi:clocks/monotonic-clock and wasi:clocks/wall-clock both registered now as r#wasi::r#clocks::now, and the second silently replaced the first.

The C# bindings support resources the host defines, like the Rust host bindings.

Testing: a test-only native library (src/tests/csharp_host/native) runs witguest in a sandbox, and the C# tests call it through the generated bindings. guest.wit got world-level functions, an inline interface and more composite types, so the Rust and C# hosts are tested against the same guest.

@ludfjig ludfjig added the C-enhancement For PRs adding features, improving functionality, docs, tests, etc. label Oct 16, 2026
@hyperlight-gh-bot

This comment has been minimized.

@syntactically

Copy link
Copy Markdown
Member

I didn't look at this in detail yet, but I did notice it is not using the same component type elaboration infrastructure from the Rust macros. I think this is probably a mistake as it does not seem to me that most of the more complex cases are possible to get right without the explicit representation of type variables. I think pretty much the only thing in State that is specific to rust is the root_mod field which uses the Mod type that represents the submodule/subtrait structure that is used for Rust. Maybe you can make State parametrised on the type of the root module like that and have a different alternative for holding the information you need for C# syntax trees? Then I think you can follow the same pattern as hyperlight_component_macro to drive the host/guest emit using read_wit_type and then having your own equivalents of rtype::emit_toplevel and host/guest::emit_toplevel that follow the structure of the fully elaborated component inductively. (Probably some of the BindgenInputParams/source stuff can be moved from hyperlight_component_macro to hyperlight_component_util also to reduce duplication).

@ludfjig
ludfjig force-pushed the component-wire-format branch from 9ab4272 to fcd4c0f Compare October 20, 2026 16:36
@ludfjig

ludfjig commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

I didn't look at this in detail yet, but I did notice it is not using the same component type elaboration infrastructure from the Rust macros. I think this is probably a mistake as it does not seem to me that most of the more complex cases are possible to get right without the explicit representation of type variables. I think pretty much the only thing in State that is specific to rust is the root_mod field which uses the Mod type that represents the submodule/subtrait structure that is used for Rust. Maybe you can make State parametrised on the type of the root module like that and have a different alternative for holding the information you need for C# syntax trees? Then I think you can follow the same pattern as hyperlight_component_macro to drive the host/guest emit using read_wit_type and then having your own equivalents of rtype::emit_toplevel and host/guest::emit_toplevel that follow the structure of the fully elaborated component inductively. (Probably some of the BindgenInputParams/source stuff can be moved from hyperlight_component_macro to hyperlight_component_util also to reduce duplication).

Thanks, reworked it this way. The C# generator now drives off read_wit_type and the elaborated component, with its own equivalents of rtypes/host emit_toplevel in types.rs and host.rs. State is parametrised on the root module (defaulting to Mod), and C# uses resolve_bound_var and emit_fn_hl_name from it. I left BindgenInputParams in the macro crate, since C# takes a WitSource and calls read_wit_type, so the source handling is already shared and the parser's only user is the macro syntax.

@hyperlight-gh-bot

This comment has been minimized.

@ludfjig
ludfjig marked this pull request as ready for review October 20, 2026 17:32
Copilot AI balanced review requested due to automatic review settings October 20, 2026 17:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Supported inputs can produce uncompilable bindings, and decoding and structural hashing defects remain.

11 open issues
What changed in this PR

Adds C# host bindings for Hyperlight components, sharing WIT type resolution and wire naming with the Rust generators.

Changes:

  • C# generation and runtime support for component values and calls.
  • Little-endian encoding and interface-qualified wire names.
  • Cross-language tests, documentation, CI and publishing integration.
File Description
src/​tests/​component_wire/​wire.wit Shared wire-format fixture.
src/​tests/​component_wire/​vectors.txt Cross-language encoding vectors.
src/​tests/​component_wire/​csharp/​WireTests.cs Encoding and decoding checks.
src/​tests/​component_wire/​csharp/​RuntimeTests.cs Runtime validation checks.
src/​tests/​component_wire/​csharp/​Program.cs Test runner and assertions.
src/​tests/​component_wire/​csharp/​Fakes.cs Fake guest and registry.
src/​tests/​component_wire/​csharp/​EdgeTests.cs Naming and type checks.
src/​tests/​component_wire/​csharp/​edge.wit Edge-case WIT fixture.
src/​tests/​component_wire/​csharp/​ComponentTests.csproj Generates and compiles test bindings.
src/​hyperlight_host/​tests/​component_wire_test.rs Rust wire-vector checks.
src/​hyperlight_component_util/​src/​util.rs WIT path resolution fallback.
src/​hyperlight_component_util/​src/​host.rs Host instance naming context.
src/​hyperlight_component_util/​src/​hl.rs Wire names and little-endian encoding.
src/​hyperlight_component_util/​src/​guest.rs Guest instance naming context.
src/​hyperlight_component_util/​src/​emit.rs Shared generation state.
src/​hyperlight_component_csharp/​src/​types.rs Type discovery and validation.
src/​hyperlight_component_csharp/​src/​tests.rs Generator tests.
src/​hyperlight_component_csharp/​src/​Runtime.cs C# codecs and support types.
src/​hyperlight_component_csharp/​src/​names.rs C# identifier helpers.
src/​hyperlight_component_csharp/​src/​lib.rs Generator API and namespaces.
src/​hyperlight_component_csharp/​src/​host.rs Call wrappers and import registration.
src/​hyperlight_component_csharp/​src/​codec.rs Type and codec generation.
src/​hyperlight_component_csharp/​README.md Generator usage documentation.
src/​hyperlight_component_csharp/​examples/​csharp_bindgen.rs Command-line generation example.
src/​hyperlight_component_csharp/​Cargo.toml Crate definition.
Justfile Build and test integration.
docs/​README.md Wire-format documentation link.
docs/​how-to-make-releases.md Published crate list.
docs/​component-wire-format.md Wire contract and C# mappings.
CHANGELOG.md Features and breaking changes.
Cargo.toml Workspace registration.
Cargo.lock Crate dependency entry.
.github/​workflows/​dep_code_checks.yml C# test execution.
.github/​workflows/​CargoPublish.yml Crate publishing integration.

Review effort: Balanced

@ludfjig
ludfjig force-pushed the component-wire-format branch from fcd4c0f to 92cc474 Compare October 20, 2026 17:58
@hyperlight-gh-bot

This comment has been minimized.

andreiltd
andreiltd previously approved these changes Oct 22, 2026

@andreiltd andreiltd left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! This looks very clean! I only reviewed rust part of the patch as I don't have prior experience with c#. That said, the PR will likely need another pair of eyes. 👀

@simongdavies simongdavies left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this should be in hyperlight rather than hyperlight-wasm, I appreciate we have the rust example but we also have a Rust hosting API so we can show how it is used completely. It feels like half a solution here, that said if we moved it to hyperlight-wasm that wouldn't be solved, we still need a dotnet hosting API.

I think we should consider this in our investigation into the feasibility of having a single host API instead of hyperlight-js/hypelright-wasm etc.

Comment thread src/tests/csharp_host/Program.cs Resolved
@ludfjig

ludfjig commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

I don't know if this should be in hyperlight rather than hyperlight-wasm, I appreciate we have the rust example but we also have a Rust hosting API so we can show how it is used completely. It feels like half a solution here, that said if we moved it to hyperlight-wasm that wouldn't be solved, we still need a dotnet hosting API.

I believe this should definitely be here in hyperlight, it's the same as host_bindgen! but for c#, and is unrelated to wasm.

I think we should consider this in our investigation into the feasibility of having a single host API instead of hyperlight-js/hypelright-wasm etc.

I agree we should consider this, but since there's no concrete plan for it yet, I think it's better we get this in. And this will still be needed even if we manage to make a single host api if we want c# host bindigs (at least some form of this)

@syntactically

Copy link
Copy Markdown
Member

I don't know if this should be in hyperlight rather than hyperlight-wasm, I appreciate we have the rust example but we also have a Rust hosting API so we can show how it is used completely. It feels like half a solution here, that said if we moved it to hyperlight-wasm that wouldn't be solved, we still need a dotnet hosting API.

I think we should consider this in our investigation into the feasibility of having a single host API instead of hyperlight-js/hypelright-wasm etc.

I definitely think it should stay in hl core. The wit macros are usable entirely without hyperlight-wasm and I think that is one promising route to having a more uniform approach to host/guest API. Especially since there is room to improve the performance of guest function dispatch by eliminating names when the wit macros are in use.

@ludfjig
ludfjig force-pushed the component-wire-format branch 3 times, most recently from c907c97 to ad25c60 Compare October 30, 2026 18:11
@hyperlight-gh-bot

This comment has been minimized.

@ludfjig
ludfjig force-pushed the component-wire-format branch from ad25c60 to 2e27c96 Compare October 30, 2026 19:56
@hyperlight-gh-bot

This comment has been minimized.

@ludfjig
ludfjig force-pushed the component-wire-format branch from 2e27c96 to 616d788 Compare October 30, 2026 22:25
@hyperlight-gh-bot

This comment has been minimized.

@ludfjig ludfjig added this to the v0.19.0 milestone Nov 2, 2026
Comment thread src/tests/csharp_host/Sandbox.cs Resolved
jsturtevant
jsturtevant previously approved these changes Nov 3, 2026
@jsturtevant

Copy link
Copy Markdown
Contributor

The addition of the test for real wit was really helpful. Thank you!

@hyperlight-gh-bot

This comment has been minimized.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Wire names were the module path plus the function, such as
r#wasi::r#clocks::now. They dropped the interface and version, so
functions in different interfaces collided. They are now
<instance>#<function>, such as wasi:clocks/monotonic-clock@0.2.0#now.

Values are encoded little-endian. Every supported target is
little-endian, so the bytes are unchanged.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Add world-level functions, an inline export interface, and nested and
composite types to guest.wit. Other host bindings can then be tested
against witguest.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
State takes its output type as a parameter, which defaults to Mod.
new, clone and resolve_bound_var work for any output, and so does
emit_fn_hl_name. read_wit_type also works outside a cargo build.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
A test-only native library runs witguest in a sandbox, and the C# tests
call it through the generated bindings. They run in the X64 build and
test jobs, which have the guests and the .NET SDK.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
@ludfjig
ludfjig force-pushed the component-wire-format branch from ca2b8f7 to 26c823e Compare November 4, 2026 17:13
@hyperlight-gh-bot

Copy link
Copy Markdown

Benchmark Results

Measured commit: 26c823e13559
Baseline commit: 6c17f7cd4e78

kvm / amd (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 776.50 ns (➖ 1.01x slower)
vec_bytes 583.98 ns (➖ 1.01x slower)
373.49 µs (➖ 1.00x slower)

payload_allocation

slot_pool_segmented
262144 520.58 ns (➖ 1.00x faster)
65536 141.30 ns (➖ 1.00x slower)

sandboxes

create_initialized_and_drop
medium 13.09 ms (➖ 1.01x slower)

slot_pool

alloc_dealloc_128 alloc_dealloc_4096 alloc_dealloc_1500
7.68 ns (➖ 1.00x faster) 7.69 ns (➖ 1.00x faster) 7.68 ns (➖ 1.00x faster)

snapshot_files

load_snapshot_unverified
small 100.59 µs (➖ 1.01x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
262144 7.11 µs (➖ 1.01x faster) 6.96 µs (➖ 1.15x faster)
65536 1.93 µs (➖ 1.07x faster) 2.03 µs (➖ 1.06x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
262144 26.88 µs (➖ 1.01x faster)
65536 6.15 µs (➖ 1.04x faster) 6.28 µs (➖ 1.01x faster)
8192 1.02 µs (➖ 1.00x faster) 1.02 µs (➖ 1.01x slower)
kvm / intel (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 762.14 ns (➖ 1.02x slower)
vec_bytes 570.28 ns (➖ 1.03x slower)
697.65 µs (➖ 1.04x slower)

payload_allocation

slot_pool_segmented
262144 646.01 ns (➖ 1.26x slower)
65536 174.03 ns (➖ 1.00x faster)

sandboxes

create_initialized_and_drop
medium 14.00 ms (➖ 1.05x faster)

slot_pool

alloc_dealloc_128 alloc_dealloc_4096 alloc_dealloc_1500
7.45 ns (➖ 1.08x slower) 7.44 ns (➖ 1.07x slower) 7.44 ns (➖ 1.06x slower)

snapshot_files

load_snapshot_unverified
small 56.24 µs (➖ 1.09x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
262144 8.08 µs (➖ 1.06x slower) 8.05 µs (➖ 1.05x slower)
65536 2.33 µs (➖ 1.07x slower) 2.27 µs (➖ 1.04x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
262144 31.88 µs (➖ 1.06x slower)
65536 7.50 µs (➖ 1.00x faster) 7.51 µs (➖ 1.01x slower)
8192 759.26 ns (➖ 1.04x slower) 756.83 ns (➖ 1.02x faster)
mshv3 / amd (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 927.73 ns (➖ 1.03x slower)
vec_bytes 724.92 ns (➖ 1.01x slower)
340.16 µs (➖ 1.02x slower)

payload_allocation

slot_pool_segmented
262144 673.65 ns (➖ 1.01x slower)
65536 182.40 ns (➖ 1.00x slower)

sandboxes

create_initialized_and_drop
medium 68.04 ms (➖ 1.03x slower)

slot_pool

alloc_dealloc_128 alloc_dealloc_4096 alloc_dealloc_1500
9.88 ns (➖ 1.00x slower) 9.88 ns (➖ 1.00x faster) 9.87 ns (➖ 1.00x faster)

snapshot_files

load_snapshot_unverified
small 109.01 µs (➖ 1.01x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
262144 9.08 µs (➖ 1.00x faster) 9.43 µs (➖ 1.03x slower)
65536 2.44 µs (➖ 1.01x faster) 2.60 µs (➖ 1.03x faster)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
262144 34.80 µs (➖ 1.05x faster)
65536 8.03 µs (➖ 1.01x faster) 8.04 µs (➖ 1.01x faster)
8192 1.32 µs (➖ 1.04x slower) 1.40 µs (➖ 1.06x faster)
mshv3 / intel (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 978.19 ns (➖ 1.04x slower)
vec_bytes 675.42 ns (➖ 1.00x slower)
679.97 µs (➖ 1.04x slower)

payload_allocation

slot_pool_segmented
262144 649.94 ns (➖ 1.03x faster)
65536 254.83 ns (➖ 1.07x slower)

sandboxes

create_initialized_and_drop
medium 73.60 ms (➖ 1.10x faster)

slot_pool

alloc_dealloc_128 alloc_dealloc_4096 alloc_dealloc_1500
9.12 ns (➖ 1.03x slower) 9.86 ns (➖ 1.13x slower) 8.69 ns (➖ 1.13x faster)

snapshot_files

load_snapshot_unverified
small 53.99 µs (➖ 1.00x faster)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
262144 7.91 µs (➖ 1.01x faster) 7.94 µs (➖ 1.00x faster)
65536 2.28 µs (➖ 1.01x faster) 2.27 µs (➖ 1.01x faster)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
262144 38.61 µs (➖ 1.00x slower)
65536 7.42 µs (➖ 1.01x faster) 7.60 µs (➖ 1.01x slower)
8192 888.38 ns (➖ 1.03x faster) 893.49 ns (➖ 1.03x faster)
hyperv-ws2025 / amd (Windows) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 1.17 µs (➖ 1.01x slower)
vec_bytes 782.51 ns (➖ 1.08x faster)
1.85 ms (➖ 1.00x slower)

payload_allocation

slot_pool_segmented
262144 790.14 ns (➖ 1.01x faster)
65536 232.77 ns (➖ 1.02x faster)

sandboxes

create_initialized_and_drop
medium 10.95 ms (➖ 1.00x slower)

slot_pool

alloc_dealloc_128 alloc_dealloc_4096 alloc_dealloc_1500
10.64 ns (➖ 1.02x faster) 10.73 ns (➖ 1.02x faster) 12.24 ns (➖ 1.11x slower)

snapshot_files

load_snapshot_unverified
small 590.90 µs (➖ 1.10x faster)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
262144 8.48 µs (➖ 1.11x faster) 8.60 µs (➖ 1.00x faster)
65536 2.53 µs (➖ 1.12x faster) 2.52 µs (➖ 1.00x faster)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
262144 37.48 µs (➖ 1.01x slower)
65536 8.77 µs (➖ 1.05x faster) 8.64 µs (➖ 1.00x slower)
8192 1.34 µs (➖ 1.01x faster) 1.38 µs (➖ 1.02x faster)
hyperv-ws2025 / intel (Windows) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 1.22 µs (➖ 1.01x faster)
vec_bytes 773.06 ns (➖ 1.01x slower)
2.95 ms (➖ 1.07x faster)

payload_allocation

slot_pool_segmented
262144 782.75 ns (➖ 1.03x slower)
65536 214.68 ns (➖ 1.01x slower)

sandboxes

create_initialized_and_drop
medium 12.70 ms (➖ 1.01x faster)

slot_pool

alloc_dealloc_128 alloc_dealloc_4096 alloc_dealloc_1500
9.75 ns (➖ 1.03x faster) 10.25 ns (➖ 1.01x faster) 9.69 ns (➖ 1.02x faster)

snapshot_files

load_snapshot_unverified
small 1.07 ms (➖ 1.41x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
262144 7.50 µs (➖ 1.01x faster) 7.65 µs (➖ 1.02x slower)
65536 2.44 µs (➖ 1.06x slower) 2.30 µs (➖ 1.01x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
262144 49.19 µs (➖ 1.03x slower)
65536 8.33 µs (➖ 1.00x faster) 8.30 µs (➖ 1.01x faster)
8192 1.07 µs (➖ 1.02x slower) 1.19 µs (➖ 1.14x slower)

Reported by cargo ci bench-report --candidate run:41875520622 --config-file bench_report.toml.

@ludfjig
ludfjig dismissed simongdavies’s stale review November 4, 2026 18:36

this shoudl be in hyperilght rather than hyperlight-wasm

@ludfjig
ludfjig merged commit 4d2ab91 into main Nov 4, 2026
108 of 126 checks passed
@ludfjig
ludfjig deleted the component-wire-format branch November 4, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C-enhancement For PRs adding features, improving functionality, docs, tests, etc.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants