From 331fef6192e95c995f896ffe02758112310abb9d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Sun, 30 Mar 2025 23:41:42 +0300 Subject: [PATCH 1/7] support llvm --- samples/bench/bench.mjs | 2 +- samples/bench/bootsharp/Boot.csproj | 30 +++++++++++++++---- samples/bench/dotnet-llvm/Program.cs | 1 - samples/bench/readme.md | 10 +++---- .../Bootsharp.Publish.Test/Pack/PackTest.cs | 3 +- .../Bootsharp.Publish/Pack/BootsharpPack.cs | 16 ++++++---- src/cs/Bootsharp/Bootsharp.csproj | 9 ++---- src/cs/Bootsharp/Build/Bootsharp.props | 3 ++ src/cs/Bootsharp/Build/Bootsharp.targets | 13 +++++--- 9 files changed, 58 insertions(+), 29 deletions(-) diff --git a/samples/bench/bench.mjs b/samples/bench/bench.mjs index ca6619e5..3a4f8581 100644 --- a/samples/bench/bench.mjs +++ b/samples/bench/bench.mjs @@ -19,7 +19,7 @@ if (!lang || lang.toLowerCase() === "rust") if (!lang || lang.toLowerCase() === "llvm") await run(".NET LLVM", await initDotNetLLVM()); if (!lang || lang.toLowerCase() === "net") - await run(".NET", await initDotNet()); + await run(".NET AOT", await initDotNet()); if (!lang || lang.toLowerCase() === "boot") await run("Bootsharp", await initBootsharp()); if (!lang || lang.toLowerCase() === "go") diff --git a/samples/bench/bootsharp/Boot.csproj b/samples/bench/bootsharp/Boot.csproj index a82b5027..91e80bff 100644 --- a/samples/bench/bootsharp/Boot.csproj +++ b/samples/bench/bootsharp/Boot.csproj @@ -1,12 +1,8 @@ - net9.0 - Release + net9.0-browser browser-wasm - true - Speed - true @@ -15,4 +11,28 @@ + + + + true + true + true + none + $(EmccFlags) -O3 + false + + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json; + + + + + + + + + + + + + diff --git a/samples/bench/dotnet-llvm/Program.cs b/samples/bench/dotnet-llvm/Program.cs index 538a1b26..dfa6363c 100644 --- a/samples/bench/dotnet-llvm/Program.cs +++ b/samples/bench/dotnet-llvm/Program.cs @@ -10,7 +10,6 @@ public struct Data public string[] Messages; } - [JsonSerializable(typeof(Data))] internal partial class SourceGenerationContext : JsonSerializerContext; diff --git a/samples/bench/readme.md b/samples/bench/readme.md index 6d440e13..c2e5069e 100644 --- a/samples/bench/readme.md +++ b/samples/bench/readme.md @@ -14,8 +14,8 @@ All results are relative to the Rust baseline (lower is better). ## 2024 (.NET 9) -| | Rust | .NET LLVM | .NET | Bootsharp | Go | -|-------------|-------|-----------|--------|-----------|---------| -| Echo Number | `1.0` | `14.4` | `21.1` | `25.7` | `718.7` | -| Echo Struct | `1.0` | `1.5` | `4.3` | `4.9` | `20.8` | -| Fibonacci | `1.0` | `1.1` | `1.5` | `1.6` | `3.8` | +| | Rust | .NET LLVM | .NET AOT | Bootsharp | Go | +|-------------|-------|-----------|-----------|-----------|---------| +| Echo Number | `1.0` | `14.4` | `21.1` | `25.7` | `718.7` | +| Echo Struct | `1.0` | `1.5` | `4.3` | `4.9` | `20.8` | +| Fibonacci | `1.0` | `1.1` | `1.5` | `1.6` | `3.8` | diff --git a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs index 1188419b..8ada7d33 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs @@ -45,6 +45,7 @@ protected override void AddAssembly (string assemblyName, params MockSource[] so BuildEngine = Engine, TrimmingEnabled = false, EmbedBinaries = false, - Threading = false + Threading = false, + LLVM = Task.LLVM }; } diff --git a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs index 9c46ab37..abb0baa7 100644 --- a/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs +++ b/src/cs/Bootsharp.Publish/Pack/BootsharpPack.cs @@ -11,6 +11,7 @@ public sealed class BootsharpPack : Microsoft.Build.Utilities.Task public required bool TrimmingEnabled { get; set; } public required bool EmbedBinaries { get; set; } public required bool Threading { get; set; } + public required bool LLVM { get; set; } public override bool Execute () { @@ -32,13 +33,18 @@ private Preferences ResolvePreferences () private SolutionInspection InspectSolution (Preferences prefs) { var inspector = new SolutionInspector(prefs, EntryAssemblyName); - // Assemblies in publish dir are trimmed and don't contain some data (eg, method arg names). - // While the inspected dir contains extra assemblies we don't need in build. Hence the filtering. - var included = Directory.GetFiles(BuildDirectory, "*.wasm").Select(Path.GetFileNameWithoutExtension).ToHashSet(); - var inspected = Directory.GetFiles(InspectedDirectory, "*.dll").Where(p => included.Contains(Path.GetFileNameWithoutExtension(p))); - var inspection = inspector.Inspect(InspectedDirectory, inspected); + var inspection = inspector.Inspect(InspectedDirectory, GetFiles()); new InspectionReporter(Log).Report(inspection); return inspection; + + IEnumerable GetFiles () + { + if (LLVM) return Directory.GetFiles(InspectedDirectory, "*.dll"); + // Assemblies in publish dir are trimmed and don't contain some data (eg, method arg names). + // While the inspected dir contains extra assemblies we don't need in build. Hence the filtering. + var included = Directory.GetFiles(BuildDirectory, "*.wasm").Select(Path.GetFileNameWithoutExtension).ToHashSet(); + return Directory.GetFiles(InspectedDirectory, "*.dll").Where(p => included.Contains(Path.GetFileNameWithoutExtension(p))); + } } private void GenerateBindings (Preferences prefs, SolutionInspection inspection) diff --git a/src/cs/Bootsharp/Bootsharp.csproj b/src/cs/Bootsharp/Bootsharp.csproj index bb3ee012..35ba3097 100644 --- a/src/cs/Bootsharp/Bootsharp.csproj +++ b/src/cs/Bootsharp/Bootsharp.csproj @@ -2,11 +2,9 @@ net9.0 - enable - enable Bootsharp Bootsharp - Compile C# solution into single-file ES module with auto-generated JavaScript bindings and type definitions. + Use C# in web apps with comfort. NU5100 @@ -15,15 +13,12 @@ - - <_Parameter1>browser - - + diff --git a/src/cs/Bootsharp/Build/Bootsharp.props b/src/cs/Bootsharp/Build/Bootsharp.props index 55fb6dd1..6f6876ed 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.props +++ b/src/cs/Bootsharp/Build/Bootsharp.props @@ -10,6 +10,7 @@ true true false + true bootsharp @@ -17,6 +18,8 @@ true false + + false diff --git a/src/cs/Bootsharp/Build/Bootsharp.targets b/src/cs/Bootsharp/Build/Bootsharp.targets index b9869418..de8c0eb7 100644 --- a/src/cs/Bootsharp/Build/Bootsharp.targets +++ b/src/cs/Bootsharp/Build/Bootsharp.targets @@ -10,6 +10,8 @@ $(BootsharpIntermediateDirectory)/Serializer.g.cs $(BootsharpIntermediateDirectory)/Interop.g.cs $(AssemblyName).dll + CopyNativeBinary + WasmNestedPublishApp @@ -39,6 +41,7 @@ false false false + false false true false @@ -91,11 +94,12 @@ - + - $(WasmAppDir)/$(WasmRuntimeAssetsLocation) + $(PublishDir) + $(WasmAppDir)/$(WasmRuntimeAssetsLocation) $(BaseOutputPath.Replace('\', '/')) $(BootSharpBaseOutputPath)$(BootsharpName) $(BootsharpPublishDirectory)/types @@ -119,7 +123,8 @@ EntryAssemblyName="$(BootsharpEntryAssemblyName)" TrimmingEnabled="$(BootsharpAggressiveTrimming)" EmbedBinaries="$(BootsharpEmbedBinaries)" - Threading="$(BootsharpThreading)"/> + Threading="$(BootsharpThreading)" + LLVM="$(BootsharpLLVM)"/> Date: Mon, 31 Mar 2025 00:43:35 +0300 Subject: [PATCH 2/7] fix tests --- .../Bootsharp.Publish.Test/Pack/PackTest.cs | 2 +- .../Pack/SolutionInspectionTest.cs | 48 +++++++++++++++++++ src/js/test/cs/Test/Test.csproj | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs index 8ada7d33..5985d7eb 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/PackTest.cs @@ -46,6 +46,6 @@ protected override void AddAssembly (string assemblyName, params MockSource[] so TrimmingEnabled = false, EmbedBinaries = false, Threading = false, - LLVM = Task.LLVM + LLVM = false }; } diff --git a/src/cs/Bootsharp.Publish.Test/Pack/SolutionInspectionTest.cs b/src/cs/Bootsharp.Publish.Test/Pack/SolutionInspectionTest.cs index 627ebc1a..9f880464 100644 --- a/src/cs/Bootsharp.Publish.Test/Pack/SolutionInspectionTest.cs +++ b/src/cs/Bootsharp.Publish.Test/Pack/SolutionInspectionTest.cs @@ -25,4 +25,52 @@ public void WhenAssemblyInspectionFailsWarningIsLogged () Execute(); Assert.Contains(Engine.Warnings, w => w.Contains("Failed to inspect 'foo.dll' assembly")); } + + [Fact] + public void IgnoresAssembliesNotPresentInBuildDirectory () + { + var buildDir = $"{Project.Root}/build"; + Task.BuildDirectory = buildDir; + Directory.CreateDirectory(buildDir); + File.WriteAllText($"{buildDir}/foo.wasm", ""); + + foreach (var file in Directory.EnumerateFiles(Project.Root)) + File.WriteAllText($"{buildDir}/{Path.GetFileName(file)}", File.ReadAllText(file)); + + AddAssembly("foo.dll", + WithClass("[JSInvokable] public static void InvFoo () {}") + ); + AddAssembly("bar.dll", + WithClass("[JSInvokable] public static void InvBar () {}") + ); + Execute(); + + Assert.Contains(Engine.Messages, w => w.Contains("foo")); + Assert.DoesNotContain(Engine.Messages, w => w.Contains("bar")); + } + + [Fact] + public void DoesntIgnoreAssembliesWhenLLVM () + { + Task.LLVM = true; + + var buildDir = $"{Project.Root}/build"; + Task.BuildDirectory = buildDir; + Directory.CreateDirectory(buildDir); + File.WriteAllText($"{buildDir}/foo.wasm", ""); + + foreach (var file in Directory.EnumerateFiles(Project.Root)) + File.WriteAllText($"{buildDir}/{Path.GetFileName(file)}", File.ReadAllText(file)); + + AddAssembly("foo.dll", + WithClass("[JSInvokable] public static void InvFoo () {}") + ); + AddAssembly("bar.dll", + WithClass("[JSInvokable] public static void InvBar () {}") + ); + Execute(); + + Assert.Contains(Engine.Messages, w => w.Contains("foo")); + Assert.Contains(Engine.Messages, w => w.Contains("bar")); + } } diff --git a/src/js/test/cs/Test/Test.csproj b/src/js/test/cs/Test/Test.csproj index 7b2a6c52..1730c2fa 100644 --- a/src/js/test/cs/Test/Test.csproj +++ b/src/js/test/cs/Test/Test.csproj @@ -15,7 +15,7 @@ - + From 981e660ad8ef222cec18711adae2e552f976b058 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 31 Mar 2025 00:59:42 +0300 Subject: [PATCH 3/7] update samples --- samples/bench/readme.md | 8 ++++---- samples/trimming/README.md | 8 ++++---- samples/trimming/cs/Program.cs | 7 +++++-- samples/trimming/cs/Trimming.csproj | 26 +++++++++++++++++++++++++- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/samples/bench/readme.md b/samples/bench/readme.md index c2e5069e..b480890d 100644 --- a/samples/bench/readme.md +++ b/samples/bench/readme.md @@ -14,8 +14,8 @@ All results are relative to the Rust baseline (lower is better). ## 2024 (.NET 9) -| | Rust | .NET LLVM | .NET AOT | Bootsharp | Go | +| | Rust | .NET LLVM | Bootsharp | .NET AOT | Go | |-------------|-------|-----------|-----------|-----------|---------| -| Echo Number | `1.0` | `14.4` | `21.1` | `25.7` | `718.7` | -| Echo Struct | `1.0` | `1.5` | `4.3` | `4.9` | `20.8` | -| Fibonacci | `1.0` | `1.1` | `1.5` | `1.6` | `3.8` | +| Echo Number | `1.0` | `11.9` | `11.9` | `21.1` | `718.7` | +| Echo Struct | `1.0` | `1.6` | `1.6` | `4.3` | `20.8` | +| Fibonacci | `1.0` | `1.1` | `1.5` | `1.5` | `3.8` | diff --git a/samples/trimming/README.md b/samples/trimming/README.md index b39009e9..b60d13da 100644 --- a/samples/trimming/README.md +++ b/samples/trimming/README.md @@ -6,7 +6,7 @@ To test and measure build size: ### Measurements (KB) -| .NET | Raw | Brotli | -|-------|-------|--------| -| 8.0.1 | 2,298 | 739 | -| 9.0.1 | 2,369 | 761 | +| | Raw | Brotli | +|-------------|-------|--------| +| .NET 8 | 2,298 | 739 | +| .NET 9 LLVM | 1,749 | 520 | diff --git a/samples/trimming/cs/Program.cs b/samples/trimming/cs/Program.cs index c7b6b5bf..d7e02f6a 100644 --- a/samples/trimming/cs/Program.cs +++ b/samples/trimming/cs/Program.cs @@ -1,9 +1,12 @@ using Bootsharp; -Log("Hello from .NET!"); - public static partial class Program { + public static void Main () + { + Log("Hello from .NET!"); + } + [JSFunction] public static partial void Log (string message); } diff --git a/samples/trimming/cs/Trimming.csproj b/samples/trimming/cs/Trimming.csproj index 6858f874..2a317976 100644 --- a/samples/trimming/cs/Trimming.csproj +++ b/samples/trimming/cs/Trimming.csproj @@ -1,7 +1,7 @@ - net9.0 + net9.0-browser browser-wasm false @@ -19,4 +19,28 @@ WorkingDirectory="$(BootsharpPublishDirectory)"/> + + + + true + true + true + none + $(EmccFlags) -Oz + false + + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json; + + + + + + + + + + + + + From 407c7e5be217e58bf60b83fd355234aca7f79cae Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 31 Mar 2025 01:00:53 +0300 Subject: [PATCH 4/7] bump ver --- src/cs/Directory.Build.props | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cs/Directory.Build.props b/src/cs/Directory.Build.props index 7657a2ea..f5f5a735 100644 --- a/src/cs/Directory.Build.props +++ b/src/cs/Directory.Build.props @@ -1,6 +1,7 @@ + - 0.5.0 + 0.6.0 Elringus javascript typescript ts js wasm node deno bun interop codegen https://sharp.elringus.com @@ -17,4 +18,5 @@ + From a3068c490a7c70c52255c19e85e80c787252784d Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 31 Mar 2025 01:23:22 +0300 Subject: [PATCH 5/7] update docs --- docs/.vitepress/config.ts | 3 +- docs/guide/llvm.md | 61 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 docs/guide/llvm.md diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 4c415b63..a5163284 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -60,7 +60,8 @@ export default defineConfig({ { text: "Interop Instances", link: "/guide/interop-instances" }, { text: "Emit Preferences", link: "/guide/emit-prefs" }, { text: "Build Configuration", link: "/guide/build-config" }, - { text: "Sideloading Binaries", link: "/guide/sideloading" } + { text: "Sideloading Binaries", link: "/guide/sideloading" }, + { text: "NativeAOT-LLVM", link: "/guide/llvm" } ] }, { diff --git a/docs/guide/llvm.md b/docs/guide/llvm.md new file mode 100644 index 00000000..0ecc1145 --- /dev/null +++ b/docs/guide/llvm.md @@ -0,0 +1,61 @@ +# NativeAOT-LLVM + +Starting with v0.6.0 Bootsharp supports .NET's experimental [NativeAOT-LLVM](https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM) backend. + +By default, when targeting `browser-wasm`, .NET is using the Mono runtime, even when compiled in AOT mode. Compared to the modern NativeAOT (previously CoreRT) runtime, Mono's performance is lacking in speed, binary size and compilation times. NativeAOT-LLVM backend not only uses the modern runtime instead of Mono, but also optimizes it with the [LLVM](https://llvm.org) toolchain, further improving the performance. + +Below is a benchmark comparing interop and compute performance of various languages and .NET versions compiled to WASM to give you a rough idea on the differences: + +| | Rust | .NET LLVM | .NET AOT | Go | +|-------------|-------|-----------|-----------|---------| +| Echo Number | `1.0` | `11.9` | `21.1` | `718.7` | +| Echo Struct | `1.0` | `1.6` | `4.3` | `20.8` | +| Fibonacci | `1.0` | `1.1` | `1.5` | `3.8` | + +— the results are relative to the Rust baseline (lower is better). Sources of the benchmark are here: https://github.com/elringus/bootsharp/tree/main/samples/bench. + +## Setup + +Use following `.csproj` as a reference for enabling NativeAOT-LLVM with Bootsharp: + +```xml + + + + + net9.0-browser + browser-wasm + + true + + + + + + + + + + + true + true + none + $(EmccFlags) -O3 + false + + https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json; + + + + + + + + + + + + + + +``` From 384c771c1bd917f3e7f5f5f295a47f543c04d3fa Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 31 Mar 2025 01:30:57 +0300 Subject: [PATCH 6/7] update docs --- docs/guide/build-config.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/guide/build-config.md b/docs/guide/build-config.md index 28edc312..a73cf5db 100644 --- a/docs/guide/build-config.md +++ b/docs/guide/build-config.md @@ -7,6 +7,7 @@ Build and publish related options are configured in `.csproj` file via MSBuild p | BootsharpName | bootsharp | Name of the generated JavaScript module. | | BootsharpEmbedBinaries | true | Whether to embed binaries to the JavaScript module file. | | BootsharpAggressiveTrimming | false | Whether to disable some .NET features to reduce binary size. | +| BootsharpLLVM | false | Enable experimental [NativeAOT-LLVM](/guide/llvm) backend. | | BootsharpBundleCommand | npx rollup | The command to bundle generated JavaScrip solution. | | BootsharpPublishDirectory | /bin | Directory to publish generated JavaScript module. | | BootsharpTypesDirectory | /types | Directory to publish type declarations. | From f41be8299c085f762de823d8d063de5247f6dfb2 Mon Sep 17 00:00:00 2001 From: Artyom Sovetnikov <2056864+Elringus@users.noreply.github.com> Date: Mon, 31 Mar 2025 01:34:31 +0300 Subject: [PATCH 7/7] formatting --- samples/trimming/cs/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/trimming/cs/Program.cs b/samples/trimming/cs/Program.cs index d7e02f6a..ce33b79a 100644 --- a/samples/trimming/cs/Program.cs +++ b/samples/trimming/cs/Program.cs @@ -6,7 +6,7 @@ public static void Main () { Log("Hello from .NET!"); } - + [JSFunction] public static partial void Log (string message); }