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