WebKit Bugzilla
Attachment 340446 Details for
Bug 185652
: JSC should have InstanceOf inline caching
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
a little more
blah.patch (text/plain), 63.13 KB, created by
Filip Pizlo
on 2018-05-15 16:47:28 PDT
(
hide
)
Description:
a little more
Filename:
MIME Type:
Creator:
Filip Pizlo
Created:
2018-05-15 16:47:28 PDT
Size:
63.13 KB
patch
obsolete
>Index: Source/JavaScriptCore/Sources.txt >=================================================================== >--- Source/JavaScriptCore/Sources.txt (revision 231772) >+++ Source/JavaScriptCore/Sources.txt (working copy) >@@ -222,6 +222,7 @@ bytecode/GetterSetterAccessCase.cpp > bytecode/InlineAccess.cpp > bytecode/InlineCallFrame.cpp > bytecode/InlineCallFrameSet.cpp >+bytecode/InstanceOfAccessCase.cpp > bytecode/IntrinsicGetterAccessCase.cpp > bytecode/JumpTable.cpp > bytecode/LLIntPrototypeLoadAdaptiveStructureWatchpoint.cpp >Index: Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj >=================================================================== >--- Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj (revision 231772) >+++ Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj (working copy) >@@ -310,6 +310,8 @@ > 0F485322187750560083B687 /* DFGArithMode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F485320187750560083B687 /* DFGArithMode.h */; }; > 0F485328187DFDEC0083B687 /* FTLAvailableRecovery.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F485324187DFDEC0083B687 /* FTLAvailableRecovery.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 0F48532A187DFDEC0083B687 /* FTLRecoveryOpcode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F485326187DFDEC0083B687 /* FTLRecoveryOpcode.h */; settings = {ATTRIBUTES = (Private, ); }; }; >+ 0F49E9A920AB4CFE001CA0AA /* InstanceOfAccessCase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F49E9A720AB4CFB001CA0AA /* InstanceOfAccessCase.cpp */; }; >+ 0F49E9AA20AB4D00001CA0AA /* InstanceOfAccessCase.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F49E9A820AB4CFB001CA0AA /* InstanceOfAccessCase.h */; }; > 0F4A38FA1C8E13DF00190318 /* SuperSampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F4A38F81C8E13DF00190318 /* SuperSampler.h */; settings = {ATTRIBUTES = (Private, ); }; }; > 0F4AE0431FE0D25700E20839 /* InferredValueInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F4AE0421FE0D25400E20839 /* InferredValueInlines.h */; }; > 0F4B94DC17B9F07500DD03A4 /* TypedArrayInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F4B94DB17B9F07500DD03A4 /* TypedArrayInlines.h */; settings = {ATTRIBUTES = (Private, ); }; }; >@@ -2299,6 +2301,8 @@ > 0F485325187DFDEC0083B687 /* FTLRecoveryOpcode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = FTLRecoveryOpcode.cpp; path = ftl/FTLRecoveryOpcode.cpp; sourceTree = "<group>"; }; > 0F485326187DFDEC0083B687 /* FTLRecoveryOpcode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = FTLRecoveryOpcode.h; path = ftl/FTLRecoveryOpcode.h; sourceTree = "<group>"; }; > 0F493AF816D0CAD10084508B /* SourceProvider.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SourceProvider.cpp; sourceTree = "<group>"; }; >+ 0F49E9A720AB4CFB001CA0AA /* InstanceOfAccessCase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InstanceOfAccessCase.cpp; sourceTree = "<group>"; }; >+ 0F49E9A820AB4CFB001CA0AA /* InstanceOfAccessCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InstanceOfAccessCase.h; sourceTree = "<group>"; }; > 0F4A38F71C8E13DF00190318 /* SuperSampler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SuperSampler.cpp; sourceTree = "<group>"; }; > 0F4A38F81C8E13DF00190318 /* SuperSampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SuperSampler.h; sourceTree = "<group>"; }; > 0F4AE0421FE0D25400E20839 /* InferredValueInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InferredValueInlines.h; sourceTree = "<group>"; }; >@@ -5962,6 +5966,8 @@ > 534E034D1E4D4B1600213F64 /* AccessCase.h */, > 53B0BE331E561AC900A8FC29 /* GetterSetterAccessCase.cpp */, > 534E03571E53BF2F00213F64 /* GetterSetterAccessCase.h */, >+ 0F49E9A720AB4CFB001CA0AA /* InstanceOfAccessCase.cpp */, >+ 0F49E9A820AB4CFB001CA0AA /* InstanceOfAccessCase.h */, > 53B0BE371E561B2400A8FC29 /* IntrinsicGetterAccessCase.cpp */, > 534E03531E53BD2900213F64 /* IntrinsicGetterAccessCase.h */, > 53B0BE351E561B0900A8FC29 /* ProxyableAccessCase.cpp */, >@@ -9208,6 +9214,7 @@ > 7C008CE7187631B600955C24 /* Microtask.h in Headers */, > FE2A87601F02381600EB31B2 /* MinimumReservedZoneSize.h in Headers */, > 86C568E211A213EE0007F7F0 /* MIPSAssembler.h in Headers */, >+ 0F49E9AA20AB4D00001CA0AA /* InstanceOfAccessCase.h in Headers */, > C4703CD7192844CC0013FBEA /* models.py in Headers */, > E3794E761B77EB97005543AE /* ModuleAnalyzer.h in Headers */, > 9F63434577274FAFB9336C38 /* ModuleNamespaceAccessCase.h in Headers */, >@@ -10303,6 +10310,7 @@ > 530A66C21FA3E78B0026A545 /* UnifiedSource143.cpp in Sources */, > 530A66C31FA3E78B0026A545 /* UnifiedSource144.cpp in Sources */, > 530A66C41FA3E78B0026A545 /* UnifiedSource145.cpp in Sources */, >+ 0F49E9A920AB4CFE001CA0AA /* InstanceOfAccessCase.cpp in Sources */, > ); > runOnlyForDeploymentPostprocessing = 0; > }; >Index: Source/JavaScriptCore/bytecode/AccessCase.cpp >=================================================================== >--- Source/JavaScriptCore/bytecode/AccessCase.cpp (revision 231772) >+++ Source/JavaScriptCore/bytecode/AccessCase.cpp (working copy) >@@ -221,7 +221,7 @@ bool AccessCase::canReplace(const Access > { > // This puts in a good effort to try to figure out if 'other' is made superfluous by '*this'. > // It's fine for this to return false if it's in doubt. >- >+ > switch (type()) { > case ArrayLength: > case StringLength: >@@ -235,6 +235,25 @@ bool AccessCase::canReplace(const Access > auto& otherCase = this->as<ModuleNamespaceAccessCase>(); > return thisCase.moduleNamespaceObject() == otherCase.moduleNamespaceObject(); > } >+ case InstanceOfHit: >+ case InstanceOfMiss: { >+ if (other.type() != type()) >+ return false; >+ >+ if (this->as<InstanceOfAccessCase>().prototype() != other.as<InstanceOfAccessCase>().prototype()) >+ return false; >+ >+ return structure() == other.structure(); >+ } >+ case InstanceOfGeneric: >+ switch (other.type()) { >+ case InstanceOfGeneric: >+ case InstanceOfHit: >+ case InstanceOfMiss: >+ return true; >+ default: >+ return false; >+ } > default: > if (other.type() != type()) > return false; >@@ -311,6 +330,9 @@ bool AccessCase::visitWeak(VM& vm) const > return false; > if (accessCase.moduleEnvironment() && !Heap::isMarked(accessCase.moduleEnvironment())) > return false; >+ } else if (type() == InstanceOfHit || type() == InstanceOfMiss) { >+ if (as<InstanceOfAccessCase>().prototype() && !Heap::isMarked(as<InstanceOfAccessCase>().prototype())) >+ return false; > } > > return true; >@@ -565,6 +587,79 @@ void AccessCase::generateImpl(AccessGene > state.succeed(); > return; > >+ case InstanceOfHit: >+ case InstanceOfMiss: >+ state.failAndRepatch.append( >+ jit.branch(CCallHelpers::NotEqual, thisGPR, CCallHelpers::TrustedImmPtr(as<InstanceOfAccessCase>().prototype()))); >+ >+ jit.boxBooleanPayload(m_type == InstanceOfHit, valueRegs.payloadGPR()); >+ state.succeed(); >+ return; >+ >+ case InstanceOfGeneric: { >+ // Legend: value = `base instanceof this`. >+ >+ ScratchRegisterAllocator allocator(stubInfo.patch.usedRegisters); >+ allocator.lock(baseGPR); >+ allocator.lock(valueRegs.payloadGPR()); >+ allocator.lock(thisGPR); >+ allocator.lock(scratchGPR); >+ >+ GPRReg scratch2GPR = allocator.allocateScratchGPR(); >+ RELEASE_ASSERT(!allocator.didReuseRegisters()); >+ >+ state.failAndIgnore.append(jit.branchIfNotObject(thisGPR)); >+ >+ jit.move(baseGPR, valueGPR); >+ >+ CCallHelpers::Label loop(&jit); >+ state.failAndIgnore.append(jit.branch8( >+ CCallHelpers::Equal, >+ CCallHelpers::Address(valueGPR, JSCell::typeInfoTypeOffset()), >+ CCallHelpers::TrustedImm32(ProxyObjectType))); >+ >+ jit.emitLoadStructure(vm, valueGPR, scratchGPR, scratch2GPR); >+#if USE(JSVALUE64) >+ jit.load64(CCallHelpers::Address(scratch2GPR, Structure::prototypeOffset()), scratch2GPR); >+ CCallHelpers::Jump hasMonoProto = jit.branchTest64(CCallHelpers::Zero, scratch2GPR); >+ jit.load64( >+ CCallHelpers::Address(valueGPR, offsetRelativeToBase(knownPolyProtoOffset)), >+ scratch2GPR); >+ hasMonoProto.link(&jit); >+#else >+ jit.load32( >+ CCallHelpers::Address(scratch2GPR, Structure::prototypeOffset() + TagOffset), >+ scratchGPR); >+ jit.load32( >+ CCallHelpers::Address(scratch2GPR, Structure::prototypeOffset() + PayloadOffset), >+ scratch2GPR); >+ CCallHelpers::Jump hasMonoProto = jit.branch32( >+ CCallHelpers::NotEqual, scratchGPR, CCallHelpers::TrustedImm32(JSValue::EmptyValueTag)); >+ jit.load32( >+ CCallHelpers::Address( >+ valueGPR, offsetRelativeToBase(knownPolyProtoOffset) + PayloadOffset), >+ scratch2GPR); >+ hasMonoProto.link(&jit); >+#endif >+ jit.move(scratch2GPR, valueGPR); >+ >+ CCallHelpers::Jump isInstance = jit.branchPtr(CCallHelpers::Equal, valueGPR, thisGPR); >+ >+#if USE(JSVALUE64) >+ jit.branchIfCell(JSValueRegs(valueGPR)).linkTo(loop, &jit); >+#else >+ jit.branchTestPtr(CCallHelpers::NonZero, valueGPR).linkTo(loop, &jit); >+#endif >+ >+ jit.boxBooleanPayload(false, valueGPR); >+ state.succeed(); >+ >+ isInstance.link(&jit); >+ jit.boxBooleanPayload(true, valueGPR); >+ state.succeed(); >+ return; >+ } >+ > case Miss: > jit.moveTrustedValue(jsUndefined(), valueRegs); > state.succeed(); >Index: Source/JavaScriptCore/bytecode/AccessCase.h >=================================================================== >--- Source/JavaScriptCore/bytecode/AccessCase.h (revision 231772) >+++ Source/JavaScriptCore/bytecode/AccessCase.h (working copy) >@@ -99,6 +99,9 @@ public: > DirectArgumentsLength, > ScopedArgumentsLength, > ModuleNamespaceLoad, >+ InstanceOfHit, >+ InstanceOfMiss, >+ InstanceOfGeneric > }; > > enum State : uint8_t { >@@ -126,7 +129,7 @@ public: > // This create method should be used for transitions. > static std::unique_ptr<AccessCase> create(VM&, JSCell* owner, PropertyOffset, Structure* oldStructure, > Structure* newStructure, const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>); >- >+ > static std::unique_ptr<AccessCase> fromStructureStubInfo(VM&, JSCell* owner, StructureStubInfo&); > > AccessType type() const { return m_type; } >Index: Source/JavaScriptCore/bytecode/InstanceOfAccessCase.cpp >=================================================================== >--- Source/JavaScriptCore/bytecode/InstanceOfAccessCase.cpp (nonexistent) >+++ Source/JavaScriptCore/bytecode/InstanceOfAccessCase.cpp (working copy) >@@ -0,0 +1,68 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY >+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE >+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR >+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, >+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, >+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR >+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY >+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#include "config.h" >+#include "InstanceOfAccessCase.h" >+ >+#if ENABLE(JIT) >+ >+namespace JSC { >+ >+std::unique_ptr<AccessCase> InstanceOfAccessCase::create( >+ VM& vm, JSCell* owner, AccessType accessType, Structure* structure, >+ const ObjectPropertyConditionSet& conditionSet, JSObject* prototype) >+{ >+ return std::unique_ptr<AccessCase>(new InstanceOfAccessCase(vm, owner, accessType, structure, conditionSet, prototype)); >+} >+ >+void InstanceOfAccessCase::dumpImpl(PrintStream& out, CommaPrinter& comma) const >+{ >+ Base::dumpImpl(out, comma); >+ out.print(comma, "prototype = ", JSValue(prototype())); >+} >+ >+std::unique_ptr<AccessCase> InstanceOfAccessCase::clone() const >+{ >+ std::unique_ptr<ProxyableAccessCase> result(new InstanceOfAccessCase(*this)); >+ result->resetState(); >+ return WTFMove(result); >+} >+ >+InstanceOfAccessCase::~InstanceOfAccessCase() >+{ >+} >+ >+InstanceOfAccessCase::InstanceOfAccessCase( >+ VM& vm, JSCell* owner, AccessType accessType, Structure* structure, >+ const ObjectPropertyConditionSet& conditionSet, JSObject* prototype) >+ : Base(vm, owner, accessType, invalidOffset, structure, conditionSet) >+ , m_prototype(vm, owner, prototype) >+{ >+} >+ >+} // namespace JSC >+ >+#endif // ENABLE(JIT) >+ >Index: Source/JavaScriptCore/bytecode/InstanceOfAccessCase.h >=================================================================== >--- Source/JavaScriptCore/bytecode/InstanceOfAccessCase.h (nonexistent) >+++ Source/JavaScriptCore/bytecode/InstanceOfAccessCase.h (working copy) >@@ -0,0 +1,61 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY >+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE >+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR >+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, >+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, >+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR >+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY >+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#pragma once >+ >+#if ENABLE(JIT) >+ >+#include "AccessCase.h" >+ >+namespace JSC { >+ >+class InstanceOfAccessCase : public AccessCase { >+public: >+ using Base = AccessCase; >+ >+ static std::unique_ptr<AccessCase> create( >+ VM&, JSCell*, AccessType, Structure*, const ObjectPropertyConditionSet&, >+ JSObject* prototype); >+ >+ JSObject* prototype() const { return m_prototype.get(); } >+ >+ void dumpImpl(PrintStream&, CommaPrinter&) const override; >+ std::unique_ptr<AccessCase> clone() const override; >+ >+ ~InstanceOfAccessCase(); >+ >+protected: >+ InstanceOfAccessCase( >+ VM&, JSCell*, AccessType, Structure*, const ObjectPropertyConditionSet&, >+ JSObject* prototype); >+ >+private: >+ WriteBarrier<JSObject> m_prototype; >+}; >+ >+} // namespace JSC >+ >+#endif // ENABLE(JIT) >+ >Index: Source/JavaScriptCore/bytecode/ObjectPropertyCondition.h >=================================================================== >--- Source/JavaScriptCore/bytecode/ObjectPropertyCondition.h (revision 231772) >+++ Source/JavaScriptCore/bytecode/ObjectPropertyCondition.h (working copy) >@@ -1,5 +1,5 @@ > /* >- * Copyright (C) 2015-2016 Apple Inc. All rights reserved. >+ * Copyright (C) 2015-2018 Apple Inc. All rights reserved. > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions >@@ -121,6 +121,22 @@ public: > vm.heap.writeBarrier(owner); > return equivalenceWithoutBarrier(object, uid, value); > } >+ >+ static ObjectPropertyCondition hasPrototypeWithoutBarrier(JSObject* object, JSObject* prototype) >+ { >+ ObjectPropertyCondition result; >+ result.m_object = object; >+ result.m_condition = PropertyCondition::hasPrototypeWithoutBarrier(prototype); >+ return result; >+ } >+ >+ static ObjectPropertyCondition hasPrototype( >+ VM& vm, JSCell* owner, JSObject* object, JSObject* prototype) >+ { >+ if (owner) >+ vm.heap.writeBarrier(owner); >+ return hasPrototypeWithoutBarrier(object, prototype); >+ } > > explicit operator bool() const { return !!m_condition; } > >Index: Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp >=================================================================== >--- Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp (revision 231772) >+++ Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp (working copy) >@@ -392,6 +392,28 @@ ObjectPropertyConditionSet generateCondi > }); > } > >+ObjectPropertyConditionSet generateConditionsForInstanceOf( >+ VM& vm, JSCell* owner, ExecState* exec, Structure* headStructure, JSObject* prototype, >+ bool shouldHit) >+{ >+ bool didHit = false; >+ ObjectPropertyConditionSet result = generateConditions( >+ vm, exec->lexicalGlobalObject(), headStructure, nullptr, >+ [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool { >+ if (object == prototype) { >+ didHit = true; >+ return true; >+ } >+ conditions.append( >+ ObjectPropertyCondition::hasPrototype( >+ vm, owner, object, object->structure()->storedPrototypeObject())); >+ return true; >+ }); >+ if (result.isValid()) >+ RELEASE_ASSERT(didHit == shouldHit); >+ return result; >+} >+ > ObjectPropertyConditionSet generateConditionsForPrototypeEquivalenceConcurrently( > VM& vm, JSGlobalObject* globalObject, Structure* headStructure, JSObject* prototype, UniquedStringImpl* uid) > { >Index: Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h >=================================================================== >--- Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h (revision 231772) >+++ Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h (working copy) >@@ -169,6 +169,9 @@ ObjectPropertyConditionSet generateCondi > VM&, JSCell* owner, ExecState*, Structure* headStructure, JSObject* prototype, > UniquedStringImpl* uid); > >+ObjectPropertyConditionSet generateConditionsForInstanceOf( >+ VM&, JSCell* owner, ExecState*, Structure* headStructure, JSObject* prototype, bool shouldHit); >+ > ObjectPropertyConditionSet generateConditionsForPrototypeEquivalenceConcurrently( > VM&, JSGlobalObject*, Structure* headStructure, JSObject* prototype, > UniquedStringImpl* uid); >Index: Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp >=================================================================== >--- Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp (revision 231772) >+++ Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp (working copy) >@@ -248,7 +248,7 @@ AccessGenerationResult PolymorphicAccess > > casesToAdd.append(WTFMove(myCase)); > } >- >+ > if (PolymorphicAccessInternal::verbose) > dataLog("casesToAdd: ", listDump(casesToAdd), "\n"); > >@@ -258,39 +258,41 @@ AccessGenerationResult PolymorphicAccess > if (casesToAdd.isEmpty()) > return AccessGenerationResult::MadeNoChanges; > >- bool shouldReset = false; >- AccessGenerationResult resetResult(AccessGenerationResult::ResetStubAndFireWatchpoints); >- auto considerPolyProtoReset = [&] (Structure* a, Structure* b) { >- if (Structure::shouldConvertToPolyProto(a, b)) { >- // For now, we only reset if this is our first time invalidating this watchpoint. >- // The reason we don't immediately fire this watchpoint is that we may be already >- // watching the poly proto watchpoint, which if fired, would destroy us. We let >- // the person handling the result to do a delayed fire. >- ASSERT(a->rareData()->sharedPolyProtoWatchpoint().get() == b->rareData()->sharedPolyProtoWatchpoint().get()); >- if (a->rareData()->sharedPolyProtoWatchpoint()->isStillValid()) { >- shouldReset = true; >- resetResult.addWatchpointToFire(*a->rareData()->sharedPolyProtoWatchpoint(), StringFireDetail("Detected poly proto optimization opportunity.")); >+ if (stubInfo.accessType != AccessType::InstanceOf) { >+ bool shouldReset = false; >+ AccessGenerationResult resetResult(AccessGenerationResult::ResetStubAndFireWatchpoints); >+ auto considerPolyProtoReset = [&] (Structure* a, Structure* b) { >+ if (Structure::shouldConvertToPolyProto(a, b)) { >+ // For now, we only reset if this is our first time invalidating this watchpoint. >+ // The reason we don't immediately fire this watchpoint is that we may be already >+ // watching the poly proto watchpoint, which if fired, would destroy us. We let >+ // the person handling the result to do a delayed fire. >+ ASSERT(a->rareData()->sharedPolyProtoWatchpoint().get() == b->rareData()->sharedPolyProtoWatchpoint().get()); >+ if (a->rareData()->sharedPolyProtoWatchpoint()->isStillValid()) { >+ shouldReset = true; >+ resetResult.addWatchpointToFire(*a->rareData()->sharedPolyProtoWatchpoint(), StringFireDetail("Detected poly proto optimization opportunity.")); >+ } > } >- } >- }; >+ }; > >- for (auto& caseToAdd : casesToAdd) { >- for (auto& existingCase : m_list) { >- Structure* a = caseToAdd->structure(); >- Structure* b = existingCase->structure(); >- considerPolyProtoReset(a, b); >+ for (auto& caseToAdd : casesToAdd) { >+ for (auto& existingCase : m_list) { >+ Structure* a = caseToAdd->structure(); >+ Structure* b = existingCase->structure(); >+ considerPolyProtoReset(a, b); >+ } > } >- } >- for (unsigned i = 0; i < casesToAdd.size(); ++i) { >- for (unsigned j = i + 1; j < casesToAdd.size(); ++j) { >- Structure* a = casesToAdd[i]->structure(); >- Structure* b = casesToAdd[j]->structure(); >- considerPolyProtoReset(a, b); >+ for (unsigned i = 0; i < casesToAdd.size(); ++i) { >+ for (unsigned j = i + 1; j < casesToAdd.size(); ++j) { >+ Structure* a = casesToAdd[i]->structure(); >+ Structure* b = casesToAdd[j]->structure(); >+ considerPolyProtoReset(a, b); >+ } > } >- } > >- if (shouldReset) >- return resetResult; >+ if (shouldReset) >+ return resetResult; >+ } > > // Now add things to the new list. Note that at this point, we will still have old cases that > // may be replaced by the new ones. That's fine. We will sort that out when we regenerate. >@@ -438,6 +440,18 @@ AccessGenerationResult PolymorphicAccess > } > m_list.resize(dstIndex); > >+ bool generatedFinalCode = false; >+ >+ // If the resulting set of cases is so big that we would stop caching and this is InstanceOf, >+ // then we want to generate the generic InstanceOf and then stop. >+ if (m_list.size() >= Options::maxAccessVariantListSize() >+ && stubInfo.accessType == AccessType::InstanceOf) { >+ while (!cases.isEmpty()) >+ m_list.append(cases.takeLast()); >+ cases.append(AccessCAse::create(vm, codeBlock, InstanceOfGeneric)); >+ generatedFinalCode = true; >+ } >+ > if (PolymorphicAccessInternal::verbose) > dataLog("Optimized cases: ", listDump(cases), "\n"); > >@@ -586,7 +600,7 @@ AccessGenerationResult PolymorphicAccess > m_list = WTFMove(cases); > > AccessGenerationResult::Kind resultKind; >- if (m_list.size() >= Options::maxAccessVariantListSize()) >+ if (m_list.size() >= Options::maxAccessVariantListSize() || generatedFinalCode) > resultKind = AccessGenerationResult::GeneratedFinalCode; > else > resultKind = AccessGenerationResult::GeneratedNewCode; >Index: Source/JavaScriptCore/bytecode/PropertyCondition.cpp >=================================================================== >--- Source/JavaScriptCore/bytecode/PropertyCondition.cpp (revision 231772) >+++ Source/JavaScriptCore/bytecode/PropertyCondition.cpp (working copy) >@@ -1,5 +1,5 @@ > /* >- * Copyright (C) 2015 Apple Inc. All rights reserved. >+ * Copyright (C) 2015-2018 Apple Inc. All rights reserved. > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions >@@ -43,17 +43,19 @@ void PropertyCondition::dumpInContext(Pr > return; > } > >- out.print(m_kind, " of ", m_uid); > switch (m_kind) { > case Presence: >- out.print(" at ", offset(), " with attributes ", attributes()); >+ out.print(m_kind, " of ", m_uid, " at ", offset(), " with attributes ", attributes()); > return; > case Absence: > case AbsenceOfSetEffect: >- out.print(" with prototype ", inContext(JSValue(prototype()), context)); >+ out.print(m_kind, " of ", m_uid, " with prototype ", inContext(JSValue(prototype()), context)); > return; > case Equivalence: >- out.print(" with ", inContext(requiredValue(), context)); >+ out.print(m_kind, " of ", m_uid, " with ", inContext(requiredValue(), context)); >+ return; >+ case HasPrototype: >+ out.print(m_kind, " with prototype ", inContext(JSValue(prototype()), context)); > return; > } > RELEASE_ASSERT_NOT_REACHED(); >@@ -78,11 +80,26 @@ bool PropertyCondition::isStillValidAssu > dataLog("Invalid because unset.\n"); > return false; > } >- >- if (!structure->propertyAccessesAreCacheable()) { >- if (PropertyConditionInternal::verbose) >- dataLog("Invalid because accesses are not cacheable.\n"); >- return false; >+ >+ switch (m_kind) { >+ case Presence: >+ case Absence: >+ case AbsenceOfSetEffect: >+ case Equivalence: >+ if (!structure->propertyAccessesAreCacheable()) { >+ if (PropertyConditionInternal::verbose) >+ dataLog("Invalid because property accesses are not cacheable.\n"); >+ return false; >+ } >+ break; >+ >+ case HasPrototype: >+ if (!structure->prototypeQueriesAreCacheable()) { >+ if (PropertyConditionInternal::verbose) >+ dataLog("Invalid because prototype queries are not cacheable.\n"); >+ return false; >+ } >+ break; > } > > switch (m_kind) { >@@ -174,6 +191,33 @@ bool PropertyCondition::isStillValidAssu > return true; > } > >+ case HasPrototype: { >+ if (structure->isDictionary()) { >+ if (PropertyConditionInternal::verbose) >+ dataLog("Invalid because it's a dictionary.\n"); >+ return false; >+ } >+ >+ if (structure->hasPolyProto()) { >+ // FIXME: I think this is too conservative. We can probably prove this if >+ // we have the base. Anyways, we should make this work when integrating >+ // OPC and poly proto. >+ // https://bugs.webkit.org/show_bug.cgi?id=177339 >+ return false; >+ } >+ >+ if (structure->storedPrototypeObject() != prototype()) { >+ if (PropertyConditionInternal::verbose) { >+ dataLog( >+ "Invalid because the prototype is ", structure->storedPrototype(), " even though " >+ "it should have been ", JSValue(prototype()), "\n"); >+ } >+ return false; >+ } >+ >+ return true; >+ } >+ > case Equivalence: { > if (!base || base->structure() != structure) { > // Conservatively return false, since we cannot verify this one without having the >@@ -226,9 +270,13 @@ bool PropertyCondition::validityRequires > case Absence: > case Equivalence: > return structure->needImpurePropertyWatchpoint(); >- default: >+ case AbsenceOfSetEffect: >+ case HasPrototype: > return false; > } >+ >+ RELEASE_ASSERT_NOT_REACHED(); >+ return false; > } > > bool PropertyCondition::isStillValid(Structure* structure, JSObject* base) const >@@ -375,6 +423,9 @@ void printInternal(PrintStream& out, JSC > case JSC::PropertyCondition::Equivalence: > out.print("Equivalence"); > return; >+ case JSC::PropertyCondition::HasPrototype: >+ out.print("HasPrototype"); >+ return; > } > RELEASE_ASSERT_NOT_REACHED(); > } >Index: Source/JavaScriptCore/bytecode/PropertyCondition.h >=================================================================== >--- Source/JavaScriptCore/bytecode/PropertyCondition.h (revision 231772) >+++ Source/JavaScriptCore/bytecode/PropertyCondition.h (working copy) >@@ -1,5 +1,5 @@ > /* >- * Copyright (C) 2015-2016 Apple Inc. All rights reserved. >+ * Copyright (C) 2015-2018 Apple Inc. All rights reserved. > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions >@@ -38,7 +38,8 @@ public: > Presence, > Absence, > AbsenceOfSetEffect, >- Equivalence // An adaptive watchpoint on this will be a pair of watchpoints, and when the structure transitions, we will set the replacement watchpoint on the new structure. >+ Equivalence, // An adaptive watchpoint on this will be a pair of watchpoints, and when the structure transitions, we will set the replacement watchpoint on the new structure. >+ HasPrototype > }; > > PropertyCondition() >@@ -77,7 +78,7 @@ public: > PropertyCondition result; > result.m_uid = uid; > result.m_kind = Absence; >- result.u.absence.prototype = prototype; >+ result.u.prototype.prototype = prototype; > return result; > } > >@@ -95,7 +96,7 @@ public: > PropertyCondition result; > result.m_uid = uid; > result.m_kind = AbsenceOfSetEffect; >- result.u.absence.prototype = prototype; >+ result.u.prototype.prototype = prototype; > return result; > } > >@@ -125,6 +126,21 @@ public: > return equivalenceWithoutBarrier(uid, value); > } > >+ static PropertyCondition hasPrototypeWithoutBarrier(JSObject* prototype) >+ { >+ PropertyCondition result; >+ result.m_kind = HasPrototype; >+ resutl.u.hasPrototype.prototype = prototype; >+ return result; >+ } >+ >+ static PropertyCondition hasPrototype(VM& vm, JSCell* owner, JSObject* prototype) >+ { >+ if (owner) >+ vm.heap.writeBarrier(owner); >+ return hasPrototypeWithoutBarrier(prototype); >+ } >+ > explicit operator bool() const { return m_uid || m_kind != Presence; } > > Kind kind() const { return m_kind; } >@@ -143,11 +159,15 @@ public: > return u.presence.attributes; > } > >- bool hasPrototype() const { return !!*this && (m_kind == Absence || m_kind == AbsenceOfSetEffect); } >+ bool hasPrototype() const >+ { >+ return !!*this >+ && (m_kind == Absence || m_kind == AbsenceOfSetEffect || m_kind == HasPrototype); >+ } > JSObject* prototype() const > { > ASSERT(hasPrototype()); >- return u.absence.prototype; >+ return u.prototype.prototype; > } > > bool hasRequiredValue() const { return !!*this && m_kind == Equivalence; } >@@ -170,7 +190,8 @@ public: > break; > case Absence: > case AbsenceOfSetEffect: >- result ^= WTF::PtrHash<JSObject*>::hash(u.absence.prototype); >+ case HasPrototype: >+ result ^= WTF::PtrHash<JSObject*>::hash(u.prototype.prototype); > break; > case Equivalence: > result ^= EncodedJSValueHash::hash(u.equivalence.value); >@@ -191,7 +212,8 @@ public: > && u.presence.attributes == other.u.presence.attributes; > case Absence: > case AbsenceOfSetEffect: >- return u.absence.prototype == other.u.absence.prototype; >+ case HasPrototype: >+ return u.prototype.prototype == other.u.prototype.prototype; > case Equivalence: > return u.equivalence.value == other.u.equivalence.value; > } >@@ -300,7 +322,7 @@ private: > } presence; > struct { > JSObject* prototype; >- } absence; >+ } prototype; > struct { > EncodedJSValue value; > } equivalence; >Index: Source/JavaScriptCore/bytecode/StructureStubInfo.cpp >=================================================================== >--- Source/JavaScriptCore/bytecode/StructureStubInfo.cpp (revision 231772) >+++ Source/JavaScriptCore/bytecode/StructureStubInfo.cpp (working copy) >@@ -235,7 +235,10 @@ void StructureStubInfo::reset(CodeBlock* > resetPutByID(codeBlock, *this); > break; > case AccessType::In: >- resetIn(codeBlock, *this); >+ resetIn(*this); >+ break; >+ case AccessType::InstanceOf: >+ resetInstanceOf(*this); > break; > } > >Index: Source/JavaScriptCore/bytecode/StructureStubInfo.h >=================================================================== >--- Source/JavaScriptCore/bytecode/StructureStubInfo.h (revision 231772) >+++ Source/JavaScriptCore/bytecode/StructureStubInfo.h (working copy) >@@ -50,7 +50,8 @@ enum class AccessType : int8_t { > GetDirect, > TryGet, > Put, >- In >+ In, >+ InstanceOf > }; > > enum class CacheType : int8_t { >@@ -138,6 +139,10 @@ public: > // we don't already have a case buffered for. Note that if this returns true but the > // bufferingCountdown is not zero then we will buffer the access case for later without > // immediately generating code for it. >+ // >+ // NOTE: This will behave oddly for InstanceOf if the user varies the prototype but not >+ // the base's structure. That seems unlikely for the canonical use of instanceof, where >+ // the prototype is fixed. > bool isNewlyAdded = bufferedStructures.add(structure); > if (isNewlyAdded) { > VM& vm = *codeBlock->vm(); >@@ -169,7 +174,7 @@ public: > StructureSet bufferedStructures; > > struct { >- CodeLocationLabel<JITStubRoutinePtrTag> start; // This is either the start of the inline IC for *byId caches, or the location of patchable jump for 'in' caches. >+ CodeLocationLabel<JITStubRoutinePtrTag> start; // This is either the start of the inline IC for *byId caches, or the location of patchable jump for 'in' and 'instanceof' caches. > RegisterSet usedRegisters; > uint32_t inlineSize; > int32_t deltaFromStartToSlowPathCallLocation; >Index: Source/JavaScriptCore/dfg/DFGOperations.cpp >=================================================================== >--- Source/JavaScriptCore/dfg/DFGOperations.cpp (revision 231772) >+++ Source/JavaScriptCore/dfg/DFGOperations.cpp (working copy) >@@ -2428,15 +2428,6 @@ int64_t JIT_OPERATION operationConvertDo > return tryConvertToInt52(value); > } > >-size_t JIT_OPERATION operationDefaultHasInstance(ExecState* exec, JSCell* value, JSCell* proto) // Returns jsBoolean(True|False) on 64-bit. >-{ >- VM* vm = &exec->vm(); >- NativeCallFrameTracer tracer(vm, exec); >- if (JSObject::defaultHasInstance(exec, value, proto)) >- return 1; >- return 0; >-} >- > char* JIT_OPERATION operationNewRawObject(ExecState* exec, Structure* structure, int32_t length, Butterfly* butterfly) > { > VM& vm = exec->vm(); >Index: Source/JavaScriptCore/jit/ICStats.h >=================================================================== >--- Source/JavaScriptCore/jit/ICStats.h (revision 231772) >+++ Source/JavaScriptCore/jit/ICStats.h (working copy) >@@ -43,6 +43,8 @@ namespace JSC { > macro(GetByIdSelfPatch) \ > macro(InAddAccessCase) \ > macro(InReplaceWithJump) \ >+ macro(InstanceOfAddAccessCase) \ >+ macro(InstanceOfReplaceWithJump) \ > macro(OperationGetById) \ > macro(OperationGetByIdGeneric) \ > macro(OperationGetByIdBuildList) \ >Index: Source/JavaScriptCore/jit/JIT.cpp >=================================================================== >--- Source/JavaScriptCore/jit/JIT.cpp (revision 231772) >+++ Source/JavaScriptCore/jit/JIT.cpp (working copy) >@@ -478,6 +478,7 @@ void JIT::privateCompileSlowCases() > m_getByIdIndex = 0; > m_getByIdWithThisIndex = 0; > m_putByIdIndex = 0; >+ m_instanceOfIndex = 0; > m_byValInstructionIndex = 0; > m_callLinkInfoIndex = 0; > >@@ -599,6 +600,7 @@ void JIT::privateCompileSlowCases() > RELEASE_ASSERT(m_getByIdIndex == m_getByIds.size()); > RELEASE_ASSERT(m_getByIdWithThisIndex == m_getByIdsWithThis.size()); > RELEASE_ASSERT(m_putByIdIndex == m_putByIds.size()); >+ RELEASE_ASSERT(m_instanceOfIndex == m_instanceOfs.size()); > RELEASE_ASSERT(m_callLinkInfoIndex == m_callCompilationInfo.size()); > RELEASE_ASSERT(numberOfValueProfiles == m_codeBlock->numberOfValueProfiles()); > >@@ -836,13 +838,16 @@ CompilationResult JIT::link() > if (record.callee) > patchBuffer.link(record.from, record.callee); > } >- >- for (unsigned i = m_getByIds.size(); i--;) >- m_getByIds[i].finalize(patchBuffer); >- for (unsigned i = m_getByIdsWithThis.size(); i--;) >- m_getByIdsWithThis[i].finalize(patchBuffer); >- for (unsigned i = m_putByIds.size(); i--;) >- m_putByIds[i].finalize(patchBuffer); >+ >+ auto finalizeInlineCaches = [&] (auto& vector) { >+ for (auto& entry : vector) >+ entry.finalize(patchBuffer, patchBuffer); >+ } >+ >+ finalizeInlineCaches(m_getByIds); >+ finalizeInlineCaches(m_getByIdsWithThis); >+ finalizeInlineCaches(m_putByIds); >+ finalizeInlineCaches(m_instanceOfs); > > if (m_byValCompilationInfo.size()) { > CodeLocationLabel<ExceptionHandlerPtrTag> exceptionHandler = patchBuffer.locationOf<ExceptionHandlerPtrTag>(m_exceptionHandler); >Index: Source/JavaScriptCore/jit/JIT.h >=================================================================== >--- Source/JavaScriptCore/jit/JIT.h (revision 231772) >+++ Source/JavaScriptCore/jit/JIT.h (working copy) >@@ -870,6 +870,7 @@ namespace JSC { > Vector<JITGetByIdGenerator> m_getByIds; > Vector<JITGetByIdWithThisGenerator> m_getByIdsWithThis; > Vector<JITPutByIdGenerator> m_putByIds; >+ Vector<JITInstanceOfGenerator> m_instanceOfs; > Vector<ByValCompilationInfo> m_byValCompilationInfo; > Vector<CallCompilationInfo> m_callCompilationInfo; > Vector<JumpTable> m_jmpTable; >@@ -885,6 +886,7 @@ namespace JSC { > unsigned m_getByIdIndex; > unsigned m_getByIdWithThisIndex; > unsigned m_putByIdIndex; >+ unsigned m_instanceOfIndex; > unsigned m_byValInstructionIndex; > unsigned m_callLinkInfoIndex; > >Index: Source/JavaScriptCore/jit/JITInlineCacheGenerator.cpp >=================================================================== >--- Source/JavaScriptCore/jit/JITInlineCacheGenerator.cpp (revision 231772) >+++ Source/JavaScriptCore/jit/JITInlineCacheGenerator.cpp (working copy) >@@ -43,23 +43,44 @@ static StructureStubInfo* garbageStubInf > } > > JITInlineCacheGenerator::JITInlineCacheGenerator( >- CodeBlock* codeBlock, CodeOrigin codeOrigin, CallSiteIndex callSite, AccessType accessType) >+ CodeBlock* codeBlock, CodeOrigin codeOrigin, CallSiteIndex callSite, AccessType accessType, >+ const RegisterSet& usedRegisters) > : m_codeBlock(codeBlock) > { > m_stubInfo = m_codeBlock ? m_codeBlock->addStubInfo(accessType) : garbageStubInfo(); > m_stubInfo->codeOrigin = codeOrigin; > m_stubInfo->callSiteIndex = callSite; >+ >+ m_stubInfo->patch.usedRegisters = usedRegisters; >+} >+ >+JITInlineCacheGenerator::~JITInlineCacheGenerator() >+{ >+} >+ >+void JITInlineCacheGenerator::finalize( >+ LinkBuffer& fastPath, LinkBuffer& slowPath, CodeLocationLabel<JITStubRoutinePtrTag> start) >+{ >+ m_stubInfo->patch.start = start; >+ >+ int32_t inlineSize = MacroAssembler::differenceBetweenCodePtr( >+ start, fastPath.locationOf<NoPtrTag>(m_done)); >+ ASSERT(inlineSize > 0); >+ m_stubInfo->patch.inlineSize = inlineSize; >+ >+ m_stubInfo->patch.deltaFromStartToSlowPathCallLocation = MacroAssembler::differenceBetweenCodePtr( >+ start, slowPath.locationOf<NoPtrTag>(m_slowPathCall)); >+ m_stubInfo->patch.deltaFromStartToSlowPathStart = MacroAssembler::differenceBetweenCodePtr( >+ start, slowPath.locationOf<NoPtrTag>(m_slowPathBegin)); > } > > JITByIdGenerator::JITByIdGenerator( > CodeBlock* codeBlock, CodeOrigin codeOrigin, CallSiteIndex callSite, AccessType accessType, > const RegisterSet& usedRegisters, JSValueRegs base, JSValueRegs value) >- : JITInlineCacheGenerator(codeBlock, codeOrigin, callSite, accessType) >+ : JITInlineCacheGenerator(codeBlock, codeOrigin, callSite, accessType, usedRegisters) > , m_base(base) > , m_value(value) > { >- m_stubInfo->patch.usedRegisters = usedRegisters; >- > m_stubInfo->patch.baseGPR = static_cast<int8_t>(base.payloadGPR()); > m_stubInfo->patch.valueGPR = static_cast<int8_t>(value.payloadGPR()); > m_stubInfo->patch.thisGPR = static_cast<int8_t>(InvalidGPRReg); >@@ -73,23 +94,8 @@ JITByIdGenerator::JITByIdGenerator( > void JITByIdGenerator::finalize(LinkBuffer& fastPath, LinkBuffer& slowPath) > { > ASSERT(m_start.isSet()); >- CodeLocationLabel<JITStubRoutinePtrTag> start = fastPath.locationOf<JITStubRoutinePtrTag>(m_start); >- m_stubInfo->patch.start = start; >- >- int32_t inlineSize = MacroAssembler::differenceBetweenCodePtr( >- start, fastPath.locationOf<NoPtrTag>(m_done)); >- ASSERT(inlineSize > 0); >- m_stubInfo->patch.inlineSize = inlineSize; >- >- m_stubInfo->patch.deltaFromStartToSlowPathCallLocation = MacroAssembler::differenceBetweenCodePtr( >- start, slowPath.locationOf<NoPtrTag>(m_slowPathCall)); >- m_stubInfo->patch.deltaFromStartToSlowPathStart = MacroAssembler::differenceBetweenCodePtr( >- start, slowPath.locationOf<NoPtrTag>(m_slowPathBegin)); >-} >- >-void JITByIdGenerator::finalize(LinkBuffer& linkBuffer) >-{ >- finalize(linkBuffer, linkBuffer); >+ JITInlineCacheGenerator::finalize( >+ fastPath, slowPath, fastPath.locationOf<JITStubRoutinePtrTag>(m_start)); > } > > void JITByIdGenerator::generateFastCommon(MacroAssembler& jit, size_t inlineICSize) >@@ -165,6 +171,42 @@ V_JITOperation_ESsiJJI JITPutByIdGenerat > return operationPutByIdNonStrictOptimize; > } > >+void JITInstanceOfGenerator::JITInstanceOfGenerator( >+ CodeBlock* codeBlock, CodeOrigin codeOrigin, CallSiteIndex callSiteIndex, >+ const RegisterSet& usedRegisters, GPRReg result, GPRReg value, GPRReg prototype, >+ GPRReg scratch1, GPRReg scratch2) >+ : JITInlineCacheGenerator( >+ codeBlock, codeOrigin, callSiteIndex, AccessType::InstanceOf, usedRegisters) >+ , m_result(result) >+ , m_value(value) >+ , m_prototype(prototype) >+{ >+ m_stubInfo->patch.baseGPR = static_cast<int8_t>(value); >+ m_stubInfo->patch.valueGPR = static_cast<int8_t>(result); >+ m_stubInfo->patch.thisGPR = static_cast<int8_t>(prototype); >+#if USE(JSVALUE32_64) >+ m_stubInfo->patch.baseTagGPR = InvalidGPRReg; >+ m_stubInfo->patch.valueTagGPR = InvalidGPRReg; >+ m_stubInfo->patch.thisTagGPR = InvalidGPRReg; >+#endif >+ >+ m_stubInfo->patch.usedRegisters.clear(result); >+ m_stubInfo->patch.usedRegisters.clear(scratch1); >+ m_stubInfo->patch.usedRegisters.clear(scratch2); >+} >+ >+void JITInstanceOfGenerator::generateFastPath(MacroAssembler& jit) >+{ >+ m_jump = m_jit.patchableJump(); >+} >+ >+void JITInstanceOfGenerator::finalize(LinkBuffer& fastPath, LinkBuffer& slowPath) >+{ >+ JITInlineCacheGenerator::finalize( >+ fastPath, slowPath, >+ linkBuffer.locationOf<JITStubRoutinePtrTag>(m_jump)); >+} >+ > } // namespace JSC > > #endif // ENABLE(JIT) >Index: Source/JavaScriptCore/jit/JITInlineCacheGenerator.h >=================================================================== >--- Source/JavaScriptCore/jit/JITInlineCacheGenerator.h (revision 231772) >+++ Source/JavaScriptCore/jit/JITInlineCacheGenerator.h (working copy) >@@ -1,5 +1,5 @@ > /* >- * Copyright (C) 2013, 2015 Apple Inc. All rights reserved. >+ * Copyright (C) 2013-2018 Apple Inc. All rights reserved. > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions >@@ -45,14 +45,31 @@ enum class AccessType : int8_t; > class JITInlineCacheGenerator { > protected: > JITInlineCacheGenerator() { } >- JITInlineCacheGenerator(CodeBlock*, CodeOrigin, CallSiteIndex, AccessType); >+ JITInlineCacheGenerator( >+ CodeBlock*, CodeOrigin, CallSiteIndex, AccessType, const RegisterSet& usedRegisters); > > public: > StructureStubInfo* stubInfo() const { return m_stubInfo; } > >+ void reportSlowPathCall(MacroAssembler::Label slowPathBegin, MacroAssembler::Call call) >+ { >+ m_slowPathBegin = slowPathBegin; >+ m_slowPathCall = call; >+ } >+ >+ MacroAssembler::Label slowPathBegin() const { return m_slowPathBegin; } >+ >+ void finalize( >+ LinkBuffer& fastPathLinkBuffer, LinkBuffer& slowPathLinkBuffer, >+ CodeLocationLabel<JITStubRoutinePtrTag> start); >+ > protected: > CodeBlock* m_codeBlock; > StructureStubInfo* m_stubInfo; >+ >+ MacroAssembler::Label m_done; >+ MacroAssembler::Label m_slowPathBegin; >+ MacroAssembler::Call m_slowPathCall; > }; > > class JITByIdGenerator : public JITInlineCacheGenerator { >@@ -60,36 +77,27 @@ protected: > JITByIdGenerator() { } > > JITByIdGenerator( >- CodeBlock*, CodeOrigin, CallSiteIndex, AccessType, const RegisterSet&, JSValueRegs base, >- JSValueRegs value); >- >+ CodeBlock*, CodeOrigin, CallSiteIndex, AccessType, const RegisterSet& usedRegisters, >+ JSValueRegs base, JSValueRegs value); >+ > public: >- void reportSlowPathCall(MacroAssembler::Label slowPathBegin, MacroAssembler::Call call) >- { >- m_slowPathBegin = slowPathBegin; >- m_slowPathCall = call; >- } >- >- MacroAssembler::Label slowPathBegin() const { return m_slowPathBegin; } > MacroAssembler::Jump slowPathJump() const > { > ASSERT(m_slowPathJump.isSet()); > return m_slowPathJump; > } > >- void finalize(LinkBuffer& fastPathLinkBuffer, LinkBuffer& slowPathLinkBuffer); >- void finalize(LinkBuffer&); >+ void finalize( >+ LinkBuffer& fastPathLinkBuffer, LinkBuffer& slowPathLinkBuffer); > > protected: >+ > void generateFastCommon(MacroAssembler&, size_t size); > > JSValueRegs m_base; > JSValueRegs m_value; >- >+ > MacroAssembler::Label m_start; >- MacroAssembler::Label m_done; >- MacroAssembler::Label m_slowPathBegin; >- MacroAssembler::Call m_slowPathCall; > MacroAssembler::Jump m_slowPathJump; > }; > >@@ -124,7 +132,7 @@ public: > > JITPutByIdGenerator( > CodeBlock*, CodeOrigin, CallSiteIndex, const RegisterSet& usedRegisters, JSValueRegs base, >- JSValueRegs, GPRReg scratch, ECMAMode, PutKind); >+ JSValueRegs value, GPRReg scratch, ECMAMode, PutKind); > > void generateFastPath(MacroAssembler&); > >@@ -135,6 +143,26 @@ private: > PutKind m_putKind; > }; > >+class JITInstanceOfGenerator : public JITInlineCacheGenerator { >+public: >+ JITInstanceOfGenerator() { } >+ >+ JITInstanceOfGenerator( >+ CodeBlock*, CodeOrigin, CallSiteIndex, const RegisterSet& usedRegisters, GPRReg result, >+ GPRReg value, GPRReg prototype, GPRReg scratch1, GPRReg scratch2); >+ >+ void generateFastPath(MacroAssembler&); >+ >+ void finalize(LinkBuffer& fastPathLinkBuffer, LinkBuffer& slowPathLinkBuffer); >+ >+private: >+ GPRReg m_result; >+ GPRReg m_value; >+ GPRReg m_prototype; >+ >+ MacroAssembler::PatchableJump m_jump; >+}; >+ > } // namespace JSC > > #endif // ENABLE(JIT) >Index: Source/JavaScriptCore/jit/JITOpcodes.cpp >=================================================================== >--- Source/JavaScriptCore/jit/JITOpcodes.cpp (revision 231772) >+++ Source/JavaScriptCore/jit/JITOpcodes.cpp (working copy) >@@ -146,41 +146,37 @@ void JIT::emit_op_instanceof(Instruction > // We use regT0 for baseVal since we will be done with this first, and we can then use it for the result. > emitGetVirtualRegister(value, regT2); > emitGetVirtualRegister(proto, regT1); >- >+ > // Check that proto are cells. baseVal must be a cell - this is checked by the get_by_id for Symbol.hasInstance. > emitJumpSlowCaseIfNotJSCell(regT2, value); > emitJumpSlowCaseIfNotJSCell(regT1, proto); > >- // Check that prototype is an object >- addSlowCase(emitJumpIfCellNotObject(regT1)); >+ JITInstanceOfGenerator gen( >+ m_codeBlock, CodeOrigin(m_bytecodeOffset), CallSiteIndex(m_bytecodeOffset), >+ RegisterSet::stubUnavailableRegisters(), >+ regT0, // result >+ regT2, // value >+ regT1, // proto >+ regT3, regT4); // scratches >+ gen.generateFastPath(*this); >+ m_instanceOfs.append(gen); > >- // Optimistically load the result true, and start looping. >- // Initially, regT1 still contains proto and regT2 still contains value. >- // As we loop regT2 will be updated with its prototype, recursively walking the prototype chain. >- move(TrustedImm64(JSValue::encode(jsBoolean(true))), regT0); >- Label loop(this); >- >- addSlowCase(branch8(Equal, Address(regT2, JSCell::typeInfoTypeOffset()), TrustedImm32(ProxyObjectType))); >- >- // Load the prototype of the object in regT2. If this is equal to regT1 - WIN! >- // Otherwise, check if we've hit null - if we have then drop out of the loop, if not go again. >- emitLoadStructure(*vm(), regT2, regT4, regT3); >- load64(Address(regT4, Structure::prototypeOffset()), regT4); >- auto hasMonoProto = branchTest64(NonZero, regT4); >- load64(Address(regT2, offsetRelativeToBase(knownPolyProtoOffset)), regT4); >- hasMonoProto.link(this); >- move(regT4, regT2); >- Jump isInstance = branchPtr(Equal, regT2, regT1); >- emitJumpIfJSCell(regT2).linkTo(loop, this); >- >- // We get here either by dropping out of the loop, or if value was not an Object. Result is false. >- move(TrustedImm64(JSValue::encode(jsBoolean(false))), regT0); >- >- // isInstance jumps right down to here, to skip setting the result to false (it has already set true). >- isInstance.link(this); > emitPutVirtualRegister(dst); > } > >+void JIT::emitSlow_op_instanceof(Instruction* currentInstruction, Vector<SlowCaseEntry>::iterator& iter) >+{ >+ addAllSlowCases(iter); >+ >+ int resultVReg = currentInstruction[1].u.operand; >+ >+ JITInstanceOfGenerator& gen = m_instanceOfs[m_instanceOfIndex++]; >+ >+ Label coldPathBegin = label(); >+ Call call = callOperation(operationInstanceOfOptimize, resultVReg, gen.stubInfo(), regT2, regT1); >+ gen.reportSlowPathCall(coldPathBegin, call); >+} >+ > void JIT::emit_op_instanceof_custom(Instruction*) > { > // This always goes to slow path since we expect it to be rare. >@@ -905,20 +901,6 @@ void JIT::emitSlow_op_jneq(Instruction* > emitJumpSlowToHot(branchTest32(Zero, returnValueGPR), target); > } > >-void JIT::emitSlow_op_instanceof(Instruction* currentInstruction, Vector<SlowCaseEntry>::iterator& iter) >-{ >- linkAllSlowCases(iter); >- >- auto& bytecode = *reinterpret_cast<OpInstanceof*>(currentInstruction); >- int dst = bytecode.dst(); >- int value = bytecode.value(); >- int proto = bytecode.prototype(); >- >- emitGetVirtualRegister(value, regT0); >- emitGetVirtualRegister(proto, regT1); >- callOperation(operationInstanceOf, dst, regT0, regT1); >-} >- > void JIT::emitSlow_op_instanceof_custom(Instruction* currentInstruction, Vector<SlowCaseEntry>::iterator& iter) > { > linkAllSlowCases(iter); >Index: Source/JavaScriptCore/jit/JITOperations.cpp >=================================================================== >--- Source/JavaScriptCore/jit/JITOperations.cpp (revision 231772) >+++ Source/JavaScriptCore/jit/JITOperations.cpp (working copy) >@@ -2179,6 +2179,34 @@ EncodedJSValue JIT_OPERATION operationIn > return JSValue::encode(jsBoolean(result)); > } > >+EncodedJSValue JIT_OPERATION operationInstanceOfGeneric(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedProto) >+{ >+ VM& vm = exec->vm(); >+ NativeCallFrameTracer tracer(&vm, exec); >+ JSValue value = JSValue::decode(encodedValue); >+ JSValue proto = JSValue::decode(encodedProto); >+ >+ stubInfo->tookSlowPath = true; >+ >+ bool result = JSObject::defaultHasInstance(exec, value, proto); >+ return JSValue::encode(jsBoolean(result)); >+} >+ >+EncodedJSValue JIT_OPERATION operationInstanceOfOptimize(ExecState* exec, StructureStubInfo* stubInfo, EncodedJSValue encodedValue, EncodedJSValue encodedProto) >+{ >+ VM& vm = exec->vm(); >+ NativeCallFrameTracer tracer(&vm, exec); >+ JSValue value = JSValue::decode(encodedValue); >+ JSValue proto = JSValue::decode(encodedProto); >+ >+ bool result = JSObject::defaultHasInstance(exec, value, proto); >+ >+ if (stubInfo->considerCaching(exec->codeBlock(), value.structureOrNull())) >+ repatchInstanceOf(exec, value, proto, stubInfo, result); >+ >+ return JSValue::encode(jsBoolean(result)); >+} >+ > int32_t JIT_OPERATION operationSizeFrameForForwardArguments(ExecState* exec, EncodedJSValue, int32_t numUsedStackSlots, int32_t) > { > VM& vm = exec->vm(); >Index: Source/JavaScriptCore/jit/JITOperations.h >=================================================================== >--- Source/JavaScriptCore/jit/JITOperations.h (revision 231772) >+++ Source/JavaScriptCore/jit/JITOperations.h (working copy) >@@ -451,7 +451,9 @@ size_t JIT_OPERATION operationDeleteByVa > JSCell* JIT_OPERATION operationPushWithScope(ExecState*, JSCell* currentScopeCell, EncodedJSValue object) WTF_INTERNAL; > JSCell* JIT_OPERATION operationPushWithScopeObject(ExecState* exec, JSCell* currentScopeCell, JSObject* object) WTF_INTERNAL; > JSCell* JIT_OPERATION operationGetPNames(ExecState*, JSObject*) WTF_INTERNAL; >-EncodedJSValue JIT_OPERATION operationInstanceOf(ExecState*, EncodedJSValue, EncodedJSValue proto) WTF_INTERNAL; >+EncodedJSValue JIT_OPERATION operationInstanceOf(ExecState*, EncodedJSValue value, EncodedJSValue proto) WTF_INTERNAL; >+EncodedJSValue JIT_OPERATION operationInstanceOfGeneric(ExecState*, StructureStubInfo*, EncodedJSValue value, EncodedJSValue proto) WTF_INTERNAL; >+EncodedJSValue JIT_OPERATION operationInstanceOfOptimize(ExecState*, StructureStubInfo*, EncodedJSValue value, EncodedJSValue proto) WTF_INTERNAL; > int32_t JIT_OPERATION operationSizeFrameForForwardArguments(ExecState*, EncodedJSValue arguments, int32_t numUsedStackSlots, int32_t firstVarArgOffset) WTF_INTERNAL; > int32_t JIT_OPERATION operationSizeFrameForVarargs(ExecState*, EncodedJSValue arguments, int32_t numUsedStackSlots, int32_t firstVarArgOffset) WTF_INTERNAL; > CallFrame* JIT_OPERATION operationSetupForwardArgumentsFrame(ExecState*, CallFrame*, EncodedJSValue, int32_t, int32_t length) WTF_INTERNAL; >Index: Source/JavaScriptCore/jit/Repatch.cpp >=================================================================== >--- Source/JavaScriptCore/jit/Repatch.cpp (revision 231772) >+++ Source/JavaScriptCore/jit/Repatch.cpp (working copy) >@@ -612,7 +612,7 @@ static InlineCacheAction tryCacheIn( > AccessGenerationResult result; > > { >- GCSafeConcurrentJSLocker locker(exec->codeBlock()->m_lock, exec->vm().heap); >+ GCSafeConcurrentJSLocker locker(exec->codeBlock()->m_lock, vm.heap); > if (forceICFailure(exec)) > return GiveUpOnCache; > >@@ -692,6 +692,74 @@ void repatchIn( > ftlThunkAwareRepatchCall(exec->codeBlock(), stubInfo.slowPathCallLocation(), operationIn); > } > >+static InlineCacheAction tryCacheInstanceOf( >+ ExecState* exec, JSValue valueValue, JSValue prototypeValue, StructureStubInfo& stubInfo, >+ bool wasFound) >+{ >+ VM& vm = exec->vm(); >+ CodeBlock* codeBlock = exec->codeBlock(); >+ AccessGenerationResult result; >+ >+ if (forceICFailure(exec)) >+ return GiveUpOnCache; >+ >+ JSObject* value = jsDynamicCast<JSObject*>(valueValue); >+ JSObject* prototype = jsDynamicCast<JSObject*>(prototypeValue); >+ >+ if (!value || !prototype) >+ return GiveUpOnCache; >+ >+ { >+ GCSafeConcurrentJSLocker locker(codeBlock->m_lock, vm.heap); >+ >+ // FIXME: Teach this to do poly proto. >+ // https://bugs.webkit.org/show_bug.cgi?id=185663 >+ >+ std::unique_ptr<AccessCase> newCase; >+ Structure* structure = value->structure(vm); >+ if (structure->prototypeQueriesAreCacheable()) { >+ ObjectPropertyConditionSet conditionSet = generateConditionsForInstanceOf( >+ vm, exec->lexicalGlobalObject(), value->structure(vm), prototype, wasFound); >+ >+ if (conditionSet.isValid()) { >+ newCase = InstanceOfAccessCase::create( >+ vm, codeBlock, wasFound ? AccessCase::InstanceOfHit : AccessCase::InstanceOfMiss, >+ structure, conditionSet, prototype); >+ } >+ } >+ >+ if (!newCase) >+ newCase = AccessCase::create(vm, codeBlock, AccessCase::InstanceOfGeneric); >+ >+ LOG_IC((ICEvent::InstanceOfAddAccessCase, structure->classInfo(), Identifier())); >+ >+ result = stubInfo.addAccessCase(locker, codeBlock, Identifier(), WTFMove(newCase)); >+ >+ if (result.generatedSomeCode()) { >+ LOG_IC((ICEvent::InstanceOfReplaceWithJump, structure->classInfo(), Identifier())); >+ >+ RELEASE_ASSERT(result.code()); >+ >+ MacroAssembler::repatchJump( >+ stubInfo.patchableJumpForIn(), >+ CodeLocationLabel<JITStubRoutinePtrTag>(result.code())); >+ } >+ } >+ >+ fireWatchpointsAndClearStubIfNeeded(vm, stubInfo, codeBlock, result); >+ >+ return result.shouldGiveUpNow() ? GiveUpOnCache : RetryCacheLater; >+} >+ >+void repatchInstanceOf( >+ ExecState* exec, JSValue valueValue, JSValue prototypeValue, StructureStubInfo& stubInfo, >+ bool wasFound) >+{ >+ SuperSamplerScope superSamplerScope(false); >+ if (tryCacheInstanceOf(exec, valueValue, prototypeValue, stubInfo, wasFound) == GiveUpOnCache) >+ ftlThunkAwareRepatchCall(exec->codeBlock(), stubInfo.slowPathCallLocation(), operationInstanceOf); >+} >+ > static void linkSlowFor(VM*, CallLinkInfo& callLinkInfo, MacroAssemblerCodeRef<JITStubRoutinePtrTag> codeRef) > { > MacroAssembler::repatchNearCall(callLinkInfo.callReturnLocation(), CodeLocationLabel<JITStubRoutinePtrTag>(codeRef.code())); >@@ -1160,11 +1228,21 @@ void resetPutByID(CodeBlock* codeBlock, > InlineAccess::rewireStubAsJump(stubInfo, stubInfo.slowPathStartLocation()); > } > >-void resetIn(CodeBlock*, StructureStubInfo& stubInfo) >+static void resetPatchableJump(StructureStubInfo& stubInfo) > { > MacroAssembler::repatchJump(stubInfo.patchableJumpForIn(), stubInfo.slowPathStartLocation()); > } > >+void resetIn(StructureStubInfo& stubInfo) >+{ >+ resetPatchableJUmp(stubInfo); >+} >+ >+void resetInstanceOf(StructureStubInfo& stubInfo) >+{ >+ resetPatchableJUmp(stubInfo); >+} >+ > } // namespace JSC > > #endif >Index: Source/JavaScriptCore/jit/Repatch.h >=================================================================== >--- Source/JavaScriptCore/jit/Repatch.h (revision 231772) >+++ Source/JavaScriptCore/jit/Repatch.h (working copy) >@@ -45,6 +45,7 @@ void buildGetByIDProtoList(ExecState*, J > void repatchPutByID(ExecState*, JSValue, Structure*, const Identifier&, const PutPropertySlot&, StructureStubInfo&, PutKind); > void buildPutByIdList(ExecState*, JSValue, Structure*, const Identifier&, const PutPropertySlot&, StructureStubInfo&, PutKind); > void repatchIn(ExecState*, JSCell*, const Identifier&, bool wasFound, const PropertySlot&, StructureStubInfo&); >+void repatchInstanceOf(ExecState*, JSValue value, JSValue prototype, StructureStubInfo&, bool wasFound); > void linkFor(ExecState*, CallLinkInfo&, CodeBlock*, JSObject* callee, MacroAssemblerCodePtr<JSEntryPtrTag>); > void linkDirectFor(ExecState*, CallLinkInfo&, CodeBlock*, MacroAssemblerCodePtr<JSEntryPtrTag>); > void linkSlowFor(ExecState*, CallLinkInfo&); >@@ -53,7 +54,8 @@ void linkVirtualFor(ExecState*, CallLink > void linkPolymorphicCall(ExecState*, CallLinkInfo&, CallVariant); > void resetGetByID(CodeBlock*, StructureStubInfo&, GetByIDKind); > void resetPutByID(CodeBlock*, StructureStubInfo&); >-void resetIn(CodeBlock*, StructureStubInfo&); >+void resetIn(StructureStubInfo&); >+void resetInstanceOf(StructureStubInfo&); > void ftlThunkAwareRepatchCall(CodeBlock*, CodeLocationCall<JSInternalPtrTag>, FunctionPtr<CFunctionPtrTag> newCalleeFunction); > > } // namespace JSC >Index: Source/JavaScriptCore/runtime/Options.h >=================================================================== >--- Source/JavaScriptCore/runtime/Options.h (revision 231772) >+++ Source/JavaScriptCore/runtime/Options.h (working copy) >@@ -284,7 +284,7 @@ constexpr bool enableWebAssemblyStreamin > v(bool, logExecutableAllocation, false, Normal, nullptr) \ > \ > v(bool, useConcurrentJIT, true, Normal, "allows the DFG / FTL compilation in threads other than the executing JS thread") \ >- v(unsigned, numberOfDFGCompilerThreads, computeNumberOfWorkerThreads(3, 2) - 1, Normal, nullptr) \ >+ v(unsigned, numberOfDFGCompilerThreads, computeNumberOfWorkerThreads(MAXIMUM_NUMBER_OF_FTL_COMPILER_THREADS, 2) - 1, Normal, nullptr) \ > v(unsigned, numberOfFTLCompilerThreads, computeNumberOfWorkerThreads(MAXIMUM_NUMBER_OF_FTL_COMPILER_THREADS, 2) - 1, Normal, nullptr) \ > v(int32, priorityDeltaOfDFGCompilerThreads, computePriorityDeltaOfWorkerThreads(-1, 0), Normal, nullptr) \ > v(int32, priorityDeltaOfFTLCompilerThreads, computePriorityDeltaOfWorkerThreads(-2, 0), Normal, nullptr) \ >Index: Source/JavaScriptCore/runtime/Structure.h >=================================================================== >--- Source/JavaScriptCore/runtime/Structure.h (revision 231772) >+++ Source/JavaScriptCore/runtime/Structure.h (working copy) >@@ -218,10 +218,15 @@ public: > bool isDictionary() const { return dictionaryKind() != NoneDictionaryKind; } > bool isUncacheableDictionary() const { return dictionaryKind() == UncachedDictionaryKind; } > >- bool propertyAccessesAreCacheable() >+ bool prototypeQueriesAreCacheable() > { > return dictionaryKind() != UncachedDictionaryKind >- && !typeInfo().prohibitsPropertyCaching() >+ && !typeInfo().prohibitsPropertyCaching(); >+ } >+ >+ bool propertyAccessesAreCacheable() >+ { >+ return prototypeQueriesAreCacheable() > && !(typeInfo().getOwnPropertySlotIsImpure() && !typeInfo().newImpurePropertyFiresWatchpoints()); > } >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 185652
:
340417
|
340419
|
340440
|
340446
|
340499
|
340511
|
340512
|
340537
|
340548
|
340549
|
340551
|
340555
|
340556
|
340573
|
340574
|
340576
|
340580
|
340585
|
340593
|
340616
|
340636
|
340649
|
340665
|
340703
|
340704