WebKit Bugzilla
New
Browse
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
164309
DFG plays fast and loose with the shadow values of a Phi
https://bugs.webkit.org/show_bug.cgi?id=164309
Summary
DFG plays fast and loose with the shadow values of a Phi
David I. Lehn
Reported
2016-11-01 18:28:26 PDT
Summary: JavaScriptCore appears to incorrectly optimize variable assignment in hot loops on x86-64 builds resulting in eventual incorrect behavior. I work on a JS library called forge that has many hashing and crypto functions. Recent Safari 10.0.1 and iOS have had problems with the SHA-1 and SHA-256 implementations. They could sometimes produce incorrect results for deterministic inputs. We found a workaround for a company that needed these to work specifically on Safari 10.0.1 and iOS. It involved adding some ">>> 0" in the middle of short shuffling loops (see patch links in the github issue). After further exploration, this has been narrowed down to likely be in JavaScriptCore x86-64 builds on mac or linux. i386 works fine. It appears hot loops doing shuffling of variables can result in errors. It was reduced down to a test case that just swaps two variables in a loop. Sometimes they do not get swapped and one value is overwritten. Adding a ">>> 0" or similar can fix this possibly due to changing how the optimizer works.
https://github.com/digitalbazaar/forge/issues/428
In particular, short web page and jsc tests cases are here:
https://github.com/digitalbazaar/forge/issues/428#issuecomment-257652657
For fun I reduced the test case to ES2015 code that could fit in a tweet: var i;for(;;)(()=>{var a=1,b;for(i=0;i<2;++i){[a,b]=[b,a]}if(!a^b))throw 0})() If you want some debugging to know how many swaps it takes, here's the "long" version: var i,c=0;for(;;)(()=>{var a=1,b;for(i=0;i<2;++i){[a,b]=[b,a];c++}if(!a^b)throw c})() Run the above in jsc and it will throw 0. Which means it failed to swap a and b properly. In i386 builds or node it will run forever.
Attachments
it's a start
(37.15 KB, patch)
2016-11-02 20:28 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
this might even work
(54.97 KB, patch)
2016-11-03 09:13 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
even more things compile
(59.85 KB, patch)
2016-11-03 09:42 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
it compiles!
(63.06 KB, patch)
2016-11-03 10:06 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
it passes the test
(71.55 KB, patch)
2016-11-03 10:25 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
the patch
(72.46 KB, patch)
2016-11-03 12:02 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
possibly better patch
(79.88 KB, patch)
2016-11-03 16:16 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
the patch
(80.20 KB, patch)
2016-11-03 16:55 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
the patch
(80.22 KB, patch)
2016-11-03 17:43 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
the patch
(80.22 KB, patch)
2016-11-03 17:46 PDT
,
Filip Pizlo
saam
: review+
Details
Formatted Diff
Diff
patch for relanding
(80.24 KB, patch)
2016-11-03 21:52 PDT
,
Filip Pizlo
no flags
Details
Formatted Diff
Diff
Show Obsolete
(10)
View All
Add attachment
proposed patch, testcase, etc.
Radar WebKit Bug Importer
Comment 1
2016-11-01 18:28:47 PDT
<
rdar://problem/29057323
>
Mark Lam
Comment 2
2016-11-01 19:52:29 PDT
I'm investigating the issue.
Filip Pizlo
Comment 3
2016-11-02 13:40:31 PDT
Looks fun, I'll take this. Here's the reduced test case that would work as a JSC stress test: var i,c=0; function foo() { var a=1,b;for(i=0;i<2;++i){[a,b]=[b,a];c++}if(!a^b)throw c } noInline(foo); for(var k = 0; k < 10000; ++k) foo() This fails when running with --useConcurrentJIT=false --useFTLJIT=true. It passes with --useConcurrentJIT=false --useFTLJIT=false.
David I. Lehn
Comment 4
2016-11-02 13:54:56 PDT
There are clearer ways to write that test than the silly minimal version I did for fun. Like format it, and set b to 0 vs undefined. :-) Can also do "var t=b;b=a;a=t;" vs the destructuring style in case this was a problem before ES2015 features were added to the code. I also realized saying this was only on x86-64 may not be true. In this particular case it worked but without knowing the cause of the issue, it could be a problem on other platforms. Since there's a easy to run test case, it may be possible to narrow it down using 'git bisect' or similar. I started to do so with a partial git checkout (the history is huge!). Fails at least back on 2016-03-13 (when the build-jsc --jsc-only flag was added). So need to check earlier than that.
Filip Pizlo
Comment 5
2016-11-02 14:03:37 PDT
This is a liveness analysis bug. There's a basic block like this: Block #2 (bc#42): 124:<!0:-> ExitOK(MustGen, W:SideState, bc#42) 113:< 1:-> Upsilon(Check:Untyped:@40, ^65, W:SideState, bc#42) 111:< 1:-> Upsilon(Check:Untyped:@65, ^40, W:SideState, bc#42) 96:<!0:-> Jump(MustGen, T:#3, W:SideState, bc#42) Clearly, @65 is live at the head of the block. But liveness analysis reports this as live at the head: @1, @24, @27, @40, @43 Note that @65 is omitted.
Filip Pizlo
Comment 6
2016-11-02 14:04:05 PDT
(In reply to
comment #4
)
> There are clearer ways to write that test than the silly minimal version I > did for fun. Like format it, and set b to 0 vs undefined. :-) Can also do > "var t=b;b=a;a=t;" vs the destructuring style in case this was a problem > before ES2015 features were added to the code. > > I also realized saying this was only on x86-64 may not be true. In this > particular case it worked but without knowing the cause of the issue, it > could be a problem on other platforms. > > Since there's a easy to run test case, it may be possible to narrow it down > using 'git bisect' or similar. I started to do so with a partial git > checkout (the history is huge!). Fails at least back on 2016-03-13 (when > the build-jsc --jsc-only flag was added). So need to check earlier than > that.
It's OK, I got this.
Filip Pizlo
Comment 7
2016-11-02 14:23:23 PDT
(In reply to
comment #5
)
> This is a liveness analysis bug. There's a basic block like this: > > Block #2 (bc#42): > 124:<!0:-> ExitOK(MustGen, W:SideState, bc#42) > 113:< 1:-> Upsilon(Check:Untyped:@40, ^65, W:SideState, bc#42) > 111:< 1:-> Upsilon(Check:Untyped:@65, ^40, W:SideState, bc#42) > 96:<!0:-> Jump(MustGen, T:#3, W:SideState, bc#42) > > Clearly, @65 is live at the head of the block. But liveness analysis > reports this as live at the head: @1, @24, @27, @40, @43 > > Note that @65 is omitted.
Oh wait, no. It's not live. It's killed by the other Upsilon. Huh. This looks like a classic swap bug. Our SSA conversion strategy nominally works in the face of swaps. I'll have to think about this a bit more.
Filip Pizlo
Comment 8
2016-11-02 14:26:41 PDT
(In reply to
comment #7
)
> (In reply to
comment #5
) > > This is a liveness analysis bug. There's a basic block like this: > > > > Block #2 (bc#42): > > 124:<!0:-> ExitOK(MustGen, W:SideState, bc#42) > > 113:< 1:-> Upsilon(Check:Untyped:@40, ^65, W:SideState, bc#42) > > 111:< 1:-> Upsilon(Check:Untyped:@65, ^40, W:SideState, bc#42) > > 96:<!0:-> Jump(MustGen, T:#3, W:SideState, bc#42) > > > > Clearly, @65 is live at the head of the block. But liveness analysis > > reports this as live at the head: @1, @24, @27, @40, @43 > > > > Note that @65 is omitted. > > Oh wait, no. It's not live. It's killed by the other Upsilon. Huh. > > This looks like a classic swap bug. Our SSA conversion strategy nominally > works in the face of swaps. I'll have to think about this a bit more.
Ah! The problem is that we're playing fast and loose with Upsilon/Phi semantics. What we should be doing is saying that the Phi node is Upsilon-live between the point when the Upsilon assigns to it and the point where the Phi lives. Separately, the Phi itself is live like any other node would be. Just as we do this for liveness, we need to do this for forward flow. Phis have a separate shadow variable that represents what the Upsilon stored, and we load from that shadow variable once we define the Phi. Interestingly, B3 gets this completely right, but we forgot to port that intelligence to DFG.
Filip Pizlo
Comment 9
2016-11-02 14:50:35 PDT
Obviously, this quirk of Phi is not relevant to flow-insensitive analyses. It's interesting to wonder if it's relevant to flow-sensitive analyses. I think that it is, but as a hack, we could make flow-sensitive analysis behave conservatively right by having Phi itself not be flow-sensitive: i.e. have Upsilon merge rather than setting. I think you could do like a pseudo-flow-sensitivity. Seems like while that's intellectually interesting, we probably just want to have AI do the more precise thing. I think that we want to use the following strategy: 1) Phis have a secondary index in liveness analysis for their shadow value. 2) InPlaceAbstractState can have a second vector for shadow values. 3) BasicBlock can have a NodeProjection that wraps a Node* and adds a bit to say if this is the primary value or the shadow value. Then, liveAtHead, valuesAtHead, and all of their friends don't have to change significantly.
Filip Pizlo
Comment 10
2016-11-02 20:28:54 PDT
Created
attachment 293741
[details]
it's a start
Saam Barati
Comment 11
2016-11-02 23:26:36 PDT
Comment on
attachment 293741
[details]
it's a start View in context:
https://bugs.webkit.org/attachment.cgi?id=293741&action=review
> Source/JavaScriptCore/dfg/DFGLivenessAnalysisPhase.cpp:-124 > - // p: Phi()
Maybe this is too simplistic, but why can't we just solve the liveness bug by saying: p: Phi() => defs p n: Upsilon(@x, ^p) => defs n, uses x Maybe I'm not completely understanding the implications of such a rule?
Filip Pizlo
Comment 12
2016-11-03 07:02:22 PDT
(In reply to
comment #11
)
> Comment on
attachment 293741
[details]
> it's a start > > View in context: >
https://bugs.webkit.org/attachment.cgi?id=293741&action=review
> > > Source/JavaScriptCore/dfg/DFGLivenessAnalysisPhase.cpp:-124 > > - // p: Phi() > > Maybe this is too simplistic, but why can't we just solve the liveness bug > by saying: > > p: Phi() => defs p > > n: Upsilon(@x, ^p) => defs n, uses x > > Maybe I'm not completely understanding the implications of such a rule?
I don't see how your rule works. Upsilon is void so I don't know what it means to def it. Also in your rule nobody uses the upsilon.
Filip Pizlo
Comment 13
2016-11-03 09:13:08 PDT
Created
attachment 293766
[details]
this might even work
Filip Pizlo
Comment 14
2016-11-03 09:42:26 PDT
Created
attachment 293769
[details]
even more things compile
Filip Pizlo
Comment 15
2016-11-03 10:06:20 PDT
Created
attachment 293770
[details]
it compiles!
Filip Pizlo
Comment 16
2016-11-03 10:25:25 PDT
Created
attachment 293773
[details]
it passes the test It passes the test, but I don't know if it passes any other tests. I'll find out soon.
Filip Pizlo
Comment 17
2016-11-03 12:02:40 PDT
Created
attachment 293783
[details]
the patch I'm hoping that this is the one!
WebKit Commit Bot
Comment 18
2016-11-03 12:04:11 PDT
Attachment 293783
[details]
did not pass style-queue: ERROR: Source/JavaScriptCore/dfg/DFGInPlaceAbstractState.h:33: Alphabetical sorting problem. [build/include_order] [4] Total errors found: 1 in 29 files If any of these errors are false positives, please file a bug against check-webkit-style.
Keith Miller
Comment 19
2016-11-03 14:08:22 PDT
> ERROR: Source/JavaScriptCore/dfg/DFGInPlaceAbstractState.h:33: Alphabetical > sorting problem. [build/include_order] [4] > Total errors found: 1 in 29 files >
You should probably fix this
Filip Pizlo
Comment 20
2016-11-03 14:09:14 PDT
(In reply to
comment #19
)
> > ERROR: Source/JavaScriptCore/dfg/DFGInPlaceAbstractState.h:33: Alphabetical > > sorting problem. [build/include_order] [4] > > Total errors found: 1 in 29 files > > > > You should probably fix this
Ooops. Fixed.
Keith Miller
Comment 21
2016-11-03 14:09:55 PDT
Looks like your patch might also be making EWS a little flakey... hard to tell though.
Filip Pizlo
Comment 22
2016-11-03 14:10:52 PDT
(In reply to
comment #21
)
> Looks like your patch might also be making EWS a little flakey... hard to > tell though.
EWS has been flakey for my last ~5 patches.
Filip Pizlo
Comment 23
2016-11-03 15:11:22 PDT
Comment on
attachment 293783
[details]
the patch I'm seeing perf regressions. Investigating.
David I. Lehn
Comment 24
2016-11-03 15:36:00 PDT
How about updating the stress test code style and readability? May also need a comment to note you need the --useConcurrentJIT=false --useFTLJIT=true flags for it to fail with just 10000 iterations. Without those I need 500000 on my machine. Might just want to set it at 500000+ always just to make sure. var count = 0; function foo() { var a=1, b=0; for (var i = 0; i < 2; ++i){ [a, b] = [b, a]; count++; } if (a !== 1 || b !== 0) throw "Error: bad result at swap count: " + count; } noInline(foo); for (var k = 0; k < 10000; ++k) foo();
Filip Pizlo
Comment 25
2016-11-03 16:05:08 PDT
(In reply to
comment #24
)
> How about updating the stress test code style and readability?
We don't usually worry about the style of regression test code.
> May also > need a comment to note you need the --useConcurrentJIT=false > --useFTLJIT=true flags for it to fail with just 10000 iterations. Without > those I need 500000 on my machine. Might just want to set it at 500000+ > always just to make sure.
This is an invariant of almost all of our optimizing compiler tests. JSC developers already know this.
> > var count = 0; > function foo() { > var a=1, b=0; > for (var i = 0; i < 2; ++i){ > [a, b] = [b, a]; > count++; > } > if (a !== 1 || b !== 0) > throw "Error: bad result at swap count: " + count; > } > noInline(foo); > > for (var k = 0; k < 10000; ++k) > foo();
Filip Pizlo
Comment 26
2016-11-03 16:06:38 PDT
The reason for the perf regressions is that I forgot to fix IntegerRangeOptimizationPhase. That's also a flow-sensitive analysis.
Filip Pizlo
Comment 27
2016-11-03 16:16:09 PDT
Created
attachment 293818
[details]
possibly better patch I think I figured out most of the remaining perf regression. I will retest now.
Filip Pizlo
Comment 28
2016-11-03 16:55:47 PDT
Created
attachment 293829
[details]
the patch
WebKit Commit Bot
Comment 29
2016-11-03 16:56:45 PDT
Attachment 293829
[details]
did not pass style-queue: ERROR: Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp:1532: When wrapping a line, only indent 4 spaces. [whitespace/indent] [3] Total errors found: 1 in 30 files If any of these errors are false positives, please file a bug against check-webkit-style.
Filip Pizlo
Comment 30
2016-11-03 17:18:17 PDT
This looks to be perf-neutral now.
Keith Miller
Comment 31
2016-11-03 17:24:51 PDT
Comment on
attachment 293829
[details]
the patch View in context:
https://bugs.webkit.org/attachment.cgi?id=293829&action=review
I'll look further later. Something came up and I have to go.
> JSTests/stress/dfg-ssa-swap.js:4 > + var a=1,b;for(i=0;i<2;++i){[a,b]=[b,a];c++}if(!a^b)throw c
why? my eyes...
> Source/JavaScriptCore/dfg/DFGCombinedLiveness.h:42 > +// values if you treat Upsilon as an opaque escape, and all of the clinets of CombinedLiveness do so.
clients.
Saam Barati
Comment 32
2016-11-03 17:25:12 PDT
Comment on
attachment 293829
[details]
the patch View in context:
https://bugs.webkit.org/attachment.cgi?id=293829&action=review
> Source/JavaScriptCore/ChangeLog:28 > + Unfortunately, DFG'a liveness analysis, abstract interpreter, and integer range optimization
DFG'a => DFG's
Filip Pizlo
Comment 33
2016-11-03 17:43:44 PDT
Created
attachment 293836
[details]
the patch Fixed a bug in LivenessAnalysis.
Filip Pizlo
Comment 34
2016-11-03 17:45:14 PDT
(In reply to
comment #31
)
> Comment on
attachment 293829
[details]
> the patch > > View in context: >
https://bugs.webkit.org/attachment.cgi?id=293829&action=review
> > I'll look further later. Something came up and I have to go. > > > JSTests/stress/dfg-ssa-swap.js:4 > > + var a=1,b;for(i=0;i<2;++i){[a,b]=[b,a];c++}if(!a^b)throw c > > why? my eyes...
Just copy-pasted the crashing code.
> > > Source/JavaScriptCore/dfg/DFGCombinedLiveness.h:42 > > +// values if you treat Upsilon as an opaque escape, and all of the clinets of CombinedLiveness do so. > > clients.
Fixed!
Filip Pizlo
Comment 35
2016-11-03 17:45:24 PDT
(In reply to
comment #32
)
> Comment on
attachment 293829
[details]
> the patch > > View in context: >
https://bugs.webkit.org/attachment.cgi?id=293829&action=review
> > > Source/JavaScriptCore/ChangeLog:28 > > + Unfortunately, DFG'a liveness analysis, abstract interpreter, and integer range optimization > > DFG'a => DFG's
Fixed!
Filip Pizlo
Comment 36
2016-11-03 17:46:08 PDT
Created
attachment 293837
[details]
the patch More fixes.
WebKit Commit Bot
Comment 37
2016-11-03 17:46:12 PDT
Attachment 293836
[details]
did not pass style-queue: ERROR: Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp:1532: When wrapping a line, only indent 4 spaces. [whitespace/indent] [3] Total errors found: 1 in 30 files If any of these errors are false positives, please file a bug against check-webkit-style.
WebKit Commit Bot
Comment 38
2016-11-03 17:49:21 PDT
Attachment 293837
[details]
did not pass style-queue: ERROR: Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp:1532: When wrapping a line, only indent 4 spaces. [whitespace/indent] [3] Total errors found: 1 in 30 files If any of these errors are false positives, please file a bug against check-webkit-style.
Mark Lam
Comment 39
2016-11-03 18:42:46 PDT
Comment on
attachment 293829
[details]
the patch View in context:
https://bugs.webkit.org/attachment.cgi?id=293829&action=review
>>> JSTests/stress/dfg-ssa-swap.js:4 >>> + var a=1,b;for(i=0;i<2;++i){[a,b]=[b,a];c++}if(!a^b)throw c >> >> why? my eyes... > > Just copy-pasted the crashing code.
Here's a version that won't hurt your eyes: function foo() { var a = 1, b = 0; var i = 2; do { var c = a; a = b; b = c; } while (--i); if (!a) throw "Error"; }
David I. Lehn
Comment 40
2016-11-03 18:56:09 PDT
(In reply to
comment #34
)
> (In reply to
comment #31
) > > Comment on
attachment 293829
[details]
> > > JSTests/stress/dfg-ssa-swap.js:4 > > > + var a=1,b;for(i=0;i<2;++i){[a,b]=[b,a];c++}if(!a^b)throw c > > > > why? my eyes... > > Just copy-pasted the crashing code. >
Sorry I posted the crazy code without a sane version! Test code style may not matter but readability does. Please consider using the
comment #24
version or similar so other eyes stay safe. Thanks.
Saam Barati
Comment 41
2016-11-03 19:33:11 PDT
Comment on
attachment 293837
[details]
the patch View in context:
https://bugs.webkit.org/attachment.cgi?id=293837&action=review
r=me
> Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:2820 > + forNode(node) = forNode(NodeFlowProjection(node, NodeFlowProjection::Shadow));
Nice
> Source/JavaScriptCore/dfg/DFGLivenessAnalysisPhase.cpp:87 > + const Vector<unsigned, 0, UnsafeVectorOverflow, 1>& > + liveAtHeadIndices = m_liveAtHead[blockIndex];
Style nit: I feel like this should go on one line.
> Source/JavaScriptCore/dfg/DFGLivenessAnalysisPhase.cpp:96 > + const HashSet<unsigned, DefaultHash<unsigned>::Hash, WTF::UnsignedWithZeroKeyHashTraits<unsigned>>& > + liveAtTailIndices = m_liveAtTail[blockIndex];
Ditto
> Source/JavaScriptCore/dfg/DFGNodeFlowProjection.h:46 > + {
Maybe worth an assert that kind()==Primary?
> Source/JavaScriptCore/dfg/DFGNodeFlowProjection.h:51 > + {
Maybe worth an assert that kind()==kind?
Filip Pizlo
Comment 42
2016-11-03 19:35:07 PDT
(In reply to
comment #41
)
> Comment on
attachment 293837
[details]
> the patch > > View in context: >
https://bugs.webkit.org/attachment.cgi?id=293837&action=review
> > r=me > > > Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:2820 > > + forNode(node) = forNode(NodeFlowProjection(node, NodeFlowProjection::Shadow)); > > Nice > > > Source/JavaScriptCore/dfg/DFGLivenessAnalysisPhase.cpp:87 > > + const Vector<unsigned, 0, UnsafeVectorOverflow, 1>& > > + liveAtHeadIndices = m_liveAtHead[blockIndex]; > > Style nit: I feel like this should go on one line.
OK.
> > > Source/JavaScriptCore/dfg/DFGLivenessAnalysisPhase.cpp:96 > > + const HashSet<unsigned, DefaultHash<unsigned>::Hash, WTF::UnsignedWithZeroKeyHashTraits<unsigned>>& > > + liveAtTailIndices = m_liveAtTail[blockIndex]; > > Ditto
Fixed.
> > > Source/JavaScriptCore/dfg/DFGNodeFlowProjection.h:46 > > + { > > Maybe worth an assert that kind()==Primary?
Fixed!
> > > Source/JavaScriptCore/dfg/DFGNodeFlowProjection.h:51 > > + { > > Maybe worth an assert that kind()==kind?
Fixed!
Filip Pizlo
Comment 43
2016-11-03 19:41:33 PDT
Landed in
https://trac.webkit.org/changeset/208364
WebKit Commit Bot
Comment 44
2016-11-03 21:38:48 PDT
Re-opened since this is blocked by
bug 164402
Filip Pizlo
Comment 45
2016-11-03 21:52:52 PDT
Created
attachment 293861
[details]
patch for relanding
WebKit Commit Bot
Comment 46
2016-11-03 21:54:57 PDT
Attachment 293861
[details]
did not pass style-queue: ERROR: Source/JavaScriptCore/dfg/DFGIntegerRangeOptimizationPhase.cpp:1532: When wrapping a line, only indent 4 spaces. [whitespace/indent] [3] Total errors found: 1 in 30 files If any of these errors are false positives, please file a bug against check-webkit-style.
Filip Pizlo
Comment 47
2016-11-03 22:32:54 PDT
Landed in
https://trac.webkit.org/changeset/208373
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug