Title: [225202] trunk
Revision
225202
Author
sbar...@apple.com
Date
2017-11-27 16:14:07 -0800 (Mon, 27 Nov 2017)

Log Message

Spread can escape when CreateRest does not
https://bugs.webkit.org/show_bug.cgi?id=180057
<rdar://problem/35676119>

Reviewed by JF Bastien.

JSTests:

* stress/spread-escapes-but-create-rest-does-not.js: Added.
(assert):
(getProperties):
(theFunc):
(let.obj.valueOf):

Source/_javascript_Core:

We previously did not handle Spread(PhantomCreateRest) only because I did not
think it was possible to generate this IR. I was wrong. We can generate
such IR when we have a PutStack(Spread) but nothing escapes the CreateRest.
This IR is rare to generate since we normally don't PutStack(Spread) because
the SetLocal almost always gets eliminated because of how our bytecode generates
op_spread. However, there exists a test case showing it is possible. Supporting
this IR pattern in FTLLower is trivial. This patch implements it and rewrites
the Validation rule for Spread.

* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGValidate.cpp:
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileSpread):
* runtime/JSFixedArray.h:
(JSC::JSFixedArray::tryCreate):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (225201 => 225202)


--- trunk/JSTests/ChangeLog	2017-11-27 23:51:21 UTC (rev 225201)
+++ trunk/JSTests/ChangeLog	2017-11-28 00:14:07 UTC (rev 225202)
@@ -1,3 +1,17 @@
+2017-11-27  Saam Barati  <sbar...@apple.com>
+
+        Spread can escape when CreateRest does not
+        https://bugs.webkit.org/show_bug.cgi?id=180057
+        <rdar://problem/35676119>
+
+        Reviewed by JF Bastien.
+
+        * stress/spread-escapes-but-create-rest-does-not.js: Added.
+        (assert):
+        (getProperties):
+        (theFunc):
+        (let.obj.valueOf):
+
 2017-11-21  Yusuke Suzuki  <utatane....@gmail.com>
 
         [DFG] Add NormalizeMapKey DFG IR

Added: trunk/JSTests/stress/spread-escapes-but-create-rest-does-not.js (0 => 225202)


--- trunk/JSTests/stress/spread-escapes-but-create-rest-does-not.js	                        (rev 0)
+++ trunk/JSTests/stress/spread-escapes-but-create-rest-does-not.js	2017-11-28 00:14:07 UTC (rev 225202)
@@ -0,0 +1,35 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+noInline(assert);
+
+function getProperties(obj) {
+    let properties = [];
+    for (let name of Object.getOwnPropertyNames(obj)) {
+        properties.push(name);
+    }
+    return properties;
+}
+
+function theFunc(obj, index, ...args) {
+    let functions = getProperties(obj);
+    let func = functions[index % functions.length];
+    obj[func](...args);
+}
+
+let o = {};
+let obj = {
+    valueOf: function (x, y) {
+        assert(x === 42);
+        assert(y === o);
+        try {
+        } catch (e) {}
+    }
+};
+
+for (let i = 0; i < 1e5; ++i) {
+    for (let _i = 0; _i < 100; _i++) {
+    }
+    theFunc(obj, 897989, 42, o);
+}

Modified: trunk/Source/_javascript_Core/ChangeLog (225201 => 225202)


--- trunk/Source/_javascript_Core/ChangeLog	2017-11-27 23:51:21 UTC (rev 225201)
+++ trunk/Source/_javascript_Core/ChangeLog	2017-11-28 00:14:07 UTC (rev 225202)
@@ -1,3 +1,28 @@
+2017-11-27  Saam Barati  <sbar...@apple.com>
+
+        Spread can escape when CreateRest does not
+        https://bugs.webkit.org/show_bug.cgi?id=180057
+        <rdar://problem/35676119>
+
+        Reviewed by JF Bastien.
+
+        We previously did not handle Spread(PhantomCreateRest) only because I did not
+        think it was possible to generate this IR. I was wrong. We can generate
+        such IR when we have a PutStack(Spread) but nothing escapes the CreateRest.
+        This IR is rare to generate since we normally don't PutStack(Spread) because
+        the SetLocal almost always gets eliminated because of how our bytecode generates
+        op_spread. However, there exists a test case showing it is possible. Supporting
+        this IR pattern in FTLLower is trivial. This patch implements it and rewrites
+        the Validation rule for Spread.
+
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGValidate.cpp:
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileSpread):
+        * runtime/JSFixedArray.h:
+        (JSC::JSFixedArray::tryCreate):
+
 2017-11-27  Don Olmstead  <don.olmst...@sony.com>
 
         [CMake][Win] Conditionally select DLL CRT or static CRT

Modified: trunk/Source/_javascript_Core/dfg/DFGOperations.cpp (225201 => 225202)


--- trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2017-11-27 23:51:21 UTC (rev 225201)
+++ trunk/Source/_javascript_Core/dfg/DFGOperations.cpp	2017-11-28 00:14:07 UTC (rev 225202)
@@ -2384,6 +2384,19 @@
     return result;
 }
 
+JSCell* operationCreateFixedArray(ExecState* exec, unsigned length)
+{
+    VM& vm = exec->vm();
+    NativeCallFrameTracer tracer(&vm, exec);
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    if (JSFixedArray* result = JSFixedArray::tryCreate(vm, vm.fixedArrayStructure.get(), length))
+        return result;
+
+    throwOutOfMemoryError(exec, scope);
+    return nullptr;
+}
+
 JSCell* JIT_OPERATION operationSpreadGeneric(ExecState* exec, JSCell* iterable)
 {
     VM& vm = exec->vm();

Modified: trunk/Source/_javascript_Core/dfg/DFGOperations.h (225201 => 225202)


--- trunk/Source/_javascript_Core/dfg/DFGOperations.h	2017-11-27 23:51:21 UTC (rev 225201)
+++ trunk/Source/_javascript_Core/dfg/DFGOperations.h	2017-11-28 00:14:07 UTC (rev 225202)
@@ -235,6 +235,7 @@
 JSCell* JIT_OPERATION operationSpreadFastArray(ExecState*, JSCell*);
 JSCell* JIT_OPERATION operationSpreadGeneric(ExecState*, JSCell*);
 JSCell* JIT_OPERATION operationNewArrayWithSpreadSlow(ExecState*, void*, uint32_t);
+JSCell* JIT_OPERATION operationCreateFixedArray(ExecState*, unsigned length);
 
 JSCell* JIT_OPERATION operationResolveScope(ExecState*, JSScope*, UniquedStringImpl*);
 EncodedJSValue JIT_OPERATION operationResolveScopeForHoistingFuncDeclInEval(ExecState*, JSScope*, UniquedStringImpl*);

Modified: trunk/Source/_javascript_Core/dfg/DFGValidate.cpp (225201 => 225202)


--- trunk/Source/_javascript_Core/dfg/DFGValidate.cpp	2017-11-27 23:51:21 UTC (rev 225201)
+++ trunk/Source/_javascript_Core/dfg/DFGValidate.cpp	2017-11-28 00:14:07 UTC (rev 225202)
@@ -790,6 +790,10 @@
                     break;
                 }
 
+                case Spread:
+                    VALIDATE((node), !node->child1()->isPhantomAllocation() || node->child1()->op() == PhantomCreateRest);
+                    break;
+
                 case EntrySwitch:
                     VALIDATE((node), node->entrySwitchData()->cases.size() == m_graph.m_numberOfEntrypoints);
                     break;

Modified: trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp (225201 => 225202)


--- trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp	2017-11-27 23:51:21 UTC (rev 225201)
+++ trunk/Source/_javascript_Core/ftl/FTLLowerDFGToB3.cpp	2017-11-28 00:14:07 UTC (rev 225202)
@@ -5273,13 +5273,56 @@
 
     void compileSpread()
     {
-        // It would be trivial to support this, but for now, we never create
-        // IR that would necessitate this. The reason is that Spread is only
-        // consumed by NewArrayWithSpread and Varargs operations. And it is
-        // never anything else. Also, any Spread(PhantomCreateRest) will turn
-        // into PhantomSpread(PhantomCreateRest).
-        RELEASE_ASSERT(m_node->child1()->op() != PhantomCreateRest); 
+        if (m_node->child1()->op() == PhantomCreateRest) {
+            // This IR is rare to generate since it requires escaping the Spread
+            // but not the CreateRest. In bytecode, we have only few operations that
+            // accept Spread's result as input. This usually leads to the Spread node not
+            // escaping. However, this can happen if for example we generate a PutStack on
+            // the Spread but nothing escapes the CreateRest.
+            LBasicBlock loopHeader = m_out.newBlock();
+            LBasicBlock loopBody = m_out.newBlock();
+            LBasicBlock slowAllocation = m_out.newBlock();
+            LBasicBlock continuation = m_out.newBlock();
+            LBasicBlock lastNext = m_out.insertNewBlocksBefore(loopHeader);
 
+            InlineCallFrame* inlineCallFrame = m_node->child1()->origin.semantic.inlineCallFrame;
+            unsigned numberOfArgumentsToSkip = m_node->child1()->numberOfArgumentsToSkip();
+            LValue sourceStart = getArgumentsStart(inlineCallFrame, numberOfArgumentsToSkip);
+            LValue length = getSpreadLengthFromInlineCallFrame(inlineCallFrame, numberOfArgumentsToSkip);
+            static_assert(sizeof(JSValue) == 8 && 1 << 3 == 8, "Assumed in the code below.");
+            LValue size = m_out.add(
+                m_out.shl(m_out.zeroExtPtr(length), m_out.constInt32(3)),
+                m_out.constIntPtr(JSFixedArray::offsetOfData()));
+
+            LValue fastArrayValue = allocateVariableSizedCell<JSFixedArray>(size, m_graph.m_vm.fixedArrayStructure.get(), slowAllocation);
+            m_out.store32(length, fastArrayValue, m_heaps.JSFixedArray_size);
+            ValueFromBlock fastArray = m_out.anchor(fastArrayValue);
+            m_out.jump(loopHeader);
+
+            m_out.appendTo(slowAllocation, loopHeader);
+            ValueFromBlock slowArray = m_out.anchor(vmCall(Int64, m_out.operation(operationCreateFixedArray), m_callFrame, length));
+            m_out.jump(loopHeader);
+
+            m_out.appendTo(loopHeader, loopBody);
+            LValue fixedArray = m_out.phi(Int64, fastArray, slowArray);
+            ValueFromBlock startIndex = m_out.anchor(m_out.constIntPtr(0));
+            m_out.branch(m_out.isZero32(length), unsure(continuation), unsure(loopBody));
+
+            m_out.appendTo(loopBody, continuation);
+            LValue index = m_out.phi(pointerType(), startIndex);
+            LValue value = m_out.load64(
+                m_out.baseIndex(m_heaps.variables, sourceStart, index));
+            m_out.store64(value, m_out.baseIndex(m_heaps.JSFixedArray_buffer, fixedArray, index));
+            LValue nextIndex = m_out.add(m_out.constIntPtr(1), index);
+            m_out.addIncomingToPhi(index, m_out.anchor(nextIndex));
+            m_out.branch(m_out.below(nextIndex, m_out.zeroExtPtr(length)), unsure(loopBody), unsure(continuation));
+
+            m_out.appendTo(continuation, lastNext);
+            mutatorFence();
+            setJSValue(fixedArray);
+            return;
+        }
+
         LValue argument = lowCell(m_node->child1());
 
         LValue result;

Modified: trunk/Source/_javascript_Core/runtime/JSFixedArray.h (225201 => 225202)


--- trunk/Source/_javascript_Core/runtime/JSFixedArray.h	2017-11-27 23:51:21 UTC (rev 225201)
+++ trunk/Source/_javascript_Core/runtime/JSFixedArray.h	2017-11-28 00:14:07 UTC (rev 225202)
@@ -43,6 +43,20 @@
         return Structure::create(vm, globalObject, prototype, TypeInfo(JSFixedArrayType, StructureFlags), info());
     }
 
+    ALWAYS_INLINE static JSFixedArray* tryCreate(VM& vm, Structure* structure, unsigned size)
+    {
+        Checked<size_t, RecordOverflow> checkedAllocationSize = allocationSize(size);
+        if (UNLIKELY(checkedAllocationSize.hasOverflowed()))
+            return nullptr;
+
+        void* buffer = tryAllocateCell<JSFixedArray>(vm.heap, checkedAllocationSize.unsafeGet());
+        if (UNLIKELY(!buffer))
+            return nullptr;
+        JSFixedArray* result = new (NotNull, buffer) JSFixedArray(vm, structure, size);
+        result->finishCreation(vm);
+        return result;
+    }
+
     ALWAYS_INLINE static JSFixedArray* createFromArray(ExecState* exec, VM& vm, JSArray* array)
     {
         auto throwScope = DECLARE_THROW_SCOPE(vm);
@@ -118,23 +132,6 @@
     void copyToArguments(ExecState*, VirtualRegister firstElementDest, unsigned offset, unsigned length);
 
 private:
-    unsigned m_size;
-
-    ALWAYS_INLINE static JSFixedArray* tryCreate(VM& vm, Structure* structure, unsigned size)
-    {
-        Checked<size_t, RecordOverflow> checkedAllocationSize = allocationSize(size);
-        if (UNLIKELY(checkedAllocationSize.hasOverflowed()))
-            return nullptr;
-
-        void* buffer = tryAllocateCell<JSFixedArray>(vm.heap, checkedAllocationSize.unsafeGet());
-        if (UNLIKELY(!buffer))
-            return nullptr;
-        JSFixedArray* result = new (NotNull, buffer) JSFixedArray(vm, structure, size);
-        result->finishCreation(vm);
-        return result;
-    }
-
-
     JSFixedArray(VM& vm, Structure* structure, unsigned size)
         : Base(vm, structure)
         , m_size(size)
@@ -143,11 +140,12 @@
             buffer()[i].setStartingValue(JSValue());
     }
 
-
     static Checked<size_t, RecordOverflow> allocationSize(Checked<size_t, RecordOverflow> numItems)
     {
         return offsetOfData() + numItems * sizeof(WriteBarrier<Unknown>);
     }
+
+    unsigned m_size;
 };
 
 } // namespace JSC
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to