Skip to content

Add experimental C API and .NET packages for components - #564

Open
ludfjig wants to merge 4 commits into
mainfrom
hyperlight-0.18-capi
Open

ludfjig wants to merge 4 commits into
mainfrom
hyperlight-0.18-capi

Conversation

@ludfjig

@ludfjig ludfjig commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

.NET and other non-Rust hosts can't use hyperlight-wasm, because its host API is Rust only.

This adds an experimental C API and .NET packages on top of it:

  • hyperlight-wasm-capi is a C API for running components. Callers register host functions, load a component and call its functions. They can also snapshot, restore and interrupt a sandbox.
  • The C API works with any component, because it never decodes values. Callers pass each argument as a byte buffer in Hyperlight's component wire format and get the result back the same way. Generated bindings, such as the .NET ones, do the encoding.
  • Hyperlight.Wasm wraps the C API. Hyperlight.Wasm.Build generates typed C# bindings from WIT at build time with Hyperlight's hyperlight-component-csharp, so builds need no Rust.
  • The C API loads a guest runtime built for the component's world from a file. SandboxBuilder::with_runtime_file is behind the experimental_runtime_file feature, and snapshots of such a sandbox cannot be saved.
  • SandboxBuilder::with_interrupt_vcpu_sigrtmin_offset changes the interrupt signal, since .NET uses SIGRTMIN.

The C API and packages are versioned as experimental and not published yet. just dotnet-test and just examples-components run their tests, and CI runs both.

@ludfjig
ludfjig force-pushed the hyperlight-0.18-capi branch 4 times, most recently from fbcf9c8 to b91d23d Compare November 4, 2026 01:22
@ludfjig
ludfjig force-pushed the hyperlight-0.18-capi branch from b91d23d to 214c584 Compare November 11, 2026 19:25
@ludfjig ludfjig added the kind/enhancement New feature or request label Nov 11, 2026
@ludfjig ludfjig changed the title hyperlight-wasm capi Add experimental C API and .NET packages for components Nov 11, 2026
@ludfjig
ludfjig force-pushed the hyperlight-0.18-capi branch 7 times, most recently from 400cf1e to 0bb6526 Compare November 12, 2026 17:49
The experimental_runtime_file feature adds
SandboxBuilder::with_runtime_file, which loads the guest runtime from
a file instead of the one built into the crate. A host built once can
then run components of any world whose runtime was built separately.
Snapshots of such a sandbox cannot be saved, because the host does not
know the runtime's world.

SandboxBuilder::with_interrupt_vcpu_sigrtmin_offset changes the signal
that interrupts a running guest on Linux. Hosts such as .NET use
SIGRTMIN themselves.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
@ludfjig
ludfjig force-pushed the hyperlight-0.18-capi branch 3 times, most recently from 152740d to 0231892 Compare November 12, 2026 18:39
@ludfjig
ludfjig marked this pull request as ready for review November 12, 2026 20:31
@ludfjig
ludfjig added this pull request to stack #576 November 13, 2026 00:42
Comment thread .github/workflows/dep_rust.yml Resolved
Comment thread dotnet/Directory.Build.props Resolved
Comment thread dotnet/README.md Resolved
Comment thread dotnet/README.md Resolved
Comment thread dotnet/README.md Resolved
pub struct ProtoWasmSandbox {
pub(super) inner: Option<UninitializedSandbox>,
host_function_definitions: HashMap<String, HostFunctionDefinition>,
runtime_file: bool,

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.

hmm, i am not so sure about this API change...

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.

It's crate-private, so the only public addition is the feature-gated with_runtime_file.

pub(super) fn new(mut inner: Sandbox) -> Result<Self> {
let snapshot = Snapshot::runtime(inner.snapshot()?);
/// Snapshot a freshly initialized runtime and wrap it. `runtime_file`
/// is whether the runtime was loaded from a file.

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.

why do we need to know this

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.

Without it, saving would record the host's built-in world rather than the runtime file's, so save refuses until we record the file's world hash.

Comment thread src/hyperlight_wasm_bindgen/Cargo.toml Resolved
Add hyperlight-wasm-capi, a C library declared in
include/hyperlight_wasm.h. Callers register host functions, load a
component and call its functions. They can also snapshot, restore and
interrupt a sandbox. It is experimental and not published.

The C API works with any component, because it never decodes values.
Callers pass each argument as a byte buffer in hyperlight's component
wire format and get the result back the same way. Generated bindings,
such as the .NET ones, do the encoding. Host functions take up to 32
parameters. Calls from a host function into its own sandbox fail.

just examples-components runs its tests against the component_sample
example world.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Hyperlight.Wasm runs components from .NET through the C API. WasmSandbox
registers host functions, loads components and calls them, with
snapshots, interrupts and CancellationToken support. Host exceptions
become the InnerException of the failed call. Their messages do not
reach the guest. The interrupt signal defaults to SIGRTMIN + 1, because
the .NET runtime suspends threads for garbage collection with SIGRTMIN.
With offset 0 a collection can hang.

Hyperlight.Wasm.Build generates C# bindings from HyperlightWit items at
build time. Its MSBuild task loads hyperlight-wasm-csharp-bindgen, a
native library around hyperlight's C# generator, so builds need no Rust.

Both packages are experimental and take the workspace version with a
preview suffix. They are not published yet.

just dotnet-test runs the tests against the component_sample example
world. CI runs them with the prebuilt component on every platform except
macOS, where the tests run inside the dotnet host, which lacks the
hypervisor entitlement. just dotnet-pack packs both packages for the
build machine's runtime identifier.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
Registering a name a second time replaces the old host function. Its finalizer then runs while the sandbox is borrowed and the host function lock is held, so a call back into the sandbox is undefined behavior.

Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
@ludfjig
ludfjig force-pushed the hyperlight-0.18-capi branch from 9e17ba8 to 1baa590 Compare November 13, 2026 21:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants