Bug 180852 - The CleanUp after LICM is erroneously removing a Check
Summary: The CleanUp after LICM is erroneously removing a Check
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: JavaScriptCore (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Saam Barati
URL:
Keywords: InRadar
: 148544 (view as bug list)
Depends on:
Blocks:
 
Reported: 2017-12-14 17:20 PST by Mark Lam
Modified: 2017-12-14 22:20 PST (History)
15 users (show)

See Also:


Attachments
Repro test case. (295 bytes, application/x-javascript)
2017-12-14 17:20 PST, Mark Lam
no flags Details
patch (6.44 KB, patch)
2017-12-14 19:03 PST, Saam Barati
fpizlo: review-
Details | Formatted Diff | Diff
patch (8.05 KB, patch)
2017-12-14 19:24 PST, Saam Barati
fpizlo: review+
Details | Formatted Diff | Diff
patch for landing (8.14 KB, patch)
2017-12-14 19:33 PST, Saam Barati
no flags Details | Formatted Diff | Diff
patch for landing (8.16 KB, patch)
2017-12-14 19:36 PST, Saam Barati
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Mark Lam 2017-12-14 17:20:33 PST
Created attachment 329425 [details]
Repro test case.

This results in a subsequent node expecting a checked String object, but instead, got a non-String cell, which leads to a crash.

I ran the test as follows:
$ JSC_useConcurrentJIT=0 jsc repro1.js
Comment 1 Radar WebKit Bug Importer 2017-12-14 17:21:28 PST
<rdar://problem/36063494>
Comment 2 Saam Barati 2017-12-14 18:06:25 PST
The bug is we're running part of AI inside LICM. This part of AI will change the proof status of a node. The bug here is:

We have two loops, L1 and L2, and two preheaders, P1 and P2. L2 is nested inside of L1. We have a Check inside a node inside L1, say in block BB, and that Check dominates all of L2. This is also a hoisting candidate, so we hoist it outside of L1 and put it inside P1. Then, when we run AI, we look at the preheader for each loop inside L1, so P1 and P2. When considering P2, we execute the Check. Inside P2, before any hoisting is done, this Check is dead code, because BB dominates P2. When we use AI to "execute" the Check, it'll set its proof status to proved. This is because inside P2, in the program before LICM runs, the Check is indeed proven at P2. But it is not proven inside P1. This "execute" call will set our proof status for the node inside *P1*, hence, we crash.

I see two easy solutions:
1. Remove the CleanUp phase after LICM
2. Run CFA before the CleanUP phase after LICM

I vote for (1) since CFA is expensive to run.
Comment 3 Saam Barati 2017-12-14 18:15:14 PST
Yet another alternative solution:
Clear isProved() for the children of all nodes we hoist.
Comment 4 Filip Pizlo 2017-12-14 18:30:15 PST
(In reply to Saam Barati from comment #2)
> The bug is we're running part of AI inside LICM. 

How is that a bug?  It should generate a valid proof. 

> This part of AI will change
> the proof status of a node. The bug here is:
> 
> We have two loops, L1 and L2, and two preheaders, P1 and P2. L2 is nested
> inside of L1. We have a Check inside a node inside L1, say in block BB, and
> that Check dominates all of L2. This is also a hoisting candidate, so we
> hoist it outside of L1 and put it inside P1. Then, when we run AI, we look
> at the preheader for each loop inside L1, so P1 and P2. When considering P2,
> we execute the Check. Inside P2, before any hoisting is done, this Check is
> dead code, because BB dominates P2. When we use AI to "execute" the Check,
> it'll set its proof status to proved. This is because inside P2, in the
> program before LICM runs, the Check is indeed proven at P2. But it is not
> proven inside P1. This "execute" call will set our proof status for the node
> inside *P1*, hence, we crash.
> 
> I see two easy solutions:
> 1. Remove the CleanUp phase after LICM
> 2. Run CFA before the CleanUP phase after LICM
> 
> I vote for (1) since CFA is expensive to run.

Clean up is pretty useful. How about 3: make licm’s use of AI leave behind valid AI state.
Comment 5 Filip Pizlo 2017-12-14 18:33:04 PST
Seems like we need a way of executing a node without changing its proved bits. That execution in LICM is to update another block’s state summary so it shouldn’t be changing proved bots. 

Maybe you can hook that into the existing AtTail versus InPlace template magic.
Comment 6 Saam Barati 2017-12-14 18:36:13 PST
(In reply to Saam Barati from comment #3)
> Yet another alternative solution:
> Clear isProved() for the children of all nodes we hoist.

I like this option the most. It also seems like Fil predicted it a few years ago:
https://bugs.webkit.org/show_bug.cgi?id=148544

I think it's ok to be conservative and just say anything we hoist no longer has valid proofs for its children. Also, it's important to note that the act of hoisting a node will never change the proof status from IsProved to NeedsCheck for anything inside the loop. It could make it go from NeedsCheck to IsProved though, but that's purely profit that we don't gain until we run AI again.
Comment 7 Saam Barati 2017-12-14 18:37:39 PST
(In reply to Filip Pizlo from comment #5)
> Seems like we need a way of executing a node without changing its proved
> bits. That execution in LICM is to update another block’s state summary so
> it shouldn’t be changing proved bots. 
> 
> Maybe you can hook that into the existing AtTail versus InPlace template
> magic.

Right. Let's say I make this change to not update proved bits for AtTail. What worries me is this:
What if hoisting the node changes its proved bits?

I'm not sure if this is possible, but it seems like it'd be better to be conservative and say things aren't proved once hoisted.
Comment 8 Saam Barati 2017-12-14 18:50:45 PST
(In reply to Saam Barati from comment #7)
> (In reply to Filip Pizlo from comment #5)
> > Seems like we need a way of executing a node without changing its proved
> > bits. That execution in LICM is to update another block’s state summary so
> > it shouldn’t be changing proved bots. 
> > 
> > Maybe you can hook that into the existing AtTail versus InPlace template
> > magic.
> 
> Right. Let's say I make this change to not update proved bits for AtTail.
> What worries me is this:
> What if hoisting the node changes its proved bits?
> 
> I'm not sure if this is possible, but it seems like it'd be better to be
> conservative and say things aren't proved once hoisted.

I actually think this is necessary. The fact that we update the Proved bits probably helps us in a bunch of scenarios where we have something like this:

```
bb#1:
a: Foo: produces Heap TOP
jump #2
bb#2
b: CantBeHoisted(Check:Cell:@a)
c: CanBeHoisted(Cell:@a)
some stuff to make a loop
```

if we hoist without updating the proved bits, we're going to be sad
Comment 9 Saam Barati 2017-12-14 18:54:09 PST
I still like the idea of making this method as part of AbstractState though so it's a nop for LICM
Comment 10 Filip Pizlo 2017-12-14 18:56:31 PST
(In reply to Saam Barati from comment #9)
> I still like the idea of making this method as part of AbstractState though
> so it's a nop for LICM

I’m fine with your solution, but it seems like there are two different execution modes in LICM at-tail:

- execute to update state of nested headers

- execute to establish the right state at the tail of the header we hoisted to. 

So, you could be precise if you wanted to be.
Comment 11 Saam Barati 2017-12-14 19:03:13 PST
Created attachment 329435 [details]
patch
Comment 12 Filip Pizlo 2017-12-14 19:07:19 PST
Comment on attachment 329435 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=329435&action=review

I think this could be simpler.

> Source/JavaScriptCore/dfg/DFGAtTailAbstractState.h:67
> +    void setProofStatus(Edge&, ProofStatus) { }

What if this set it to NeedsCheck?  Then you don’t need more data structures.

> Source/JavaScriptCore/dfg/DFGLICMPhase.cpp:313
> +        m_hoistedNodes.add(node);

I don’t think you need this.

> Source/JavaScriptCore/dfg/DFGLICMPhase.cpp:354
> +    HashSet<Node*> m_hoistedNodes;

And if you did need it, it should probably be a vector. Optimizing away duplicates isn’t going to help you.
Comment 13 Saam Barati 2017-12-14 19:08:40 PST
(In reply to Filip Pizlo from comment #10)
> (In reply to Saam Barati from comment #9)
> > I still like the idea of making this method as part of AbstractState though
> > so it's a nop for LICM
> 
> I’m fine with your solution, but it seems like there are two different
> execution modes in LICM at-tail:
> 
> - execute to update state of nested headers
> 
> - execute to establish the right state at the tail of the header we hoisted
> to. 
> 
> So, you could be precise if you wanted to be.

I see. Yeah this makes sense. We could do it with a bit inside AtTail. The patch I uploaded is conservative and never does it, and says anything we hoist can't have proven children. I like the idea of being precise. I'll make a new patch
Comment 14 Saam Barati 2017-12-14 19:18:25 PST
*** Bug 148544 has been marked as a duplicate of this bug. ***
Comment 15 Saam Barati 2017-12-14 19:24:06 PST
Created attachment 329442 [details]
patch
Comment 16 Filip Pizlo 2017-12-14 19:28:28 PST
Comment on attachment 329442 [details]
patch

Nice!
Comment 17 Saam Barati 2017-12-14 19:29:15 PST
Comment on attachment 329442 [details]
patch

View in context: https://bugs.webkit.org/attachment.cgi?id=329442&action=review

> Source/JavaScriptCore/ChangeLog:25
> +        The fix I'm going with is to conservatively say the proof status for the
> +        children of all nodes we hoist is NeedsCheck.

This no longer reflects what I'm actually doing. I'll update it to be accurate.
Comment 18 Saam Barati 2017-12-14 19:33:41 PST
Created attachment 329445 [details]
patch for landing
Comment 19 Saam Barati 2017-12-14 19:36:42 PST
Created attachment 329446 [details]
patch for landing

Fix typo
Comment 20 WebKit Commit Bot 2017-12-14 22:20:11 PST
Comment on attachment 329446 [details]
patch for landing

Clearing flags on attachment: 329446

Committed r225966: <https://trac.webkit.org/changeset/225966>
Comment 21 WebKit Commit Bot 2017-12-14 22:20:13 PST
All reviewed patches have been landed.  Closing bug.