WASI stands for WebAssembly System Interface, and allows to run WebAssembly code independently of the browsers, as it provides access to operating system features such as file system access or networking. It is highly experimental, but at the same time a tremendously interesting project, and one that has the potential of contributing to a massive paradigm-shift in the industry, making WebAssembly truly ubiquitous.
The great mad scientist of web things at Microsoft, Steve Sanderson, recently published the first version of an experiemental WASI SDK for .NET, which allows building .NET 7 and ASP.NET Core applications into standalone WASI compliant apps, and running them from WASI hosts. Steve’s repo provides the easy to follow steps to get going on Windows and Linux, in this post I will walk through some additional hoops that one may need to jump on arm64 Macs.
Getting started and building π
The prerequisite to get going is to have a .NET 7 SDK installed. As we will discover later, the latest official preview 7.0.100-preview.2 will not be enough, but for now we can use it to bootstrap the project - in this case a regular bare-bones ASP.NET Core web application
> dotnet new web
Once the project is created, the following package references need to be added to bring in the experimental Wasi.Sdk and the corresponding ASP.NET Core server implementation that Steve built:
<ItemGroup>
<PackageReference Include="Wasi.AspNetCore.Server.Native" Version="0.1.0" />
<PackageReference Include="Wasi.Sdk" Version="0.1.0" />
</ItemGroup>
In addition to that, an extra property under the main PropertyGroup is needed, one that will correspond to the port used by your application in launchSettings.json:
<WasiRunnerArgs>--tcplisten localhost:5100 --env ASPNETCORE_URLS=http://localhost:5100</WasiRunnerArgs>
Since our app will need to listen on networking interface from inside of a WASI host, an additional change is needed in the generated code, namely UseWasiConnectionListener() needs to be added to the default WebApplication builder:
var builder = WebApplication.CreateBuilder(args).UseWasiConnectionListener();
At this point the application will build correctly, but it will not run yet, since we have no WASI host at our disposal yet.
Running the application π
In order to run this WASI-based application, we need a WASI runtime. An excellent lightweight runtime is wasmtime, and it happens to be the one used by this experimental Wasi.Sdk by default - it simply expects it to be available on the PATH. Unfortunately, there is no official distribution of wasmtime for arm64 MacOS (yet). If you attempt to execute the regular wasmtime installation command, you will see the following:
> curl https://wasmtime.dev/install.sh -sSf | bash
Error: Sorry! Wasmtime currently only provides pre-built binaries for x86_64 architectures.
Thankfully, this apparent significant problem is relatively easy to solve. While wasmtime is not distributed for arm64 MacOS, it can actually build for that target without any problems. We will need Rust to be installed on our machine first, and in case you do not, this can be done easily with Rustup
> curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
With that available, we can clone the official wasmtime repository and simply run the build, specifying aarch64-apple-darwin as the build target.
> cargo build --target aarch64-apple-darwin --release
This should take less than a minute, and will build wasmtime executable into the target/aarch64-apple-darwin/release folder. We can easily verify that we built it successfully, by simply executing it.
> ./wasmtime
wasmtime 0.35.0
Wasmtime WebAssembly Runtime
USAGE:
wasmtime <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
SUBCOMMANDS:
compile Compiles a WebAssembly module
config Controls Wasmtime configuration settings
help Prints this message or the help of the given subcommand(s)
run Runs a WebAssembly module
settings Displays available Cranelift settings for a target
wast Runs a WebAssembly test script file
If a subcommand is not provided, the `run` subcommand will be used.
Usage examples:
Running a WebAssembly module with a start function:
wasmtime example.wasm
Passing command line arguments to a WebAssembly module:
wasmtime example.wasm arg1 arg2 arg3
Invoking a specific function (e.g. `add`) in a WebAssembly module:
wasmtime example.wasm --invoke add 1 2
At this point, we can go back to our initial ASP.NET Core project and set the WasiRunner property to the path to wasmtime we just built.
<WasiRunner>{your relevant path}/target/aarch64-apple-darwin/release/wasmtime</WasiRunner>
Alternatively, you can simply add the path to the wasmtime executable to your PATH, which will be picked up by the SDK automatically, without any additional changes to the project file.
Next, when we run this project, even though wasmtime is reachable now, we will still get the following error, coming from Quic implementation (or rather it being missing) for arm64 Mac:
> dotnet run
Building...
Unhandled Exception:
System.PlatformNotSupportedException: System.Net.Quic is not supported on this platform.
at System.Net.Quic.QuicImplementationProviders.get_Default()
at Microsoft.AspNetCore.Hosting.WebHostBuilderQuicExtensions.UseQuic(IWebHostBuilder hostBuilder)
at Microsoft.AspNetCore.Hosting.WebHostBuilderKestrelExtensions.UseKestrel(IWebHostBuilder hostBuilder)
at Microsoft.AspNetCore.Hosting.WebHostBuilderKestrelExtensions.UseKestrel(IWebHostBuilder hostBuilder, Action`2 configureOptions)
at Microsoft.AspNetCore.WebHost.ConfigureWebDefaults(IWebHostBuilder builder)
at Microsoft.Extensions.Hosting.GenericHostBuilderExtensions.<>c__DisplayClass0_0.<ConfigureWebHostDefaults>b__0(IWebHostBuilder webHostBuilder)
at Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions.ConfigureWebHost(IHostBuilder builder, Action`1 configure, Action`1 configureWebHostBuilder)
at Microsoft.Extensions.Hosting.GenericHostWebHostBuilderExtensions.ConfigureWebHost(IHostBuilder builder, Action`1 configure)
at Microsoft.Extensions.Hosting.GenericHostBuilderExtensions.ConfigureWebHostDefaults(IHostBuilder builder, Action`1 configure)
at Microsoft.AspNetCore.Builder.WebApplicationBuilder..ctor(WebApplicationOptions options, Action`1 configureDefaults)
at Microsoft.AspNetCore.Builder.WebApplication.CreateBuilder(String[] args)
at Program.<Main>$(String[] args)
Steve recently made a PR fixing this exception, though it has not been released as the official preview release yet - the latest SDK version is 7.0.100-preview.2, and the change will be part of 7.0.100-preview.3. A related PR on the runtime side can be found here. All things considered, since it is already fixed, just not “officially available”, the best idea is to install a daily build of the .NET SDK for arm64 MacOS from the daily feed; the stable link to the latest one is this. For example, at the time of writing the current version available there is 7.0.100-preview.4.22179.13.
Once this SDK is installed, we should be able to run the application:
> dotnet run
Building...
info: Microsoft.Hosting.Lifetime
Now listening on: http://localhost:5100
All that is left, is to navigate to that http://localhost:5100/ URL in the browser, where we should now see our hello world. In a quite spectacular fashion, we now have an ASP.NET Core application, running as WebAssembly application in a WASI runtime, and being reachable from the browser.