Uh oh!
There was an error while loading. Please reload this page.
NativeAOT: Partially expand static initialization - #83911
Conversation
ghost
commented
Mar 24, 2023
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch, @kunalspathak Issue DetailsCloses #80954 and contributes to #64242 In the JIT world we successfully get rid of static class initializations (either beforefieldinit or normal cctors) naturally or via tiered compilation (Tier1 means that we already initialized eveyrything we needed on hot path in the previous tier). However, it's not the case for AOT. The plan is to help NativeAOT to avoid hitting staticintfield=int.Parse("42");// mimic a complex cctorintTest(){returnfield;}Current codegen on NativeAOT: ; Method Prog:Test():int:thissubrsp,40call CORINFO_HELP_READYTORUN_NONGCSTATIC_BASEmoveax, dword ptr [rax]addrsp,40retExpected codegen: subrsp,40test byte ptr [(reloc)],1 ;; is already initialized?jne SHORT G_M3272_IG04call CORINFO_HELP_READYTORUN_NONGCSTATIC_BASE ;; it's not -- fallbackSHORT G_M3272_IG04:moveax, dword ptr [(reloc)] ;; just access field's valueaddrsp,40retThe implementation in this PR only handles JIT for now just to test it, while I'm trying to figure out how to implement
|
Uh oh!
There was an error while loading. Please reload this page.
It should be compared with |
Managed to get NAOT working locally (not yet pushed), so for staticintfield=int.Parse("42");// mimic a complex cctorintTest(){returnfield;}Was: ; Method Prog:Test():int:thissubrsp,40call CORINFO_HELP_READYTORUN_NONGCSTATIC_BASEmoveax, dword ptr [rax]addrsp,40ret; Total bytes of code 16 Now I get: ; Assembly listing for method Prog:Test():int:thissubrsp,40learax,[(reloc)]cmp dword ptr [rax-08H],0je SHORT G_M3272_IG05G_M3272_IG03:moveax, dword ptr [(reloc)]addrsp,40retG_M3272_IG05:call CORINFO_HELP_READYTORUN_NONGCSTATIC_BASEjmp SHORT G_M3272_IG03; Total bytes of code 35(nongc statics) |
EgorBo
commented
Mar 25, 2023
Same but for JIT: Was: ; Assembly listing for method Prog:Test():int:thisG_M3272_IG01:subrsp,40movrcx,0xD1FFAB1Emovedx,3call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASEmoveax, dword ptr [(reloc)]addrsp,40ret; Total bytes of code 35New: ; Assembly listing for method Prog:Test():int:thissubrsp,40test byte ptr [(reloc)],1je SHORT G_M3272_IG05G_M3272_IG03:moveax, dword ptr [(reloc)]addrsp,40retG_M3272_IG05:movrcx,0xD1FFAB1Emovedx,3call CORINFO_HELP_GETSHARED_NONGCSTATIC_BASEjmp SHORT G_M3272_IG03; Total bytes of code 46Presumably, JIT version can be made 5 bytes smaller if we replace the cold part with "call INITCLASS(cls)". |
GC statics come with a bigger size increase on NAOT: staticstringfield=(42).ToString();[MethodImpl(MethodImplOptions.NoInlining)]stringTest(){returnfield;}Was: ; Method Prog:Test():System.String:thissubrsp,40call CORINFO_HELP_READYTORUN_GCSTATIC_BASEmovrax, gword ptr [rax+08H]addrsp,40ret; Total bytes of code: 18Now: ; Method Prog:Test():System.String:thissubrsp,40learax,[(reloc)] ;; NonGCStaticsBasecmp dword ptr [rax-08H],0je SHORT G_M48517_IG05G_M48517_IG03:movrax, qword ptr [(reloc)] ;; this is a different reloc (GCStaticBase)movrax, gword ptr [rax+08H]addrsp,40retG_M48517_IG05:call CORINFO_HELP_READYTORUN_GCSTATIC_BASEjmp SHORT G_M48517_IG03; Total bytes of code: 40due to double-indirect for field access |
Benchmarks (JIT, TieredCompilation=0): // case 1: normal cctor. JIT doesn't hoist such initializations// from loops (needs loop peeling)publicclassTestClassWithCctor{publicstaticintfield;staticTestClassWithCctor(){field=42;}}[Benchmark]publicintNonHoistableStaticInit(){intsum=0;for(inti=0;i<10000;i++){sum+=TestClassWithCctor.field;}returnsum;}// case 2: beforefieldinitpublicclassTestClassWithBeforefieldinit{publicstaticintfield=42;}[Benchmark]publicintSimpleStaticInit(){returnTestClassWithBeforefieldinit.field;}
codegen diff: https://www.diffchecker.com/87bWA1er/ |
EgorBo
commented
Mar 25, 2023
/azp list |
This comment was marked as outdated.
This comment was marked as outdated.
EgorBo
commented
Mar 25, 2023
/azp run runtime-coreclr outerloop, runtime-extra-platforms, runtime-coreclr jitstress |
|
Azure Pipelines successfully started running 3 pipeline(s). |
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
… expand-static-init
EgorBo
commented
Apr 6, 2023
@kunalspathak@AndyAyersMS@SingleAccretion I've addressed feedback, anything else? |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Co-authored-by: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com>
Co-authored-by: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com>
EgorBo
commented
Apr 6, 2023
@SingleAccretion Thanks! @kunalspathak@AndyAyersMS waiting for a green approve now 🙂 |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
AndyAyersMS
left a comment
There was a problem hiding this comment.
Would it make sense to have some kind of "post phase" check to make sure each possible candidate was either expanded or intentionally not expanded?
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
E.g. for runtime lookups I put an assert in Lower.cpp to make sure all of them are expanded (because it was required). In this case I didn't do that because it's just an optimization and we skip some type of static initializations on JIT already + we skip cold blocks so I didn't do that |
Closes#80954 and contributes to #64242
In the JIT world we successfully get rid of static class initializations (either beforefieldinit or normal cctors) naturally or via tiered compilation (Tier1 means that we already initialized eveyrything we needed on hot path in the previous tier). However, it's not the case for AOT. The plan is to help NativeAOT to avoid hitting
calloverhead in this case by partially inline "is class already initialized?" check with a fast path, e.g.:Current codegen on NativeAOT:
New codegen (NativeAOT):
It also works for JIT (mainly, to test it, but could be useful for TC=0).
Benchmarks
I was using JIT with TieredCompilation=0
codegen diff: https://www.diffchecker.com/87bWA1er/
Can't say yet the total size overhead for a hello world, in the worst case we can leave this optimization only for "prefer speed" mode (-Os)