Skip to content

Commit

Permalink
Build WASM runtimes (#35566)
Browse files Browse the repository at this point in the history
  • Loading branch information
vargaz committed May 12, 2020
1 parent 5f884ba commit 7f042fc
Show file tree
Hide file tree
Showing 15 changed files with 3,665 additions and 2 deletions.
2 changes: 2 additions & 0 deletions eng/liveBuilds.targets
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@
Include="$(MonoArtifactsPath)\cross\*.*" />
<MonoIncludeFiles Condition="'$(TargetsMobile)' == 'true' or '$(TargetOS)' == 'Browser'"
Include="$(MonoArtifactsPath)\include\**\*.*" />
<WasmDistFiles Condition="'$(TargetOS)' == 'Browser'"
Include="$(MonoArtifactsPath)\wasm\runtimes\**\*.*" />
</ItemGroup>

<Error Condition="'@(RuntimeFiles)' == ''" Text="The '$(RuntimeFlavor)' subset must be built before building this project." />
Expand Down
7 changes: 6 additions & 1 deletion src/installer/pkg/projects/netcoreapp/src/netcoreapp.depproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,16 @@
Include="@(MonoCrossFiles)">
<TargetPath>runtimes/$(PackageRID)/native/cross</TargetPath>
</RuntimeFiles>
<RuntimeFiles Condition="'$(TargetsMobile)' == 'true'"
<RuntimeFiles Condition="'$(TargetsMobile)' == 'true' or '$(TargetsBrowser)' == 'true'"
Include="@(MonoIncludeFiles)">
<TargetPath>runtimes/$(PackageRID)/native/include/%(RecursiveDir)</TargetPath>
</RuntimeFiles>

<RuntimeFiles Condition="'$(TargetsBrowser)' == 'true'"
Include="@(WasmDistFiles)">
<TargetPath>runtimes/$(PackageRID)/native/wasm/runtimes/%(RecursiveDir)</TargetPath>
</RuntimeFiles>

<CoreCLRCrossTargetFiles Condition="'%(FileName)' == 'clrjit' or '%(FileName)' == 'libclrjit'">
<TargetPath>runtimes/$(CoreCLRCrossTargetComponentDirName)_$(TargetArchitecture)/native</TargetPath>
</CoreCLRCrossTargetFiles>
Expand Down
8 changes: 8 additions & 0 deletions src/libraries/pretest.proj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@
TargetRuntimeIdentifier="$(PackageRID)" />
</Target>

<!-- Wasm runtimes depend on the mono runtime and libs to be built, so they can only be built here -->
<Target Name="BuildWasmPackageDependency"
Condition="'$(TargetOS)' == 'Browser'"
AfterTargets="RestoreTestHost">
<MSBuild Projects="$(MonoProjectRoot)\wasm\wasm.proj"
Properties="Configuration=$(Configuration);TargetOS=$(TargetOS);TargetArchitecture=$(TargetArchitecture)"/>
</Target>

<UsingTask TaskName="CreateFrameworkListFile" AssemblyFile="$(DotNetBuildTasksSharedFrameworkTaskFile)"/>
<Target Name="GenerateFrameworkListFile"
Condition="'$(BinPlaceTestRuntimePack)' == 'true'"
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/restore/runtime/runtime.depproj
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@
<NativeBinPlaceItem Include="@(MonoIncludeFiles)">
<DestinationSubDirectory>include/%(RecursiveDir)</DestinationSubDirectory>
</NativeBinPlaceItem>
<NativeBinPlaceItem Include="@(WasmDistFiles)">
<DestinationSubDirectory>wasm/%(RecursiveDir)</DestinationSubDirectory>
</NativeBinPlaceItem>
</ItemGroup>
<ItemGroup Condition="'$(TargetsMobile)' != 'true'">
<TestHostBinPlaceItem Include="@(RuntimeFiles)" />
Expand Down
21 changes: 20 additions & 1 deletion src/mono/mono.proj
Original file line number Diff line number Diff line change
Expand Up @@ -944,9 +944,16 @@
Targets="Restore;Build" />
</Target>

<!-- Need to publish this since it references System.Reflection.MetadataLoadContext -->
<Target Name="BuildWasmAppBuilder">
<MSBuild Projects="$(MonoProjectRoot)msbuild\WasmAppBuilder\WasmAppBuilder.csproj"
Properties="Configuration=$(Configuration);"
Targets="Restore;Build;Publish"/>
</Target>

<!-- Ordering matters! Overwriting the Build target. -->
<!-- General targets -->
<Target Name="Build" DependsOnTargets="BuildMonoRuntimeUnix;BuildMonoRuntimeWindows;BuildAppleAppBuilder;BuildAndroidAppBuilder">
<Target Name="Build" DependsOnTargets="BuildMonoRuntimeUnix;BuildMonoRuntimeWindows;BuildAppleAppBuilder;BuildAndroidAppBuilder;BuildWasmAppBuilder">
<PropertyGroup>
<_MonoRuntimeFilePath Condition="'$(TargetsWindows)' == 'true' and '$(Platform)' == 'x64'">$(MonoObjDir)x64\Bin\$(Configuration)\mono-2.0-sgen.dll</_MonoRuntimeFilePath>
<_MonoRuntimeFilePath Condition="'$(TargetsWindows)' == 'true' and '$(Platform)' == 'x86'">$(MonoObjDir)Win32\Bin\$(Configuration)\mono-2.0-sgen.dll</_MonoRuntimeFilePath>
Expand Down Expand Up @@ -981,6 +988,18 @@
<Destination>$(BinDir)cross\opt</Destination>
</_MonoRuntimeArtifacts>
<_MonoIncludeArtifacts Include="$(MonoObjDir)out\include\**" />
<_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-ee-interp.a">
<Destination>$(BinDir)libmono-ee-interp.a</Destination>
</_MonoRuntimeArtifacts>
<_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-icall-table.a">
<Destination>$(BinDir)libmono-icall-table.a</Destination>
</_MonoRuntimeArtifacts>
<_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-ilgen.a">
<Destination>$(BinDir)libmono-ilgen.a</Destination>
</_MonoRuntimeArtifacts>
<_MonoRuntimeArtifacts Condition="'$(TargetsBrowser)' == 'true'" Include="$(MonoObjDir)out\lib\libmono-profiler-aot.a">
<Destination>$(BinDir)libmono-profiler-aot.a</Destination>
</_MonoRuntimeArtifacts>
</ItemGroup>

<Copy SourceFiles="@(_MonoRuntimeArtifacts)"
Expand Down
146 changes: 146 additions & 0 deletions src/mono/msbuild/WasmAppBuilder/PInvokeTableGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// -*- indent-tabs-mode: nil -*-
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Reflection;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

public class PInvokeTableGenerator : Task
{
[Required]
public ITaskItem[]? Modules { get; set; }
[Required]
public ITaskItem[]? Assemblies { get; set; }
[Required]
public string? OutputPath { get; set; }

public override bool Execute () {
GenPInvokeTable (Modules!.Select (item => item.ItemSpec).ToArray (), Assemblies!.Select (item => item.ItemSpec).ToArray ());
return true;
}

void GenPInvokeTable (string[] pinvokeModules, string[] assemblies) {
var modules = new Dictionary<string, string> ();
foreach (var module in pinvokeModules)
modules [module] = module;

var pinvokes = new List<PInvoke> ();

var resolver = new PathAssemblyResolver (assemblies);
var mlc = new MetadataLoadContext (resolver, "System.Private.CoreLib");
foreach (var aname in assemblies) {
var a = mlc.LoadFromAssemblyPath (aname);
foreach (var type in a.GetTypes ())
CollectPInvokes (pinvokes, type);
}

Log.LogMessage (MessageImportance.Normal, $"Generating pinvoke table to '{OutputPath}'.");

using (var w = File.CreateText (OutputPath!)) {
EmitPInvokeTable (w, modules, pinvokes);
}
}

void CollectPInvokes (List<PInvoke> pinvokes, Type type) {
foreach (var method in type.GetMethods (BindingFlags.DeclaredOnly|BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance)) {
if ((method.Attributes & MethodAttributes.PinvokeImpl) == 0)
continue;
var dllimport = method.CustomAttributes.First (attr => attr.AttributeType.Name == "DllImportAttribute");
var module = (string)dllimport.ConstructorArguments [0].Value!;
var entrypoint = (string)dllimport.NamedArguments.First (arg => arg.MemberName == "EntryPoint").TypedValue.Value!;
pinvokes.Add (new PInvoke (entrypoint, module, method));
}
}

void EmitPInvokeTable (StreamWriter w, Dictionary<string, string> modules, List<PInvoke> pinvokes) {
w.WriteLine ("// GENERATED FILE, DO NOT MODIFY");
w.WriteLine ("typedef struct {");
w.WriteLine ("const char *name;");
w.WriteLine ("void *func;");
w.WriteLine ("} PinvokeImport;");
w.WriteLine ();

foreach (var pinvoke in pinvokes) {
if (modules.ContainsKey (pinvoke.Module))
w.WriteLine (GenPInvokeDecl (pinvoke));
}

foreach (var module in modules.Keys) {
string symbol = module.Replace (".", "_") + "_imports";
w.WriteLine ("static PinvokeImport " + symbol + " [] = {");
foreach (var pinvoke in pinvokes) {
if (pinvoke.Module == module)
w.WriteLine ("{\"" + pinvoke.EntryPoint + "\", " + pinvoke.EntryPoint + "},");
}
w.WriteLine ("{NULL, NULL}");
w.WriteLine ("};");
}
w.Write ("static void *pinvoke_tables[] = { ");
foreach (var module in modules.Keys) {
string symbol = module.Replace (".", "_") + "_imports";
w.Write (symbol + ",");
}
w.WriteLine ("};");
w.Write ("static char *pinvoke_names[] = { ");
foreach (var module in modules.Keys) {
w.Write ("\"" + module + "\"" + ",");
}
w.WriteLine ("};");
}

string MapType (Type t) {
string name = t.Name;
if (name == "Void")
return "void";
else if (name == "Double")
return "double";
else if (name == "Single")
return "float";
else if (name == "Int64")
return "int64_t";
else if (name == "UInt64")
return "uint64_t";
else
return "int";
}

string GenPInvokeDecl (PInvoke pinvoke) {
var sb = new StringBuilder ();
var method = pinvoke.Method;
sb.Append (MapType (method.ReturnType));
sb.Append ($" {pinvoke.EntryPoint} (");
int pindex = 0;
var pars = method.GetParameters ();
foreach (var p in pars) {
if (pindex > 0)
sb.Append (",");
sb.Append (MapType (pars [pindex].ParameterType));
pindex ++;
}
sb.Append (");");
return sb.ToString ();
}
}

class PInvoke
{
public PInvoke (string entry_point, string module, MethodInfo method) {
EntryPoint = entry_point;
Module = module;
Method = method;
}

public string EntryPoint;
public string Module;
public MethodInfo Method;
}
16 changes: 16 additions & 0 deletions src/mono/msbuild/WasmAppBuilder/WasmAppBuilder.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="$(RefOnlyMicrosoftBuildVersion)" />
<PackageReference Include="Microsoft.Build.Framework" Version="$(RefOnlyMicrosoftBuildFrameworkVersion)" />
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="$(RefOnlyMicrosoftBuildTasksCoreVersion)" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="$(RefOnlyMicrosoftBuildUtilitiesCoreVersion)" />
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="4.7.1" />
</ItemGroup>
<ItemGroup>
<Compile Include="PInvokeTableGenerator.cs" />
</ItemGroup>
</Project>
100 changes: 100 additions & 0 deletions src/mono/wasm/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
TOP=$(realpath $(CURDIR)/../../..)
-include Make.config

#
# These variables are set by wasm.targets
#
EMSDK_PATH?=emsdk
CONFIG?=Release
BINDIR?=$(TOP)/artifacts/bin
OBJDIR?=$(TOP)/artifacts/obj
PINVOKE_TABLE?=$(TOP)/artifacts/obj/mono/Browser.wasm.$(CONFIG)/wasm/pinvoke-table.h
MONO_BIN_DIR?=$(BINDIR)/mono/Browser.wasm.$(CONFIG)
SYS_NATIVE_DIR?=$(BINDIR)/native/net5.0-Browser-$(CONFIG)-wasm/

all: build-native

#
# EMSCRIPTEN SETUP
#
# If EMSDK_PATH is not set by the caller, download and setup a local emsdk install.
#

EMSCRIPTEN_VERSION=1.39.11
EMSDK_LOCAL_PATH=emsdk
EMCC=source $(CURDIR)/emsdk_env.sh && emcc

.stamp-wasm-install-and-select-$(EMSCRIPTEN_VERSION):
rm -rf $(EMSDK_LOCAL_PATH)
git clone https://github.com/emscripten-core/emsdk.git $(EMSDK_LOCAL_PATH)
cd $(EMSDK_LOCAL_PATH) && git checkout $(EMSCRIPTEN_VERSION)
cd $(EMSDK_LOCAL_PATH) && ./emsdk install $(EMSCRIPTEN_VERSION)
cd $(EMSDK_LOCAL_PATH) && ./emsdk activate --embedded $(EMSCRIPTEN_VERSION)
touch $@

ifeq ($(EMSDK_PATH),emsdk)
provision-wasm: .stamp-wasm-install-and-select-$(EMSCRIPTEN_VERSION)
else
provision-wasm:
endif

# emsdk_env.sh calls emsdk construct_env which is a bit slow so make a copy
emsdk_env.sh: | provision-wasm
cd $(EMSDK_PATH) && ./emsdk construct_env $(CURDIR)/emsdk_env.sh

MONO_OBJ_DIR=$(OBJDIR)/mono/Browser.wasm.$(CONFIG)
MONO_LIBS = $(MONO_BIN_DIR)/{libmono-ee-interp.a,libmono.a,libmono-ilgen.a,libmono-icall-table.a} ${SYS_NATIVE_DIR}/libSystem.Native.a
MONO_INCLUDE_DIR=$(MONO_BIN_DIR)/include/mono-2.0
BUILDS_BIN_DIR=$(MONO_BIN_DIR)/wasm/runtimes
BUILDS_OBJ_DIR=$(MONO_OBJ_DIR)/wasm/runtimes

EMCC_FLAGS=--profiling-funcs -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s BINARYEN=1 -s ALIASING_FUNCTION_POINTERS=0 -s NO_EXIT_RUNTIME=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall', 'FS_createPath', 'FS_createDataFile', 'cwrap', 'setValue', 'getValue', 'UTF8ToString', 'addFunction']" -s "EXPORTED_FUNCTIONS=['_putchar']" --source-map-base http://example.com -s WASM_OBJECT_FILES=0 -s FORCE_FILESYSTEM=1 -s USE_ZLIB=1
EMCC_DEBUG_FLAGS =-g -Os -s ASSERTIONS=1 -DENABLE_NETCORE=1
EMCC_RELEASE_FLAGS=-Oz --llvm-opts 2 --llvm-lto 1 -DENABLE_NETCORE=1

#
# Interpreter builds
#

# $(1) - name
# $(2) - runtime dir
# $(3) - EMCC_FLAGS
# $(4) - libs
define InterpBuildTemplate

$(BUILDS_BIN_DIR)/$(1)/:
mkdir -p $$@

$(BUILDS_OBJ_DIR)/$(1)/:
mkdir -p $$@

$(BUILDS_BIN_DIR)/$(1)/dotnet.js: $(BUILDS_OBJ_DIR)/$(1)/driver.o $(BUILDS_OBJ_DIR)/$(1)/corebindings.o runtime/library_mono.js runtime/binding_support.js runtime/dotnet_support.js $(5) | $(BUILDS_BIN_DIR)/$(1)/ emsdk_env.sh
$(EMCC) $(EMCC_FLAGS) $(3) --js-library runtime/library_mono.js --js-library runtime/binding_support.js --js-library runtime/dotnet_support.js $(BUILDS_OBJ_DIR)/$(1)/driver.o $(BUILDS_OBJ_DIR)/$(1)/corebindings.o $(4) -o $(BUILDS_BIN_DIR)/$(1)/dotnet.js

$(BUILDS_OBJ_DIR)/$(1)/pinvoke-table.h: $(PINVOKE_TABLE) | $(BUILDS_OBJ_DIR)/$(1)/
if cmp -s $(PINVOKE_TABLE) $$@ ; then : ; else cp $(PINVOKE_TABLE) $$@ ; fi

$(BUILDS_OBJ_DIR)/$(1)/driver.o: runtime/driver.c runtime/corebindings.c $(BUILDS_OBJ_DIR)/$(1)/pinvoke-table.h $(5) | $(BUILDS_OBJ_DIR)/$(1)/ emsdk_env.sh
$(EMCC) $(EMCC_FLAGS) $(3) -Oz -DCORE_BINDINGS -I$(BUILDS_OBJ_DIR)/$(1) -I$(MONO_INCLUDE_DIR) runtime/driver.c -c -o $$@

$(BUILDS_OBJ_DIR)/$(1)/corebindings.o: runtime/corebindings.c | $(BUILDS_OBJ_DIR)/$(1)/ emsdk_env.sh
$(EMCC) $(3) -Oz -I$(MONO_INCLUDE_DIR) runtime/corebindings.c -c -o $$@

build-native: $(BUILDS_BIN_DIR)/$(1)/dotnet.js

build-interp-$(1): $(BUILDS_BIN_DIR)/$(1)/dotnet.js

endef

$(eval $(call InterpBuildTemplate,debug,,$(EMCC_DEBUG_FLAGS),$(MONO_LIBS)))
$(eval $(call InterpBuildTemplate,release,,$(EMCC_RELEASE_FLAGS),$(MONO_LIBS)))

build:
EMSDK_PATH=$(PWD)/wasm/emsdk ../../../.dotnet/dotnet build /p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=Release

clean-emsdk:
$(RM) -rf $(EMSDK_LOCAL_PATH)

clean:
$(RM) -rf $(BUILDS_BIN_DIR) $(BUILDS_OBJ_DIR)
$(RM) emsdk_env.sh
Loading

0 comments on commit 7f042fc

Please sign in to comment.