in_by_val inside structure property for-in loop should use an opcode like has_structu...
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 22 May 2020 22:31:13 +0000 (22:31 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 22 May 2020 22:31:13 +0000 (22:31 +0000)
https://bugs.webkit.org/show_bug.cgi?id=212239

Reviewed by Tadeu Zagallo.

JSTests:

* microbenchmarks/in-by-val-inside-for-in-loop.js: Added.
(assert):
(count):
* stress/in-by-val-inside-for-in-loop.js: Added.
(assert):
(test1.count):
(test1):
(test2.count):
(test2):
(test3.count):
(test3):
(test4.count):
(test4):
(test5.count):
(test5):
(test6.count):
(test6):
(test7.count):
(test7):
(test8.count):
(test8):

Source/JavaScriptCore:

There is code inside Speedometer 2 that is like:

```
for (let p in o) {
    if (p in o2)
        ...
}
```

Where o and o2 frequently share the same structure. Elm does this when it's
diffing two objects. We already optimize o2[p] (get_by_val) in the above loop
for structure properties. This patch adds that same optimization for in_by_val.
Because we already emit a "structure" loop for for-in, where we iterate structure
properties, the fast path for the above, where o and o2 have the same
structure is simply a structure check followed by return true.

This patch introduces the new opcode: op_in_structure_property. Its fast path is identical
to op_has_structure_property. Its slow path, however, behaves like "in", which
uses the HasProperty internal method, unlike op_has_structure_property,
which uses the GetOwnProperty internal method. This behavior difference is
observable using Proxy.

This a 5% perf improvement in the Elm subtest in Speedometer 2.

* bytecode/BytecodeList.rb:
* bytecode/BytecodeUseDef.cpp:
(JSC::computeUsesForBytecodeIndexImpl):
(JSC::computeDefsForBytecodeIndexImpl):
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitInByVal):
(JSC::rewriteOp):
(JSC::StructureForInContext::finalize):
* bytecompiler/BytecodeGenerator.h:
(JSC::StructureForInContext::addInInst):
* dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel):
* dfg/DFGClobberize.h:
(JSC::DFG::clobberize):
* dfg/DFGDoesGC.cpp:
(JSC::DFG::doesGC):
* dfg/DFGFixupPhase.cpp:
(JSC::DFG::FixupPhase::fixupNode):
* dfg/DFGNodeType.h:
* dfg/DFGOperations.cpp:
* dfg/DFGOperations.h:
* dfg/DFGPredictionPropagationPhase.cpp:
* dfg/DFGSafeToExecute.h:
(JSC::DFG::safeToExecute):
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileInStructureProperty):
* dfg/DFGSpeculativeJIT.h:
* dfg/DFGSpeculativeJIT32_64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLCapabilities.cpp:
(JSC::FTL::canCompile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileHasStructurePropertyImpl):
(JSC::FTL::DFG::LowerDFGToB3::compileHasStructureProperty):
(JSC::FTL::DFG::LowerDFGToB3::compileInStructureProperty):
* jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
(JSC::JIT::privateCompileSlowCases):
* jit/JIT.h:
* jit/JITOpcodes.cpp:
(JSC::JIT::emit_op_in_structure_property):
* jit/JITOpcodes32_64.cpp:
(JSC::JIT::emit_op_in_structure_property):
* llint/LLIntOffsetsExtractor.cpp:
* llint/LowLevelInterpreter.asm:
* llint/LowLevelInterpreter64.asm:
* runtime/CommonSlowPaths.cpp:
(JSC::SLOW_PATH_DECL):
* runtime/CommonSlowPaths.h:
* runtime/JSObject.cpp:
(JSC::JSObject::hasPropertyGeneric const):
* runtime/JSPropertyNameEnumerator.h:

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@262083 268f45cc-cd09-0410-ab3c-d52691b4dbfc

35 files changed:
JSTests/ChangeLog
JSTests/microbenchmarks/in-by-val-inside-for-in-loop.js [new file with mode: 0644]
JSTests/stress/in-by-val-inside-for-in-loop.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/BytecodeList.rb
Source/JavaScriptCore/bytecode/BytecodeUseDef.cpp
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp
Source/JavaScriptCore/bytecompiler/BytecodeGenerator.h
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGCapabilities.cpp
Source/JavaScriptCore/dfg/DFGClobberize.h
Source/JavaScriptCore/dfg/DFGDoesGC.cpp
Source/JavaScriptCore/dfg/DFGFixupPhase.cpp
Source/JavaScriptCore/dfg/DFGNodeType.h
Source/JavaScriptCore/dfg/DFGOperations.cpp
Source/JavaScriptCore/dfg/DFGOperations.h
Source/JavaScriptCore/dfg/DFGPredictionPropagationPhase.cpp
Source/JavaScriptCore/dfg/DFGSafeToExecute.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT32_64.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/ftl/FTLCapabilities.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/jit/JIT.cpp
Source/JavaScriptCore/jit/JIT.h
Source/JavaScriptCore/jit/JITOpcodes.cpp
Source/JavaScriptCore/jit/JITOpcodes32_64.cpp
Source/JavaScriptCore/llint/LLIntOffsetsExtractor.cpp
Source/JavaScriptCore/llint/LowLevelInterpreter.asm
Source/JavaScriptCore/llint/LowLevelInterpreter64.asm
Source/JavaScriptCore/runtime/CommonSlowPaths.cpp
Source/JavaScriptCore/runtime/CommonSlowPaths.h
Source/JavaScriptCore/runtime/JSPropertyNameEnumerator.h

index 62919d4..3059ea2 100644 (file)
@@ -1,3 +1,32 @@
+2020-05-22  Saam Barati  <sbarati@apple.com>
+
+        in_by_val inside structure property for-in loop should use an opcode like has_structure_property but for "in"
+        https://bugs.webkit.org/show_bug.cgi?id=212239
+
+        Reviewed by Tadeu Zagallo.
+
+        * microbenchmarks/in-by-val-inside-for-in-loop.js: Added.
+        (assert):
+        (count):
+        * stress/in-by-val-inside-for-in-loop.js: Added.
+        (assert):
+        (test1.count):
+        (test1):
+        (test2.count):
+        (test2):
+        (test3.count):
+        (test3):
+        (test4.count):
+        (test4):
+        (test5.count):
+        (test5):
+        (test6.count):
+        (test6):
+        (test7.count):
+        (test7):
+        (test8.count):
+        (test8):
+
 2020-05-22  Keith Miller  <keith_miller@apple.com>
 
         Checkpoint inlined call return handler needs an exception check when dispatching
diff --git a/JSTests/microbenchmarks/in-by-val-inside-for-in-loop.js b/JSTests/microbenchmarks/in-by-val-inside-for-in-loop.js
new file mode 100644 (file)
index 0000000..273ac2d
--- /dev/null
@@ -0,0 +1,25 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function count(o) {
+    let c = 0;
+    for (let p in o) {
+        if (p in o)
+            ++c;
+    }
+    return c;
+}
+noInline(count);
+
+let o = {
+    a:20,
+    b:30,
+    c:40,
+    d:50
+};
+
+for (let i = 0; i < 300000; ++i) {
+    assert(count(o) === 4);
+}
diff --git a/JSTests/stress/in-by-val-inside-for-in-loop.js b/JSTests/stress/in-by-val-inside-for-in-loop.js
new file mode 100644 (file)
index 0000000..7f976ed
--- /dev/null
@@ -0,0 +1,227 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+function test1() {
+    function count(o) {
+        let c = 0;
+        for (let p in o) {
+            if (p in o)
+                ++c;
+        }
+        return c;
+    }
+    noInline(count);
+
+    let o = {
+        a:20,
+        b:30,
+        c:40,
+        d:50
+    };
+
+    for (let i = 0; i < 1000; ++i) {
+        assert(count(o) === 4);
+    }
+}
+test1();
+
+function test2() {
+    function count(o) {
+        let c = 0;
+        for (let p in o) {
+            if (p === "a")
+                delete o.a;
+            if (p in o)
+                ++c;
+        }
+        return c;
+    }
+    noInline(count);
+
+    let o = {
+        a:20,
+        b:30,
+        c:40,
+        d:50
+    };
+
+    for (let i = 0; i < 1000; ++i) {
+        assert(count(o) === 3);
+    }
+}
+test2();
+
+function test3() {
+    function count(o) {
+        let c = 0;
+        for (let p in o) {
+            p = "a";
+            if (p in o)
+                ++c;
+        }
+        return c;
+    }
+    noInline(count);
+
+    let o = {
+        a:20,
+        b:30,
+        c:40,
+        d:50
+    };
+
+    for (let i = 0; i < 1000; ++i) {
+        assert(count(o) === 4);
+    }
+}
+test3();
+
+function test4() {
+    function count(o) {
+        let c = 0;
+        for (let p in o) {
+            p = "f";
+            if (p in o)
+                ++c;
+        }
+        return c;
+    }
+    noInline(count);
+
+    let o = {
+        a:20,
+        b:30,
+        c:40,
+        d:50
+    };
+
+    for (let i = 0; i < 1000; ++i) {
+        assert(count(o) === 0);
+    }
+}
+test4();
+
+function test5() {
+    function count(o) {
+        let c = 0;
+        for (let p in o) {
+            delete o[p];
+            if (p in o)
+                ++c;
+        }
+        return c;
+    }
+    noInline(count);
+
+    let o = {
+        a:20,
+        b:30,
+        c:40,
+        d:50
+    };
+
+    let p = {
+        a:20,
+        b:30,
+        c:40,
+        d:50
+    };
+
+    o.__proto__ = p;
+
+    for (let i = 0; i < 1000; ++i) {
+        assert(count(o) === 4);
+    }
+}
+test5();
+
+function test6() {
+    function count(o) {
+        let c = 0;
+        for (let p in o) {
+            delete o[p];
+            if (p in o)
+                ++c;
+        }
+        return c;
+    }
+    noInline(count);
+
+    let o = {
+        a:20,
+        b:30,
+        c:40,
+        d:50
+    };
+
+    let p = { };
+
+    o.__proto__ = p;
+
+    for (let i = 0; i < 1000; ++i) {
+        assert(count(o) === 0);
+    }
+}
+test6();
+
+function test7() {
+    function count(o, o2) {
+        let c = 0;
+        for (let p in o) {
+            if (p in o2)
+                ++c;
+        }
+        return c;
+    }
+    noInline(count);
+
+    for (let i = 0; i < 1000; ++i) {
+        let o = {a: 20};
+        if (!!(i % 2)) {
+            let ok = false;
+            try {
+                count(o, null);
+            } catch(e) {
+                assert(e instanceof TypeError);
+                ok = e.toString().indexOf("o2 is not an Object") >= 0;
+            }
+            assert(ok);
+        } else {
+            assert(count(o, {}) === 0);
+        }
+    }
+}
+test7();
+
+function test8() {
+    function count(o, proto) {
+        let c = 0;
+        for (let p in o) {
+            delete o[p];
+            o.__proto__ = proto;
+            if (p in o)
+                ++c;
+        }
+        return c;
+    }
+    noInline(count);
+
+    let hasOwnPropertyCalled = false;
+    let p = new Proxy({}, {
+        has() {
+            hasOwnPropertyCalled = true;
+            return false;
+        }
+    });
+
+    for (let i = 0; i < 1000; ++i) {
+        let o = { a:20 };
+
+        assert(count(o, p) === 0);
+        assert(hasOwnPropertyCalled === true);
+        hasOwnPropertyCalled = false;
+    }
+}
+test8();
index 300fa66..873b1ba 100644 (file)
@@ -1,3 +1,94 @@
+2020-05-22  Saam Barati  <sbarati@apple.com>
+
+        in_by_val inside structure property for-in loop should use an opcode like has_structure_property but for "in"
+        https://bugs.webkit.org/show_bug.cgi?id=212239
+
+        Reviewed by Tadeu Zagallo.
+
+        There is code inside Speedometer 2 that is like:
+        
+        ```
+        for (let p in o) {
+            if (p in o2)
+                ...
+        }
+        ```
+        
+        Where o and o2 frequently share the same structure. Elm does this when it's
+        diffing two objects. We already optimize o2[p] (get_by_val) in the above loop
+        for structure properties. This patch adds that same optimization for in_by_val.
+        Because we already emit a "structure" loop for for-in, where we iterate structure
+        properties, the fast path for the above, where o and o2 have the same
+        structure is simply a structure check followed by return true.
+        
+        This patch introduces the new opcode: op_in_structure_property. Its fast path is identical
+        to op_has_structure_property. Its slow path, however, behaves like "in", which
+        uses the HasProperty internal method, unlike op_has_structure_property,
+        which uses the GetOwnProperty internal method. This behavior difference is
+        observable using Proxy.
+        
+        This a 5% perf improvement in the Elm subtest in Speedometer 2.
+
+        * bytecode/BytecodeList.rb:
+        * bytecode/BytecodeUseDef.cpp:
+        (JSC::computeUsesForBytecodeIndexImpl):
+        (JSC::computeDefsForBytecodeIndexImpl):
+        * bytecompiler/BytecodeGenerator.cpp:
+        (JSC::BytecodeGenerator::emitInByVal):
+        (JSC::rewriteOp):
+        (JSC::StructureForInContext::finalize):
+        * bytecompiler/BytecodeGenerator.h:
+        (JSC::StructureForInContext::addInInst):
+        * dfg/DFGAbstractInterpreterInlines.h:
+        (JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::parseBlock):
+        * dfg/DFGCapabilities.cpp:
+        (JSC::DFG::capabilityLevel):
+        * dfg/DFGClobberize.h:
+        (JSC::DFG::clobberize):
+        * dfg/DFGDoesGC.cpp:
+        (JSC::DFG::doesGC):
+        * dfg/DFGFixupPhase.cpp:
+        (JSC::DFG::FixupPhase::fixupNode):
+        * dfg/DFGNodeType.h:
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGOperations.h:
+        * dfg/DFGPredictionPropagationPhase.cpp:
+        * dfg/DFGSafeToExecute.h:
+        (JSC::DFG::safeToExecute):
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileInStructureProperty):
+        * dfg/DFGSpeculativeJIT.h:
+        * dfg/DFGSpeculativeJIT32_64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLCapabilities.cpp:
+        (JSC::FTL::canCompile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileNode):
+        (JSC::FTL::DFG::LowerDFGToB3::compileHasStructurePropertyImpl):
+        (JSC::FTL::DFG::LowerDFGToB3::compileHasStructureProperty):
+        (JSC::FTL::DFG::LowerDFGToB3::compileInStructureProperty):
+        * jit/JIT.cpp:
+        (JSC::JIT::privateCompileMainPass):
+        (JSC::JIT::privateCompileSlowCases):
+        * jit/JIT.h:
+        * jit/JITOpcodes.cpp:
+        (JSC::JIT::emit_op_in_structure_property):
+        * jit/JITOpcodes32_64.cpp:
+        (JSC::JIT::emit_op_in_structure_property):
+        * llint/LLIntOffsetsExtractor.cpp:
+        * llint/LowLevelInterpreter.asm:
+        * llint/LowLevelInterpreter64.asm:
+        * runtime/CommonSlowPaths.cpp:
+        (JSC::SLOW_PATH_DECL):
+        * runtime/CommonSlowPaths.h:
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::hasPropertyGeneric const):
+        * runtime/JSPropertyNameEnumerator.h:
+
 2020-05-22  Keith Miller  <keith_miller@apple.com>
 
         Checkpoint inlined call return handler needs an exception check when dispatching
index 7d5b8c9..4b02b2c 100644 (file)
@@ -1105,6 +1105,14 @@ op :has_structure_property,
         enumerator: VirtualRegister,
     }
 
+op :in_structure_property,
+    args: {
+        dst: VirtualRegister,
+        base: VirtualRegister,
+        property: VirtualRegister,
+        enumerator: VirtualRegister,
+    }
+
 op :has_generic_property,
     args: {
         dst: VirtualRegister,
index e8f0aea..d56adcc 100644 (file)
@@ -246,6 +246,7 @@ void computeUsesForBytecodeIndexImpl(VirtualRegister scopeRegister, const Instru
     USES(OpGetByValWithThis, base, thisValue, property)
     USES(OpInstanceofCustom, value, constructor, hasInstanceValue)
     USES(OpHasStructureProperty, base, property, enumerator)
+    USES(OpInStructureProperty, base, property, enumerator)
     USES(OpConstructVarargs, callee, thisValue, arguments)
     USES(OpCallVarargs, callee, thisValue, arguments)
     USES(OpTailCallVarargs, callee, thisValue, arguments)
@@ -374,6 +375,7 @@ void computeDefsForBytecodeIndexImpl(unsigned numVars, const Instruction* instru
     DEFS(OpGetEnumerableLength, dst)
     DEFS(OpHasIndexedProperty, dst)
     DEFS(OpHasStructureProperty, dst)
+    DEFS(OpInStructureProperty, dst)
     DEFS(OpHasGenericProperty, dst)
     DEFS(OpGetDirectPname, dst)
     DEFS(OpGetPropertyEnumerator, dst)
index 49df991..f4c9c09 100644 (file)
@@ -2479,6 +2479,20 @@ RegisterID* BytecodeGenerator::emitInstanceOfCustom(RegisterID* dst, RegisterID*
 
 RegisterID* BytecodeGenerator::emitInByVal(RegisterID* dst, RegisterID* property, RegisterID* base)
 {
+    for (size_t i = m_forInContextStack.size(); i--; ) {
+        ForInContext& context = m_forInContextStack[i].get();
+        if (context.local() != property)
+            continue;
+
+        if (!context.isStructureForInContext())
+            break;
+
+        StructureForInContext& structureContext = context.asStructureForInContext();
+        OpInStructureProperty::emit<OpcodeSize::Wide32>(this, dst, base, property, structureContext.enumerator());
+        structureContext.addInInst(m_lastInstruction.offset(), property->index());
+        return dst;
+    }
+
     OpInByVal::emit(this, dst, base, property);
     return dst;
 }
@@ -5183,6 +5197,41 @@ void ForInContext::finalize(BytecodeGenerator& generator, UnlinkedCodeBlockGener
     }
 }
 
+template <typename OldOpType, typename TupleType>
+ALWAYS_INLINE void rewriteOp(BytecodeGenerator& generator, TupleType& instTuple)
+{
+    unsigned instIndex = std::get<0>(instTuple);
+    int propertyRegIndex = std::get<1>(instTuple);
+    auto instruction = generator.m_writer.ref(instIndex);
+    auto end = instIndex + instruction->size();
+    ASSERT(instruction->isWide32());
+
+    generator.m_writer.seek(instIndex);
+
+    auto bytecode = instruction->as<OldOpType>();
+
+    // disable peephole optimizations
+    generator.m_lastOpcodeID = op_end;
+
+    // Change the opcode to get_by_val.
+    // 1. dst stays the same.
+    // 2. base stays the same.
+    // 3. property gets switched to the original property.
+
+    if constexpr (std::is_same<OldOpType, OpGetDirectPname>::value) {
+        static_assert(sizeof(OpGetByVal) <= sizeof(OpGetDirectPname));
+        OpGetByVal::emit<OpcodeSize::Wide32>(&generator, bytecode.m_dst, bytecode.m_base, VirtualRegister(propertyRegIndex));
+    } else if constexpr (std::is_same<OldOpType, OpInStructureProperty>::value) {
+        static_assert(sizeof(OpInByVal) <= sizeof(OpInStructureProperty));
+        OpInByVal::emit<OpcodeSize::Wide32>(&generator, bytecode.m_dst, bytecode.m_base, VirtualRegister(propertyRegIndex));
+    } else
+        RELEASE_ASSERT_NOT_REACHED();
+
+    // 4. nop out the remaining bytes
+    while (generator.m_writer.position() < end)
+        OpNop::emit<OpcodeSize::Narrow>(&generator);
+}
+
 void StructureForInContext::finalize(BytecodeGenerator& generator, UnlinkedCodeBlockGenerator* codeBlock, unsigned bodyBytecodeEndOffset)
 {
     Base::finalize(generator, codeBlock, bodyBytecodeEndOffset);
@@ -5191,30 +5240,13 @@ void StructureForInContext::finalize(BytecodeGenerator& generator, UnlinkedCodeB
 
     OpcodeID lastOpcodeID = generator.m_lastOpcodeID;
     InstructionStream::MutableRef lastInstruction = generator.m_lastInstruction;
-    for (const auto& instTuple : m_getInsts) {
-        unsigned instIndex = std::get<0>(instTuple);
-        int propertyRegIndex = std::get<1>(instTuple);
-        auto instruction = generator.m_writer.ref(instIndex);
-        auto end = instIndex + instruction->size();
-        ASSERT(instruction->isWide32());
-
-        generator.m_writer.seek(instIndex);
 
-        auto bytecode = instruction->as<OpGetDirectPname>();
+    for (const auto& instTuple : m_getInsts)
+        rewriteOp<OpGetDirectPname>(generator, instTuple);
 
-        // disable peephole optimizations
-        generator.m_lastOpcodeID = op_end;
+    for (const auto& instTuple : m_inInsts)
+        rewriteOp<OpInStructureProperty>(generator, instTuple);
 
-        // Change the opcode to get_by_val.
-        // 1. dst stays the same.
-        // 2. base stays the same.
-        // 3. property gets switched to the original property.
-        OpGetByVal::emit<OpcodeSize::Wide32>(&generator, bytecode.m_dst, bytecode.m_base, VirtualRegister(propertyRegIndex));
-
-        // 4. nop out the remaining bytes
-        while (generator.m_writer.position() < end)
-            OpNop::emit<OpcodeSize::Narrow>(&generator);
-    }
     generator.m_writer.seek(generator.m_writer.size());
     if (generator.m_lastInstruction.offset() + generator.m_lastInstruction->size() != generator.m_writer.size()) {
         generator.m_lastOpcodeID = lastOpcodeID;
index 12ec5e1..afa3d92 100644 (file)
@@ -238,6 +238,7 @@ namespace JSC {
         using Base = ForInContext;
     public:
         using GetInst = std::tuple<unsigned, int>;
+        using InInst = GetInst;
 
         StructureForInContext(RegisterID* localRegister, RegisterID* indexRegister, RegisterID* propertyRegister, RegisterID* enumeratorRegister, unsigned bodyBytecodeStartOffset)
             : ForInContext(localRegister, Type::StructureForIn, bodyBytecodeStartOffset)
@@ -256,6 +257,11 @@ namespace JSC {
             m_getInsts.append(GetInst { instIndex, propertyRegIndex });
         }
 
+        void addInInst(unsigned instIndex, int propertyRegIndex)
+        {
+            m_inInsts.append(InInst { instIndex, propertyRegIndex });
+        }
+
         void finalize(BytecodeGenerator&, UnlinkedCodeBlockGenerator*, unsigned bodyBytecodeEndOffset);
 
     private:
@@ -263,6 +269,7 @@ namespace JSC {
         RefPtr<RegisterID> m_propertyRegister;
         RefPtr<RegisterID> m_enumeratorRegister;
         Vector<GetInst> m_getInsts;
+        Vector<InInst> m_inInsts;
     };
 
     class IndexedForInContext : public ForInContext {
@@ -381,6 +388,10 @@ namespace JSC {
         friend class IndexedForInContext;
         friend class StructureForInContext;
         friend class StrictModeScope;
+
+        template <typename OldOpType, typename TupleType>
+        friend void rewriteOp(BytecodeGenerator&, TupleType&);
+
     public:
         typedef DeclarationStacks::FunctionStack FunctionStack;
 
index 5e4ef6d..5fc1983 100644 (file)
@@ -4114,6 +4114,7 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
         clobberWorld();
         break;
     }
+    case InStructureProperty:
     case HasStructureProperty: {
         setNonCellTypeForNode(node, SpecBoolean);
         clobberWorld();
index 1d80291..cdec186 100644 (file)
@@ -7824,6 +7824,15 @@ void ByteCodeParser::parseBlock(unsigned limit)
             NEXT_OPCODE(op_has_structure_property);
         }
 
+        case op_in_structure_property: {
+            auto bytecode = currentInstruction->as<OpInStructureProperty>();
+            set(bytecode.m_dst, addToGraph(InStructureProperty,
+                get(bytecode.m_base),
+                get(bytecode.m_property),
+                get(bytecode.m_enumerator)));
+            NEXT_OPCODE(op_in_structure_property);
+        }
+
         case op_has_indexed_property: {
             auto bytecode = currentInstruction->as<OpHasIndexedProperty>();
             Node* base = get(bytecode.m_base);
index 681e7ff..e850250 100644 (file)
@@ -253,6 +253,7 @@ CapabilityLevel capabilityLevel(OpcodeID opcodeID, CodeBlock* codeBlock, const I
     case op_get_enumerable_length:
     case op_has_generic_property:
     case op_has_structure_property:
+    case op_in_structure_property:
     case op_has_indexed_property:
     case op_get_direct_pname:
     case op_get_property_enumerator:
index 0f6acd0..0401ac1 100644 (file)
@@ -697,6 +697,7 @@ void clobberize(Graph& graph, Node* node, const ReadFunctor& read, const WriteFu
     case ToObject:
     case HasGenericProperty:
     case HasStructureProperty:
+    case InStructureProperty:
     case GetPropertyEnumerator:
     case GetDirectPname:
     case InstanceOfCustom:
index 0776acc..4edbb1f 100644 (file)
@@ -297,6 +297,7 @@ bool doesGC(Graph& graph, Node* node)
     case HasIndexedProperty:
     case HasOwnProperty:
     case HasStructureProperty:
+    case InStructureProperty:
     case InById:
     case InByVal:
     case InstanceOf:
index 7db50d2..5852fc1 100644 (file)
@@ -2179,6 +2179,12 @@ private:
             fixEdge<KnownCellUse>(node->child3());
             break;
         }
+        case InStructureProperty: {
+            fixEdge<CellUse>(node->child1());
+            fixEdge<StringUse>(node->child2());
+            fixEdge<KnownCellUse>(node->child3());
+            break;
+        }
         case HasIndexedProperty: {
             node->setArrayMode(
                 node->arrayMode().refine(
index ff8f144..43b53e6 100644 (file)
@@ -490,6 +490,7 @@ namespace JSC { namespace DFG {
     /* Must generate because of Proxies on the prototype chain */ \
     macro(HasIndexedProperty, NodeMustGenerate | NodeResultBoolean | NodeHasVarArgs) \
     macro(HasStructureProperty, NodeResultBoolean) \
+    macro(InStructureProperty, NodeMustGenerate | NodeResultBoolean) \
     macro(HasGenericProperty, NodeResultBoolean) \
     macro(GetDirectPname, NodeMustGenerate | NodeHasVarArgs | NodeResultJS) \
     macro(GetPropertyEnumerator, NodeMustGenerate | NodeResultJS) \
index 88d18fc..57a9437 100644 (file)
@@ -2274,6 +2274,15 @@ EncodedJSValue JIT_OPERATION operationHasGenericProperty(JSGlobalObject* globalO
     RELEASE_AND_RETURN(scope, JSValue::encode(jsBoolean(base->hasPropertyGeneric(globalObject, propertyName, PropertySlot::InternalMethodType::GetOwnProperty))));
 }
 
+EncodedJSValue JIT_OPERATION operationInStructureProperty(JSGlobalObject* globalObject, JSCell* base, JSCell* property)
+{
+    VM& vm = globalObject->vm();
+    CallFrame* callFrame = DECLARE_CALL_FRAME(vm);
+    JITOperationPrologueCallFrameTracer tracer(vm, callFrame);
+
+    return JSValue::encode(jsBoolean(CommonSlowPaths::opInByVal(globalObject, base, property)));
+}
+
 size_t JIT_OPERATION operationHasIndexedPropertyByInt(JSGlobalObject* globalObject, JSCell* baseCell, int32_t subscript, int32_t internalMethodType)
 {
     VM& vm = globalObject->vm();
index 8fcfbcc..7e547c4 100644 (file)
@@ -99,6 +99,7 @@ EncodedJSValue JIT_OPERATION operationGetByValWithThis(JSGlobalObject*, EncodedJ
 EncodedJSValue JIT_OPERATION operationGetPrototypeOf(JSGlobalObject*, EncodedJSValue) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationGetPrototypeOfObject(JSGlobalObject*, JSObject*) WTF_INTERNAL;
 EncodedJSValue JIT_OPERATION operationHasGenericProperty(JSGlobalObject*, EncodedJSValue, JSCell*);
+EncodedJSValue JIT_OPERATION operationInStructureProperty(JSGlobalObject*, JSCell*, JSCell*);
 size_t JIT_OPERATION operationHasIndexedPropertyByInt(JSGlobalObject*, JSCell*, int32_t, int32_t);
 JSCell* JIT_OPERATION operationGetPropertyEnumerator(JSGlobalObject*, EncodedJSValue);
 JSCell* JIT_OPERATION operationGetPropertyEnumeratorCell(JSGlobalObject*, JSCell*);
index bf871bd..f80fa86 100644 (file)
@@ -1206,6 +1206,7 @@ private:
         }
         case HasGenericProperty:
         case HasStructureProperty:
+        case InStructureProperty:
         case HasIndexedProperty: {
             setPrediction(SpecBoolean);
             break;
index 1b4f609..b3a809e 100644 (file)
@@ -618,6 +618,7 @@ bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node, bool igno
     case GetEnumerableLength:
     case HasGenericProperty:
     case HasStructureProperty:
+    case InStructureProperty:
     case GetDirectPname:
     case GetPropertyEnumerator:
     case PhantomNewObject:
index 49d7f7e..3d225dc 100644 (file)
@@ -12826,6 +12826,33 @@ void SpeculativeJIT::compileHasStructureProperty(Node* node)
     blessedBooleanResult(resultRegs.payloadGPR(), node);
 }
 
+void SpeculativeJIT::compileInStructureProperty(Node* node)
+{
+    SpeculateCellOperand base(this, node->child1());
+    SpeculateCellOperand property(this, node->child2());
+    SpeculateCellOperand enumerator(this, node->child3());
+    JSValueRegsTemporary result(this);
+
+    GPRReg baseGPR = base.gpr();
+    GPRReg propertyGPR = property.gpr();
+    JSValueRegs resultRegs = result.regs();
+
+    CCallHelpers::JumpList wrongStructure;
+
+    m_jit.load32(MacroAssembler::Address(baseGPR, JSCell::structureIDOffset()), resultRegs.payloadGPR());
+    wrongStructure.append(m_jit.branch32(MacroAssembler::NotEqual,
+        resultRegs.payloadGPR(),
+        MacroAssembler::Address(enumerator.gpr(), JSPropertyNameEnumerator::cachedStructureIDOffset())));
+
+    moveTrueTo(resultRegs.payloadGPR());
+    MacroAssembler::Jump done = m_jit.jump();
+
+    done.link(&m_jit);
+
+    addSlowPathGenerator(slowPathCall(wrongStructure, this, operationInStructureProperty, resultRegs, TrustedImmPtr::weakPointer(m_graph, m_graph.globalObjectFor(node->origin.semantic)), baseGPR, propertyGPR));
+    blessedBooleanResult(resultRegs.payloadGPR(), node);
+}
+
 void SpeculativeJIT::compileGetPropertyEnumerator(Node* node)
 {
     if (node->child1().useKind() == CellUse) {
index ea5fd11..acbb03c 100644 (file)
@@ -1447,6 +1447,7 @@ public:
     void compilePutByIdDirect(Node*);
     void compilePutByIdWithThis(Node*);
     void compileHasStructureProperty(Node*);
+    void compileInStructureProperty(Node*);
     void compileGetDirectPname(Node*);
     void compileGetPropertyEnumerator(Node*);
     void compileGetEnumeratorPname(Node*);
index 3618161..19d0e28 100644 (file)
@@ -4057,6 +4057,10 @@ void SpeculativeJIT::compile(Node* node)
         compileHasStructureProperty(node);
         break;
     }
+    case InStructureProperty: {
+        compileInStructureProperty(node);
+        break;
+    }
     case HasIndexedProperty: {
         compileHasIndexedProperty(node);
         break;
index 164ff13..4d26d35 100644 (file)
@@ -4988,6 +4988,10 @@ void SpeculativeJIT::compile(Node* node)
         compileHasStructureProperty(node);
         break;
     }
+    case InStructureProperty: {
+        compileInStructureProperty(node);
+        break;
+    }
     case HasIndexedProperty: {
         compileHasIndexedProperty(node);
         break;
index 26f45c2..c5a1601 100644 (file)
@@ -280,6 +280,7 @@ inline CapabilityLevel canCompile(Node* node)
     case BooleanToNumber:
     case HasGenericProperty:
     case HasStructureProperty:
+    case InStructureProperty:
     case HasIndexedProperty:
     case GetDirectPname:
     case GetEnumerableLength:
index 91039ee..fb7fcee 100644 (file)
@@ -1477,6 +1477,9 @@ private:
         case HasStructureProperty:
             compileHasStructureProperty();
             break;
+        case InStructureProperty:
+            compileInStructureProperty();
+            break;
         case GetDirectPname:
             compileGetDirectPname();
             break;
@@ -12241,35 +12244,51 @@ private:
         setJSValue(vmCall(Int64, operationHasGenericProperty, weakPointer(globalObject), base, property));
     }
 
-    void compileHasStructureProperty()
+    template <typename SlowPathCall>
+    void compileHasStructurePropertyImpl(LValue base, SlowPathCall slowPathCall)
     {
         JSGlobalObject* globalObject = m_graph.globalObjectFor(m_node->origin.semantic);
-        LValue base = lowJSValue(m_node->child1());
         LValue property = lowString(m_node->child2());
         LValue enumerator = lowCell(m_node->child3());
 
+        LBasicBlock isCellCase = m_out.newBlock();
         LBasicBlock correctStructure = m_out.newBlock();
-        LBasicBlock wrongStructure = m_out.newBlock();
+        LBasicBlock slowPath = m_out.newBlock();
         LBasicBlock continuation = m_out.newBlock();
 
+        m_out.branch(isCell(base, provenType(m_node->child1())),
+            usually(isCellCase), rarely(slowPath));
+
+        LBasicBlock lastNext = m_out.appendTo(isCellCase, correctStructure);
+
         m_out.branch(m_out.notEqual(
             m_out.load32(base, m_heaps.JSCell_structureID),
             m_out.load32(enumerator, m_heaps.JSPropertyNameEnumerator_cachedStructureID)),
-            rarely(wrongStructure), usually(correctStructure));
+            rarely(slowPath), usually(correctStructure));
 
-        LBasicBlock lastNext = m_out.appendTo(correctStructure, wrongStructure);
+        m_out.appendTo(correctStructure, slowPath);
         ValueFromBlock correctStructureResult = m_out.anchor(m_out.booleanTrue);
         m_out.jump(continuation);
 
-        m_out.appendTo(wrongStructure, continuation);
-        ValueFromBlock wrongStructureResult = m_out.anchor(
+        m_out.appendTo(slowPath, continuation);
+        ValueFromBlock slowPathResult = m_out.anchor(
             m_out.equal(
                 m_out.constInt64(JSValue::encode(jsBoolean(true))), 
-                vmCall(Int64, operationHasGenericProperty, weakPointer(globalObject), base, property)));
+                vmCall(Int64, slowPathCall, weakPointer(globalObject), base, property)));
         m_out.jump(continuation);
 
         m_out.appendTo(continuation, lastNext);
-        setBoolean(m_out.phi(Int32, correctStructureResult, wrongStructureResult));
+        setBoolean(m_out.phi(Int32, correctStructureResult, slowPathResult));
+    }
+
+    void compileHasStructureProperty()
+    {
+        compileHasStructurePropertyImpl(lowJSValue(m_node->child1()), operationHasGenericProperty);
+    }
+
+    void compileInStructureProperty()
+    {
+        compileHasStructurePropertyImpl(lowCell(m_node->child1()), operationInStructureProperty);
     }
 
     void compileGetDirectPname()
index 32447d6..005d3a1 100644 (file)
@@ -460,6 +460,7 @@ void JIT::privateCompileMainPass()
         DEFINE_OP(op_put_to_arguments)
 
         DEFINE_OP(op_has_structure_property)
+        DEFINE_OP(op_in_structure_property)
         DEFINE_OP(op_has_indexed_property)
         DEFINE_OP(op_get_direct_pname)
         DEFINE_OP(op_enumerator_structure_pname)
@@ -616,6 +617,7 @@ void JIT::privateCompileSlowCases()
         DEFINE_SLOWCASE_SLOW_OP(nstricteq)
         DEFINE_SLOWCASE_SLOW_OP(get_direct_pname)
         DEFINE_SLOWCASE_SLOW_OP(has_structure_property)
+        DEFINE_SLOWCASE_SLOW_OP(in_structure_property)
         DEFINE_SLOWCASE_SLOW_OP(resolve_scope)
         DEFINE_SLOWCASE_SLOW_OP(check_tdz)
         DEFINE_SLOWCASE_SLOW_OP(to_property_key)
index 34f3d50..d631f01 100644 (file)
@@ -619,6 +619,7 @@ namespace JSC {
         void emit_op_unsigned(const Instruction*);
         void emit_op_urshift(const Instruction*);
         void emit_op_has_structure_property(const Instruction*);
+        void emit_op_in_structure_property(const Instruction*);
         void emit_op_has_indexed_property(const Instruction*);
         void emit_op_get_direct_pname(const Instruction*);
         void emit_op_enumerator_structure_pname(const Instruction*);
index 7277fe8..3519891 100644 (file)
@@ -1372,6 +1372,24 @@ void JIT::emit_op_has_structure_property(const Instruction* currentInstruction)
     emitPutVirtualRegister(dst);
 }
 
+void JIT::emit_op_in_structure_property(const Instruction* currentInstruction)
+{
+    auto bytecode = currentInstruction->as<OpInStructureProperty>();
+    VirtualRegister dst = bytecode.m_dst;
+    VirtualRegister base = bytecode.m_base;
+    VirtualRegister enumerator = bytecode.m_enumerator;
+
+    emitGetVirtualRegister(base, regT0);
+    emitGetVirtualRegister(enumerator, regT1);
+    emitJumpSlowCaseIfNotJSCell(regT0, base);
+
+    load32(Address(regT0, JSCell::structureIDOffset()), regT0);
+    addSlowCase(branch32(NotEqual, regT0, Address(regT1, JSPropertyNameEnumerator::cachedStructureIDOffset())));
+    
+    move(TrustedImm64(JSValue::encode(jsBoolean(true))), regT0);
+    emitPutVirtualRegister(dst);
+}
+
 void JIT::privateCompileHasIndexedProperty(ByValInfo* byValInfo, ReturnAddressPtr returnAddress, JITArrayMode arrayMode)
 {
     const Instruction* currentInstruction = m_codeBlock->instructions().at(byValInfo->bytecodeIndex).ptr();
index 4c5bd0c..77e3452 100644 (file)
@@ -1150,6 +1150,25 @@ void JIT::emit_op_has_structure_property(const Instruction* currentInstruction)
     emitStoreBool(dst, regT0);
 }
 
+void JIT::emit_op_in_structure_property(const Instruction* currentInstruction)
+{
+    auto bytecode = currentInstruction->as<OpInStructureProperty>();
+    VirtualRegister dst = bytecode.m_dst;
+    VirtualRegister base = bytecode.m_base;
+    VirtualRegister enumerator = bytecode.m_enumerator;
+
+    emitLoadPayload(base, regT0);
+    emitJumpSlowCaseIfNotJSCell(base);
+
+    emitLoadPayload(enumerator, regT1);
+
+    load32(Address(regT0, JSCell::structureIDOffset()), regT0);
+    addSlowCase(branch32(NotEqual, regT0, Address(regT1, JSPropertyNameEnumerator::cachedStructureIDOffset())));
+    
+    move(TrustedImm32(1), regT0);
+    emitStoreBool(dst, regT0);
+}
+
 void JIT::privateCompileHasIndexedProperty(ByValInfo* byValInfo, ReturnAddressPtr returnAddress, JITArrayMode arrayMode)
 {
     const Instruction* currentInstruction = m_codeBlock->instructions().at(byValInfo->bytecodeIndex).ptr();
index 7125bd4..1fc2322 100644 (file)
@@ -49,6 +49,7 @@
 #include "JSLexicalEnvironment.h"
 #include "JSModuleRecord.h"
 #include "JSObject.h"
+#include "JSPropertyNameEnumerator.h"
 #include "JSString.h"
 #include "JSTypeInfo.h"
 #include "JumpTable.h"
index fde531f..b41f402 100644 (file)
@@ -1769,7 +1769,12 @@ slowPathOp(greater)
 slowPathOp(greatereq)
 slowPathOp(has_generic_property)
 slowPathOp(has_indexed_property)
-slowPathOp(has_structure_property)
+
+if not JSVALUE64
+    slowPathOp(has_structure_property)
+    slowPathOp(in_structure_property)
+end
+
 slowPathOp(in_by_id)
 slowPathOp(in_by_val)
 slowPathOp(is_function)
index 65592e8..1569f48 100644 (file)
@@ -2847,3 +2847,27 @@ llintOp(op_log_shadow_chicken_tail, OpLogShadowChickenTail, macro (size, get, di
     callSlowPath(_llint_slow_path_log_shadow_chicken_tail)
     dispatch()
 end)
+
+macro hasStructurePropertyImpl(size, get, dispatch, return, slowPathCall)
+    get(m_base, t0)
+    loadConstantOrVariable(size, t0, t1)
+    btqnz t1, notCellMask, .slowPath
+
+    loadVariable(get, m_enumerator, t0)
+    loadi JSCell::m_structureID[t1], t1
+    bineq t1, JSPropertyNameEnumerator::m_cachedStructureID[t0], .slowPath
+
+    return(ValueTrue)
+
+.slowPath:
+    callSlowPath(slowPathCall)
+    dispatch()
+end
+
+llintOpWithReturn(op_has_structure_property, OpHasStructureProperty, macro (size, get, dispatch, return)
+    hasStructurePropertyImpl(size, get, dispatch,  return, _slow_path_has_structure_property)
+end)
+
+llintOpWithReturn(op_in_structure_property, OpInStructureProperty, macro (size, get, dispatch, return)
+    hasStructurePropertyImpl(size, get, dispatch,  return, _slow_path_in_structure_property)
+end)
index b6558f4..2d93455 100644 (file)
@@ -1051,16 +1051,33 @@ SLOW_PATH_DECL(slow_path_has_structure_property)
     JSObject* base = GET(bytecode.m_base).jsValue().toObject(globalObject);
     CHECK_EXCEPTION();
     JSValue property = GET(bytecode.m_property).jsValue();
-    ASSERT(property.isString());
+    RELEASE_ASSERT(property.isString());
+#if USE(JSVALUE32_64)
     JSPropertyNameEnumerator* enumerator = jsCast<JSPropertyNameEnumerator*>(GET(bytecode.m_enumerator).jsValue().asCell());
     if (base->structure(vm)->id() == enumerator->cachedStructureID())
         RETURN(jsBoolean(true));
+#endif
     JSString* string = asString(property);
     auto propertyName = string->toIdentifier(globalObject);
     CHECK_EXCEPTION();
     RETURN(jsBoolean(base->hasPropertyGeneric(globalObject, propertyName, PropertySlot::InternalMethodType::GetOwnProperty)));
 }
 
+SLOW_PATH_DECL(slow_path_in_structure_property)
+{
+    BEGIN();
+    auto bytecode = pc->as<OpInStructureProperty>();
+    JSValue base = GET(bytecode.m_base).jsValue();
+#if USE(JSVALUE32_64)
+    JSPropertyNameEnumerator* enumerator = jsCast<JSPropertyNameEnumerator*>(GET(bytecode.m_enumerator).jsValue().asCell());
+    if (base.isCell() && base.asCell()->structure(vm)->id() == enumerator->cachedStructureID())
+        RETURN(jsBoolean(true));
+#endif
+    JSValue property = GET(bytecode.m_property).jsValue();
+    RELEASE_ASSERT(property.isString());
+    RETURN(jsBoolean(CommonSlowPaths::opInByVal(globalObject, base, asString(property))));
+}
+
 SLOW_PATH_DECL(slow_path_has_generic_property)
 {
     BEGIN();
index 84c1ab6..3c0714c 100644 (file)
@@ -254,6 +254,7 @@ SLOW_PATH_HIDDEN_DECL(slow_path_to_property_key);
 SLOW_PATH_HIDDEN_DECL(slow_path_get_enumerable_length);
 SLOW_PATH_HIDDEN_DECL(slow_path_has_generic_property);
 SLOW_PATH_HIDDEN_DECL(slow_path_has_structure_property);
+SLOW_PATH_HIDDEN_DECL(slow_path_in_structure_property);
 SLOW_PATH_HIDDEN_DECL(slow_path_has_indexed_property);
 SLOW_PATH_HIDDEN_DECL(slow_path_get_direct_pname);
 SLOW_PATH_HIDDEN_DECL(slow_path_get_property_enumerator);
index 3f94ed0..fd18c9c 100644 (file)
@@ -87,6 +87,8 @@ public:
     static void visitChildren(JSCell*, SlotVisitor&);
 
 private:
+    friend class LLIntOffsetsExtractor;
+
     JSPropertyNameEnumerator(VM&, Structure*, uint32_t, uint32_t, WriteBarrier<JSString>*, unsigned);
     void finishCreation(VM&, RefPtr<PropertyNameArrayData>&&);