JIT: track memory loop dependence of trees during value numbering - #55936

Merged
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree
Jul 22, 2021
Merged

JIT: track memory loop dependence of trees during value numbering#55936
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.

First, refactor the mapStore value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.

Next, during VN computation, record loop memory dependence from mapStores
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.

This dependence info is tracked via a side map.

Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.

I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.

Resolves#54118.

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.
First, refactor the `mapStore` value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.
Next, during VN computation, record loop memory dependence from `mapStores`
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.
This dependence info is tracked via a side map.
Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.
I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.
Resolvesdotnet#54118.
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 19, 2021
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@briansull@jakobbotsch PTAL
cc @dotnet/jit-contrib

Passes the more elaborate test case from #54118 (added here).

5 methods with SPMI diffs (not counting the new test). Will say more about them in a follow-up note a bit later today.

benchmarks.run.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 407
Total bytes of diff: 399
Total bytes of delta: -8 (-1.97% of base)
Total relative delta: -0.02
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file improvements (bytes):
-8 : 26074.dasm (-1.97% of base)
1 total files with Code Size differences (1 improved, 0 regressed), 0 unchanged.
Top method improvements (bytes):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
Top method improvements (percentages):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
1 total methods with Code Size differences (1 improved, 0 regressed), 0 unchanged.

coreclr_tests.pmi.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 2649
Total bytes of diff: 2638
Total bytes of delta: -11 (-0.42% of base)
Total relative delta: -0.07
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file regressions (bytes):
3 : 86555.dasm (1.76% of base)
2 : 85392.dasm (0.99% of base)
Top file improvements (bytes):
-8 : 219848.dasm (-1.97% of base)
-5 : 86566.dasm (-6.33% of base)
-3 : 86074.dasm (-1.68% of base)
5 total files with Code Size differences (3 improved, 2 regressed), 1 unchanged.
Top method regressions (bytes):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (bytes):
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
Top method regressions (percentages):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (percentages):
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
5 total methods with Code Size differences (3 improved, 2 regressed), 1 unchanged.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

;; 86566 (source unknown) -- we now hoist load of a static var out of a loop. Since there are no opaque memory accesses / synchronization points in the loop, this seems ok. Similar diffs in 85392.

BeforeAfter
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
align [13 bytes]
G_M57269_IG03:
mov eax, dword ptr [CLASS-VAR]
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd rax, eax
mov esi, dword ptr [rax]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
mov eax, dword ptr [CLASS-VAR]
align [7 bytes]
G_M57269_IG03:
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd r8, eax
mov esi, dword ptr [r8]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03

;; 219848 -- V8.Crypto.RSAKey:pkcs1pad2

We previously moved a read past a write in a fully unrolled loop, now we don't.

BeforeAfter
 movzx r15, byte ptr [rbx+16]
G_M31322_IG07:
mov byte ptr [rbx+16], 0
test r15d, r15d
jne SHORT G_M31322_IG11
 G_M31322_IG07:
mov byte ptr [rbx+16], 0
cmp byte ptr [rbx+16], 0
jne SHORT G_M31322_IG11

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

x86 test failure seems unrelated; looks like an instance of #54469.

tracing\\eventpipe\\reverseouter\\reverseouter\\reverseouter.cmd
7.3s: Assert failure(PID 5424 [0x00001530], Thread: 3364 [0x0d24]): Consistency check failed: AV in clr at this callstack:
7.3s: ------
7.3s: CORECLR! AllocMemTracker::~AllocMemTracker + 0xFE (0x70a3a1d5)
7.3s: CORECLR! IBCLoggerAwareAllocMemTracker::~IBCLoggerAwareAllocMemTracker + 0xDC (0x707b0799)
7.3s: CORECLR! ClassLoader::DoIncrementalLoad + 0x437 (0x70576fda)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey_Body + 0x894 (0x7057fd27)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey + 0x19A (0x7057ef6b)
7.3s: CORECLR! ClassLoader::LoadConstructedTypeThrowing + 0x63D (0x7057bb38)
7.3s: CORECLR! ClassLoader::LoadGenericInstantiationThrowing + 0x4DF (0x7057c4e1)
7.3s: CORECLR! SigPointer::GetTypeHandleThrowing + 0xE92 (0x706aedf5)

Comment threadsrc/coreclr/jit/compiler.h Outdated
// The map provides the entry block of the most closely enclosing loop that
// defines the memory region accessed when defining the nodes's VN.
//
// This information should consulted when considering hoisting node out of a loop, as the VN

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.

should => should be

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
//
bool IsTreeLoopMemoryInvariant(GenTree* tree)
{
if (tree->OperIsIndir() && ((tree->gtFlags & GTF_IND_INVARIANT) != 0))

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.

You could add a comment, stating that we early out returning true for any GT_IND marked as Invariant

@AndyAyersMSAndyAyersMSJul 21, 2021

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.

Actually we probably don't need this bail out as we should be recording that invariant VNs indirs are dependent on an invariant memory state. So I'll likely just delete this and verify we still get the same results.

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
return true;
}

// Todo: other operators that read memory

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.

If we are going to have a ToDo here, then I think that we have a convention on how we write ToDo's

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.

This is a vestige from an earlier version where VN wasn't annotating all trees. But now it does.

So here I think we can just check all trees. However I believe calls are handled specially during hoisting, so we may still need an early bail-out for calls.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib it might be interesting for more of you to look this one over, given our ongoing discussions of VN.

@briansullbriansull left a comment

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.

Looks Good

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Forgot to mention that the revised version has the same set of SPMI diffs as the first version.

@AndyAyersMS
AndyAyersMS merged commit 1b4f786 into dotnet:mainJul 22, 2021
@AndyAyersMS
AndyAyersMS deleted the FixLicmMemoryDependenceAmbientVnTree branch July 22, 2021 01:03
@jakobbotsch

Copy link
Copy Markdown
Member

I just rechecked my examples with newest fixes from master and it looks like there are still some unhandled cases:

// Generated by Fuzzlyn v1.2 on 2021-07-22 13:37:14// Seed: 14815563263006255362// Reduced from 12.9 KiB to 0.4 KiB in 00:00:17// Debug: Outputs 1// Release: Outputs 0publicclassProgram{staticshort[]s_2;publicstaticvoidMain(){byte[]vr7=newbyte[]{0};boolvr11=default(bool);for(intvr9=0;vr9<2;vr9++){if(vr11){s_2[0]=0;}vr7[0]=1;bytevr10=vr7[0];System.Console.WriteLine(vr10);}}}

Looks like they all involve some form of control flow before the pattern.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Thanks, will take a look.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Seems like we need to add similar loop dependence tracking to VNF_PhiDef.

int[] arr = { -1 };
ref int r = ref arr[0];
int val = -1;
for (int i = 0; i < 2; i++)

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.

To test loop-dependent VN, these tests all depend on these loops remaining as loops. But Checked JIT loop unrolling under stress will probably fully unroll them. And what if we change the loop unrolling heuristics? Maybe the upper bound should be make an argument.

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.

See #56184

assert(arg1VN == VNNormalValue(arg1VN));
assert(arg2VN == VNNormalValue(arg2VN));
assert(arg3VN == VNNormalValue(arg3VN));
assert((func == VNF_MapStore) || (arg3VN == VNNormalValue(arg3VN)));

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.

Comment above: "// Note: Currently the only four operand func is the VNF_PtrToArrElem operation" is no longer true

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.

Thanks, fixed in #56184.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 22, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixesdotnet#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in dotnet#55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit that referenced this pull request Jul 27, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixes#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in #55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 27, 2021
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in dotnet#55936 and dotnet#56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of dotnet#54118.
AndyAyersMS added a commit that referenced this pull request Jul 28, 2021
…56436)
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in #55936 and #56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of #54118.
@ghostghost locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid hoisting of indirections proven to be loop invariant

4 participants

@AndyAyersMS@jakobbotsch@BruceForstall@briansull
, '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

JIT: track memory loop dependence of trees during value numbering - #55936

Merged
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree
Jul 22, 2021
Merged

JIT: track memory loop dependence of trees during value numbering#55936
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.

First, refactor the mapStore value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.

Next, during VN computation, record loop memory dependence from mapStores
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.

This dependence info is tracked via a side map.

Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.

I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.

Resolves#54118.

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.
First, refactor the `mapStore` value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.
Next, during VN computation, record loop memory dependence from `mapStores`
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.
This dependence info is tracked via a side map.
Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.
I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.
Resolvesdotnet#54118.
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 19, 2021
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@briansull@jakobbotsch PTAL
cc @dotnet/jit-contrib

Passes the more elaborate test case from #54118 (added here).

5 methods with SPMI diffs (not counting the new test). Will say more about them in a follow-up note a bit later today.

benchmarks.run.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 407
Total bytes of diff: 399
Total bytes of delta: -8 (-1.97% of base)
Total relative delta: -0.02
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file improvements (bytes):
-8 : 26074.dasm (-1.97% of base)
1 total files with Code Size differences (1 improved, 0 regressed), 0 unchanged.
Top method improvements (bytes):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
Top method improvements (percentages):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
1 total methods with Code Size differences (1 improved, 0 regressed), 0 unchanged.

coreclr_tests.pmi.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 2649
Total bytes of diff: 2638
Total bytes of delta: -11 (-0.42% of base)
Total relative delta: -0.07
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file regressions (bytes):
3 : 86555.dasm (1.76% of base)
2 : 85392.dasm (0.99% of base)
Top file improvements (bytes):
-8 : 219848.dasm (-1.97% of base)
-5 : 86566.dasm (-6.33% of base)
-3 : 86074.dasm (-1.68% of base)
5 total files with Code Size differences (3 improved, 2 regressed), 1 unchanged.
Top method regressions (bytes):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (bytes):
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
Top method regressions (percentages):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (percentages):
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
5 total methods with Code Size differences (3 improved, 2 regressed), 1 unchanged.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

;; 86566 (source unknown) -- we now hoist load of a static var out of a loop. Since there are no opaque memory accesses / synchronization points in the loop, this seems ok. Similar diffs in 85392.

BeforeAfter
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
align [13 bytes]
G_M57269_IG03:
mov eax, dword ptr [CLASS-VAR]
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd rax, eax
mov esi, dword ptr [rax]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
mov eax, dword ptr [CLASS-VAR]
align [7 bytes]
G_M57269_IG03:
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd r8, eax
mov esi, dword ptr [r8]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03

;; 219848 -- V8.Crypto.RSAKey:pkcs1pad2

We previously moved a read past a write in a fully unrolled loop, now we don't.

BeforeAfter
 movzx r15, byte ptr [rbx+16]
G_M31322_IG07:
mov byte ptr [rbx+16], 0
test r15d, r15d
jne SHORT G_M31322_IG11
 G_M31322_IG07:
mov byte ptr [rbx+16], 0
cmp byte ptr [rbx+16], 0
jne SHORT G_M31322_IG11

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

x86 test failure seems unrelated; looks like an instance of #54469.

tracing\\eventpipe\\reverseouter\\reverseouter\\reverseouter.cmd
7.3s: Assert failure(PID 5424 [0x00001530], Thread: 3364 [0x0d24]): Consistency check failed: AV in clr at this callstack:
7.3s: ------
7.3s: CORECLR! AllocMemTracker::~AllocMemTracker + 0xFE (0x70a3a1d5)
7.3s: CORECLR! IBCLoggerAwareAllocMemTracker::~IBCLoggerAwareAllocMemTracker + 0xDC (0x707b0799)
7.3s: CORECLR! ClassLoader::DoIncrementalLoad + 0x437 (0x70576fda)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey_Body + 0x894 (0x7057fd27)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey + 0x19A (0x7057ef6b)
7.3s: CORECLR! ClassLoader::LoadConstructedTypeThrowing + 0x63D (0x7057bb38)
7.3s: CORECLR! ClassLoader::LoadGenericInstantiationThrowing + 0x4DF (0x7057c4e1)
7.3s: CORECLR! SigPointer::GetTypeHandleThrowing + 0xE92 (0x706aedf5)

Comment threadsrc/coreclr/jit/compiler.h Outdated
// The map provides the entry block of the most closely enclosing loop that
// defines the memory region accessed when defining the nodes's VN.
//
// This information should consulted when considering hoisting node out of a loop, as the VN

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.

should => should be

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
//
bool IsTreeLoopMemoryInvariant(GenTree* tree)
{
if (tree->OperIsIndir() && ((tree->gtFlags & GTF_IND_INVARIANT) != 0))

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.

You could add a comment, stating that we early out returning true for any GT_IND marked as Invariant

@AndyAyersMSAndyAyersMSJul 21, 2021

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.

Actually we probably don't need this bail out as we should be recording that invariant VNs indirs are dependent on an invariant memory state. So I'll likely just delete this and verify we still get the same results.

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
return true;
}

// Todo: other operators that read memory

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.

If we are going to have a ToDo here, then I think that we have a convention on how we write ToDo's

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.

This is a vestige from an earlier version where VN wasn't annotating all trees. But now it does.

So here I think we can just check all trees. However I believe calls are handled specially during hoisting, so we may still need an early bail-out for calls.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib it might be interesting for more of you to look this one over, given our ongoing discussions of VN.

@briansullbriansull left a comment

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.

Looks Good

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Forgot to mention that the revised version has the same set of SPMI diffs as the first version.

@AndyAyersMS
AndyAyersMS merged commit 1b4f786 into dotnet:mainJul 22, 2021
@AndyAyersMS
AndyAyersMS deleted the FixLicmMemoryDependenceAmbientVnTree branch July 22, 2021 01:03
@jakobbotsch

Copy link
Copy Markdown
Member

I just rechecked my examples with newest fixes from master and it looks like there are still some unhandled cases:

// Generated by Fuzzlyn v1.2 on 2021-07-22 13:37:14// Seed: 14815563263006255362// Reduced from 12.9 KiB to 0.4 KiB in 00:00:17// Debug: Outputs 1// Release: Outputs 0publicclassProgram{staticshort[]s_2;publicstaticvoidMain(){byte[]vr7=newbyte[]{0};boolvr11=default(bool);for(intvr9=0;vr9<2;vr9++){if(vr11){s_2[0]=0;}vr7[0]=1;bytevr10=vr7[0];System.Console.WriteLine(vr10);}}}

Looks like they all involve some form of control flow before the pattern.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Thanks, will take a look.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Seems like we need to add similar loop dependence tracking to VNF_PhiDef.

int[] arr = { -1 };
ref int r = ref arr[0];
int val = -1;
for (int i = 0; i < 2; i++)

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.

To test loop-dependent VN, these tests all depend on these loops remaining as loops. But Checked JIT loop unrolling under stress will probably fully unroll them. And what if we change the loop unrolling heuristics? Maybe the upper bound should be make an argument.

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.

See #56184

assert(arg1VN == VNNormalValue(arg1VN));
assert(arg2VN == VNNormalValue(arg2VN));
assert(arg3VN == VNNormalValue(arg3VN));
assert((func == VNF_MapStore) || (arg3VN == VNNormalValue(arg3VN)));

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.

Comment above: "// Note: Currently the only four operand func is the VNF_PtrToArrElem operation" is no longer true

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.

Thanks, fixed in #56184.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 22, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixesdotnet#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in dotnet#55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit that referenced this pull request Jul 27, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixes#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in #55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 27, 2021
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in dotnet#55936 and dotnet#56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of dotnet#54118.
AndyAyersMS added a commit that referenced this pull request Jul 28, 2021
…56436)
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in #55936 and #56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of #54118.
@ghostghost locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid hoisting of indirections proven to be loop invariant

4 participants

@AndyAyersMS@jakobbotsch@BruceForstall@briansull
, '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

JIT: track memory loop dependence of trees during value numbering - #55936

Merged
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree
Jul 22, 2021
Merged

JIT: track memory loop dependence of trees during value numbering#55936
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.

First, refactor the mapStore value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.

Next, during VN computation, record loop memory dependence from mapStores
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.

This dependence info is tracked via a side map.

Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.

I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.

Resolves#54118.

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.
First, refactor the `mapStore` value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.
Next, during VN computation, record loop memory dependence from `mapStores`
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.
This dependence info is tracked via a side map.
Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.
I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.
Resolvesdotnet#54118.
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 19, 2021
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@briansull@jakobbotsch PTAL
cc @dotnet/jit-contrib

Passes the more elaborate test case from #54118 (added here).

5 methods with SPMI diffs (not counting the new test). Will say more about them in a follow-up note a bit later today.

benchmarks.run.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 407
Total bytes of diff: 399
Total bytes of delta: -8 (-1.97% of base)
Total relative delta: -0.02
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file improvements (bytes):
-8 : 26074.dasm (-1.97% of base)
1 total files with Code Size differences (1 improved, 0 regressed), 0 unchanged.
Top method improvements (bytes):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
Top method improvements (percentages):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
1 total methods with Code Size differences (1 improved, 0 regressed), 0 unchanged.

coreclr_tests.pmi.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 2649
Total bytes of diff: 2638
Total bytes of delta: -11 (-0.42% of base)
Total relative delta: -0.07
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file regressions (bytes):
3 : 86555.dasm (1.76% of base)
2 : 85392.dasm (0.99% of base)
Top file improvements (bytes):
-8 : 219848.dasm (-1.97% of base)
-5 : 86566.dasm (-6.33% of base)
-3 : 86074.dasm (-1.68% of base)
5 total files with Code Size differences (3 improved, 2 regressed), 1 unchanged.
Top method regressions (bytes):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (bytes):
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
Top method regressions (percentages):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (percentages):
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
5 total methods with Code Size differences (3 improved, 2 regressed), 1 unchanged.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

;; 86566 (source unknown) -- we now hoist load of a static var out of a loop. Since there are no opaque memory accesses / synchronization points in the loop, this seems ok. Similar diffs in 85392.

BeforeAfter
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
align [13 bytes]
G_M57269_IG03:
mov eax, dword ptr [CLASS-VAR]
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd rax, eax
mov esi, dword ptr [rax]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
mov eax, dword ptr [CLASS-VAR]
align [7 bytes]
G_M57269_IG03:
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd r8, eax
mov esi, dword ptr [r8]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03

;; 219848 -- V8.Crypto.RSAKey:pkcs1pad2

We previously moved a read past a write in a fully unrolled loop, now we don't.

BeforeAfter
 movzx r15, byte ptr [rbx+16]
G_M31322_IG07:
mov byte ptr [rbx+16], 0
test r15d, r15d
jne SHORT G_M31322_IG11
 G_M31322_IG07:
mov byte ptr [rbx+16], 0
cmp byte ptr [rbx+16], 0
jne SHORT G_M31322_IG11

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

x86 test failure seems unrelated; looks like an instance of #54469.

tracing\\eventpipe\\reverseouter\\reverseouter\\reverseouter.cmd
7.3s: Assert failure(PID 5424 [0x00001530], Thread: 3364 [0x0d24]): Consistency check failed: AV in clr at this callstack:
7.3s: ------
7.3s: CORECLR! AllocMemTracker::~AllocMemTracker + 0xFE (0x70a3a1d5)
7.3s: CORECLR! IBCLoggerAwareAllocMemTracker::~IBCLoggerAwareAllocMemTracker + 0xDC (0x707b0799)
7.3s: CORECLR! ClassLoader::DoIncrementalLoad + 0x437 (0x70576fda)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey_Body + 0x894 (0x7057fd27)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey + 0x19A (0x7057ef6b)
7.3s: CORECLR! ClassLoader::LoadConstructedTypeThrowing + 0x63D (0x7057bb38)
7.3s: CORECLR! ClassLoader::LoadGenericInstantiationThrowing + 0x4DF (0x7057c4e1)
7.3s: CORECLR! SigPointer::GetTypeHandleThrowing + 0xE92 (0x706aedf5)

Comment threadsrc/coreclr/jit/compiler.h Outdated
// The map provides the entry block of the most closely enclosing loop that
// defines the memory region accessed when defining the nodes's VN.
//
// This information should consulted when considering hoisting node out of a loop, as the VN

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.

should => should be

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
//
bool IsTreeLoopMemoryInvariant(GenTree* tree)
{
if (tree->OperIsIndir() && ((tree->gtFlags & GTF_IND_INVARIANT) != 0))

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.

You could add a comment, stating that we early out returning true for any GT_IND marked as Invariant

@AndyAyersMSAndyAyersMSJul 21, 2021

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.

Actually we probably don't need this bail out as we should be recording that invariant VNs indirs are dependent on an invariant memory state. So I'll likely just delete this and verify we still get the same results.

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
return true;
}

// Todo: other operators that read memory

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.

If we are going to have a ToDo here, then I think that we have a convention on how we write ToDo's

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.

This is a vestige from an earlier version where VN wasn't annotating all trees. But now it does.

So here I think we can just check all trees. However I believe calls are handled specially during hoisting, so we may still need an early bail-out for calls.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib it might be interesting for more of you to look this one over, given our ongoing discussions of VN.

@briansullbriansull left a comment

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.

Looks Good

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Forgot to mention that the revised version has the same set of SPMI diffs as the first version.

@AndyAyersMS
AndyAyersMS merged commit 1b4f786 into dotnet:mainJul 22, 2021
@AndyAyersMS
AndyAyersMS deleted the FixLicmMemoryDependenceAmbientVnTree branch July 22, 2021 01:03
@jakobbotsch

Copy link
Copy Markdown
Member

I just rechecked my examples with newest fixes from master and it looks like there are still some unhandled cases:

// Generated by Fuzzlyn v1.2 on 2021-07-22 13:37:14// Seed: 14815563263006255362// Reduced from 12.9 KiB to 0.4 KiB in 00:00:17// Debug: Outputs 1// Release: Outputs 0publicclassProgram{staticshort[]s_2;publicstaticvoidMain(){byte[]vr7=newbyte[]{0};boolvr11=default(bool);for(intvr9=0;vr9<2;vr9++){if(vr11){s_2[0]=0;}vr7[0]=1;bytevr10=vr7[0];System.Console.WriteLine(vr10);}}}

Looks like they all involve some form of control flow before the pattern.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Thanks, will take a look.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Seems like we need to add similar loop dependence tracking to VNF_PhiDef.

int[] arr = { -1 };
ref int r = ref arr[0];
int val = -1;
for (int i = 0; i < 2; i++)

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.

To test loop-dependent VN, these tests all depend on these loops remaining as loops. But Checked JIT loop unrolling under stress will probably fully unroll them. And what if we change the loop unrolling heuristics? Maybe the upper bound should be make an argument.

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.

See #56184

assert(arg1VN == VNNormalValue(arg1VN));
assert(arg2VN == VNNormalValue(arg2VN));
assert(arg3VN == VNNormalValue(arg3VN));
assert((func == VNF_MapStore) || (arg3VN == VNNormalValue(arg3VN)));

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.

Comment above: "// Note: Currently the only four operand func is the VNF_PtrToArrElem operation" is no longer true

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.

Thanks, fixed in #56184.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 22, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixesdotnet#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in dotnet#55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit that referenced this pull request Jul 27, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixes#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in #55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 27, 2021
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in dotnet#55936 and dotnet#56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of dotnet#54118.
AndyAyersMS added a commit that referenced this pull request Jul 28, 2021
…56436)
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in #55936 and #56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of #54118.
@ghostghost locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid hoisting of indirections proven to be loop invariant

4 participants

@AndyAyersMS@jakobbotsch@BruceForstall@briansull
, '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

JIT: track memory loop dependence of trees during value numbering - #55936

Merged
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree
Jul 22, 2021
Merged

JIT: track memory loop dependence of trees during value numbering#55936
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.

First, refactor the mapStore value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.

Next, during VN computation, record loop memory dependence from mapStores
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.

This dependence info is tracked via a side map.

Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.

I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.

Resolves#54118.

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.
First, refactor the `mapStore` value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.
Next, during VN computation, record loop memory dependence from `mapStores`
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.
This dependence info is tracked via a side map.
Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.
I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.
Resolvesdotnet#54118.
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 19, 2021
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@briansull@jakobbotsch PTAL
cc @dotnet/jit-contrib

Passes the more elaborate test case from #54118 (added here).

5 methods with SPMI diffs (not counting the new test). Will say more about them in a follow-up note a bit later today.

benchmarks.run.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 407
Total bytes of diff: 399
Total bytes of delta: -8 (-1.97% of base)
Total relative delta: -0.02
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file improvements (bytes):
-8 : 26074.dasm (-1.97% of base)
1 total files with Code Size differences (1 improved, 0 regressed), 0 unchanged.
Top method improvements (bytes):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
Top method improvements (percentages):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
1 total methods with Code Size differences (1 improved, 0 regressed), 0 unchanged.

coreclr_tests.pmi.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 2649
Total bytes of diff: 2638
Total bytes of delta: -11 (-0.42% of base)
Total relative delta: -0.07
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file regressions (bytes):
3 : 86555.dasm (1.76% of base)
2 : 85392.dasm (0.99% of base)
Top file improvements (bytes):
-8 : 219848.dasm (-1.97% of base)
-5 : 86566.dasm (-6.33% of base)
-3 : 86074.dasm (-1.68% of base)
5 total files with Code Size differences (3 improved, 2 regressed), 1 unchanged.
Top method regressions (bytes):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (bytes):
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
Top method regressions (percentages):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (percentages):
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
5 total methods with Code Size differences (3 improved, 2 regressed), 1 unchanged.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

;; 86566 (source unknown) -- we now hoist load of a static var out of a loop. Since there are no opaque memory accesses / synchronization points in the loop, this seems ok. Similar diffs in 85392.

BeforeAfter
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
align [13 bytes]
G_M57269_IG03:
mov eax, dword ptr [CLASS-VAR]
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd rax, eax
mov esi, dword ptr [rax]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
mov eax, dword ptr [CLASS-VAR]
align [7 bytes]
G_M57269_IG03:
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd r8, eax
mov esi, dword ptr [r8]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03

;; 219848 -- V8.Crypto.RSAKey:pkcs1pad2

We previously moved a read past a write in a fully unrolled loop, now we don't.

BeforeAfter
 movzx r15, byte ptr [rbx+16]
G_M31322_IG07:
mov byte ptr [rbx+16], 0
test r15d, r15d
jne SHORT G_M31322_IG11
 G_M31322_IG07:
mov byte ptr [rbx+16], 0
cmp byte ptr [rbx+16], 0
jne SHORT G_M31322_IG11

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

x86 test failure seems unrelated; looks like an instance of #54469.

tracing\\eventpipe\\reverseouter\\reverseouter\\reverseouter.cmd
7.3s: Assert failure(PID 5424 [0x00001530], Thread: 3364 [0x0d24]): Consistency check failed: AV in clr at this callstack:
7.3s: ------
7.3s: CORECLR! AllocMemTracker::~AllocMemTracker + 0xFE (0x70a3a1d5)
7.3s: CORECLR! IBCLoggerAwareAllocMemTracker::~IBCLoggerAwareAllocMemTracker + 0xDC (0x707b0799)
7.3s: CORECLR! ClassLoader::DoIncrementalLoad + 0x437 (0x70576fda)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey_Body + 0x894 (0x7057fd27)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey + 0x19A (0x7057ef6b)
7.3s: CORECLR! ClassLoader::LoadConstructedTypeThrowing + 0x63D (0x7057bb38)
7.3s: CORECLR! ClassLoader::LoadGenericInstantiationThrowing + 0x4DF (0x7057c4e1)
7.3s: CORECLR! SigPointer::GetTypeHandleThrowing + 0xE92 (0x706aedf5)

Comment threadsrc/coreclr/jit/compiler.h Outdated
// The map provides the entry block of the most closely enclosing loop that
// defines the memory region accessed when defining the nodes's VN.
//
// This information should consulted when considering hoisting node out of a loop, as the VN

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.

should => should be

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
//
bool IsTreeLoopMemoryInvariant(GenTree* tree)
{
if (tree->OperIsIndir() && ((tree->gtFlags & GTF_IND_INVARIANT) != 0))

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.

You could add a comment, stating that we early out returning true for any GT_IND marked as Invariant

@AndyAyersMSAndyAyersMSJul 21, 2021

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.

Actually we probably don't need this bail out as we should be recording that invariant VNs indirs are dependent on an invariant memory state. So I'll likely just delete this and verify we still get the same results.

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
return true;
}

// Todo: other operators that read memory

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.

If we are going to have a ToDo here, then I think that we have a convention on how we write ToDo's

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.

This is a vestige from an earlier version where VN wasn't annotating all trees. But now it does.

So here I think we can just check all trees. However I believe calls are handled specially during hoisting, so we may still need an early bail-out for calls.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib it might be interesting for more of you to look this one over, given our ongoing discussions of VN.

@briansullbriansull left a comment

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.

Looks Good

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Forgot to mention that the revised version has the same set of SPMI diffs as the first version.

@AndyAyersMS
AndyAyersMS merged commit 1b4f786 into dotnet:mainJul 22, 2021
@AndyAyersMS
AndyAyersMS deleted the FixLicmMemoryDependenceAmbientVnTree branch July 22, 2021 01:03
@jakobbotsch

Copy link
Copy Markdown
Member

I just rechecked my examples with newest fixes from master and it looks like there are still some unhandled cases:

// Generated by Fuzzlyn v1.2 on 2021-07-22 13:37:14// Seed: 14815563263006255362// Reduced from 12.9 KiB to 0.4 KiB in 00:00:17// Debug: Outputs 1// Release: Outputs 0publicclassProgram{staticshort[]s_2;publicstaticvoidMain(){byte[]vr7=newbyte[]{0};boolvr11=default(bool);for(intvr9=0;vr9<2;vr9++){if(vr11){s_2[0]=0;}vr7[0]=1;bytevr10=vr7[0];System.Console.WriteLine(vr10);}}}

Looks like they all involve some form of control flow before the pattern.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Thanks, will take a look.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Seems like we need to add similar loop dependence tracking to VNF_PhiDef.

int[] arr = { -1 };
ref int r = ref arr[0];
int val = -1;
for (int i = 0; i < 2; i++)

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.

To test loop-dependent VN, these tests all depend on these loops remaining as loops. But Checked JIT loop unrolling under stress will probably fully unroll them. And what if we change the loop unrolling heuristics? Maybe the upper bound should be make an argument.

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.

See #56184

assert(arg1VN == VNNormalValue(arg1VN));
assert(arg2VN == VNNormalValue(arg2VN));
assert(arg3VN == VNNormalValue(arg3VN));
assert((func == VNF_MapStore) || (arg3VN == VNNormalValue(arg3VN)));

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.

Comment above: "// Note: Currently the only four operand func is the VNF_PtrToArrElem operation" is no longer true

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.

Thanks, fixed in #56184.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 22, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixesdotnet#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in dotnet#55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit that referenced this pull request Jul 27, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixes#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in #55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 27, 2021
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in dotnet#55936 and dotnet#56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of dotnet#54118.
AndyAyersMS added a commit that referenced this pull request Jul 28, 2021
…56436)
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in #55936 and #56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of #54118.
@ghostghost locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid hoisting of indirections proven to be loop invariant

4 participants

@AndyAyersMS@jakobbotsch@BruceForstall@briansull
, '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

JIT: track memory loop dependence of trees during value numbering - #55936

Merged
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree
Jul 22, 2021
Merged

JIT: track memory loop dependence of trees during value numbering#55936
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.

First, refactor the mapStore value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.

Next, during VN computation, record loop memory dependence from mapStores
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.

This dependence info is tracked via a side map.

Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.

I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.

Resolves#54118.

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.
First, refactor the `mapStore` value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.
Next, during VN computation, record loop memory dependence from `mapStores`
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.
This dependence info is tracked via a side map.
Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.
I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.
Resolvesdotnet#54118.
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 19, 2021
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@briansull@jakobbotsch PTAL
cc @dotnet/jit-contrib

Passes the more elaborate test case from #54118 (added here).

5 methods with SPMI diffs (not counting the new test). Will say more about them in a follow-up note a bit later today.

benchmarks.run.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 407
Total bytes of diff: 399
Total bytes of delta: -8 (-1.97% of base)
Total relative delta: -0.02
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file improvements (bytes):
-8 : 26074.dasm (-1.97% of base)
1 total files with Code Size differences (1 improved, 0 regressed), 0 unchanged.
Top method improvements (bytes):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
Top method improvements (percentages):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
1 total methods with Code Size differences (1 improved, 0 regressed), 0 unchanged.

coreclr_tests.pmi.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 2649
Total bytes of diff: 2638
Total bytes of delta: -11 (-0.42% of base)
Total relative delta: -0.07
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file regressions (bytes):
3 : 86555.dasm (1.76% of base)
2 : 85392.dasm (0.99% of base)
Top file improvements (bytes):
-8 : 219848.dasm (-1.97% of base)
-5 : 86566.dasm (-6.33% of base)
-3 : 86074.dasm (-1.68% of base)
5 total files with Code Size differences (3 improved, 2 regressed), 1 unchanged.
Top method regressions (bytes):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (bytes):
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
Top method regressions (percentages):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (percentages):
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
5 total methods with Code Size differences (3 improved, 2 regressed), 1 unchanged.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

;; 86566 (source unknown) -- we now hoist load of a static var out of a loop. Since there are no opaque memory accesses / synchronization points in the loop, this seems ok. Similar diffs in 85392.

BeforeAfter
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
align [13 bytes]
G_M57269_IG03:
mov eax, dword ptr [CLASS-VAR]
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd rax, eax
mov esi, dword ptr [rax]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
mov eax, dword ptr [CLASS-VAR]
align [7 bytes]
G_M57269_IG03:
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd r8, eax
mov esi, dword ptr [r8]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03

;; 219848 -- V8.Crypto.RSAKey:pkcs1pad2

We previously moved a read past a write in a fully unrolled loop, now we don't.

BeforeAfter
 movzx r15, byte ptr [rbx+16]
G_M31322_IG07:
mov byte ptr [rbx+16], 0
test r15d, r15d
jne SHORT G_M31322_IG11
 G_M31322_IG07:
mov byte ptr [rbx+16], 0
cmp byte ptr [rbx+16], 0
jne SHORT G_M31322_IG11

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

x86 test failure seems unrelated; looks like an instance of #54469.

tracing\\eventpipe\\reverseouter\\reverseouter\\reverseouter.cmd
7.3s: Assert failure(PID 5424 [0x00001530], Thread: 3364 [0x0d24]): Consistency check failed: AV in clr at this callstack:
7.3s: ------
7.3s: CORECLR! AllocMemTracker::~AllocMemTracker + 0xFE (0x70a3a1d5)
7.3s: CORECLR! IBCLoggerAwareAllocMemTracker::~IBCLoggerAwareAllocMemTracker + 0xDC (0x707b0799)
7.3s: CORECLR! ClassLoader::DoIncrementalLoad + 0x437 (0x70576fda)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey_Body + 0x894 (0x7057fd27)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey + 0x19A (0x7057ef6b)
7.3s: CORECLR! ClassLoader::LoadConstructedTypeThrowing + 0x63D (0x7057bb38)
7.3s: CORECLR! ClassLoader::LoadGenericInstantiationThrowing + 0x4DF (0x7057c4e1)
7.3s: CORECLR! SigPointer::GetTypeHandleThrowing + 0xE92 (0x706aedf5)

Comment threadsrc/coreclr/jit/compiler.h Outdated
// The map provides the entry block of the most closely enclosing loop that
// defines the memory region accessed when defining the nodes's VN.
//
// This information should consulted when considering hoisting node out of a loop, as the VN

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.

should => should be

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
//
bool IsTreeLoopMemoryInvariant(GenTree* tree)
{
if (tree->OperIsIndir() && ((tree->gtFlags & GTF_IND_INVARIANT) != 0))

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.

You could add a comment, stating that we early out returning true for any GT_IND marked as Invariant

@AndyAyersMSAndyAyersMSJul 21, 2021

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.

Actually we probably don't need this bail out as we should be recording that invariant VNs indirs are dependent on an invariant memory state. So I'll likely just delete this and verify we still get the same results.

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
return true;
}

// Todo: other operators that read memory

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.

If we are going to have a ToDo here, then I think that we have a convention on how we write ToDo's

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.

This is a vestige from an earlier version where VN wasn't annotating all trees. But now it does.

So here I think we can just check all trees. However I believe calls are handled specially during hoisting, so we may still need an early bail-out for calls.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib it might be interesting for more of you to look this one over, given our ongoing discussions of VN.

@briansullbriansull left a comment

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.

Looks Good

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Forgot to mention that the revised version has the same set of SPMI diffs as the first version.

@AndyAyersMS
AndyAyersMS merged commit 1b4f786 into dotnet:mainJul 22, 2021
@AndyAyersMS
AndyAyersMS deleted the FixLicmMemoryDependenceAmbientVnTree branch July 22, 2021 01:03
@jakobbotsch

Copy link
Copy Markdown
Member

I just rechecked my examples with newest fixes from master and it looks like there are still some unhandled cases:

// Generated by Fuzzlyn v1.2 on 2021-07-22 13:37:14// Seed: 14815563263006255362// Reduced from 12.9 KiB to 0.4 KiB in 00:00:17// Debug: Outputs 1// Release: Outputs 0publicclassProgram{staticshort[]s_2;publicstaticvoidMain(){byte[]vr7=newbyte[]{0};boolvr11=default(bool);for(intvr9=0;vr9<2;vr9++){if(vr11){s_2[0]=0;}vr7[0]=1;bytevr10=vr7[0];System.Console.WriteLine(vr10);}}}

Looks like they all involve some form of control flow before the pattern.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Thanks, will take a look.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Seems like we need to add similar loop dependence tracking to VNF_PhiDef.

int[] arr = { -1 };
ref int r = ref arr[0];
int val = -1;
for (int i = 0; i < 2; i++)

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.

To test loop-dependent VN, these tests all depend on these loops remaining as loops. But Checked JIT loop unrolling under stress will probably fully unroll them. And what if we change the loop unrolling heuristics? Maybe the upper bound should be make an argument.

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.

See #56184

assert(arg1VN == VNNormalValue(arg1VN));
assert(arg2VN == VNNormalValue(arg2VN));
assert(arg3VN == VNNormalValue(arg3VN));
assert((func == VNF_MapStore) || (arg3VN == VNNormalValue(arg3VN)));

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.

Comment above: "// Note: Currently the only four operand func is the VNF_PtrToArrElem operation" is no longer true

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.

Thanks, fixed in #56184.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 22, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixesdotnet#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in dotnet#55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit that referenced this pull request Jul 27, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixes#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in #55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 27, 2021
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in dotnet#55936 and dotnet#56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of dotnet#54118.
AndyAyersMS added a commit that referenced this pull request Jul 28, 2021
…56436)
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in #55936 and #56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of #54118.
@ghostghost locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid hoisting of indirections proven to be loop invariant

4 participants

@AndyAyersMS@jakobbotsch@BruceForstall@briansull
, '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

JIT: track memory loop dependence of trees during value numbering - #55936

Merged
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree
Jul 22, 2021
Merged

JIT: track memory loop dependence of trees during value numbering#55936
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.

First, refactor the mapStore value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.

Next, during VN computation, record loop memory dependence from mapStores
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.

This dependence info is tracked via a side map.

Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.

I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.

Resolves#54118.

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.
First, refactor the `mapStore` value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.
Next, during VN computation, record loop memory dependence from `mapStores`
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.
This dependence info is tracked via a side map.
Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.
I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.
Resolvesdotnet#54118.
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 19, 2021
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@briansull@jakobbotsch PTAL
cc @dotnet/jit-contrib

Passes the more elaborate test case from #54118 (added here).

5 methods with SPMI diffs (not counting the new test). Will say more about them in a follow-up note a bit later today.

benchmarks.run.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 407
Total bytes of diff: 399
Total bytes of delta: -8 (-1.97% of base)
Total relative delta: -0.02
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file improvements (bytes):
-8 : 26074.dasm (-1.97% of base)
1 total files with Code Size differences (1 improved, 0 regressed), 0 unchanged.
Top method improvements (bytes):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
Top method improvements (percentages):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
1 total methods with Code Size differences (1 improved, 0 regressed), 0 unchanged.

coreclr_tests.pmi.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 2649
Total bytes of diff: 2638
Total bytes of delta: -11 (-0.42% of base)
Total relative delta: -0.07
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file regressions (bytes):
3 : 86555.dasm (1.76% of base)
2 : 85392.dasm (0.99% of base)
Top file improvements (bytes):
-8 : 219848.dasm (-1.97% of base)
-5 : 86566.dasm (-6.33% of base)
-3 : 86074.dasm (-1.68% of base)
5 total files with Code Size differences (3 improved, 2 regressed), 1 unchanged.
Top method regressions (bytes):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (bytes):
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
Top method regressions (percentages):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (percentages):
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
5 total methods with Code Size differences (3 improved, 2 regressed), 1 unchanged.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

;; 86566 (source unknown) -- we now hoist load of a static var out of a loop. Since there are no opaque memory accesses / synchronization points in the loop, this seems ok. Similar diffs in 85392.

BeforeAfter
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
align [13 bytes]
G_M57269_IG03:
mov eax, dword ptr [CLASS-VAR]
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd rax, eax
mov esi, dword ptr [rax]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
mov eax, dword ptr [CLASS-VAR]
align [7 bytes]
G_M57269_IG03:
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd r8, eax
mov esi, dword ptr [r8]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03

;; 219848 -- V8.Crypto.RSAKey:pkcs1pad2

We previously moved a read past a write in a fully unrolled loop, now we don't.

BeforeAfter
 movzx r15, byte ptr [rbx+16]
G_M31322_IG07:
mov byte ptr [rbx+16], 0
test r15d, r15d
jne SHORT G_M31322_IG11
 G_M31322_IG07:
mov byte ptr [rbx+16], 0
cmp byte ptr [rbx+16], 0
jne SHORT G_M31322_IG11

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

x86 test failure seems unrelated; looks like an instance of #54469.

tracing\\eventpipe\\reverseouter\\reverseouter\\reverseouter.cmd
7.3s: Assert failure(PID 5424 [0x00001530], Thread: 3364 [0x0d24]): Consistency check failed: AV in clr at this callstack:
7.3s: ------
7.3s: CORECLR! AllocMemTracker::~AllocMemTracker + 0xFE (0x70a3a1d5)
7.3s: CORECLR! IBCLoggerAwareAllocMemTracker::~IBCLoggerAwareAllocMemTracker + 0xDC (0x707b0799)
7.3s: CORECLR! ClassLoader::DoIncrementalLoad + 0x437 (0x70576fda)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey_Body + 0x894 (0x7057fd27)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey + 0x19A (0x7057ef6b)
7.3s: CORECLR! ClassLoader::LoadConstructedTypeThrowing + 0x63D (0x7057bb38)
7.3s: CORECLR! ClassLoader::LoadGenericInstantiationThrowing + 0x4DF (0x7057c4e1)
7.3s: CORECLR! SigPointer::GetTypeHandleThrowing + 0xE92 (0x706aedf5)

Comment threadsrc/coreclr/jit/compiler.h Outdated
// The map provides the entry block of the most closely enclosing loop that
// defines the memory region accessed when defining the nodes's VN.
//
// This information should consulted when considering hoisting node out of a loop, as the VN

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.

should => should be

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
//
bool IsTreeLoopMemoryInvariant(GenTree* tree)
{
if (tree->OperIsIndir() && ((tree->gtFlags & GTF_IND_INVARIANT) != 0))

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.

You could add a comment, stating that we early out returning true for any GT_IND marked as Invariant

@AndyAyersMSAndyAyersMSJul 21, 2021

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.

Actually we probably don't need this bail out as we should be recording that invariant VNs indirs are dependent on an invariant memory state. So I'll likely just delete this and verify we still get the same results.

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
return true;
}

// Todo: other operators that read memory

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.

If we are going to have a ToDo here, then I think that we have a convention on how we write ToDo's

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.

This is a vestige from an earlier version where VN wasn't annotating all trees. But now it does.

So here I think we can just check all trees. However I believe calls are handled specially during hoisting, so we may still need an early bail-out for calls.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib it might be interesting for more of you to look this one over, given our ongoing discussions of VN.

@briansullbriansull left a comment

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.

Looks Good

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Forgot to mention that the revised version has the same set of SPMI diffs as the first version.

@AndyAyersMS
AndyAyersMS merged commit 1b4f786 into dotnet:mainJul 22, 2021
@AndyAyersMS
AndyAyersMS deleted the FixLicmMemoryDependenceAmbientVnTree branch July 22, 2021 01:03
@jakobbotsch

Copy link
Copy Markdown
Member

I just rechecked my examples with newest fixes from master and it looks like there are still some unhandled cases:

// Generated by Fuzzlyn v1.2 on 2021-07-22 13:37:14// Seed: 14815563263006255362// Reduced from 12.9 KiB to 0.4 KiB in 00:00:17// Debug: Outputs 1// Release: Outputs 0publicclassProgram{staticshort[]s_2;publicstaticvoidMain(){byte[]vr7=newbyte[]{0};boolvr11=default(bool);for(intvr9=0;vr9<2;vr9++){if(vr11){s_2[0]=0;}vr7[0]=1;bytevr10=vr7[0];System.Console.WriteLine(vr10);}}}

Looks like they all involve some form of control flow before the pattern.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Thanks, will take a look.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Seems like we need to add similar loop dependence tracking to VNF_PhiDef.

int[] arr = { -1 };
ref int r = ref arr[0];
int val = -1;
for (int i = 0; i < 2; i++)

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.

To test loop-dependent VN, these tests all depend on these loops remaining as loops. But Checked JIT loop unrolling under stress will probably fully unroll them. And what if we change the loop unrolling heuristics? Maybe the upper bound should be make an argument.

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.

See #56184

assert(arg1VN == VNNormalValue(arg1VN));
assert(arg2VN == VNNormalValue(arg2VN));
assert(arg3VN == VNNormalValue(arg3VN));
assert((func == VNF_MapStore) || (arg3VN == VNNormalValue(arg3VN)));

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.

Comment above: "// Note: Currently the only four operand func is the VNF_PtrToArrElem operation" is no longer true

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.

Thanks, fixed in #56184.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 22, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixesdotnet#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in dotnet#55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit that referenced this pull request Jul 27, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixes#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in #55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 27, 2021
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in dotnet#55936 and dotnet#56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of dotnet#54118.
AndyAyersMS added a commit that referenced this pull request Jul 28, 2021
…56436)
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in #55936 and #56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of #54118.
@ghostghost locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid hoisting of indirections proven to be loop invariant

4 participants

@AndyAyersMS@jakobbotsch@BruceForstall@briansull
, '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

JIT: track memory loop dependence of trees during value numbering - #55936

Merged
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree
Jul 22, 2021
Merged

JIT: track memory loop dependence of trees during value numbering#55936
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.

First, refactor the mapStore value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.

Next, during VN computation, record loop memory dependence from mapStores
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.

This dependence info is tracked via a side map.

Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.

I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.

Resolves#54118.

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.
First, refactor the `mapStore` value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.
Next, during VN computation, record loop memory dependence from `mapStores`
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.
This dependence info is tracked via a side map.
Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.
I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.
Resolvesdotnet#54118.
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 19, 2021
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@briansull@jakobbotsch PTAL
cc @dotnet/jit-contrib

Passes the more elaborate test case from #54118 (added here).

5 methods with SPMI diffs (not counting the new test). Will say more about them in a follow-up note a bit later today.

benchmarks.run.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 407
Total bytes of diff: 399
Total bytes of delta: -8 (-1.97% of base)
Total relative delta: -0.02
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file improvements (bytes):
-8 : 26074.dasm (-1.97% of base)
1 total files with Code Size differences (1 improved, 0 regressed), 0 unchanged.
Top method improvements (bytes):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
Top method improvements (percentages):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
1 total methods with Code Size differences (1 improved, 0 regressed), 0 unchanged.

coreclr_tests.pmi.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 2649
Total bytes of diff: 2638
Total bytes of delta: -11 (-0.42% of base)
Total relative delta: -0.07
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file regressions (bytes):
3 : 86555.dasm (1.76% of base)
2 : 85392.dasm (0.99% of base)
Top file improvements (bytes):
-8 : 219848.dasm (-1.97% of base)
-5 : 86566.dasm (-6.33% of base)
-3 : 86074.dasm (-1.68% of base)
5 total files with Code Size differences (3 improved, 2 regressed), 1 unchanged.
Top method regressions (bytes):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (bytes):
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
Top method regressions (percentages):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (percentages):
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
5 total methods with Code Size differences (3 improved, 2 regressed), 1 unchanged.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

;; 86566 (source unknown) -- we now hoist load of a static var out of a loop. Since there are no opaque memory accesses / synchronization points in the loop, this seems ok. Similar diffs in 85392.

BeforeAfter
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
align [13 bytes]
G_M57269_IG03:
mov eax, dword ptr [CLASS-VAR]
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd rax, eax
mov esi, dword ptr [rax]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
mov eax, dword ptr [CLASS-VAR]
align [7 bytes]
G_M57269_IG03:
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd r8, eax
mov esi, dword ptr [r8]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03

;; 219848 -- V8.Crypto.RSAKey:pkcs1pad2

We previously moved a read past a write in a fully unrolled loop, now we don't.

BeforeAfter
 movzx r15, byte ptr [rbx+16]
G_M31322_IG07:
mov byte ptr [rbx+16], 0
test r15d, r15d
jne SHORT G_M31322_IG11
 G_M31322_IG07:
mov byte ptr [rbx+16], 0
cmp byte ptr [rbx+16], 0
jne SHORT G_M31322_IG11

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

x86 test failure seems unrelated; looks like an instance of #54469.

tracing\\eventpipe\\reverseouter\\reverseouter\\reverseouter.cmd
7.3s: Assert failure(PID 5424 [0x00001530], Thread: 3364 [0x0d24]): Consistency check failed: AV in clr at this callstack:
7.3s: ------
7.3s: CORECLR! AllocMemTracker::~AllocMemTracker + 0xFE (0x70a3a1d5)
7.3s: CORECLR! IBCLoggerAwareAllocMemTracker::~IBCLoggerAwareAllocMemTracker + 0xDC (0x707b0799)
7.3s: CORECLR! ClassLoader::DoIncrementalLoad + 0x437 (0x70576fda)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey_Body + 0x894 (0x7057fd27)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey + 0x19A (0x7057ef6b)
7.3s: CORECLR! ClassLoader::LoadConstructedTypeThrowing + 0x63D (0x7057bb38)
7.3s: CORECLR! ClassLoader::LoadGenericInstantiationThrowing + 0x4DF (0x7057c4e1)
7.3s: CORECLR! SigPointer::GetTypeHandleThrowing + 0xE92 (0x706aedf5)

Comment threadsrc/coreclr/jit/compiler.h Outdated
// The map provides the entry block of the most closely enclosing loop that
// defines the memory region accessed when defining the nodes's VN.
//
// This information should consulted when considering hoisting node out of a loop, as the VN

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.

should => should be

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
//
bool IsTreeLoopMemoryInvariant(GenTree* tree)
{
if (tree->OperIsIndir() && ((tree->gtFlags & GTF_IND_INVARIANT) != 0))

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.

You could add a comment, stating that we early out returning true for any GT_IND marked as Invariant

@AndyAyersMSAndyAyersMSJul 21, 2021

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.

Actually we probably don't need this bail out as we should be recording that invariant VNs indirs are dependent on an invariant memory state. So I'll likely just delete this and verify we still get the same results.

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
return true;
}

// Todo: other operators that read memory

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.

If we are going to have a ToDo here, then I think that we have a convention on how we write ToDo's

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.

This is a vestige from an earlier version where VN wasn't annotating all trees. But now it does.

So here I think we can just check all trees. However I believe calls are handled specially during hoisting, so we may still need an early bail-out for calls.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib it might be interesting for more of you to look this one over, given our ongoing discussions of VN.

@briansullbriansull left a comment

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.

Looks Good

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Forgot to mention that the revised version has the same set of SPMI diffs as the first version.

@AndyAyersMS
AndyAyersMS merged commit 1b4f786 into dotnet:mainJul 22, 2021
@AndyAyersMS
AndyAyersMS deleted the FixLicmMemoryDependenceAmbientVnTree branch July 22, 2021 01:03
@jakobbotsch

Copy link
Copy Markdown
Member

I just rechecked my examples with newest fixes from master and it looks like there are still some unhandled cases:

// Generated by Fuzzlyn v1.2 on 2021-07-22 13:37:14// Seed: 14815563263006255362// Reduced from 12.9 KiB to 0.4 KiB in 00:00:17// Debug: Outputs 1// Release: Outputs 0publicclassProgram{staticshort[]s_2;publicstaticvoidMain(){byte[]vr7=newbyte[]{0};boolvr11=default(bool);for(intvr9=0;vr9<2;vr9++){if(vr11){s_2[0]=0;}vr7[0]=1;bytevr10=vr7[0];System.Console.WriteLine(vr10);}}}

Looks like they all involve some form of control flow before the pattern.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Thanks, will take a look.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Seems like we need to add similar loop dependence tracking to VNF_PhiDef.

int[] arr = { -1 };
ref int r = ref arr[0];
int val = -1;
for (int i = 0; i < 2; i++)

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.

To test loop-dependent VN, these tests all depend on these loops remaining as loops. But Checked JIT loop unrolling under stress will probably fully unroll them. And what if we change the loop unrolling heuristics? Maybe the upper bound should be make an argument.

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.

See #56184

assert(arg1VN == VNNormalValue(arg1VN));
assert(arg2VN == VNNormalValue(arg2VN));
assert(arg3VN == VNNormalValue(arg3VN));
assert((func == VNF_MapStore) || (arg3VN == VNNormalValue(arg3VN)));

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.

Comment above: "// Note: Currently the only four operand func is the VNF_PtrToArrElem operation" is no longer true

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.

Thanks, fixed in #56184.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 22, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixesdotnet#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in dotnet#55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit that referenced this pull request Jul 27, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixes#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in #55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 27, 2021
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in dotnet#55936 and dotnet#56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of dotnet#54118.
AndyAyersMS added a commit that referenced this pull request Jul 28, 2021
…56436)
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in #55936 and #56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of #54118.
@ghostghost locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid hoisting of indirections proven to be loop invariant

4 participants

@AndyAyersMS@jakobbotsch@BruceForstall@briansull
, '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

JIT: track memory loop dependence of trees during value numbering - #55936

Merged
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree
Jul 22, 2021
Merged

JIT: track memory loop dependence of trees during value numbering#55936
AndyAyersMS merged 2 commits into
dotnet:mainfrom
AndyAyersMS:FixLicmMemoryDependenceAmbientVnTree

Conversation

@AndyAyersMS

Copy link
Copy Markdown
Member

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.

First, refactor the mapStore value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.

Next, during VN computation, record loop memory dependence from mapStores
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.

This dependence info is tracked via a side map.

Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.

I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.

Resolves#54118.

Leverage value numbering's alias analysis to annotate trees with the loop
memory dependence of the tree's value number.
First, refactor the `mapStore` value number so that it also tracks the loop
number where the store occurs. This is done via an extra non-value-num arg,
so add appropriate bypasses to logic in the jit that expect to only find
value number args. Also update the dumping to display the loop information.
Next, during VN computation, record loop memory dependence from `mapStores`
with the tree currently being value numbered, whenever a value number comes
from a particular map. There may be multiple such recording events per tree,
so add logic on the recording side to track the most constraining dependence.
Note value numbering happens in execution order, so there is an unambiguous
current tree being value numbered.
This dependence info is tracked via a side map.
Finally, during hoisting, for each potentially hoistable tree, consult the side
map to recover the loop memory dependence of a tree, and if that dependence is
at or within the loop that we're hoisting from, block the hoist.
I've also absorbed the former class var (static field) hosting exclusion into
this new logic. This gives us slightly more relaxed dependence in some cases.
Resolvesdotnet#54118.
@ghostghost added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 19, 2021
@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

@briansull@jakobbotsch PTAL
cc @dotnet/jit-contrib

Passes the more elaborate test case from #54118 (added here).

5 methods with SPMI diffs (not counting the new test). Will say more about them in a follow-up note a bit later today.

benchmarks.run.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 407
Total bytes of diff: 399
Total bytes of delta: -8 (-1.97% of base)
Total relative delta: -0.02
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file improvements (bytes):
-8 : 26074.dasm (-1.97% of base)
1 total files with Code Size differences (1 improved, 0 regressed), 0 unchanged.
Top method improvements (bytes):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
Top method improvements (percentages):
-8 (-1.97% of base) : 26074.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
1 total methods with Code Size differences (1 improved, 0 regressed), 0 unchanged.

coreclr_tests.pmi.windows.x64.checked.mch:


Summary of Code Size diffs:
(Lower is better)
Total bytes of base: 2649
Total bytes of diff: 2638
Total bytes of delta: -11 (-0.42% of base)
Total relative delta: -0.07
diff is an improvement.
relative diff is an improvement.
Detail diffs

Top file regressions (bytes):
3 : 86555.dasm (1.76% of base)
2 : 85392.dasm (0.99% of base)
Top file improvements (bytes):
-8 : 219848.dasm (-1.97% of base)
-5 : 86566.dasm (-6.33% of base)
-3 : 86074.dasm (-1.68% of base)
5 total files with Code Size differences (3 improved, 2 regressed), 1 unchanged.
Top method regressions (bytes):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (bytes):
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
Top method regressions (percentages):
3 ( 1.76% of base) : 86555.dasm - T:Main():int
2 ( 0.99% of base) : 85392.dasm - T:Main():int
Top method improvements (percentages):
-5 (-6.33% of base) : 86566.dasm - test:Main():int
-8 (-1.97% of base) : 219848.dasm - V8.Crypto.RSAKey:pkcs1pad2(System.String,int):V8.Crypto.BigInteger
-3 (-1.68% of base) : 86074.dasm - ILGEN_0x981b6a55:Method_0x9d35bca7():float
5 total methods with Code Size differences (3 improved, 2 regressed), 1 unchanged.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

;; 86566 (source unknown) -- we now hoist load of a static var out of a loop. Since there are no opaque memory accesses / synchronization points in the loop, this seems ok. Similar diffs in 85392.

BeforeAfter
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
align [13 bytes]
G_M57269_IG03:
mov eax, dword ptr [CLASS-VAR]
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd rax, eax
mov esi, dword ptr [rax]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03
G_M57269_IG02:
mov dword ptr [CLASS-VAR], 5
mov eax, dword ptr [CLASS-VAR]
align [7 bytes]
G_M57269_IG03:
mov edx, eax
inc ecx
jne SHORT G_M57269_IG05
movsxd r8, eax
mov esi, dword ptr [r8]
G_M57269_IG05:
cmp ecx, 7
jl SHORT G_M57269_IG03

;; 219848 -- V8.Crypto.RSAKey:pkcs1pad2

We previously moved a read past a write in a fully unrolled loop, now we don't.

BeforeAfter
 movzx r15, byte ptr [rbx+16]
G_M31322_IG07:
mov byte ptr [rbx+16], 0
test r15d, r15d
jne SHORT G_M31322_IG11
 G_M31322_IG07:
mov byte ptr [rbx+16], 0
cmp byte ptr [rbx+16], 0
jne SHORT G_M31322_IG11

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

x86 test failure seems unrelated; looks like an instance of #54469.

tracing\\eventpipe\\reverseouter\\reverseouter\\reverseouter.cmd
7.3s: Assert failure(PID 5424 [0x00001530], Thread: 3364 [0x0d24]): Consistency check failed: AV in clr at this callstack:
7.3s: ------
7.3s: CORECLR! AllocMemTracker::~AllocMemTracker + 0xFE (0x70a3a1d5)
7.3s: CORECLR! IBCLoggerAwareAllocMemTracker::~IBCLoggerAwareAllocMemTracker + 0xDC (0x707b0799)
7.3s: CORECLR! ClassLoader::DoIncrementalLoad + 0x437 (0x70576fda)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey_Body + 0x894 (0x7057fd27)
7.3s: CORECLR! ClassLoader::LoadTypeHandleForTypeKey + 0x19A (0x7057ef6b)
7.3s: CORECLR! ClassLoader::LoadConstructedTypeThrowing + 0x63D (0x7057bb38)
7.3s: CORECLR! ClassLoader::LoadGenericInstantiationThrowing + 0x4DF (0x7057c4e1)
7.3s: CORECLR! SigPointer::GetTypeHandleThrowing + 0xE92 (0x706aedf5)

Comment threadsrc/coreclr/jit/compiler.h Outdated
// The map provides the entry block of the most closely enclosing loop that
// defines the memory region accessed when defining the nodes's VN.
//
// This information should consulted when considering hoisting node out of a loop, as the VN

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.

should => should be

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
//
bool IsTreeLoopMemoryInvariant(GenTree* tree)
{
if (tree->OperIsIndir() && ((tree->gtFlags & GTF_IND_INVARIANT) != 0))

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.

You could add a comment, stating that we early out returning true for any GT_IND marked as Invariant

@AndyAyersMSAndyAyersMSJul 21, 2021

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.

Actually we probably don't need this bail out as we should be recording that invariant VNs indirs are dependent on an invariant memory state. So I'll likely just delete this and verify we still get the same results.

Comment threadsrc/coreclr/jit/optimizer.cpp Outdated
return true;
}

// Todo: other operators that read memory

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.

If we are going to have a ToDo here, then I think that we have a convention on how we write ToDo's

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.

This is a vestige from an earlier version where VN wasn't annotating all trees. But now it does.

So here I think we can just check all trees. However I believe calls are handled specially during hoisting, so we may still need an early bail-out for calls.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

cc @dotnet/jit-contrib it might be interesting for more of you to look this one over, given our ongoing discussions of VN.

@briansullbriansull left a comment

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.

Looks Good

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Forgot to mention that the revised version has the same set of SPMI diffs as the first version.

@AndyAyersMS
AndyAyersMS merged commit 1b4f786 into dotnet:mainJul 22, 2021
@AndyAyersMS
AndyAyersMS deleted the FixLicmMemoryDependenceAmbientVnTree branch July 22, 2021 01:03
@jakobbotsch

Copy link
Copy Markdown
Member

I just rechecked my examples with newest fixes from master and it looks like there are still some unhandled cases:

// Generated by Fuzzlyn v1.2 on 2021-07-22 13:37:14// Seed: 14815563263006255362// Reduced from 12.9 KiB to 0.4 KiB in 00:00:17// Debug: Outputs 1// Release: Outputs 0publicclassProgram{staticshort[]s_2;publicstaticvoidMain(){byte[]vr7=newbyte[]{0};boolvr11=default(bool);for(intvr9=0;vr9<2;vr9++){if(vr11){s_2[0]=0;}vr7[0]=1;bytevr10=vr7[0];System.Console.WriteLine(vr10);}}}

Looks like they all involve some form of control flow before the pattern.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Thanks, will take a look.

@AndyAyersMS

Copy link
Copy Markdown
MemberAuthor

Seems like we need to add similar loop dependence tracking to VNF_PhiDef.

int[] arr = { -1 };
ref int r = ref arr[0];
int val = -1;
for (int i = 0; i < 2; i++)

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.

To test loop-dependent VN, these tests all depend on these loops remaining as loops. But Checked JIT loop unrolling under stress will probably fully unroll them. And what if we change the loop unrolling heuristics? Maybe the upper bound should be make an argument.

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.

See #56184

assert(arg1VN == VNNormalValue(arg1VN));
assert(arg2VN == VNNormalValue(arg2VN));
assert(arg3VN == VNNormalValue(arg3VN));
assert((func == VNF_MapStore) || (arg3VN == VNNormalValue(arg3VN)));

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.

Comment above: "// Note: Currently the only four operand func is the VNF_PtrToArrElem operation" is no longer true

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.

Thanks, fixed in #56184.

AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 22, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixesdotnet#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in dotnet#55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit that referenced this pull request Jul 27, 2021
Specify `Overwrite` when setting loop dependence map entries, as we may
refine the initial result.
Fixes#56174.
Extract loop dependence of `VNF_PhiMemoryDef`.
Fixes new case noted in #55936, and 13/16 or so other cases Jakob sent
me privately. Also update a comment and fix tests to work better with
jitstress per other notes on that PR.
AndyAyersMS added a commit to AndyAyersMS/runtime that referenced this pull request Jul 27, 2021
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in dotnet#55936 and dotnet#56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of dotnet#54118.
AndyAyersMS added a commit that referenced this pull request Jul 28, 2021
…56436)
If a loop is removed (because of unrolling) then the loop dependence
tracking introduced in #55936 and #56184 may not properly update.
So when a loop is removed, walk up the chain of parent loops looking
for one that is not removed, and record the dependence on that parent.
Addresses last part of #54118.
@ghostghost locked as resolved and limited conversation to collaborators Aug 21, 2021
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invalid hoisting of indirections proven to be loop invariant

4 participants

@AndyAyersMS@jakobbotsch@BruceForstall@briansull