{"id":49436,"date":"2023-12-04T10:22:45","date_gmt":"2023-12-04T18:22:45","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=49436"},"modified":"2024-12-13T14:03:06","modified_gmt":"2024-12-13T22:03:06","slug":"extending-web-assembly-to-the-cloud","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/extending-web-assembly-to-the-cloud\/","title":{"rendered":"Extending WebAssembly to the Cloud with .NET"},"content":{"rendered":"<p><a href=\"https:\/\/webassembly.org\/\">WebAssembly (Wasm)<\/a> is an exciting new(ish) virtual machine and (assembly) instruction format. Wasm was born in the browser and is an important part of the <a href=\"https:\/\/learn.microsoft.com\/aspnet\/core\/blazor\/hosting-models#blazor-webassembly\">Blazor<\/a> project. The second act for Wasm is cloud computing, for apps and functions. WebAssembly System Interface (WASI) is the new enabler, providing a way for WebAssembly code to call and implement arbitrary APIs, safely and across languages. It is now possible to create WASI apps with .NET using the <code>wasi-experimental<\/code> workload in .NET 8. We&#8217;re exploring these new technologies and running .NET apps in this environment &#8230;. really, anywhere.<\/p>\n<p>This post will help you understand the broadening use of Wasm and describe what&#8217;s already possible with .NET. They say that history doesn&#8217;t repeat itself, but that it rhymes. We&#8217;re back for another round of &#8220;write once, run anywhere&#8221;. WASI apps are portable binaries that run on any hardware or operating system and are not specific to any programming language. This time, it feels different. It&#8217;s not just vendor neural; it&#8217;s everything neutral.<\/p>\n<h2>Wasm and WASI<\/h2>\n<p>Wasm may be offering us a reboot of compute in the cloud, with the promise of a single cloud-native binary, higher density, and cheaper multi-tenancy. It also opens up the possibility of edge compute for the same reasons. In fact, <a href=\"https:\/\/developers.cloudflare.com\/workers\/runtime-apis\/webassembly\/\">CloudFlare<\/a> and <a href=\"https:\/\/docs.fastly.com\/products\/compute\">Fastly<\/a> already host public compute at the edge with Wasm.<\/p>\n<p>Wasm is different than running an app in a Linux container, which is a (good and clever) re-packaging of existing standards and code. Wasm is more like running an app in an environment with no operating system, with just assembly code, memory, and standardized (and gated) access to the outside world (via WASI).<\/p>\n<p>The <a href=\"https:\/\/www.youtube.com\/watch?v=Tz2SOjKZwVA\">Hyperlight presentation at Build 2023<\/a> (4m video) provides insight into what a Wasm-enabled cloud might look like. It demonstrates a Blazor app running in a new lighweight and secure hypervisor. Hyperlight sparks the imagination on new hosting paradigms.<\/p>\n<p><a href=\"https:\/\/github.com\/WebAssembly\/wasi\">WebAssembly System Interface (WASI)<\/a>, <a href=\"https:\/\/github.com\/WebAssembly\/component-model\/blob\/main\/design\/mvp\/WIT.md\">WebAssembly Interface Types (WIT)<\/a>, and <a href=\"https:\/\/github.com\/WebAssembly\/component-model\">WebAssembly Component Model<\/a> are the key specs in this latest round of Wasm innovation. They are very much still in the design phase and <a href=\"https:\/\/github.com\/WebAssembly\/WASI\/blob\/33de9e568c35424765e7b10952b181f01a724fca\/README.md#important-note-wasi-is-in-transition\">undergoing significant change<\/a>. This post (and the .NET 8 implementation) is oriented around WASI Preview 1. We expect the .NET 9 implementation to use WASI Preview 2.<\/p>\n<p>WIT and <a href=\"https:\/\/github.com\/bytecodealliance\/wit-bindgen\">wit-bindgen<\/a> enable components written in any source language to communicate and with the host system. <a href=\"https:\/\/github.com\/bytecodealliance\/wit-bindgen\/issues\/713\">Implementation of WIT support for C#<\/a> is lead by <a href=\"https:\/\/github.com\/silesmo\">@silesmo<\/a>. Wasm and WIT together define the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Application_binary_interface\">Application Binary Interface (ABI)<\/a>.<\/p>\n<p>We expect WASI to become a standard set of WIT types that provide access to <a href=\"https:\/\/github.com\/WebAssembly\/WASI\/blob\/main\/Proposals.md\">low-level functionality<\/a> (like <a href=\"https:\/\/github.com\/WebAssembly\/wasi-clocks\/blob\/97fa4efa19c9549b9ada91435cf27eee808d8ab6\/wit\/wall-clock.wit#L35\">getting the time<\/a> and <a href=\"https:\/\/github.com\/WebAssembly\/wasi-filesystem\/blob\/3a05fcf9a6e10019c4f1fce42184926d8e541c2d\/wit\/types.wit#L315-L325\">reading a file<\/a>). These low-level types effectively form a &#8220;Wasm standard library&#8221; across programming languages and operating systems. We&#8217;re never had standard and shared functionality that Rust devs and .NET devs, for example, could both use. There isn&#8217;t any widely-deployed historical precedent of native code that exposed APIs with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Object-oriented_programming\">OO<\/a>-ish shape (like interfaces) that could be used across programming languages and operating systems.<\/p>\n<p>The standard WIT types start with <code>wasi-<\/code> and define the &#8220;platform&#8221;. You can think of them in a similar way to the <code>System<\/code> namespace in .NET (matching the &#8216;S&#8217; in WASI). Continuing the analogy, you can create your own .NET namespaces beyond the <code>System<\/code> ones, and the same is true with WIT.<\/p>\n<p>These posts do a great job of framing WASI in more detail.<\/p>\n<ul>\n<li><a href=\"https:\/\/hacks.mozilla.org\/2019\/03\/standardizing-wasi-a-webassembly-system-interface\/\">Standardizing WASI: A system interface to run WebAssembly outside the web<\/a><\/li>\n<li><a href=\"https:\/\/bytecodealliance.org\/articles\/announcing-the-bytecode-alliance\">Announcing the Bytecode Alliance: Building a secure by default, composable future for WebAssembly<\/a><\/li>\n<li><a href=\"https:\/\/bytecodealliance.org\/articles\/webassembly-the-updated-roadmap-for-developers\">WebAssembly: An Updated Roadmap for Developers<\/a><\/li>\n<\/ul>\n<p>The promise on the horizon is being able to take an existing .NET app or library and compile it to a Wasm target. Our design instinct is to implement WIT interfaces relatively high into the .NET stack (like create an ADO.NET Data Provider for <a href=\"https:\/\/github.com\/WebAssembly\/wasi-sql\"><code>wasi-sql<\/code><\/a>), which would enable existing code (including many existing NuGet packages) to just work, particularly for code without native dependencies.<\/p>\n<p>Wasm apps are run in a Wasm runtime, like <a href=\"https:\/\/github.com\/bytecodealliance\/wasmtime\"><code>wasmtime<\/code><\/a>. Much like Docker, you can configure that runtime with specific capabilities. For example, if you want Wasm code to have access to a key\/value store, you can <a href=\"https:\/\/github.com\/SteveSandersonMS\/spiderlightning-dotnet\/blob\/main\/sample\/ConsoleApp\/slightfile.toml\">expose a key\/value interface to it<\/a>, which could be backed by a local database or a cloud service.<\/p>\n<p>Wasm runtimes are intended to be embeddable within apps. In fact, there is a <code>wasmtime<\/code> package for <a href=\"https:\/\/github.com\/bytecodealliance\/wasmtime-dotnet\">hosting Wasm in .NET apps<\/a>. .NET code can run as Wasm, but .NET apps can host <code>wasmtime<\/code>?!? Yes, this space can start to seem circular. While these scenarios seem circular, they <a href=\"https:\/\/www.youtube.com\/watch?v=5u1UaqkPZbg\">might end up being pretty useful<\/a>, vaguely similar to how <a href=\"https:\/\/learn.microsoft.com\/dotnet\/framework\/app-domains\/application-domains\">AppDomains<\/a> have been used. It&#8217;s also reminiscent of all the &#8220;docker in docker&#8221; scenarios.<\/p>\n<p>We expect a lot more innovation, more Wasm runtimes, and more industry participants. In fact, Wasm has gradulated to a <a href=\"https:\/\/www.w3.org\/TR\/wasm-core-1\/\">W3C spec<\/a>. The W3C is a perfect home for Wasm, to let it grow as a broad industry spec, just like HTML and XML before it.<\/p>\n<h2><code>wasi-experimental<\/code> workload<\/h2>\n<p>.NET 8 includes a new workload called <code>wasi-experimental<\/code>. It builds on top of the Wasm functionality used by Blazor, extending it to run in <code>wasmtime<\/code> and invoke WASI interfaces. It is far from done, but already enables useful functionality.<\/p>\n<p>Let&#8217;s move on from theory to demonstrating the new capabilities.<\/p>\n<p>After installing the <a href=\"https:\/\/dotnet.microsoft.com\/download\/dotnet\/8.0\">.NET 8 SDK<\/a>, you can install the <code>wasi-experimental<\/code> workload.<\/p>\n<pre><code class=\"language-bash\">dotnet workload install wasi-experimental<\/code><\/pre>\n<p>Note: This command may require admin permisions, for example with  <code>sudo<\/code> on Linux and macOS.<\/p>\n<p>You also need to install <a href=\"https:\/\/github.com\/bytecodealliance\/wasmtime\/tree\/main#installation\"><code>wasmtime<\/code><\/a> to run the Wasm code you are soon going to produce.<\/p>\n<p>Try a simple example with the <code>wasi-console<\/code> template.<\/p>\n<pre><code class=\"language-bash\">$ dotnet new wasiconsole -o wasiconsole\r\n$ cd wasiconsole\r\n$ cat Program.cs \r\nusing System;\r\n\r\nConsole.WriteLine(\"Hello, WASI Console!\");\r\n$ dotnet run\r\nWasmAppHost --runtime-config \/Users\/rich\/wasiconsole\/bin\/Debug\/net8.0\/wasi-wasm\/AppBundle\/wasiconsole.runtimeconfig.json\r\nRunning: wasmtime run --dir . -- dotnet.wasm wasiconsole\r\nUsing working directory: \/Users\/rich\/wasiconsole\/bin\/Debug\/net8.0\/wasi-wasm\/AppBundle\r\nHello, WASI Console!<\/code><\/pre>\n<p>The app was run using <code>wasmtime<\/code>. There is no x64 or Arm64 here, just Wasm. <\/p>\n<p><code>dotnet run<\/code> is providing extra information (in the console output) to help explain what is going on. That will likely change in future. All of the interaction with the host system is managed by <code>wasmtime<\/code>.<\/p>\n<p>We can look a bit deeper at the <code>AppBundle<\/code> directory.<\/p>\n<pre><code class=\"language-bash\">$ ls -l bin\/Release\/net8.0\/wasi-wasm\/AppBundle\r\ntotal 24872\r\n-rwxr--r--  1 rich  staff  11191074 Oct 31 07:53 dotnet.wasm\r\n-rwxr--r--  1 rich  staff   1526128 Oct 11 14:00 icudt.dat\r\ndrwxr-xr-x  6 rich  staff       192 Nov 19 19:35 managed\r\n-rwxr-xr-x  1 rich  staff        48 Nov 19 19:35 run-wasmtime.sh\r\n-rw-r--r--  1 rich  staff       915 Nov 19 19:35 runtimeconfig.bin\r\ndrwxr-xr-x  2 rich  staff        64 Nov 19 19:35 tmp\r\n-rw-r--r--  1 rich  staff      1457 Nov 19 19:35 wasiconsole.runtimeconfig.json\r\n$ ls -l bin\/Release\/net8.0\/wasi-wasm\/AppBundle\/managed \r\ntotal 3432\r\n-rw-r--r--  1 rich  staff    27136 Nov 19 19:35 System.Console.dll\r\n-rw-r--r--  1 rich  staff  1711616 Nov 19 19:35 System.Private.CoreLib.dll\r\n-rw-r--r--  1 rich  staff     5632 Nov 19 19:35 System.Runtime.dll\r\n-rw-r--r--  1 rich  staff     5120 Nov 19 19:35 wasiconsole.dll<\/code><\/pre>\n<p>The SDK published the app into a self-contained deployment.  The .NET runtime &#8212; <code>dotnet.wasm<\/code> &#8212; has already been compiled to Wasm (on our build machines). The app and <code>dotnet.wasm<\/code> are loaded together in <code>wasmtime<\/code>, which runs all the code. The actual managed code of the app &#8212; in the <code>managed<\/code> directory &#8212; is interpreted at runtime, just like with Blazor WebAssembly. <a href=\"https:\/\/github.com\/yowl\">@yowl<\/a> and <a href=\"https:\/\/github.com\/SingleAccretion\">@SingleAccretion<\/a> community members have been experimenting with <a href=\"https:\/\/github.com\/dotnet\/runtimelab\/tree\/feature\/NativeAOT-LLVM\">Wasm and native AOT<\/a>.<\/p>\n<p>You might be wondering why we need all these files to be separate, when the obviously better option is to have one <code>wasiconsole.wasm<\/code> file. We can do that, too, but it is covered a little later in the post, since we need to install a bit more software on the machine (that doesn&#8217;t currently come with the <code>wasi-experimental<\/code> workload).<\/p>\n<h2>What does <code>RuntimeInformation<\/code> tell us?<\/h2>\n<p><code>RuntimeInformation<\/code> is one of my favorite types. It gives us a better sense of the target environment.<\/p>\n<p>We can change the sample a tiny bit to show some more useful information.<\/p>\n<pre><code class=\"language-csharp\">using System;\r\nusing System.Runtime.InteropServices;\r\n\r\nConsole.WriteLine($\"Hello {RuntimeInformation.OSDescription}:{RuntimeInformation.OSArchitecture}\");\r\nConsole.WriteLine($\"With love from {RuntimeInformation.FrameworkDescription}\");<\/code><\/pre>\n<p>It produces this output.<\/p>\n<pre><code class=\"language-bash\">Hello WASI:Wasm\r\nWith love from .NET 8.0.0<\/code><\/pre>\n<p>The first line is interesting. The operating system is <code>WASI<\/code> and the architecture is <code>Wasm<\/code>. That makes sense, with a little more context. There is a mention earlier in the post that Wasm can be thought as &#8220;no operating system&#8221;, however, we cannot simply call it <code>Wasm<\/code> since the existing browser and WASI environments are quite different. As a result, the only coherent name for this environment is <code>WASI<\/code>, while <code>Wasm<\/code> is unambigiously the &#8220;chip architecture&#8221;.<\/p>\n<p><code>Wasm<\/code> is a 32-bit compute environment, which means that 2^32 bytes are addressable. However, a Wasm runtime can be configured to use <a href=\"https:\/\/github.com\/WebAssembly\/memory64\"><code>memory64<\/code><\/a>, enabling access to &gt;4GB of memory. We don&#8217;t have support for that yet.<\/p>\n<h2>Accessing the host file system<\/h2>\n<p>Wasmtime (and other Wasm runtimes) provide the option to map a host directory to a guest directory. This is similar to volume mounting with Docker from a user standpoint, however, the implementation details are different.<\/p>\n<p>Let&#8217;s look at a simple app that depends of directory mounting. It <a href=\"https:\/\/github.com\/richlander\/wasm-samples\/blob\/main\/tomarkup\/README.md\">converts markdown to HTML<\/a> using the <a href=\"https:\/\/www.nuget.org\/packages\/Markdig\">Markdig<\/a> package. It&#8217;s fair to say that Markdig wasn&#8217;t written to run as Wasm. Markdig is happy as long as a cozy managed environment can be created for it and that&#8217;s what we&#8217;ve done.<\/p>\n<p>Let&#8217;s try on a Mac M1 (Arm64) machine.<\/p>\n<pre><code class=\"language-bash\">$ pwd\r\n\/Users\/rich\/git\/wasm-samples\/tomarkup\r\n$ dotnet publish\r\n$ cd bin\/Release\/net8.0\/wasi-wasm\/AppBundle \r\n$ cat run-wasmtime.sh\r\nwasmtime run --dir . dotnet.wasm tomarkup $*\r\n$ .\/run-wasmtime.sh \r\nA valid inputfile must be provided.\r\n$  wasmtime run --dir . --mapdir \/markdown::\/Users\/rich\/markdown --mapdir \/tmp::\/Users\/rich dotnet.wasm tomarkup $* \/markdown\/README.md \/tmp\/README.html\r\n$ ls ~\/*.html\r\n\/Users\/rich\/README.html\r\n$ cat ~\/markdown\/README.md | head -n 3  \r\n# .NET Runtime\r\n\r\n[![Build Status](https:\/\/dev.azure.com\/dnceng-public\/public\/_apis\/build\/status\/dotnet\/runtime\/runtime?branchName=main)](https:\/\/dev.azure.com\/dnceng-public\/public\/_build\/latest?definitionId=129&amp;branchName=main)\r\n$ cat ~\/README.html | head -n 3       \r\n&lt;h1&gt;.NET Runtime&lt;\/h1&gt;\r\n&lt;p&gt;&lt;a href=\"https:\/\/dev.azure.com\/dnceng-public\/public\/_build\/latest?definitionId=129&amp;amp;branchName=main\"&gt;&lt;img src=\"https:\/\/dev.azure.com\/dnceng-public\/public\/_apis\/build\/status\/dotnet\/runtime\/runtime?branchName=main\" alt=\"Build Status\" \/&gt;&lt;\/a&gt;\r\n&lt;a href=\"https:\/\/github.com\/dotnet\/runtime\/labels\/help%20wanted\"&gt;&lt;img src=\"https:\/\/img.shields.io\/github\/issues\/dotnet\/runtime\/help%20wanted?style=flat-square&amp;amp;color=%232EA043&amp;amp;label=help%20wanted\" alt=\"Help Wanted\" \/&gt;&lt;\/a&gt;<\/code><\/pre>\n<p><code>--mapdir<\/code> is mounting the directories, from host to guest.<\/p>\n<p>As you can see, a <a href=\"https:\/\/github.com\/dotnet\/runtime\/blob\/main\/README.md\">markdown file<\/a> has been converted to HTML. The first three lines of each file have been shown for brevity.<\/p>\n<p>The CLI gestures required for directory mounting are currently a bit inconvenient. That&#8217;s something we&#8217;ll need to look at in a future release. It&#8217;s a really a question of how <code>dotnet run<\/code> and <code>wasmtime run<\/code> should relate.<\/p>\n<h2>But can it word count?<\/h2>\n<p>I recently published <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/the-convenience-of-system-io\/\">The Convenience of System.IO<\/a>, which focused on word counting. Can we get the same code to run as Wasm and see how fast it runs?<\/p>\n<p>The word counting benchmarks in that post were run on Linux x64. Let&#8217;s keep that the same, but run as Wasm this time.<\/p>\n<pre><code class=\"language-bash\">$ pwd\r\n\/Users\/rich\/git\/convenience\/wordcount\/count\r\n$ grep asm count.csproj \r\n    &lt;RuntimeIdentifier&gt;wasi-wasm&lt;\/RuntimeIdentifier&gt;\r\n    &lt;WasmSingleFileBundle&gt;true&lt;\/WasmSingleFileBundle&gt;\r\n$ dotnet publish\r\n$ cd bin\/Release\/net8.0\/wasi-wasm\/AppBundle\/\r\n$ WASMTIME_NEW_CLI=0 wasmtime run --mapdir \/text::\/home\/rich\/git\/convenience\/wordcount count.wasm $* \/text\/Clarissa_Harlowe\r\n    11716  110023  610515 \/text\/Clarissa_Harlowe\/clarissa_volume1.txt\r\n    12124  110407  610557 \/text\/Clarissa_Harlowe\/clarissa_volume2.txt\r\n    11961  109622  606948 \/text\/Clarissa_Harlowe\/clarissa_volume3.txt\r\n    12168  111908  625888 \/text\/Clarissa_Harlowe\/clarissa_volume4.txt\r\n    12626  108593  614062 \/text\/Clarissa_Harlowe\/clarissa_volume5.txt\r\n    12434  107576  607619 \/text\/Clarissa_Harlowe\/clarissa_volume6.txt\r\n    12818  112713  628322 \/text\/Clarissa_Harlowe\/clarissa_volume7.txt\r\n    12331  109785  611792 \/text\/Clarissa_Harlowe\/clarissa_volume8.txt\r\n    11771  104934  598265 \/text\/Clarissa_Harlowe\/clarissa_volume9.txt\r\n        9     153    1044 \/text\/Clarissa_Harlowe\/summary.md\r\n   109958  985714  5515012 total<\/code><\/pre>\n<p>I updated the <a href=\"https:\/\/github.com\/richlander\/convenience\/blob\/main\/wordcount\/count\/count.csproj\">project file<\/a> to include <code>&lt;RuntimeIdentifier&gt;wasi-wasm&lt;\/RuntimeIdentifier&gt;<\/code> and <code>&lt;WasmSingleFileBundle&gt;true&lt;\/WasmSingleFileBundle&gt;<\/code> and commented out the <code>PublishAot<\/code> related properties. I also added a <a href=\"https:\/\/github.com\/dotnet\/runtime\/issues\/95345\"><code>runtimeconfig.template.json<\/code><\/a> file.     No changes were made to the app code.<\/p>\n<p>We now have the whole app in a single file bundle.<\/p>\n<pre><code class=\"language-bash\">$ ls -l bin\/Release\/net8.0\/wasi-wasm\/AppBundle\/\r\ntotal 6684\r\n-rw-r--r-- 1 rich rich    1397 Nov 19 19:59 count.runtimeconfig.json\r\n-rwxr-xr-x 1 rich rich 6827282 Nov 19 19:59 count.wasm\r\n-rw-r--r-- 1 rich rich     915 Nov 19 19:59 runtimeconfig.bin\r\n-rwxr-xr-x 1 rich rich      27 Nov 19 19:59 run-wasmtime.sh\r\ndrwxr-xr-x 2 rich rich    4096 Nov 19 19:59 tmp<\/code><\/pre>\n<p>That looks better. The app is just shy of 7MB. I had to install the <a href=\"https:\/\/github.com\/WebAssembly\/wasi-sdk\/releases\/tag\/wasi-sdk-20\">WASI-SDK<\/a> to use the <code>WasmSingleFileBundle<\/code> property and set an environment variable to enable <code>dotnet publish<\/code> to find the required tools.<\/p>\n<pre><code class=\"language-bash\">$ echo $WASI_SDK_PATH\r\n\/home\/rich\/wasi-sdk\/wasi-sdk-20.0\/<\/code><\/pre>\n<p>There was a recent breaking change in wasmtime. I chose to use <a href=\"https:\/\/github.com\/bytecodealliance\/wasmtime\/issues\/7384\">WASMTIME_NEW_CLI=0<\/a> to get back to the old behavior for running the sample.<\/p>\n<p>Let&#8217;s get back to performance. First, run as wasm (executing managed code via an interpreter):<\/p>\n<pre><code class=\"language-bash\">$ time WASMTIME_NEW_CLI=0 wasmtime run --mapdir \/text::\/home\/rich\/git\/convenience\/wordcount count.wasm $* \/text\/Clarissa_Harlowe\r\n    11716  110023  610515 \/text\/Clarissa_Harlowe\/clarissa_volume1.txt\r\n    12124  110407  610557 \/text\/Clarissa_Harlowe\/clarissa_volume2.txt\r\n    11961  109622  606948 \/text\/Clarissa_Harlowe\/clarissa_volume3.txt\r\n    12168  111908  625888 \/text\/Clarissa_Harlowe\/clarissa_volume4.txt\r\n    12626  108593  614062 \/text\/Clarissa_Harlowe\/clarissa_volume5.txt\r\n    12434  107576  607619 \/text\/Clarissa_Harlowe\/clarissa_volume6.txt\r\n    12818  112713  628322 \/text\/Clarissa_Harlowe\/clarissa_volume7.txt\r\n    12331  109785  611792 \/text\/Clarissa_Harlowe\/clarissa_volume8.txt\r\n    11771  104934  598265 \/text\/Clarissa_Harlowe\/clarissa_volume9.txt\r\n        9     153    1044 \/text\/Clarissa_Harlowe\/summary.md\r\n   109958  985714  5515012 total\r\nElapsed time (ms): 821\r\nElapsed time (us): 821223.8\r\n\r\nreal    0m0.897s\r\nuser    0m0.846s\r\nsys 0m0.030s<\/code><\/pre>\n<p>Now with our (even more) <a href=\"https:\/\/github.com\/dotnet\/runtimelab\/tree\/feature\/NativeAOT-LLVM\">experimental native AOT support<\/a> for Wasm.<\/p>\n<pre><code class=\"language-bash\">$ time WASMTIME_NEW_CLI=0 wasmtime run --mapdir \/text::\/home\/rich\/git\/convenience\/wordcount count.wasm $* \/text\/Clarissa_Harlowe\r\n    11716  110023  610515 \/text\/Clarissa_Harlowe\/clarissa_volume1.txt\r\n    12124  110407  610557 \/text\/Clarissa_Harlowe\/clarissa_volume2.txt\r\n    11961  109622  606948 \/text\/Clarissa_Harlowe\/clarissa_volume3.txt\r\n    12168  111908  625888 \/text\/Clarissa_Harlowe\/clarissa_volume4.txt\r\n    12626  108593  614062 \/text\/Clarissa_Harlowe\/clarissa_volume5.txt\r\n    12434  107576  607619 \/text\/Clarissa_Harlowe\/clarissa_volume6.txt\r\n    12818  112713  628322 \/text\/Clarissa_Harlowe\/clarissa_volume7.txt\r\n    12331  109785  611792 \/text\/Clarissa_Harlowe\/clarissa_volume8.txt\r\n    11771  104934  598265 \/text\/Clarissa_Harlowe\/clarissa_volume9.txt\r\n        9     153    1044 \/text\/Clarissa_Harlowe\/summary.md\r\n   109958  985714  5515012 total\r\nElapsed time (ms): 60\r\nElapsed time (us): 60322.2\r\n\r\nreal    0m0.107s\r\nuser    0m0.064s\r\nsys 0m0.045s<\/code><\/pre>\n<p>Now, run with CoreCLR on Linux x64:<\/p>\n<pre><code class=\"language-bash\">$ time .\/app\/count ..\/Clarissa_Harlowe\/\r\n    11716  110023  610515 ..\/Clarissa_Harlowe\/clarissa_volume1.txt\r\n    12124  110407  610557 ..\/Clarissa_Harlowe\/clarissa_volume2.txt\r\n    11961  109622  606948 ..\/Clarissa_Harlowe\/clarissa_volume3.txt\r\n    12168  111908  625888 ..\/Clarissa_Harlowe\/clarissa_volume4.txt\r\n    12626  108593  614062 ..\/Clarissa_Harlowe\/clarissa_volume5.txt\r\n    12434  107576  607619 ..\/Clarissa_Harlowe\/clarissa_volume6.txt\r\n    12818  112713  628322 ..\/Clarissa_Harlowe\/clarissa_volume7.txt\r\n    12331  109785  611792 ..\/Clarissa_Harlowe\/clarissa_volume8.txt\r\n    11771  104934  598265 ..\/Clarissa_Harlowe\/clarissa_volume9.txt\r\n        9     153    1044 ..\/Clarissa_Harlowe\/summary.md\r\n   109958  985714  5515012 total\r\nElapsed time (ms): 77\r\nElapsed time (us): 77252.9\r\n\r\nreal    0m0.128s\r\nuser    0m0.096s\r\nsys 0m0.014s<\/code><\/pre>\n<p>Those are interesting results. We&#8217;ve got interpretation, AOT, and JIT code generation approaches to compare. The Wasm intepreter is able to count (just shy of) one million words in just under one second while AOT-compiled Wasm and the JIT runtime can do the same around 100 milliseconds.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2023\/12\/clarissa-read-perf.png\" alt=\"performance chart comparing wasm, AOT, and JIT implementations\" \/><\/p>\n<p>Note: <code>Main method<\/code> is the time to run <code>main<\/code>, as measured by <code>StopWatch<\/code>. <code>Process<\/code> is the complete process duration, as measured by <code>time<\/code>.<\/p>\n<p>This chart shows all of the results in context, including those in the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/the-convenience-of-system-io\/#performance-parity-with-wc\">The convenience of System.IO<\/a> post.<\/p>\n<p><code>wasmtime<\/code> JIT compiles the Wasm code to the target environment (in this case to Linux+x64). It&#8217;s possible to AOT the Wasm code, using <a href=\"https:\/\/bytecodealliance.github.io\/wamr.dev\/blog\/introduction-to-wamr-running-modes\/\"><code>wamr<\/code><\/a>, for example. I&#8217;ll leave that for another post.<\/p>\n<h2>Light-weight functions<\/h2>\n<p>Hmmm &#8230; But if we were using the Wasm more like a typical function than an app, we might not be counting a million words, but doing something more light-weight. Let&#8217;s re-run the comparison, but with the smallest file instead.<\/p>\n<p>With Wasm, using our interpreter:<\/p>\n<pre><code class=\"language-bash\">$ time WASMTIME_NEW_CLI=0 wasmtime run --mapdir \/text::\/home\/rich\/git\/convenience\/wordcount count.wasm $* \/text\/Clarissa_Harlowe\/summary.md\r\n        9     153    1044 \/text\/Clarissa_Harlowe\/summary.md\r\nElapsed time (ms): 21\r\nElapsed time (us): 21020.8\r\n\r\nreal    0m0.098s\r\nuser    0m0.083s\r\nsys 0m0.014s<\/code><\/pre>\n<p>With Wasm and native AOT:<\/p>\n<pre><code class=\"language-bash\">$ time WASMTIME_NEW_CLI=0 wasmtime run --mapdir \/text::\/home\/rich\/git\/convenience\/wordcount count.wasm $* \/text\/Clarissa_Harlowe\/summary.md\r\n        9     153    1044 \/text\/Clarissa_Harlowe\/summary.md\r\nElapsed time (ms): 0\r\nElapsed time (us): 825.3\r\n\r\nreal    0m0.048s\r\nuser    0m0.035s\r\nsys 0m0.014s<\/code><\/pre>\n<p>Again, with CoreCLR:<\/p>\n<pre><code class=\"language-bash\">$ time .\/app\/count ..\/Clarissa_Harlowe\/summary.md \r\n        9     153    1044 ..\/Clarissa_Harlowe\/summary.md\r\nElapsed time (ms): 16\r\nElapsed time (us): 16100\r\n\r\nreal    0m0.063s\r\nuser    0m0.027s\r\nsys 0m0.019s<\/code><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2023\/12\/clarissa-summary-read-perf.png\" alt=\"performance chart comparing wasm, AOT, and JIT implementations\" \/><\/p>\n<p>This chart shows all of the results in context, this time for the smaller document.<\/p>\n<p>Interesting. For a smaller workload, the performance differences between some of these options starts to close. We can also see the differences in runtime startup costs. It&#8217;s too early to say, but these dynamics may be a key consideration for this technology. An important caveat is that word counting is just one scenario and other scenarios may have quite different results. For now, this sample is providing a sufficient taste of what to expect.<\/p>\n<p>This is all still early days. As we get farther, we&#8217;ll want to test more interesting scenarios to developer a more representative understanding. I&#8217;m also sure that some of these performance numbers will improve.<\/p>\n<h2>A <code>slight<\/code> improvement in functionality<\/h2>\n<p>The promise of WASI is being able to rely on a set of interfaces (and matching implementations) with rich functionality. <a href=\"https:\/\/github.com\/deislabs\/spiderlightning\">SpiderLighting<\/a> delivers on this promise.<\/p>\n<blockquote>\n<p>SpiderLightning: A set of WIT interfaces that abstract distributed application capabilities and a runtime CLI for running Wasm applications that use these capabilities.<\/p>\n<\/blockquote>\n<p>As stated earlier, WASI is intended to define the same kind of platform functionality we have with the <code>System<\/code> namespace (but a small subset of that). You can see the interface definitions for (at least one vision of) that in the SpiderLighting <a href=\"https:\/\/github.com\/deislabs\/spiderlightning\/tree\/main\/wit\">wit<\/a> directory.<\/p>\n<blockquote>\n<p>If you&#8217;ve got wits about you, then use your wits<\/p>\n<\/blockquote>\n<p>You should be able to walk up to any <code>WIT<\/code> interface, reference it, see its full type shape, and start coding with it. We&#8217;re still a couple steps away from the final experience, but that&#8217;s the vision.<\/p>\n<p>SpiderLighting ships a handy CLI tool called <code>slight<\/code> that wires up <code>wasmtime<\/code>, your app, the <a href=\"https:\/\/github.com\/WebAssembly\/wasi-sdk\">WASI SDK<\/a> and any of the WIT implementations required by your app (as declared in a <a href=\"https:\/\/github.com\/SteveSandersonMS\/spiderlightning-dotnet\/blob\/main\/sample\/WebServer\/slightfile.toml\"><code>slightfile.toml<\/code><\/a>).<\/p>\n<p>The SpiderLighting team told us they built <code>slight<\/code> (and related components) as a tool to help them develop the <a href=\"https:\/\/github.com\/WebAssembly\/wasi-cloud-core\">wasi-cloud-core<\/a> specification, to enable serverless capabilities. In the near future, we expect other app hosts, like <a href=\"https:\/\/www.fermyon.com\/spin\">Fermyon Spin<\/a>, to use wasi-cloud-core interfaces and then we&#8217;d use one of those hosts instead of <code>slight<\/code>.<\/p>\n<p>We have a set of <a href=\"https:\/\/github.com\/SteveSandersonMS\/spiderlightning-dotnet\/tree\/main\/sample\">Spiderlight samples<\/a>. The following sample creates a WASI key-value store and then prints to the console. Note that <code>dotnet run<\/code> is using <code>slight<\/code> as an implementation detail.<\/p>\n<pre><code class=\"language-csharp\">using SpiderLightning;\r\n\r\nusing var keyValue = new KeyValue(\"placeholder-name\");\r\nkeyValue.Set(\"somekey\", \"Hello from .NET. This value is from a SpiderLightning key-value store.\");\r\n\r\nConsole.WriteLine(keyValue.GetString(\"somekey\"));<\/code><\/pre>\n<p>Remember that <code>KeyValue<\/code> isn&#8217;t a C# type but a WASI interface projected into C#.<\/p>\n<p>Here&#8217;s what the code looks like when run.<\/p>\n<pre><code class=\"language-bash\">$ pwd\r\n\/home\/rich\/git\/spiderlightning-dotnet\r\n$ docker run --rm -it -v $(pwd):\/source -w \/source\/sample\/ConsoleApp wasi-sdk dotnet run -c Release\r\nHello from .NET. This value is from a SpiderLightning key-value store.<\/code><\/pre>\n<p>I&#8217;m running the app in a <a href=\"https:\/\/github.com\/SteveSandersonMS\/spiderlightning-dotnet\/tree\/main\/docker\">container<\/a> that has all the required dependencies installed. Can containers and WASI be used together? For sure.<\/p>\n<p>You can build apps that rely on the WASI SDK in a <a href=\"https:\/\/github.com\/WebAssembly\/wasi-sdk\/releases\/tag\/wasi-sdk-20\">limited set of environments<\/a> and run them with <code>slight<\/code> in a <a href=\"https:\/\/github.com\/deislabs\/spiderlightning\/releases\/tag\/v0.5.1\">larger set of environments<\/a>. Windows and macOS Arm64 seem to be worst off for support. That will certainly change over time.<\/p>\n<h2>Web scenarios<\/h2>\n<p>A lot of the interest in WASI is to enable hosting small and portable Wasm functions and apps. A key aspect of that is using some form of web programming model. At the moment, we don&#8217;t have ASP.NET Core enabled with WASI. For now, we&#8217;ve exposed the <a href=\"https:\/\/github.com\/deislabs\/spiderlightning\/blob\/main\/wit\/http-server.wit\"><code>http-server<\/code><\/a> WASI type.<\/p>\n<p>It enables the <a href=\"https:\/\/github.com\/SteveSandersonMS\/spiderlightning-dotnet\/blob\/main\/sample\/WebServer\/Program.cs\">following pattern<\/a>.<\/p>\n<pre><code class=\"language-csharp\">HttpServer.OnIncomingRequest(request =&gt;\r\n{\r\n    return new HttpResponse(200)\r\n        .WithBody($\"&lt;h1&gt;Hello!&lt;\/h1&gt; You did a {request.Method} request to {request.Uri} with {request.Headers.Count} headers\")\r\n        .WithHeaders(new[] { KeyValuePair.Create(\"content-type\", \"text\/html\") });\r\n});<\/code><\/pre>\n<p>That&#8217;s a bit low-level. The <a href=\"https:\/\/github.com\/SteveSandersonMS\/spiderlightning-dotnet\/blob\/9db2a9b1e4c6b31c0de8518803c79b4eb0dcd1cf\/src\/SpiderLightning\/Http\/HttpServer.cs#L22-L25\">delegate<\/a> also isn&#8217;t async friendly. Here&#8217;s <a href=\"https:\/\/github.com\/bytecodealliance\/preview2-prototyping\/blob\/ecfc05ae3b4bd0dd15eaa3aadd70ebdaabc6ea8c\/wit\/deps\/sockets\/tcp.wit#L149-L158\">some hints<\/a> into how async might eventually work.<\/p>\n<p>I tried writing a <a href=\"https:\/\/github.com\/SteveSandersonMS\/spiderlightning-dotnet\/pull\/6\">bigger sample with this API<\/a>. It&#8217;s currently blocked because we don&#8217;t have a way to call <code>https<\/code> endpoints. I could have worked around that by copying all the required JSON files locally, but wouldn&#8217;t be nearly as compelling.<\/p>\n<p>This area is the most interesting, but is also the least defined. We expect that we&#8217;re at least a year away from being able to run what we all consider real web apps and functions. We&#8217;re aiming for a model where you don&#8217;t have to change much of your code to use Wasm as a deployment target.<\/p>\n<h2>Experiment<\/h2>\n<p>The WASI workload is currently an experiment, hence the workload name. It will remain an experiment at least until WASI itelf has a stable 1.0 release. We cannot predict with any certainty when that will be.<\/p>\n<p>There are several backlog items to investigate and resolve:<\/p>\n<ul>\n<li>Integrated debugging<\/li>\n<li>AOT support<\/li>\n<li><code>dotnet run<\/code> with <code>wasmtime<\/code> CLI arguments<\/li>\n<li>Support for more WASI interfaces, likely via better <code>witbindgen<\/code> support<\/li>\n<\/ul>\n<h2>Closing<\/h2>\n<p>The higher-level story is that we&#8217;ve been able to adapt our Blazor Wasm implementation &#8212; and really .NET as a whole &#8212; to this new frontier of portable computing. A lot already works, as demonstrated by these few demos.<\/p>\n<p>You can try out everything (and more) you&#8217;ve read in this post with .NET 8. Start with the following command, to install the required software.<\/p>\n<pre><code class=\"language-bash\">dotnet workload install wasi-experimental<\/code><\/pre>\n<p>Over the next year, we&#8217;ll be focused on improving the capability and UX of our current implementation and following along with the general evolution of WASI. We&#8217;re also looking forward to watching how cloud teams adopt WASI within their services. To date, we&#8217;ve been building enabling technology. As we get farther, we&#8217;ll consider focusing on more targeting experiences that pair with a cloud service. For now, that&#8217;s all future looking, like the rest of WASI.<\/p>\n<blockquote>\n<p>Brevity is the soul of wit. &#8212; William Shakespeare<\/p>\n<\/blockquote>\n<p>If you got this far, you can appreciate that you wouldn&#8217;t be nearly so well informed if I&#8217;d been brief. But with the length and detail of this post, I&#8217;m at my wit&#8217;s end.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We&#8217;re closely following the progress of WebAssembly, including WebAssembly System Interface (WASI). There&#8217;s a new experimental workload in .NET 8 for WASI that extends the capabilities of Wasm towards the Cloud.<\/p>\n","protected":false},"author":1312,"featured_media":50399,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685,7509,7252],"tags":[7673],"class_list":["post-49436","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","category-aspnetcore","category-cloud","tag-wasm"],"acf":[],"blog_post_summary":"<p>We&#8217;re closely following the progress of WebAssembly, including WebAssembly System Interface (WASI). There&#8217;s a new experimental workload in .NET 8 for WASI that extends the capabilities of Wasm towards the Cloud.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/49436","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/1312"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=49436"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/49436\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/50399"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=49436"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=49436"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=49436"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}