[Mono]: Reduce Mono AOT cross compiler x64 memory footprint. - #97096

Merged
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use
Feb 9, 2024
Merged

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint.#97096
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes a large amount of memory (up to 6 GB). This is mainly due to generated LLVM module not being optimized at all while kept in memory during full module generation. Mono x64 also lacks support for several intrinsics as well as Vector 256/512 that in turn leads to massive inlining of intrinsics functions generating a very large LLVM module, where majority of this code ends up as dead code due to IsSupported/IsHardwareAccelerated returning false.

The follow commit adjusts several things that will bring down the memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows from 6 GB down to ~750 MB.

  • Use PSNE implementations on intrinsics not supported on Mono.
  • Add ILLinker substitutions for intrinsics not supported on Mono. Enables ILLinker to do dead code elimination, reduce code to AOT compile.
  • Prevent aggressive inlining for a couple of unsupported intrinsics types making sure we don't end up with excessive inlining, exploding code size.
  • Run a couple of LLVM optimization passes on each generated method doing early code simplification and dead code elimination during LLVM module generation.
  • Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics implementation for all unsupported Mono x64 SIMD intrinsics.
  • Fixed numerous memory leaks in Mono AOT cross compiler code.
  • Fix a couple of sequence points free after use errors.
  • Fix an anonymous struct build warning triggering build error for LLVM enabled cross compiler on Windows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true for all codegens (e.g. interpreter)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like interpreter mark all vectors as not being hardware accelerated:

} else if (in_corlib &&
(!strncmp ("System.Runtime.Intrinsics", klass_name_space, 25) &&
!strncmp ("Vector", klass_name, 6) &&
!strcmp (tm, "get_IsHardwareAccelerated"))) {
*op = MINT_LDC_I4_0;
}

so for that case it should be ok.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @kg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mark v128 as hardware accelerated in interp in some cases, I believe.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so then this should be OK since it only affects v256/v512.

Comment threadsrc/mono/mono/mini/aot-compiler.c Outdated
@vargaz

Copy link
Copy Markdown
Contributor

Wouldn't be better to split this into smaller PRs ?

@lambdageek

Copy link
Copy Markdown
Member

I wonder if we can make working with symbols a little more typesafe so that we have some distinction between a mempool allocated symbol and a temporary malloc-allocated symbol. Maybe we can just pass around a GString for the temporary ones?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For RyuJIT, we have a fallback that handles any unrecognized get_IsHardwareAccelerated and get_IsSupported APIs for System.Numerics, System.Runtime.Intrinsics, and System.Runtime.Intrinsics.* to ensure they can be treated as constant false. Each namespace is being handled a little bit differently since get_IsSupported for some namespaces needs to fallback to user-code instead.

Would it be a good idea to similarly make this general-purpose. Notably vector_size == 64 should also be false on x86/x64 for example?

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a common fallback for IsHardwareAccelerted in instrinsics.c:

/* Fallback if SIMD is disabled */
if (in_corlib && ((!strcmp ("System.Numerics", cmethod_klass_name_space) && !strcmp ("Vector", cmethod_klass_name)) || !strncmp ("System.Runtime.Intrinsics", cmethod_klass_name_space, 25))) {
if (!strcmp (cmethod->name, "get_IsHardwareAccelerated")) {
EMIT_NEW_ICONST (cfg, ins, 0);
ins->type = STACK_I4;
return ins;
}
}

So I guess we should be able to drop that change (just made it explicitly for better visibility in this PR) and rely on that fallback to end up with the same result. Not sure why 64-bit vector size was not included in the past.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this change to rely on fallback for get_IsHardwareAccelerated.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Vector64?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a similar line for System.Runtime.Intrinsics.Arm and System.Runtime.Intrinsics.Wasm missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or general purpose code handling any unrecognized System.Runtime.Intrinsics.* namespace?)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled elsewhere in that source file.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will find Arm and Wasm under respective defines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they not need a path to ensure that IsSupported returns constant false and the intrinsics directly generate a PNSE exception, rather than hitting the recursive fallback, or is that handled elsewhere as well?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly focused on x86 in this PR, other code will still go through the fallbacks, but could probably be enhanced as well.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment on lines 6218 to 6155

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why this is needed on Mono a bit?

The software fallback for V128/V256/V512 is currently implemented as doing 2x operations on the lower/upper halves (except for a couple of methods like Shuffle which operate on the full vector). So V512 is implemented as 2x V256, V256 is implemented as 2x V128, and V128 is implemented as 2x V64. V64 is then implemented as a loop over the scalar elements with potentially large amounts of generic code.

So I would imagine that on any platform where V128 is supported, that the codegen for V256/V512 should be generally nice/small, even if aggressively inlined and with no real dead code elimination required. Even in the case where you have an unsupported type like V256<Guid>, the first V128 operation should have a PNSE thrown (since whether a type is supported or not is typically a known constant).

So I'd only expect that this is needed for V64 on x86/x64 where there is no acceleration and it has to hit the scalar fallback with the loop over the elements. For RyuJIT this loop isn't an issue due to the generic specialization we do, allowing us to get it down to only the code path that is actually used. I believe this isn't possible for Mono today and is non-trivial to add, so the skipping of inlining does make sense in that regard.

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without disabling aggressive inlining I ended up with methods that where huge, and not doing aggressive inline on these types (but still honor inline size limits) made them sane again. I will need to re-iterate around that change in order to tell exactly what happened with our inliner in the aggressive case.

@lateralusXlateralusXJan 22, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just validated this, with disabling the aggressive inlining as above a .net9 full AOT of S.P.C takes ~1.7 GB of memory, and not doing this will consume an additional 600 MB of memory, so I believe its worth preventing aggressive inlining for these types that are not hardware accelerated on any of the Mono supported platforms. I didn't add V64 since it seems to be handled a little differently on at least ARM case. I won't have bandwidth at the moment to do more deep analysis around why aggressive inlining of these template types cause that large increase in memory so I think doing this change, at least short term is worth it, we could probably file an issue around the bloat of Vector2561 and Vector5121, but maybe the better long term solution is to actually implement intrinsic support for these types.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For interpreter I'm handling this in a general fashion by detecting early dead pieces of code and not inlining any of the calls there. #97514. It is possible that a similar approach for jit can produce further improvements without having to special case classes.

@lateralusXlateralusXJan 25, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try to experiment with some dead code elimination, but that cause issues and doesn't work with our llvm codegen, so we explicitly turn that pass off for code that will be passed over to llvm. Instead I made sure we could do more in linker, but still these methods still explode and survives first simple llvm optimizations pass we now do in function manager pass, so feels like something in the inlined code prevents elimination until very late in the opt chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to reenable branch optimizations when using llvm in this pr:
#97189

@steveisok

Copy link
Copy Markdown
Member

System.Collections.Concurrent failed to AOT in a couple of suites. Not sure why, but here's the log https://helixre107v0xdcypoyl9e7f.blob.core.windows.net/dotnet-runtime-refs-pull-97096-merge-21c2561d7c2b483faf/Invariant.Tests/1/console.3bf63a3a.log?helixlogtype=result

Comment threadsrc/mono/mono/mini/aot-runtime.c Outdated
Comment threadsrc/mono/mono/mini/exceptions-amd64.c Outdated
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>.
@lewing

Copy link
Copy Markdown
Member

Hopfully I resolved the conflicts correctly, someone should review.

vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 4e885b1 to 65f6f9cCompareFebruary 9, 2024 04:16
vargaz added a commit that referenced this pull request Feb 9, 2024
…#98151)
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 65f6f9c to aa39dffCompareFebruary 9, 2024 05:29
vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
vargaz added a commit that referenced this pull request Feb 9, 2024
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes
a large amount of memory (up to 6 GB). This is mainly due to generated
LLVM module not being optimized at all while kept in memory during
full module generation. Mono x64 also lacks support for several
intrinsics as well as Vector 256/512 that in turn leads to massive
inlining of intrinsics functions generating a very large LLVM module,
where majority of this code ends up as dead code due to
IsSupported/IsHardwareAccelerated returning false.
The follow commit adjusts several things that will bring down the
memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows
from 6 GB down to ~750 MB.
* Use PSNE implementations on intrinsics not supported on Mono.
* Add ILLinker substitutions for intrinsics not supported on Mono. Enables
ILLinker to do dead code elimination, reduce code to AOT compile.
* Prevent aggressive inlining for a couple of unsupported intrinsics types
making sure we don't end up with excessive inlining, exploding code size.
* Run a couple of LLVM optimization passes on each generated method doing
early code simplification and dead code elimination during LLVM module
generation.
* Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics
implementation for all unsupported Mono x64 SIMD intrinsics.
* Fixed numerous memory leaks in Mono AOT cross compiler code.
* Fix a couple of sequence points free after use errors.
* Fix an anonymous struct build warning triggering build error for
LLVM enabled cross compiler on Windows.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from aa39dff to cfaf8d9CompareFebruary 9, 2024 08:46
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from ea8a484 to 5ab0d94CompareFebruary 9, 2024 09:06
@vargaz

Copy link
Copy Markdown
Contributor

Failures are unrelated.

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d)
image.

Good job everyone!

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d) image.

Good job everyone!

Edit. after the subsequent fix to this PR (#98515), the package size improvements on iOS HelloWorld are mostly gone (i.e., back to original values).

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

12 participants

@lateralusX@vargaz@lambdageek@steveisok@vitek-karas@lewing@matouskozak@kg@marek-safar@BrzVlad@tannergooding@fanyang-mono
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint. - #97096

Merged
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use
Feb 9, 2024
Merged

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint.#97096
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes a large amount of memory (up to 6 GB). This is mainly due to generated LLVM module not being optimized at all while kept in memory during full module generation. Mono x64 also lacks support for several intrinsics as well as Vector 256/512 that in turn leads to massive inlining of intrinsics functions generating a very large LLVM module, where majority of this code ends up as dead code due to IsSupported/IsHardwareAccelerated returning false.

The follow commit adjusts several things that will bring down the memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows from 6 GB down to ~750 MB.

  • Use PSNE implementations on intrinsics not supported on Mono.
  • Add ILLinker substitutions for intrinsics not supported on Mono. Enables ILLinker to do dead code elimination, reduce code to AOT compile.
  • Prevent aggressive inlining for a couple of unsupported intrinsics types making sure we don't end up with excessive inlining, exploding code size.
  • Run a couple of LLVM optimization passes on each generated method doing early code simplification and dead code elimination during LLVM module generation.
  • Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics implementation for all unsupported Mono x64 SIMD intrinsics.
  • Fixed numerous memory leaks in Mono AOT cross compiler code.
  • Fix a couple of sequence points free after use errors.
  • Fix an anonymous struct build warning triggering build error for LLVM enabled cross compiler on Windows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true for all codegens (e.g. interpreter)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like interpreter mark all vectors as not being hardware accelerated:

} else if (in_corlib &&
(!strncmp ("System.Runtime.Intrinsics", klass_name_space, 25) &&
!strncmp ("Vector", klass_name, 6) &&
!strcmp (tm, "get_IsHardwareAccelerated"))) {
*op = MINT_LDC_I4_0;
}

so for that case it should be ok.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @kg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mark v128 as hardware accelerated in interp in some cases, I believe.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so then this should be OK since it only affects v256/v512.

Comment threadsrc/mono/mono/mini/aot-compiler.c Outdated
@vargaz

Copy link
Copy Markdown
Contributor

Wouldn't be better to split this into smaller PRs ?

@lambdageek

Copy link
Copy Markdown
Member

I wonder if we can make working with symbols a little more typesafe so that we have some distinction between a mempool allocated symbol and a temporary malloc-allocated symbol. Maybe we can just pass around a GString for the temporary ones?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For RyuJIT, we have a fallback that handles any unrecognized get_IsHardwareAccelerated and get_IsSupported APIs for System.Numerics, System.Runtime.Intrinsics, and System.Runtime.Intrinsics.* to ensure they can be treated as constant false. Each namespace is being handled a little bit differently since get_IsSupported for some namespaces needs to fallback to user-code instead.

Would it be a good idea to similarly make this general-purpose. Notably vector_size == 64 should also be false on x86/x64 for example?

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a common fallback for IsHardwareAccelerted in instrinsics.c:

/* Fallback if SIMD is disabled */
if (in_corlib && ((!strcmp ("System.Numerics", cmethod_klass_name_space) && !strcmp ("Vector", cmethod_klass_name)) || !strncmp ("System.Runtime.Intrinsics", cmethod_klass_name_space, 25))) {
if (!strcmp (cmethod->name, "get_IsHardwareAccelerated")) {
EMIT_NEW_ICONST (cfg, ins, 0);
ins->type = STACK_I4;
return ins;
}
}

So I guess we should be able to drop that change (just made it explicitly for better visibility in this PR) and rely on that fallback to end up with the same result. Not sure why 64-bit vector size was not included in the past.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this change to rely on fallback for get_IsHardwareAccelerated.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Vector64?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a similar line for System.Runtime.Intrinsics.Arm and System.Runtime.Intrinsics.Wasm missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or general purpose code handling any unrecognized System.Runtime.Intrinsics.* namespace?)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled elsewhere in that source file.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will find Arm and Wasm under respective defines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they not need a path to ensure that IsSupported returns constant false and the intrinsics directly generate a PNSE exception, rather than hitting the recursive fallback, or is that handled elsewhere as well?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly focused on x86 in this PR, other code will still go through the fallbacks, but could probably be enhanced as well.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment on lines 6218 to 6155

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why this is needed on Mono a bit?

The software fallback for V128/V256/V512 is currently implemented as doing 2x operations on the lower/upper halves (except for a couple of methods like Shuffle which operate on the full vector). So V512 is implemented as 2x V256, V256 is implemented as 2x V128, and V128 is implemented as 2x V64. V64 is then implemented as a loop over the scalar elements with potentially large amounts of generic code.

So I would imagine that on any platform where V128 is supported, that the codegen for V256/V512 should be generally nice/small, even if aggressively inlined and with no real dead code elimination required. Even in the case where you have an unsupported type like V256<Guid>, the first V128 operation should have a PNSE thrown (since whether a type is supported or not is typically a known constant).

So I'd only expect that this is needed for V64 on x86/x64 where there is no acceleration and it has to hit the scalar fallback with the loop over the elements. For RyuJIT this loop isn't an issue due to the generic specialization we do, allowing us to get it down to only the code path that is actually used. I believe this isn't possible for Mono today and is non-trivial to add, so the skipping of inlining does make sense in that regard.

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without disabling aggressive inlining I ended up with methods that where huge, and not doing aggressive inline on these types (but still honor inline size limits) made them sane again. I will need to re-iterate around that change in order to tell exactly what happened with our inliner in the aggressive case.

@lateralusXlateralusXJan 22, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just validated this, with disabling the aggressive inlining as above a .net9 full AOT of S.P.C takes ~1.7 GB of memory, and not doing this will consume an additional 600 MB of memory, so I believe its worth preventing aggressive inlining for these types that are not hardware accelerated on any of the Mono supported platforms. I didn't add V64 since it seems to be handled a little differently on at least ARM case. I won't have bandwidth at the moment to do more deep analysis around why aggressive inlining of these template types cause that large increase in memory so I think doing this change, at least short term is worth it, we could probably file an issue around the bloat of Vector2561 and Vector5121, but maybe the better long term solution is to actually implement intrinsic support for these types.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For interpreter I'm handling this in a general fashion by detecting early dead pieces of code and not inlining any of the calls there. #97514. It is possible that a similar approach for jit can produce further improvements without having to special case classes.

@lateralusXlateralusXJan 25, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try to experiment with some dead code elimination, but that cause issues and doesn't work with our llvm codegen, so we explicitly turn that pass off for code that will be passed over to llvm. Instead I made sure we could do more in linker, but still these methods still explode and survives first simple llvm optimizations pass we now do in function manager pass, so feels like something in the inlined code prevents elimination until very late in the opt chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to reenable branch optimizations when using llvm in this pr:
#97189

@steveisok

Copy link
Copy Markdown
Member

System.Collections.Concurrent failed to AOT in a couple of suites. Not sure why, but here's the log https://helixre107v0xdcypoyl9e7f.blob.core.windows.net/dotnet-runtime-refs-pull-97096-merge-21c2561d7c2b483faf/Invariant.Tests/1/console.3bf63a3a.log?helixlogtype=result

Comment threadsrc/mono/mono/mini/aot-runtime.c Outdated
Comment threadsrc/mono/mono/mini/exceptions-amd64.c Outdated
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>.
@lewing

Copy link
Copy Markdown
Member

Hopfully I resolved the conflicts correctly, someone should review.

vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 4e885b1 to 65f6f9cCompareFebruary 9, 2024 04:16
vargaz added a commit that referenced this pull request Feb 9, 2024
…#98151)
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 65f6f9c to aa39dffCompareFebruary 9, 2024 05:29
vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
vargaz added a commit that referenced this pull request Feb 9, 2024
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes
a large amount of memory (up to 6 GB). This is mainly due to generated
LLVM module not being optimized at all while kept in memory during
full module generation. Mono x64 also lacks support for several
intrinsics as well as Vector 256/512 that in turn leads to massive
inlining of intrinsics functions generating a very large LLVM module,
where majority of this code ends up as dead code due to
IsSupported/IsHardwareAccelerated returning false.
The follow commit adjusts several things that will bring down the
memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows
from 6 GB down to ~750 MB.
* Use PSNE implementations on intrinsics not supported on Mono.
* Add ILLinker substitutions for intrinsics not supported on Mono. Enables
ILLinker to do dead code elimination, reduce code to AOT compile.
* Prevent aggressive inlining for a couple of unsupported intrinsics types
making sure we don't end up with excessive inlining, exploding code size.
* Run a couple of LLVM optimization passes on each generated method doing
early code simplification and dead code elimination during LLVM module
generation.
* Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics
implementation for all unsupported Mono x64 SIMD intrinsics.
* Fixed numerous memory leaks in Mono AOT cross compiler code.
* Fix a couple of sequence points free after use errors.
* Fix an anonymous struct build warning triggering build error for
LLVM enabled cross compiler on Windows.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from aa39dff to cfaf8d9CompareFebruary 9, 2024 08:46
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from ea8a484 to 5ab0d94CompareFebruary 9, 2024 09:06
@vargaz

Copy link
Copy Markdown
Contributor

Failures are unrelated.

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d)
image.

Good job everyone!

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d) image.

Good job everyone!

Edit. after the subsequent fix to this PR (#98515), the package size improvements on iOS HelloWorld are mostly gone (i.e., back to original values).

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

12 participants

@lateralusX@vargaz@lambdageek@steveisok@vitek-karas@lewing@matouskozak@kg@marek-safar@BrzVlad@tannergooding@fanyang-mono
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint. - #97096

Merged
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use
Feb 9, 2024
Merged

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint.#97096
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes a large amount of memory (up to 6 GB). This is mainly due to generated LLVM module not being optimized at all while kept in memory during full module generation. Mono x64 also lacks support for several intrinsics as well as Vector 256/512 that in turn leads to massive inlining of intrinsics functions generating a very large LLVM module, where majority of this code ends up as dead code due to IsSupported/IsHardwareAccelerated returning false.

The follow commit adjusts several things that will bring down the memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows from 6 GB down to ~750 MB.

  • Use PSNE implementations on intrinsics not supported on Mono.
  • Add ILLinker substitutions for intrinsics not supported on Mono. Enables ILLinker to do dead code elimination, reduce code to AOT compile.
  • Prevent aggressive inlining for a couple of unsupported intrinsics types making sure we don't end up with excessive inlining, exploding code size.
  • Run a couple of LLVM optimization passes on each generated method doing early code simplification and dead code elimination during LLVM module generation.
  • Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics implementation for all unsupported Mono x64 SIMD intrinsics.
  • Fixed numerous memory leaks in Mono AOT cross compiler code.
  • Fix a couple of sequence points free after use errors.
  • Fix an anonymous struct build warning triggering build error for LLVM enabled cross compiler on Windows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true for all codegens (e.g. interpreter)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like interpreter mark all vectors as not being hardware accelerated:

} else if (in_corlib &&
(!strncmp ("System.Runtime.Intrinsics", klass_name_space, 25) &&
!strncmp ("Vector", klass_name, 6) &&
!strcmp (tm, "get_IsHardwareAccelerated"))) {
*op = MINT_LDC_I4_0;
}

so for that case it should be ok.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @kg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mark v128 as hardware accelerated in interp in some cases, I believe.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so then this should be OK since it only affects v256/v512.

Comment threadsrc/mono/mono/mini/aot-compiler.c Outdated
@vargaz

Copy link
Copy Markdown
Contributor

Wouldn't be better to split this into smaller PRs ?

@lambdageek

Copy link
Copy Markdown
Member

I wonder if we can make working with symbols a little more typesafe so that we have some distinction between a mempool allocated symbol and a temporary malloc-allocated symbol. Maybe we can just pass around a GString for the temporary ones?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For RyuJIT, we have a fallback that handles any unrecognized get_IsHardwareAccelerated and get_IsSupported APIs for System.Numerics, System.Runtime.Intrinsics, and System.Runtime.Intrinsics.* to ensure they can be treated as constant false. Each namespace is being handled a little bit differently since get_IsSupported for some namespaces needs to fallback to user-code instead.

Would it be a good idea to similarly make this general-purpose. Notably vector_size == 64 should also be false on x86/x64 for example?

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a common fallback for IsHardwareAccelerted in instrinsics.c:

/* Fallback if SIMD is disabled */
if (in_corlib && ((!strcmp ("System.Numerics", cmethod_klass_name_space) && !strcmp ("Vector", cmethod_klass_name)) || !strncmp ("System.Runtime.Intrinsics", cmethod_klass_name_space, 25))) {
if (!strcmp (cmethod->name, "get_IsHardwareAccelerated")) {
EMIT_NEW_ICONST (cfg, ins, 0);
ins->type = STACK_I4;
return ins;
}
}

So I guess we should be able to drop that change (just made it explicitly for better visibility in this PR) and rely on that fallback to end up with the same result. Not sure why 64-bit vector size was not included in the past.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this change to rely on fallback for get_IsHardwareAccelerated.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Vector64?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a similar line for System.Runtime.Intrinsics.Arm and System.Runtime.Intrinsics.Wasm missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or general purpose code handling any unrecognized System.Runtime.Intrinsics.* namespace?)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled elsewhere in that source file.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will find Arm and Wasm under respective defines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they not need a path to ensure that IsSupported returns constant false and the intrinsics directly generate a PNSE exception, rather than hitting the recursive fallback, or is that handled elsewhere as well?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly focused on x86 in this PR, other code will still go through the fallbacks, but could probably be enhanced as well.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment on lines 6218 to 6155

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why this is needed on Mono a bit?

The software fallback for V128/V256/V512 is currently implemented as doing 2x operations on the lower/upper halves (except for a couple of methods like Shuffle which operate on the full vector). So V512 is implemented as 2x V256, V256 is implemented as 2x V128, and V128 is implemented as 2x V64. V64 is then implemented as a loop over the scalar elements with potentially large amounts of generic code.

So I would imagine that on any platform where V128 is supported, that the codegen for V256/V512 should be generally nice/small, even if aggressively inlined and with no real dead code elimination required. Even in the case where you have an unsupported type like V256<Guid>, the first V128 operation should have a PNSE thrown (since whether a type is supported or not is typically a known constant).

So I'd only expect that this is needed for V64 on x86/x64 where there is no acceleration and it has to hit the scalar fallback with the loop over the elements. For RyuJIT this loop isn't an issue due to the generic specialization we do, allowing us to get it down to only the code path that is actually used. I believe this isn't possible for Mono today and is non-trivial to add, so the skipping of inlining does make sense in that regard.

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without disabling aggressive inlining I ended up with methods that where huge, and not doing aggressive inline on these types (but still honor inline size limits) made them sane again. I will need to re-iterate around that change in order to tell exactly what happened with our inliner in the aggressive case.

@lateralusXlateralusXJan 22, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just validated this, with disabling the aggressive inlining as above a .net9 full AOT of S.P.C takes ~1.7 GB of memory, and not doing this will consume an additional 600 MB of memory, so I believe its worth preventing aggressive inlining for these types that are not hardware accelerated on any of the Mono supported platforms. I didn't add V64 since it seems to be handled a little differently on at least ARM case. I won't have bandwidth at the moment to do more deep analysis around why aggressive inlining of these template types cause that large increase in memory so I think doing this change, at least short term is worth it, we could probably file an issue around the bloat of Vector2561 and Vector5121, but maybe the better long term solution is to actually implement intrinsic support for these types.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For interpreter I'm handling this in a general fashion by detecting early dead pieces of code and not inlining any of the calls there. #97514. It is possible that a similar approach for jit can produce further improvements without having to special case classes.

@lateralusXlateralusXJan 25, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try to experiment with some dead code elimination, but that cause issues and doesn't work with our llvm codegen, so we explicitly turn that pass off for code that will be passed over to llvm. Instead I made sure we could do more in linker, but still these methods still explode and survives first simple llvm optimizations pass we now do in function manager pass, so feels like something in the inlined code prevents elimination until very late in the opt chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to reenable branch optimizations when using llvm in this pr:
#97189

@steveisok

Copy link
Copy Markdown
Member

System.Collections.Concurrent failed to AOT in a couple of suites. Not sure why, but here's the log https://helixre107v0xdcypoyl9e7f.blob.core.windows.net/dotnet-runtime-refs-pull-97096-merge-21c2561d7c2b483faf/Invariant.Tests/1/console.3bf63a3a.log?helixlogtype=result

Comment threadsrc/mono/mono/mini/aot-runtime.c Outdated
Comment threadsrc/mono/mono/mini/exceptions-amd64.c Outdated
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>.
@lewing

Copy link
Copy Markdown
Member

Hopfully I resolved the conflicts correctly, someone should review.

vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 4e885b1 to 65f6f9cCompareFebruary 9, 2024 04:16
vargaz added a commit that referenced this pull request Feb 9, 2024
…#98151)
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 65f6f9c to aa39dffCompareFebruary 9, 2024 05:29
vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
vargaz added a commit that referenced this pull request Feb 9, 2024
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes
a large amount of memory (up to 6 GB). This is mainly due to generated
LLVM module not being optimized at all while kept in memory during
full module generation. Mono x64 also lacks support for several
intrinsics as well as Vector 256/512 that in turn leads to massive
inlining of intrinsics functions generating a very large LLVM module,
where majority of this code ends up as dead code due to
IsSupported/IsHardwareAccelerated returning false.
The follow commit adjusts several things that will bring down the
memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows
from 6 GB down to ~750 MB.
* Use PSNE implementations on intrinsics not supported on Mono.
* Add ILLinker substitutions for intrinsics not supported on Mono. Enables
ILLinker to do dead code elimination, reduce code to AOT compile.
* Prevent aggressive inlining for a couple of unsupported intrinsics types
making sure we don't end up with excessive inlining, exploding code size.
* Run a couple of LLVM optimization passes on each generated method doing
early code simplification and dead code elimination during LLVM module
generation.
* Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics
implementation for all unsupported Mono x64 SIMD intrinsics.
* Fixed numerous memory leaks in Mono AOT cross compiler code.
* Fix a couple of sequence points free after use errors.
* Fix an anonymous struct build warning triggering build error for
LLVM enabled cross compiler on Windows.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from aa39dff to cfaf8d9CompareFebruary 9, 2024 08:46
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from ea8a484 to 5ab0d94CompareFebruary 9, 2024 09:06
@vargaz

Copy link
Copy Markdown
Contributor

Failures are unrelated.

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d)
image.

Good job everyone!

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d) image.

Good job everyone!

Edit. after the subsequent fix to this PR (#98515), the package size improvements on iOS HelloWorld are mostly gone (i.e., back to original values).

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

12 participants

@lateralusX@vargaz@lambdageek@steveisok@vitek-karas@lewing@matouskozak@kg@marek-safar@BrzVlad@tannergooding@fanyang-mono
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint. - #97096

Merged
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use
Feb 9, 2024
Merged

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint.#97096
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes a large amount of memory (up to 6 GB). This is mainly due to generated LLVM module not being optimized at all while kept in memory during full module generation. Mono x64 also lacks support for several intrinsics as well as Vector 256/512 that in turn leads to massive inlining of intrinsics functions generating a very large LLVM module, where majority of this code ends up as dead code due to IsSupported/IsHardwareAccelerated returning false.

The follow commit adjusts several things that will bring down the memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows from 6 GB down to ~750 MB.

  • Use PSNE implementations on intrinsics not supported on Mono.
  • Add ILLinker substitutions for intrinsics not supported on Mono. Enables ILLinker to do dead code elimination, reduce code to AOT compile.
  • Prevent aggressive inlining for a couple of unsupported intrinsics types making sure we don't end up with excessive inlining, exploding code size.
  • Run a couple of LLVM optimization passes on each generated method doing early code simplification and dead code elimination during LLVM module generation.
  • Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics implementation for all unsupported Mono x64 SIMD intrinsics.
  • Fixed numerous memory leaks in Mono AOT cross compiler code.
  • Fix a couple of sequence points free after use errors.
  • Fix an anonymous struct build warning triggering build error for LLVM enabled cross compiler on Windows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true for all codegens (e.g. interpreter)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like interpreter mark all vectors as not being hardware accelerated:

} else if (in_corlib &&
(!strncmp ("System.Runtime.Intrinsics", klass_name_space, 25) &&
!strncmp ("Vector", klass_name, 6) &&
!strcmp (tm, "get_IsHardwareAccelerated"))) {
*op = MINT_LDC_I4_0;
}

so for that case it should be ok.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @kg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mark v128 as hardware accelerated in interp in some cases, I believe.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so then this should be OK since it only affects v256/v512.

Comment threadsrc/mono/mono/mini/aot-compiler.c Outdated
@vargaz

Copy link
Copy Markdown
Contributor

Wouldn't be better to split this into smaller PRs ?

@lambdageek

Copy link
Copy Markdown
Member

I wonder if we can make working with symbols a little more typesafe so that we have some distinction between a mempool allocated symbol and a temporary malloc-allocated symbol. Maybe we can just pass around a GString for the temporary ones?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For RyuJIT, we have a fallback that handles any unrecognized get_IsHardwareAccelerated and get_IsSupported APIs for System.Numerics, System.Runtime.Intrinsics, and System.Runtime.Intrinsics.* to ensure they can be treated as constant false. Each namespace is being handled a little bit differently since get_IsSupported for some namespaces needs to fallback to user-code instead.

Would it be a good idea to similarly make this general-purpose. Notably vector_size == 64 should also be false on x86/x64 for example?

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a common fallback for IsHardwareAccelerted in instrinsics.c:

/* Fallback if SIMD is disabled */
if (in_corlib && ((!strcmp ("System.Numerics", cmethod_klass_name_space) && !strcmp ("Vector", cmethod_klass_name)) || !strncmp ("System.Runtime.Intrinsics", cmethod_klass_name_space, 25))) {
if (!strcmp (cmethod->name, "get_IsHardwareAccelerated")) {
EMIT_NEW_ICONST (cfg, ins, 0);
ins->type = STACK_I4;
return ins;
}
}

So I guess we should be able to drop that change (just made it explicitly for better visibility in this PR) and rely on that fallback to end up with the same result. Not sure why 64-bit vector size was not included in the past.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this change to rely on fallback for get_IsHardwareAccelerated.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Vector64?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a similar line for System.Runtime.Intrinsics.Arm and System.Runtime.Intrinsics.Wasm missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or general purpose code handling any unrecognized System.Runtime.Intrinsics.* namespace?)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled elsewhere in that source file.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will find Arm and Wasm under respective defines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they not need a path to ensure that IsSupported returns constant false and the intrinsics directly generate a PNSE exception, rather than hitting the recursive fallback, or is that handled elsewhere as well?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly focused on x86 in this PR, other code will still go through the fallbacks, but could probably be enhanced as well.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment on lines 6218 to 6155

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why this is needed on Mono a bit?

The software fallback for V128/V256/V512 is currently implemented as doing 2x operations on the lower/upper halves (except for a couple of methods like Shuffle which operate on the full vector). So V512 is implemented as 2x V256, V256 is implemented as 2x V128, and V128 is implemented as 2x V64. V64 is then implemented as a loop over the scalar elements with potentially large amounts of generic code.

So I would imagine that on any platform where V128 is supported, that the codegen for V256/V512 should be generally nice/small, even if aggressively inlined and with no real dead code elimination required. Even in the case where you have an unsupported type like V256<Guid>, the first V128 operation should have a PNSE thrown (since whether a type is supported or not is typically a known constant).

So I'd only expect that this is needed for V64 on x86/x64 where there is no acceleration and it has to hit the scalar fallback with the loop over the elements. For RyuJIT this loop isn't an issue due to the generic specialization we do, allowing us to get it down to only the code path that is actually used. I believe this isn't possible for Mono today and is non-trivial to add, so the skipping of inlining does make sense in that regard.

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without disabling aggressive inlining I ended up with methods that where huge, and not doing aggressive inline on these types (but still honor inline size limits) made them sane again. I will need to re-iterate around that change in order to tell exactly what happened with our inliner in the aggressive case.

@lateralusXlateralusXJan 22, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just validated this, with disabling the aggressive inlining as above a .net9 full AOT of S.P.C takes ~1.7 GB of memory, and not doing this will consume an additional 600 MB of memory, so I believe its worth preventing aggressive inlining for these types that are not hardware accelerated on any of the Mono supported platforms. I didn't add V64 since it seems to be handled a little differently on at least ARM case. I won't have bandwidth at the moment to do more deep analysis around why aggressive inlining of these template types cause that large increase in memory so I think doing this change, at least short term is worth it, we could probably file an issue around the bloat of Vector2561 and Vector5121, but maybe the better long term solution is to actually implement intrinsic support for these types.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For interpreter I'm handling this in a general fashion by detecting early dead pieces of code and not inlining any of the calls there. #97514. It is possible that a similar approach for jit can produce further improvements without having to special case classes.

@lateralusXlateralusXJan 25, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try to experiment with some dead code elimination, but that cause issues and doesn't work with our llvm codegen, so we explicitly turn that pass off for code that will be passed over to llvm. Instead I made sure we could do more in linker, but still these methods still explode and survives first simple llvm optimizations pass we now do in function manager pass, so feels like something in the inlined code prevents elimination until very late in the opt chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to reenable branch optimizations when using llvm in this pr:
#97189

@steveisok

Copy link
Copy Markdown
Member

System.Collections.Concurrent failed to AOT in a couple of suites. Not sure why, but here's the log https://helixre107v0xdcypoyl9e7f.blob.core.windows.net/dotnet-runtime-refs-pull-97096-merge-21c2561d7c2b483faf/Invariant.Tests/1/console.3bf63a3a.log?helixlogtype=result

Comment threadsrc/mono/mono/mini/aot-runtime.c Outdated
Comment threadsrc/mono/mono/mini/exceptions-amd64.c Outdated
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>.
@lewing

Copy link
Copy Markdown
Member

Hopfully I resolved the conflicts correctly, someone should review.

vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 4e885b1 to 65f6f9cCompareFebruary 9, 2024 04:16
vargaz added a commit that referenced this pull request Feb 9, 2024
…#98151)
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 65f6f9c to aa39dffCompareFebruary 9, 2024 05:29
vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
vargaz added a commit that referenced this pull request Feb 9, 2024
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes
a large amount of memory (up to 6 GB). This is mainly due to generated
LLVM module not being optimized at all while kept in memory during
full module generation. Mono x64 also lacks support for several
intrinsics as well as Vector 256/512 that in turn leads to massive
inlining of intrinsics functions generating a very large LLVM module,
where majority of this code ends up as dead code due to
IsSupported/IsHardwareAccelerated returning false.
The follow commit adjusts several things that will bring down the
memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows
from 6 GB down to ~750 MB.
* Use PSNE implementations on intrinsics not supported on Mono.
* Add ILLinker substitutions for intrinsics not supported on Mono. Enables
ILLinker to do dead code elimination, reduce code to AOT compile.
* Prevent aggressive inlining for a couple of unsupported intrinsics types
making sure we don't end up with excessive inlining, exploding code size.
* Run a couple of LLVM optimization passes on each generated method doing
early code simplification and dead code elimination during LLVM module
generation.
* Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics
implementation for all unsupported Mono x64 SIMD intrinsics.
* Fixed numerous memory leaks in Mono AOT cross compiler code.
* Fix a couple of sequence points free after use errors.
* Fix an anonymous struct build warning triggering build error for
LLVM enabled cross compiler on Windows.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from aa39dff to cfaf8d9CompareFebruary 9, 2024 08:46
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from ea8a484 to 5ab0d94CompareFebruary 9, 2024 09:06
@vargaz

Copy link
Copy Markdown
Contributor

Failures are unrelated.

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d)
image.

Good job everyone!

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d) image.

Good job everyone!

Edit. after the subsequent fix to this PR (#98515), the package size improvements on iOS HelloWorld are mostly gone (i.e., back to original values).

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

12 participants

@lateralusX@vargaz@lambdageek@steveisok@vitek-karas@lewing@matouskozak@kg@marek-safar@BrzVlad@tannergooding@fanyang-mono
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint. - #97096

Merged
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use
Feb 9, 2024
Merged

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint.#97096
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes a large amount of memory (up to 6 GB). This is mainly due to generated LLVM module not being optimized at all while kept in memory during full module generation. Mono x64 also lacks support for several intrinsics as well as Vector 256/512 that in turn leads to massive inlining of intrinsics functions generating a very large LLVM module, where majority of this code ends up as dead code due to IsSupported/IsHardwareAccelerated returning false.

The follow commit adjusts several things that will bring down the memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows from 6 GB down to ~750 MB.

  • Use PSNE implementations on intrinsics not supported on Mono.
  • Add ILLinker substitutions for intrinsics not supported on Mono. Enables ILLinker to do dead code elimination, reduce code to AOT compile.
  • Prevent aggressive inlining for a couple of unsupported intrinsics types making sure we don't end up with excessive inlining, exploding code size.
  • Run a couple of LLVM optimization passes on each generated method doing early code simplification and dead code elimination during LLVM module generation.
  • Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics implementation for all unsupported Mono x64 SIMD intrinsics.
  • Fixed numerous memory leaks in Mono AOT cross compiler code.
  • Fix a couple of sequence points free after use errors.
  • Fix an anonymous struct build warning triggering build error for LLVM enabled cross compiler on Windows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true for all codegens (e.g. interpreter)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like interpreter mark all vectors as not being hardware accelerated:

} else if (in_corlib &&
(!strncmp ("System.Runtime.Intrinsics", klass_name_space, 25) &&
!strncmp ("Vector", klass_name, 6) &&
!strcmp (tm, "get_IsHardwareAccelerated"))) {
*op = MINT_LDC_I4_0;
}

so for that case it should be ok.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @kg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mark v128 as hardware accelerated in interp in some cases, I believe.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so then this should be OK since it only affects v256/v512.

Comment threadsrc/mono/mono/mini/aot-compiler.c Outdated
@vargaz

Copy link
Copy Markdown
Contributor

Wouldn't be better to split this into smaller PRs ?

@lambdageek

Copy link
Copy Markdown
Member

I wonder if we can make working with symbols a little more typesafe so that we have some distinction between a mempool allocated symbol and a temporary malloc-allocated symbol. Maybe we can just pass around a GString for the temporary ones?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For RyuJIT, we have a fallback that handles any unrecognized get_IsHardwareAccelerated and get_IsSupported APIs for System.Numerics, System.Runtime.Intrinsics, and System.Runtime.Intrinsics.* to ensure they can be treated as constant false. Each namespace is being handled a little bit differently since get_IsSupported for some namespaces needs to fallback to user-code instead.

Would it be a good idea to similarly make this general-purpose. Notably vector_size == 64 should also be false on x86/x64 for example?

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a common fallback for IsHardwareAccelerted in instrinsics.c:

/* Fallback if SIMD is disabled */
if (in_corlib && ((!strcmp ("System.Numerics", cmethod_klass_name_space) && !strcmp ("Vector", cmethod_klass_name)) || !strncmp ("System.Runtime.Intrinsics", cmethod_klass_name_space, 25))) {
if (!strcmp (cmethod->name, "get_IsHardwareAccelerated")) {
EMIT_NEW_ICONST (cfg, ins, 0);
ins->type = STACK_I4;
return ins;
}
}

So I guess we should be able to drop that change (just made it explicitly for better visibility in this PR) and rely on that fallback to end up with the same result. Not sure why 64-bit vector size was not included in the past.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this change to rely on fallback for get_IsHardwareAccelerated.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Vector64?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a similar line for System.Runtime.Intrinsics.Arm and System.Runtime.Intrinsics.Wasm missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or general purpose code handling any unrecognized System.Runtime.Intrinsics.* namespace?)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled elsewhere in that source file.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will find Arm and Wasm under respective defines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they not need a path to ensure that IsSupported returns constant false and the intrinsics directly generate a PNSE exception, rather than hitting the recursive fallback, or is that handled elsewhere as well?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly focused on x86 in this PR, other code will still go through the fallbacks, but could probably be enhanced as well.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment on lines 6218 to 6155

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why this is needed on Mono a bit?

The software fallback for V128/V256/V512 is currently implemented as doing 2x operations on the lower/upper halves (except for a couple of methods like Shuffle which operate on the full vector). So V512 is implemented as 2x V256, V256 is implemented as 2x V128, and V128 is implemented as 2x V64. V64 is then implemented as a loop over the scalar elements with potentially large amounts of generic code.

So I would imagine that on any platform where V128 is supported, that the codegen for V256/V512 should be generally nice/small, even if aggressively inlined and with no real dead code elimination required. Even in the case where you have an unsupported type like V256<Guid>, the first V128 operation should have a PNSE thrown (since whether a type is supported or not is typically a known constant).

So I'd only expect that this is needed for V64 on x86/x64 where there is no acceleration and it has to hit the scalar fallback with the loop over the elements. For RyuJIT this loop isn't an issue due to the generic specialization we do, allowing us to get it down to only the code path that is actually used. I believe this isn't possible for Mono today and is non-trivial to add, so the skipping of inlining does make sense in that regard.

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without disabling aggressive inlining I ended up with methods that where huge, and not doing aggressive inline on these types (but still honor inline size limits) made them sane again. I will need to re-iterate around that change in order to tell exactly what happened with our inliner in the aggressive case.

@lateralusXlateralusXJan 22, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just validated this, with disabling the aggressive inlining as above a .net9 full AOT of S.P.C takes ~1.7 GB of memory, and not doing this will consume an additional 600 MB of memory, so I believe its worth preventing aggressive inlining for these types that are not hardware accelerated on any of the Mono supported platforms. I didn't add V64 since it seems to be handled a little differently on at least ARM case. I won't have bandwidth at the moment to do more deep analysis around why aggressive inlining of these template types cause that large increase in memory so I think doing this change, at least short term is worth it, we could probably file an issue around the bloat of Vector2561 and Vector5121, but maybe the better long term solution is to actually implement intrinsic support for these types.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For interpreter I'm handling this in a general fashion by detecting early dead pieces of code and not inlining any of the calls there. #97514. It is possible that a similar approach for jit can produce further improvements without having to special case classes.

@lateralusXlateralusXJan 25, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try to experiment with some dead code elimination, but that cause issues and doesn't work with our llvm codegen, so we explicitly turn that pass off for code that will be passed over to llvm. Instead I made sure we could do more in linker, but still these methods still explode and survives first simple llvm optimizations pass we now do in function manager pass, so feels like something in the inlined code prevents elimination until very late in the opt chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to reenable branch optimizations when using llvm in this pr:
#97189

@steveisok

Copy link
Copy Markdown
Member

System.Collections.Concurrent failed to AOT in a couple of suites. Not sure why, but here's the log https://helixre107v0xdcypoyl9e7f.blob.core.windows.net/dotnet-runtime-refs-pull-97096-merge-21c2561d7c2b483faf/Invariant.Tests/1/console.3bf63a3a.log?helixlogtype=result

Comment threadsrc/mono/mono/mini/aot-runtime.c Outdated
Comment threadsrc/mono/mono/mini/exceptions-amd64.c Outdated
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>.
@lewing

Copy link
Copy Markdown
Member

Hopfully I resolved the conflicts correctly, someone should review.

vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 4e885b1 to 65f6f9cCompareFebruary 9, 2024 04:16
vargaz added a commit that referenced this pull request Feb 9, 2024
…#98151)
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 65f6f9c to aa39dffCompareFebruary 9, 2024 05:29
vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
vargaz added a commit that referenced this pull request Feb 9, 2024
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes
a large amount of memory (up to 6 GB). This is mainly due to generated
LLVM module not being optimized at all while kept in memory during
full module generation. Mono x64 also lacks support for several
intrinsics as well as Vector 256/512 that in turn leads to massive
inlining of intrinsics functions generating a very large LLVM module,
where majority of this code ends up as dead code due to
IsSupported/IsHardwareAccelerated returning false.
The follow commit adjusts several things that will bring down the
memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows
from 6 GB down to ~750 MB.
* Use PSNE implementations on intrinsics not supported on Mono.
* Add ILLinker substitutions for intrinsics not supported on Mono. Enables
ILLinker to do dead code elimination, reduce code to AOT compile.
* Prevent aggressive inlining for a couple of unsupported intrinsics types
making sure we don't end up with excessive inlining, exploding code size.
* Run a couple of LLVM optimization passes on each generated method doing
early code simplification and dead code elimination during LLVM module
generation.
* Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics
implementation for all unsupported Mono x64 SIMD intrinsics.
* Fixed numerous memory leaks in Mono AOT cross compiler code.
* Fix a couple of sequence points free after use errors.
* Fix an anonymous struct build warning triggering build error for
LLVM enabled cross compiler on Windows.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from aa39dff to cfaf8d9CompareFebruary 9, 2024 08:46
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from ea8a484 to 5ab0d94CompareFebruary 9, 2024 09:06
@vargaz

Copy link
Copy Markdown
Contributor

Failures are unrelated.

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d)
image.

Good job everyone!

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d) image.

Good job everyone!

Edit. after the subsequent fix to this PR (#98515), the package size improvements on iOS HelloWorld are mostly gone (i.e., back to original values).

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

12 participants

@lateralusX@vargaz@lambdageek@steveisok@vitek-karas@lewing@matouskozak@kg@marek-safar@BrzVlad@tannergooding@fanyang-mono
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint. - #97096

Merged
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use
Feb 9, 2024
Merged

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint.#97096
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes a large amount of memory (up to 6 GB). This is mainly due to generated LLVM module not being optimized at all while kept in memory during full module generation. Mono x64 also lacks support for several intrinsics as well as Vector 256/512 that in turn leads to massive inlining of intrinsics functions generating a very large LLVM module, where majority of this code ends up as dead code due to IsSupported/IsHardwareAccelerated returning false.

The follow commit adjusts several things that will bring down the memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows from 6 GB down to ~750 MB.

  • Use PSNE implementations on intrinsics not supported on Mono.
  • Add ILLinker substitutions for intrinsics not supported on Mono. Enables ILLinker to do dead code elimination, reduce code to AOT compile.
  • Prevent aggressive inlining for a couple of unsupported intrinsics types making sure we don't end up with excessive inlining, exploding code size.
  • Run a couple of LLVM optimization passes on each generated method doing early code simplification and dead code elimination during LLVM module generation.
  • Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics implementation for all unsupported Mono x64 SIMD intrinsics.
  • Fixed numerous memory leaks in Mono AOT cross compiler code.
  • Fix a couple of sequence points free after use errors.
  • Fix an anonymous struct build warning triggering build error for LLVM enabled cross compiler on Windows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true for all codegens (e.g. interpreter)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like interpreter mark all vectors as not being hardware accelerated:

} else if (in_corlib &&
(!strncmp ("System.Runtime.Intrinsics", klass_name_space, 25) &&
!strncmp ("Vector", klass_name, 6) &&
!strcmp (tm, "get_IsHardwareAccelerated"))) {
*op = MINT_LDC_I4_0;
}

so for that case it should be ok.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @kg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mark v128 as hardware accelerated in interp in some cases, I believe.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so then this should be OK since it only affects v256/v512.

Comment threadsrc/mono/mono/mini/aot-compiler.c Outdated
@vargaz

Copy link
Copy Markdown
Contributor

Wouldn't be better to split this into smaller PRs ?

@lambdageek

Copy link
Copy Markdown
Member

I wonder if we can make working with symbols a little more typesafe so that we have some distinction between a mempool allocated symbol and a temporary malloc-allocated symbol. Maybe we can just pass around a GString for the temporary ones?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For RyuJIT, we have a fallback that handles any unrecognized get_IsHardwareAccelerated and get_IsSupported APIs for System.Numerics, System.Runtime.Intrinsics, and System.Runtime.Intrinsics.* to ensure they can be treated as constant false. Each namespace is being handled a little bit differently since get_IsSupported for some namespaces needs to fallback to user-code instead.

Would it be a good idea to similarly make this general-purpose. Notably vector_size == 64 should also be false on x86/x64 for example?

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a common fallback for IsHardwareAccelerted in instrinsics.c:

/* Fallback if SIMD is disabled */
if (in_corlib && ((!strcmp ("System.Numerics", cmethod_klass_name_space) && !strcmp ("Vector", cmethod_klass_name)) || !strncmp ("System.Runtime.Intrinsics", cmethod_klass_name_space, 25))) {
if (!strcmp (cmethod->name, "get_IsHardwareAccelerated")) {
EMIT_NEW_ICONST (cfg, ins, 0);
ins->type = STACK_I4;
return ins;
}
}

So I guess we should be able to drop that change (just made it explicitly for better visibility in this PR) and rely on that fallback to end up with the same result. Not sure why 64-bit vector size was not included in the past.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this change to rely on fallback for get_IsHardwareAccelerated.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Vector64?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a similar line for System.Runtime.Intrinsics.Arm and System.Runtime.Intrinsics.Wasm missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or general purpose code handling any unrecognized System.Runtime.Intrinsics.* namespace?)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled elsewhere in that source file.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will find Arm and Wasm under respective defines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they not need a path to ensure that IsSupported returns constant false and the intrinsics directly generate a PNSE exception, rather than hitting the recursive fallback, or is that handled elsewhere as well?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly focused on x86 in this PR, other code will still go through the fallbacks, but could probably be enhanced as well.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment on lines 6218 to 6155

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why this is needed on Mono a bit?

The software fallback for V128/V256/V512 is currently implemented as doing 2x operations on the lower/upper halves (except for a couple of methods like Shuffle which operate on the full vector). So V512 is implemented as 2x V256, V256 is implemented as 2x V128, and V128 is implemented as 2x V64. V64 is then implemented as a loop over the scalar elements with potentially large amounts of generic code.

So I would imagine that on any platform where V128 is supported, that the codegen for V256/V512 should be generally nice/small, even if aggressively inlined and with no real dead code elimination required. Even in the case where you have an unsupported type like V256<Guid>, the first V128 operation should have a PNSE thrown (since whether a type is supported or not is typically a known constant).

So I'd only expect that this is needed for V64 on x86/x64 where there is no acceleration and it has to hit the scalar fallback with the loop over the elements. For RyuJIT this loop isn't an issue due to the generic specialization we do, allowing us to get it down to only the code path that is actually used. I believe this isn't possible for Mono today and is non-trivial to add, so the skipping of inlining does make sense in that regard.

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without disabling aggressive inlining I ended up with methods that where huge, and not doing aggressive inline on these types (but still honor inline size limits) made them sane again. I will need to re-iterate around that change in order to tell exactly what happened with our inliner in the aggressive case.

@lateralusXlateralusXJan 22, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just validated this, with disabling the aggressive inlining as above a .net9 full AOT of S.P.C takes ~1.7 GB of memory, and not doing this will consume an additional 600 MB of memory, so I believe its worth preventing aggressive inlining for these types that are not hardware accelerated on any of the Mono supported platforms. I didn't add V64 since it seems to be handled a little differently on at least ARM case. I won't have bandwidth at the moment to do more deep analysis around why aggressive inlining of these template types cause that large increase in memory so I think doing this change, at least short term is worth it, we could probably file an issue around the bloat of Vector2561 and Vector5121, but maybe the better long term solution is to actually implement intrinsic support for these types.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For interpreter I'm handling this in a general fashion by detecting early dead pieces of code and not inlining any of the calls there. #97514. It is possible that a similar approach for jit can produce further improvements without having to special case classes.

@lateralusXlateralusXJan 25, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try to experiment with some dead code elimination, but that cause issues and doesn't work with our llvm codegen, so we explicitly turn that pass off for code that will be passed over to llvm. Instead I made sure we could do more in linker, but still these methods still explode and survives first simple llvm optimizations pass we now do in function manager pass, so feels like something in the inlined code prevents elimination until very late in the opt chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to reenable branch optimizations when using llvm in this pr:
#97189

@steveisok

Copy link
Copy Markdown
Member

System.Collections.Concurrent failed to AOT in a couple of suites. Not sure why, but here's the log https://helixre107v0xdcypoyl9e7f.blob.core.windows.net/dotnet-runtime-refs-pull-97096-merge-21c2561d7c2b483faf/Invariant.Tests/1/console.3bf63a3a.log?helixlogtype=result

Comment threadsrc/mono/mono/mini/aot-runtime.c Outdated
Comment threadsrc/mono/mono/mini/exceptions-amd64.c Outdated
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>.
@lewing

Copy link
Copy Markdown
Member

Hopfully I resolved the conflicts correctly, someone should review.

vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 4e885b1 to 65f6f9cCompareFebruary 9, 2024 04:16
vargaz added a commit that referenced this pull request Feb 9, 2024
…#98151)
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 65f6f9c to aa39dffCompareFebruary 9, 2024 05:29
vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
vargaz added a commit that referenced this pull request Feb 9, 2024
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes
a large amount of memory (up to 6 GB). This is mainly due to generated
LLVM module not being optimized at all while kept in memory during
full module generation. Mono x64 also lacks support for several
intrinsics as well as Vector 256/512 that in turn leads to massive
inlining of intrinsics functions generating a very large LLVM module,
where majority of this code ends up as dead code due to
IsSupported/IsHardwareAccelerated returning false.
The follow commit adjusts several things that will bring down the
memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows
from 6 GB down to ~750 MB.
* Use PSNE implementations on intrinsics not supported on Mono.
* Add ILLinker substitutions for intrinsics not supported on Mono. Enables
ILLinker to do dead code elimination, reduce code to AOT compile.
* Prevent aggressive inlining for a couple of unsupported intrinsics types
making sure we don't end up with excessive inlining, exploding code size.
* Run a couple of LLVM optimization passes on each generated method doing
early code simplification and dead code elimination during LLVM module
generation.
* Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics
implementation for all unsupported Mono x64 SIMD intrinsics.
* Fixed numerous memory leaks in Mono AOT cross compiler code.
* Fix a couple of sequence points free after use errors.
* Fix an anonymous struct build warning triggering build error for
LLVM enabled cross compiler on Windows.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from aa39dff to cfaf8d9CompareFebruary 9, 2024 08:46
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from ea8a484 to 5ab0d94CompareFebruary 9, 2024 09:06
@vargaz

Copy link
Copy Markdown
Contributor

Failures are unrelated.

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d)
image.

Good job everyone!

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d) image.

Good job everyone!

Edit. after the subsequent fix to this PR (#98515), the package size improvements on iOS HelloWorld are mostly gone (i.e., back to original values).

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

12 participants

@lateralusX@vargaz@lambdageek@steveisok@vitek-karas@lewing@matouskozak@kg@marek-safar@BrzVlad@tannergooding@fanyang-mono
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint. - #97096

Merged
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use
Feb 9, 2024
Merged

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint.#97096
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes a large amount of memory (up to 6 GB). This is mainly due to generated LLVM module not being optimized at all while kept in memory during full module generation. Mono x64 also lacks support for several intrinsics as well as Vector 256/512 that in turn leads to massive inlining of intrinsics functions generating a very large LLVM module, where majority of this code ends up as dead code due to IsSupported/IsHardwareAccelerated returning false.

The follow commit adjusts several things that will bring down the memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows from 6 GB down to ~750 MB.

  • Use PSNE implementations on intrinsics not supported on Mono.
  • Add ILLinker substitutions for intrinsics not supported on Mono. Enables ILLinker to do dead code elimination, reduce code to AOT compile.
  • Prevent aggressive inlining for a couple of unsupported intrinsics types making sure we don't end up with excessive inlining, exploding code size.
  • Run a couple of LLVM optimization passes on each generated method doing early code simplification and dead code elimination during LLVM module generation.
  • Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics implementation for all unsupported Mono x64 SIMD intrinsics.
  • Fixed numerous memory leaks in Mono AOT cross compiler code.
  • Fix a couple of sequence points free after use errors.
  • Fix an anonymous struct build warning triggering build error for LLVM enabled cross compiler on Windows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true for all codegens (e.g. interpreter)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like interpreter mark all vectors as not being hardware accelerated:

} else if (in_corlib &&
(!strncmp ("System.Runtime.Intrinsics", klass_name_space, 25) &&
!strncmp ("Vector", klass_name, 6) &&
!strcmp (tm, "get_IsHardwareAccelerated"))) {
*op = MINT_LDC_I4_0;
}

so for that case it should be ok.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @kg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mark v128 as hardware accelerated in interp in some cases, I believe.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so then this should be OK since it only affects v256/v512.

Comment threadsrc/mono/mono/mini/aot-compiler.c Outdated
@vargaz

Copy link
Copy Markdown
Contributor

Wouldn't be better to split this into smaller PRs ?

@lambdageek

Copy link
Copy Markdown
Member

I wonder if we can make working with symbols a little more typesafe so that we have some distinction between a mempool allocated symbol and a temporary malloc-allocated symbol. Maybe we can just pass around a GString for the temporary ones?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For RyuJIT, we have a fallback that handles any unrecognized get_IsHardwareAccelerated and get_IsSupported APIs for System.Numerics, System.Runtime.Intrinsics, and System.Runtime.Intrinsics.* to ensure they can be treated as constant false. Each namespace is being handled a little bit differently since get_IsSupported for some namespaces needs to fallback to user-code instead.

Would it be a good idea to similarly make this general-purpose. Notably vector_size == 64 should also be false on x86/x64 for example?

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a common fallback for IsHardwareAccelerted in instrinsics.c:

/* Fallback if SIMD is disabled */
if (in_corlib && ((!strcmp ("System.Numerics", cmethod_klass_name_space) && !strcmp ("Vector", cmethod_klass_name)) || !strncmp ("System.Runtime.Intrinsics", cmethod_klass_name_space, 25))) {
if (!strcmp (cmethod->name, "get_IsHardwareAccelerated")) {
EMIT_NEW_ICONST (cfg, ins, 0);
ins->type = STACK_I4;
return ins;
}
}

So I guess we should be able to drop that change (just made it explicitly for better visibility in this PR) and rely on that fallback to end up with the same result. Not sure why 64-bit vector size was not included in the past.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this change to rely on fallback for get_IsHardwareAccelerated.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Vector64?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a similar line for System.Runtime.Intrinsics.Arm and System.Runtime.Intrinsics.Wasm missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or general purpose code handling any unrecognized System.Runtime.Intrinsics.* namespace?)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled elsewhere in that source file.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will find Arm and Wasm under respective defines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they not need a path to ensure that IsSupported returns constant false and the intrinsics directly generate a PNSE exception, rather than hitting the recursive fallback, or is that handled elsewhere as well?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly focused on x86 in this PR, other code will still go through the fallbacks, but could probably be enhanced as well.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment on lines 6218 to 6155

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why this is needed on Mono a bit?

The software fallback for V128/V256/V512 is currently implemented as doing 2x operations on the lower/upper halves (except for a couple of methods like Shuffle which operate on the full vector). So V512 is implemented as 2x V256, V256 is implemented as 2x V128, and V128 is implemented as 2x V64. V64 is then implemented as a loop over the scalar elements with potentially large amounts of generic code.

So I would imagine that on any platform where V128 is supported, that the codegen for V256/V512 should be generally nice/small, even if aggressively inlined and with no real dead code elimination required. Even in the case where you have an unsupported type like V256<Guid>, the first V128 operation should have a PNSE thrown (since whether a type is supported or not is typically a known constant).

So I'd only expect that this is needed for V64 on x86/x64 where there is no acceleration and it has to hit the scalar fallback with the loop over the elements. For RyuJIT this loop isn't an issue due to the generic specialization we do, allowing us to get it down to only the code path that is actually used. I believe this isn't possible for Mono today and is non-trivial to add, so the skipping of inlining does make sense in that regard.

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without disabling aggressive inlining I ended up with methods that where huge, and not doing aggressive inline on these types (but still honor inline size limits) made them sane again. I will need to re-iterate around that change in order to tell exactly what happened with our inliner in the aggressive case.

@lateralusXlateralusXJan 22, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just validated this, with disabling the aggressive inlining as above a .net9 full AOT of S.P.C takes ~1.7 GB of memory, and not doing this will consume an additional 600 MB of memory, so I believe its worth preventing aggressive inlining for these types that are not hardware accelerated on any of the Mono supported platforms. I didn't add V64 since it seems to be handled a little differently on at least ARM case. I won't have bandwidth at the moment to do more deep analysis around why aggressive inlining of these template types cause that large increase in memory so I think doing this change, at least short term is worth it, we could probably file an issue around the bloat of Vector2561 and Vector5121, but maybe the better long term solution is to actually implement intrinsic support for these types.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For interpreter I'm handling this in a general fashion by detecting early dead pieces of code and not inlining any of the calls there. #97514. It is possible that a similar approach for jit can produce further improvements without having to special case classes.

@lateralusXlateralusXJan 25, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try to experiment with some dead code elimination, but that cause issues and doesn't work with our llvm codegen, so we explicitly turn that pass off for code that will be passed over to llvm. Instead I made sure we could do more in linker, but still these methods still explode and survives first simple llvm optimizations pass we now do in function manager pass, so feels like something in the inlined code prevents elimination until very late in the opt chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to reenable branch optimizations when using llvm in this pr:
#97189

@steveisok

Copy link
Copy Markdown
Member

System.Collections.Concurrent failed to AOT in a couple of suites. Not sure why, but here's the log https://helixre107v0xdcypoyl9e7f.blob.core.windows.net/dotnet-runtime-refs-pull-97096-merge-21c2561d7c2b483faf/Invariant.Tests/1/console.3bf63a3a.log?helixlogtype=result

Comment threadsrc/mono/mono/mini/aot-runtime.c Outdated
Comment threadsrc/mono/mono/mini/exceptions-amd64.c Outdated
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>.
@lewing

Copy link
Copy Markdown
Member

Hopfully I resolved the conflicts correctly, someone should review.

vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 4e885b1 to 65f6f9cCompareFebruary 9, 2024 04:16
vargaz added a commit that referenced this pull request Feb 9, 2024
…#98151)
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 65f6f9c to aa39dffCompareFebruary 9, 2024 05:29
vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
vargaz added a commit that referenced this pull request Feb 9, 2024
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes
a large amount of memory (up to 6 GB). This is mainly due to generated
LLVM module not being optimized at all while kept in memory during
full module generation. Mono x64 also lacks support for several
intrinsics as well as Vector 256/512 that in turn leads to massive
inlining of intrinsics functions generating a very large LLVM module,
where majority of this code ends up as dead code due to
IsSupported/IsHardwareAccelerated returning false.
The follow commit adjusts several things that will bring down the
memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows
from 6 GB down to ~750 MB.
* Use PSNE implementations on intrinsics not supported on Mono.
* Add ILLinker substitutions for intrinsics not supported on Mono. Enables
ILLinker to do dead code elimination, reduce code to AOT compile.
* Prevent aggressive inlining for a couple of unsupported intrinsics types
making sure we don't end up with excessive inlining, exploding code size.
* Run a couple of LLVM optimization passes on each generated method doing
early code simplification and dead code elimination during LLVM module
generation.
* Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics
implementation for all unsupported Mono x64 SIMD intrinsics.
* Fixed numerous memory leaks in Mono AOT cross compiler code.
* Fix a couple of sequence points free after use errors.
* Fix an anonymous struct build warning triggering build error for
LLVM enabled cross compiler on Windows.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from aa39dff to cfaf8d9CompareFebruary 9, 2024 08:46
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from ea8a484 to 5ab0d94CompareFebruary 9, 2024 09:06
@vargaz

Copy link
Copy Markdown
Contributor

Failures are unrelated.

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d)
image.

Good job everyone!

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d) image.

Good job everyone!

Edit. after the subsequent fix to this PR (#98515), the package size improvements on iOS HelloWorld are mostly gone (i.e., back to original values).

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

12 participants

@lateralusX@vargaz@lambdageek@steveisok@vitek-karas@lewing@matouskozak@kg@marek-safar@BrzVlad@tannergooding@fanyang-mono
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint. - #97096

Merged
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use
Feb 9, 2024
Merged

[Mono]: Reduce Mono AOT cross compiler x64 memory footprint.#97096
steveisok merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/reduce-aot-llvm-memory-use

Conversation

@lateralusX

Copy link
Copy Markdown
Member

Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes a large amount of memory (up to 6 GB). This is mainly due to generated LLVM module not being optimized at all while kept in memory during full module generation. Mono x64 also lacks support for several intrinsics as well as Vector 256/512 that in turn leads to massive inlining of intrinsics functions generating a very large LLVM module, where majority of this code ends up as dead code due to IsSupported/IsHardwareAccelerated returning false.

The follow commit adjusts several things that will bring down the memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows from 6 GB down to ~750 MB.

  • Use PSNE implementations on intrinsics not supported on Mono.
  • Add ILLinker substitutions for intrinsics not supported on Mono. Enables ILLinker to do dead code elimination, reduce code to AOT compile.
  • Prevent aggressive inlining for a couple of unsupported intrinsics types making sure we don't end up with excessive inlining, exploding code size.
  • Run a couple of LLVM optimization passes on each generated method doing early code simplification and dead code elimination during LLVM module generation.
  • Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics implementation for all unsupported Mono x64 SIMD intrinsics.
  • Fixed numerous memory leaks in Mono AOT cross compiler code.
  • Fix a couple of sequence points free after use errors.
  • Fix an anonymous struct build warning triggering build error for LLVM enabled cross compiler on Windows.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this true for all codegens (e.g. interpreter)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like interpreter mark all vectors as not being hardware accelerated:

} else if (in_corlib &&
(!strncmp ("System.Runtime.Intrinsics", klass_name_space, 25) &&
!strncmp ("Vector", klass_name, 6) &&
!strcmp (tm, "get_IsHardwareAccelerated"))) {
*op = MINT_LDC_I4_0;
}

so for that case it should be ok.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cc @kg

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We mark v128 as hardware accelerated in interp in some cases, I believe.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, so then this should be OK since it only affects v256/v512.

Comment threadsrc/mono/mono/mini/aot-compiler.c Outdated
@vargaz

Copy link
Copy Markdown
Contributor

Wouldn't be better to split this into smaller PRs ?

@lambdageek

Copy link
Copy Markdown
Member

I wonder if we can make working with symbols a little more typesafe so that we have some distinction between a mempool allocated symbol and a temporary malloc-allocated symbol. Maybe we can just pass around a GString for the temporary ones?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For RyuJIT, we have a fallback that handles any unrecognized get_IsHardwareAccelerated and get_IsSupported APIs for System.Numerics, System.Runtime.Intrinsics, and System.Runtime.Intrinsics.* to ensure they can be treated as constant false. Each namespace is being handled a little bit differently since get_IsSupported for some namespaces needs to fallback to user-code instead.

Would it be a good idea to similarly make this general-purpose. Notably vector_size == 64 should also be false on x86/x64 for example?

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have a common fallback for IsHardwareAccelerted in instrinsics.c:

/* Fallback if SIMD is disabled */
if (in_corlib && ((!strcmp ("System.Numerics", cmethod_klass_name_space) && !strcmp ("Vector", cmethod_klass_name)) || !strncmp ("System.Runtime.Intrinsics", cmethod_klass_name_space, 25))) {
if (!strcmp (cmethod->name, "get_IsHardwareAccelerated")) {
EMIT_NEW_ICONST (cfg, ins, 0);
ins->type = STACK_I4;
return ins;
}
}

So I guess we should be able to drop that change (just made it explicitly for better visibility in this PR) and rely on that fallback to end up with the same result. Not sure why 64-bit vector size was not included in the past.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this change to rely on fallback for get_IsHardwareAccelerated.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about Vector64?

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a similar line for System.Runtime.Intrinsics.Arm and System.Runtime.Intrinsics.Wasm missing?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(or general purpose code handling any unrecognized System.Runtime.Intrinsics.* namespace?)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handled elsewhere in that source file.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You will find Arm and Wasm under respective defines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do they not need a path to ensure that IsSupported returns constant false and the intrinsics directly generate a PNSE exception, rather than hitting the recursive fallback, or is that handled elsewhere as well?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mainly focused on x86 in this PR, other code will still go through the fallbacks, but could probably be enhanced as well.

Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment threadsrc/mono/mono/mini/simd-intrinsics.c Outdated
Comment on lines 6218 to 6155

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you help me understand why this is needed on Mono a bit?

The software fallback for V128/V256/V512 is currently implemented as doing 2x operations on the lower/upper halves (except for a couple of methods like Shuffle which operate on the full vector). So V512 is implemented as 2x V256, V256 is implemented as 2x V128, and V128 is implemented as 2x V64. V64 is then implemented as a loop over the scalar elements with potentially large amounts of generic code.

So I would imagine that on any platform where V128 is supported, that the codegen for V256/V512 should be generally nice/small, even if aggressively inlined and with no real dead code elimination required. Even in the case where you have an unsupported type like V256<Guid>, the first V128 operation should have a PNSE thrown (since whether a type is supported or not is typically a known constant).

So I'd only expect that this is needed for V64 on x86/x64 where there is no acceleration and it has to hit the scalar fallback with the loop over the elements. For RyuJIT this loop isn't an issue due to the generic specialization we do, allowing us to get it down to only the code path that is actually used. I believe this isn't possible for Mono today and is non-trivial to add, so the skipping of inlining does make sense in that regard.

@lateralusXlateralusXJan 17, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So without disabling aggressive inlining I ended up with methods that where huge, and not doing aggressive inline on these types (but still honor inline size limits) made them sane again. I will need to re-iterate around that change in order to tell exactly what happened with our inliner in the aggressive case.

@lateralusXlateralusXJan 22, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just validated this, with disabling the aggressive inlining as above a .net9 full AOT of S.P.C takes ~1.7 GB of memory, and not doing this will consume an additional 600 MB of memory, so I believe its worth preventing aggressive inlining for these types that are not hardware accelerated on any of the Mono supported platforms. I didn't add V64 since it seems to be handled a little differently on at least ARM case. I won't have bandwidth at the moment to do more deep analysis around why aggressive inlining of these template types cause that large increase in memory so I think doing this change, at least short term is worth it, we could probably file an issue around the bloat of Vector2561 and Vector5121, but maybe the better long term solution is to actually implement intrinsic support for these types.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For interpreter I'm handling this in a general fashion by detecting early dead pieces of code and not inlining any of the calls there. #97514. It is possible that a similar approach for jit can produce further improvements without having to special case classes.

@lateralusXlateralusXJan 25, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did try to experiment with some dead code elimination, but that cause issues and doesn't work with our llvm codegen, so we explicitly turn that pass off for code that will be passed over to llvm. Instead I made sure we could do more in linker, but still these methods still explode and survives first simple llvm optimizations pass we now do in function manager pass, so feels like something in the inlined code prevents elimination until very late in the opt chain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to reenable branch optimizations when using llvm in this pr:
#97189

@steveisok

Copy link
Copy Markdown
Member

System.Collections.Concurrent failed to AOT in a couple of suites. Not sure why, but here's the log https://helixre107v0xdcypoyl9e7f.blob.core.windows.net/dotnet-runtime-refs-pull-97096-merge-21c2561d7c2b483faf/Invariant.Tests/1/console.3bf63a3a.log?helixlogtype=result

Comment threadsrc/mono/mono/mini/aot-runtime.c Outdated
Comment threadsrc/mono/mono/mini/exceptions-amd64.c Outdated
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>
vargaz added a commit that referenced this pull request Feb 8, 2024
Extracted from #97096.
Author: Johan Lorensson <lateralusx.github@gmail.com>.
@lewing

Copy link
Copy Markdown
Member

Hopfully I resolved the conflicts correctly, someone should review.

vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 4e885b1 to 65f6f9cCompareFebruary 9, 2024 04:16
vargaz added a commit that referenced this pull request Feb 9, 2024
…#98151)
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from 65f6f9c to aa39dffCompareFebruary 9, 2024 05:29
vargaz added a commit to vargaz/runtime that referenced this pull request Feb 9, 2024
Extracted from dotnet#97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
vargaz added a commit that referenced this pull request Feb 9, 2024
Extracted from #97096.
Author: Johan Lorensson lateralusx.github@gmail.com.
Building .net8 S.P.C using Mono AOT cross compiler in full AOT consumes
a large amount of memory (up to 6 GB). This is mainly due to generated
LLVM module not being optimized at all while kept in memory during
full module generation. Mono x64 also lacks support for several
intrinsics as well as Vector 256/512 that in turn leads to massive
inlining of intrinsics functions generating a very large LLVM module,
where majority of this code ends up as dead code due to
IsSupported/IsHardwareAccelerated returning false.
The follow commit adjusts several things that will bring down the
memory usage, compiling .net8/.net9 Mono S.P.C on x64 Windows
from 6 GB down to ~750 MB.
* Use PSNE implementations on intrinsics not supported on Mono.
* Add ILLinker substitutions for intrinsics not supported on Mono. Enables
ILLinker to do dead code elimination, reduce code to AOT compile.
* Prevent aggressive inlining for a couple of unsupported intrinsics types
making sure we don't end up with excessive inlining, exploding code size.
* Run a couple of LLVM optimization passes on each generated method doing
early code simplification and dead code elimination during LLVM module
generation.
* Explicit SN_get_IsHardwareAccelerated/SN_get_IsSupported intrinsics
implementation for all unsupported Mono x64 SIMD intrinsics.
* Fixed numerous memory leaks in Mono AOT cross compiler code.
* Fix a couple of sequence points free after use errors.
* Fix an anonymous struct build warning triggering build error for
LLVM enabled cross compiler on Windows.
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from aa39dff to cfaf8d9CompareFebruary 9, 2024 08:46
@vargaz
vargazforce-pushed the lateralusX/reduce-aot-llvm-memory-use branch from ea8a484 to 5ab0d94CompareFebruary 9, 2024 09:06
@vargaz

Copy link
Copy Markdown
Contributor

Failures are unrelated.

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d)
image.

Good job everyone!

@matouskozak

Copy link
Copy Markdown
Member

This PR looks to be responsible for ~200kB package size improvement on iOS HelloWorld (range of commits 339443b...a79c62d) image.

Good job everyone!

Edit. after the subsequent fix to this PR (#98515), the package size improvements on iOS HelloWorld are mostly gone (i.e., back to original values).

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

12 participants

@lateralusX@vargaz@lambdageek@steveisok@vitek-karas@lewing@matouskozak@kg@marek-safar@BrzVlad@tannergooding@fanyang-mono