make EmccInitialHeapSize dynamic based on size of the assemblies and AOT DATA segments
Co-authored-by: Ankit Jain <radical@gmail.com>
<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>
<_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("DATA ", "").Replace(" 0", "").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)" />
<Project>
<UsingTask TaskName="WasmAppBuilder" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
<UsingTask TaskName="WasmLoadAssembliesAndReferences" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
+ <UsingTask TaskName="WasmCalculateInitialHeapSize" AssemblyFile="$(WasmAppBuilderTasksAssemblyPath)" />
<!--
Required public items/properties:
- $(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)
<PropertyGroup>
<_EmccExportedRuntimeMethods>"[@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]"</_EmccExportedRuntimeMethods>
<_EmccExportedFunctions>@(EmccExportedFunction -> '%(Identity)',',')</_EmccExportedFunctions>
- <EmccInitialHeapSize>536870912</EmccInitialHeapSize>
+ <EmccInitialHeapSize>16777216</EmccInitialHeapSize>
</PropertyGroup>
<ItemGroup>
<_EmccLinkFlags Include="-s INITIAL_MEMORY=$(EmccInitialHeapSize)" />
--- /dev/null
+// 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;
+ }
+}