From 8fdba2b30cf3d3a859d5f777c143a3863900e12b Mon Sep 17 00:00:00 2001
From: Meri Khamoyan <96171496+mkhamoyan@users.noreply.github.com>
Date: Fri, 7 Jun 2024 14:02:49 +0200
Subject: [PATCH] [wasm][wasi] Allow to build wasi in library mode (#102806)
Allow to build wasi app in library mode
---
.../scenarios/BuildWasiAppsJobsList.txt | 1 +
src/mono/browser/runtime/runtime.h | 1 +
.../Sdk/Sdk.props | 2 +-
.../Microsoft.NET.Sdk.WebAssembly.Pack.props | 1 -
...Microsoft.NET.Sdk.WebAssembly.Pack.targets | 12 +++-
src/mono/nuget/mono-packages.proj | 1 +
.../Wasi.Build.Tests/WasiLibraryModeTests.cs | 70 +++++++++++++++++++
src/mono/wasi/build/WasiApp.targets | 1 +
src/mono/wasi/runtime/main.c | 48 ++++++++++++-
src/mono/wasi/wasi.proj | 3 +-
src/mono/wasm/build/WasmApp.Common.targets | 12 ++--
.../ManagedToNativeGenerator.cs | 4 +-
.../WasmAppBuilder/PInvokeTableGenerator.cs | 9 ++-
13 files changed, 151 insertions(+), 14 deletions(-)
create mode 100644 src/mono/wasi/Wasi.Build.Tests/WasiLibraryModeTests.cs
diff --git a/eng/testing/scenarios/BuildWasiAppsJobsList.txt b/eng/testing/scenarios/BuildWasiAppsJobsList.txt
index bdb9ecf6e5f07..86c0517585a48 100644
--- a/eng/testing/scenarios/BuildWasiAppsJobsList.txt
+++ b/eng/testing/scenarios/BuildWasiAppsJobsList.txt
@@ -4,3 +4,4 @@ Wasi.Build.Tests.SdkMissingTests
Wasi.Build.Tests.RuntimeConfigTests
Wasi.Build.Tests.WasiTemplateTests
Wasi.Build.Tests.PInvokeTableGeneratorTests
+Wasi.Build.Tests.WasiLibraryModeTests
diff --git a/src/mono/browser/runtime/runtime.h b/src/mono/browser/runtime/runtime.h
index 0ad4d2abd451a..380c1fcd15b5f 100644
--- a/src/mono/browser/runtime/runtime.h
+++ b/src/mono/browser/runtime/runtime.h
@@ -20,5 +20,6 @@ MonoAssembly *mono_wasm_assembly_load (const char *name);
MonoClass *mono_wasm_assembly_find_class (MonoAssembly *assembly, const char *namespace, const char *name);
MonoMethod *mono_wasm_assembly_find_method (MonoClass *klass, const char *name, int arguments);
void mono_wasm_marshal_get_managed_wrapper (const char* assemblyName, const char* typeName, const char* methodName, int num_params);
+int initialize_runtime ();
#endif
diff --git a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.props b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.props
index f6a1d14ac17c2..c8836d71c79c0 100644
--- a/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.props
+++ b/src/mono/nuget/Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk/Sdk/Sdk.props
@@ -3,7 +3,7 @@
wasm
wasi
true
- Exe
+ Exe
true
diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Pack.props b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Pack.props
index a41609b5c15a6..16a5b064c380b 100644
--- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Pack.props
+++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Pack.props
@@ -15,6 +15,5 @@ Copyright (c) .NET Foundation. All rights reserved.
<_WebAssemblyPropsFile>$(MSBuildThisFileDirectory)\Microsoft.NET.Sdk.WebAssembly.Browser.props
- <_WebAssemblyTargetsFile>$(MSBuildThisFileDirectory)\Microsoft.NET.Sdk.WebAssembly.Browser.targets
diff --git a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Pack.targets b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Pack.targets
index df15f880ba1b3..9780bd3ce9648 100644
--- a/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Pack.targets
+++ b/src/mono/nuget/Microsoft.NET.Sdk.WebAssembly.Pack/build/Microsoft.NET.Sdk.WebAssembly.Pack.targets
@@ -9,4 +9,14 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
-
+
+
+ <_WebAssemblyTargetsFile>$(MSBuildThisFileDirectory)\Microsoft.NET.Sdk.WebAssembly.Browser.targets
+
+
+
+
+ true
+ true
+
+
diff --git a/src/mono/nuget/mono-packages.proj b/src/mono/nuget/mono-packages.proj
index 10add92c17efc..69f52d4234017 100644
--- a/src/mono/nuget/mono-packages.proj
+++ b/src/mono/nuget/mono-packages.proj
@@ -16,6 +16,7 @@
+
diff --git a/src/mono/wasi/Wasi.Build.Tests/WasiLibraryModeTests.cs b/src/mono/wasi/Wasi.Build.Tests/WasiLibraryModeTests.cs
new file mode 100644
index 0000000000000..2c191e745dac2
--- /dev/null
+++ b/src/mono/wasi/Wasi.Build.Tests/WasiLibraryModeTests.cs
@@ -0,0 +1,70 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.IO;
+using Xunit;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+using Wasm.Build.Tests;
+
+#nullable enable
+
+namespace Wasi.Build.Tests;
+
+public class WasiLibraryModeTests : BuildTestBase
+{
+ public WasiLibraryModeTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
+ : base(output, buildContext)
+ {
+ }
+
+ [Fact]
+ public void ConsoleBuildLibraryMode()
+ {
+ string config = "Release";
+ string id = $"{config}_{GetRandomId()}";
+ string projectFile = CreateWasmTemplateProject(id, "wasiconsole");
+ string code =
+ """
+ using System;
+ using System.Runtime.InteropServices;
+ public unsafe class Test
+ {
+ [UnmanagedCallersOnly(EntryPoint = "MyCallback")]
+ public static int MyCallback()
+ {
+ Console.WriteLine("WASM Library MyCallback is called");
+ return 100;
+ }
+ }
+ """;
+ string csprojCode =
+ """
+
+
+ net9.0
+ wasi-wasm
+ true
+ Library
+ true
+
+
+ """;
+ File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), code);
+ File.WriteAllText(Path.Combine(_projectDir!, $"{id}.csproj"), csprojCode);
+ string projectName = Path.GetFileNameWithoutExtension(projectFile);
+ var buildArgs = new BuildArgs(projectName, config, AOT: false, ProjectFileContents: id, ExtraBuildArgs: null);
+ buildArgs = ExpandBuildArgs(buildArgs);
+ (_, string output) = BuildProject(buildArgs,
+ id: id,
+ new BuildProjectOptions(
+ DotnetWasmFromRuntimePack: false,
+ CreateProject: false,
+ Publish: false,
+ TargetFramework: BuildTestBase.DefaultTargetFramework
+ ));
+
+ Assert.Contains("Build succeeded.", output);
+ }
+}
diff --git a/src/mono/wasi/build/WasiApp.targets b/src/mono/wasi/build/WasiApp.targets
index 27a3632bb3a60..c2e3ae6f11962 100644
--- a/src/mono/wasi/build/WasiApp.targets
+++ b/src/mono/wasi/build/WasiApp.targets
@@ -201,6 +201,7 @@
<_WasmCommonCFlags Condition="'$(InvariantGlobalization)' == 'true'" Include="-DINVARIANT_GLOBALIZATION=1" />
<_WasmCommonCFlags Condition="'$(InvariantTimezone)' == 'true'" Include="-DINVARIANT_TIMEZONE=1" />
<_WasmCommonCFlags Condition="'$(WasmLinkIcalls)' == 'true'" Include="-DLINK_ICALLS=1" />
+ <_WasmCommonCFlags Condition="'$(_IsLibraryMode)' == 'true'" Include="-DWASM_LIBRARY_MODE=1" />
<_WasiClangCFlags Include="@(_WasmCommonCFlags)" />
<_WasiClangCFlags Include=""-I%(_WasmCommonIncludePaths.Identity)"" />
diff --git a/src/mono/wasi/runtime/main.c b/src/mono/wasi/runtime/main.c
index 80d1a1317edf8..4e68d69368073 100644
--- a/src/mono/wasi/runtime/main.c
+++ b/src/mono/wasi/runtime/main.c
@@ -1,4 +1,5 @@
-#include
+#include
+#include
#include
#include
@@ -10,7 +11,40 @@ const char* dotnet_wasi_getentrypointassemblyname();
WASI_AFTER_RUNTIME_LOADED_DECLARATIONS
#endif
-int main(int argc, char * argv[]) {
+#ifdef WASM_LIBRARY_MODE
+// _initialize is a function generated by the WASI SDK libc that calls the LLVM synthesized __wasm_call_ctors function for reactor components:
+// https://github.com/WebAssembly/wasi-libc/blob/9f51a7102085ec6a6ced5778f0864c9af9f50000/libc-bottom-half/crt/crt1-reactor.c#L7-L27
+// We define and call it for WASM_LIBRARY_MODE and TARGET_WASI to call all the global c++ static constructors. This ensures the runtime is initialized
+// when calling into WebAssembly Component Model components.
+extern void _initialize();
+
+// CustomNativeMain programs are built using the same libbootstrapperdll as WASM_LIBRARY_MODE but wasi-libc will not provide an _initialize implementation,
+// so create a dummy one here and make it weak to allow wasi-libc to provide the real implementation for WASI reactor components.
+__attribute__((weak)) void _initialize()
+{
+}
+
+// Guard the "_initialize" call so that well-behaving hosts do not get affected by this workaround.
+static bool g_CalledInitialize = false;
+
+__attribute__((constructor))
+void WasiInitializationFlag() {
+ *(volatile bool*)&g_CalledInitialize = true;
+}
+
+static bool runtime_initialized = false;
+
+#endif
+
+int initialize_runtime()
+{
+#if defined(WASM_LIBRARY_MODE)
+ if (runtime_initialized)
+ return 0;
+ if (!g_CalledInitialize)
+ _initialize();
+ runtime_initialized = true;
+#endif
#ifndef WASM_SINGLE_FILE
mono_set_assemblies_path("managed");
@@ -21,7 +55,14 @@ int main(int argc, char * argv[]) {
// This is supplied from the MSBuild itemgroup @(WasiAfterRuntimeLoaded)
WASI_AFTER_RUNTIME_LOADED_CALLS
#endif
-
+ return 0;
+}
+
+#ifndef WASM_LIBRARY_MODE
+int main(int argc, char * argv[]) {
+ int initval = initialize_runtime();
+ if (initval != 0)
+ return initval;
int arg_ofs = 0;
#ifdef WASM_SINGLE_FILE
/*
@@ -71,3 +112,4 @@ int main(int argc, char * argv[]) {
}
return ret < 0 ? -ret : ret;
}
+#endif
diff --git a/src/mono/wasi/wasi.proj b/src/mono/wasi/wasi.proj
index 59d2137cf9723..120607ce7e9fc 100644
--- a/src/mono/wasi/wasi.proj
+++ b/src/mono/wasi/wasi.proj
@@ -60,7 +60,8 @@
Assemblies="@(WasmPInvokeAssembly)"
PInvokeModules="@(WasmPInvokeModule)"
PInvokeOutputPath="$(WasmPInvokeTablePath)"
- InterpToNativeOutputPath="$(WasmInterpToNativeTablePath)">
+ InterpToNativeOutputPath="$(WasmInterpToNativeTablePath)"
+ IsLibraryMode="$(_IsLibraryMode)">
diff --git a/src/mono/wasm/build/WasmApp.Common.targets b/src/mono/wasm/build/WasmApp.Common.targets
index 98ef4fcd61bdc..7f9e8230815ab 100644
--- a/src/mono/wasm/build/WasmApp.Common.targets
+++ b/src/mono/wasm/build/WasmApp.Common.targets
@@ -162,8 +162,9 @@
true
- true
- true
+ <_IsLibraryMode Condition="'$(OutputType)' == 'Library' and '$(UsingMicrosoftNETSdkWebAssembly)' == 'true'">true
+ true
+ true
true
Build
@@ -173,7 +174,7 @@
true
true
- true
+ true
false