[browser] improve default initial memory size (#80507)
authorPavel Savara <pavel.savara@gmail.com>
Tue, 24 Jan 2023 20:52:27 +0000 (21:52 +0100)
committerGitHub <noreply@github.com>
Tue, 24 Jan 2023 20:52:27 +0000 (21:52 +0100)
make EmccInitialHeapSize dynamic based on size of the assemblies and AOT DATA segments

Co-authored-by: Ankit Jain <radical@gmail.com>
src/mono/sample/wasm/browser-bench/Wasm.Browser.Bench.Sample.csproj
src/mono/wasm/build/WasmApp.Native.targets
src/mono/wasm/build/WasmApp.targets
src/mono/wasm/wasm.proj
src/tasks/WasmAppBuilder/WasmCalculateInitialHeapSize.cs [new file with mode: 0644]

index 1f4f8e0..0ee75e1 100644 (file)
@@ -9,10 +9,6 @@
     <EnableAggressiveTrimming Condition="'$(EnableAOTAndTrimming)' != ''">$(EnableAOTAndTrimming)</EnableAggressiveTrimming>
     <PublishTrimmed Condition="'$(EnableAOTAndTrimming)' != ''">$(EnableAOTAndTrimming)</PublishTrimmed>
     <RunAOTCompilation Condition="'$(EnableAOTAndTrimming)' != ''">$(EnableAOTAndTrimming)</RunAOTCompilation>
-    <!-- the default heap size is ~512MB, which is too much because AppStart loads more copies
-      of the wasm runtime and can leak a few of them. the result is that browser-bench's memory
-      usage can climb as high as 3GB or more and then fail -->
-    <EmccInitialHeapSize>83886080</EmccInitialHeapSize>
   </PropertyGroup>
 
   <ItemGroup>
index 6f9524e..57e69e8 100644 (file)
       <_EmccLinkRsp>$(_WasmIntermediateOutputPath)emcc-link.rsp</_EmccLinkRsp>
 
       <EmccInitialHeapSize Condition="'$(EmccInitialHeapSize)' == ''">$(EmccTotalMemory)</EmccInitialHeapSize>
-      <EmccInitialHeapSize Condition="'$(EmccInitialHeapSize)' == ''">536870912</EmccInitialHeapSize>
     </PropertyGroup>
 
     <ItemGroup>
       <_EmccLDFlags Include="-s ASSERTIONS=$(_EmccAssertionLevelDefault)" Condition="'$(_WasmDevel)' == 'true'" />
       <_EmccLDFlags Include="@(_EmccCommonFlags)" />
       <_EmccLDSFlags Include="-Wl,--allow-undefined" />
-      <_EmccLDSFlags Include="-s INITIAL_MEMORY=$(EmccInitialHeapSize)" />
 
       <!-- ILLinker should have removed unused imports, so error for Publish -->
       <_EmccLDSFlags Include="-s ERROR_ON_UNDEFINED_SYMBOLS=0" Condition="'$(WasmBuildingForNestedPublish)' != 'true'" />
     </ItemGroup>
   </Target>
 
-  <Target Name="_WasmWriteRspFilesForLinking" DependsOnTargets="_CheckEmccIsExpectedVersion">
+  <Target Name="_WasmCalculateInitialHeapSize" 
+      Condition="'$(EmccInitialHeapSize)' == ''"
+      DependsOnTargets="_CheckEmccIsExpectedVersion">
+    <ItemGroup>
+      <_AOTObjectFile Include="%(_BitcodeFile.ObjectFile)" />
+    </ItemGroup>
+
+    <!-- for AOT builds we use llvm-size tool to collect size of the DATA segment in each object file -->
+    <Exec Command="llvm-size$(_ExeExt) -d --format=sysv @(_AOTObjectFile, ' ')"
+          Condition="'$(_WasmShouldAOT)' == 'true'"
+          IgnoreStandardErrorWarningFormat="true"
+          ConsoleToMsBuild="true"
+          StandardOutputImportance="low" StandardErrorImportance="low"
+          EnvironmentVariables="@(EmscriptenEnvVars)" >
+      <Output TaskParameter="ConsoleOutput" ItemName="LlvmAotSizeOutput" />
+    </Exec>
+    <ItemGroup Condition="'$(_WasmShouldAOT)' == 'true'">
+      <_AOTDataSegmentSize Condition="$([System.String]::Copy('%(LlvmAotSizeOutput.Identity)').StartsWith('DATA '))"
+        Include="$([System.String]::Copy('%(LlvmAotSizeOutput.Identity)').Replace(&quot;DATA &quot;, &quot;&quot;).Replace(&quot; 0&quot;, &quot;&quot;).Trim())" />
+    </ItemGroup>
+
+    <WasmCalculateInitialHeapSize
+        Assemblies="@(WasmAssembliesToBundle)"
+        AOTDataSegmentSizes="@(_AOTDataSegmentSize)">
+      <Output TaskParameter="InitialHeapSize" PropertyName="_WasmCalculatedInitialHeapSize" />
+    </WasmCalculateInitialHeapSize>
+    <PropertyGroup>
+      <EmccInitialHeapSize Condition="'$(EmccInitialHeapSize)' == '' and '$(_WasmCalculatedInitialHeapSize)' != '' and $(_WasmCalculatedInitialHeapSize) > 16777216">$(_WasmCalculatedInitialHeapSize)</EmccInitialHeapSize>
+      <EmccInitialHeapSize Condition="'$(EmccInitialHeapSize)' == ''">16777216</EmccInitialHeapSize>
+    </PropertyGroup>
+  </Target>
+
+  <Target Name="_WasmWriteRspFilesForLinking" DependsOnTargets="_CheckEmccIsExpectedVersion;_WasmCalculateInitialHeapSize">
     <PropertyGroup>
       <_WasmEHLib Condition="'$(WasmEnableExceptionHandling)' == 'true'">libmono-wasm-eh-wasm.a</_WasmEHLib>
       <_WasmEHLib Condition="'$(WasmEnableExceptionHandling)' != 'true'">libmono-wasm-eh-js.a</_WasmEHLib>
     </PropertyGroup>
     <ItemGroup>
       <!-- order matters -->
+      <_EmccLDSFlags Include="-s INITIAL_MEMORY=$(EmccInitialHeapSize)" />
+
       <_WasmNativeFileForLinking Include="%(_BitcodeFile.ObjectFile)" />
       <_WasmNativeFileForLinking Include="%(_WasmSourceFileToCompile.ObjectFile)" />
 
index 9f5647e..9d2eb3a 100644 (file)
@@ -1,6 +1,7 @@
 <Project>
   <UsingTask TaskName="WasmAppBuilder" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
   <UsingTask TaskName="WasmLoadAssembliesAndReferences" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
+  <UsingTask TaskName="WasmCalculateInitialHeapSize" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
 
   <!--
       Required public items/properties:
@@ -51,7 +52,7 @@
       - $(EmccFlags)                        - Emcc flags used for both compiling native files, and linking
       - $(EmccExtraLDFlags)                 - Extra emcc flags for linking
       - $(EmccExtraCFlags)                  - Extra emcc flags for compiling native files
-      - $(EmccInitialHeapSize)              - Initial heap size specified with `emcc`. Default value: 536870912
+      - $(EmccInitialHeapSize)              - Initial heap size specified with `emcc`. Default value: 16777216 or size of the DLLs, whichever is larger.
                                               Corresponds to `INITIAL_MEMORY` arg for emcc.
                                               (previously named EmccTotalMemory, which is still kept as an alias)
 
index 10b11cd..dd279eb 100644 (file)
     <PropertyGroup>
       <_EmccExportedRuntimeMethods>&quot;[@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]&quot;</_EmccExportedRuntimeMethods>
       <_EmccExportedFunctions>@(EmccExportedFunction -> '%(Identity)',',')</_EmccExportedFunctions>
-      <EmccInitialHeapSize>536870912</EmccInitialHeapSize>
+      <EmccInitialHeapSize>16777216</EmccInitialHeapSize>
     </PropertyGroup>
     <ItemGroup>
       <_EmccLinkFlags Include="-s INITIAL_MEMORY=$(EmccInitialHeapSize)" />
diff --git a/src/tasks/WasmAppBuilder/WasmCalculateInitialHeapSize.cs b/src/tasks/WasmAppBuilder/WasmCalculateInitialHeapSize.cs
new file mode 100644 (file)
index 0000000..d54bd65
--- /dev/null
@@ -0,0 +1,59 @@
+// 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 Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+
+namespace Microsoft.WebAssembly.Build.Tasks;
+
+/// <summary>estimate the total memory needed for the assemblies and AOT data segments.</summary>
+public class WasmCalculateInitialHeapSize : Task
+{
+    [Required]
+    public string[] Assemblies { get; set; } = Array.Empty<string>();
+
+    public string[] AOTDataSegmentSizes { get; set; } = Array.Empty<string>();
+
+    [Output]
+    public long InitialHeapSize { get; private set; }
+
+    public override bool Execute()
+    {
+        long totalDllSize = 0;
+        long totalDataSize = 0;
+
+        foreach (string asm in Assemblies)
+        {
+            var info = new FileInfo(asm);
+            if (!info.Exists)
+            {
+                Log.LogError($"Could not find assembly '{asm}'");
+                return false;
+            }
+            totalDllSize += info.Length;
+        }
+
+        // during non-AOT builds, AOTDataSegmentSizes is empty
+        foreach (string segment in AOTDataSegmentSizes)
+        {
+            if (!long.TryParse(segment, out long segmentSize))
+            {
+                Log.LogError($"Could not parse AOT Data segment size '{segment}");
+                return false;
+            }
+            totalDataSize += segmentSize;
+        }
+
+        // this is arbitrary guess about memory overhead of the runtime, after the assemblies are loaded
+        const double extraMemoryRatio = 1.2;
+        // plus size of data segments generated by AOT
+        long memorySize = totalDataSize + (long)(totalDllSize * extraMemoryRatio);
+
+        // round it up to 64KB page size for wasm
+        InitialHeapSize = (memorySize + 0x10000) & 0xFFFF0000;
+
+        return true;
+    }
+}