Skip to content

Commit

Permalink
[wasm] Build static components; include hot_reload in runtime (dotnet…
Browse files Browse the repository at this point in the history
…#54568)

* [wasm] Build static components; include hot_reload in runtime

   Workaround until dotnet#54565 is fixed
   Build the runtime always with support for hot_reload, and without diagnostics_tracing

* Update wasm.proj

* Add a browser functional test for hot reload

   Just check that the capabilities are non-empty which is a good proxy for hot reload being enabled in the runtime.

* Turn off trimming for hot reload functional test

* Disable test on browser AOT

* fix whitespace

Co-authored-by: Thays Grazia <thaystg@gmail.com>
  • Loading branch information
lambdageek and thaystg committed Jun 24, 2021
1 parent 4f4f0db commit 93f407d
Show file tree
Hide file tree
Showing 14 changed files with 318 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFrameworks>$(NetCoreAppCurrent)</TargetFrameworks>
<TestRuntime>true</TestRuntime>
<DeltaScript>deltascript.json</DeltaScript>
<SkipTestUtilitiesReference>true</SkipTestUtilitiesReference>
</PropertyGroup>
<ItemGroup>
<Compile Include="MethodBody1.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace System.Reflection.Metadata
public class ApplyUpdateTest
{
[Fact]
[ActiveIssue("https://github.com/dotnet/runtime/issues/54617", typeof(PlatformDetection), nameof(PlatformDetection.IsBrowser), nameof(PlatformDetection.IsMonoAOT))]
void StaticMethodBodyUpdate()
{
ApplyUpdateUtil.TestCase(static () =>
Expand Down
1 change: 0 additions & 1 deletion src/mono/mono.proj
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@
<_MonoCMakeArgs Include="-DENABLE_INTERP_LIB=1"/>
<_MonoCMakeArgs Include="-DDISABLE_ICALL_TABLES=1"/>
<_MonoCMakeArgs Include="-DDISABLE_CRASH_REPORTING=1"/>
<_MonoCMakeArgs Include="-DDISABLE_COMPONENTS=1"/>
<_MonoCMakeArgs Include="-DENABLE_ICALL_EXPORT=1"/>
<_MonoCMakeArgs Include="-DENABLE_LAZY_GC_THREAD_CREATION=1"/>
<_MonoCMakeArgs Include="-DENABLE_LLVM_RUNTIME=1"/>
Expand Down
8 changes: 8 additions & 0 deletions src/mono/wasm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,20 @@ provision-wasm: .stamp-wasm-install-and-select-$(EMSCRIPTEN_VERSION)
@echo "----------------------------------------------------------"
@echo "Installed emsdk into EMSDK_PATH=$(TOP)/src/mono/wasm/emsdk"

# FIXME: When https://github.com/dotnet/runtime/issues/54565 is fixed, and the WasmApp targets are updated to use mono runtime components, remove this
MONO_COMPONENT_LIBS= \
$(MONO_BIN_DIR)/libmono-component-hot_reload-static.a \
$(MONO_BIN_DIR)/libmono-component-diagnostics_tracing-stub-static.a

MONO_OBJ_DIR=$(OBJDIR)/mono/Browser.wasm.$(CONFIG)
MONO_INCLUDE_DIR=$(MONO_BIN_DIR)/include/mono-2.0
BUILDS_OBJ_DIR=$(MONO_OBJ_DIR)/wasm
# libmonosgen-2.0 is in MONO_LIBS twice because the components and the runtime are depend on each other
MONO_LIBS = \
$(MONO_BIN_DIR)/libmono-ee-interp.a \
$(MONO_BIN_DIR)/libmonosgen-2.0.a \
$(MONO_COMPONENT_LIBS) \
$(MONO_BIN_DIR)/libmonosgen-2.0.a \
$(MONO_BIN_DIR)/libmono-ilgen.a \
$(MONO_BIN_DIR)/libmono-icall-table.a \
$(MONO_BIN_DIR)/libmono-profiler-aot.a \
Expand Down
3 changes: 3 additions & 0 deletions src/mono/wasm/wasm.proj
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,16 @@
<ItemGroup>
<ICULibNativeFiles Include="$(ICULibDir)/libicuuc.a;
$(ICULibDir)/libicui18n.a" />
<MonoComponentLibs Include="$(MonoArtifactsPath)libmono-component-hot_reload-static.a;
$(MonoArtifactsPath)libmono-component-diagnostics_tracing-stub-static.a" />
<MonoLibFiles Include="$(MonoArtifactsPath)libmono-ee-interp.a;
$(MonoArtifactsPath)libmonosgen-2.0.a;
$(MonoArtifactsPath)libmono-ilgen.a;
$(MonoArtifactsPath)libmono-icall-table.a;
$(NativeBinDir)libSystem.Native.a;
$(NativeBinDir)libSystem.IO.Compression.Native.a" />
<MonoLibFiles Include="@(ICULibNativeFiles)" />
<MonoLibFiles Include="@(MonoComponentLibs)" />
<PInvokeTableFile Include="$(WasmObjDir)\pinvoke-table.h" />
<ICULibFiles Include="$(ICULibDir)/*.dat" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk" TreatAsLocalProperty="EnableAggressiveTrimming;PublishTrimmed">
<PropertyGroup>
<TestRuntime>true</TestRuntime>
<DeltaScript>deltascript.json</DeltaScript>
<OutputType>library</OutputType>
<IsTestProject>false</IsTestProject>
<IsTestSupportProject>true</IsTestSupportProject>
<!-- to call AsssemblyExtensions.ApplyUpdate we need Optimize=false, EmitDebugInformation=true in all configurations -->
<Optimize>false</Optimize>
<EmitDebugInformation>true</EmitDebugInformation>
<!-- hot reload is not compatible with trimming -->
<EnableAggressiveTrimming>false</EnableAggressiveTrimming>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>

<ItemGroup>
<Compile Include="MethodBody1.cs" />
</ItemGroup>

<ItemGroup>
<!-- This package from https://github.com/dotnet/hotreload-utils provides
targets that read the json delta script and generates deltas based on the baseline assembly and the modified sources.
Projects must define the DeltaScript property that specifies the (relative) path to the json script.
Deltas will be emitted next to the output assembly. Deltas will be copied when the current
project is referenced from other other projects.
-->
<PackageReference Include="Microsoft.DotNet.HotReload.Utils.Generator.BuildTool" Version="$(MicrosoftDotNetHotReloadUtilsGeneratorBuildToolVersion)" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace ApplyUpdateReferencedAssembly
{
public class MethodBody1 {
public static string StaticMethod1 () {
return "OLD STRING";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace ApplyUpdateReferencedAssembly
{
public class MethodBody1 {
public static string StaticMethod1 () {
return "NEW STRING";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace ApplyUpdateReferencedAssembly
{
public class MethodBody1 {
public static string StaticMethod1 () {
return "NEWEST STRING";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": [
{"document": "MethodBody1.cs", "update": "MethodBody1_v1.cs"},
{"document": "MethodBody1.cs", "update": "MethodBody1_v2.cs"},
]
}

81 changes: 81 additions & 0 deletions src/tests/FunctionalTests/WebAssembly/Browser/HotReload/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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.Reflection;
using System.Runtime.CompilerServices;

namespace Sample
{
public class Test
{
public static void Main(string[] args)
{
Console.WriteLine ("Hello, World!");
}

[MethodImpl(MethodImplOptions.NoInlining)]
public static int TestMeaning()
{
const int success = 42;
const int failure = 1;

var ty = typeof(System.Reflection.Metadata.AssemblyExtensions);
var mi = ty.GetMethod("GetApplyUpdateCapabilities", BindingFlags.NonPublic | BindingFlags.Static, Array.Empty<Type>());

if (mi == null)
return failure;

var caps = mi.Invoke(null, null) as string;

if (String.IsNullOrEmpty(caps))
return failure;

var assm = typeof (ApplyUpdateReferencedAssembly.MethodBody1).Assembly;

var r = ApplyUpdateReferencedAssembly.MethodBody1.StaticMethod1();
if ("OLD STRING" != r)
return failure;

ApplyUpdate(assm);

r = ApplyUpdateReferencedAssembly.MethodBody1.StaticMethod1();
if ("NEW STRING" != r)
return failure;

ApplyUpdate(assm);

r = ApplyUpdateReferencedAssembly.MethodBody1.StaticMethod1();
if ("NEWEST STRING" != r)
return failure;

return success;
}

private static System.Collections.Generic.Dictionary<Assembly, int> assembly_count = new();

internal static void ApplyUpdate (System.Reflection.Assembly assm)
{
int count;
if (!assembly_count.TryGetValue(assm, out count))
count = 1;
else
count++;
assembly_count [assm] = count;

/* FIXME WASM: Location is empty on wasm. Make up a name based on Name */
string basename = assm.Location;
if (basename == "")
basename = assm.GetName().Name + ".dll";
Console.Error.WriteLine($"Apply Delta Update for {basename}, revision {count}");

string dmeta_name = $"{basename}.{count}.dmeta";
string dil_name = $"{basename}.{count}.dil";
byte[] dmeta_data = System.IO.File.ReadAllBytes(dmeta_name);
byte[] dil_data = System.IO.File.ReadAllBytes(dil_name);
byte[] dpdb_data = null; // TODO also use the dpdb data

System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assm, dmeta_data, dil_data, dpdb_data);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<MonoForceInterpreter>true</MonoForceInterpreter>
<RunAOTCompilation>false</RunAOTCompilation>
<PublishTrimmed>false</PublishTrimmed>
<TestRuntime>true</TestRuntime>
<Scenario>WasmTestOnBrowser</Scenario>
<ExpectedExitCode>42</ExpectedExitCode>
<WasmMainJSPath>runtime.js</WasmMainJSPath>
<EnableDefaultItems>false</EnableDefaultItems>
<!-- setting WasmXHarnessMonoArgs doesn't work here, but see runtime.js -->
<!-- <WasmXHarnessMonoArgs>- -setenv=DOTNET_MODIFIABLE_ASSEMBLIES=debug</WasmXHarnessMonoArgs> -->
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Content Include="index.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<ProjectReference Include="ApplyUpdateReferencedAssembly\ApplyUpdateReferencedAssembly.csproj" />
</ItemGroup>

<Target Name="AfterWasmBuildApp" AfterTargets="WasmBuildApp">
<Copy SourceFiles="$(OutDir)\index.html" DestinationFolder="$(WasmAppDir)" />
</Target>

<Target Name="PreserveEnCAssembliesFromLinking"
Condition="'$(TargetOS)' == 'Browser' and '$(EnableAggressiveTrimming)' == 'true'"
BeforeTargets="ConfigureTrimming">
<ItemGroup>
<!-- Don't modify EnC test assemblies -->
<TrimmerRootAssembly
Condition="$([System.String]::Copy('%(ResolvedFileToPublish.FileName)%(ResolvedFileToPublish.Extension)').EndsWith('ApplyUpdateReferencedAssembly.dll'))"
Include="%(ResolvedFileToPublish.FullPath)" />
</ItemGroup>
</Target>

<Target Name="IncludeDeltasInWasmBundle"
BeforeTargets="PrepareForWasmBuildApp"
Condition="'$(TargetOS)' == 'Browser'">
<ItemGroup>
<!-- FIXME: this belongs in eng/testing/tests.wasm.targets -->
<!-- FIXME: Can we do something on the Content items in the referenced projects themselves to get this for free? -->
<WasmFilesToIncludeInFileSystem Include="@(PublishItemsOutputGroupOutputs)"
Condition="$([System.String]::new('%(PublishItemsOutputGroupOutputs.Identity)').EndsWith('.dmeta'))" />
<WasmFilesToIncludeInFileSystem Include="@(PublishItemsOutputGroupOutputs)"
Condition="$([System.String]::new('%(PublishItemsOutputGroupOutputs.Identity)').EndsWith('.dil'))" />
<WasmFilesToIncludeInFileSystem Include="@(PublishItemsOutputGroupOutputs)"
Condition="$([System.String]::new('%(PublishItemsOutputGroupOutputs.Identity)').EndsWith('.dpdb'))" />
</ItemGroup>
</Target>

</Project>
55 changes: 55 additions & 0 deletions src/tests/FunctionalTests/WebAssembly/Browser/HotReload/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>
<head>
<title>TESTS</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body onload="onLoad()">
<h3 id="header">Wasm Browser Sample</h3>
Result from Sample.Test.TestMeaning: <span id="out"></span>
<script type='text/javascript'>
var is_testing = false;
var onLoad = function() {
var url = new URL(decodeURI(window.location));
let args = url.searchParams.getAll('arg');
is_testing = args !== undefined && (args.find(arg => arg == '--testing') !== undefined);
};

var test_exit = function(exit_code)
{
if (!is_testing) {
console.log(`test_exit: ${exit_code}`);
return;
}

/* Set result in a tests_done element, to be read by xharness */
var tests_done_elem = document.createElement("label");
tests_done_elem.id = "tests_done";
tests_done_elem.innerHTML = exit_code.toString();
document.body.appendChild(tests_done_elem);

console.log(`WASM EXIT ${exit_code}`);
};

var App = {
init: function () {
var exit_code = BINDING.call_static_method("[WebAssembly.Browser.HotReload.Test] Sample.Test:TestMeaning", []);
document.getElementById("out").innerHTML = exit_code;

if (is_testing)
{
console.debug(`exit_code: ${exit_code}`);
test_exit(exit_code);
}
},
};
</script>
<script type="text/javascript" src="runtime.js"></script>

<script defer src="dotnet.js"></script>

</body>
</html>
47 changes: 47 additions & 0 deletions src/tests/FunctionalTests/WebAssembly/Browser/HotReload/runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

var Module = {

config: null,

preInit: async function() {
Module.config = await MONO.mono_wasm_load_config("./mono-config.json");
},

onRuntimeInitialized: function () {
if (!Module.config || Module.config.error) {
console.log("No config found");
test_exit(1);
throw(Module.config.error);
}

Module.config.loaded_cb = function () {
try {
App.init ();
} catch (error) {
test_exit(1);
throw (error);
}
};
Module.config.fetch_file_cb = function (asset) {
return fetch (asset, { credentials: 'same-origin' });
}

if (Module.config.environment_variables !== undefined) {
console.log ("expected environment variables to be undefined, but they're: ", Module.config.environment_variables);
test_exit(1);
}
Module.config.environment_variables = {
"DOTNET_MODIFIABLE_ASSEMBLIES": "debug"
};

try
{
MONO.mono_load_runtime_and_bcl_args (Module.config);
} catch (error) {
test_exit(1);
throw(error);
}
},
};

0 comments on commit 93f407d

Please sign in to comment.