Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/testing/scenarios/BuildWasiAppsJobsList.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ Wasi.Build.Tests.SdkMissingTests
Wasi.Build.Tests.RuntimeConfigTests
Wasi.Build.Tests.WasiTemplateTests
Wasi.Build.Tests.PInvokeTableGeneratorTests
Wasi.Build.Tests.WasiLibraryModeTests
1 change: 1 addition & 0 deletions src/mono/browser/runtime/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<TargetArchitecture>wasm</TargetArchitecture>
<TargetOS>wasi</TargetOS>
<UseMonoRuntime>true</UseMonoRuntime>
<OutputType>Exe</OutputType>
<OutputType Condition="'$(OutputType)' == ''">Exe</OutputType>
<PublishTrimmed Condition="'$(PublishTrimmed)' == ''">true</PublishTrimmed>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,5 @@ Copyright (c) .NET Foundation. All rights reserved.
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<_WebAssemblyPropsFile>$(MSBuildThisFileDirectory)\Microsoft.NET.Sdk.WebAssembly.Browser.props</_WebAssemblyPropsFile>
<_WebAssemblyTargetsFile>$(MSBuildThisFileDirectory)\Microsoft.NET.Sdk.WebAssembly.Browser.targets</_WebAssemblyTargetsFile>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,14 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
<Project ToolsVersion="14.0"></Project>
<Project ToolsVersion="14.0">
<PropertyGroup Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">
<_WebAssemblyTargetsFile>$(MSBuildThisFileDirectory)\Microsoft.NET.Sdk.WebAssembly.Browser.targets</_WebAssemblyTargetsFile>
</PropertyGroup>

<!-- Library Mode defaults-->
<PropertyGroup Condition="'$(OutputType)' == 'Library'">
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>
</Project>
1 change: 1 addition & 0 deletions src/mono/nuget/mono-packages.proj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<ItemGroup Condition="'$(TargetsWasi)' == 'true'">
<ProjectReference Include="Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk\Microsoft.NET.Runtime.WebAssembly.Wasi.Sdk.pkgproj" />
<ProjectReference Include="..\wasm\templates\Microsoft.NET.Runtime.WebAssembly.Templates.csproj" />
<ProjectReference Include="Microsoft.NET.Sdk.WebAssembly.Pack\Microsoft.NET.Sdk.WebAssembly.Pack.pkgproj" />
</ItemGroup>

<ItemGroup Condition="'$(TargetsiOS)' == 'true' or '$(TargetsiOSSimulator)' == 'true'">
Expand Down
6 changes: 3 additions & 3 deletions src/mono/sample/wasi/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
TODOWASI - - tcplisten localhost:64000 - - env DEBUGGER_FD=4
-->
<Exec WorkingDirectory="bin/wasi-wasm/AppBundle"
Condition="'$(WasmBuildNative)' != 'true'"
Condition="'$(WasmBuildNative)' != 'true' and '$(OutputType)' != 'Library'"
Command="$(WasmtimeDir)wasmtime$(_ExeExt) $(MONO_LOG_LEVEL) --dir . dotnet.wasm $(_SampleProjectName)" IgnoreExitCode="true" />
<Exec WorkingDirectory="bin/wasi-wasm/AppBundle"
Condition="'$(WasmBuildNative)' == 'true' and '$(WasmSingleFileBundle)' != 'true'"
Condition="'$(WasmBuildNative)' == 'true' and '$(WasmSingleFileBundle)' != 'true' and '$(OutputType)' != 'Library'"
Command="$(WasmtimeDir)wasmtime$(_ExeExt) $(MONO_LOG_LEVEL) --dir . dotnet.wasm" IgnoreExitCode="true" />
<Exec WorkingDirectory="bin/wasi-wasm/AppBundle"
Condition="'$(WasmSingleFileBundle)' == 'true'"
Condition="'$(WasmSingleFileBundle)' == 'true' and '$(OutputType)' != 'Library'"
Command="$(WasmtimeDir)wasmtime$(_ExeExt) $(MONO_LOG_LEVEL) $([System.IO.Path]::ChangeExtension($(_SampleAssembly), '.wasm'))" IgnoreExitCode="true" />
</Target>

Expand Down
74 changes: 74 additions & 0 deletions src/mono/wasi/Wasi.Build.Tests/WasiLibraryModeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// 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 =
"""
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
<TargetOs>wasi</TargetOs>
<WasmBuildNative>true</WasmBuildNative>
<WasmNativeStrip>false</WasmNativeStrip>
<IsBrowserWasmProject>false</IsBrowserWasmProject>
<WasmSingleFileBundle>true</WasmSingleFileBundle>
<OutputType>Library</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
</Project>
""";
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);
}
}
1 change: 1 addition & 0 deletions src/mono/wasi/build/WasiApp.targets
Original file line number Diff line number Diff line change
Expand Up @@ -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="&quot;-I%(_WasmCommonIncludePaths.Identity)&quot;" />
Expand Down
39 changes: 36 additions & 3 deletions src/mono/wasi/runtime/main.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <string.h>
#include <stdbool.h>
#include <string.h>
#include <driver.h>
#include <mono/metadata/assembly.h>

Expand All @@ -10,7 +11,31 @@ 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()
{
}

static bool runtime_initialized = false;

#endif

int initialize_runtime()
{
#if defined(WASM_LIBRARY_MODE)
if (runtime_initialized)
return 0;
_initialize();
runtime_initialized = true;
#endif

#ifndef WASM_SINGLE_FILE
mono_set_assemblies_path("managed");
Expand All @@ -21,7 +46,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
/*
Expand Down Expand Up @@ -71,3 +103,4 @@ int main(int argc, char * argv[]) {
}
return ret < 0 ? -ret : ret;
}
#endif
3 changes: 2 additions & 1 deletion src/mono/wasi/wasi.proj
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
Assemblies="@(WasmPInvokeAssembly)"
PInvokeModules="@(WasmPInvokeModule)"
PInvokeOutputPath="$(WasmPInvokeTablePath)"
InterpToNativeOutputPath="$(WasmInterpToNativeTablePath)">
InterpToNativeOutputPath="$(WasmInterpToNativeTablePath)"
IsLibraryMode="$(IsLibraryMode)">
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
</ManagedToNativeGenerator>
</Target>
Expand Down
11 changes: 7 additions & 4 deletions src/mono/wasm/build/WasmApp.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,9 @@
</WasmLinkDotNetDependsOn>

<WasmDedup Condition="'$(WasmDedup)' == ''">true</WasmDedup>
<IsWasiProject Condition="'$(IsWasiProject)' == '' and '$(RuntimeIdentifier)' == 'wasi-wasm' and '$(OutputType)' != 'Library'">true</IsWasiProject>
<IsBrowserWasmProject Condition="'$(IsBrowserWasmProject)' == '' and '$(RuntimeIdentifier)' == 'browser-wasm' and '$(OutputType)' != 'Library'">true</IsBrowserWasmProject>
<IsLibraryMode Condition="'$(OutputType)' == 'Library' and '$(UsingMicrosoftNETSdkWebAssembly)' == 'true'">true</IsLibraryMode>
<IsWasiProject Condition="'$(IsWasiProject)' == '' and '$(RuntimeIdentifier)' == 'wasi-wasm'">true</IsWasiProject>
<IsBrowserWasmProject Condition="'$(IsBrowserWasmProject)' == '' and '$(RuntimeIdentifier)' == 'browser-wasm'">true</IsBrowserWasmProject>
<IsWasmProject Condition="'$(IsWasmProject)' == '' and ('$(IsWasiProject)' == 'true' or '$(IsBrowserWasmProject)' == 'true')">true</IsWasmProject>
<WasmBuildAppAfterThisTarget Condition="'$(WasmBuildAppAfterThisTarget)' == '' and '$(DisableAutoWasmBuildApp)' != 'true'">Build</WasmBuildAppAfterThisTarget>

Expand All @@ -174,6 +175,7 @@
<!-- VS uses DeployOnBuild, and sdk sets _IsPublishing -->
<WasmBuildOnlyAfterPublish Condition="'$(WasmBuildOnlyAfterPublish)' == '' and ('$(DeployOnBuild)' == 'true' or '$(_IsPublishing)' == 'true')">true</WasmBuildOnlyAfterPublish>
<WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == '' and '$(OutputType)' != 'Library'">true</WasmGenerateAppBundle>
<WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == '' and '$(IsLibraryMode)' == 'true'">true</WasmGenerateAppBundle>
<WasmGenerateAppBundle Condition="'$(WasmGenerateAppBundle)' == ''">false</WasmGenerateAppBundle>

<!-- FIXME: can't set to true because
Expand Down Expand Up @@ -346,7 +348,7 @@

<Target Name="_WasmGetRuntimeConfigPath">
<PropertyGroup>
<_MainAssemblyPath Condition="'%(WasmAssembliesToBundle.FileName)' == $(AssemblyName) and '%(WasmAssembliesToBundle.Extension)' == '.dll' and $(WasmGenerateAppBundle) == 'true'">%(WasmAssembliesToBundle.Identity)</_MainAssemblyPath>
<_MainAssemblyPath Condition="'%(WasmAssembliesToBundle.FileName)' == '$(AssemblyName)' and '%(WasmAssembliesToBundle.Extension)' == '.dll' and $(WasmGenerateAppBundle) == 'true'">%(WasmAssembliesToBundle.Identity)</_MainAssemblyPath>
<_WasmRuntimeConfigFilePath Condition="'$(_WasmRuntimeConfigFilePath)' == '' and $(_MainAssemblyPath) != ''">$([System.IO.Path]::ChangeExtension($(_MainAssemblyPath), '.runtimeconfig.json'))</_WasmRuntimeConfigFilePath>
<_ParsedRuntimeConfigFilePath Condition="'$(_WasmRuntimeConfigFilePath)' != ''">$([System.IO.Path]::GetDirectoryName($(_WasmRuntimeConfigFilePath)))\runtimeconfig.bin</_ParsedRuntimeConfigFilePath>
</PropertyGroup>
Expand Down Expand Up @@ -799,7 +801,8 @@
PInvokeModules="@(_WasmPInvokeModules)"
PInvokeOutputPath="$(_WasmPInvokeTablePath)"
InterpToNativeOutputPath="$(_WasmInterpToNativeTablePath)"
CacheFilePath="$(_WasmM2NCachePath)">
CacheFilePath="$(_WasmM2NCachePath)"
IsLibraryMode="$(IsLibraryMode)">
<Output TaskParameter="FileWrites" ItemName="FileWrites" />
</ManagedToNativeGenerator>
</Target>
Expand Down
4 changes: 3 additions & 1 deletion src/tasks/WasmAppBuilder/ManagedToNativeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public class ManagedToNativeGenerator : Task
public string? InterpToNativeOutputPath { get; set; }
public string? CacheFilePath { get; set; }

public bool IsLibraryMode { get; set; }

[Output]
public string[]? FileWrites { get; private set; }

Expand Down Expand Up @@ -69,7 +71,7 @@ private void ExecuteInternal(LogAdapter log)
List<string> managedAssemblies = FilterOutUnmanagedBinaries(Assemblies);
if (ShouldRun(managedAssemblies))
{
var pinvoke = new PInvokeTableGenerator(FixupSymbolName, log);
var pinvoke = new PInvokeTableGenerator(FixupSymbolName, log, IsLibraryMode);
var icall = new IcallTableGenerator(RuntimeIcallTableFile, FixupSymbolName, log);

var resolver = new PathAssemblyResolver(managedAssemblies);
Expand Down
9 changes: 8 additions & 1 deletion src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ internal sealed class PInvokeTableGenerator
private readonly List<PInvoke> pinvokes = new();
private readonly List<PInvokeCallback> callbacks = new();
private readonly PInvokeCollector _pinvokeCollector;
private readonly bool _isLibraryMode;

public PInvokeTableGenerator(Func<string, string> fixupSymbolName, LogAdapter log)
public PInvokeTableGenerator(Func<string, string> fixupSymbolName, LogAdapter log, bool isLibraryMode = false)
{
Log = log;
_fixupSymbolName = fixupSymbolName;
_pinvokeCollector = new(log);
_isLibraryMode = isLibraryMode;
}

public void ScanAssembly(Assembly asm)
Expand Down Expand Up @@ -379,6 +381,11 @@ private void EmitNativeToInterp(StreamWriter w, List<PInvokeCallback> callbacks)
if (!is_void)
sb.Append($" {MapType(method.ReturnType)} res;\n");

if (_isLibraryMode && HasAttribute(method, "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute"))
{
sb.Append($" initialize_runtime(); \n");
}

// In case when null force interpreter to initialize the pointers
sb.Append($" if (!(WasmInterpEntrySig_{cb_index})wasm_native_to_interp_ftndescs [{cb_index}].func) {{\n");
var assemblyFullName = cb.Method.DeclaringType == null ? "" : cb.Method.DeclaringType.Assembly.FullName;
Expand Down