Skip to content

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

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

ludfjig wants to merge 6 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 wasi::http::[method]fields.get. The old name dropped the interface and version, so functions in different interfaces or versions of one package could collide.

@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

Comment on lines +19 to +20
let result = generate_from_path(Path::new(wit), None, namespace)
.map_err(|e| e.to_string())
case byte[] bytes:
{
var hash = new HashCode();
hash.AddBytes(bytes);
Comment on lines +421 to +424
// Every element takes at least one byte.
if (count > (uint)(_data.Length - _position))
{
throw new WireFormatException($"list length {count} exceeds remaining input");
Comment on lines +128 to +130
Value::Tuple(ts) => {
let types = ts.iter().map(|t| ty(s, t)).collect::<Vec<_>>();
format!("({})", types.join(", "))
Comment on lines +414 to +426
src.line(format!("{} = {i},", pascal(c.name)));
}
src.close();
}
Value::Flags(flags) => {
src.line("[global::System.Flags]");
src.line(format!("public enum {name} : uint"));
src.open();
if flags.iter().all(|f| pascal(f.name) != "None") {
src.line("None = 0,");
}
for (i, f) in flags.iter().enumerate() {
src.line(format!("{} = 1u << {i},", pascal(f.name)));
/// Hex literal with the low `count` bits set. WIT flags have at most 32
/// members.
fn flags_mask(count: usize) -> String {
format!("0x{:X}", u32::MAX >> (32 - count))
if i > 0 {
src.line("");
}
src.line(format!("{};", signature(s, group, f, &pascal(function))));
src.line(format!("public {name}({RT}.IGuestCaller caller)"));
src.open();
src.line("_caller = caller;");
let export_names = group_member_names(s, exports);
Comment on lines +68 to +71
if namespace.is_empty()
|| namespace
.split('.')
.any(|part| part.is_empty() || !part.chars().all(|c| c.is_alphanumeric() || c == '_'))
Comment thread src/hyperlight_component_csharp/src/names.rs Outdated
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]>
Signed-off-by: Ludvig Liljenberg <[email protected]>
@ludfjig
ludfjig force-pushed the component-wire-format branch from fcd4c0f to 92cc474 Compare October 20, 2026 17:58
@hyperlight-gh-bot

Copy link
Copy Markdown

Benchmark Results

Measured commit: 92cc47430d09
Baseline commit: b8783e8cd14d

kvm / amd (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 854.25 ns (➖ 1.15x slower)
vec_bytes 589.48 ns (➖ 1.00x slower)
373.37 µs (➖ 1.00x slower)

payload_allocation

slot_pool_segmented
262144 523.75 ns (➖ 1.00x slower)
65536 142.35 ns (➖ 1.01x faster)

sandboxes

create_initialized_and_drop
medium 83.14 ms (➖ 1.01x slower)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
7.71 ns (➖ 1.01x faster) 7.72 ns (➖ 1.00x slower) 7.91 ns (➖ 1.02x slower)

snapshot_files

load_snapshot_unverified
small 93.57 µs (➖ 1.02x slower)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 7.29 µs (➖ 1.03x slower) 7.18 µs (➖ 1.01x slower)
65536 2.06 µs (➖ 1.06x slower) 1.93 µs (➖ 1.10x faster)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 6.30 µs (➖ 1.04x faster) 6.22 µs (➖ 1.03x faster)
8192 1.06 µs (➖ 1.00x faster) 1.07 µs (➖ 1.00x slower)
262144 27.43 µs (➖ 1.01x faster)
kvm / intel (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 784.40 ns (➖ 1.05x slower)
vec_bytes 593.87 ns (➖ 1.04x slower)
697.11 µs (➖ 1.01x slower)

payload_allocation

slot_pool_segmented
262144 592.64 ns (➖ 1.06x slower)
65536 155.16 ns (➖ 1.00x slower)

sandboxes

create_initialized_and_drop
medium 80.90 ms (➖ 1.03x faster)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
7.43 ns (➖ 1.03x faster) 7.80 ns (➖ 1.04x slower) 7.50 ns (➖ 1.01x faster)

snapshot_files

load_snapshot_unverified
small 50.08 µs (➖ 1.06x slower)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 8.56 µs (➖ 1.01x faster) 8.54 µs (➖ 1.00x faster)
65536 2.38 µs (➖ 1.01x faster) 2.38 µs (➖ 1.02x faster)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 7.95 µs (➖ 1.02x faster) 7.73 µs (➖ 1.00x faster)
8192 857.54 ns (➖ 1.01x slower) 832.34 ns (➖ 1.01x slower)
262144 32.98 µs (➖ 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 936.50 ns (➖ 1.01x slower)
vec_bytes 718.22 ns (➖ 1.01x slower)
280.88 µs (➖ 1.02x faster)

payload_allocation

slot_pool_segmented
262144 716.88 ns (➖ 1.00x faster)
65536 192.38 ns (➖ 1.01x slower)

sandboxes

create_initialized_and_drop
medium 61.24 ms (➖ 1.01x faster)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
9.41 ns (➖ 1.03x faster) 10.08 ns (➖ 1.07x slower) 9.44 ns (➖ 1.00x slower)

snapshot_files

load_snapshot_unverified
small 85.70 µs (➖ 1.00x faster)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 9.90 µs (➖ 1.01x faster) 9.66 µs (➖ 1.01x faster)
65536 2.39 µs (➖ 1.04x faster) 2.26 µs (➖ 1.01x slower)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 8.38 µs (➖ 1.21x faster) 8.55 µs (➖ 1.16x faster)
8192 1.30 µs (➖ 1.05x faster) 1.29 µs (➖ 1.02x slower)
262144 35.68 µs (➖ 1.07x faster)
mshv3 / intel (Linux) (➖ stable)

No benchmark improved or regressed.

Benchmark Results

function_call_codec

encode_control decode_vec_bytes_copy
byte_chunks 929.13 ns (➖ 1.01x slower)
vec_bytes 640.90 ns (➖ 1.02x faster)
762.84 µs (➖ 1.18x slower)

payload_allocation

slot_pool_segmented
262144 657.59 ns (➖ 1.01x slower)
65536 173.35 ns (➖ 1.01x slower)

sandboxes

create_initialized_and_drop
medium 71.28 ms (➖ 1.05x slower)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
9.60 ns (➖ 1.02x slower) 9.43 ns (➖ 1.01x faster) 9.39 ns (➖ 1.01x slower)

snapshot_files

load_snapshot_unverified
small 46.75 µs (➖ 1.03x slower)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 7.94 µs (➖ 1.00x slower) 8.62 µs (➖ 1.09x slower)
65536 2.29 µs (➖ 1.02x slower) 2.27 µs (➖ 1.03x slower)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 7.60 µs (➖ 1.01x slower) 7.56 µs (➖ 1.01x faster)
8192 917.85 ns (➖ 1.03x faster) 938.28 ns (➖ 1.01x slower)
262144 37.74 µs (➖ 1.00x 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.13 µs (➖ 1.05x faster)
vec_bytes 778.81 ns (➖ 1.01x faster)
1.86 ms (➖ 1.01x faster)

payload_allocation

slot_pool_segmented
262144 793.21 ns (➖ 1.01x slower)
65536 233.98 ns (➖ 1.00x slower)

sandboxes

create_initialized_and_drop
medium 85.71 ms (➖ 1.09x slower)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
10.37 ns (➖ 1.01x faster) 10.53 ns (➖ 1.05x slower) 10.11 ns (➖ 1.02x faster)

snapshot_files

load_snapshot_unverified
small 725.84 µs (➖ 1.13x slower)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 8.99 µs (➖ 1.05x faster) 8.98 µs (➖ 1.03x faster)
65536 2.38 µs (➖ 1.00x slower) 2.36 µs (➖ 1.01x slower)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 8.84 µs (➖ 1.06x faster) 8.85 µs (➖ 1.06x faster)
8192 1.45 µs (➖ 1.05x slower) 1.33 µs (➖ 1.02x slower)
262144 40.27 µ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.20 µs (➖ 1.01x faster)
vec_bytes 777.58 ns (➖ 1.02x faster)
3.18 ms (➖ 1.06x slower)

payload_allocation

slot_pool_segmented
262144 748.82 ns (➖ 1.00x slower)
65536 210.47 ns (➖ 1.00x slower)

sandboxes

create_initialized_and_drop
medium 108.27 ms (➖ 1.03x faster)

slot_pool

alloc_dealloc_1500 alloc_dealloc_4096 alloc_dealloc_128
10.20 ns (➖ 1.01x slower) 10.07 ns (➖ 1.14x faster) 9.98 ns (➖ 1.02x faster)

snapshot_files

load_snapshot_unverified
small 540.06 µs (➖ 2.04x faster)

virtq_readonly

slot_pool_segmented_fragmented slot_pool_segmented
262144 7.87 µs (➖ 1.03x slower) 7.81 µs (➖ 1.01x slower)
65536 2.47 µs (➖ 1.07x slower) 2.35 µs (➖ 1.00x slower)

virtq_readwrite

slot_pool_segmented_fragmented slot_pool_segmented
65536 8.55 µs (➖ 1.03x faster) 9.16 µs (➖ 1.07x slower)
8192 1.13 µs (➖ 1.10x faster) 1.11 µs (➖ 1.01x slower)
262144 44.43 µs (➖ 1.09x faster)

Reported by cargo ci bench-report --candidate run:39470678697 --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.

3 participants