Skip to content

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

Open
ludfjig wants to merge 7 commits into
mainfrom
component-wire-format
Open

ludfjig wants to merge 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/[email protected]#[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 kind/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.

@@ -0,0 +1,43 @@
// SPDX-License-Identifier: Apache-2.0

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.

Is there a reason not to use a dotnet test framework like xunit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wanted to keep it as light as possible and have no requirements to download anything

@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.

Comment thread src/hyperlight_component_util/src/hl.rs Resolved
Comment thread CHANGELOG.md Resolved

@jsturtevant jsturtevant 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.

overall LGTM, a few comments and really cool way to enable .net developers to write hyperlight guests and I love all the tests!

We don't really have a way to mark a package as experimental. Maybe this is something we want to think about in 1.0? I am sure we will have more of these in the future.

@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.

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/[email protected]#now.

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

Signed-off-by: Ludvig Liljenberg <[email protected]>
Signed-off-by: Ludvig Liljenberg <[email protected]>
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 <[email protected]>
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 <[email protected]>
Signed-off-by: Ludvig Liljenberg <[email protected]>
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 <[email protected]>
@ludfjig
ludfjig force-pushed the component-wire-format branch from 2e27c96 to 616d788 Compare October 30, 2026 22:25
@hyperlight-gh-bot

Copy link
Copy Markdown

Benchmark Results

Measured commit: 616d7889bc27
Baseline commit: b99eb5f65293

kvm / amd (Linux) (🌟 5.99x)

Top improvements

  • sandboxes/create_initialized_and_drop/medium — 🌟 5.99x faster

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
373.22 µs (➖ 1.00x slower)
byte_chunks 798.60 ns (➖ 1.06x slower)
vec_bytes 587.69 ns (➖ 1.01x slower)

payload_allocation

slot_pool_segmented
65536 141.23 ns (➖ 1.01x faster)
262144 520.81 ns (➖ 1.02x faster)

sandboxes

create_initialized_and_drop
medium 13.40 ms (🌟 5.99x faster)

slot_pool

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

snapshot_files

load_snapshot_unverified
small 117.09 µs (➖ 1.00x faster)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 1.94 µs (➖ 1.09x faster) 2.04 µs (➖ 1.07x slower)
262144 7.19 µs (➖ 1.02x slower) 8.59 µs (➖ 1.20x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.01 µs (➖ 1.00x slower) 1.00 µs (➖ 1.02x faster)
65536 6.22 µs (➖ 1.03x faster) 6.48 µs (➖ 1.01x slower)
262144 26.89 µs (➖ 1.03x faster)
kvm / intel (Linux) (🌟 5.58x)

Top improvements

  • sandboxes/create_initialized_and_drop/medium — 🌟 5.58x faster

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
766.72 µs (➖ 1.03x slower)
byte_chunks 813.76 ns (➖ 1.01x slower)
vec_bytes 613.37 ns (➖ 1.01x slower)

payload_allocation

slot_pool_segmented
65536 205.24 ns (➖ 1.01x faster)
262144 590.33 ns (➖ 1.01x faster)

sandboxes

create_initialized_and_drop
medium 15.56 ms (🌟 5.58x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
8.08 ns (➖ 1.01x faster) 8.21 ns (➖ 1.01x slower) 8.26 ns (➖ 1.01x slower)

snapshot_files

load_snapshot_unverified
small 64.36 µs (➖ 1.01x faster)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.57 µs (➖ 1.01x slower) 2.51 µs (➖ 1.01x faster)
262144 8.96 µs (➖ 1.01x faster) 9.09 µs (➖ 1.00x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 846.28 ns (➖ 1.00x faster) 854.01 ns (➖ 1.03x faster)
65536 8.49 µs (➖ 1.00x faster) 8.40 µs (➖ 1.02x faster)
262144 35.30 µs (➖ 1.02x faster)
mshv3 / amd (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
318.09 µs (➖ 1.00x slower)
byte_chunks 944.33 ns (➖ 1.03x slower)
vec_bytes 703.38 ns (➖ 1.01x faster)

payload_allocation

slot_pool_segmented
65536 181.24 ns (➖ 1.03x faster)
262144 670.06 ns (➖ 1.01x faster)

sandboxes

create_initialized_and_drop
medium 66.52 ms (➖ 1.10x slower)

slot_pool

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

snapshot_files

load_snapshot_unverified
small 108.17 µs (➖ 1.01x faster)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.51 µs (➖ 1.02x slower) 2.64 µs (➖ 1.00x faster)
262144 9.10 µs (➖ 1.00x faster) 9.99 µs (➖ 1.06x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.31 µs (➖ 1.05x slower) 1.27 µs (➖ 1.02x faster)
65536 8.02 µs (➖ 1.09x faster) 7.98 µs (➖ 1.01x faster)
262144 34.69 µs (➖ 1.01x faster)
mshv3 / intel (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
693.51 µs (➖ 1.11x faster)
byte_chunks 933.87 ns (➖ 1.01x slower)
vec_bytes 642.79 ns (➖ 1.01x faster)

payload_allocation

slot_pool_segmented
65536 259.47 ns (➖ 1.01x faster)
262144 712.29 ns (➖ 1.04x faster)

sandboxes

create_initialized_and_drop
medium 74.28 ms (➖ 1.15x slower)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
8.70 ns (➖ 1.00x slower) 8.71 ns (➖ 1.00x faster) 8.69 ns (➖ 1.00x slower)

snapshot_files

load_snapshot_unverified
small 57.59 µs (➖ 1.05x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.26 µs (➖ 1.00x slower) 2.27 µs (➖ 1.00x slower)
262144 7.86 µs (➖ 1.00x faster) 8.09 µs (➖ 1.03x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 904.47 ns (➖ 1.03x faster) 895.92 ns (➖ 1.03x faster)
65536 7.47 µs (➖ 1.01x faster) 7.49 µs (➖ 1.03x faster)
262144 41.58 µs (➖ 1.03x slower)
hyperv-ws2025 / amd (Windows) (🌟 9.48x)

Top improvements

  • sandboxes/create_initialized_and_drop/medium — 🌟 9.48x faster

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
1.86 ms (➖ 1.01x faster)
byte_chunks 1.15 µs (➖ 1.01x slower)
vec_bytes 752.06 ns (➖ 1.02x faster)

payload_allocation

slot_pool_segmented
65536 230.53 ns (➖ 1.00x faster)
262144 793.10 ns (➖ 1.00x slower)

sandboxes

create_initialized_and_drop
medium 11.39 ms (🌟 9.48x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
10.77 ns (➖ 1.01x slower) 10.65 ns (➖ 1.00x faster) 11.69 ns (➖ 1.10x slower)

snapshot_files

load_snapshot_unverified
small 742.01 µs (➖ 1.05x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.52 µs (➖ 1.11x faster) 2.52 µs (➖ 1.04x faster)
262144 8.51 µs (➖ 1.11x faster) 8.62 µs (➖ 1.01x faster)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.38 µs (➖ 1.01x faster) 1.38 µs (➖ 1.05x slower)
65536 8.85 µs (➖ 1.01x faster) 8.53 µs (➖ 1.06x faster)
262144 39.53 µs (➖ 1.13x faster)
hyperv-ws2025 / intel (Windows) (🌟 6.80x)

Top improvements

  • sandboxes/create_initialized_and_drop/medium — 🌟 6.80x faster

Benchmark Results

function_call_codec

decode_vec_bytes_copy encode_control
3.00 ms (➖ 1.04x faster)
byte_chunks 1.17 µs (➖ 1.00x faster)
vec_bytes 763.38 ns (➖ 1.01x faster)

payload_allocation

slot_pool_segmented
65536 205.48 ns (➖ 1.01x faster)
262144 825.64 ns (➖ 1.10x slower)

sandboxes

create_initialized_and_drop
medium 16.52 ms (🌟 6.80x faster)

slot_pool

alloc_dealloc_4096 alloc_dealloc_128 alloc_dealloc_1500
9.94 ns (➖ 1.12x faster) 9.62 ns (➖ 1.15x faster) 10.56 ns (➖ 1.08x slower)

snapshot_files

load_snapshot_unverified
small 603.03 µs (➖ 1.01x slower)

virtq_readonly

slot_pool_segmented slot_pool_segmented_fragmented
65536 2.44 µs (➖ 1.05x slower) 2.29 µs (➖ 1.00x faster)
262144 7.45 µs (➖ 1.02x faster) 7.61 µs (➖ 1.01x slower)

virtq_readwrite

slot_pool_segmented slot_pool_segmented_fragmented
8192 1.07 µs (➖ 1.00x faster) 1.17 µs (➖ 1.10x slower)
65536 8.89 µs (➖ 1.05x slower) 8.26 µs (➖ 1.00x faster)
262144 43.53 µs (➖ 1.12x faster)

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/enhancement For PRs adding features, improving functionality, docs, tests, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants