Title: [222115] trunk
Revision
222115
Author
sbar...@apple.com
Date
2017-09-15 16:27:56 -0700 (Fri, 15 Sep 2017)

Log Message

Arity fixup during inlining should do a 2 phase commit so it properly recovers the frame in case of exit
https://bugs.webkit.org/show_bug.cgi?id=176981

Reviewed by Yusuke Suzuki.

JSTests:

* stress/exit-during-inlined-arity-fixup-recover-proper-frame.js: Added.
(assert):
(verify):
(func):
(const.bar.createBuiltin):

Source/_javascript_Core:

This patch makes inline arity fixup happen in two phases:
1. We get all the values we need and MovHint them to the expected locals.
2. We SetLocal them inside the callee's CodeOrigin. This way, if we exit, the callee's
   frame is already set up. If any SetLocal exits, we have a valid exit state.
   This is required because if we didn't do this in two phases, we may exit in
   the middle of arity fixup from the caller's CodeOrigin. This is unsound because if
   we did the SetLocals in the caller's frame, the memcpy may clobber needed parts
   of the frame right before exiting. For example, consider if we need to pad two args:
   [arg3][arg2][arg1][arg0]
   [fix ][fix ][arg3][arg2][arg1][arg0]
   We memcpy starting from arg0 in the direction of arg3. If we were to exit at a type check
   for arg3's SetLocal in the caller's CodeOrigin, we'd exit with a frame like so:
   [arg3][arg2][arg1][arg2][arg1][arg0]
   And the caller would then just end up thinking its argument are:
   [arg3][arg2][arg1][arg2]
   which is incorrect.

This patch also fixes a couple of bugs in IdentitiyWithProfile:
1. The bytecode generator for this bytecode intrinsic was written incorrectly.
   It needed to store the result of evaluating its argument in a temporary that
   it creates. Otherwise, it might try to simply overwrite a constant
   or a register that it didn't own.
2. We weren't eliminating this node in CSE inside the DFG.

* bytecompiler/NodesCodegen.cpp:
(JSC::BytecodeIntrinsicNode::emit_intrinsic_idWithProfile):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::inlineCall):
* dfg/DFGCSEPhase.cpp:

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (222114 => 222115)


--- trunk/JSTests/ChangeLog	2017-09-15 22:18:51 UTC (rev 222114)
+++ trunk/JSTests/ChangeLog	2017-09-15 23:27:56 UTC (rev 222115)
@@ -1,3 +1,16 @@
+2017-09-15  Saam Barati  <sbar...@apple.com>
+
+        Arity fixup during inlining should do a 2 phase commit so it properly recovers the frame in case of exit
+        https://bugs.webkit.org/show_bug.cgi?id=176981
+
+        Reviewed by Yusuke Suzuki.
+
+        * stress/exit-during-inlined-arity-fixup-recover-proper-frame.js: Added.
+        (assert):
+        (verify):
+        (func):
+        (const.bar.createBuiltin):
+
 2017-09-14  Saam Barati  <sbar...@apple.com>
 
         It should be valid to exit before each set when doing arity fixup when inlining

Added: trunk/JSTests/stress/exit-during-inlined-arity-fixup-recover-proper-frame.js (0 => 222115)


--- trunk/JSTests/stress/exit-during-inlined-arity-fixup-recover-proper-frame.js	                        (rev 0)
+++ trunk/JSTests/stress/exit-during-inlined-arity-fixup-recover-proper-frame.js	2017-09-15 23:27:56 UTC (rev 222115)
@@ -0,0 +1,31 @@
+let i;
+function verify(a, b, c, d, e, f) {
+    function assert(b, m) {
+        if (!b)
+            throw new Error(m);
+    }
+    assert(a === i);
+    assert(b === i+1);
+    assert(c === i+2);
+    assert(d === null);
+    assert(e === undefined);
+    assert(f === undefined);
+}
+noInline(verify);
+
+function func(a, b, c, d, e, f)
+{
+    verify(a, b, c, d, e, f);
+    return !!(a%2) ? a + b + c + d : a + b + c + d;
+}
+
+const bar = createBuiltin(`(function (f, a, b, c, d) {
+    let y = @idWithProfile(null, "SpecInt32Only");
+    return f(a, b, c, y);
+})`);
+
+noInline(bar);
+
+for (i = 0; i < 1000; ++i) {
+    bar(func, i, i+1, i+2, i+3);
+}

Modified: trunk/Source/_javascript_Core/ChangeLog (222114 => 222115)


--- trunk/Source/_javascript_Core/ChangeLog	2017-09-15 22:18:51 UTC (rev 222114)
+++ trunk/Source/_javascript_Core/ChangeLog	2017-09-15 23:27:56 UTC (rev 222115)
@@ -1,3 +1,41 @@
+2017-09-15  Saam Barati  <sbar...@apple.com>
+
+        Arity fixup during inlining should do a 2 phase commit so it properly recovers the frame in case of exit
+        https://bugs.webkit.org/show_bug.cgi?id=176981
+
+        Reviewed by Yusuke Suzuki.
+
+        This patch makes inline arity fixup happen in two phases:
+        1. We get all the values we need and MovHint them to the expected locals.
+        2. We SetLocal them inside the callee's CodeOrigin. This way, if we exit, the callee's
+           frame is already set up. If any SetLocal exits, we have a valid exit state.
+           This is required because if we didn't do this in two phases, we may exit in
+           the middle of arity fixup from the caller's CodeOrigin. This is unsound because if
+           we did the SetLocals in the caller's frame, the memcpy may clobber needed parts
+           of the frame right before exiting. For example, consider if we need to pad two args:
+           [arg3][arg2][arg1][arg0]
+           [fix ][fix ][arg3][arg2][arg1][arg0]
+           We memcpy starting from arg0 in the direction of arg3. If we were to exit at a type check
+           for arg3's SetLocal in the caller's CodeOrigin, we'd exit with a frame like so:
+           [arg3][arg2][arg1][arg2][arg1][arg0]
+           And the caller would then just end up thinking its argument are:
+           [arg3][arg2][arg1][arg2]
+           which is incorrect.
+       
+       
+        This patch also fixes a couple of bugs in IdentitiyWithProfile:
+        1. The bytecode generator for this bytecode intrinsic was written incorrectly.
+           It needed to store the result of evaluating its argument in a temporary that
+           it creates. Otherwise, it might try to simply overwrite a constant
+           or a register that it didn't own.
+        2. We weren't eliminating this node in CSE inside the DFG.
+
+        * bytecompiler/NodesCodegen.cpp:
+        (JSC::BytecodeIntrinsicNode::emit_intrinsic_idWithProfile):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::inlineCall):
+        * dfg/DFGCSEPhase.cpp:
+
 2017-09-15  JF Bastien  <jfbast...@apple.com>
 
         WTF: use Forward.h when appropriate instead of Vector.h

Modified: trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp (222114 => 222115)


--- trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2017-09-15 22:18:51 UTC (rev 222114)
+++ trunk/Source/_javascript_Core/bytecompiler/NodesCodegen.cpp	2017-09-15 23:27:56 UTC (rev 222115)
@@ -1026,9 +1026,9 @@
 
 RegisterID* BytecodeIntrinsicNode::emit_intrinsic_idWithProfile(BytecodeGenerator& generator, RegisterID* dst)
 {
-
     ArgumentListNode* node = m_args->m_listNode;
-    RefPtr<RegisterID> idValue = generator.emitNode(node);
+    RefPtr<RegisterID> idValue = generator.newTemporary();
+    generator.emitNode(idValue.get(), node);
     SpeculatedType speculation = SpecNone;
     while (node->m_next) {
         node = node->m_next;

Modified: trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp (222114 => 222115)


--- trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp	2017-09-15 22:18:51 UTC (rev 222114)
+++ trunk/Source/_javascript_Core/dfg/DFGByteCodeParser.cpp	2017-09-15 23:27:56 UTC (rev 222115)
@@ -1577,19 +1577,29 @@
         calleeVariable = calleeSet->variableAccessData();
         calleeVariable->mergeShouldNeverUnbox(true);
     }
-    
+
+    Vector<std::pair<Node*, VirtualRegister>> delayedAritySets;
+
     if (arityFixupCount) {
+        // Note: we do arity fixup in two phases:
+        // 1. We get all the values we need and MovHint them to the expected locals.
+        // 2. We SetLocal them inside the callee's CodeOrigin. This way, if we exit, the callee's
+        //    frame is already set up. If any SetLocal exits, we have a valid exit state.
+        //    This is required because if we didn't do this in two phases, we may exit in
+        //    the middle of arity fixup from the caller's CodeOrigin. This is unsound because if
+        //    we did the SetLocals in the caller's frame, the memcpy may clobber needed parts
+        //    of the frame right before exiting. For example, consider if we need to pad two args:
+        //    [arg3][arg2][arg1][arg0]
+        //    [fix ][fix ][arg3][arg2][arg1][arg0]
+        //    We memcpy starting from arg0 in the direction of arg3. If we were to exit at a type check
+        //    for arg3's SetLocal in the caller's CodeOrigin, we'd exit with a frame like so:
+        //    [arg3][arg2][arg1][arg2][arg1][arg0]
+        //    And the caller would then just end up thinking its argument are:
+        //    [arg3][arg2][arg1][arg2]
+        //    which is incorrect.
+
         Node* undefined = addToGraph(JSConstant, OpInfo(m_constantUndefined));
-        auto fill = [&] (VirtualRegister reg, Node* value) {
-            // It's valid to exit here since we'll exit to the top of the
-            // call and re-setup the arguments.
-            m_exitOK = true;
-            addToGraph(ExitOK);
-
-            set(reg, value, ImmediateNakedSet);
-        };
-
-        // The stack needs to be aligned due to ABIs. Thus, we have a hole if the count of arguments is not aligned.
+        // The stack needs to be aligned due to the JS calling convention. Thus, we have a hole if the count of arguments is not aligned.
         // We call this hole "extra slot". Consider the following case, the number of arguments is 2. If this argument
         // count does not fulfill the stack alignment requirement, we already inserted extra slots.
         //
@@ -1603,11 +1613,21 @@
         //
         // In such cases, we do not need to move frames.
         if (registerOffsetAfterFixup != registerOffset) {
-            for (int index = 0; index < argumentCountIncludingThis; ++index)
-                fill(virtualRegisterForArgument(index, registerOffsetAfterFixup), get(virtualRegisterForArgument(index, registerOffset)));
+            for (int index = 0; index < argumentCountIncludingThis; ++index) {
+                Node* value = get(virtualRegisterForArgument(index, registerOffset));
+                VirtualRegister argumentToSet = m_inlineStackTop->remapOperand(virtualRegisterForArgument(index, registerOffsetAfterFixup));
+                addToGraph(MovHint, OpInfo(argumentToSet.offset()), value);
+                m_setLocalQueue.append(DelayedSetLocal { currentCodeOrigin(), argumentToSet, value, ImmediateNakedSet });
+            }
         }
-        for (int index = 0; index < arityFixupCount; ++index)
-            fill(virtualRegisterForArgument(argumentCountIncludingThis + index, registerOffsetAfterFixup), undefined);
+        for (int index = 0; index < arityFixupCount; ++index) {
+            VirtualRegister argumentToSet = m_inlineStackTop->remapOperand(virtualRegisterForArgument(argumentCountIncludingThis + index, registerOffsetAfterFixup));
+            addToGraph(MovHint, OpInfo(argumentToSet.offset()), undefined);
+            m_setLocalQueue.append(DelayedSetLocal { currentCodeOrigin(), argumentToSet, undefined, ImmediateNakedSet });
+        }
+
+        // At this point, it's OK to OSR exit because we finished setting up
+        // our callee's frame. We emit an ExitOK below from the callee's CodeOrigin.
     }
 
     InlineStackEntry inlineStackEntry(
@@ -1620,7 +1640,10 @@
 
     // At this point, it's again OK to OSR exit.
     m_exitOK = true;
+    addToGraph(ExitOK);
 
+    processSetLocalQueue();
+
     InlineVariableData inlineVariableData;
     inlineVariableData.inlineCallFrame = m_inlineStackTop->m_inlineCallFrame;
     inlineVariableData.argumentPositionStart = argumentPositionStart;

Modified: trunk/Source/_javascript_Core/dfg/DFGCSEPhase.cpp (222114 => 222115)


--- trunk/Source/_javascript_Core/dfg/DFGCSEPhase.cpp	2017-09-15 22:18:51 UTC (rev 222114)
+++ trunk/Source/_javascript_Core/dfg/DFGCSEPhase.cpp	2017-09-15 23:27:56 UTC (rev 222115)
@@ -448,7 +448,7 @@
                 m_node = block->at(nodeIndex);
                 m_graph.performSubstitution(m_node);
             
-                if (m_node->op() == Identity) {
+                if (m_node->op() == Identity || m_node->op() == IdentityWithProfile) {
                     m_node->replaceWith(m_node->child1().node());
                     m_changed = true;
                 } else {
@@ -651,7 +651,7 @@
                 
                 m_graph.performSubstitution(m_node);
                 
-                if (m_node->op() == Identity) {
+                if (m_node->op() == Identity || m_node->op() == IdentityWithProfile) {
                     m_node->replaceWith(m_node->child1().node());
                     m_changed = true;
                 } else
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to