Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/dep_rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,19 @@ jobs:
run: just test ${{ matrix.config }} ${{ env.FEATURES }}
working-directory: ./src/hyperlight_wasm

# Runs before Test Examples, which replaces the native modules with
# Pulley ones. Skipped on macOS, where the tests run inside the dotnet
# host, which lacks the hypervisor entitlement.
- name: Set up .NET
if: runner.os != 'macOS'
uses: actions/setup-dotnet@0f4a354dd5547a28c70b7853dbd95d5b67886e73 # v6.1.0
with:
dotnet-version: 8.0.x

- name: Test .NET packages
if: runner.os != 'macOS'
run: just dotnet-test-prebuilt ${{ matrix.config }} ${{ env.FEATURES }}

- name: Install github-cli (Windows)
if: runner.os == 'Windows'
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ $RECYCLE.BIN/
## Hyperlight

**/include/hyperlight*.h
!src/hyperlight_wasm_capi/include/hyperlight_wasm.h
**/include/printf.h
**/native/HyperlightGuest.lib
**/native/HyperlightGuest.pdb
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Prerelease] - Unreleased

### Added

* Add `SandboxBuilder::with_interrupt_vcpu_sigrtmin_offset` to change the signal that interrupts a guest on Linux.
* Experimental: add the `experimental_runtime_file` feature, with `SandboxBuilder::with_runtime_file` to load the guest runtime from a file. Snapshots of such a sandbox cannot be saved.
* Experimental: add the `hyperlight-wasm-capi` crate, a C API that calls component functions with arguments and results in Hyperlight's component wire format. It is not published.
* Experimental: add the `Hyperlight.Wasm` and `Hyperlight.Wasm.Build` .NET packages, which run components from .NET with typed C# bindings generated from WIT at build time. See `dotnet/README.md`. They are not published.

### Changed

* Update Hyperlight dependencies to 0.19.0.
Expand Down
24 changes: 24 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = [ "src/hyperlight_wasm", "src/examples_common", "src/hyperlight_wasm_aot", "src/hyperlight_wasm_runtime", "src/hyperlight_wasm_macro" ]
members = [ "src/hyperlight_wasm", "src/hyperlight_wasm_capi", "src/hyperlight_wasm_csharp_bindgen", "src/examples_common", "src/hyperlight_wasm_aot", "src/hyperlight_wasm_runtime", "src/hyperlight_wasm_macro" ]
exclude = [ "src/tests/rust_guests/rust_wasm_samples", "src/tests/rust_guests/component_sample", "src/tests/rust_guests/greeter_sample" ]
resolver = "2"

Expand All @@ -15,6 +15,7 @@ readme = "README.md"
[workspace.dependencies]
hyperlight-common = { version = "0.19.0", default-features = false }
hyperlight-component-macro = "0.19.0"
hyperlight-component-csharp = "0.19.0"
hyperlight-component-util = "0.19.0"
hyperlight-guest = "0.19.0"
hyperlight-guest-bin = "0.19.0"
Expand Down
19 changes: 19 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,25 @@ run-examples-components target=default-target features="":
{{ wit-world }} cargo test -p hyperlight-wasm {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --lib snapshot::tests::component
{{ wit-world-c }} cargo run {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example c-component
{{ wit-world }} {{ wit-world-name-greeter }} cargo run {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} --example component_greeter_example
{{ wit-world }} cargo test -p hyperlight-wasm-capi {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }} -- --include-ignored

# Builds the native libraries and a guest runtime for the component_sample example world, for the .NET tests
dotnet-test-assets target=default-target features="": ensure-cargo-hyperlight
cargo build -p hyperlight-wasm-capi -p hyperlight-wasm-csharp-bindgen {{ if features =="" {''} else {"--no-default-features -F kvm -F " + features } }} --profile={{ if target == "debug" {"dev"} else { target } }}
{{ wit-world }} cargo hyperlight build {{ if features =~ "wasmtime_latest" {"--no-default-features -F wasmtime_latest"} else {""} }} --profile={{ if target == "debug" {"dev"} else { target } }} --manifest-path src/hyperlight_wasm_runtime/Cargo.toml --target-dir target/component-sample-runtime --locked

# Runs the .NET tests. Needs the .NET 8 SDK or later.
dotnet-test target=default-target features="": (build-rust-component-examples target features) (dotnet-test-prebuilt target features)

# Runs the .NET tests on the component_sample component that build-ci-examples builds.
dotnet-test-prebuilt target=default-target features="": (dotnet-test-assets target features)
dotnet test dotnet/tests/Hyperlight.Wasm.Tests -c {{ if target == "debug" {"Debug"} else {"Release"} }}

# Packs the .NET packages for this machine's runtime identifier into dotnet/artifacts
dotnet-pack target="release":
cargo build -p hyperlight-wasm-capi -p hyperlight-wasm-csharp-bindgen --profile={{ if target == "debug" {"dev"} else { target } }}
dotnet pack dotnet/src/Hyperlight.Wasm -c {{ if target == "debug" {"Debug"} else {"Release"} }} -o dotnet/artifacts
dotnet pack dotnet/src/Hyperlight.Wasm.Build -c {{ if target == "debug" {"Debug"} else {"Release"} }} -o dotnet/artifacts

# Test a component and a module compiled with pulley
examples-pulley target=default-target features="": (build-pulley-rust-component-examples target features) (build-pulley-rust-wasm-examples target features) (run-examples-pulley target features)
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ WIT_WORLD=</path/to/output.wasm> HYPERLIGHT_COMPONENT_MACRO_DEBUG=/tmp/guest.rs
HYPERLIGHT_COMPONENT_MACRO_DEBUG=/tmp/host.rs cargo build
```

### Other languages

These are experimental.

* [`src/hyperlight_wasm_capi`](src/hyperlight_wasm_capi) is a C API. Its header is `include/hyperlight_wasm.h`.
* [`dotnet`](dotnet) has .NET packages with C# bindings generated from WIT.

Both load a guest runtime built for the component's world from a file.


## Code of Conduct

Expand Down
26 changes: 26 additions & 0 deletions dotnet/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project>

<PropertyGroup>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- Experimental packages use the workspace version with a prerelease suffix. -->
<_HyperlightCargoToml>$([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)../Cargo.toml'))</_HyperlightCargoToml>
<VersionPrefix>$([System.Text.RegularExpressions.Regex]::Match('$(_HyperlightCargoToml)', '(?&lt;=\[workspace\.package\][^\[]*\nversion\s*=\s*")[^"]+').Value)</VersionPrefix>
<VersionSuffix>preview</VersionSuffix>
<Authors>The Hyperlight Authors</Authors>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/hyperlight-dev/hyperlight-wasm</PackageProjectUrl>
<RepositoryUrl>https://github.com/hyperlight-dev/hyperlight-wasm</RepositoryUrl>
<!-- Cargo output for the native libraries. -->
<HyperlightCargoProfile Condition="'$(HyperlightCargoProfile)' == '' and '$(Configuration)' == 'Release'">release</HyperlightCargoProfile>
<HyperlightCargoProfile Condition="'$(HyperlightCargoProfile)' == ''">debug</HyperlightCargoProfile>
<HyperlightNativeDir Condition="'$(HyperlightNativeDir)' == ''">$(MSBuildThisFileDirectory)../target/$(HyperlightCargoProfile)/</HyperlightNativeDir>
<HyperlightNativePrefix Condition="!$([MSBuild]::IsOSPlatform('Windows'))">lib</HyperlightNativePrefix>
<HyperlightNativeSuffix Condition="$([MSBuild]::IsOSPlatform('Windows'))">.dll</HyperlightNativeSuffix>
<HyperlightNativeSuffix Condition="$([MSBuild]::IsOSPlatform('Linux'))">.so</HyperlightNativeSuffix>
<HyperlightNativeSuffix Condition="$([MSBuild]::IsOSPlatform('OSX'))">.dylib</HyperlightNativeSuffix>
</PropertyGroup>

</Project>
8 changes: 8 additions & 0 deletions dotnet/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project>

<PropertyGroup>
<!-- The SDK defines NETCoreSdkPortableRuntimeIdentifier after Directory.Build.props. -->
<HyperlightNativeRid Condition="'$(HyperlightNativeRid)' == ''">$(NETCoreSdkPortableRuntimeIdentifier)</HyperlightNativeRid>
</PropertyGroup>

</Project>
87 changes: 87 additions & 0 deletions dotnet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Hyperlight.Wasm for .NET

> **Warning:** These packages are experimental. Their API and the generated C#
> can change in any release.

Run WebAssembly components in Hyperlight micro VMs from .NET, with typed bindings generated from WIT.

* `Hyperlight.Wasm` is the runtime library. It wraps the native `hyperlight-wasm-capi` library.
* `Hyperlight.Wasm.Build` generates C# from WIT at build time. It needs no Rust toolchain.

Both target .NET 8 or later. The packages hold native libraries for the runtime identifier they were packed on.

## Usage

```xml
<ItemGroup>
<PackageReference Include="Hyperlight.Wasm" Version="0.16.0-preview" />
<PackageReference Include="Hyperlight.Wasm.Build" Version="0.16.0-preview" />
<HyperlightWit Include="wit/example.wit" World="example" />
</ItemGroup>
```

Each `HyperlightWit` item is a `.wit` file or directory. `World` selects a world when the package has several. Without it, the last world is used. `Namespace` sets the namespace of the generated types and defaults to the project's root namespace.

For a world `example` that imports `host` and exports `adder`, the build generates:

* `ExampleComponent`, with an `Adder` property for the export.
* `ExampleComponent.RegisterImports`, which registers an `IHost` implementation as host functions.
* C# types for the world's records, variants, enums and flags.

```csharp
using var sandbox = new WasmSandbox(new SandboxOptions
{
RuntimePath = "example-runtime",
});
ExampleComponent.RegisterImports(sandbox, new Host());
sandbox.Load("example.aot");

var component = new ExampleComponent(sandbox);
uint sum = component.Adder.Add(1, 2);
```

`docs/component-wire-format.md` in hyperlight lists how WIT types map to C#.

## Guest runtime and components
Comment thread
jsturtevant marked this conversation as resolved.
Resolved

A component needs a guest runtime built for its WIT world, because the runtime's guest bindings are generated when it is compiled. Building a runtime needs Rust and a hyperlight-wasm checkout of the same version as the packages:

```sh
wasm-tools component wit example.wit -w -o example-world.wasm
WIT_WORLD=example-world.wasm cargo hyperlight build --release \
--manifest-path src/hyperlight_wasm_runtime/Cargo.toml --target-dir target/example-runtime
```

Precompile the component with `hyperlight-wasm-aot compile --component`. The runtime, the AOT tool and the packages must come from the same hyperlight-wasm version.

## Sandboxes

* Register host functions, then load a component, then call it.
* Calls are serialized. Host functions run on the calling thread and cannot call back into their own sandbox.
* An exception thrown by a host function fails the guest call. The `HyperlightException` holds it as `InnerException`.
* A trap or interrupted call poisons the sandbox. Restore a snapshot taken with `CreateSnapshot` to use it again.
* `Call` takes a `CancellationToken`. Cancelling interrupts the guest. `CreateInterruptHandle` returns a handle that does the same from any thread.

## Hypervisor entitlement on macOS

macOS lets a process use Hypervisor.framework only when its executable is signed with the `com.apple.security.hypervisor` entitlement. Publish the app as a single file and sign that executable with an entitlements file such as `dev/macos-entitlements.plist`:

```sh
dotnet publish -c Release -r osx-arm64 -p:PublishSingleFile=true
codesign -f -s - --entitlements macos-entitlements.plist bin/Release/net8.0/osx-arm64/publish/MyApp
```

`dotnet MyApp.dll` and `dotnet test` run inside the `dotnet` host, so they cannot create sandboxes on macOS.

## Surrogate process on Windows

Hyperlight runs a helper process, `hyperlight_surrogate_<hash>.exe`, for each sandbox on Windows. The first sandbox writes it to the directory of the running executable, which must be writable. For `dotnet MyApp.dll` that is the .NET installation directory, which needs administrator rights under Program Files. Publish the app with its own executable in a writable directory, or run it once as administrator.

## Development

```sh
just dotnet-test # builds the native libraries, a test runtime and component, then runs the tests
just dotnet-pack # packs both packages into dotnet/artifacts
```

`HyperlightNativeDir` overrides where the projects find the native libraries.
Loading
Loading