Skip to content

Commit

Permalink
[wasm] Introduce <InvariantTimezone> build flag (#87284)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelsavara authored Jul 1, 2023
1 parent e80ef86 commit d388585
Show file tree
Hide file tree
Showing 19 changed files with 220 additions and 41 deletions.
16 changes: 16 additions & 0 deletions docs/design/features/timezone-invariant-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Timezone Invariant Mode

Author: [Pavel Savara](https://github.com/pavelsavara)

It's currently only available for Browser OS.
The timezone database is not part of the browser environment (as opposed to other operating systems).
Therefore dotnet bundles the timezone database as binary as part of the runtime.
That makes download size larger and application startup slower.
If your application doesn't need to work with time zone information, you could use this feature to make the runtime about 200KB smaller.

You enable it in project file:
```xml
<PropertyGroup>
<InvariantTimezone>true</InvariantTimezone>
</PropertyGroup>
```
1 change: 1 addition & 0 deletions eng/testing/scenarios/BuildWasmAppsJobsList.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Wasm.Build.Tests.ConfigSrcTests
Wasm.Build.Tests.IcuShardingTests
Wasm.Build.Tests.HybridGlobalizationTests
Wasm.Build.Tests.InvariantGlobalizationTests
Wasm.Build.Tests.InvariantTimezoneTests
Wasm.Build.Tests.MainWithArgsTests
Wasm.Build.Tests.NativeBuildTests
Wasm.Build.Tests.NativeLibraryTests
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ Copyright (c) .NET Foundation. All rights reserved.

<!-- Runtime feature defaults to trim unnecessary code -->
<InvariantGlobalization Condition="'$(InvariantGlobalization)' == ''">false</InvariantGlobalization>
<InvariantTimezone Condition="'$(BlazorEnableTimeZoneSupport)' == 'false'">true</InvariantTimezone>
<InvariantTimezone Condition="'$(InvariantTimezone)' == ''">false</InvariantTimezone>
<EventSourceSupport Condition="'$(EventSourceSupport)' == ''">false</EventSourceSupport>
<UseSystemResourceKeys Condition="'$(UseSystemResourceKeys)' == ''">true</UseSystemResourceKeys>
<EnableUnsafeUTF7Encoding Condition="'$(EnableUnsafeUTF7Encoding)' == ''">false</EnableUnsafeUTF7Encoding>
Expand Down
20 changes: 20 additions & 0 deletions src/mono/sample/wasm/browser-advanced/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ public partial class Test
public static int Main(string[] args)
{
Console.WriteLine ("Hello, World!");

var start = DateTime.UtcNow;
var timezonesCount = TimeZoneInfo.GetSystemTimeZones().Count;
var end = DateTime.UtcNow;
Console.WriteLine($"Found {timezonesCount} timezones in the TZ database in {end-start}");

TimeZoneInfo utc = TimeZoneInfo.FindSystemTimeZoneById("UTC");
Console.WriteLine($"{utc.DisplayName} BaseUtcOffset is {utc.BaseUtcOffset}");

try
{
TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo");
Console.WriteLine($"{tst.DisplayName} BaseUtcOffset is {tst.BaseUtcOffset}");
}
catch (TimeZoneNotFoundException tznfe)
{
Console.WriteLine($"Could not find Asia/Tokyo: {tznfe.Message}");
}


return 0;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<PropertyGroup>
<EnableAggressiveTrimming>true</EnableAggressiveTrimming>
<PublishTrimmed>true</PublishTrimmed>
<InvariantTimezone>true</InvariantTimezone>
<WasmEnableLegacyJsInterop>false</WasmEnableLegacyJsInterop>
<WasmEnableWebcil>false</WasmEnableWebcil>
<WasmEmitSymbolMap>true</WasmEmitSymbolMap>
Expand Down
34 changes: 28 additions & 6 deletions src/mono/wasi/Wasi.Build.Tests/WasiTemplateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,19 @@ public void ConsoleBuildThenPublish(string config)
UseCache: false));
}

public static TheoryData<string, bool> TestDataForConsolePublishAndRun()
public static TheoryData<string, bool, bool> TestDataForConsolePublishAndRun()
{
var data = new TheoryData<string, bool>();
data.Add("Debug", false);
data.Add("Debug", true);
data.Add("Release", false); // Release relinks by default
var data = new TheoryData<string, bool, bool>();
data.Add("Debug", false, false);
data.Add("Debug", true, true);
data.Add("Release", false, false); // Release relinks by default
return data;
}

[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/82515", TestPlatforms.Windows)]
[MemberData(nameof(TestDataForConsolePublishAndRun))]
public void ConsolePublishAndRunForSingleFileBundle(string config, bool relinking)
public void ConsolePublishAndRunForSingleFileBundle(string config, bool relinking, bool invariantTimezone)
{
string id = $"{config}_{Path.GetRandomFileName()}";
string projectFile = CreateWasmTemplateProject(id, "wasiconsole");
Expand All @@ -96,6 +96,8 @@ public void ConsolePublishAndRunForSingleFileBundle(string config, bool relinkin
string extraProperties = "<WasmSingleFileBundle>true</WasmSingleFileBundle>";
if (relinking)
extraProperties += "<WasmBuildNative>true</WasmBuildNative>";
if (invariantTimezone)
extraProperties += "<InvariantTimezone>true</InvariantTimezone>";

AddItemsPropertiesToProject(projectFile, extraProperties);

Expand Down Expand Up @@ -125,6 +127,15 @@ public void ConsolePublishAndRunForSingleFileBundle(string config, bool relinkin
Assert.Contains("args[0] = x", res.Output);
Assert.Contains("args[1] = y", res.Output);
Assert.Contains("args[2] = z", res.Output);
if(invariantTimezone)
{
Assert.Contains("Could not find Asia/Tokyo", res.Output);
}
else
{
Assert.Contains("Asia/Tokyo BaseUtcOffset is 09:00:00", res.Output);
}

}

private static readonly string s_simpleMainWithArgs = """
Expand All @@ -133,6 +144,17 @@ public void ConsolePublishAndRunForSingleFileBundle(string config, bool relinkin
Console.WriteLine("Hello, Wasi Console!");
for (int i = 0; i < args.Length; i ++)
Console.WriteLine($"args[{i}] = {args[i]}");
try
{
TimeZoneInfo tst = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo");
Console.WriteLine($"{tst.DisplayName} BaseUtcOffset is {tst.BaseUtcOffset}");
}
catch (TimeZoneNotFoundException tznfe)
{
Console.WriteLine($"Could not find Asia/Tokyo: {tznfe.Message}");
}
return 42;
""";
}
8 changes: 7 additions & 1 deletion src/mono/wasi/build/WasiApp.Native.targets
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
<WasmBuildNative Condition="'$(RunAOTCompilation)' == 'true' and '$(RunAOTCompilationAfterBuild)' == 'true'">true</WasmBuildNative>

<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(WasmEnableSIMD)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantTimezone)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantGlobalization)' == 'true'">true</WasmBuildNative>

<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and @(NativeFileReference->Count()) > 0" >true</WasmBuildNative>

Expand All @@ -75,6 +77,8 @@
<WasmBuildNative Condition="'$(RunAOTCompilation)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and @(NativeFileReference->Count()) > 0" >true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(WasmEnableSIMD)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantTimezone)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantGlobalization)' == 'true'">true</WasmBuildNative>

<!-- not aot, not trimmed app, no reason to relink -->
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(PublishTrimmed)' != 'true'">false</WasmBuildNative>
Expand Down Expand Up @@ -168,8 +172,9 @@

<ItemGroup>
<_WasmCommonCFlags Include="-DGEN_PINVOKE=1" />
<_WasmCommonCFlags Condition="'$(WasmSingleFileBundle)' == 'true'" Include="-DWASM_SINGLE_FILE=1" />
<_WasmCommonCFlags Condition="'$(WasmSingleFileBundle)' == 'true'" Include="-DWASM_SINGLE_FILE=1" />
<_WasmCommonCFlags Condition="'$(InvariantGlobalization)' == 'true'" Include="-DINVARIANT_GLOBALIZATION=1" />
<_WasmCommonCFlags Condition="'$(InvariantTimezone)' == 'true'" Include="-DINVARIANT_TIMEZONE=1" />

<!-- Adding optimization flag at the top, so it gets precedence -->
<!--<_EmccCFlags Include="$(EmccCompileOptimizationFlag)" />-->
Expand Down Expand Up @@ -273,6 +278,7 @@
<!--<_WasmNativeFileForLinking Include="%(_BitcodeFile.ObjectFile)" />-->
<!--<_WasmNativeFileForLinking Include="%(_WasmSourceFileToCompile.ObjectFile)" />-->
<_MonoRuntimeComponentDontLink Include="libmono-component-diagnostics_tracing-static.a" />
<_MonoRuntimeComponentDontLink Include="wasm-bundled-timezones.a" Condition="'$(InvariantTimezone)' == 'true'"/>

<_WasmNativeFileForLinking
Include="$(MicrosoftNetCoreAppRuntimePackRidNativeDir)*.a"
Expand Down
12 changes: 7 additions & 5 deletions src/mono/wasi/build/WasiApp.targets
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
Public properties (optional):
- $(WasmAppDir) - AppBundle dir (Defaults to `$(OutputPath)\$(Configuration)\AppBundle`)
- $(WasmMainAssemblyFileName)- Defaults to $(TargetFileName)
- $(WasmBuildNative) - Whenever to build the native executable. Defaults to false.
- $(WasmNativeStrip) - Whenever to strip the native executable. Defaults to true.
- $(WasmLinkIcalls) - Whenever to link out unused icalls. Defaults to $(WasmBuildNative).
- $(WasmBuildNative) - Whether to build the native executable. Defaults to false.
- $(WasmNativeStrip) - Whether to strip the native executable. Defaults to true.
- $(WasmLinkIcalls) - Whether to link out unused icalls. Defaults to $(WasmBuildNative).
- $(RunAOTCompilation) - Defaults to false.
- $(WasmDebugLevel)
Expand All @@ -24,11 +24,12 @@
- $(WasmNativeDebugSymbols) - Build with native debug symbols, useful only with `$(RunAOTCompilation)`, or `$(WasmBuildNative)`
Defaults to true.
- $(WasmEmitSymbolMap) - Generates a `dotnet.js.symbols` file with a map of wasm function number to name.
- $(WasmDedup) - Whenever to dedup generic instances when using AOT. Defaults to true.
- $(WasmDedup) - Whether to dedup generic instances when using AOT. Defaults to true.
- $(WasmProfilers) - Profilers to use
- $(AOTProfilePath) - profile data file to be used for profile-guided optimization
- $(InvariantGlobalization) - Whenever to disable ICU. Defaults to false.
- $(InvariantGlobalization) - Whether to disable ICU. Defaults to false.
- $(InvariantTimezone) - Whether to disable Timezone database. Defaults to false.
- $(WasmResolveAssembliesBeforeBuild) - Resolve the assembly dependencies. Defaults to false
- $(WasmAssemblySearchPaths) - used for resolving assembly dependencies
Expand Down Expand Up @@ -119,6 +120,7 @@
<WasmSingleFileBundle Condition="'$(WasmSingleFileBundle)' == ''">false</WasmSingleFileBundle>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(WasmSingleFileBundle)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantGlobalization)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantTimezone)' == 'true'">true</WasmBuildNative>

<WasiBundleAssemblies Condition="'$(WasiBundleAssemblies)' == ''">true</WasiBundleAssemblies>
<WasiRunner Condition="'$(WasiRunner)' == ''">wasmtime</WasiRunner>
Expand Down
4 changes: 4 additions & 0 deletions src/mono/wasi/runtime/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ int32_t monoeg_g_hasenv(const char *variable);
void mono_free (void*);
int32_t mini_parse_debug_option (const char *option);
char *mono_method_get_full_name (MonoMethod *method);
#ifndef INVARIANT_TIMEZONE
extern void mono_register_timezones_bundle (void);
#endif /* INVARIANT_TIMEZONE */
#ifdef WASM_SINGLE_FILE
extern void mono_register_assemblies_bundle (void);
#ifndef INVARIANT_GLOBALIZATION
Expand Down Expand Up @@ -439,7 +441,9 @@ mono_wasm_load_runtime (const char *unused, int debug_level)

mini_parse_debug_option ("top-runtime-invoke-unhandled");

#ifndef INVARIANT_TIMEZONE
mono_register_timezones_bundle ();
#endif /* INVARIANT_TIMEZONE */
#ifdef WASM_SINGLE_FILE
mono_register_assemblies_bundle ();
#endif
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasi/wasi.proj
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
</Target>

<UsingTask TaskName="EmitBundleObjectFiles" AssemblyFile="$(MonoTargetsTasksAssemblyPath)" />
<Target Name="GenerateTimezonesArchive" Returns="@(_WasmArchivedTimezones)">
<Target Name="GenerateTimezonesArchive" Returns="@(_WasmArchivedTimezones)" Condition="'$(InvariantTimezone)' != 'true'">
<PropertyGroup>
<_WasmTimezonesPath>$([MSBuild]::NormalizePath('$(PkgSystem_Runtime_TimeZoneData)', 'contentFiles', 'any', 'any', 'data'))</_WasmTimezonesPath>
<_WasmTimezonesBundleObjectFile>wasm-bundled-timezones.o</_WasmTimezonesBundleObjectFile>
Expand Down
21 changes: 1 addition & 20 deletions src/mono/wasm/Wasm.Build.Tests/InvariantGlobalizationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,10 @@ private void TestInvariantGlobalization(BuildArgs buildArgs, bool? invariantGlob
if (dotnetWasmFromRuntimePack == null)
dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release");

string programText = @"
using System;
using System.Globalization;
// https://github.com/dotnet/runtime/blob/main/docs/design/features/globalization-invariant-mode.md#cultures-and-culture-data
try
{
CultureInfo culture = new (""es-ES"", false);
Console.WriteLine($""es-ES: Is Invariant LCID: {culture.LCID == CultureInfo.InvariantCulture.LCID}, NativeName: {culture.NativeName}"");
}
catch (CultureNotFoundException cnfe)
{
Console.WriteLine($""Could not create es-ES culture: {cnfe.Message}"");
}
Console.WriteLine($""CurrentCulture.NativeName: {CultureInfo.CurrentCulture.NativeName}"");
return 42;
";

BuildProject(buildArgs,
id: id,
new BuildProjectOptions(
InitProject: () => File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText),
InitProject: () => File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "Wasm.Buid.Tests.Programs", "InvariantGlobalization.cs"), Path.Combine(_projectDir!, "Program.cs")),
DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack,
GlobalizationMode: invariantGlobalization == true ? GlobalizationMode.Invariant : null));

Expand Down
73 changes: 73 additions & 0 deletions src/mono/wasm/Wasm.Build.Tests/InvariantTimezoneTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.IO;
using Xunit;
using Xunit.Abstractions;

#nullable enable

namespace Wasm.Build.Tests
{
public class InvariantTimezoneTests : BuildTestBase
{
public InvariantTimezoneTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
}

public static IEnumerable<object?[]> InvariantTimezoneTestData(bool aot, RunHost host)
=> ConfigWithAOTData(aot)
.Multiply(
new object?[] { null },
new object?[] { false },
new object?[] { true })
.WithRunHosts(host)
.UnwrapItemsAsArrays();

[Theory]
[MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false, RunHost.All })]
[MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ true, RunHost.All })]
public void AOT_InvariantTimezone(BuildArgs buildArgs, bool? invariantTimezone, RunHost host, string id)
=> TestInvariantTimezone(buildArgs, invariantTimezone, host, id);

[Theory]
[MemberData(nameof(InvariantTimezoneTestData), parameters: new object[] { /*aot*/ false, RunHost.All })]
public void RelinkingWithoutAOT(BuildArgs buildArgs, bool? invariantTimezone, RunHost host, string id)
=> TestInvariantTimezone(buildArgs, invariantTimezone, host, id,
extraProperties: "<WasmBuildNative>true</WasmBuildNative>",
dotnetWasmFromRuntimePack: false);

private void TestInvariantTimezone(BuildArgs buildArgs, bool? invariantTimezone,
RunHost host, string id, string extraProperties="", bool? dotnetWasmFromRuntimePack=null)
{
string projectName = $"invariant_{invariantTimezone?.ToString() ?? "unset"}";
if (invariantTimezone != null)
extraProperties = $"{extraProperties}<InvariantTimezone>{invariantTimezone}</InvariantTimezone>";

buildArgs = buildArgs with { ProjectName = projectName };
buildArgs = ExpandBuildArgs(buildArgs, extraProperties);

if (dotnetWasmFromRuntimePack == null)
dotnetWasmFromRuntimePack = !(buildArgs.AOT || buildArgs.Config == "Release");

BuildProject(buildArgs,
id: id,
new BuildProjectOptions(
InitProject: () => File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "Wasm.Buid.Tests.Programs", "InvariantTimezone.cs"), Path.Combine(_projectDir!, "Program.cs")),
DotnetWasmFromRuntimePack: dotnetWasmFromRuntimePack));

string output = RunAndTestWasmApp(buildArgs, expectedExitCode: 42, host: host, id: id);
Assert.Contains("UTC BaseUtcOffset is 0", output);
if (invariantTimezone == true)
{
Assert.Contains("Could not find Asia/Tokyo", output);
}
else
{
Assert.Contains("Asia/Tokyo BaseUtcOffset is 09:00:00", output);
}
}
}
}
9 changes: 9 additions & 0 deletions src/mono/wasm/build/WasmApp.Native.targets
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@
<!-- removing legacy interop needs relink -->
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(WasmEnableLegacyJsInterop)' == 'false'" >true</WasmBuildNative>

<!-- InvariantTimezone and InvariantGlobalization need rebuild -->
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantTimezone)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantGlobalization)' == 'true'">true</WasmBuildNative>

<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and @(NativeFileReference->Count()) > 0" >true</WasmBuildNative>

<WasmBuildNative Condition="'$(WasmBuildNative)' == ''">false</WasmBuildNative>
Expand All @@ -129,6 +133,10 @@
<!-- removing legacy interop needs relink -->
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(WasmEnableLegacyJsInterop)' == 'false'" >true</WasmBuildNative>

<!-- InvariantTimezone and InvariantGlobalization need rebuild -->
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantTimezone)' == 'true'">true</WasmBuildNative>
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(InvariantGlobalization)' == 'true'">true</WasmBuildNative>

<!-- not aot, not trimmed app, no reason to relink -->
<WasmBuildNative Condition="'$(WasmBuildNative)' == '' and '$(PublishTrimmed)' != 'true'">false</WasmBuildNative>

Expand Down Expand Up @@ -219,6 +227,7 @@
<_EmccCFlags Include="-DENABLE_AOT=1" Condition="'$(_WasmShouldAOT)' == 'true'" />
<_EmccCFlags Include="-DDRIVER_GEN=1" Condition="'$(_WasmShouldAOT)' == 'true'" />
<_EmccCFlags Include="-DINVARIANT_GLOBALIZATION=1" Condition="'$(InvariantGlobalization)' == 'true'" />
<_EmccCFlags Include="-DINVARIANT_TIMEZONE=1" Condition="'$(InvariantTimezone)' == 'true'" />
<_EmccCFlags Include="-DLINK_ICALLS=1" Condition="'$(WasmLinkIcalls)' == 'true'" />
<_EmccCFlags Include="-DENABLE_AOT_PROFILER=1" Condition="$(WasmProfilers.Contains('aot'))" />
<_EmccCFlags Include="-DENABLE_BROWSER_PROFILER=1" Condition="$(WasmProfilers.Contains('browser'))" />
Expand Down
Loading

0 comments on commit d388585

Please sign in to comment.