Implement polymorphic prototypes
authorsbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Oct 2017 01:53:18 +0000 (01:53 +0000)
committersbarati@apple.com <sbarati@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 4 Oct 2017 01:53:18 +0000 (01:53 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176391

Reviewed by Filip Pizlo.

JSTests:

* microbenchmarks/poly-proto-access.js: Added.
(assert):
(foo.C):
(foo.C.prototype.get bar):
(foo):
(bar):
* microbenchmarks/poly-proto-put-transition-speed.js: Added.
(assert):
(makePolyProtoObject.foo.C):
(makePolyProtoObject.foo):
(makePolyProtoObject):
(performSet):
* microbenchmarks/poly-proto-setter-speed.js: Added.
(assert):
(makePolyProtoObject.foo.C):
(makePolyProtoObject.foo.C.prototype.set p):
(makePolyProtoObject.foo):
(makePolyProtoObject):
(performSet):
* stress/constructor-with-return.js:
(i.tests.forEach.Constructor):
(i.tests.forEach):
(tests.forEach.Constructor): Deleted.
(tests.forEach): Deleted.
* stress/dom-jit-with-poly-proto.js: Added.
(assert):
(makePolyProtoObject.foo.C):
(makePolyProtoObject.foo):
(makePolyProtoObject):
(validate):
* stress/poly-proto-custom-value-and-accessor.js: Added.
(assert):
(makePolyProtoObject.foo.C):
(makePolyProtoObject.foo):
(makePolyProtoObject):
(items.forEach):
(set get for):
* stress/poly-proto-intrinsic-getter-correctness.js: Added.
(assert):
(makePolyProtoObject.foo.C):
(makePolyProtoObject.foo):
(makePolyProtoObject):
(foo):
* stress/poly-proto-miss.js: Added.
(makePolyProtoInstanceWithNullPrototype.foo.C):
(makePolyProtoInstanceWithNullPrototype.foo):
(makePolyProtoInstanceWithNullPrototype):
(assert):
(validate):
* stress/poly-proto-op-in-caching.js: Added.
(assert):
(makePolyProtoObject.foo.C):
(makePolyProtoObject.foo):
(makePolyProtoObject):
(validate):
(validate2):
* stress/poly-proto-put-transition.js: Added.
(assert):
(makePolyProtoObject.foo.C):
(makePolyProtoObject.foo):
(makePolyProtoObject):
(performSet):
(i.obj.__proto__.set p):
* stress/poly-proto-set-prototype.js: Added.
(assert):
(let.alternateProto.get x):
(let.alternateProto2.get y):
(let.alternateProto2.get x):
(foo.C):
(foo):
(validate):
* stress/poly-proto-setter.js: Added.
(assert):
(makePolyProtoObject.foo.C):
(makePolyProtoObject.foo.C.prototype.set p):
(makePolyProtoObject.foo.C.prototype.get p):
(makePolyProtoObject.foo):
(makePolyProtoObject):
(performSet):
* stress/poly-proto-using-inheritance.js: Added.
(assert):
(foo.C):
(foo.C.prototype.get baz):
(foo):
(bar.C):
(bar):
(validate):
* stress/primitive-poly-proto.js: Added.
(makePolyProtoInstance.foo.C):
(makePolyProtoInstance.foo):
(makePolyProtoInstance):
(assert):
(validate):
* stress/prototype-is-not-js-object.js: Added.
(foo.bar):
(foo):
(assert):
(validate):
* stress/try-get-by-id-poly-proto.js: Added.
(assert):
(makePolyProtoObject.foo.C):
(makePolyProtoObject.foo):
(makePolyProtoObject):
(tryGetByIdText):
(x.__proto__.get bar):
(validate):
* typeProfiler/overflow.js:

Source/JavaScriptCore:

This patch changes JSC's object model with respect to where the prototype
of an object is stored. Previously, it was always stored as
a constant value inside Structure. So an object's structure used to
always tell you what its prototype is. Anytime an object changed
its prototype, it would do a structure transition. This enables
a large class of optimizations: just by doing a structure check,
we know what the prototype is.

However, this design falls down when you have many objects that
have the same shape, but only differ in what their prototype value
is. This arises in many JS programs. A simple, and probably common, example
is when the program has a constructor inside of a function:
```
function foo() {
    class C {
        constructor() { this.field1 = 42; ...; this.fieldN = 42; }
        method1() { doStuffWith(this.field); }
        method2() { doStuffWith(this.field); }
    }
    let c = new C;
    do things with c;
    }
repeatedly call foo() here.
```

Before this patch, in the above program, each time `new C` created an
object, it would create an object with a different structure. The
reason for this is that each time foo is called, there is a new
instance of C.prototype. However, each `new C` that was created
with have identical shape sans its prototype value. This would
cause all ICs that used `c` to quickly give up on any form of caching
because they would see too many structures and give up and permanently
divert control flow to the slow path.

This patch fixes this issue by expanding the notion of where the prototype
of an object is stored. There are now two notions of where the prototype
is stored. A Structure can now be in two modes:
1. Mono proto mode. This is the same mode as we used to have. It means
the structure itself has a constant prototype value.
2. Poly proto mode. This means the structure knows nothing about the
prototype value itself. Objects with this structure store their prototype
in normal object field storage. The structure will tell you the offset of
this prototype inside the object's storage. As of today, we only reserve
inline slots for the prototype field because poly proto only occurs
for JSFinalObject. However, this will be expanded to support out of line
offsets in a future patch when we extend poly proto to work when we inherit
from builtin types like Map and Array.

In this initial patch, we do poly proto style inline caching whenever
we see an object that is poly proto or if an object in its prototype lookup
chain is poly proto. Poly proto ICs work by verifying the lookup chain
at runtime. This essentially boils down to performing structure checks
up the prototype chain. In a future patch, we're going to extend object
property condition set to work with objects that don't have poly proto bases.

Initially, accesses that have poly proto access chains will always turn
into GetById/PutById in the DFG. In a future patch, I'm going to teach
the DFG how to inline certain accesses that have poly proto in the access
chain.

One of most interesting parts about this patch is how we decide when to go
poly proto. This patch uses a profiling based approach. An IC will inform
a watchpoint that it sees an opportunity when two Structure's are structurally
the same, sans the base object's prototype. This means that two structures
have equivalent shapes all the way up the prototype chain. To support fast
structural comparison, we compute a hash for a structure based on the properties
it has. We compute this hash as we add properties to the structure. This
computation is nearly free since we always add UniquedStringImpl*'s which
already have their hashes computed. To compare structural equivalence, we
just compare hash values all the way up the prototype chain. This means we
can get hash conflicts between two structures, but it's extremely rare. First,
it'll be rare for two structures to have the same hash. Secondly, we only
consider structures originating from the same executable.

How we set up this poly proto watchpoint is crucial to its design. When we create_this
an object originating from some executable, that executable will create a Box<InlineWatchpointSet>.
Each structure that originates from this executable will get a copy of that
Box<InlineWatchpointSet>. As that structure transitions to new structures,
they too will get a copy of that Box<InilneWatchpointSet>. Therefore, when
invalidating an arbitrary structure's poly proto watchpoint, we will know
the next time we create_this from that executable that it had been
invalidated, and that we should create an object with a poly proto
structure. We also use the pointer value of this Box<InlineWatchpointSet>
to determine if two structures originated from the same executable. This
pruning will severely limit the chances of getting a hash conflict in practice.

This patch is neutral on my MBP on traditional JS benchmarks like Octane/Kraken/Sunspider.
It may be a 1-2% ARES-6 progression.

This patch is between neutral and a 9x progression on the various tests
I added. Most of the microbenchmarks are progressed by at least 50%.

* JavaScriptCore.xcodeproj/project.pbxproj:
* Sources.txt:
* builtins/BuiltinNames.cpp:
* builtins/BuiltinNames.h:
(JSC::BuiltinNames::BuiltinNames):
(JSC::BuiltinNames::underscoreProtoPrivateName const):
* bytecode/AccessCase.cpp:
(JSC::AccessCase::AccessCase):
(JSC::AccessCase::create):
(JSC::AccessCase::commit):
(JSC::AccessCase::guardedByStructureCheck const):
(JSC::AccessCase::canReplace const):
(JSC::AccessCase::dump const):
(JSC::AccessCase::visitWeak const):
(JSC::AccessCase::propagateTransitions const):
(JSC::AccessCase::generateWithGuard):
(JSC::AccessCase::generateImpl):
* bytecode/AccessCase.h:
(JSC::AccessCase::usesPolyProto const):
(JSC::AccessCase::AccessCase):
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::finishCreation):
* bytecode/GetByIdStatus.cpp:
(JSC::GetByIdStatus::computeForStubInfoWithoutExitSiteFeedback):
* bytecode/GetterSetterAccessCase.cpp:
(JSC::GetterSetterAccessCase::GetterSetterAccessCase):
(JSC::GetterSetterAccessCase::create):
* bytecode/GetterSetterAccessCase.h:
* bytecode/InternalFunctionAllocationProfile.h:
(JSC::InternalFunctionAllocationProfile::createAllocationStructureFromBase):
* bytecode/IntrinsicGetterAccessCase.cpp:
(JSC::IntrinsicGetterAccessCase::IntrinsicGetterAccessCase):
* bytecode/IntrinsicGetterAccessCase.h:
* bytecode/ModuleNamespaceAccessCase.cpp:
(JSC::ModuleNamespaceAccessCase::ModuleNamespaceAccessCase):
* bytecode/ObjectAllocationProfile.cpp: Added.
(JSC::ObjectAllocationProfile::initializeProfile):
(JSC::ObjectAllocationProfile::possibleDefaultPropertyCount):
* bytecode/ObjectAllocationProfile.h:
(JSC::ObjectAllocationProfile::clear):
(JSC::ObjectAllocationProfile::initialize): Deleted.
(JSC::ObjectAllocationProfile::possibleDefaultPropertyCount): Deleted.
* bytecode/ObjectPropertyConditionSet.cpp:
* bytecode/PolyProtoAccessChain.cpp: Added.
(JSC::PolyProtoAccessChain::create):
(JSC::PolyProtoAccessChain::needImpurePropertyWatchpoint const):
(JSC::PolyProtoAccessChain::operator== const):
(JSC::PolyProtoAccessChain::dump const):
* bytecode/PolyProtoAccessChain.h: Added.
(JSC::PolyProtoAccessChain::clone):
(JSC::PolyProtoAccessChain:: const):
(JSC::PolyProtoAccessChain::operator!= const):
(JSC::PolyProtoAccessChain::forEach const):
* bytecode/PolymorphicAccess.cpp:
(JSC::PolymorphicAccess::addCases):
(JSC::PolymorphicAccess::regenerate):
(WTF::printInternal):
* bytecode/PolymorphicAccess.h:
(JSC::AccessGenerationResult::shouldResetStub const):
(JSC::AccessGenerationState::AccessGenerationState):
* bytecode/PropertyCondition.cpp:
(JSC::PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint const):
* bytecode/ProxyableAccessCase.cpp:
(JSC::ProxyableAccessCase::ProxyableAccessCase):
(JSC::ProxyableAccessCase::create):
* bytecode/ProxyableAccessCase.h:
* bytecode/PutByIdStatus.cpp:
(JSC::PutByIdStatus::computeForStubInfo):
* bytecode/StructureStubInfo.cpp:
(JSC::StructureStubInfo::addAccessCase):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::load):
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGGraph.cpp:
(JSC::DFG::Graph::canDoFastSpread):
* dfg/DFGOperations.cpp:
* dfg/DFGSpeculativeJIT.cpp:
(JSC::DFG::SpeculativeJIT::compileInstanceOfForObject):
(JSC::DFG::SpeculativeJIT::compileInstanceOf):
* dfg/DFGSpeculativeJIT.h:
* dfg/DFGSpeculativeJIT64.cpp:
(JSC::DFG::SpeculativeJIT::compile):
* ftl/FTLLowerDFGToB3.cpp:
(JSC::FTL::DFG::LowerDFGToB3::compileInstanceOf):
* jit/JITOpcodes.cpp:
(JSC::JIT::emit_op_instanceof):
* jit/JITOpcodes32_64.cpp:
(JSC::JIT::emit_op_instanceof):
* jit/Repatch.cpp:
(JSC::tryCacheGetByID):
(JSC::tryCachePutByID):
(JSC::tryRepatchIn):
* jsc.cpp:
(WTF::DOMJITGetterBaseJSObject::DOMJITGetterBaseJSObject):
(WTF::DOMJITGetterBaseJSObject::createStructure):
(WTF::DOMJITGetterBaseJSObject::create):
(WTF::DOMJITGetterBaseJSObject::DOMJITAttribute::DOMJITAttribute):
(WTF::DOMJITGetterBaseJSObject::DOMJITAttribute::slowCall):
(WTF::DOMJITGetterBaseJSObject::DOMJITAttribute::callDOMGetter):
(WTF::DOMJITGetterBaseJSObject::customGetter):
(WTF::DOMJITGetterBaseJSObject::finishCreation):
(GlobalObject::finishCreation):
(functionCreateDOMJITGetterBaseJSObject):
* llint/LLIntSlowPaths.cpp:
(JSC::LLInt::LLINT_SLOW_PATH_DECL):
* runtime/ArrayPrototype.cpp:
(JSC::holesMustForwardToPrototype):
(JSC::fastJoin):
(JSC::arrayProtoFuncReverse):
(JSC::moveElements):
* runtime/ClonedArguments.cpp:
(JSC::ClonedArguments::createEmpty):
(JSC::ClonedArguments::createWithInlineFrame):
(JSC::ClonedArguments::createWithMachineFrame):
(JSC::ClonedArguments::createByCopyingFrom):
* runtime/CommonSlowPaths.cpp:
(JSC::SLOW_PATH_DECL):
* runtime/FunctionExecutable.cpp:
(JSC::FunctionExecutable::visitChildren):
* runtime/FunctionExecutable.h:
* runtime/FunctionRareData.cpp:
(JSC::FunctionRareData::initializeObjectAllocationProfile):
* runtime/FunctionRareData.h:
* runtime/InternalFunction.cpp:
(JSC::InternalFunction::createSubclassStructureSlow):
* runtime/JSArray.cpp:
(JSC::JSArray::fastSlice):
(JSC::JSArray::shiftCountWithArrayStorage):
(JSC::JSArray::shiftCountWithAnyIndexingType):
(JSC::JSArray::isIteratorProtocolFastAndNonObservable):
* runtime/JSArrayInlines.h:
(JSC::JSArray::canFastCopy):
* runtime/JSCJSValue.cpp:
(JSC::JSValue::dumpInContextAssumingStructure const):
* runtime/JSFunction.cpp:
(JSC::JSFunction::prototypeForConstruction):
(JSC::JSFunction::allocateAndInitializeRareData):
(JSC::JSFunction::initializeRareData):
(JSC::JSFunction::getOwnPropertySlot):
* runtime/JSFunction.h:
* runtime/JSMap.cpp:
(JSC::JSMap::isIteratorProtocolFastAndNonObservable):
(JSC::JSMap::canCloneFastAndNonObservable):
* runtime/JSObject.cpp:
(JSC::JSObject::putInlineSlow):
(JSC::JSObject::createInitialIndexedStorage):
(JSC::JSObject::createArrayStorage):
(JSC::JSObject::convertUndecidedToArrayStorage):
(JSC::JSObject::convertInt32ToArrayStorage):
(JSC::JSObject::convertDoubleToArrayStorage):
(JSC::JSObject::convertContiguousToArrayStorage):
(JSC::JSObject::ensureInt32Slow):
(JSC::JSObject::ensureDoubleSlow):
(JSC::JSObject::ensureContiguousSlow):
(JSC::JSObject::ensureArrayStorageSlow):
(JSC::JSObject::setPrototypeDirect):
(JSC::JSObject::ordinaryToPrimitive const):
(JSC::JSObject::putByIndexBeyondVectorLength):
(JSC::JSObject::putDirectIndexSlowOrBeyondVectorLength):
(JSC::JSObject::getEnumerableLength):
(JSC::JSObject::anyObjectInChainMayInterceptIndexedAccesses const):
(JSC::JSObject::prototypeChainMayInterceptStoreTo):
(JSC::JSObject::needsSlowPutIndexing const):
(JSC::JSObject::suggestedArrayStorageTransition const):
* runtime/JSObject.h:
(JSC::JSObject::finishCreation):
(JSC::JSObject::getPrototypeDirect const):
(JSC::JSObject::getPropertySlot):
* runtime/JSObjectInlines.h:
(JSC::JSObject::getPropertySlot):
(JSC::JSObject::getNonIndexPropertySlot):
(JSC::JSObject::putInlineForJSObject):
* runtime/JSPropertyNameEnumerator.h:
(JSC::propertyNameEnumerator):
* runtime/JSSet.cpp:
(JSC::JSSet::isIteratorProtocolFastAndNonObservable):
(JSC::JSSet::canCloneFastAndNonObservable):
* runtime/LazyClassStructure.h:
(JSC::LazyClassStructure::prototypeConcurrently const): Deleted.
* runtime/Operations.cpp:
(JSC::normalizePrototypeChain):
* runtime/Operations.h:
* runtime/Options.h:
* runtime/PrototypeMap.cpp:
(JSC::PrototypeMap::createEmptyStructure):
(JSC::PrototypeMap::emptyStructureForPrototypeFromBaseStructure):
(JSC::PrototypeMap::emptyObjectStructureForPrototype):
(JSC::PrototypeMap::clearEmptyObjectStructureForPrototype):
* runtime/PrototypeMap.h:
* runtime/Structure.cpp:
(JSC::Structure::Structure):
(JSC::Structure::create):
(JSC::Structure::holesMustForwardToPrototype const):
(JSC::Structure::changePrototypeTransition):
(JSC::Structure::isCheapDuringGC):
(JSC::Structure::toStructureShape):
(JSC::Structure::dump const):
(JSC::Structure::canCachePropertyNameEnumerator const):
(JSC::Structure::anyObjectInChainMayInterceptIndexedAccesses const): Deleted.
(JSC::Structure::needsSlowPutIndexing const): Deleted.
(JSC::Structure::suggestedArrayStorageTransition const): Deleted.
(JSC::Structure::prototypeForLookup const): Deleted.
(JSC::Structure::prototypeChainMayInterceptStoreTo): Deleted.
(JSC::Structure::canUseForAllocationsOf): Deleted.
* runtime/Structure.h:
* runtime/StructureChain.h:
* runtime/StructureInlines.h:
(JSC::Structure::create):
(JSC::Structure::storedPrototypeObject const):
(JSC::Structure::storedPrototypeStructure const):
(JSC::Structure::storedPrototype const):
(JSC::prototypeForLookupPrimitiveImpl):
(JSC::Structure::prototypeForLookup const):
(JSC::Structure::prototypeChain const):
(JSC::Structure::isValid const):
(JSC::Structure::add):
(JSC::Structure::setPropertyTable):
(JSC::Structure::shouldConvertToPolyProto):
* runtime/StructureRareData.h:
* runtime/TypeProfilerLog.cpp:
(JSC::TypeProfilerLog::processLogEntries):
* runtime/TypeSet.cpp:
(JSC::TypeSet::addTypeInformation):
* runtime/TypeSet.h:
* runtime/WriteBarrier.h:
(JSC::WriteBarrierBase<Unknown>::isInt32 const):

Source/WTF:

* wtf/Box.h:
(WTF::Box::operator bool const):
(WTF::Box::operator bool): Deleted.
Make Box movable. Also ensure its operator bool doesn't do an atomic increment.
* wtf/RefPtr.h:
(WTF::RefPtr::operator bool const):
Add `explicit operator bool()` for RefPtr.

Tools:

* Scripts/run-jsc-stress-tests:

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

96 files changed:
JSTests/ChangeLog
JSTests/microbenchmarks/poly-proto-access.js [new file with mode: 0644]
JSTests/microbenchmarks/poly-proto-put-transition-speed.js [new file with mode: 0644]
JSTests/microbenchmarks/poly-proto-setter-speed.js [new file with mode: 0644]
JSTests/stress/constructor-with-return.js
JSTests/stress/dom-jit-with-poly-proto.js [new file with mode: 0644]
JSTests/stress/poly-proto-custom-value-and-accessor.js [new file with mode: 0644]
JSTests/stress/poly-proto-intrinsic-getter-correctness.js [new file with mode: 0644]
JSTests/stress/poly-proto-miss.js [new file with mode: 0644]
JSTests/stress/poly-proto-op-in-caching.js [new file with mode: 0644]
JSTests/stress/poly-proto-put-transition.js [new file with mode: 0644]
JSTests/stress/poly-proto-set-prototype.js [new file with mode: 0644]
JSTests/stress/poly-proto-setter.js [new file with mode: 0644]
JSTests/stress/poly-proto-using-inheritance.js [new file with mode: 0644]
JSTests/stress/primitive-poly-proto.js [new file with mode: 0644]
JSTests/stress/prototype-is-not-js-object.js [new file with mode: 0644]
JSTests/stress/try-get-by-id-poly-proto.js [new file with mode: 0644]
JSTests/typeProfiler/overflow.js
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
Source/JavaScriptCore/Sources.txt
Source/JavaScriptCore/builtins/BuiltinNames.cpp
Source/JavaScriptCore/builtins/BuiltinNames.h
Source/JavaScriptCore/bytecode/AccessCase.cpp
Source/JavaScriptCore/bytecode/AccessCase.h
Source/JavaScriptCore/bytecode/CodeBlock.cpp
Source/JavaScriptCore/bytecode/GetByIdStatus.cpp
Source/JavaScriptCore/bytecode/GetterSetterAccessCase.cpp
Source/JavaScriptCore/bytecode/GetterSetterAccessCase.h
Source/JavaScriptCore/bytecode/InternalFunctionAllocationProfile.h
Source/JavaScriptCore/bytecode/IntrinsicGetterAccessCase.cpp
Source/JavaScriptCore/bytecode/IntrinsicGetterAccessCase.h
Source/JavaScriptCore/bytecode/ModuleNamespaceAccessCase.cpp
Source/JavaScriptCore/bytecode/ObjectAllocationProfile.cpp [new file with mode: 0644]
Source/JavaScriptCore/bytecode/ObjectAllocationProfile.h
Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp
Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp [new file with mode: 0644]
Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h [new file with mode: 0644]
Source/JavaScriptCore/bytecode/PolymorphicAccess.cpp
Source/JavaScriptCore/bytecode/PolymorphicAccess.h
Source/JavaScriptCore/bytecode/PropertyCondition.cpp
Source/JavaScriptCore/bytecode/ProxyableAccessCase.cpp
Source/JavaScriptCore/bytecode/ProxyableAccessCase.h
Source/JavaScriptCore/bytecode/PutByIdStatus.cpp
Source/JavaScriptCore/bytecode/StructureStubInfo.cpp
Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
Source/JavaScriptCore/dfg/DFGGraph.cpp
Source/JavaScriptCore/dfg/DFGOperations.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.cpp
Source/JavaScriptCore/dfg/DFGSpeculativeJIT.h
Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
Source/JavaScriptCore/jit/JITOpcodes.cpp
Source/JavaScriptCore/jit/JITOpcodes32_64.cpp
Source/JavaScriptCore/jit/Repatch.cpp
Source/JavaScriptCore/jsc.cpp
Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
Source/JavaScriptCore/runtime/ArrayPrototype.cpp
Source/JavaScriptCore/runtime/ClonedArguments.cpp
Source/JavaScriptCore/runtime/CommonSlowPaths.cpp
Source/JavaScriptCore/runtime/FunctionExecutable.cpp
Source/JavaScriptCore/runtime/FunctionExecutable.h
Source/JavaScriptCore/runtime/FunctionRareData.cpp
Source/JavaScriptCore/runtime/FunctionRareData.h
Source/JavaScriptCore/runtime/InternalFunction.cpp
Source/JavaScriptCore/runtime/JSArray.cpp
Source/JavaScriptCore/runtime/JSArrayInlines.h
Source/JavaScriptCore/runtime/JSCJSValue.cpp
Source/JavaScriptCore/runtime/JSFunction.cpp
Source/JavaScriptCore/runtime/JSFunction.h
Source/JavaScriptCore/runtime/JSMap.cpp
Source/JavaScriptCore/runtime/JSObject.cpp
Source/JavaScriptCore/runtime/JSObject.h
Source/JavaScriptCore/runtime/JSObjectInlines.h
Source/JavaScriptCore/runtime/JSPropertyNameEnumerator.h
Source/JavaScriptCore/runtime/JSSet.cpp
Source/JavaScriptCore/runtime/LazyClassStructure.h
Source/JavaScriptCore/runtime/Operations.cpp
Source/JavaScriptCore/runtime/Operations.h
Source/JavaScriptCore/runtime/Options.h
Source/JavaScriptCore/runtime/PrototypeMap.cpp
Source/JavaScriptCore/runtime/PrototypeMap.h
Source/JavaScriptCore/runtime/Structure.cpp
Source/JavaScriptCore/runtime/Structure.h
Source/JavaScriptCore/runtime/StructureChain.h
Source/JavaScriptCore/runtime/StructureInlines.h
Source/JavaScriptCore/runtime/StructureRareData.h
Source/JavaScriptCore/runtime/TypeProfilerLog.cpp
Source/JavaScriptCore/runtime/TypeSet.cpp
Source/JavaScriptCore/runtime/TypeSet.h
Source/JavaScriptCore/runtime/WriteBarrier.h
Source/WTF/ChangeLog
Source/WTF/wtf/Box.h
Source/WTF/wtf/RefPtr.h
Tools/ChangeLog
Tools/Scripts/run-jsc-stress-tests

index add1388..941cd6e 100644 (file)
@@ -1,3 +1,118 @@
+2017-10-03  Saam Barati  <sbarati@apple.com>
+
+        Implement polymorphic prototypes
+        https://bugs.webkit.org/show_bug.cgi?id=176391
+
+        Reviewed by Filip Pizlo.
+
+        * microbenchmarks/poly-proto-access.js: Added.
+        (assert):
+        (foo.C):
+        (foo.C.prototype.get bar):
+        (foo):
+        (bar):
+        * microbenchmarks/poly-proto-put-transition-speed.js: Added.
+        (assert):
+        (makePolyProtoObject.foo.C):
+        (makePolyProtoObject.foo):
+        (makePolyProtoObject):
+        (performSet):
+        * microbenchmarks/poly-proto-setter-speed.js: Added.
+        (assert):
+        (makePolyProtoObject.foo.C):
+        (makePolyProtoObject.foo.C.prototype.set p):
+        (makePolyProtoObject.foo):
+        (makePolyProtoObject):
+        (performSet):
+        * stress/constructor-with-return.js:
+        (i.tests.forEach.Constructor):
+        (i.tests.forEach):
+        (tests.forEach.Constructor): Deleted.
+        (tests.forEach): Deleted.
+        * stress/dom-jit-with-poly-proto.js: Added.
+        (assert):
+        (makePolyProtoObject.foo.C):
+        (makePolyProtoObject.foo):
+        (makePolyProtoObject):
+        (validate):
+        * stress/poly-proto-custom-value-and-accessor.js: Added.
+        (assert):
+        (makePolyProtoObject.foo.C):
+        (makePolyProtoObject.foo):
+        (makePolyProtoObject):
+        (items.forEach):
+        (set get for):
+        * stress/poly-proto-intrinsic-getter-correctness.js: Added.
+        (assert):
+        (makePolyProtoObject.foo.C):
+        (makePolyProtoObject.foo):
+        (makePolyProtoObject):
+        (foo):
+        * stress/poly-proto-miss.js: Added.
+        (makePolyProtoInstanceWithNullPrototype.foo.C):
+        (makePolyProtoInstanceWithNullPrototype.foo):
+        (makePolyProtoInstanceWithNullPrototype):
+        (assert):
+        (validate):
+        * stress/poly-proto-op-in-caching.js: Added.
+        (assert):
+        (makePolyProtoObject.foo.C):
+        (makePolyProtoObject.foo):
+        (makePolyProtoObject):
+        (validate):
+        (validate2):
+        * stress/poly-proto-put-transition.js: Added.
+        (assert):
+        (makePolyProtoObject.foo.C):
+        (makePolyProtoObject.foo):
+        (makePolyProtoObject):
+        (performSet):
+        (i.obj.__proto__.set p):
+        * stress/poly-proto-set-prototype.js: Added.
+        (assert):
+        (let.alternateProto.get x):
+        (let.alternateProto2.get y):
+        (let.alternateProto2.get x):
+        (foo.C):
+        (foo):
+        (validate):
+        * stress/poly-proto-setter.js: Added.
+        (assert):
+        (makePolyProtoObject.foo.C):
+        (makePolyProtoObject.foo.C.prototype.set p):
+        (makePolyProtoObject.foo.C.prototype.get p):
+        (makePolyProtoObject.foo):
+        (makePolyProtoObject):
+        (performSet):
+        * stress/poly-proto-using-inheritance.js: Added.
+        (assert):
+        (foo.C):
+        (foo.C.prototype.get baz):
+        (foo):
+        (bar.C):
+        (bar):
+        (validate):
+        * stress/primitive-poly-proto.js: Added.
+        (makePolyProtoInstance.foo.C):
+        (makePolyProtoInstance.foo):
+        (makePolyProtoInstance):
+        (assert):
+        (validate):
+        * stress/prototype-is-not-js-object.js: Added.
+        (foo.bar):
+        (foo):
+        (assert):
+        (validate):
+        * stress/try-get-by-id-poly-proto.js: Added.
+        (assert):
+        (makePolyProtoObject.foo.C):
+        (makePolyProtoObject.foo):
+        (makePolyProtoObject):
+        (tryGetByIdText):
+        (x.__proto__.get bar):
+        (validate):
+        * typeProfiler/overflow.js:
+
 2017-10-03  JF Bastien  <jfbastien@apple.com>
 
         WebAssembly: no VM / JS version of everything but Instance
diff --git a/JSTests/microbenchmarks/poly-proto-access.js b/JSTests/microbenchmarks/poly-proto-access.js
new file mode 100644 (file)
index 0000000..d1a16f1
--- /dev/null
@@ -0,0 +1,43 @@
+function assert(b) { if (!b) throw new Error("Bad"); }
+noInline(assert);
+
+function foo() {
+    class C {
+        constructor()
+        {
+            this.x = 20;
+        }
+
+        get bar()
+        {
+            assert(this.x === 20);
+            assert(this.foo === undefined || this.foo === 42);
+            return 45;
+        }
+    }
+    return new C;
+}
+
+foo();
+let a = [];
+for (let i = 0; i < 15; ++i)
+    a.push(foo());
+
+function bar(o) {
+    assert(o.foo === undefined || o.foo === 42);
+    assert(o.bar === 45);
+}
+noInline(bar);
+
+let start = Date.now();
+for (let i = 0; i < 1000000; ++i) {
+    if (i === 5000) {
+        for (let arr of a)
+            arr.__proto__.foo = 42;
+    }
+    for (let j = 0; j < a.length; ++j) {
+        bar(a[j]);
+    }
+}
+if (false)
+    print(Date.now() - start);
diff --git a/JSTests/microbenchmarks/poly-proto-put-transition-speed.js b/JSTests/microbenchmarks/poly-proto-put-transition-speed.js
new file mode 100644 (file)
index 0000000..3bb40f3
--- /dev/null
@@ -0,0 +1,43 @@
+function assert(b, m) {
+    if (!b)
+        throw new Error("Bad:" + m);
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() { this._field = 42; }
+        };
+        return new C;
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+let global;
+
+function performSet(o) {
+    o.p = 20;
+}
+
+let items = [];
+for (let i = 0; i < 25; ++i) {
+    let item = makePolyProtoObject();
+    item.__proto__ = null;
+    items.push(item);
+}
+
+let start = Date.now();
+for (let i = 0; i < 100000; ++i) {
+    for (let i = 0; i < items.length; ++i) {
+        let obj = items[i];
+        performSet(obj);
+        assert(Object.hasOwnProperty.call(obj, "p"));
+        assert(obj.p === 20);
+        assert(obj._field === 42);
+    }
+
+}
+if (false)
+    print(Date.now() - start);
diff --git a/JSTests/microbenchmarks/poly-proto-setter-speed.js b/JSTests/microbenchmarks/poly-proto-setter-speed.js
new file mode 100644 (file)
index 0000000..aa6ef2b
--- /dev/null
@@ -0,0 +1,52 @@
+function assert(b, m) {
+    if (!b)
+        throw new Error("Bad:" + m);
+}
+
+let called = false;
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() 
+            {
+                this._p = null;
+            }
+
+            set p(x)
+            {
+                called = true;
+                this._p = x;
+            }
+        };
+        return new C;
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+function performSet(o) {
+    o.p = 20;
+}
+
+let items = [];
+for (let i = 0; i < 8; ++i) {
+    items.push(makePolyProtoObject());
+}
+
+function performSet(x, i) {
+    x.p = i;
+}
+
+let start = Date.now();
+for (let i = 0; i < 10000; ++i) {
+    for (let i = 0; i < items.length; ++i) {
+        let o = items[i];
+        performSet(o, i);
+        assert(o._p === i);
+        assert(called === true);
+        called = false;
+    }
+}
+if (false)
+    print(Date.now() - start);
index 32a2892..d0a895b 100644 (file)
@@ -21,19 +21,21 @@ var tests = [
     new Test(Object(Symbol.iterator), true),
 ];
 
-tests.forEach(function (test) {
-    function Constructor() {
-        return test.value;
-    }
-
-    var result = new Constructor();
-    if (test.returnIt) {
-        if (test.value !== result) {
-            throw "Bad result: " + result;
+for (let i = 0; i < 1000; ++i) {
+    tests.forEach(function (test) {
+        function Constructor() {
+            return test.value;
         }
-    } else {
-        if (!(result instanceof Constructor)) {
-            throw "Bad result: " + result;
+
+        var result = new Constructor();
+        if (test.returnIt) {
+            if (test.value !== result) {
+                throw "Bad result: " + result;
+            }
+        } else {
+            if (!(result instanceof Constructor)) {
+                throw "Bad result: " + result;
+            }
         }
-    }
-});
+    });
+}
diff --git a/JSTests/stress/dom-jit-with-poly-proto.js b/JSTests/stress/dom-jit-with-poly-proto.js
new file mode 100644 (file)
index 0000000..339f80e
--- /dev/null
@@ -0,0 +1,39 @@
+function assert(b, m) {
+    if (!b)
+        throw new Error("Bad:" + m);
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() {
+                this._field = 25;
+            }
+        };
+        return new C;
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+let proto = createDOMJITGetterBaseJSObject();
+let obj = makePolyProtoObject();
+obj.__proto__ = proto;
+
+function validate(x, v) {
+    assert(x.customGetter === v, x.customGetter);
+}
+noInline(validate);
+
+for (let i = 0; i < 1000; ++i)
+    validate(obj, proto);
+
+proto.foo = 25;
+for (let i = 0; i < 1000; ++i)
+    validate(obj, proto);
+
+Reflect.setPrototypeOf(obj, {});
+for (let i = 0; i < 1000; ++i) {
+    validate(obj, undefined);
+}
diff --git a/JSTests/stress/poly-proto-custom-value-and-accessor.js b/JSTests/stress/poly-proto-custom-value-and-accessor.js
new file mode 100644 (file)
index 0000000..a56c838
--- /dev/null
@@ -0,0 +1,93 @@
+function assert(b, m) {
+    if (!b)
+        throw new Error("Bad:" + m);
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C { 
+            constructor() { this.field = 20; }
+        };
+        return new C;
+    }
+    for (let i = 0; i < 15; ++i) {
+        assert(foo().field === 20);
+    }
+    return foo();
+}
+
+let items = [
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+    makePolyProtoObject(),
+];
+
+let customGetterSetter = createCustomTestGetterSetter();
+items.forEach((x) => {
+    x.__proto__ = customGetterSetter;
+    assert(x.__proto__ === customGetterSetter);
+});
+
+function validate(x, valueResult, accessorResult) {
+    assert(x.customValue === valueResult);
+
+    assert(x.customAccessor === accessorResult);
+
+    let o = {};
+    x.customValue = o;
+    assert(o.result === valueResult);
+
+    o = {};
+    x.customAccessor = o;
+    assert(o.result === accessorResult);
+
+    assert(x.randomProp === 42 || x.randomProp === undefined);
+}
+noInline(validate);
+
+
+let start = Date.now();
+for (let i = 0; i < 10000; ++i) {
+    for (let i = 0; i < items.length; ++i) {
+        validate(items[i], customGetterSetter, items[i]);
+    }
+}
+
+customGetterSetter.randomProp = 42;
+
+for (let i = 0; i < 10000; ++i) {
+    for (let i = 0; i < items.length; ++i) {
+        validate(items[i], customGetterSetter, items[i]);
+    }
+}
+
+items.forEach((x) => {
+    Reflect.setPrototypeOf(x, {
+        get customValue() { return 42; },
+        get customAccessor() { return 22; },
+        set customValue(x) { x.result = 42; },
+        set customAccessor(x) { x.result = 22; },
+    });
+});
+
+for (let i = 0; i < 10000; ++i) {
+    for (let i = 0; i < items.length; ++i) {
+        validate(items[i], 42, 22);
+    }
+}
+
+if (false)
+    print(Date.now() - start);
diff --git a/JSTests/stress/poly-proto-intrinsic-getter-correctness.js b/JSTests/stress/poly-proto-intrinsic-getter-correctness.js
new file mode 100644 (file)
index 0000000..7cf4bb7
--- /dev/null
@@ -0,0 +1,37 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad!");
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() {
+                this._field = 42;
+            }
+        };
+        return new C;
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+let x = new Uint32Array(10);
+let p = x.__proto__.__proto__;
+let obj = makePolyProtoObject();
+obj.__proto__ = p;
+x.__proto__ = obj;
+
+function foo(x) {
+    return x.byteLength;
+}
+noInline(foo);
+
+for (let i = 0; i < 1000; ++i) {
+    assert(foo(x) === 10 * 4);
+};
+
+obj.__proto__ = {};
+
+assert(foo(x) === undefined);
diff --git a/JSTests/stress/poly-proto-miss.js b/JSTests/stress/poly-proto-miss.js
new file mode 100644 (file)
index 0000000..4210460
--- /dev/null
@@ -0,0 +1,51 @@
+function makePolyProtoInstanceWithNullPrototype() {
+    function foo() {
+        class C {
+            constructor() { this.x = 20; }
+        };
+        C.prototype.y = 42;
+        let result = new C;
+        return result;
+    }
+
+    for (let i = 0; i < 5; ++i)
+        foo();
+    let result = foo();
+    result.__proto__ = null;
+    return result;
+}
+
+function assert(b) {
+    if (!b)
+        throw new Error("Bad asssertion")
+}
+
+let instances = [
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+    makePolyProtoInstanceWithNullPrototype(),
+];
+
+let p = undefined;
+function validate(x) {
+    assert(x.x === 20);
+    assert(x.p === undefined);
+}
+noInline(validate);
+
+let start = Date.now();
+for (let i = 0; i < 100000; ++i) {
+    for (let i = 0; i < instances.length; ++i)
+        validate(instances[i]);
+}
+if (false)
+    print(Date.now() - start);
diff --git a/JSTests/stress/poly-proto-op-in-caching.js b/JSTests/stress/poly-proto-op-in-caching.js
new file mode 100644 (file)
index 0000000..5367019
--- /dev/null
@@ -0,0 +1,64 @@
+function assert(b, m) {
+    if (!b)
+        throw new Error("Bad:" + m);
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() {
+                this.field = 42;
+            }
+        };
+        return new C;
+    }
+    for (let i = 0; i < 15; ++i) {
+        assert(foo().field === 42);
+    }
+    return foo();
+}
+
+function validate(o, b) {
+    assert("x" in o === b);
+}
+noInline(validate);
+
+let start = Date.now();
+
+let objs = [];
+for (let i = 0; i < 10; ++i)
+    objs.push(makePolyProtoObject());
+
+objs.forEach(obj => Reflect.setPrototypeOf(obj, {x:20}));
+
+for (let i = 0; i < 10000; ++i) {
+    for (let obj of objs)
+        validate(obj, true);
+}
+
+objs.forEach(obj => Reflect.setPrototypeOf(obj, {}));
+for (let i = 0; i < 10000; ++i) {
+    for (let obj of objs)
+        validate(obj, false);
+}
+
+
+function validate2(o, b) {
+    assert("x" in o === b);
+}
+noInline(validate2);
+
+objs.forEach(obj => Reflect.setPrototypeOf(obj, null));
+for (let i = 0; i < 10000; ++i) {
+    for (let obj of objs)
+        validate2(obj, false);
+}
+
+objs.forEach(obj => Reflect.setPrototypeOf(obj, {x:25}));
+for (let i = 0; i < 10000; ++i) {
+    for (let obj of objs)
+        validate2(obj, true);
+}
+
+if (false)
+    print(Date.now() - start);
diff --git a/JSTests/stress/poly-proto-put-transition.js b/JSTests/stress/poly-proto-put-transition.js
new file mode 100644 (file)
index 0000000..345f3e5
--- /dev/null
@@ -0,0 +1,52 @@
+function assert(b, m) {
+    if (!b)
+        throw new Error("Bad:" + m);
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() {
+                this._field = 42;
+            }
+        };
+        return new C;
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+let global;
+
+function performSet(o) {
+    o.p = 20;
+}
+
+let start = Date.now();
+for (let i = 0; i < 1000; ++i) {
+    let obj = makePolyProtoObject();
+    obj.__proto__ = null;
+    performSet(obj);
+    assert(Object.hasOwnProperty.call(obj, "p"));
+    assert(obj.p === 20);
+
+}
+
+for (let i = 0; i < 1000; ++i) {
+    let obj = makePolyProtoObject();
+    obj.__proto__ = { set p(x) { global = x; } };
+    performSet(obj);
+    assert(!obj.hasOwnProperty("p"));
+    assert(global === 20);
+    global = null;
+}
+
+for (let i = 0; i < 1000; ++i) {
+    let obj = makePolyProtoObject();
+    performSet(obj);
+    assert(obj.hasOwnProperty("p"));
+    assert(obj.p === 20);
+}
+if (false)
+    print(Date.now() - start);
diff --git a/JSTests/stress/poly-proto-set-prototype.js b/JSTests/stress/poly-proto-set-prototype.js
new file mode 100644 (file)
index 0000000..6cac7e4
--- /dev/null
@@ -0,0 +1,65 @@
+function assert(b) {
+    if (!b)
+        throw new Error("bad");
+}
+
+let alternateProto = {
+    get x() {
+        return null;
+    }
+};
+
+let alternateProto2 = {
+    get y() { return 22; },
+    get x() {
+        return null;
+    }
+};
+
+Object.defineProperty(Object.prototype, "x", {
+    get: function() { return this._x; }
+});
+
+function foo() {
+    class C {
+        constructor() {
+            this._x = 42;
+        }
+    };
+    return new C;
+}
+
+function validate(o, p) {
+    assert(o.x === p);
+}
+noInline(validate);
+
+let arr = [];
+foo();
+for (let i = 0; i < 25; ++i)
+    arr.push(foo());
+
+for (let i = 0; i < 100; ++i) {
+    for (let a of arr)
+        validate(a, 42);
+}
+
+for (let a of arr) {
+    a.__proto__ = alternateProto;
+}
+for (let i = 0; i < 100; ++i) {
+    for (let a of arr) {
+        validate(a, null);
+    }
+}
+
+for (let a of arr) {
+    a.__proto__ = alternateProto2;
+}
+
+for (let i = 0; i < 100; ++i) {
+    for (let a of arr) {
+        validate(a, null);
+        assert(a.y === 22);
+    }
+}
diff --git a/JSTests/stress/poly-proto-setter.js b/JSTests/stress/poly-proto-setter.js
new file mode 100644 (file)
index 0000000..3faa06e
--- /dev/null
@@ -0,0 +1,71 @@
+function assert(b, m) {
+    if (!b)
+        throw new Error("Bad:" + m);
+}
+
+let called = false;
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() 
+            {
+                this._p = null;
+            }
+
+            set p(x)
+            {
+                called = true;
+                this._p = x;
+            }
+            get p()
+            {
+                return this._p;
+            }
+        };
+        return new C;
+    }
+    for (let i = 0; i < 15; ++i) {
+        assert(foo().p === null);
+    }
+    return foo();
+}
+
+function performSet(o) {
+    o.p = 20;
+}
+
+let items = [];
+for (let i = 0; i < 20; ++i) {
+    items.push(makePolyProtoObject());
+}
+
+function performSet(x, i) {
+    x.p = i;
+}
+
+let start = Date.now();
+for (let i = 0; i < 100000; ++i) {
+    for (let i = 0; i < items.length; ++i) {
+        let o = items[i];
+        performSet(o, i);
+        assert(o._p === i);
+        assert(called === true);
+        called = false;
+    }
+}
+
+items.forEach(o => {
+    Reflect.setPrototypeOf(o, null);
+});
+
+for (let i = 0; i < 100000; ++i) {
+    for (let i = 0; i < items.length; ++i) {
+        let o = items[i];
+        performSet(o, i);
+        assert(o.p === i);
+        assert(called === false);
+    }
+}
+
+if (false)
+    print(Date.now() - start);
diff --git a/JSTests/stress/poly-proto-using-inheritance.js b/JSTests/stress/poly-proto-using-inheritance.js
new file mode 100644 (file)
index 0000000..30b1a1a
--- /dev/null
@@ -0,0 +1,51 @@
+function assert(b) {
+    if (!b)
+        throw new Error("Bad");
+}
+
+function foo() {
+    class C {
+        constructor()
+        {
+            this.y = 22;
+        }
+        get baz() { return this.x; }
+    }
+    C.prototype.field = 42;
+    new C;
+    return C;
+}
+
+for (let i = 0; i < 5; ++i)
+    foo();
+
+function bar(p) {
+    class C extends p {
+        constructor() {
+            super();
+            this.x = 22;
+        }
+    };
+    let result = new C;
+    return result;
+}
+
+for (let i = 0; i < 5; ++i)
+    bar(foo());
+
+let instances = [];
+for (let i = 0; i < 40; ++i)
+    instances.push(bar(foo()));
+
+function validate(item) {
+    assert(item.x === 22);
+    assert(item.baz === 22);
+    assert(item.field === 42);
+}
+
+let start = Date.now();
+for (let i = 0; i < 100000; ++i) {
+    instances.forEach((x) => validate(x));
+}
+if (false)
+    print(Date.now() - start);
diff --git a/JSTests/stress/primitive-poly-proto.js b/JSTests/stress/primitive-poly-proto.js
new file mode 100644 (file)
index 0000000..db3917d
--- /dev/null
@@ -0,0 +1,53 @@
+let y = 42;
+function makePolyProtoInstance() {
+    function foo() {
+        class C {
+            constructor() { this.x = 20; }
+        };
+        C.prototype.y = y;
+        return new C;
+    }
+
+    for (let i = 0; i < 5; ++i)
+        foo();
+    return foo();
+}
+
+let polyProtoInstance = makePolyProtoInstance();
+String.prototype.__proto__ = polyProtoInstance;
+Symbol.prototype.__proto__ = polyProtoInstance;
+let strings = [
+    "foo",
+    Symbol("foo"),
+    "bar",
+    Symbol("bar"),
+];
+
+function assert(b) {
+    if (!b)
+        throw new Error("Bad asssertion")
+}
+noInline(assert);
+
+function validate(s) {
+    assert(s.x === 20);
+    assert(s.y === y);
+    assert(s.nonExistentProperty === undefined);
+    assert(typeof s.hasOwnProperty === "function");
+    assert(s.hasOwnProperty === Object.prototype.hasOwnProperty);
+}
+noInline(validate);
+
+for (let i = 0; i < 1000; ++i) {
+    for (let s of strings) {
+        validate(s);
+    }
+}
+
+y = 27;
+polyProtoInstance.__proto__ = {z:400, y: y};
+for (let i = 0; i < 1000; ++i) {
+    for (let s of strings) {
+        validate(s);
+    }
+}
diff --git a/JSTests/stress/prototype-is-not-js-object.js b/JSTests/stress/prototype-is-not-js-object.js
new file mode 100644 (file)
index 0000000..6ae2ca1
--- /dev/null
@@ -0,0 +1,32 @@
+function foo() {
+    function bar() {
+        this.x = 42;
+    }
+    bar.prototype = 50;
+    return new bar();
+}
+
+function assert(b) {
+    if (!b)
+        throw new Error("Bad");
+}
+
+let items = [
+    foo(),
+    foo(),
+    foo(),
+    foo(),
+    foo(),
+    foo(),
+];
+
+function validate(item) {
+    assert(item.notThere === undefined);
+    assert(item.x === 42);
+    assert(item.__proto__ === Object.prototype);
+}
+
+for (let i = 0; i < 10000; ++i) {
+    for (let item of items)
+        validate(item);
+}
diff --git a/JSTests/stress/try-get-by-id-poly-proto.js b/JSTests/stress/try-get-by-id-poly-proto.js
new file mode 100644 (file)
index 0000000..3a2e4c2
--- /dev/null
@@ -0,0 +1,38 @@
+function assert(b, m) {
+    if (!b)
+        throw new Error("Bad:" + m);
+}
+
+function makePolyProtoObject() {
+    function foo() {
+        class C {
+            constructor() { this._field = 42; }
+        };
+        return new C;
+    }
+    for (let i = 0; i < 15; ++i)
+        foo();
+    return foo();
+}
+
+function tryGetByIdText(propertyName) { return `(function (base) { return @tryGetById(base, '${propertyName}'); })`; }
+let getFoo = createBuiltin(tryGetByIdText("foo"));
+let getBar = createBuiltin(tryGetByIdText("bar"));
+let getNonExistentField = createBuiltin(tryGetByIdText("nonExistentField"));
+
+let x = makePolyProtoObject();
+x.__proto__ = { foo: 42, get bar() { return 22; } };
+let barGetter = Object.getOwnPropertyDescriptor(x.__proto__, "bar").get;
+assert(typeof barGetter === "function");
+assert(barGetter() === 22);
+
+function validate(x) {
+    assert(getFoo(x) === 42);
+    assert(loadGetterFromGetterSetter(getBar(x)) === barGetter);
+    assert(getNonExistentField(x) === undefined);
+}
+noInline(validate);
+
+for (let i = 0; i < 1000; ++i) {
+    validate(x);
+}
index 9152d6a..1c93980 100644 (file)
@@ -9,6 +9,8 @@ var oldProto;
 for (var i = 0; i < MaxStructureCountWithoutOverflow; i++) {
     // Make sure we get a new prototype chain on each assignment to x because objects with shared prototype chains will be merged.
     x = new Proto;
+    x['field' + i] = 20;
+    x = x
     oldProto = Proto;
     Proto = function() {};
     Proto.prototype.__proto__ = oldProto.prototype;
@@ -20,6 +22,8 @@ Proto = function() {};
 oldProto = null;
 for (var i = 0; i < MaxStructureCountWithoutOverflow - 1; i++) {
     y = new Proto;
+    y['field' + i] = 20;
+    y = y
     oldProto = Proto;
     Proto = function() {};
     Proto.prototype.__proto__ = oldProto.prototype;
index 549bca8..57a3b29 100644 (file)
@@ -1,3 +1,329 @@
+2017-10-03  Saam Barati  <sbarati@apple.com>
+
+        Implement polymorphic prototypes
+        https://bugs.webkit.org/show_bug.cgi?id=176391
+
+        Reviewed by Filip Pizlo.
+
+        This patch changes JSC's object model with respect to where the prototype
+        of an object is stored. Previously, it was always stored as
+        a constant value inside Structure. So an object's structure used to
+        always tell you what its prototype is. Anytime an object changed
+        its prototype, it would do a structure transition. This enables
+        a large class of optimizations: just by doing a structure check,
+        we know what the prototype is.
+        
+        However, this design falls down when you have many objects that
+        have the same shape, but only differ in what their prototype value
+        is. This arises in many JS programs. A simple, and probably common, example
+        is when the program has a constructor inside of a function:
+        ```
+        function foo() {
+            class C {
+                constructor() { this.field1 = 42; ...; this.fieldN = 42; }
+                method1() { doStuffWith(this.field); }
+                method2() { doStuffWith(this.field); }
+            }
+            let c = new C;
+            do things with c;
+            }
+        repeatedly call foo() here.
+        ```
+        
+        Before this patch, in the above program, each time `new C` created an
+        object, it would create an object with a different structure. The
+        reason for this is that each time foo is called, there is a new
+        instance of C.prototype. However, each `new C` that was created
+        with have identical shape sans its prototype value. This would
+        cause all ICs that used `c` to quickly give up on any form of caching
+        because they would see too many structures and give up and permanently
+        divert control flow to the slow path.
+        
+        This patch fixes this issue by expanding the notion of where the prototype
+        of an object is stored. There are now two notions of where the prototype
+        is stored. A Structure can now be in two modes:
+        1. Mono proto mode. This is the same mode as we used to have. It means
+        the structure itself has a constant prototype value.
+        2. Poly proto mode. This means the structure knows nothing about the
+        prototype value itself. Objects with this structure store their prototype
+        in normal object field storage. The structure will tell you the offset of
+        this prototype inside the object's storage. As of today, we only reserve
+        inline slots for the prototype field because poly proto only occurs
+        for JSFinalObject. However, this will be expanded to support out of line
+        offsets in a future patch when we extend poly proto to work when we inherit
+        from builtin types like Map and Array.
+        
+        In this initial patch, we do poly proto style inline caching whenever
+        we see an object that is poly proto or if an object in its prototype lookup
+        chain is poly proto. Poly proto ICs work by verifying the lookup chain
+        at runtime. This essentially boils down to performing structure checks
+        up the prototype chain. In a future patch, we're going to extend object
+        property condition set to work with objects that don't have poly proto bases.
+        
+        Initially, accesses that have poly proto access chains will always turn
+        into GetById/PutById in the DFG. In a future patch, I'm going to teach
+        the DFG how to inline certain accesses that have poly proto in the access
+        chain.
+        
+        One of most interesting parts about this patch is how we decide when to go
+        poly proto. This patch uses a profiling based approach. An IC will inform
+        a watchpoint that it sees an opportunity when two Structure's are structurally
+        the same, sans the base object's prototype. This means that two structures
+        have equivalent shapes all the way up the prototype chain. To support fast
+        structural comparison, we compute a hash for a structure based on the properties
+        it has. We compute this hash as we add properties to the structure. This
+        computation is nearly free since we always add UniquedStringImpl*'s which
+        already have their hashes computed. To compare structural equivalence, we
+        just compare hash values all the way up the prototype chain. This means we
+        can get hash conflicts between two structures, but it's extremely rare. First,
+        it'll be rare for two structures to have the same hash. Secondly, we only
+        consider structures originating from the same executable.
+        
+        How we set up this poly proto watchpoint is crucial to its design. When we create_this
+        an object originating from some executable, that executable will create a Box<InlineWatchpointSet>.
+        Each structure that originates from this executable will get a copy of that
+        Box<InlineWatchpointSet>. As that structure transitions to new structures,
+        they too will get a copy of that Box<InilneWatchpointSet>. Therefore, when
+        invalidating an arbitrary structure's poly proto watchpoint, we will know
+        the next time we create_this from that executable that it had been
+        invalidated, and that we should create an object with a poly proto
+        structure. We also use the pointer value of this Box<InlineWatchpointSet>
+        to determine if two structures originated from the same executable. This
+        pruning will severely limit the chances of getting a hash conflict in practice.
+        
+        This patch is neutral on my MBP on traditional JS benchmarks like Octane/Kraken/Sunspider.
+        It may be a 1-2% ARES-6 progression.
+        
+        This patch is between neutral and a 9x progression on the various tests
+        I added. Most of the microbenchmarks are progressed by at least 50%.
+
+        * JavaScriptCore.xcodeproj/project.pbxproj:
+        * Sources.txt:
+        * builtins/BuiltinNames.cpp:
+        * builtins/BuiltinNames.h:
+        (JSC::BuiltinNames::BuiltinNames):
+        (JSC::BuiltinNames::underscoreProtoPrivateName const):
+        * bytecode/AccessCase.cpp:
+        (JSC::AccessCase::AccessCase):
+        (JSC::AccessCase::create):
+        (JSC::AccessCase::commit):
+        (JSC::AccessCase::guardedByStructureCheck const):
+        (JSC::AccessCase::canReplace const):
+        (JSC::AccessCase::dump const):
+        (JSC::AccessCase::visitWeak const):
+        (JSC::AccessCase::propagateTransitions const):
+        (JSC::AccessCase::generateWithGuard):
+        (JSC::AccessCase::generateImpl):
+        * bytecode/AccessCase.h:
+        (JSC::AccessCase::usesPolyProto const):
+        (JSC::AccessCase::AccessCase):
+        * bytecode/CodeBlock.cpp:
+        (JSC::CodeBlock::finishCreation):
+        * bytecode/GetByIdStatus.cpp:
+        (JSC::GetByIdStatus::computeForStubInfoWithoutExitSiteFeedback):
+        * bytecode/GetterSetterAccessCase.cpp:
+        (JSC::GetterSetterAccessCase::GetterSetterAccessCase):
+        (JSC::GetterSetterAccessCase::create):
+        * bytecode/GetterSetterAccessCase.h:
+        * bytecode/InternalFunctionAllocationProfile.h:
+        (JSC::InternalFunctionAllocationProfile::createAllocationStructureFromBase):
+        * bytecode/IntrinsicGetterAccessCase.cpp:
+        (JSC::IntrinsicGetterAccessCase::IntrinsicGetterAccessCase):
+        * bytecode/IntrinsicGetterAccessCase.h:
+        * bytecode/ModuleNamespaceAccessCase.cpp:
+        (JSC::ModuleNamespaceAccessCase::ModuleNamespaceAccessCase):
+        * bytecode/ObjectAllocationProfile.cpp: Added.
+        (JSC::ObjectAllocationProfile::initializeProfile):
+        (JSC::ObjectAllocationProfile::possibleDefaultPropertyCount):
+        * bytecode/ObjectAllocationProfile.h:
+        (JSC::ObjectAllocationProfile::clear):
+        (JSC::ObjectAllocationProfile::initialize): Deleted.
+        (JSC::ObjectAllocationProfile::possibleDefaultPropertyCount): Deleted.
+        * bytecode/ObjectPropertyConditionSet.cpp:
+        * bytecode/PolyProtoAccessChain.cpp: Added.
+        (JSC::PolyProtoAccessChain::create):
+        (JSC::PolyProtoAccessChain::needImpurePropertyWatchpoint const):
+        (JSC::PolyProtoAccessChain::operator== const):
+        (JSC::PolyProtoAccessChain::dump const):
+        * bytecode/PolyProtoAccessChain.h: Added.
+        (JSC::PolyProtoAccessChain::clone):
+        (JSC::PolyProtoAccessChain:: const):
+        (JSC::PolyProtoAccessChain::operator!= const):
+        (JSC::PolyProtoAccessChain::forEach const):
+        * bytecode/PolymorphicAccess.cpp:
+        (JSC::PolymorphicAccess::addCases):
+        (JSC::PolymorphicAccess::regenerate):
+        (WTF::printInternal):
+        * bytecode/PolymorphicAccess.h:
+        (JSC::AccessGenerationResult::shouldResetStub const):
+        (JSC::AccessGenerationState::AccessGenerationState):
+        * bytecode/PropertyCondition.cpp:
+        (JSC::PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint const):
+        * bytecode/ProxyableAccessCase.cpp:
+        (JSC::ProxyableAccessCase::ProxyableAccessCase):
+        (JSC::ProxyableAccessCase::create):
+        * bytecode/ProxyableAccessCase.h:
+        * bytecode/PutByIdStatus.cpp:
+        (JSC::PutByIdStatus::computeForStubInfo):
+        * bytecode/StructureStubInfo.cpp:
+        (JSC::StructureStubInfo::addAccessCase):
+        * dfg/DFGByteCodeParser.cpp:
+        (JSC::DFG::ByteCodeParser::load):
+        (JSC::DFG::ByteCodeParser::parseBlock):
+        * dfg/DFGGraph.cpp:
+        (JSC::DFG::Graph::canDoFastSpread):
+        * dfg/DFGOperations.cpp:
+        * dfg/DFGSpeculativeJIT.cpp:
+        (JSC::DFG::SpeculativeJIT::compileInstanceOfForObject):
+        (JSC::DFG::SpeculativeJIT::compileInstanceOf):
+        * dfg/DFGSpeculativeJIT.h:
+        * dfg/DFGSpeculativeJIT64.cpp:
+        (JSC::DFG::SpeculativeJIT::compile):
+        * ftl/FTLLowerDFGToB3.cpp:
+        (JSC::FTL::DFG::LowerDFGToB3::compileInstanceOf):
+        * jit/JITOpcodes.cpp:
+        (JSC::JIT::emit_op_instanceof):
+        * jit/JITOpcodes32_64.cpp:
+        (JSC::JIT::emit_op_instanceof):
+        * jit/Repatch.cpp:
+        (JSC::tryCacheGetByID):
+        (JSC::tryCachePutByID):
+        (JSC::tryRepatchIn):
+        * jsc.cpp:
+        (WTF::DOMJITGetterBaseJSObject::DOMJITGetterBaseJSObject):
+        (WTF::DOMJITGetterBaseJSObject::createStructure):
+        (WTF::DOMJITGetterBaseJSObject::create):
+        (WTF::DOMJITGetterBaseJSObject::DOMJITAttribute::DOMJITAttribute):
+        (WTF::DOMJITGetterBaseJSObject::DOMJITAttribute::slowCall):
+        (WTF::DOMJITGetterBaseJSObject::DOMJITAttribute::callDOMGetter):
+        (WTF::DOMJITGetterBaseJSObject::customGetter):
+        (WTF::DOMJITGetterBaseJSObject::finishCreation):
+        (GlobalObject::finishCreation):
+        (functionCreateDOMJITGetterBaseJSObject):
+        * llint/LLIntSlowPaths.cpp:
+        (JSC::LLInt::LLINT_SLOW_PATH_DECL):
+        * runtime/ArrayPrototype.cpp:
+        (JSC::holesMustForwardToPrototype):
+        (JSC::fastJoin):
+        (JSC::arrayProtoFuncReverse):
+        (JSC::moveElements):
+        * runtime/ClonedArguments.cpp:
+        (JSC::ClonedArguments::createEmpty):
+        (JSC::ClonedArguments::createWithInlineFrame):
+        (JSC::ClonedArguments::createWithMachineFrame):
+        (JSC::ClonedArguments::createByCopyingFrom):
+        * runtime/CommonSlowPaths.cpp:
+        (JSC::SLOW_PATH_DECL):
+        * runtime/FunctionExecutable.cpp:
+        (JSC::FunctionExecutable::visitChildren):
+        * runtime/FunctionExecutable.h:
+        * runtime/FunctionRareData.cpp:
+        (JSC::FunctionRareData::initializeObjectAllocationProfile):
+        * runtime/FunctionRareData.h:
+        * runtime/InternalFunction.cpp:
+        (JSC::InternalFunction::createSubclassStructureSlow):
+        * runtime/JSArray.cpp:
+        (JSC::JSArray::fastSlice):
+        (JSC::JSArray::shiftCountWithArrayStorage):
+        (JSC::JSArray::shiftCountWithAnyIndexingType):
+        (JSC::JSArray::isIteratorProtocolFastAndNonObservable):
+        * runtime/JSArrayInlines.h:
+        (JSC::JSArray::canFastCopy):
+        * runtime/JSCJSValue.cpp:
+        (JSC::JSValue::dumpInContextAssumingStructure const):
+        * runtime/JSFunction.cpp:
+        (JSC::JSFunction::prototypeForConstruction):
+        (JSC::JSFunction::allocateAndInitializeRareData):
+        (JSC::JSFunction::initializeRareData):
+        (JSC::JSFunction::getOwnPropertySlot):
+        * runtime/JSFunction.h:
+        * runtime/JSMap.cpp:
+        (JSC::JSMap::isIteratorProtocolFastAndNonObservable):
+        (JSC::JSMap::canCloneFastAndNonObservable):
+        * runtime/JSObject.cpp:
+        (JSC::JSObject::putInlineSlow):
+        (JSC::JSObject::createInitialIndexedStorage):
+        (JSC::JSObject::createArrayStorage):
+        (JSC::JSObject::convertUndecidedToArrayStorage):
+        (JSC::JSObject::convertInt32ToArrayStorage):
+        (JSC::JSObject::convertDoubleToArrayStorage):
+        (JSC::JSObject::convertContiguousToArrayStorage):
+        (JSC::JSObject::ensureInt32Slow):
+        (JSC::JSObject::ensureDoubleSlow):
+        (JSC::JSObject::ensureContiguousSlow):
+        (JSC::JSObject::ensureArrayStorageSlow):
+        (JSC::JSObject::setPrototypeDirect):
+        (JSC::JSObject::ordinaryToPrimitive const):
+        (JSC::JSObject::putByIndexBeyondVectorLength):
+        (JSC::JSObject::putDirectIndexSlowOrBeyondVectorLength):
+        (JSC::JSObject::getEnumerableLength):
+        (JSC::JSObject::anyObjectInChainMayInterceptIndexedAccesses const):
+        (JSC::JSObject::prototypeChainMayInterceptStoreTo):
+        (JSC::JSObject::needsSlowPutIndexing const):
+        (JSC::JSObject::suggestedArrayStorageTransition const):
+        * runtime/JSObject.h:
+        (JSC::JSObject::finishCreation):
+        (JSC::JSObject::getPrototypeDirect const):
+        (JSC::JSObject::getPropertySlot):
+        * runtime/JSObjectInlines.h:
+        (JSC::JSObject::getPropertySlot):
+        (JSC::JSObject::getNonIndexPropertySlot):
+        (JSC::JSObject::putInlineForJSObject):
+        * runtime/JSPropertyNameEnumerator.h:
+        (JSC::propertyNameEnumerator):
+        * runtime/JSSet.cpp:
+        (JSC::JSSet::isIteratorProtocolFastAndNonObservable):
+        (JSC::JSSet::canCloneFastAndNonObservable):
+        * runtime/LazyClassStructure.h:
+        (JSC::LazyClassStructure::prototypeConcurrently const): Deleted.
+        * runtime/Operations.cpp:
+        (JSC::normalizePrototypeChain):
+        * runtime/Operations.h:
+        * runtime/Options.h:
+        * runtime/PrototypeMap.cpp:
+        (JSC::PrototypeMap::createEmptyStructure):
+        (JSC::PrototypeMap::emptyStructureForPrototypeFromBaseStructure):
+        (JSC::PrototypeMap::emptyObjectStructureForPrototype):
+        (JSC::PrototypeMap::clearEmptyObjectStructureForPrototype):
+        * runtime/PrototypeMap.h:
+        * runtime/Structure.cpp:
+        (JSC::Structure::Structure):
+        (JSC::Structure::create):
+        (JSC::Structure::holesMustForwardToPrototype const):
+        (JSC::Structure::changePrototypeTransition):
+        (JSC::Structure::isCheapDuringGC):
+        (JSC::Structure::toStructureShape):
+        (JSC::Structure::dump const):
+        (JSC::Structure::canCachePropertyNameEnumerator const):
+        (JSC::Structure::anyObjectInChainMayInterceptIndexedAccesses const): Deleted.
+        (JSC::Structure::needsSlowPutIndexing const): Deleted.
+        (JSC::Structure::suggestedArrayStorageTransition const): Deleted.
+        (JSC::Structure::prototypeForLookup const): Deleted.
+        (JSC::Structure::prototypeChainMayInterceptStoreTo): Deleted.
+        (JSC::Structure::canUseForAllocationsOf): Deleted.
+        * runtime/Structure.h:
+        * runtime/StructureChain.h:
+        * runtime/StructureInlines.h:
+        (JSC::Structure::create):
+        (JSC::Structure::storedPrototypeObject const):
+        (JSC::Structure::storedPrototypeStructure const):
+        (JSC::Structure::storedPrototype const):
+        (JSC::prototypeForLookupPrimitiveImpl):
+        (JSC::Structure::prototypeForLookup const):
+        (JSC::Structure::prototypeChain const):
+        (JSC::Structure::isValid const):
+        (JSC::Structure::add):
+        (JSC::Structure::setPropertyTable):
+        (JSC::Structure::shouldConvertToPolyProto):
+        * runtime/StructureRareData.h:
+        * runtime/TypeProfilerLog.cpp:
+        (JSC::TypeProfilerLog::processLogEntries):
+        * runtime/TypeSet.cpp:
+        (JSC::TypeSet::addTypeInformation):
+        * runtime/TypeSet.h:
+        * runtime/WriteBarrier.h:
+        (JSC::WriteBarrierBase<Unknown>::isInt32 const):
+
 2017-10-03  JF Bastien  <jfbastien@apple.com>
 
         WebAssembly: no VM / JS version of everything but Instance
index b9052da..6994f8e 100644 (file)
                4443AE3316E188D90076F110 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51F0EB6105C86C6B00E6DF1B /* Foundation.framework */; };
                451539B912DC994500EF7AC4 /* Yarr.h in Headers */ = {isa = PBXBuildFile; fileRef = 451539B812DC994500EF7AC4 /* Yarr.h */; settings = {ATTRIBUTES = (Private, ); }; };
                473DA4A4764C45FE871B0485 /* DefinePropertyAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = 169948EDE68D4054B01EF797 /* DefinePropertyAttributes.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               521131F71F82BF14007CCEEE /* PolyProtoAccessChain.h in Headers */ = {isa = PBXBuildFile; fileRef = 521131F61F82BF11007CCEEE /* PolyProtoAccessChain.h */; settings = {ATTRIBUTES = (Private, ); }; };
                521322461ECBCE8200F65615 /* WebAssemblyFunctionBase.h in Headers */ = {isa = PBXBuildFile; fileRef = 521322441ECBCE8200F65615 /* WebAssemblyFunctionBase.h */; };
                5250D2D21E8DA05A0029A932 /* WasmThunks.h in Headers */ = {isa = PBXBuildFile; fileRef = 5250D2D01E8DA05A0029A932 /* WasmThunks.h */; settings = {ATTRIBUTES = (Private, ); }; };
                525C0DDA1E935847002184CD /* WasmCallee.h in Headers */ = {isa = PBXBuildFile; fileRef = 525C0DD81E935847002184CD /* WasmCallee.h */; settings = {ATTRIBUTES = (Private, ); }; };
                4CE978E385A8498199052153 /* ModuleNamespaceAccessCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ModuleNamespaceAccessCase.h; sourceTree = "<group>"; };
                51F0EB6105C86C6B00E6DF1B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
                51F0EC0705C86C9A00E6DF1B /* libobjc.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libobjc.dylib; path = /usr/lib/libobjc.dylib; sourceTree = "<absolute>"; };
+               521131F51F82BF11007CCEEE /* PolyProtoAccessChain.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PolyProtoAccessChain.cpp; sourceTree = "<group>"; };
+               521131F61F82BF11007CCEEE /* PolyProtoAccessChain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PolyProtoAccessChain.h; sourceTree = "<group>"; };
                521322431ECBCE8200F65615 /* WebAssemblyFunctionBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebAssemblyFunctionBase.cpp; path = js/WebAssemblyFunctionBase.cpp; sourceTree = "<group>"; };
                521322441ECBCE8200F65615 /* WebAssemblyFunctionBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebAssemblyFunctionBase.h; path = js/WebAssemblyFunctionBase.h; sourceTree = "<group>"; };
                5250D2CF1E8DA05A0029A932 /* WasmThunks.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WasmThunks.cpp; sourceTree = "<group>"; };
                                969A07950ED1D3AE00F1F681 /* Opcode.h */,
                                0F2BDC2B151FDE8B00CD8910 /* Operands.h */,
                                A70447E917A0BD4600F5898E /* OperandsInlines.h */,
+                               521131F51F82BF11007CCEEE /* PolyProtoAccessChain.cpp */,
+                               521131F61F82BF11007CCEEE /* PolyProtoAccessChain.h */,
                                0FF9CE711B9CD6D0004EDCA6 /* PolymorphicAccess.cpp */,
                                0FF9CE721B9CD6D0004EDCA6 /* PolymorphicAccess.h */,
                                0F98205D16BFE37F00240D02 /* PreciseJumpTargets.cpp */,
                                A7986D5717A0BB1E00A95DD0 /* DFGEdgeUsesStructure.h in Headers */,
                                0F8F14341ADF090100ED792C /* DFGEpoch.h in Headers */,
                                0FBC0AE81496C7C700D4FBDD /* DFGExitProfile.h in Headers */,
+                               521131F71F82BF14007CCEEE /* PolyProtoAccessChain.h in Headers */,
                                A78A9775179738B8009DF744 /* DFGFailedFinalizer.h in Headers */,
                                A7BFF3C0179868940002F462 /* DFGFiltrationResult.h in Headers */,
                                A78A9777179738B8009DF744 /* DFGFinalizer.h in Headers */,
index b4a4b46..4e688cd 100644 (file)
@@ -227,9 +227,11 @@ bytecode/LazyOperandValueProfile.cpp
 bytecode/MethodOfGettingAValueProfile.cpp
 bytecode/ModuleNamespaceAccessCase.cpp
 bytecode/ModuleProgramCodeBlock.cpp
+bytecode/ObjectAllocationProfile.cpp
 bytecode/ObjectPropertyCondition.cpp
 bytecode/ObjectPropertyConditionSet.cpp
 bytecode/Opcode.cpp
+bytecode/PolyProtoAccessChain.cpp
 bytecode/PolymorphicAccess.cpp
 bytecode/PreciseJumpTargets.cpp
 bytecode/ProgramCodeBlock.cpp
index 482bf02..065d845 100644 (file)
@@ -44,6 +44,7 @@ JSC_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(INITIALIZE_BUILTIN_PRIVATE_NAM
 #undef INITIALIZE_BUILTIN_PRIVATE_NAMES
 
 SymbolImpl::StaticSymbolImpl dollarVMPrivateName { "PrivateSymbol.$vm", SymbolImpl::s_flagIsPrivate };
+SymbolImpl::StaticSymbolImpl underscoreProtoPrivateName { "PrivateSymbol.__proto__", SymbolImpl::s_flagIsPrivate };
 
 } // namespace Symbols
 } // namespace JSC
index a18513b..00399c8 100644 (file)
@@ -206,6 +206,7 @@ JSC_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(DECLARE_BUILTIN_PRIVATE_NAMES)
 #undef DECLARE_BUILTIN_PRIVATE_NAMES
 
 extern SymbolImpl::StaticSymbolImpl dollarVMPrivateName;
+extern SymbolImpl::StaticSymbolImpl underscoreProtoPrivateName;
 }
 
 #define INITIALIZE_PRIVATE_TO_PUBLIC_ENTRY(name) m_privateToPublicMap.add(m_##name##PrivateName.impl(), &m_##name);
@@ -230,6 +231,7 @@ public:
         JSC_COMMON_PRIVATE_IDENTIFIERS_EACH_WELL_KNOWN_SYMBOL(INITIALIZE_BUILTIN_SYMBOLS)
         , m_dollarVMName(Identifier::fromString(vm, "$vm"))
         , m_dollarVMPrivateName(Identifier::fromUid(vm, &static_cast<SymbolImpl&>(Symbols::dollarVMPrivateName)))
+        , m_underscoreProtoPrivateName(Identifier::fromUid(vm, &static_cast<SymbolImpl&>(Symbols::underscoreProtoPrivateName)))
     {
         JSC_FOREACH_BUILTIN_FUNCTION_NAME(INITIALIZE_PRIVATE_TO_PUBLIC_ENTRY)
         JSC_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(INITIALIZE_PRIVATE_TO_PUBLIC_ENTRY)
@@ -238,6 +240,8 @@ public:
         JSC_COMMON_PRIVATE_IDENTIFIERS_EACH_WELL_KNOWN_SYMBOL(INITIALIZE_SYMBOL_PUBLIC_TO_PRIVATE_ENTRY)
         m_privateToPublicMap.add(m_dollarVMPrivateName.impl(), &m_dollarVMName);
         m_publicToPrivateMap.add(m_dollarVMName.impl(), &m_dollarVMPrivateName);
+        m_privateToPublicMap.add(m_underscoreProtoPrivateName.impl(), &commonIdentifiers->underscoreProto);
+        m_publicToPrivateMap.add(commonIdentifiers->underscoreProto.impl(), &m_underscoreProtoPrivateName);
     }
 
     const Identifier* lookUpPrivateName(const Identifier&) const;
@@ -250,6 +254,7 @@ public:
     JSC_COMMON_PRIVATE_IDENTIFIERS_EACH_WELL_KNOWN_SYMBOL(DECLARE_BUILTIN_SYMBOL_ACCESSOR)
     const JSC::Identifier& dollarVMPublicName() const { return m_dollarVMName; }
     const JSC::Identifier& dollarVMPrivateName() const { return m_dollarVMPrivateName; }
+    const JSC::Identifier& underscoreProtoPrivateName() const { return m_underscoreProtoPrivateName; }
 
 private:
     Identifier m_emptyIdentifier;
@@ -258,6 +263,7 @@ private:
     JSC_COMMON_PRIVATE_IDENTIFIERS_EACH_WELL_KNOWN_SYMBOL(DECLARE_BUILTIN_SYMBOLS)
     const JSC::Identifier m_dollarVMName;
     const JSC::Identifier m_dollarVMPrivateName;
+    const JSC::Identifier m_underscoreProtoPrivateName;
     typedef HashMap<RefPtr<UniquedStringImpl>, const Identifier*, IdentifierRepHash> BuiltinNamesMap;
     BuiltinNamesMap m_publicToPrivateMap;
     BuiltinNamesMap m_privateToPublicMap;
index 4198655..6cb1f1f 100644 (file)
@@ -55,36 +55,39 @@ namespace AccessCaseInternal {
 static const bool verbose = false;
 }
 
-AccessCase::AccessCase(VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet)
+AccessCase::AccessCase(VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain)
     : m_type(type)
     , m_offset(offset)
+    , m_polyProtoAccessChain(WTFMove(prototypeAccessChain))
 {
     m_structure.setMayBeNull(vm, owner, structure);
     m_conditionSet = conditionSet;
 }
 
-std::unique_ptr<AccessCase> AccessCase::create(VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet)
+std::unique_ptr<AccessCase> AccessCase::create(VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain)
 {
     switch (type) {
     case InHit:
     case InMiss:
+        break;
     case ArrayLength:
     case StringLength:
     case DirectArgumentsLength:
     case ScopedArgumentsLength:
     case ModuleNamespaceLoad:
     case Replace:
+        RELEASE_ASSERT(!prototypeAccessChain);
         break;
     default:
-        ASSERT_NOT_REACHED();
+        RELEASE_ASSERT_NOT_REACHED();
     };
 
-    return std::unique_ptr<AccessCase>(new AccessCase(vm, owner, type, offset, structure, conditionSet));
+    return std::unique_ptr<AccessCase>(new AccessCase(vm, owner, type, offset, structure, conditionSet, WTFMove(prototypeAccessChain)));
 }
 
 std::unique_ptr<AccessCase> AccessCase::create(
     VM& vm, JSCell* owner, PropertyOffset offset, Structure* oldStructure, Structure* newStructure,
-    const ObjectPropertyConditionSet& conditionSet)
+    const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain)
 {
     RELEASE_ASSERT(oldStructure == newStructure->previousID());
 
@@ -96,7 +99,7 @@ std::unique_ptr<AccessCase> AccessCase::create(
         return nullptr;
     }
 
-    return std::unique_ptr<AccessCase>(new AccessCase(vm, owner, Transition, offset, newStructure, conditionSet));
+    return std::unique_ptr<AccessCase>(new AccessCase(vm, owner, Transition, offset, newStructure, conditionSet, WTFMove(prototypeAccessChain)));
 }
 
 AccessCase::~AccessCase()
@@ -135,7 +138,8 @@ Vector<WatchpointSet*, 2> AccessCase::commit(VM& vm, const Identifier& ident)
     Vector<WatchpointSet*, 2> result;
 
     if ((structure() && structure()->needImpurePropertyWatchpoint())
-        || m_conditionSet.needImpurePropertyWatchpoint())
+        || m_conditionSet.needImpurePropertyWatchpoint()
+        || (m_polyProtoAccessChain && m_polyProtoAccessChain->needImpurePropertyWatchpoint()))
         result.append(vm.ensureWatchpointSetForImpureProperty(ident));
 
     if (additionalSet())
@@ -151,6 +155,9 @@ bool AccessCase::guardedByStructureCheck() const
     if (viaProxy())
         return false;
 
+    if (m_polyProtoAccessChain)
+        return false;
+
     switch (m_type) {
     case ArrayLength:
     case StringLength:
@@ -210,6 +217,19 @@ bool AccessCase::canReplace(const AccessCase& other) const
         return thisCase.moduleNamespaceObject() == otherCase.moduleNamespaceObject();
     }
     default:
+        if (other.type() != type())
+            return false;
+
+        if (m_polyProtoAccessChain) {
+            if (!other.m_polyProtoAccessChain)
+                return false;
+            // This is the only check we need since PolyProtoAccessChain contains the base structure.
+            // If we ever change it to contain only the prototype chain, we'll also need to change
+            // this to check the base structure.
+            return structure() == other.structure()
+                && *m_polyProtoAccessChain == *other.m_polyProtoAccessChain;
+        }
+
         if (!guardedByStructureCheck() || !other.guardedByStructureCheck())
             return false;
 
@@ -219,22 +239,27 @@ bool AccessCase::canReplace(const AccessCase& other) const
 
 void AccessCase::dump(PrintStream& out) const
 {
-    out.print(m_type, ":(");
+    out.print("\n", m_type, ":(");
 
     CommaPrinter comma;
 
     out.print(comma, m_state);
 
-    if (m_type == Transition)
-        out.print(comma, "structure = ", pointerDump(structure()), " -> ", pointerDump(newStructure()));
-    else if (m_structure)
-        out.print(comma, "structure = ", pointerDump(m_structure.get()));
-
     if (isValidOffset(m_offset))
         out.print(comma, "offset = ", m_offset);
     if (!m_conditionSet.isEmpty())
         out.print(comma, "conditions = ", m_conditionSet);
 
+    if (m_polyProtoAccessChain) {
+        out.print(comma, "prototype access chain = ");
+        m_polyProtoAccessChain->dump(structure(), out);
+    } else {
+        if (m_type == Transition)
+            out.print(comma, "structure = ", pointerDump(structure()), " -> ", pointerDump(newStructure()));
+        else if (m_structure)
+            out.print(comma, "structure = ", pointerDump(m_structure.get()));
+    }
+
     dumpImpl(out, comma);
     out.print(")");
 }
@@ -243,6 +268,12 @@ bool AccessCase::visitWeak(VM& vm) const
 {
     if (m_structure && !Heap::isMarked(m_structure.get()))
         return false;
+    if (m_polyProtoAccessChain) {
+        for (Structure* structure : m_polyProtoAccessChain->chain()) {
+            if (!Heap::isMarked(structure))
+                return false;
+        }
+    }
     if (!m_conditionSet.areStillLive())
         return false;
     if (isAccessor()) {
@@ -273,6 +304,11 @@ bool AccessCase::propagateTransitions(SlotVisitor& visitor) const
     if (m_structure)
         result &= m_structure->markIfCheap(visitor);
 
+    if (m_polyProtoAccessChain) {
+        for (Structure* structure : m_polyProtoAccessChain->chain())
+            result &= structure->markIfCheap(visitor);
+    }
+
     switch (m_type) {
     case Transition:
         if (Heap::isMarkedConcurrently(m_structure->previousID()))
@@ -372,26 +408,72 @@ void AccessCase::generateWithGuard(
     }
 
     default: {
-        if (viaProxy()) {
-            fallThrough.append(
-                jit.branch8(
-                    CCallHelpers::NotEqual,
-                    CCallHelpers::Address(baseGPR, JSCell::typeInfoTypeOffset()),
-                    CCallHelpers::TrustedImm32(PureForwardingProxyType)));
-
-            jit.loadPtr(CCallHelpers::Address(baseGPR, JSProxy::targetOffset()), scratchGPR);
-
-            fallThrough.append(
-                jit.branchStructure(
-                    CCallHelpers::NotEqual,
-                    CCallHelpers::Address(scratchGPR, JSCell::structureIDOffset()),
-                    structure()));
+        if (m_polyProtoAccessChain) {
+            GPRReg baseForAccessGPR = state.scratchGPR;
+            jit.move(state.baseGPR, baseForAccessGPR);
+            m_polyProtoAccessChain->forEach(structure(), [&] (Structure* structure, bool atEnd) {
+                fallThrough.append(
+                    jit.branchStructure(
+                        CCallHelpers::NotEqual,
+                        CCallHelpers::Address(baseForAccessGPR, JSCell::structureIDOffset()),
+                        structure));
+                if (atEnd) {
+                    if ((m_type == Miss || m_type == InMiss || m_type == Transition) && structure->hasPolyProto()) {
+                        // For a Miss/InMiss/Transition, we must ensure we're at the end when the last item is poly proto.
+                        // Transitions must do this because they need to verify there isn't a setter in the chain.
+                        // Miss/InMiss need to do this to ensure there isn't a new item at the end of the chain that
+                        // has the property.
+                        PropertyOffset polyProtoOffset = structure->polyProtoOffset();
+                        RELEASE_ASSERT(isInlineOffset(polyProtoOffset));
+#if USE(JSVALUE64)
+                        jit.load64(MacroAssembler::Address(baseForAccessGPR, offsetRelativeToBase(polyProtoOffset)), baseForAccessGPR);
+                        fallThrough.append(jit.branch64(CCallHelpers::NotEqual, baseForAccessGPR, CCallHelpers::TrustedImm64(ValueNull)));
+#else
+                        jit.load32(MacroAssembler::Address(baseForAccessGPR, offsetRelativeToBase(polyProtoOffset) + PayloadOffset), baseForAccessGPR);
+                        fallThrough.append(jit.branchTestPtr(CCallHelpers::NonZero, baseForAccessGPR));
+#endif
+                    }
+                } else {
+                    if (structure->hasMonoProto()) {
+                        JSValue prototype = structure->prototypeForLookup(state.m_globalObject);
+                        RELEASE_ASSERT(prototype.isObject());
+                        jit.move(CCallHelpers::TrustedImmPtr(asObject(prototype)), baseForAccessGPR);
+                    } else {
+                        RELEASE_ASSERT(structure->isObject()); // Primitives must have a stored prototype. We use prototypeForLookup for them.
+                        PropertyOffset polyProtoOffset = structure->polyProtoOffset();
+                        RELEASE_ASSERT(isInlineOffset(polyProtoOffset));
+#if USE(JSVALUE64)
+                        jit.load64(MacroAssembler::Address(baseForAccessGPR, offsetRelativeToBase(polyProtoOffset)), baseForAccessGPR);
+                        fallThrough.append(jit.branch64(CCallHelpers::Equal, baseForAccessGPR, CCallHelpers::TrustedImm64(ValueNull)));
+#else
+                        jit.load32(MacroAssembler::Address(baseForAccessGPR, offsetRelativeToBase(polyProtoOffset) + PayloadOffset), baseForAccessGPR);
+                        fallThrough.append(jit.branchTestPtr(CCallHelpers::Zero, baseForAccessGPR));
+#endif
+                    }
+                }
+            });
         } else {
-            fallThrough.append(
-                jit.branchStructure(
-                    CCallHelpers::NotEqual,
-                    CCallHelpers::Address(baseGPR, JSCell::structureIDOffset()),
-                    structure()));
+            if (viaProxy()) {
+                fallThrough.append(
+                    jit.branch8(
+                        CCallHelpers::NotEqual,
+                        CCallHelpers::Address(baseGPR, JSCell::typeInfoTypeOffset()),
+                        CCallHelpers::TrustedImm32(PureForwardingProxyType)));
+
+                jit.loadPtr(CCallHelpers::Address(baseGPR, JSProxy::targetOffset()), scratchGPR);
+
+                fallThrough.append(
+                    jit.branchStructure(
+                        CCallHelpers::NotEqual,
+                        CCallHelpers::Address(scratchGPR, JSCell::structureIDOffset()),
+                        structure()));
+            } else {
+                fallThrough.append(
+                    jit.branchStructure(
+                        CCallHelpers::NotEqual,
+                        CCallHelpers::Address(baseGPR, JSCell::structureIDOffset()),
+                        structure()));
+            }
         }
         break;
     } };
@@ -428,6 +510,8 @@ void AccessCase::generateImpl(AccessGenerationState& state)
     ASSERT(m_conditionSet.structuresEnsureValidityAssumingImpurePropertyWatchpoint());
 
     for (const ObjectPropertyCondition& condition : m_conditionSet) {
+        RELEASE_ASSERT(!m_polyProtoAccessChain);
+
         Structure* structure = condition.object()->structure();
 
         if (condition.isWatchableAssumingImpurePropertyWatchpoint()) {
@@ -504,13 +588,19 @@ void AccessCase::generateImpl(AccessGenerationState& state)
             baseForGetGPR = baseGPR;
 
         GPRReg baseForAccessGPR;
-        if (!m_conditionSet.isEmpty()) {
-            jit.move(
-                CCallHelpers::TrustedImmPtr(alternateBase()),
-                scratchGPR);
+        if (m_polyProtoAccessChain) {
+            // This isn't pretty, but we know we got here via generateWithGuard,
+            // and it left the baseForAccess inside scratchGPR. We could re-derive the base,
+            // but it'd require emitting the same code to load the base twice.
             baseForAccessGPR = scratchGPR;
-        } else
-            baseForAccessGPR = baseForGetGPR;
+        } else {
+            if (!m_conditionSet.isEmpty()) {
+                jit.move(
+                    CCallHelpers::TrustedImmPtr(alternateBase()), scratchGPR);
+                baseForAccessGPR = scratchGPR;
+            } else
+                baseForAccessGPR = baseForGetGPR;
+        }
 
         GPRReg loadedValueGPR = InvalidGPRReg;
         if (m_type != CustomValueGetter && m_type != CustomAccessorGetter && m_type != CustomValueSetter && m_type != CustomAccessorSetter) {
@@ -1016,7 +1106,7 @@ void AccessCase::generateImpl(AccessGenerationState& state)
         
     case IntrinsicGetter: {
         RELEASE_ASSERT(isValidOffset(offset()));
-        
+
         // We need to ensure the getter value does not move from under us. Note that GetterSetters
         // are immutable so we just need to watch the property not any value inside it.
         Structure* currStructure;
index 74c2b90..a287c5f 100644 (file)
@@ -29,6 +29,7 @@
 
 #include "JSFunctionInlines.h"
 #include "ObjectPropertyConditionSet.h"
+#include "PolyProtoAccessChain.h"
 #include <wtf/CommaPrinter.h>
 
 namespace JSC {
@@ -120,11 +121,11 @@ public:
     }
 
     static std::unique_ptr<AccessCase> create(VM&, JSCell* owner, AccessType, PropertyOffset = invalidOffset,
-        Structure* = nullptr, const ObjectPropertyConditionSet& = ObjectPropertyConditionSet());
+        Structure* = nullptr, const ObjectPropertyConditionSet& = ObjectPropertyConditionSet(), std::unique_ptr<PolyProtoAccessChain> = nullptr);
 
     // This create method should be used for transitions.
     static std::unique_ptr<AccessCase> create(VM&, JSCell* owner, PropertyOffset, Structure* oldStructure,
-        Structure* newStructure, const ObjectPropertyConditionSet& = ObjectPropertyConditionSet());
+        Structure* newStructure, const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>);
 
     static std::unique_ptr<AccessCase> fromStructureStubInfo(VM&, JSCell* owner, StructureStubInfo&);
 
@@ -186,9 +187,25 @@ public:
 
     virtual ~AccessCase();
 
+    bool usesPolyProto() const
+    {
+        return !!m_polyProtoAccessChain;
+    }
+
 protected:
-    AccessCase(VM&, JSCell* owner, AccessType, PropertyOffset, Structure*, const ObjectPropertyConditionSet&);
-    AccessCase(const AccessCase&) = default;
+    AccessCase(VM&, JSCell* owner, AccessType, PropertyOffset, Structure*, const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>);
+    AccessCase(AccessCase&&) = default;
+    AccessCase(const AccessCase& other)
+        : m_type(other.m_type)
+        , m_state(other.m_state)
+        , m_offset(other.m_offset)
+        , m_structure(other.m_structure)
+        , m_conditionSet(other.m_conditionSet)
+    {
+        if (other.m_polyProtoAccessChain)
+            m_polyProtoAccessChain = other.m_polyProtoAccessChain->clone();
+    }
+
     AccessCase& operator=(const AccessCase&) = delete;
     void resetState() { m_state = Primordial; }
 
@@ -227,6 +244,8 @@ private:
     WriteBarrier<Structure> m_structure;
 
     ObjectPropertyConditionSet m_conditionSet;
+
+    std::unique_ptr<PolyProtoAccessChain> m_polyProtoAccessChain;
 };
 
 } // namespace JSC
index 8e42e1b..20b4dd2 100644 (file)
@@ -593,7 +593,7 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink
             int inferredInlineCapacity = pc[opLength - 2].u.operand;
 
             instructions[i + opLength - 1] = objectAllocationProfile;
-            objectAllocationProfile->initialize(vm,
+            objectAllocationProfile->initializeProfile(vm,
                 m_globalObject.get(), this, m_globalObject->objectPrototype(), inferredInlineCapacity);
             break;
         }
index 723386e..cc0a247 100644 (file)
@@ -219,6 +219,9 @@ GetByIdStatus GetByIdStatus::computeForStubInfoWithoutExitSiteFeedback(
             const AccessCase& access = list->at(listIndex);
             if (access.viaProxy())
                 return GetByIdStatus(slowPathState, true);
+
+            if (access.usesPolyProto())
+                return GetByIdStatus(slowPathState, true);
             
             Structure* structure = access.structure();
             if (!structure) {
index 606691b..61b31ec 100644 (file)
@@ -42,17 +42,17 @@ namespace GetterSetterAccessCaseInternal {
 static const bool verbose = false;
 }
 
-GetterSetterAccessCase::GetterSetterAccessCase(VM& vm, JSCell* owner, AccessType accessType, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet, JSObject* customSlotBase)
-    : Base(vm, owner, accessType, offset, structure, conditionSet, viaProxy, additionalSet)
+GetterSetterAccessCase::GetterSetterAccessCase(VM& vm, JSCell* owner, AccessType accessType, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet, JSObject* customSlotBase, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain)
+    : Base(vm, owner, accessType, offset, structure, conditionSet, viaProxy, additionalSet, WTFMove(prototypeAccessChain))
 {
     m_customSlotBase.setMayBeNull(vm, owner, customSlotBase);
 }
 
 
 std::unique_ptr<AccessCase> GetterSetterAccessCase::create(
-    VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure,
-    const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet,
-    PropertySlot::GetValueFunc customGetter, JSObject* customSlotBase, std::optional<DOMAttributeAnnotation> domAttribute)
+    VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet,
+    bool viaProxy, WatchpointSet* additionalSet, PropertySlot::GetValueFunc customGetter, JSObject* customSlotBase,
+    std::optional<DOMAttributeAnnotation> domAttribute, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain)
 {
     switch (type) {
     case Getter:
@@ -63,18 +63,18 @@ std::unique_ptr<AccessCase> GetterSetterAccessCase::create(
         ASSERT_NOT_REACHED();
     };
 
-    std::unique_ptr<GetterSetterAccessCase> result(new GetterSetterAccessCase(vm, owner, type, offset, structure, conditionSet, viaProxy, additionalSet, customSlotBase));
+    std::unique_ptr<GetterSetterAccessCase> result(new GetterSetterAccessCase(vm, owner, type, offset, structure, conditionSet, viaProxy, additionalSet, customSlotBase, WTFMove(prototypeAccessChain)));
     result->m_domAttribute = domAttribute;
     result->m_customAccessor.getter = customGetter;
     return WTFMove(result);
 }
 
 std::unique_ptr<AccessCase> GetterSetterAccessCase::create(VM& vm, JSCell* owner, AccessType type, Structure* structure, PropertyOffset offset,
-    const ObjectPropertyConditionSet& conditionSet, PutPropertySlot::PutValueFunc customSetter,
+    const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain, PutPropertySlot::PutValueFunc customSetter,
     JSObject* customSlotBase)
 {
     ASSERT(type == Setter || type == CustomValueSetter || type == CustomAccessorSetter);
-    std::unique_ptr<GetterSetterAccessCase> result(new GetterSetterAccessCase(vm, owner, type, offset, structure, conditionSet, false, nullptr, customSlotBase));
+    std::unique_ptr<GetterSetterAccessCase> result(new GetterSetterAccessCase(vm, owner, type, offset, structure, conditionSet, false, nullptr, customSlotBase, WTFMove(prototypeAccessChain)));
     result->m_customAccessor.setter = customSetter;
     return WTFMove(result);
 }
index 7626255..242abc9 100644 (file)
@@ -48,16 +48,12 @@ public:
 
     static std::unique_ptr<AccessCase> create(
         VM&, JSCell* owner, AccessType, PropertyOffset, Structure*,
-        const ObjectPropertyConditionSet& = ObjectPropertyConditionSet(),
-        bool viaProxy = false,
-        WatchpointSet* additionalSet = nullptr,
-        PropertySlot::GetValueFunc = nullptr,
-        JSObject* customSlotBase = nullptr,
-        std::optional<DOMAttributeAnnotation> = std::nullopt);
+        const ObjectPropertyConditionSet&, bool viaProxy, WatchpointSet* additionalSet, PropertySlot::GetValueFunc,
+        JSObject* customSlotBase, std::optional<DOMAttributeAnnotation>, std::unique_ptr<PolyProtoAccessChain>);
 
     static std::unique_ptr<AccessCase> create(VM&, JSCell* owner, AccessType, Structure*, PropertyOffset,
-        const ObjectPropertyConditionSet&, PutPropertySlot::PutValueFunc = nullptr,
-        JSObject* customSlotBase = nullptr);
+        const ObjectPropertyConditionSet&, std::unique_ptr<PolyProtoAccessChain>,
+        PutPropertySlot::PutValueFunc = nullptr, JSObject* customSlotBase = nullptr);
 
     void dumpImpl(PrintStream&, CommaPrinter&) const override;
     std::unique_ptr<AccessCase> clone() const override;
@@ -67,7 +63,7 @@ public:
     void* customAccessor() const { return m_customAccessor.opaque; }
 
 private:
-    GetterSetterAccessCase(VM&, JSCell*, AccessType, PropertyOffset, Structure*, const ObjectPropertyConditionSet&, bool viaProxy, WatchpointSet* additionalSet, JSObject* customSlotBase);
+    GetterSetterAccessCase(VM&, JSCell*, AccessType, PropertyOffset, Structure*, const ObjectPropertyConditionSet&, bool viaProxy, WatchpointSet* additionalSet, JSObject* customSlotBase, std::unique_ptr<PolyProtoAccessChain>);
 
     GetterSetterAccessCase(const GetterSetterAccessCase&);
 
index 248c6a5..110bf92 100644 (file)
@@ -46,8 +46,11 @@ private:
 inline Structure* InternalFunctionAllocationProfile::createAllocationStructureFromBase(VM& vm, JSGlobalObject* globalObject, JSCell* owner, JSObject* prototype, Structure* baseStructure)
 {
     ASSERT(!m_structure || m_structure.get()->classInfo() != baseStructure->classInfo());
+    ASSERT(baseStructure->hasMonoProto());
 
     Structure* structure;
+    // FIXME: Implement polymorphic prototypes for subclasses of builtin types:
+    // https://bugs.webkit.org/show_bug.cgi?id=177318
     if (prototype == baseStructure->storedPrototype())
         structure = baseStructure;
     else
index 92d6a55..b631dba 100644 (file)
@@ -33,7 +33,7 @@
 namespace JSC {
 
 IntrinsicGetterAccessCase::IntrinsicGetterAccessCase(VM& vm, JSCell* owner, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, JSFunction* intrinsicFunction)
-    : Base(vm, owner, IntrinsicGetter, offset, structure, conditionSet)
+    : Base(vm, owner, IntrinsicGetter, offset, structure, conditionSet, nullptr)
 {
     m_intrinsicFunction.set(vm, owner, intrinsicFunction);
 }
index 1021c18..67d42f8 100644 (file)
@@ -42,6 +42,8 @@ public:
     static bool canEmitIntrinsicGetter(JSFunction*, Structure*);
     void emitIntrinsicGetter(AccessGenerationState&);
 
+    // FIXME: Make this work with poly proto:
+    // https://bugs.webkit.org/show_bug.cgi?id=177318
     static std::unique_ptr<AccessCase> create(VM&, JSCell*, PropertyOffset, Structure*, const ObjectPropertyConditionSet&, JSFunction* intrinsicFunction);
 
     std::unique_ptr<AccessCase> clone() const override;
index 3c168c6..b8e1798 100644 (file)
@@ -38,7 +38,7 @@
 namespace JSC {
 
 ModuleNamespaceAccessCase::ModuleNamespaceAccessCase(VM& vm, JSCell* owner, JSModuleNamespaceObject* moduleNamespaceObject, JSModuleEnvironment* moduleEnvironment, ScopeOffset scopeOffset)
-    : Base(vm, owner, ModuleNamespaceLoad, invalidOffset, nullptr, ObjectPropertyConditionSet())
+    : Base(vm, owner, ModuleNamespaceLoad, invalidOffset, nullptr, ObjectPropertyConditionSet(), nullptr)
     , m_scopeOffset(scopeOffset)
 {
     m_moduleNamespaceObject.set(vm, owner, moduleNamespaceObject);
diff --git a/Source/JavaScriptCore/bytecode/ObjectAllocationProfile.cpp b/Source/JavaScriptCore/bytecode/ObjectAllocationProfile.cpp
new file mode 100644 (file)
index 0000000..3056092
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2017 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 "ObjectAllocationProfile.h"
+
+#include "JSFunction.h"
+#include "JSFunctionInlines.h"
+
+namespace JSC {
+
+void ObjectAllocationProfile::initializeProfile(VM& vm, JSGlobalObject* globalObject, JSCell* owner, JSObject* prototype, unsigned inferredInlineCapacity, JSFunction* constructor)
+{
+    ASSERT(!m_allocator);
+    ASSERT(!m_structure);
+    ASSERT(!m_inlineCapacity);
+
+    // FIXME: When going poly proto, we should make an allocator and teach
+    // create_this' fast path how to allocate a poly proto object.
+    // https://bugs.webkit.org/show_bug.cgi?id=177517
+    bool isPolyProto = false;
+    FunctionExecutable* executable = nullptr;
+    if (constructor) {
+        // FIXME: A JSFunction should watch the poly proto watchpoint if it is not invalidated.
+        // That way it can clear this object allocation profile to ensure it stops allocating
+        // mono proto |this| values when it knows that it should be allocating poly proto
+        // |this| values:
+        // https://bugs.webkit.org/show_bug.cgi?id=177792
+
+        executable = constructor->jsExecutable();
+        isPolyProto = false;
+        if (Options::forcePolyProto())
+            isPolyProto = true;
+        else
+            isPolyProto = executable->ensurePolyProtoWatchpoint().hasBeenInvalidated() && executable->singletonFunction()->hasBeenInvalidated();
+
+        if (isPolyProto) {
+            if (Structure* structure = executable->cachedPolyProtoStructure()) {
+                RELEASE_ASSERT(structure->typeInfo().type() == FinalObjectType);
+                m_allocator = nullptr;
+                m_structure.set(vm, owner, structure);
+                m_inlineCapacity = structure->inlineCapacity();
+                return;
+            }
+        }
+    }
+
+    unsigned inlineCapacity = 0;
+    if (inferredInlineCapacity < JSFinalObject::defaultInlineCapacity()) {
+        // Try to shrink the object based on static analysis.
+        inferredInlineCapacity += possibleDefaultPropertyCount(vm, prototype);
+
+        if (!inferredInlineCapacity) {
+            // Empty objects are rare, so most likely the static analyzer just didn't
+            // see the real initializer function. This can happen with helper functions.
+            inferredInlineCapacity = JSFinalObject::defaultInlineCapacity();
+        } else if (inferredInlineCapacity > JSFinalObject::defaultInlineCapacity()) {
+            // Default properties are weak guesses, so don't allow them to turn a small
+            // object into a large object.
+            inferredInlineCapacity = JSFinalObject::defaultInlineCapacity();
+        }
+
+        inlineCapacity = inferredInlineCapacity;
+        ASSERT(inlineCapacity < JSFinalObject::maxInlineCapacity());
+    } else {
+        // Normal or large object.
+        inlineCapacity = inferredInlineCapacity;
+        if (inlineCapacity > JSFinalObject::maxInlineCapacity())
+            inlineCapacity = JSFinalObject::maxInlineCapacity();
+    }
+
+    if (isPolyProto) {
+        ++inlineCapacity;
+        inlineCapacity = std::min(inlineCapacity, JSFinalObject::maxInlineCapacity());
+    }
+
+    ASSERT(inlineCapacity > 0);
+    ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity());
+
+    size_t allocationSize = JSFinalObject::allocationSize(inlineCapacity);
+    MarkedAllocator* allocator = vm.cellSpace.allocatorFor(allocationSize);
+
+    // Take advantage of extra inline capacity available in the size class.
+    if (allocator) {
+        size_t slop = (allocator->cellSize() - allocationSize) / sizeof(WriteBarrier<Unknown>);
+        inlineCapacity += slop;
+        if (inlineCapacity > JSFinalObject::maxInlineCapacity())
+            inlineCapacity = JSFinalObject::maxInlineCapacity();
+    }
+
+    Structure* structure = vm.prototypeMap.emptyObjectStructureForPrototype(globalObject, prototype, inlineCapacity, isPolyProto);
+
+    if (isPolyProto) {
+        ASSERT(structure->hasPolyProto());
+        m_allocator = nullptr;
+        executable->setCachedPolyProtoStructure(vm, structure);
+    } else {
+        if (executable) {
+            executable->ensurePolyProtoWatchpoint();
+            structure->ensureRareData(vm)->setSharedPolyProtoWatchpoint(executable->sharedPolyProtoWatchpoint());
+        }
+
+        m_allocator = allocator;
+    }
+
+    // Ensure that if another thread sees the structure, it will see it properly created
+    WTF::storeStoreFence();
+
+    m_structure.set(vm, owner, structure);
+    m_inlineCapacity = inlineCapacity;
+}
+
+unsigned ObjectAllocationProfile::possibleDefaultPropertyCount(VM& vm, JSObject* prototype)
+{
+    if (prototype == prototype->globalObject()->objectPrototype())
+        return 0;
+
+    size_t count = 0;
+    PropertyNameArray propertyNameArray(&vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include);
+    prototype->structure()->getPropertyNamesFromStructure(vm, propertyNameArray, EnumerationMode());
+    PropertyNameArrayData::PropertyNameVector& propertyNameVector = propertyNameArray.data()->propertyNameVector();
+    for (size_t i = 0; i < propertyNameVector.size(); ++i) {
+        JSValue value = prototype->getDirect(vm, propertyNameVector[i]);
+
+        // Functions are common, and are usually class-level objects that are not overridden.
+        if (jsDynamicCast<JSFunction*>(vm, value))
+            continue;
+
+        ++count;
+
+    }
+    return count;
+}
+
+} // namespace JSC
index d9865f5..4c17c8d 100644 (file)
@@ -48,59 +48,7 @@ public:
 
     bool isNull() { return !m_structure; }
 
-    void initialize(VM& vm, JSGlobalObject* globalObject, JSCell* owner, JSObject* prototype, unsigned inferredInlineCapacity)
-    {
-        ASSERT(!m_allocator);
-        ASSERT(!m_structure);
-        ASSERT(!m_inlineCapacity);
-
-        unsigned inlineCapacity = 0;
-        if (inferredInlineCapacity < JSFinalObject::defaultInlineCapacity()) {
-            // Try to shrink the object based on static analysis.
-            inferredInlineCapacity += possibleDefaultPropertyCount(vm, prototype);
-
-            if (!inferredInlineCapacity) {
-                // Empty objects are rare, so most likely the static analyzer just didn't
-                // see the real initializer function. This can happen with helper functions.
-                inferredInlineCapacity = JSFinalObject::defaultInlineCapacity();
-            } else if (inferredInlineCapacity > JSFinalObject::defaultInlineCapacity()) {
-                // Default properties are weak guesses, so don't allow them to turn a small
-                // object into a large object.
-                inferredInlineCapacity = JSFinalObject::defaultInlineCapacity();
-            }
-
-            inlineCapacity = inferredInlineCapacity;
-            ASSERT(inlineCapacity < JSFinalObject::maxInlineCapacity());
-        } else {
-            // Normal or large object.
-            inlineCapacity = inferredInlineCapacity;
-            if (inlineCapacity > JSFinalObject::maxInlineCapacity())
-                inlineCapacity = JSFinalObject::maxInlineCapacity();
-        }
-
-        ASSERT(inlineCapacity > 0);
-        ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity());
-
-        size_t allocationSize = JSFinalObject::allocationSize(inlineCapacity);
-        MarkedAllocator* allocator = vm.cellSpace.allocatorFor(allocationSize);
-        
-        // Take advantage of extra inline capacity available in the size class.
-        if (allocator) {
-            size_t slop = (allocator->cellSize() - allocationSize) / sizeof(WriteBarrier<Unknown>);
-            inlineCapacity += slop;
-            if (inlineCapacity > JSFinalObject::maxInlineCapacity())
-                inlineCapacity = JSFinalObject::maxInlineCapacity();
-        }
-
-        Structure* structure = vm.prototypeMap.emptyObjectStructureForPrototype(globalObject, prototype, inlineCapacity);
-
-        // Ensure that if another thread sees the structure, it will see it properly created
-        WTF::storeStoreFence();
-
-        m_allocator = allocator;
-        m_structure.set(vm, owner, structure);
-        m_inlineCapacity = inlineCapacity;
-    }
+    void initializeProfile(VM&, JSGlobalObject*, JSCell* owner, JSObject* prototype, unsigned inferredInlineCapacity, JSFunction* constructor = nullptr);
 
     Structure* structure()
     {
@@ -113,7 +61,7 @@ public:
 
     void clear()
     {
-        m_allocator = 0;
+        m_allocator = nullptr;
         m_structure.clear();
         m_inlineCapacity = 0;
         ASSERT(isNull());
@@ -125,28 +73,7 @@ public:
     }
 
 private:
-
-    unsigned possibleDefaultPropertyCount(VM& vm, JSObject* prototype)
-    {
-        if (prototype == prototype->globalObject()->objectPrototype())
-            return 0;
-
-        size_t count = 0;
-        PropertyNameArray propertyNameArray(&vm, PropertyNameMode::StringsAndSymbols, PrivateSymbolMode::Include);
-        prototype->structure()->getPropertyNamesFromStructure(vm, propertyNameArray, EnumerationMode());
-        PropertyNameArrayData::PropertyNameVector& propertyNameVector = propertyNameArray.data()->propertyNameVector();
-        for (size_t i = 0; i < propertyNameVector.size(); ++i) {
-            JSValue value = prototype->getDirect(vm, propertyNameVector[i]);
-
-            // Functions are common, and are usually class-level objects that are not overridden.
-            if (jsDynamicCast<JSFunction*>(vm, value))
-                continue;
-
-            ++count;
-
-        }
-        return count;
-    }
+    unsigned possibleDefaultPropertyCount(VM&, JSObject* prototype);
 
     MarkedAllocator* m_allocator; // Precomputed to make things easier for generated code.
     WriteBarrier<Structure> m_structure;
index 446684e..d5e59b2 100644 (file)
@@ -204,11 +204,15 @@ ObjectPropertyCondition generateCondition(
         break;
     }
     case PropertyCondition::Absence: {
+        if (structure->hasPolyProto())
+            return ObjectPropertyCondition();
         result = ObjectPropertyCondition::absence(
             vm, owner, object, uid, object->structure()->storedPrototypeObject());
         break;
     }
     case PropertyCondition::AbsenceOfSetEffect: {
+        if (structure->hasPolyProto())
+            return ObjectPropertyCondition();
         result = ObjectPropertyCondition::absenceOfSetEffect(
             vm, owner, object, uid, object->structure()->storedPrototypeObject());
         break;
@@ -258,6 +262,15 @@ ObjectPropertyConditionSet generateConditions(
                 dataLog("It's a proxy, so invalid.\n");
             return ObjectPropertyConditionSet::invalid();
         }
+
+        if (structure->hasPolyProto()) {
+            // FIXME: Integrate this with PolyProtoAccessChain:
+            // https://bugs.webkit.org/show_bug.cgi?id=177339
+            // Or at least allow OPC set generation when the
+            // base is not poly proto:
+            // https://bugs.webkit.org/show_bug.cgi?id=177721
+            return ObjectPropertyConditionSet::invalid();
+        }
         
         JSValue value = structure->prototypeForLookup(globalObject);
         
diff --git a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp
new file mode 100644 (file)
index 0000000..27ce7e0
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 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 "PolyProtoAccessChain.h"
+
+#include "JSObject.h"
+
+namespace JSC {
+
+std::unique_ptr<PolyProtoAccessChain> PolyProtoAccessChain::create(JSGlobalObject* globalObject, JSCell* base, const PropertySlot& slot, bool& usesPolyProto)
+{
+    JSObject* target = slot.isUnset() ? nullptr : slot.slotBase();
+    return create(globalObject, base, target, usesPolyProto);
+}
+
+std::unique_ptr<PolyProtoAccessChain> PolyProtoAccessChain::create(JSGlobalObject* globalObject, JSCell* base, JSObject* target, bool& usesPolyProto)
+{
+    JSCell* current = base;
+    VM& vm = *base->vm();
+
+    bool found = false;
+
+    usesPolyProto = false;
+
+    std::unique_ptr<PolyProtoAccessChain> result(new PolyProtoAccessChain());
+
+    for (unsigned iterationNumber = 0; true; ++iterationNumber) {
+        Structure* structure = current->structure(vm);
+
+        if (!structure->propertyAccessesAreCacheable())
+            return nullptr;
+
+        if (structure->isProxy())
+            return nullptr;
+
+        if (structure->isDictionary()) {
+            ASSERT(structure->isObject());
+            if (structure->hasBeenFlattenedBefore())
+                return nullptr;
+
+            structure->flattenDictionaryStructure(vm, asObject(current));
+        }
+
+        // To save memory, we don't include the base in the chain. We let
+        // AccessCase provide the base to us as needed.
+        if (iterationNumber)
+            result->m_chain.append(structure);
+        else
+            RELEASE_ASSERT(current == base);
+
+        if (current == target) {
+            found = true;
+            break;
+        }
+
+        // We only have poly proto if we need to access our prototype via
+        // the poly proto protocol. If the slot base is the only poly proto
+        // thing in the chain, and we have a cache hit on it, then we're not
+        // poly proto.
+        usesPolyProto |= structure->hasPolyProto();
+
+        JSValue prototype = structure->prototypeForLookup(globalObject, current);
+        if (prototype.isNull())
+            break;
+        current = asObject(prototype);
+    }
+
+    if (!found && !!target)
+        return nullptr;
+
+    return result;
+}
+
+bool PolyProtoAccessChain::needImpurePropertyWatchpoint() const
+{
+    for (Structure* structure : m_chain) {
+        if (structure->needImpurePropertyWatchpoint())
+            return true;
+    }
+    return false;
+}
+
+bool PolyProtoAccessChain::operator==(const PolyProtoAccessChain& other) const
+{
+    return m_chain == other.m_chain;
+}
+
+void PolyProtoAccessChain::dump(Structure* baseStructure, PrintStream& out) const
+{
+    out.print("PolyPolyProtoAccessChain: [\n");
+    forEach(baseStructure, [&] (Structure* structure, bool) {
+        out.print("\t");
+        structure->dump(out);
+        out.print("\n");
+    });
+}
+
+}
diff --git a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h
new file mode 100644 (file)
index 0000000..52ab76e
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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
+
+#include <wtf/Vector.h>
+
+namespace JSC {
+
+class JSGlobalObject;
+class JSObject;
+class PropertySlot;
+class Structure;
+
+class PolyProtoAccessChain {
+    WTF_MAKE_FAST_ALLOCATED;
+
+public:
+    PolyProtoAccessChain(PolyProtoAccessChain&) = default;
+
+    // Returns nullptr when invalid.
+    static std::unique_ptr<PolyProtoAccessChain> create(JSGlobalObject*, JSCell* base, const PropertySlot&, bool& usesPolyProto);
+    static std::unique_ptr<PolyProtoAccessChain> create(JSGlobalObject*, JSCell* base, JSObject* target, bool& usesPolyProto);
+
+    std::unique_ptr<PolyProtoAccessChain> clone()
+    {
+        return std::make_unique<PolyProtoAccessChain>(*this);
+    }
+
+    const Vector<Structure*>& chain() const { return m_chain; }
+
+    void dump(Structure* baseStructure, PrintStream& out) const;
+
+    bool operator==(const PolyProtoAccessChain& other) const;
+    bool operator!=(const PolyProtoAccessChain& other) const
+    {
+        return !(*this == other);
+    }
+
+    bool needImpurePropertyWatchpoint() const;
+
+    template <typename Func>
+    void forEach(Structure* baseStructure, const Func& func) const
+    {
+        bool atEnd = !m_chain.size();
+        func(baseStructure, atEnd);
+        for (unsigned i = 0; i < m_chain.size(); ++i) {
+            atEnd = i + 1 == m_chain.size();
+            func(m_chain[i], atEnd);
+        }
+    }
+
+private:
+    PolyProtoAccessChain() = default;
+
+    // This does not include the base. We rely on AccessCase providing it for us. That said, this data
+    // structure is tied to the base that it was created with.
+    Vector<Structure*> m_chain; 
+};
+
+}
index f8a3977..6e6232f 100644 (file)
@@ -258,6 +258,41 @@ AccessGenerationResult PolymorphicAccess::addCases(
     if (casesToAdd.isEmpty())
         return AccessGenerationResult::MadeNoChanges;
 
+    bool shouldReset = false;
+    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.
+            // FIXME: We should probably have all that can watch for the poly proto
+            // watchpoint do it. This will allow stubs to clear out their non-poly-proto
+            // IC cases. This is likely to be faster if you build up a bunch of ICs, then
+            // trigger poly proto, then only access poly proto objects. If we do this, we'll
+            // need this code below to delay firing the watchpoint since it might cause
+            // us to deallocate ourselves:
+            // https://bugs.webkit.org/show_bug.cgi?id=177765
+            ASSERT(a->rareData()->sharedPolyProtoWatchpoint().get() == b->rareData()->sharedPolyProtoWatchpoint().get());
+            shouldReset |= a->rareData()->sharedPolyProtoWatchpoint()->hasBeenInvalidated(); 
+            a->rareData()->sharedPolyProtoWatchpoint()->invalidate(vm, 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 (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 AccessGenerationResult::ResetStub;
+
     // 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.
     for (auto& caseToAdd : casesToAdd) {
@@ -339,7 +374,7 @@ AccessGenerationResult PolymorphicAccess::regenerate(
     if (PolymorphicAccessInternal::verbose)
         dataLog("Regenerate with m_list: ", listDump(m_list), "\n");
     
-    AccessGenerationState state(vm);
+    AccessGenerationState state(vm, codeBlock->globalObject());
 
     state.access = this;
     state.stubInfo = &stubInfo;
@@ -590,6 +625,9 @@ void printInternal(PrintStream& out, AccessGenerationResult::Kind kind)
     case AccessGenerationResult::GeneratedFinalCode:
         out.print("GeneratedFinalCode");
         return;
+    case AccessGenerationResult::ResetStub:
+        out.print("ResetStub");
+        return;
     }
     
     RELEASE_ASSERT_NOT_REACHED();
index 39b5b05..8569b57 100644 (file)
@@ -52,7 +52,8 @@ public:
         GaveUp,
         Buffered,
         GeneratedNewCode,
-        GeneratedFinalCode // Generated so much code that we never want to generate code again.
+        GeneratedFinalCode, // Generated so much code that we never want to generate code again.
+        ResetStub // We found out some data that makes us want to start over fresh with this stub. Currently, this happens when we detect poly proto.
     };
     
     AccessGenerationResult()
@@ -98,6 +99,7 @@ public:
     bool buffered() const { return m_kind == Buffered; }
     bool generatedNewCode() const { return m_kind == GeneratedNewCode; }
     bool generatedFinalCode() const { return m_kind == GeneratedFinalCode; }
+    bool shouldResetStub() const { return m_kind == ResetStub; }
     
     // If we gave up on this attempt to generate code, or if we generated the "final" code, then we
     // should give up after this.
@@ -171,14 +173,16 @@ private:
 };
 
 struct AccessGenerationState {
-    AccessGenerationState(VM& vm)
+    AccessGenerationState(VM& vm, JSGlobalObject* globalObject)
         : m_vm(vm) 
+        , m_globalObject(globalObject)
         , m_calculatedRegistersForCallAndExceptionHandling(false)
         , m_needsToRestoreRegistersIfException(false)
         , m_calculatedCallSiteIndex(false)
     {
     }
     VM& m_vm;
+    JSGlobalObject* m_globalObject;
     CCallHelpers* jit { nullptr };
     ScratchRegisterAllocator* allocator;
     ScratchRegisterAllocator::PreservedState preservedReusedRegisterState;
index 12f69d9..cdcbb98 100644 (file)
@@ -107,14 +107,30 @@ bool PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint(
             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;
+        }
+
         PropertyOffset currentOffset = structure->getConcurrently(uid());
         if (currentOffset != invalidOffset) {
             if (PropertyConditionInternal::verbose)
                 dataLog("Invalid because the property exists at offset: ", currentOffset, "\n");
             return false;
         }
-        
-        if (structure->storedPrototypeObject() != prototype()) {
+
+        JSObject* currentPrototype;
+        if (structure->hasMonoProto())
+            currentPrototype = structure->storedPrototypeObject();
+        else {
+            RELEASE_ASSERT(base);
+            currentPrototype = jsDynamicCast<JSObject*>(*structure->vm(), base->getPrototypeDirect());
+        }
+
+        if (currentPrototype != prototype()) {
             if (PropertyConditionInternal::verbose) {
                 dataLog(
                     "Invalid because the prototype is ", structure->storedPrototype(), " even though "
@@ -145,6 +161,14 @@ bool PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint(
                 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) {
index 63879c4..b31a31f 100644 (file)
 
 namespace JSC {
 
-ProxyableAccessCase::ProxyableAccessCase(VM& vm, JSCell* owner, AccessType accessType, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet)
-    : Base(vm, owner, accessType, offset, structure, conditionSet)
+ProxyableAccessCase::ProxyableAccessCase(VM& vm, JSCell* owner, AccessType accessType, PropertyOffset offset, Structure* structure,
+    const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain)
+    : Base(vm, owner, accessType, offset, structure, conditionSet, WTFMove(prototypeAccessChain))
     , m_viaProxy(viaProxy)
     , m_additionalSet(additionalSet)
 {
 }
 
-std::unique_ptr<AccessCase> ProxyableAccessCase::create(VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet)
+std::unique_ptr<AccessCase> ProxyableAccessCase::create(VM& vm, JSCell* owner, AccessType type, PropertyOffset offset, Structure* structure, const ObjectPropertyConditionSet& conditionSet, bool viaProxy, WatchpointSet* additionalSet, std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain)
 {
     ASSERT(type == Load || type == Miss || type == GetGetter);
-    return std::unique_ptr<AccessCase>(new ProxyableAccessCase(vm, owner, type, offset, structure, conditionSet, viaProxy, additionalSet));
+    return std::unique_ptr<AccessCase>(new ProxyableAccessCase(vm, owner, type, offset, structure, conditionSet, viaProxy, additionalSet, WTFMove(prototypeAccessChain)));
 }
 
 ProxyableAccessCase::~ProxyableAccessCase()
index 578be22..5d3b9c3 100644 (file)
@@ -33,13 +33,13 @@ namespace JSC {
 
 class ProxyableAccessCase : public AccessCase {
 public:
-    typedef AccessCase Base;
+    using Base = AccessCase;
 
     bool viaProxy() const override { return m_viaProxy; }
     WatchpointSet* additionalSet() const override { return m_additionalSet.get(); }
 
     static std::unique_ptr<AccessCase> create(VM&, JSCell*, AccessType, PropertyOffset, Structure*, const ObjectPropertyConditionSet& = ObjectPropertyConditionSet(),
-        bool viaProxy = false, WatchpointSet* additionalSet = nullptr);
+        bool viaProxy = false, WatchpointSet* additionalSet = nullptr, std::unique_ptr<PolyProtoAccessChain> = nullptr);
 
     void dumpImpl(PrintStream&, CommaPrinter&) const override;
     std::unique_ptr<AccessCase> clone() const override;
@@ -47,7 +47,7 @@ public:
     ~ProxyableAccessCase();
 
 protected:
-    ProxyableAccessCase(VM&, JSCell*, AccessType, PropertyOffset, Structure*, const ObjectPropertyConditionSet&, bool viaProxy, WatchpointSet* additionalSet);
+    ProxyableAccessCase(VM&, JSCell*, AccessType, PropertyOffset, Structure*, const ObjectPropertyConditionSet&, bool viaProxy, WatchpointSet* additionalSet, std::unique_ptr<PolyProtoAccessChain>);
 
 private:
     bool m_viaProxy;
index f3a9edb..739e008 100644 (file)
@@ -184,6 +184,8 @@ PutByIdStatus PutByIdStatus::computeForStubInfo(
             const AccessCase& access = list->at(i);
             if (access.viaProxy())
                 return PutByIdStatus(slowPathState);
+            if (access.usesPolyProto())
+                return PutByIdStatus(slowPathState);
             
             PutByIdVariant variant;
             
index 3e3bf24..560b058 100644 (file)
@@ -131,6 +131,11 @@ AccessGenerationResult StructureStubInfo::addAccessCase(
         if (StructureStubInfoInternal::verbose)
             dataLog("Had stub, result: ", result, "\n");
 
+        if (result.shouldResetStub()) {
+            reset(codeBlock);
+            return result;
+        }
+
         if (!result.buffered()) {
             bufferedStructures.clear();
             return result;
@@ -152,6 +157,11 @@ AccessGenerationResult StructureStubInfo::addAccessCase(
         if (StructureStubInfoInternal::verbose)
             dataLog("Created stub, result: ", result, "\n");
 
+        if (result.shouldResetStub()) {
+            reset(codeBlock);
+            return result;
+        }
+
         if (!result.buffered()) {
             bufferedStructures.clear();
             return result;
index 98f1d2c..7218af5 100644 (file)
@@ -3744,6 +3744,11 @@ Node* ByteCodeParser::load(
                 // the structures in the variant.structureSet() agree on the prototype (it would be
                 // hilariously rare if they didn't). Note that we are relying on structureSet() having
                 // at least one element. That will always be true here because of how GetByIdStatus/PutByIdStatus work.
+
+                // FIXME: right now, if we have an OPCS, we have mono proto. However, this will
+                // need to be changed in the future once we have a hybrid data structure for
+                // poly proto:
+                // https://bugs.webkit.org/show_bug.cgi?id=177339
                 JSObject* prototype = variant.structureSet()[0]->storedPrototypeObject();
                 bool allAgree = true;
                 for (unsigned i = 1; i < variant.structureSet().size(); ++i) {
@@ -4341,12 +4346,16 @@ bool ByteCodeParser::parseBlock(unsigned limit)
             if (function) {
                 if (FunctionRareData* rareData = function->rareData()) {
                     if (Structure* structure = rareData->objectAllocationStructure()) {
-                        m_graph.freeze(rareData);
-                        m_graph.watchpoints().addLazily(rareData->allocationProfileWatchpointSet());
-                        // The callee is still live up to this point.
-                        addToGraph(Phantom, callee);
-                        set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(NewObject, OpInfo(m_graph.registerStructure(structure))));
-                        alreadyEmitted = true;
+                        // FIXME: we should be able to allocate a poly proto object here:
+                        // https://bugs.webkit.org/show_bug.cgi?id=177517
+                        if (structure->hasMonoProto()) {
+                            m_graph.freeze(rareData);
+                            m_graph.watchpoints().addLazily(rareData->allocationProfileWatchpointSet());
+                            // The callee is still live up to this point.
+                            addToGraph(Phantom, callee);
+                            set(VirtualRegister(currentInstruction[1].u.operand), addToGraph(NewObject, OpInfo(m_graph.registerStructure(structure))));
+                            alreadyEmitted = true;
+                        }
                     }
                 }
             }
index 14f0c00..5bfe527 100644 (file)
@@ -1787,7 +1787,8 @@ bool Graph::canDoFastSpread(Node* node, const AbstractValue& value)
     ArrayPrototype* arrayPrototype = globalObjectFor(node->child1()->origin.semantic)->arrayPrototype();
     bool allGood = true;
     value.m_structure.forEach([&] (RegisteredStructure structure) {
-        allGood &= structure->storedPrototype() == arrayPrototype
+        allGood &= structure->hasMonoProto()
+            && structure->storedPrototype() == arrayPrototype
             && !structure->isDictionary()
             && structure->getConcurrently(m_vm.propertyNames->iteratorSymbol.impl()) == invalidOffset
             && !structure->mayInterceptIndexedAccesses();
index 28060fb..9f158db 100644 (file)
@@ -244,7 +244,11 @@ JSCell* JIT_OPERATION operationCreateThis(ExecState* exec, JSObject* constructor
     if (constructor->type() == JSFunctionType) {
         auto rareData = jsCast<JSFunction*>(constructor)->rareData(exec, inlineCapacity);
         RETURN_IF_EXCEPTION(scope, nullptr);
-        return constructEmptyObject(exec, rareData->objectAllocationProfile()->structure());
+        Structure* structure = rareData->objectAllocationProfile()->structure();
+        JSObject* result = constructEmptyObject(exec, structure);
+        if (structure->hasPolyProto())
+            result->putDirect(vm, structure->polyProtoOffset(), jsCast<JSFunction*>(constructor)->prototypeForConstruction(vm, exec));
+        return result;
     }
 
     JSValue proto = constructor->get(exec, vm.propertyNames->prototype);
index 0eaaac1..30ab3a2 100644 (file)
@@ -3237,7 +3237,7 @@ void SpeculativeJIT::compilePutByValForCellWithSymbol(Node* node, Edge& child1,
     noResult(node);
 }
 
-void SpeculativeJIT::compileInstanceOfForObject(Node*, GPRReg valueReg, GPRReg prototypeReg, GPRReg scratchReg, GPRReg scratch2Reg)
+void SpeculativeJIT::compileInstanceOfForObject(Node*, GPRReg valueReg, GPRReg prototypeReg, GPRReg scratchReg, GPRReg scratch2Reg, GPRReg scratch3Reg)
 {
     // Check that prototype is an object.
     speculationCheck(BadType, JSValueRegs(), 0, m_jit.branchIfNotObject(prototypeReg));
@@ -3249,8 +3249,23 @@ void SpeculativeJIT::compileInstanceOfForObject(Node*, GPRReg valueReg, GPRReg p
     MacroAssembler::Label loop(&m_jit);
     MacroAssembler::Jump performDefaultHasInstance = m_jit.branch8(MacroAssembler::Equal,
         MacroAssembler::Address(scratchReg, JSCell::typeInfoTypeOffset()), TrustedImm32(ProxyObjectType));
-    m_jit.emitLoadStructure(*m_jit.vm(), scratchReg, scratchReg, scratch2Reg);
-    m_jit.loadPtr(MacroAssembler::Address(scratchReg, Structure::prototypeOffset() + CellPayloadOffset), scratchReg);
+    m_jit.emitLoadStructure(*m_jit.vm(), scratchReg, scratch3Reg, scratch2Reg);
+#if USE(JSVALUE64)
+    m_jit.load64(MacroAssembler::Address(scratch3Reg, Structure::prototypeOffset()), scratch3Reg);
+    auto isMonoProto = m_jit.branchIfNotInt32(JSValueRegs(scratch3Reg));
+    m_jit.zeroExtend32ToPtr(scratch3Reg, scratch3Reg);
+    m_jit.load64(JITCompiler::BaseIndex(scratchReg, scratch3Reg, JITCompiler::TimesEight, JSObject::offsetOfInlineStorage()), scratch3Reg);
+    isMonoProto.link(&m_jit);
+    m_jit.move(scratch3Reg, scratchReg);
+#else
+    m_jit.load32(MacroAssembler::Address(scratch3Reg, Structure::prototypeOffset() + TagOffset), scratch2Reg);
+    m_jit.load32(MacroAssembler::Address(scratch3Reg, Structure::prototypeOffset() + PayloadOffset), scratch3Reg);
+    auto isMonoProto = m_jit.branch32(CCallHelpers::NotEqual, scratch2Reg, TrustedImm32(JSValue::Int32Tag));
+    m_jit.load32(JITCompiler::BaseIndex(scratchReg, scratch3Reg, JITCompiler::TimesEight, JSObject::offsetOfInlineStorage() + PayloadOffset), scratch3Reg);
+    isMonoProto.link(&m_jit);
+    m_jit.move(scratch3Reg, scratchReg);
+#endif
+
     MacroAssembler::Jump isInstance = m_jit.branchPtr(MacroAssembler::Equal, scratchReg, prototypeReg);
 #if USE(JSVALUE64)
     m_jit.branchIfCell(JSValueRegs(scratchReg)).linkTo(loop, &m_jit);
@@ -3381,10 +3396,12 @@ void SpeculativeJIT::compileInstanceOf(Node* node)
         SpeculateCellOperand prototype(this, node->child2());
         GPRTemporary scratch(this);
         GPRTemporary scratch2(this);
+        GPRTemporary scratch3(this);
         
         GPRReg prototypeReg = prototype.gpr();
         GPRReg scratchReg = scratch.gpr();
         GPRReg scratch2Reg = scratch2.gpr();
+        GPRReg scratch3Reg = scratch3.gpr();
         
         MacroAssembler::Jump isCell = m_jit.branchIfCell(value.jsValueRegs());
         GPRReg valueReg = value.jsValueRegs().payloadGPR();
@@ -3394,7 +3411,7 @@ void SpeculativeJIT::compileInstanceOf(Node* node)
         
         isCell.link(&m_jit);
         
-        compileInstanceOfForObject(node, valueReg, prototypeReg, scratchReg, scratch2Reg);
+        compileInstanceOfForObject(node, valueReg, prototypeReg, scratchReg, scratch2Reg, scratch3Reg);
         
         done.link(&m_jit);
 
@@ -3407,13 +3424,15 @@ void SpeculativeJIT::compileInstanceOf(Node* node)
     
     GPRTemporary scratch(this);
     GPRTemporary scratch2(this);
+    GPRTemporary scratch3(this);
     
     GPRReg valueReg = value.gpr();
     GPRReg prototypeReg = prototype.gpr();
     GPRReg scratchReg = scratch.gpr();
     GPRReg scratch2Reg = scratch2.gpr();
+    GPRReg scratch3Reg = scratch3.gpr();
     
-    compileInstanceOfForObject(node, valueReg, prototypeReg, scratchReg, scratch2Reg);
+    compileInstanceOfForObject(node, valueReg, prototypeReg, scratchReg, scratch2Reg, scratch3Reg);
 
     blessedBooleanResult(scratchReg, node);
 }
index 7f81b54..3165bd0 100644 (file)
@@ -759,7 +759,7 @@ public:
     void nonSpeculativeNonPeepholeStrictEq(Node*, bool invert = false);
     bool nonSpeculativeStrictEq(Node*, bool invert = false);
     
-    void compileInstanceOfForObject(Node*, GPRReg valueReg, GPRReg prototypeReg, GPRReg scratchAndResultReg, GPRReg scratch2Reg);
+    void compileInstanceOfForObject(Node*, GPRReg valueReg, GPRReg prototypeReg, GPRReg scratchAndResultReg, GPRReg scratch2Reg, GPRReg scratch3Reg);
     void compileInstanceOf(Node*);
     void compileInstanceOfCustom(Node*);
 
index 516caa7..cf66e6a 100644 (file)
@@ -4292,7 +4292,9 @@ void SpeculativeJIT::compile(Node* node)
         slowPath.append(m_jit.branchTestPtr(MacroAssembler::Zero, rareDataGPR));
         m_jit.loadPtr(JITCompiler::Address(rareDataGPR, FunctionRareData::offsetOfObjectAllocationProfile() + ObjectAllocationProfile::offsetOfAllocator()), allocatorGPR);
         m_jit.loadPtr(JITCompiler::Address(rareDataGPR, FunctionRareData::offsetOfObjectAllocationProfile() + ObjectAllocationProfile::offsetOfStructure()), structureGPR);
+
         slowPath.append(m_jit.branchTestPtr(MacroAssembler::Zero, allocatorGPR));
+
         emitAllocateJSObject(resultGPR, nullptr, allocatorGPR, structureGPR, TrustedImmPtr(0), scratchGPR, slowPath);
         
         m_jit.loadPtr(JITCompiler::Address(calleeGPR, JSFunction::offsetOfRareData()), rareDataGPR);
index a99b5c4..be58792 100644 (file)
@@ -8953,10 +8953,12 @@ private:
         
         LBasicBlock isCellCase = m_out.newBlock();
         LBasicBlock loop = m_out.newBlock();
+        LBasicBlock loadPrototypeBits = m_out.newBlock();
+        LBasicBlock loadPolyProto = m_out.newBlock();
+        LBasicBlock comparePrototype = m_out.newBlock();
         LBasicBlock notYetInstance = m_out.newBlock();
-        LBasicBlock continuation = m_out.newBlock();
-        LBasicBlock loadPrototypeDirect = m_out.newBlock();
         LBasicBlock defaultHasInstanceSlow = m_out.newBlock();
+        LBasicBlock continuation = m_out.newBlock();
         
         LValue condition;
         if (m_node->child1().useKind() == UntypedUse)
@@ -8974,16 +8976,28 @@ private:
         ValueFromBlock originalValue = m_out.anchor(cell);
         m_out.jump(loop);
         
-        m_out.appendTo(loop, loadPrototypeDirect);
+        m_out.appendTo(loop, loadPrototypeBits);
         LValue value = m_out.phi(Int64, originalValue);
         LValue type = m_out.load8ZeroExt32(value, m_heaps.JSCell_typeInfoType);
         m_out.branch(
             m_out.notEqual(type, m_out.constInt32(ProxyObjectType)),
-            usually(loadPrototypeDirect), rarely(defaultHasInstanceSlow));
+            usually(loadPrototypeBits), rarely(defaultHasInstanceSlow));
 
-        m_out.appendTo(loadPrototypeDirect, notYetInstance);
+        m_out.appendTo(loadPrototypeBits, loadPolyProto);
         LValue structure = loadStructure(value);
-        LValue currentPrototype = m_out.load64(structure, m_heaps.Structure_prototype);
+
+        LValue prototypeBits = m_out.load64(structure, m_heaps.Structure_prototype);
+        ValueFromBlock directProtoype = m_out.anchor(prototypeBits);
+        m_out.branch(isInt32(prototypeBits), unsure(loadPolyProto), unsure(comparePrototype));
+
+        m_out.appendTo(loadPolyProto, comparePrototype);
+        LValue index = m_out.bitAnd(prototypeBits, m_out.constInt64(UINT_MAX));
+        ValueFromBlock polyProto = m_out.anchor(
+            m_out.load64(m_out.baseIndex(m_heaps.properties.atAnyNumber(), value, index, ScaleEight, JSObject::offsetOfInlineStorage())));
+        m_out.jump(comparePrototype);
+
+        m_out.appendTo(comparePrototype, notYetInstance);
+        LValue currentPrototype = m_out.phi(Int64, directProtoype, polyProto);
         ValueFromBlock isInstanceResult = m_out.anchor(m_out.booleanTrue);
         m_out.branch(
             m_out.equal(currentPrototype, prototype),
index 8b4be65..d454ef0 100644 (file)
@@ -166,8 +166,13 @@ void JIT::emit_op_instanceof(Instruction* currentInstruction)
 
     // 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, regT2, regT3);
-    load64(Address(regT2, Structure::prototypeOffset()), regT2);
+    emitLoadStructure(*vm(), regT2, regT4, regT3);
+    load64(Address(regT4, Structure::prototypeOffset()), regT4);
+    auto hasMonoProto = branchIfNotInt32(JSValueRegs(regT4));
+    zeroExtend32ToPtr(regT4, regT4);
+    load64(BaseIndex(regT2, regT4, TimesEight, JSObject::offsetOfInlineStorage()), regT4);
+    hasMonoProto.link(this);
+    move(regT4, regT2);
     Jump isInstance = branchPtr(Equal, regT2, regT1);
     emitJumpIfJSCell(regT2).linkTo(loop, this);
 
index 0ad7828..b2f60c4 100644 (file)
@@ -248,8 +248,13 @@ void JIT::emit_op_instanceof(Instruction* currentInstruction)
 
     // Load the prototype of the cell 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.
-    loadPtr(Address(regT2, JSCell::structureIDOffset()), regT2);
-    load32(Address(regT2, Structure::prototypeOffset() + OBJECT_OFFSETOF(JSValue, u.asBits.payload)), regT2);
+    loadPtr(Address(regT2, JSCell::structureIDOffset()), regT4);
+    load32(Address(regT4, Structure::prototypeOffset() + TagOffset), regT3);
+    load32(Address(regT4, Structure::prototypeOffset() + PayloadOffset), regT4);
+    auto isMonoProto = branch32(NotEqual, regT3, TrustedImm32(JSValue::Int32Tag));
+    load32(BaseIndex(regT2, regT4, TimesEight, JSObject::offsetOfInlineStorage() + PayloadOffset), regT4);
+    isMonoProto.link(this);
+    move(regT4, regT2);
     Jump isInstance = branchPtr(Equal, regT2, regT1);
     branchTest32(NonZero, regT2).linkTo(loop, this);
 
index 19b7f18..cbfc049 100644 (file)
@@ -244,6 +244,8 @@ static InlineCacheAction tryCacheGetByID(const GCSafeConcurrentJSLocker& locker,
             }
         }
 
+        std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain;
+
         PropertyOffset offset = slot.isUnset() ? invalidOffset : slot.cachedOffset();
 
         if (slot.isUnset() || slot.slotBase() != baseValue) {
@@ -255,23 +257,35 @@ static InlineCacheAction tryCacheGetByID(const GCSafeConcurrentJSLocker& locker,
                     return GiveUpOnCache;
                 structure->flattenDictionaryStructure(vm, jsCast<JSObject*>(baseCell));
             }
-            
+
             if (slot.isUnset() && structure->typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence())
                 return GiveUpOnCache;
 
-            if (slot.isUnset()) {
-                conditionSet = generateConditionsForPropertyMiss(
-                    vm, codeBlock, exec, structure, propertyName.impl());
-            } else {
-                conditionSet = generateConditionsForPrototypePropertyHit(
-                    vm, codeBlock, exec, structure, slot.slotBase(),
-                    propertyName.impl());
-            }
-            
-            if (!conditionSet.isValid())
+            bool usesPolyProto;
+            prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, slot, usesPolyProto);
+            if (!prototypeAccessChain) {
+                // It's invalid to access this prototype property.
                 return GiveUpOnCache;
+            }
 
-            offset = slot.isUnset() ? invalidOffset : conditionSet.slotBaseCondition().offset();
+            if (!usesPolyProto) {
+                // We use ObjectPropertyConditionSet instead for faster accesses.
+                prototypeAccessChain = nullptr;
+
+                if (slot.isUnset()) {
+                    conditionSet = generateConditionsForPropertyMiss(
+                        vm, codeBlock, exec, structure, propertyName.impl());
+                } else {
+                    conditionSet = generateConditionsForPrototypePropertyHit(
+                        vm, codeBlock, exec, structure, slot.slotBase(),
+                        propertyName.impl());
+                }
+
+                if (!conditionSet.isValid())
+                    return GiveUpOnCache;
+            }
+
+            offset = slot.isUnset() ? invalidOffset : slot.cachedOffset();
         }
 
         JSFunction* getter = nullptr;
@@ -293,13 +307,17 @@ static InlineCacheAction tryCacheGetByID(const GCSafeConcurrentJSLocker& locker,
             else
                 RELEASE_ASSERT_NOT_REACHED();
 
-            newCase = ProxyableAccessCase::create(vm, codeBlock, type, offset, structure, conditionSet, loadTargetFromProxy, slot.watchpointSet());
-        } else if (!loadTargetFromProxy && getter && IntrinsicGetterAccessCase::canEmitIntrinsicGetter(getter, structure))
+            newCase = ProxyableAccessCase::create(vm, codeBlock, type, offset, structure, conditionSet, loadTargetFromProxy, slot.watchpointSet(), WTFMove(prototypeAccessChain));
+        } else if (!loadTargetFromProxy && getter && IntrinsicGetterAccessCase::canEmitIntrinsicGetter(getter, structure) && !prototypeAccessChain) {
+            // FIXME: We should make this work with poly proto, but for our own sanity, we probably
+            // want to do a pointer check on the actual getter. A good time to make this work would
+            // be when we can inherit from builtin types in poly proto fashion:
+            // https://bugs.webkit.org/show_bug.cgi?id=177318
             newCase = IntrinsicGetterAccessCase::create(vm, codeBlock, slot.cachedOffset(), structure, conditionSet, getter);
-        else {
+        else {
             if (slot.isCacheableValue() || slot.isUnset()) {
                 newCase = ProxyableAccessCase::create(vm, codeBlock, slot.isUnset() ? AccessCase::Miss : AccessCase::Load,
-                    offset, structure, conditionSet, loadTargetFromProxy, slot.watchpointSet());
+                    offset, structure, conditionSet, loadTargetFromProxy, slot.watchpointSet(), WTFMove(prototypeAccessChain));
             } else {
                 AccessCase::AccessType type;
                 if (slot.isCacheableGetter())
@@ -316,7 +334,7 @@ static InlineCacheAction tryCacheGetByID(const GCSafeConcurrentJSLocker& locker,
                     vm, codeBlock, type, offset, structure, conditionSet, loadTargetFromProxy,
                     slot.watchpointSet(), slot.isCacheableCustom() ? slot.customGetter() : nullptr,
                     slot.isCacheableCustom() ? slot.slotBase() : nullptr,
-                    domAttribute);
+                    domAttribute, WTFMove(prototypeAccessChain));
             }
         }
     }
@@ -386,6 +404,7 @@ static InlineCacheAction tryCachePutByID(const GCSafeConcurrentJSLocker& locker,
         return GiveUpOnCache;
 
     std::unique_ptr<AccessCase> newCase;
+    JSCell* baseCell = baseValue.asCell();
 
     if (slot.base() == baseValue && slot.isCacheablePut()) {
         if (slot.type() == PutPropertySlot::ExistingProperty) {
@@ -429,48 +448,83 @@ static InlineCacheAction tryCachePutByID(const GCSafeConcurrentJSLocker& locker,
             ASSERT(!newStructure->isDictionary());
             ASSERT(newStructure->isObject());
             
+            std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain;
             ObjectPropertyConditionSet conditionSet;
             if (putKind == NotDirect) {
-                conditionSet =
-                    generateConditionsForPropertySetterMiss(
-                        vm, codeBlock, exec, newStructure, ident.impl());
-                if (!conditionSet.isValid())
+                bool usesPolyProto;
+                prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, nullptr, usesPolyProto);
+                if (!prototypeAccessChain) {
+                    // It's invalid to access this prototype property.
                     return GiveUpOnCache;
+                }
+
+                if (!usesPolyProto) {
+                    prototypeAccessChain = nullptr;
+                    conditionSet =
+                        generateConditionsForPropertySetterMiss(
+                            vm, codeBlock, exec, newStructure, ident.impl());
+                    if (!conditionSet.isValid())
+                        return GiveUpOnCache;
+                }
+
             }
 
-            newCase = AccessCase::create(vm, codeBlock, offset, structure, newStructure, conditionSet);
+            newCase = AccessCase::create(vm, codeBlock, offset, structure, newStructure, conditionSet, WTFMove(prototypeAccessChain));
         }
     } else if (slot.isCacheableCustom() || slot.isCacheableSetter()) {
         if (slot.isCacheableCustom()) {
             ObjectPropertyConditionSet conditionSet;
+            std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain;
 
             if (slot.base() != baseValue) {
-                conditionSet =
-                    generateConditionsForPrototypePropertyHit(
-                        vm, codeBlock, exec, structure, slot.base(), ident.impl());
-                if (!conditionSet.isValid())
+                bool usesPolyProto;
+                prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, slot.base(), usesPolyProto);
+                if (!prototypeAccessChain) {
+                    // It's invalid to access this prototype property.
                     return GiveUpOnCache;
+                }
+
+                if (!usesPolyProto) {
+                    prototypeAccessChain = nullptr;
+                    conditionSet =
+                        generateConditionsForPrototypePropertyHit(
+                            vm, codeBlock, exec, structure, slot.base(), ident.impl());
+                    if (!conditionSet.isValid())
+                        return GiveUpOnCache;
+                }
             }
 
             newCase = GetterSetterAccessCase::create(
-                vm, codeBlock, slot.isCustomAccessor() ? AccessCase::CustomAccessorSetter : AccessCase::CustomValueSetter, structure, invalidOffset, conditionSet,
-                slot.customSetter(), slot.base());
+                vm, codeBlock, slot.isCustomAccessor() ? AccessCase::CustomAccessorSetter : AccessCase::CustomValueSetter, structure, invalidOffset,
+                conditionSet, WTFMove(prototypeAccessChain), slot.customSetter(), slot.base());
         } else {
             ObjectPropertyConditionSet conditionSet;
-            PropertyOffset offset;
+            std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain;
+            PropertyOffset offset = slot.cachedOffset();
 
             if (slot.base() != baseValue) {
-                conditionSet =
-                    generateConditionsForPrototypePropertyHit(
-                        vm, codeBlock, exec, structure, slot.base(), ident.impl());
-                if (!conditionSet.isValid())
+                bool usesPolyProto;
+                prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, slot.base(), usesPolyProto);
+                if (!prototypeAccessChain) {
+                    // It's invalid to access this prototype property.
                     return GiveUpOnCache;
-                offset = conditionSet.slotBaseCondition().offset();
-            } else
-                offset = slot.cachedOffset();
+                }
+
+                if (!usesPolyProto) {
+                    prototypeAccessChain = nullptr;
+                    conditionSet =
+                        generateConditionsForPrototypePropertyHit(
+                            vm, codeBlock, exec, structure, slot.base(), ident.impl());
+                    if (!conditionSet.isValid())
+                        return GiveUpOnCache;
+
+                    RELEASE_ASSERT(offset == conditionSet.slotBaseCondition().offset());
+                }
+
+            }
 
             newCase = GetterSetterAccessCase::create(
-                vm, codeBlock, AccessCase::Setter, structure, offset, conditionSet);
+                vm, codeBlock, AccessCase::Setter, structure, offset, conditionSet, WTFMove(prototypeAccessChain));
         }
     }
 
@@ -517,15 +571,35 @@ static InlineCacheAction tryRepatchIn(
     VM& vm = exec->vm();
     Structure* structure = base->structure(vm);
     
+    std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain;
     ObjectPropertyConditionSet conditionSet;
     if (wasFound) {
         if (slot.slotBase() != base) {
-            conditionSet = generateConditionsForPrototypePropertyHit(
-                vm, codeBlock, exec, structure, slot.slotBase(), ident.impl());
+            bool usesPolyProto;
+            prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), base, slot, usesPolyProto);
+            if (!prototypeAccessChain) {
+                // It's invalid to access this prototype property.
+                return GiveUpOnCache;
+            }
+            if (!usesPolyProto) {
+                prototypeAccessChain = nullptr;
+                conditionSet = generateConditionsForPrototypePropertyHit(
+                    vm, codeBlock, exec, structure, slot.slotBase(), ident.impl());
+            }
         }
     } else {
-        conditionSet = generateConditionsForPropertyMiss(
-            vm, codeBlock, exec, structure, ident.impl());
+        bool usesPolyProto;
+        prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), base, slot, usesPolyProto);
+        if (!prototypeAccessChain) {
+            // It's invalid to access this prototype property.
+            return GiveUpOnCache;
+        }
+
+        if (!usesPolyProto) {
+            prototypeAccessChain = nullptr;
+            conditionSet = generateConditionsForPropertyMiss(
+                vm, codeBlock, exec, structure, ident.impl());
+        }
     }
     if (!conditionSet.isValid())
         return GiveUpOnCache;
@@ -533,7 +607,7 @@ static InlineCacheAction tryRepatchIn(
     LOG_IC((ICEvent::InAddAccessCase, structure->classInfo(), ident));
 
     std::unique_ptr<AccessCase> newCase = AccessCase::create(
-        vm, codeBlock, wasFound ? AccessCase::InHit : AccessCase::InMiss, invalidOffset, structure, conditionSet);
+        vm, codeBlock, wasFound ? AccessCase::InHit : AccessCase::InMiss, invalidOffset, structure, conditionSet, WTFMove(prototypeAccessChain));
 
     AccessGenerationResult result = stubInfo.addAccessCase(locker, codeBlock, ident, WTFMove(newCase));
     
index 5277583..8b934c9 100644 (file)
@@ -953,6 +953,90 @@ void DOMJITCheckSubClassObject::finishCreation(VM& vm, JSGlobalObject* globalObj
     putDirectNativeFunction(vm, globalObject, Identifier::fromString(&vm, "func"), 0, safeFunction, NoIntrinsic, &DOMJITCheckSubClassObjectSignature, static_cast<unsigned>(PropertyAttribute::ReadOnly));
 }
 
+class DOMJITGetterBaseJSObject : public DOMJITNode {
+public:
+    DOMJITGetterBaseJSObject(VM& vm, Structure* structure)
+        : Base(vm, structure)
+    {
+    }
+
+    DECLARE_INFO;
+    using Base = DOMJITNode;
+    static const unsigned StructureFlags = Base::StructureFlags;
+
+    static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
+    {
+        return Structure::create(vm, globalObject, prototype, TypeInfo(JSC::JSType(LastJSCObjectType + 1), StructureFlags), info());
+    }
+
+    static DOMJITGetterBaseJSObject* create(VM& vm, Structure* structure)
+    {
+        DOMJITGetterBaseJSObject* getter = new (NotNull, allocateCell<DOMJITGetterBaseJSObject>(vm.heap, sizeof(DOMJITGetterBaseJSObject))) DOMJITGetterBaseJSObject(vm, structure);
+        getter->finishCreation(vm);
+        return getter;
+    }
+
+    class DOMJITAttribute : public DOMJIT::GetterSetter {
+    public:
+        constexpr DOMJITAttribute()
+            : DOMJIT::GetterSetter(
+                DOMJITGetterBaseJSObject::customGetter,
+#if ENABLE(JIT)
+                &callDOMGetter,
+#else
+                nullptr,
+#endif
+                SpecBytecodeTop)
+        {
+        }
+
+#if ENABLE(JIT)
+        static EncodedJSValue JIT_OPERATION slowCall(ExecState* exec, void* pointer)
+        {
+            VM& vm = exec->vm();
+            NativeCallFrameTracer tracer(&vm, exec);
+            JSObject* object = static_cast<JSObject*>(pointer);
+            return JSValue::encode(object->getPrototypeDirect());
+        }
+
+        static Ref<DOMJIT::CallDOMGetterSnippet> callDOMGetter()
+        {
+            Ref<DOMJIT::CallDOMGetterSnippet> snippet = DOMJIT::CallDOMGetterSnippet::create();
+            snippet->requireGlobalObject = false;
+            snippet->setGenerator([=](CCallHelpers& jit, SnippetParams& params) {
+                JSValueRegs results = params[0].jsValueRegs();
+                GPRReg dom = params[1].gpr();
+                params.addSlowPathCall(jit.jump(), jit, slowCall, results, dom);
+                return CCallHelpers::JumpList();
+
+            });
+            return snippet;
+        }
+#endif
+    };
+
+private:
+    void finishCreation(VM&);
+
+    static EncodedJSValue customGetter(ExecState* exec, EncodedJSValue thisValue, PropertyName)
+    {
+        VM& vm = exec->vm();
+        JSObject* thisObject = jsDynamicCast<JSObject*>(vm, JSValue::decode(thisValue));
+        RELEASE_ASSERT(thisObject);
+        return JSValue::encode(thisObject->getPrototypeDirect());
+    }
+};
+
+static const DOMJITGetterBaseJSObject::DOMJITAttribute DOMJITGetterBaseJSObjectDOMJIT;
+
+void DOMJITGetterBaseJSObject::finishCreation(VM& vm)
+{
+    Base::finishCreation(vm);
+    const DOMJIT::GetterSetter* domJIT = &DOMJITGetterBaseJSObjectDOMJIT;
+    auto* customGetterSetter = DOMAttributeGetterSetter::create(vm, domJIT->getter(), nullptr, DOMAttributeAnnotation { JSObject::info(), domJIT });
+    putDirectCustomAccessor(vm, Identifier::fromString(&vm, "customGetter"), customGetterSetter, PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor);
+}
+
 const ClassInfo Element::s_info = { "Element", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(Element) };
 const ClassInfo Masquerader::s_info = { "Masquerader", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(Masquerader) };
 const ClassInfo Root::s_info = { "Root", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(Root) };
@@ -965,6 +1049,7 @@ const ClassInfo DOMJITNode::s_info = { "DOMJITNode", &Base::s_info, nullptr, nul
 #endif
 const ClassInfo DOMJITGetter::s_info = { "DOMJITGetter", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DOMJITGetter) };
 const ClassInfo DOMJITGetterComplex::s_info = { "DOMJITGetterComplex", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DOMJITGetterComplex) };
+const ClassInfo DOMJITGetterBaseJSObject::s_info = { "DOMJITGetterBaseJSObject", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DOMJITGetterBaseJSObject) };
 #if ENABLE(JIT)
 const ClassInfo DOMJITFunctionObject::s_info = { "DOMJITFunctionObject", &Base::s_info, nullptr, &DOMJITFunctionObject::checkSubClassSnippet, CREATE_METHOD_TABLE(DOMJITFunctionObject) };
 #else
@@ -1146,6 +1231,7 @@ static EncodedJSValue JSC_HOST_CALL functionCreateDOMJITGetterObject(ExecState*)
 static EncodedJSValue JSC_HOST_CALL functionCreateDOMJITGetterComplexObject(ExecState*);
 static EncodedJSValue JSC_HOST_CALL functionCreateDOMJITFunctionObject(ExecState*);
 static EncodedJSValue JSC_HOST_CALL functionCreateDOMJITCheckSubClassObject(ExecState*);
+static EncodedJSValue JSC_HOST_CALL functionCreateDOMJITGetterBaseJSObject(ExecState*);
 static EncodedJSValue JSC_HOST_CALL functionCreateBuiltin(ExecState*);
 static EncodedJSValue JSC_HOST_CALL functionCreateGlobalObject(ExecState*);
 static EncodedJSValue JSC_HOST_CALL functionSetImpureGetterDelegate(ExecState*);
@@ -1464,6 +1550,7 @@ protected:
         addFunction(vm, "createDOMJITGetterComplexObject", functionCreateDOMJITGetterComplexObject, 0);
         addFunction(vm, "createDOMJITFunctionObject", functionCreateDOMJITFunctionObject, 0);
         addFunction(vm, "createDOMJITCheckSubClassObject", functionCreateDOMJITCheckSubClassObject, 0);
+        addFunction(vm, "createDOMJITGetterBaseJSObject", functionCreateDOMJITGetterBaseJSObject, 0);
         addFunction(vm, "createBuiltin", functionCreateBuiltin, 2);
         addFunction(vm, "createGlobalObject", functionCreateGlobalObject, 0);
         addFunction(vm, "setImpureGetterDelegate", functionSetImpureGetterDelegate, 2);
@@ -2200,6 +2287,16 @@ EncodedJSValue JSC_HOST_CALL functionCreateDOMJITCheckSubClassObject(ExecState*
     return JSValue::encode(result);
 }
 
+EncodedJSValue JSC_HOST_CALL functionCreateDOMJITGetterBaseJSObject(ExecState* exec)
+{
+    VM& vm = exec->vm();
+    JSLockHolder lock(vm);
+    Structure* structure = DOMJITGetterBaseJSObject::createStructure(vm, exec->lexicalGlobalObject(), jsNull());
+    DOMJITGetterBaseJSObject* result = DOMJITGetterBaseJSObject::create(vm, structure);
+    return JSValue::encode(result);
+}
+
+
 EncodedJSValue JSC_HOST_CALL functionSetImpureGetterDelegate(ExecState* exec)
 {
     VM& vm = exec->vm();
index 2315c82..8a0e1ac 100644 (file)
@@ -667,6 +667,24 @@ LLINT_SLOW_PATH_DECL(slow_path_get_by_id)
         && baseValue.isCell()
         && slot.isCacheable()) {
 
+        {
+            StructureID oldStructureID = pc[4].u.structureID;
+            if (oldStructureID) {
+                auto opcode = Interpreter::getOpcodeID(pc[0]);
+                if (opcode == op_get_by_id
+                    || opcode == op_get_by_id_unset
+                    || opcode == op_get_by_id_proto_load) {
+                    Structure* a = vm.heap.structureIDTable().get(oldStructureID);
+                    Structure* b = baseValue.asCell()->structure(vm);
+
+                    if (Structure::shouldConvertToPolyProto(a, b)) {
+                        ASSERT(a->rareData()->sharedPolyProtoWatchpoint().get() == b->rareData()->sharedPolyProtoWatchpoint().get());
+                        a->rareData()->sharedPolyProtoWatchpoint()->invalidate(vm, StringFireDetail("Detected poly proto opportunity."));
+                    }
+                }
+            }
+        }
+
         JSCell* baseCell = baseValue.asCell();
         Structure* structure = baseCell->structure();
         if (slot.isValue() && slot.slotBase() == baseValue) {
@@ -737,6 +755,22 @@ LLINT_SLOW_PATH_DECL(slow_path_put_by_id)
         && baseValue.isCell()
         && slot.isCacheablePut()) {
 
+
+        {
+            StructureID oldStructureID = pc[4].u.structureID;
+            if (oldStructureID) {
+                Structure* a = vm.heap.structureIDTable().get(oldStructureID);
+                Structure* b = baseValue.asCell()->structure(vm);
+                if (slot.type() == PutPropertySlot::NewProperty)
+                    b = b->previousID();
+
+                if (Structure::shouldConvertToPolyProto(a, b)) {
+                    a->rareData()->sharedPolyProtoWatchpoint()->invalidate(vm, StringFireDetail("Detected poly proto opportunity."));
+                    b->rareData()->sharedPolyProtoWatchpoint()->invalidate(vm, StringFireDetail("Detected poly proto opportunity."));
+                }
+            }
+        }
+
         // Start out by clearing out the old cache.
         pc[4].u.pointer = nullptr; // old structure
         pc[5].u.pointer = nullptr; // offset
@@ -760,16 +794,17 @@ LLINT_SLOW_PATH_DECL(slow_path_put_by_id)
                 if (!structure->isDictionary() && structure->previousID()->outOfLineCapacity() == structure->outOfLineCapacity()) {
                     ASSERT(structure->previousID()->transitionWatchpointSetHasBeenInvalidated());
 
-                    if (normalizePrototypeChain(exec, structure) != InvalidPrototypeChain) {
+                    bool sawPolyProto = false;
+                    auto result = normalizePrototypeChain(exec, baseCell, sawPolyProto);
+                    if (result != InvalidPrototypeChain && !sawPolyProto) {
                         ASSERT(structure->previousID()->isObject());
                         pc[4].u.structureID = structure->previousID()->id();
                         pc[5].u.operand = slot.cachedOffset();
                         pc[6].u.structureID = structure->id();
                         if (!(pc[8].u.putByIdFlags & PutByIdIsDirect)) {
-                            StructureChain* chain = structure->prototypeChain(exec);
+                            StructureChain* chain = structure->prototypeChain(exec, asObject(baseCell));
                             ASSERT(chain);
-                            pc[7].u.structureChain.set(
-                                vm, codeBlock, chain);
+                            pc[7].u.structureChain.set(vm, codeBlock, chain);
                         }
                         pc[8].u.putByIdFlags = static_cast<PutByIdFlags>(
                             pc[8].u.putByIdFlags |
index 66c96e4..58d1fdc 100644 (file)
@@ -522,10 +522,9 @@ template<typename T> static inline bool containsHole(T* data, unsigned length)
     return false;
 }
 
-static inline bool holesMustForwardToPrototype(ExecState& state, JSObject* object)
+static inline bool holesMustForwardToPrototype(VM& vm, JSObject* object)
 {
-    auto& vm = state.vm();
-    return object->structure(vm)->holesMustForwardToPrototype(vm);
+    return object->structure(vm)->holesMustForwardToPrototype(vm, object);
 }
 
 static JSValue slowJoin(ExecState& exec, JSObject* thisObject, JSString* separator, uint64_t length)
@@ -610,7 +609,7 @@ static inline JSValue fastJoin(ExecState& state, JSObject* thisObject, StringVie
                     goto generalCase;
             } else {
                 if (!holesKnownToBeOK) {
-                    if (holesMustForwardToPrototype(state, thisObject))
+                    if (holesMustForwardToPrototype(vm, thisObject))
                         goto generalCase;
                     holesKnownToBeOK = true;
                 }
@@ -634,7 +633,7 @@ static inline JSValue fastJoin(ExecState& state, JSObject* thisObject, StringVie
                 joiner.append(state, jsDoubleNumber(value));
             else {
                 if (!holesKnownToBeOK) {
-                    if (thisObject->structure(vm)->holesMustForwardToPrototype(vm))
+                    if (holesMustForwardToPrototype(vm, thisObject))
                         goto generalCase;
                     holesKnownToBeOK = true;
                 }
@@ -816,7 +815,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
         if (length > butterfly.publicLength())
             break;
         auto data = butterfly.contiguous().data();
-        if (containsHole(data, length) && holesMustForwardToPrototype(*exec, thisObject))
+        if (containsHole(data, length) && holesMustForwardToPrototype(vm, thisObject))
             break;
         std::reverse(data, data + length);
         return JSValue::encode(thisObject);
@@ -826,7 +825,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
         if (length > butterfly.publicLength())
             break;
         auto data = butterfly.contiguousDouble().data();
-        if (containsHole(data, length) && holesMustForwardToPrototype(*exec, thisObject))
+        if (containsHole(data, length) && holesMustForwardToPrototype(vm, thisObject))
             break;
         std::reverse(data, data + length);
         return JSValue::encode(thisObject);
@@ -835,7 +834,7 @@ EncodedJSValue JSC_HOST_CALL arrayProtoFuncReverse(ExecState* exec)
         auto& storage = *thisObject->butterfly()->arrayStorage();
         if (length > storage.vectorLength())
             break;
-        if (storage.hasHoles() && holesMustForwardToPrototype(*exec, thisObject))
+        if (storage.hasHoles() && holesMustForwardToPrototype(vm, thisObject))
             break;
         auto data = storage.vector().data();
         std::reverse(data, data + length);
@@ -1176,7 +1175,7 @@ static bool moveElements(ExecState* exec, VM& vm, JSArray* target, unsigned targ
 {
     auto scope = DECLARE_THROW_SCOPE(vm);
 
-    if (LIKELY(!hasAnyArrayStorage(source->indexingType()) && !source->structure()->holesMustForwardToPrototype(vm))) {
+    if (LIKELY(!hasAnyArrayStorage(source->indexingType()) && !holesMustForwardToPrototype(vm, source))) {
         for (unsigned i = 0; i < sourceLength; ++i) {
             JSValue value = source->tryGetIndexQuickly(i);
             if (value) {
index ce97a51..89c2644 100644 (file)
@@ -49,10 +49,9 @@ ClonedArguments* ClonedArguments::createEmpty(
         return 0;
 
     Butterfly* butterfly;
-    if (UNLIKELY(structure->needsSlowPutIndexing())) {
+    if (UNLIKELY(structure->mayInterceptIndexedAccesses() || structure->storedPrototypeObject()->needsSlowPutIndexing())) {
         butterfly = createArrayStorageButterfly(vm, nullptr, structure, length, vectorLength);
         butterfly->arrayStorage()->m_numValuesInVector = vectorLength;
-
     } else {
         IndexingHeader indexingHeader;
         indexingHeader.setVectorLength(vectorLength);
@@ -81,7 +80,7 @@ ClonedArguments* ClonedArguments::createEmpty(ExecState* exec, JSFunction* calle
     // NB. Some clients might expect that the global object of of this object is the global object
     // of the callee. We don't do this for now, but maybe we should.
     ClonedArguments* result = createEmpty(vm, exec->lexicalGlobalObject()->clonedArgumentsStructure(), callee, length);
-    ASSERT(!result->structure(vm)->needsSlowPutIndexing() || shouldUseSlowPut(result->structure(vm)->indexingType()));
+    ASSERT(!result->needsSlowPutIndexing() || shouldUseSlowPut(result->structure(vm)->indexingType()));
     return result;
 }
 
@@ -125,14 +124,14 @@ ClonedArguments* ClonedArguments::createWithInlineFrame(ExecState* myFrame, Exec
     } }
 
     ASSERT(myFrame->lexicalGlobalObject()->clonedArgumentsStructure() == result->structure());
-    ASSERT(!result->structure()->needsSlowPutIndexing() || shouldUseSlowPut(result->structure()->indexingType()));
+    ASSERT(!result->needsSlowPutIndexing() || shouldUseSlowPut(result->structure()->indexingType()));
     return result;
 }
 
 ClonedArguments* ClonedArguments::createWithMachineFrame(ExecState* myFrame, ExecState* targetFrame, ArgumentsMode mode)
 {
     ClonedArguments* result = createWithInlineFrame(myFrame, targetFrame, nullptr, mode);
-    ASSERT(!result->structure()->needsSlowPutIndexing() || shouldUseSlowPut(result->structure()->indexingType()));
+    ASSERT(!result->needsSlowPutIndexing() || shouldUseSlowPut(result->structure()->indexingType()));
     return result;
 }
 
@@ -145,7 +144,7 @@ ClonedArguments* ClonedArguments::createByCopyingFrom(
     
     for (unsigned i = length; i--;)
         result->putDirectIndex(exec, i, argumentStart[i].jsValue());
-    ASSERT(!result->structure(vm)->needsSlowPutIndexing() || shouldUseSlowPut(result->structure(vm)->indexingType()));
+    ASSERT(!result->needsSlowPutIndexing() || shouldUseSlowPut(result->structure(vm)->indexingType()));
     return result;
 }
 
index 1375c9d..1fb0f42 100644 (file)
@@ -242,6 +242,8 @@ SLOW_PATH_DECL(slow_path_create_this)
         size_t inlineCapacity = pc[3].u.operand;
         Structure* structure = constructor->rareData(exec, inlineCapacity)->objectAllocationProfile()->structure();
         result = constructEmptyObject(exec, structure);
+        if (structure->hasPolyProto())
+            result->putDirect(vm, structure->polyProtoOffset(), constructor->prototypeForConstruction(vm, exec));
     } else {
         // http://ecma-international.org/ecma-262/6.0/#sec-ordinarycreatefromconstructor
         JSValue proto = constructorAsObject->get(exec, vm.propertyNames->prototype);
index 9895e58..f158672 100644 (file)
@@ -91,6 +91,7 @@ void FunctionExecutable::visitChildren(JSCell* cell, SlotVisitor& visitor)
         codeBlockForConstruct->visitWeakly(visitor);
     visitor.append(thisObject->m_unlinkedExecutable);
     visitor.append(thisObject->m_singletonFunction);
+    visitor.append(thisObject->m_cachedPolyProtoStructure);
 }
 
 FunctionExecutable* FunctionExecutable::fromGlobalCode(
index ef9e018..3f6686c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009, 2010, 2013-2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2009-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -27,6 +27,7 @@
 
 #include "ScriptExecutable.h"
 #include "SourceCode.h"
+#include <wtf/Box.h>
 
 namespace JSC {
 
@@ -172,6 +173,18 @@ public:
     DECLARE_INFO;
 
     InferredValue* singletonFunction() { return m_singletonFunction.get(); }
+    // Cached poly proto structure for the result of constructing this executable.
+    Structure* cachedPolyProtoStructure() { return m_cachedPolyProtoStructure.get(); }
+    void setCachedPolyProtoStructure(VM& vm, Structure* structure) { m_cachedPolyProtoStructure.set(vm, this, structure); }
+
+    InlineWatchpointSet& ensurePolyProtoWatchpoint()
+    {
+        if (!m_polyProtoWatchpoint)
+            m_polyProtoWatchpoint = Box<InlineWatchpointSet>::create(IsWatched);
+        return *m_polyProtoWatchpoint;
+    }
+
+    Box<InlineWatchpointSet> sharedPolyProtoWatchpoint() const { return m_polyProtoWatchpoint; }
 
 private:
     friend class ExecutableBase;
@@ -189,6 +202,8 @@ private:
     WriteBarrier<FunctionCodeBlock> m_codeBlockForConstruct;
     RefPtr<TypeSet> m_returnStatementTypeSet;
     WriteBarrier<InferredValue> m_singletonFunction;
+    WriteBarrier<Structure> m_cachedPolyProtoStructure;
+    Box<InlineWatchpointSet> m_polyProtoWatchpoint;
 };
 
 } // namespace JSC
index cacefa0..a137c27 100644 (file)
@@ -79,9 +79,9 @@ FunctionRareData::~FunctionRareData()
 {
 }
 
-void FunctionRareData::initializeObjectAllocationProfile(VM& vm, JSGlobalObject* globalObject, JSObject* prototype, size_t inlineCapacity)
+void FunctionRareData::initializeObjectAllocationProfile(VM& vm, JSGlobalObject* globalObject, JSObject* prototype, size_t inlineCapacity, JSFunction* constructor)
 {
-    m_objectAllocationProfile.initialize(vm, globalObject, this, prototype, inlineCapacity);
+    m_objectAllocationProfile.initializeProfile(vm, globalObject, this, prototype, inlineCapacity, constructor);
 }
 
 void FunctionRareData::clear(const char* reason)
index 0dd577e..3a4a5a0 100644 (file)
@@ -79,7 +79,7 @@ public:
 
     void clear(const char* reason);
 
-    void initializeObjectAllocationProfile(VM&, JSGlobalObject*, JSObject* prototype, size_t inlineCapacity);
+    void initializeObjectAllocationProfile(VM&, JSGlobalObject*, JSObject* prototype, size_t inlineCapacity, JSFunction* constructor);
 
     bool isObjectAllocationProfileInitialized() { return !m_objectAllocationProfile.isNull(); }
 
index 121a6ba..f7e2e0a 100644 (file)
@@ -96,12 +96,13 @@ const String InternalFunction::calculatedDisplayName(VM& vm)
 
 Structure* InternalFunction::createSubclassStructureSlow(ExecState* exec, JSValue newTarget, Structure* baseClass)
 {
-
     VM& vm = exec->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
     ASSERT(!newTarget || newTarget.isConstructor());
     ASSERT(newTarget && newTarget != exec->jsCallee());
 
+    ASSERT(baseClass->hasMonoProto());
+
     // newTarget may be an InternalFunction if we were called from Reflect.construct.
     JSFunction* targetFunction = jsDynamicCast<JSFunction*>(vm, newTarget);
     JSGlobalObject* lexicalGlobalObject = exec->lexicalGlobalObject();
index 62ac604..76e3705 100644 (file)
@@ -738,7 +738,7 @@ JSArray* JSArray::fastSlice(ExecState& exec, unsigned startIndex, unsigned count
     case ArrayWithInt32:
     case ArrayWithContiguous: {
         VM& vm = exec.vm();
-        if (count >= MIN_SPARSE_ARRAY_INDEX || structure(vm)->holesMustForwardToPrototype(vm))
+        if (count >= MIN_SPARSE_ARRAY_INDEX || structure(vm)->holesMustForwardToPrototype(vm, this))
             return nullptr;
 
         JSGlobalObject* lexicalGlobalObject = exec.lexicalGlobalObject();
@@ -773,7 +773,7 @@ bool JSArray::shiftCountWithArrayStorage(VM& vm, unsigned startIndex, unsigned c
     
     // If the array contains holes or is otherwise in an abnormal state,
     // use the generic algorithm in ArrayPrototype.
-    if ((storage->hasHoles() && this->structure(vm)->holesMustForwardToPrototype(vm)) 
+    if ((storage->hasHoles() && this->structure(vm)->holesMustForwardToPrototype(vm, this)) 
         || hasSparseMap() 
         || shouldUseSlowPut(indexingType())) {
         return false;
@@ -904,7 +904,7 @@ bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned& startInde
         // We have to check for holes before we start moving things around so that we don't get halfway 
         // through shifting and then realize we should have been in ArrayStorage mode.
         unsigned end = oldLength - count;
-        if (this->structure(vm)->holesMustForwardToPrototype(vm)) {
+        if (this->structure(vm)->holesMustForwardToPrototype(vm, this)) {
             for (unsigned i = startIndex; i < end; ++i) {
                 JSValue v = butterfly->contiguous()[i + count].get();
                 if (UNLIKELY(!v)) {
@@ -945,7 +945,7 @@ bool JSArray::shiftCountWithAnyIndexingType(ExecState* exec, unsigned& startInde
         // We have to check for holes before we start moving things around so that we don't get halfway 
         // through shifting and then realize we should have been in ArrayStorage mode.
         unsigned end = oldLength - count;
-        if (this->structure(vm)->holesMustForwardToPrototype(vm)) {
+        if (this->structure(vm)->holesMustForwardToPrototype(vm, this)) {
             for (unsigned i = startIndex; i < end; ++i) {
                 double v = butterfly->contiguousDouble()[i + count];
                 if (UNLIKELY(v != v)) {
@@ -1292,7 +1292,7 @@ bool JSArray::isIteratorProtocolFastAndNonObservable()
     if (structure->mayInterceptIndexedAccesses())
         return false;
 
-    if (structure->storedPrototype() != globalObject->arrayPrototype())
+    if (getPrototypeDirect() != globalObject->arrayPrototype())
         return false;
 
     VM& vm = globalObject->vm();
index 76fe0f5..6cc6843 100644 (file)
@@ -64,8 +64,8 @@ inline bool JSArray::canFastCopy(VM& vm, JSArray* otherArray)
         return false;
     // FIXME: We should have a watchpoint for indexed properties on Array.prototype and Object.prototype
     // instead of walking the prototype chain. https://bugs.webkit.org/show_bug.cgi?id=155592
-    if (structure(vm)->holesMustForwardToPrototype(vm)
-        || otherArray->structure(vm)->holesMustForwardToPrototype(vm))
+    if (structure(vm)->holesMustForwardToPrototype(vm, this)
+        || otherArray->structure(vm)->holesMustForwardToPrototype(vm, otherArray))
         return false;
     return true;
 }
index c83599f..d030452 100644 (file)
@@ -277,13 +277,13 @@ void JSValue::dumpInContextAssumingStructure(
         else if (structure->classInfo()->isSubClassOf(JSObject::info())) {
             out.print("Object: ", RawPointer(asCell()));
             out.print(" with butterfly ", RawPointer(asObject(asCell())->butterfly()));
-            out.print(" (", inContext(*structure, context), ")");
+            out.print(" (Structure ", inContext(*structure, context), ")");
         } else {
             out.print("Cell: ", RawPointer(asCell()));
             out.print(" (", inContext(*structure, context), ")");
         }
 #if USE(JSVALUE64)
-        out.print(", ID: ", asCell()->structureID());
+        out.print(", StructureID: ", asCell()->structureID());
 #endif
     } else if (isTrue())
         out.print("True");
index 1ed138c..f863d0e 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "AsyncGeneratorPrototype.h"
 #include "BuiltinNames.h"
+#include "CatchScope.h"
 #include "ClonedArguments.h"
 #include "CodeBlock.h"
 #include "CommonIdentifiers.h"
@@ -126,16 +127,24 @@ FunctionRareData* JSFunction::allocateRareData(VM& vm)
     return m_rareData.get();
 }
 
+JSObject* JSFunction::prototypeForConstruction(VM& vm, ExecState* exec)
+{
+    auto scope = DECLARE_CATCH_SCOPE(vm);
+    JSValue prototype = get(exec, vm.propertyNames->prototype);
+    ASSERT_UNUSED(scope, !scope.exception());
+    if (prototype.isObject())
+        return asObject(prototype);
+
+    return globalObject(vm)->objectPrototype();
+}
+
 FunctionRareData* JSFunction::allocateAndInitializeRareData(ExecState* exec, size_t inlineCapacity)
 {
     ASSERT(!m_rareData);
     VM& vm = exec->vm();
-    JSObject* prototype = jsDynamicCast<JSObject*>(vm, get(exec, vm.propertyNames->prototype));
-    JSGlobalObject* globalObject = this->globalObject(vm);
-    if (!prototype)
-        prototype = globalObject->objectPrototype();
+    JSObject* prototype = prototypeForConstruction(vm, exec);
     FunctionRareData* rareData = FunctionRareData::create(vm);
-    rareData->initializeObjectAllocationProfile(vm, globalObject, prototype, inlineCapacity);
+    rareData->initializeObjectAllocationProfile(vm, globalObject(vm), prototype, inlineCapacity, this);
 
     // A DFG compilation thread may be trying to read the rare data
     // We want to ensure that it sees it properly allocated
@@ -149,11 +158,8 @@ FunctionRareData* JSFunction::initializeRareData(ExecState* exec, size_t inlineC
 {
     ASSERT(!!m_rareData);
     VM& vm = exec->vm();
-    JSObject* prototype = jsDynamicCast<JSObject*>(vm, get(exec, vm.propertyNames->prototype));
-    JSGlobalObject* globalObject = this->globalObject(vm);
-    if (!prototype)
-        prototype = globalObject->objectPrototype();
-    m_rareData->initializeObjectAllocationProfile(vm, globalObject, prototype, inlineCapacity);
+    JSObject* prototype = prototypeForConstruction(vm, exec);
+    m_rareData->initializeObjectAllocationProfile(vm, globalObject(vm), prototype, inlineCapacity, this);
     return m_rareData.get();
 }
 
@@ -371,7 +377,6 @@ bool JSFunction::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyN
             offset = thisObject->getDirectOffset(vm, vm.propertyNames->prototype, attributes);
             ASSERT(isValidOffset(offset));
         }
-
         slot.setValue(thisObject, attributes, thisObject->getDirect(offset), offset);
     }
 
index 17332f4..4794502 100644 (file)
@@ -148,6 +148,9 @@ public:
 
     void setFunctionName(ExecState*, JSValue name);
 
+    // Returns the __proto__ for the |this| value if this JSFunction were to be constructed.
+    JSObject* prototypeForConstruction(VM&, ExecState*);
+
 protected:
     JS_EXPORT_PRIVATE JSFunction(VM&, JSGlobalObject*, Structure*);
     JSFunction(VM&, FunctionExecutable*, JSScope*, Structure*);
index 12b7b70..0da1a77 100644 (file)
@@ -56,7 +56,7 @@ bool JSMap::isIteratorProtocolFastAndNonObservable()
     if (structure == globalObject->mapStructure())
         return true;
 
-    if (structure->storedPrototype() != globalObject->mapPrototype())
+    if (getPrototypeDirect() != globalObject->mapPrototype())
         return false;
 
     VM& vm = globalObject->vm();
@@ -73,6 +73,9 @@ bool JSMap::canCloneFastAndNonObservable(Structure* structure)
         if (!globalObject->isMapPrototypeSetFastAndNonObservable())
             return false;
 
+        if (structure->hasPolyProto())
+            return false;
+
         if (structure->storedPrototype() != globalObject->mapPrototype())
             return false;
 
index cb8c44d..d593cb0 100644 (file)
@@ -770,7 +770,7 @@ bool JSObject::putInlineSlow(ExecState* exec, PropertyName propertyName, JSValue
         PropertyOffset offset = obj->structure(vm)->get(vm, propertyName, attributes);
         if (isValidOffset(offset)) {
             if (attributes & PropertyAttribute::ReadOnly) {
-                ASSERT(structure(vm)->prototypeChainMayInterceptStoreTo(vm, propertyName) || obj == this);
+                ASSERT(this->prototypeChainMayInterceptStoreTo(vm, propertyName) || obj == this);
                 return typeError(exec, scope, slot.isStrictMode(), ASCIILiteral(ReadonlyPropertyWriteError));
             }
 
@@ -1016,7 +1016,7 @@ Butterfly* JSObject::createInitialIndexedStorage(VM& vm, unsigned length)
     ASSERT(length <= MAX_STORAGE_VECTOR_LENGTH);
     IndexingType oldType = indexingType();
     ASSERT_UNUSED(oldType, !hasIndexedProperties(oldType));
-    ASSERT(!structure()->needsSlowPutIndexing());
+    ASSERT(!needsSlowPutIndexing());
     ASSERT(!indexingShouldBeSparse());
     Structure* structure = this->structure(vm);
     unsigned propertyCapacity = structure->outOfLineCapacity();
@@ -1112,7 +1112,7 @@ ArrayStorage* JSObject::createArrayStorage(VM& vm, unsigned length, unsigned vec
 
     Butterfly* newButterfly = createArrayStorageButterfly(vm, this, oldStructure, length, vectorLength, m_butterfly.getMayBeNull());
     ArrayStorage* result = newButterfly->arrayStorage();
-    Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, oldStructure->suggestedArrayStorageTransition());
+    Structure* newStructure = Structure::nonPropertyTransition(vm, oldStructure, suggestedArrayStorageTransition());
     nukeStructureAndSetButterfly(vm, oldStructureID, newButterfly);
     setStructure(vm, newStructure);
     return result;
@@ -1207,7 +1207,7 @@ ArrayStorage* JSObject::convertUndecidedToArrayStorage(VM& vm, NonPropertyTransi
 
 ArrayStorage* JSObject::convertUndecidedToArrayStorage(VM& vm)
 {
-    return convertUndecidedToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition());
+    return convertUndecidedToArrayStorage(vm, suggestedArrayStorageTransition());
 }
 
 ContiguousDoubles JSObject::convertInt32ToDouble(VM& vm)
@@ -1265,7 +1265,7 @@ ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm, NonPropertyTransition
 
 ArrayStorage* JSObject::convertInt32ToArrayStorage(VM& vm)
 {
-    return convertInt32ToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition());
+    return convertInt32ToArrayStorage(vm, suggestedArrayStorageTransition());
 }
 
 ContiguousJSValues JSObject::convertDoubleToContiguous(VM& vm)
@@ -1318,7 +1318,7 @@ ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm, NonPropertyTransitio
 
 ArrayStorage* JSObject::convertDoubleToArrayStorage(VM& vm)
 {
-    return convertDoubleToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition());
+    return convertDoubleToArrayStorage(vm, suggestedArrayStorageTransition());
 }
 
 ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm, NonPropertyTransition transition)
@@ -1367,7 +1367,7 @@ ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm, NonPropertyTrans
 
 ArrayStorage* JSObject::convertContiguousToArrayStorage(VM& vm)
 {
-    return convertContiguousToArrayStorage(vm, structure(vm)->suggestedArrayStorageTransition());
+    return convertContiguousToArrayStorage(vm, suggestedArrayStorageTransition());
 }
 
 void JSObject::convertUndecidedForValue(VM& vm, JSValue value)
@@ -1448,7 +1448,7 @@ ContiguousJSValues JSObject::ensureInt32Slow(VM& vm)
     
     switch (indexingType()) {
     case ALL_BLANK_INDEXING_TYPES:
-        if (UNLIKELY(indexingShouldBeSparse() || structure(vm)->needsSlowPutIndexing()))
+        if (UNLIKELY(indexingShouldBeSparse() || needsSlowPutIndexing()))
             return ContiguousJSValues();
         return createInitialInt32(vm, 0);
         
@@ -1475,7 +1475,7 @@ ContiguousDoubles JSObject::ensureDoubleSlow(VM& vm)
     
     switch (indexingType()) {
     case ALL_BLANK_INDEXING_TYPES:
-        if (UNLIKELY(indexingShouldBeSparse() || structure(vm)->needsSlowPutIndexing()))
+        if (UNLIKELY(indexingShouldBeSparse() || needsSlowPutIndexing()))
             return ContiguousDoubles();
         return createInitialDouble(vm, 0);
         
@@ -1504,7 +1504,7 @@ ContiguousJSValues JSObject::ensureContiguousSlow(VM& vm)
     
     switch (indexingType()) {
     case ALL_BLANK_INDEXING_TYPES:
-        if (UNLIKELY(indexingShouldBeSparse() || structure(vm)->needsSlowPutIndexing()))
+        if (UNLIKELY(indexingShouldBeSparse() || needsSlowPutIndexing()))
             return ContiguousJSValues();
         return createInitialContiguous(vm, 0);
         
@@ -1541,22 +1541,22 @@ ArrayStorage* JSObject::ensureArrayStorageSlow(VM& vm)
         
     case ALL_UNDECIDED_INDEXING_TYPES:
         ASSERT(!indexingShouldBeSparse());
-        ASSERT(!structure(vm)->needsSlowPutIndexing());
+        ASSERT(!needsSlowPutIndexing());
         return convertUndecidedToArrayStorage(vm);
         
     case ALL_INT32_INDEXING_TYPES:
         ASSERT(!indexingShouldBeSparse());
-        ASSERT(!structure(vm)->needsSlowPutIndexing());
+        ASSERT(!needsSlowPutIndexing());
         return convertInt32ToArrayStorage(vm);
         
     case ALL_DOUBLE_INDEXING_TYPES:
         ASSERT(!indexingShouldBeSparse());
-        ASSERT(!structure(vm)->needsSlowPutIndexing());
+        ASSERT(!needsSlowPutIndexing());
         return convertDoubleToArrayStorage(vm);
         
     case ALL_CONTIGUOUS_INDEXING_TYPES:
         ASSERT(!indexingShouldBeSparse());
-        ASSERT(!structure(vm)->needsSlowPutIndexing());
+        ASSERT(!needsSlowPutIndexing());
         return convertContiguousToArrayStorage(vm);
         
     default:
@@ -1634,14 +1634,17 @@ void JSObject::setPrototypeDirect(VM& vm, JSValue prototype)
     if (prototype.isObject())
         vm.prototypeMap.addPrototype(asObject(prototype));
     
-    Structure* newStructure = Structure::changePrototypeTransition(vm, structure(vm), prototype);
-    setStructure(vm, newStructure);
-    
-    if (!newStructure->anyObjectInChainMayInterceptIndexedAccesses())
+    if (structure(vm)->hasMonoProto()) {
+        Structure* newStructure = Structure::changePrototypeTransition(vm, structure(vm), prototype);
+        setStructure(vm, newStructure);
+    } else
+        putDirect(vm, structure(vm)->polyProtoOffset(), prototype);
+
+    if (!anyObjectInChainMayInterceptIndexedAccesses())
         return;
     
     if (vm.prototypeMap.isPrototype(this)) {
-        newStructure->globalObject()->haveABadTime(vm);
+        structure(vm)->globalObject()->haveABadTime(vm);
         return;
     }
     
@@ -1952,7 +1955,8 @@ JSValue JSObject::ordinaryToPrimitive(ExecState* exec, PreferredPrimitiveType hi
 
     // Make sure that whatever default value methods there are on object's prototype chain are
     // being watched.
-    this->structure()->startWatchingInternalPropertiesIfNecessaryForEntireChain(vm);
+    for (const JSObject* object = this; object; object = object->structure(vm)->storedPrototypeObject(object))
+        object->structure(vm)->startWatchingInternalPropertiesIfNecessary(vm);
 
     JSValue value;
     if (hint == PreferString) {
@@ -2727,7 +2731,7 @@ bool JSObject::putByIndexBeyondVectorLength(ExecState* exec, unsigned i, JSValue
             return putByIndexBeyondVectorLengthWithArrayStorage(
                 exec, i, value, shouldThrow, createArrayStorage(vm, 0, 0));
         }
-        if (structure(vm)->needsSlowPutIndexing()) {
+        if (needsSlowPutIndexing()) {
             // Convert the indexing type to the SlowPutArrayStorage and retry.
             createArrayStorage(vm, i + 1, getNewVectorLength(0, 0, 0, i + 1));
             return putByIndex(this, exec, i, value, shouldThrow);
@@ -2879,7 +2883,7 @@ bool JSObject::putDirectIndexSlowOrBeyondVectorLength(ExecState* exec, unsigned
             return putDirectIndexBeyondVectorLengthWithArrayStorage(
                 exec, i, value, attributes, mode, createArrayStorage(vm, 0, 0));
         }
-        if (structure(vm)->needsSlowPutIndexing()) {
+        if (needsSlowPutIndexing()) {
             ArrayStorage* storage = createArrayStorage(vm, i + 1, getNewVectorLength(0, 0, 0, i + 1));
             storage->m_vector[i].set(vm, this, value);
             storage->m_numValuesInVector++;
@@ -3559,7 +3563,7 @@ uint32_t JSObject::getEnumerableLength(ExecState* exec, JSObject* object)
 {
     VM& vm = exec->vm();
     Structure* structure = object->structure(vm);
-    if (structure->holesMustForwardToPrototype(vm))
+    if (structure->holesMustForwardToPrototype(vm, object))
         return 0;
     switch (object->indexingType()) {
     case ALL_BLANK_INDEXING_TYPES:
@@ -3669,4 +3673,56 @@ JSValue JSObject::getMethod(ExecState* exec, CallData& callData, CallType& callT
     return method;
 }
 
+bool JSObject::anyObjectInChainMayInterceptIndexedAccesses() const
+{
+    VM& vm = *this->vm();
+    for (const JSObject* current = this; ;) {
+        if (current->structure(vm)->mayInterceptIndexedAccesses())
+            return true;
+        
+        JSValue prototype = current->getPrototypeDirect();
+        if (prototype.isNull())
+            return false;
+        
+        current = asObject(prototype);
+    }
+}
+
+bool JSObject::prototypeChainMayInterceptStoreTo(VM& vm, PropertyName propertyName)
+{
+    if (parseIndex(propertyName))
+        return anyObjectInChainMayInterceptIndexedAccesses();
+    
+    for (JSObject* current = this; ;) {
+        JSValue prototype = current->getPrototypeDirect();
+        if (prototype.isNull())
+            return false;
+        
+        current = asObject(prototype);
+        
+        unsigned attributes;
+        PropertyOffset offset = current->structure(vm)->get(vm, propertyName, attributes);
+        if (!JSC::isValidOffset(offset))
+            continue;
+        
+        if (attributes & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor))
+            return true;
+        
+        return false;
+    }
+}
+
+bool JSObject::needsSlowPutIndexing() const
+{
+    return anyObjectInChainMayInterceptIndexedAccesses() || globalObject()->isHavingABadTime();
+}
+
+NonPropertyTransition JSObject::suggestedArrayStorageTransition() const
+{
+    if (needsSlowPutIndexing())
+        return NonPropertyTransition::AllocateSlowPutArrayStorage;
+    
+    return NonPropertyTransition::AllocateArrayStorage;
+}
+
 } // namespace JSC
index b5f4cd2..13aa339 100644 (file)
@@ -40,6 +40,7 @@
 #include "PutDirectIndexMode.h"
 #include "PutPropertySlot.h"
 #include "Structure.h"
+#include "StructureTransitionTable.h"
 #include "VM.h"
 #include "JSString.h"
 #include "SparseArrayValueMap.h"
@@ -733,6 +734,12 @@ public:
     JS_EXPORT_PRIVATE static bool isExtensible(JSObject*, ExecState*);
     bool isSealed(VM& vm) { return structure(vm)->isSealed(vm); }
     bool isFrozen(VM& vm) { return structure(vm)->isFrozen(vm); }
+
+    bool anyObjectInChainMayInterceptIndexedAccesses() const;
+    JS_EXPORT_PRIVATE bool prototypeChainMayInterceptStoreTo(VM&, PropertyName);
+    bool needsSlowPutIndexing() const;
+    NonPropertyTransition suggestedArrayStorageTransition() const;
+
 private:
     ALWAYS_INLINE bool isExtensibleImpl() { return isStructureExtensible(); }
 public:
@@ -866,7 +873,7 @@ protected:
     {
         Base::finishCreation(vm);
         ASSERT(inherits(vm, info()));
-        ASSERT(getPrototypeDirect().isNull() || Heap::heap(this) == Heap::heap(getPrototypeDirect()));
+        ASSERT(structure()->hasPolyProto() || getPrototypeDirect().isNull() || Heap::heap(this) == Heap::heap(getPrototypeDirect()));
         ASSERT(structure()->isObject());
         ASSERT(classInfo(vm));
     }
@@ -1298,7 +1305,7 @@ inline JSObject::JSObject(VM& vm, Structure* structure, Butterfly* butterfly)
 
 inline JSValue JSObject::getPrototypeDirect() const
 {
-    return structure()->storedPrototype();
+    return structure()->storedPrototype(this);
 }
 
 inline JSValue JSObject::getPrototype(VM& vm, ExecState* exec)
@@ -1400,7 +1407,9 @@ ALWAYS_INLINE bool JSObject::getPropertySlot(ExecState* exec, PropertyName prope
         Structure* structure = structureIDTable.get(object->structureID());
         if (object->getOwnNonIndexPropertySlot(vm, structure, propertyName, slot))
             return true;
-        JSValue prototype = structure->storedPrototype();
+        // FIXME: This doesn't look like it's following the specification:
+        // https://bugs.webkit.org/show_bug.cgi?id=172572
+        JSValue prototype = structure->storedPrototype(object);
         if (!prototype.isObject())
             break;
         object = asObject(prototype);
index 1e0d9d8..2e0b609 100644 (file)
@@ -116,7 +116,7 @@ ALWAYS_INLINE bool JSObject::getPropertySlot(ExecState* exec, unsigned propertyN
             return true;
         JSValue prototype;
         if (LIKELY(structure->classInfo()->methodTable.getPrototype == defaultGetPrototype || slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry))
-            prototype = structure->storedPrototype();
+            prototype = object->getPrototypeDirect();
         else {
             prototype = object->getPrototype(vm, exec);
             RETURN_IF_EXCEPTION(scope, false);
@@ -150,7 +150,7 @@ ALWAYS_INLINE bool JSObject::getNonIndexPropertySlot(ExecState* exec, PropertyNa
         }
         JSValue prototype;
         if (LIKELY(structure->classInfo()->methodTable.getPrototype == defaultGetPrototype || slot.internalMethodType() == PropertySlot::InternalMethodType::VMInquiry))
-            prototype = structure->storedPrototype();
+            prototype = object->getPrototypeDirect();
         else {
             prototype = object->getPrototype(vm, exec);
             RETURN_IF_EXCEPTION(scope, false);
@@ -219,7 +219,7 @@ ALWAYS_INLINE bool JSObject::putInlineForJSObject(JSCell* cell, ExecState* exec,
     }
 
     if (thisObject->canPerformFastPutInline(vm, propertyName)) {
-        ASSERT(!thisObject->structure(vm)->prototypeChainMayInterceptStoreTo(vm, propertyName));
+        ASSERT(!thisObject->prototypeChainMayInterceptStoreTo(vm, propertyName));
         if (!thisObject->putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot))
             return typeError(exec, scope, slot.isStrictMode(), ASCIILiteral(ReadonlyPropertyWriteError));
         return true;
index 0db6819..b8b37a3 100644 (file)
@@ -108,7 +108,7 @@ inline JSPropertyNameEnumerator* propertyNameEnumerator(ExecState* exec, JSObjec
     Structure* structure = base->structure(vm);
     if (!indexedLength
         && (enumerator = structure->cachedPropertyNameEnumerator())
-        && enumerator->cachedPrototypeChain() == structure->prototypeChain(exec))
+        && enumerator->cachedPrototypeChain() == structure->prototypeChain(exec, base))
         return enumerator;
 
     uint32_t numberStructureProperties = 0;
@@ -133,12 +133,15 @@ inline JSPropertyNameEnumerator* propertyNameEnumerator(ExecState* exec, JSObjec
 
     ASSERT(propertyNames.size() < UINT32_MAX);
 
-    bool successfullyNormalizedChain = normalizePrototypeChain(exec, structure) != InvalidPrototypeChain;
+    bool sawPolyProto;
+    bool successfullyNormalizedChain = normalizePrototypeChain(exec, base, sawPolyProto) != InvalidPrototypeChain;
 
     enumerator = JSPropertyNameEnumerator::create(vm, structure, indexedLength, numberStructureProperties, WTFMove(propertyNames));
-    enumerator->setCachedPrototypeChain(vm, structure->prototypeChain(exec));
-    if (!indexedLength && successfullyNormalizedChain && structure->canCachePropertyNameEnumerator())
-        structure->setCachedPropertyNameEnumerator(vm, enumerator);
+    if (!indexedLength && successfullyNormalizedChain && base->structure(vm) == structure) {
+        enumerator->setCachedPrototypeChain(vm, structure->prototypeChain(exec, base));
+        if (structure->canCachePropertyNameEnumerator())
+            structure->setCachedPropertyNameEnumerator(vm, enumerator);
+    }
     return enumerator;
 }
 
index abee613..b5f6b5c 100644 (file)
@@ -56,7 +56,7 @@ bool JSSet::isIteratorProtocolFastAndNonObservable()
     if (structure == globalObject->setStructure())
         return true;
 
-    if (structure->storedPrototype() != globalObject->jsSetPrototype())
+    if (getPrototypeDirect() != globalObject->jsSetPrototype())
         return false;
 
     VM& vm = globalObject->vm();
@@ -73,6 +73,9 @@ bool JSSet::canCloneFastAndNonObservable(Structure* structure)
         if (!globalObject->isSetPrototypeAddFastAndNonObservable())
             return false;
 
+        if (structure->hasPolyProto())
+            return false;
+
         if (structure->storedPrototype() != globalObject->jsSetPrototype())
             return false;
 
index b02dec7..66d44c9 100644 (file)
@@ -101,13 +101,6 @@ public:
         return m_structure.getConcurrently();
     }
     
-    JSObject* prototypeConcurrently() const
-    {
-        if (Structure* structure = getConcurrently())
-            return structure->storedPrototypeObject();
-        return nullptr;
-    }
-    
     JSObject* constructorConcurrently() const
     {
         return m_constructor.get();
index c41dc30..6d10558 100644 (file)
@@ -136,23 +136,30 @@ bool jsIsFunctionType(JSValue v)
     return false;
 }
 
-size_t normalizePrototypeChain(CallFrame* callFrame, Structure* structure)
+size_t normalizePrototypeChain(CallFrame* callFrame, JSCell* base, bool& sawPolyProto)
 {
     VM& vm = callFrame->vm();
     size_t count = 0;
+    sawPolyProto = false;
+    JSCell* current = base;
+    JSGlobalObject* globalObject = callFrame->lexicalGlobalObject();
     while (1) {
+        Structure* structure = current->structure(vm);
         if (structure->isProxy())
             return InvalidPrototypeChain;
-        JSValue v = structure->prototypeForLookup(callFrame);
-        if (v.isNull())
+
+        sawPolyProto |= structure->hasPolyProto();
+
+        JSValue prototype = structure->prototypeForLookup(globalObject, current);
+        if (prototype.isNull())
             return count;
 
-        JSCell* base = v.asCell();
-        structure = base->structure(vm);
+        current = prototype.asCell();
+        structure = current->structure(vm);
         if (structure->isDictionary()) {
             if (structure->hasBeenFlattenedBefore())
                 return InvalidPrototypeChain;
-            structure->flattenDictionaryStructure(vm, asObject(base));
+            structure->flattenDictionaryStructure(vm, asObject(current));
         }
 
         ++count;
index 6b50469..287c432 100644 (file)
@@ -34,7 +34,7 @@ JSValue jsTypeStringForValue(CallFrame*, JSValue);
 JSValue jsTypeStringForValue(VM&, JSGlobalObject*, JSValue);
 bool jsIsObjectTypeOrNull(CallFrame*, JSValue);
 bool jsIsFunctionType(JSValue);
-size_t normalizePrototypeChain(CallFrame*, Structure*);
+size_t normalizePrototypeChain(CallFrame*, JSCell*, bool& sawPolyProto);
 
 ALWAYS_INLINE JSString* jsString(ExecState* exec, JSString* s1, JSString* s2)
 {
index 8475c22..d043e2f 100644 (file)
@@ -470,7 +470,8 @@ typedef const char* optionString;
     v(bool, useFastTLSForWasmContext, true, Normal, "If true, we will store context in fast TLS. If false, we will pin it to a register.") \
     v(bool, useCallICsForWebAssemblyToJSCalls, true, Normal, "If true, we will use CallLinkInfo to inline cache Wasm to JS calls.") \
     v(bool, useObjectRestSpread, true, Normal, "If true, we will enable Object Rest/Spread feature.") \
-    v(bool, useArrayAllocationProfiling, true, Normal, "If true, we will use our normal array allocation profiling. If false, the allocation profile will always claim to be undecided.")
+    v(bool, useArrayAllocationProfiling, true, Normal, "If true, we will use our normal array allocation profiling. If false, the allocation profile will always claim to be undecided.")\
+    v(bool, forcePolyProto, false, Normal, "If true, create_this will always create an object with a poly proto structure.")
 
 
 enum OptionEquivalence {
index eb2d714..af4edbb 100644 (file)
@@ -54,18 +54,34 @@ void PrototypeMap::addPrototype(JSObject* object)
     // used as a prototype.
 }
 
-inline Structure* PrototypeMap::createEmptyStructure(JSGlobalObject* globalObject, JSObject* prototype, const TypeInfo& typeInfo, const ClassInfo* classInfo, IndexingType indexingType, unsigned inlineCapacity)
+inline Structure* PrototypeMap::createEmptyStructure(JSGlobalObject* globalObject, JSObject* prototype, const TypeInfo& typeInfo, const ClassInfo* classInfo, IndexingType indexingType, unsigned inlineCapacity, bool makePolyProtoStructure)
 {
-    auto key = std::make_tuple(prototype, inlineCapacity, classInfo, globalObject);
+    RELEASE_ASSERT(!!prototype); // We use nullptr inside the HashMap for prototype to mean poly proto, so user's of this API must provide non-null prototypes.
+
+    auto key = std::make_tuple(makePolyProtoStructure ? nullptr : prototype, inlineCapacity, classInfo, globalObject);
     if (Structure* structure = m_structures.get(key)) {
+        if (makePolyProtoStructure) {
+            addPrototype(prototype);
+            RELEASE_ASSERT(structure->hasPolyProto());
+        } else
+            RELEASE_ASSERT(structure->hasMonoProto());
         ASSERT(isPrototype(prototype));
         return structure;
     }
 
     addPrototype(prototype);
-    Structure* structure = Structure::create(
-        globalObject->vm(), globalObject, prototype, typeInfo, classInfo, indexingType, inlineCapacity);
+
+    VM& vm = globalObject->vm();
+    Structure* structure;
+    if (makePolyProtoStructure) {
+        structure = Structure::create(
+            Structure::PolyProto, vm, globalObject, prototype, typeInfo, classInfo, indexingType, inlineCapacity);
+    } else {
+        structure = Structure::create(
+            vm, globalObject, prototype, typeInfo, classInfo, indexingType, inlineCapacity);
+    }
     m_structures.set(key, Weak<Structure>(structure));
+
     return structure;
 }
 
@@ -73,20 +89,21 @@ Structure* PrototypeMap::emptyStructureForPrototypeFromBaseStructure(JSGlobalObj
 {
     // We currently do not have inline capacity static analysis for subclasses and all internal function constructors have a default inline capacity of 0.
     IndexingType indexingType = baseStructure->indexingType();
-    if (prototype->structure()->anyObjectInChainMayInterceptIndexedAccesses() && hasIndexedProperties(indexingType))
+    if (prototype->anyObjectInChainMayInterceptIndexedAccesses() && hasIndexedProperties(indexingType))
         indexingType = (indexingType & ~IndexingShapeMask) | SlowPutArrayStorageShape;
 
-    return createEmptyStructure(globalObject, prototype, baseStructure->typeInfo(), baseStructure->classInfo(), indexingType, 0);
+    return createEmptyStructure(globalObject, prototype, baseStructure->typeInfo(), baseStructure->classInfo(), indexingType, 0, false);
 }
 
-Structure* PrototypeMap::emptyObjectStructureForPrototype(JSGlobalObject* globalObject, JSObject* prototype, unsigned inlineCapacity)
+Structure* PrototypeMap::emptyObjectStructureForPrototype(JSGlobalObject* globalObject, JSObject* prototype, unsigned inlineCapacity, bool makePolyProtoStructure)
 {
-    return createEmptyStructure(globalObject, prototype, JSFinalObject::typeInfo(), JSFinalObject::info(), JSFinalObject::defaultIndexingType, inlineCapacity);
+    return createEmptyStructure(globalObject, prototype, JSFinalObject::typeInfo(), JSFinalObject::info(), JSFinalObject::defaultIndexingType, inlineCapacity, makePolyProtoStructure);
 }
 
 void PrototypeMap::clearEmptyObjectStructureForPrototype(JSGlobalObject* globalObject, JSObject* object, unsigned inlineCapacity)
 {
     m_structures.remove(std::make_tuple(object, inlineCapacity, JSFinalObject::info(), globalObject));
+    m_structures.remove(std::make_tuple(nullptr, inlineCapacity, JSFinalObject::info(), globalObject));
 }
 
 } // namespace JSC
index 18758d0..c12ed01 100644 (file)
@@ -46,18 +46,18 @@ public:
     {
     }
 
-    JS_EXPORT_PRIVATE Structure* emptyObjectStructureForPrototype(JSGlobalObject*, JSObject*, unsigned inlineCapacity);
+    JS_EXPORT_PRIVATE Structure* emptyObjectStructureForPrototype(JSGlobalObject*, JSObject*, unsigned inlineCapacity, bool makePolyProtoStructure = false);
     JS_EXPORT_PRIVATE Structure* emptyStructureForPrototypeFromBaseStructure(JSGlobalObject*, JSObject*, Structure*);
     void clearEmptyObjectStructureForPrototype(JSGlobalObject*, JSObject*, unsigned inlineCapacity);
     JS_EXPORT_PRIVATE void addPrototype(JSObject*);
     inline TriState isPrototype(JSObject*) const; // Returns a conservative estimate.
 
 private:
-    Structure* createEmptyStructure(JSGlobalObject*, JSObject* prototype, const TypeInfo&, const ClassInfo*, IndexingType, unsigned inlineCapacity);
+    Structure* createEmptyStructure(JSGlobalObject*, JSObject* prototype, const TypeInfo&, const ClassInfo*, IndexingType, unsigned inlineCapacity, bool makePolyProtoStructure);
 
     WeakGCMap<JSObject*, JSObject> m_prototypes;
     // FIXME: make the key a struct.
-    typedef WeakGCMap<std::tuple<JSObject*, unsigned, const ClassInfo*, JSGlobalObject*>, Structure> StructureMap;
+    using StructureMap = WeakGCMap<std::tuple<JSObject*, unsigned, const ClassInfo*, JSGlobalObject*>, Structure>;
     StructureMap m_structures;
 };
 
index 132b238..f043d25 100644 (file)
@@ -188,6 +188,7 @@ Structure::Structure(VM& vm, JSGlobalObject* globalObject, JSValue prototype, co
     , m_classInfo(classInfo)
     , m_transitionWatchpointSet(IsWatched)
     , m_offset(invalidOffset)
+    , m_propertyHash(0)
 {
     setDictionaryKind(NoneDictionaryKind);
     setIsPinnedPropertyTable(false);
@@ -220,6 +221,7 @@ Structure::Structure(VM& vm)
     , m_classInfo(info())
     , m_transitionWatchpointSet(IsWatched)
     , m_offset(invalidOffset)
+    , m_propertyHash(0)
 {
     setDictionaryKind(NoneDictionaryKind);
     setIsPinnedPropertyTable(false);
@@ -247,10 +249,11 @@ Structure::Structure(VM& vm, Structure* previous, DeferredStructureTransitionWat
     : JSCell(vm, vm.structureStructure.get())
     , m_inlineCapacity(previous->m_inlineCapacity)
     , m_bitField(0)
-    , m_prototype(vm, this, previous->storedPrototype())
+    , m_prototype(vm, this, previous->m_prototype.get())
     , m_classInfo(previous->m_classInfo)
     , m_transitionWatchpointSet(IsWatched)
     , m_offset(invalidOffset)
+    , m_propertyHash(previous->m_propertyHash)
 {
     setDictionaryKind(previous->dictionaryKind());
     setIsPinnedPropertyTable(false);
@@ -296,6 +299,23 @@ void Structure::destroy(JSCell* cell)
     static_cast<Structure*>(cell)->Structure::~Structure();
 }
 
+Structure* Structure::create(PolyProtoTag, VM& vm, JSGlobalObject* globalObject, JSObject* prototype, const TypeInfo& typeInfo, const ClassInfo* classInfo, IndexingType indexingType, unsigned inlineCapacity)
+{
+    Structure* result = create(vm, globalObject, prototype, typeInfo, classInfo, indexingType, inlineCapacity);
+
+    unsigned oldOutOfLineCapacity = result->outOfLineCapacity();
+    result->addPropertyWithoutTransition(
+        vm, vm.propertyNames->builtinNames().underscoreProtoPrivateName(), static_cast<unsigned>(PropertyAttribute::DontEnum),
+        [&] (const GCSafeConcurrentJSLocker&, PropertyOffset offset, PropertyOffset newLastOffset) {
+            RELEASE_ASSERT(Structure::outOfLineCapacity(newLastOffset) == oldOutOfLineCapacity);
+            RELEASE_ASSERT(isInlineOffset(offset));
+            result->m_prototype.set(vm, result, jsNumber(offset));
+            result->setLastOffset(newLastOffset);
+        });
+
+    return result;
+}
+
 void Structure::findStructuresAndMapForMaterialization(Vector<Structure*, 8>& structures, Structure*& structure, PropertyTable*& table)
 {
     ASSERT(structures.isEmpty());
@@ -399,26 +419,14 @@ Structure* Structure::addPropertyTransitionToExistingStructureConcurrently(Struc
     return addPropertyTransitionToExistingStructureImpl(structure, uid, attributes, offset);
 }
 
-bool Structure::anyObjectInChainMayInterceptIndexedAccesses() const
+bool Structure::holesMustForwardToPrototype(VM& vm, JSObject* base) const
 {
-    for (const Structure* current = this; ;) {
-        if (current->mayInterceptIndexedAccesses())
-            return true;
-        
-        JSValue prototype = current->storedPrototype();
-        if (prototype.isNull())
-            return false;
-        
-        current = asObject(prototype)->structure();
-    }
-}
+    ASSERT(base->structure(vm) == this);
 
-bool Structure::holesMustForwardToPrototype(VM& vm) const
-{
     if (this->mayInterceptIndexedAccesses())
         return true;
 
-    JSValue prototype = this->storedPrototype();
+    JSValue prototype = this->storedPrototype(base);
     if (!prototype.isObject())
         return false;
     JSObject* object = asObject(prototype);
@@ -427,7 +435,7 @@ bool Structure::holesMustForwardToPrototype(VM& vm) const
         Structure& structure = *object->structure(vm);
         if (hasIndexedProperties(object->indexingType()) || structure.mayInterceptIndexedAccesses())
             return true;
-        prototype = structure.storedPrototype();
+        prototype = structure.storedPrototype(object);
         if (!prototype.isObject())
             return false;
         object = asObject(prototype);
@@ -437,20 +445,6 @@ bool Structure::holesMustForwardToPrototype(VM& vm) const
     return false;
 }
 
-bool Structure::needsSlowPutIndexing() const
-{
-    return anyObjectInChainMayInterceptIndexedAccesses()
-        || globalObject()->isHavingABadTime();
-}
-
-NonPropertyTransition Structure::suggestedArrayStorageTransition() const
-{
-    if (needsSlowPutIndexing())
-        return NonPropertyTransition::AllocateSlowPutArrayStorage;
-    
-    return NonPropertyTransition::AllocateArrayStorage;
-}
-
 Structure* Structure::addPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset)
 {
     Structure* newStructure = addPropertyTransitionToExistingStructure(
@@ -553,6 +547,8 @@ Structure* Structure::removePropertyTransition(VM& vm, Structure* structure, Pro
 
 Structure* Structure::changePrototypeTransition(VM& vm, Structure* structure, JSValue prototype)
 {
+    ASSERT(prototype.isObject() || prototype.isNull());
+
     DeferGC deferGC(vm.heap);
     Structure* transition = create(vm, structure);
 
@@ -1068,11 +1064,6 @@ void Structure::didTransitionFromThisStructure(DeferredStructureTransitionWatchp
         m_transitionWatchpointSet.fireAll(*vm(), StructureFireDetail(this));
 }
 
-JSValue Structure::prototypeForLookup(CodeBlock* codeBlock) const
-{
-    return prototypeForLookup(codeBlock->globalObject());
-}
-
 void Structure::visitChildren(JSCell* cell, SlotVisitor& visitor)
 {
     Structure* thisObject = jsCast<Structure*>(cell);
@@ -1110,7 +1101,7 @@ bool Structure::isCheapDuringGC()
     // https://bugs.webkit.org/show_bug.cgi?id=157334
     
     return (!m_globalObject || Heap::isMarkedConcurrently(m_globalObject.get()))
-        && (!storedPrototypeObject() || Heap::isMarkedConcurrently(storedPrototypeObject()));
+        && (hasPolyProto() || !storedPrototypeObject() || Heap::isMarkedConcurrently(storedPrototypeObject()));
 }
 
 bool Structure::markIfCheap(SlotVisitor& visitor)
@@ -1122,40 +1113,19 @@ bool Structure::markIfCheap(SlotVisitor& visitor)
     return true;
 }
 
-bool Structure::prototypeChainMayInterceptStoreTo(VM& vm, PropertyName propertyName)
-{
-    if (parseIndex(propertyName))
-        return anyObjectInChainMayInterceptIndexedAccesses();
-    
-    for (Structure* current = this; ;) {
-        JSValue prototype = current->storedPrototype();
-        if (prototype.isNull())
-            return false;
-        
-        current = prototype.asCell()->structure(vm);
-        
-        unsigned attributes;
-        PropertyOffset offset = current->get(vm, propertyName, attributes);
-        if (!JSC::isValidOffset(offset))
-            continue;
-        
-        if (attributes & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor))
-            return true;
-        
-        return false;
-    }
-}
-
-Ref<StructureShape> Structure::toStructureShape(JSValue value)
+Ref<StructureShape> Structure::toStructureShape(JSValue value, bool& sawPolyProtoStructure)
 {
     Ref<StructureShape> baseShape = StructureShape::create();
     RefPtr<StructureShape> curShape = baseShape.ptr();
     Structure* curStructure = this;
     JSValue curValue = value;
+    sawPolyProtoStructure = false;
     while (curStructure) {
+        sawPolyProtoStructure |= curStructure->hasPolyProto();
         curStructure->forEachPropertyConcurrently(
             [&] (const PropertyMapEntry& entry) -> bool {
-                curShape->addProperty(*entry.key);
+                if (!PropertyName(entry.key).isPrivateName())
+                    curShape->addProperty(*entry.key);
                 return true;
             });
 
@@ -1169,26 +1139,24 @@ Ref<StructureShape> Structure::toStructureShape(JSValue value)
 
         curShape->markAsFinal();
 
-        if (curStructure->storedPrototypeStructure()) {
-            auto newShape = StructureShape::create();
-            curShape->setProto(newShape.copyRef());
-            curShape = WTFMove(newShape);
-            curValue = curStructure->storedPrototype();
-        }
+        if (!curValue.isObject())
+            break;
 
-        curStructure = curStructure->storedPrototypeStructure();
+        JSObject* object = asObject(curValue);
+        JSObject* prototypeObject = object->structure()->storedPrototypeObject(object);
+        if (!prototypeObject)
+            break;
+
+        auto newShape = StructureShape::create();
+        curShape->setProto(newShape.copyRef());
+        curShape = WTFMove(newShape);
+        curValue = prototypeObject;
+        curStructure = prototypeObject->structure();
     }
     
     return baseShape;
 }
 
-bool Structure::canUseForAllocationsOf(Structure* other)
-{
-    return inlineCapacity() == other->inlineCapacity()
-        && storedPrototype() == other->storedPrototype()
-        && objectInitializationBlob() == other->objectInitializationBlob();
-}
-
 void Structure::dump(PrintStream& out) const
 {
     out.print(RawPointer(this), ":[", classInfo()->className, ", {");
@@ -1203,7 +1171,9 @@ void Structure::dump(PrintStream& out) const
     
     out.print("}, ", IndexingTypeDump(indexingType()));
     
-    if (m_prototype.get().isCell())
+    if (hasPolyProto())
+        out.print(", PolyProto offset:", polyProtoOffset());
+    else if (m_prototype.get().isCell())
         out.print(", Proto:", RawPointer(m_prototype.get().asCell()));
 
     switch (dictionaryKind()) {
@@ -1288,9 +1258,7 @@ bool Structure::canCachePropertyNameEnumerator() const
     while (true) {
         if (!structure->get())
             break;
-        if (structure->get()->isDictionary())
-            return false;
-        if (structure->get()->typeInfo().overridesGetPropertyNames())
+        if (structure->get()->isDictionary() || structure->get()->typeInfo().overridesGetPropertyNames())
             return false;
         structure++;
     }
index dd36c77..bf40120 100644 (file)
@@ -129,7 +129,9 @@ public:
     typedef JSCell Base;
     static const unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal;
     
+    enum PolyProtoTag { PolyProto };
     static Structure* create(VM&, JSGlobalObject*, JSValue prototype, const TypeInfo&, const ClassInfo*, IndexingType = NonArray, unsigned inlineCapacity = 0);
+    static Structure* create(PolyProtoTag, VM&, JSGlobalObject*, JSObject* prototype, const TypeInfo&, const ClassInfo*, IndexingType = NonArray, unsigned inlineCapacity = 0);
 
     ~Structure();
 
@@ -138,7 +140,19 @@ protected:
     {
         Base::finishCreation(vm);
         ASSERT(m_prototype);
-        ASSERT(m_prototype.isObject() || m_prototype.isNull());
+        ASSERT(m_prototype.isObject() || m_prototype.isNull() || m_prototype.isInt32());
+    }
+
+    void finishCreation(VM& vm, const Structure* previous)
+    {
+        this->finishCreation(vm);
+        if (previous->hasRareData()) {
+            const StructureRareData* previousRareData = previous->rareData();
+            if (previousRareData->hasSharedPolyProtoWatchpoint()) {
+                ensureRareData(vm);
+                rareData()->setSharedPolyProtoWatchpoint(previousRareData->copySharedPolyProtoWatchpoint());
+            }
+        }
     }
 
     void finishCreation(VM& vm, CreatingEarlyCellTag)
@@ -167,7 +181,7 @@ public:
     static Structure* addPropertyTransitionToExistingStructureConcurrently(Structure*, UniquedStringImpl* uid, unsigned attributes, PropertyOffset&);
     JS_EXPORT_PRIVATE static Structure* addPropertyTransitionToExistingStructure(Structure*, PropertyName, unsigned attributes, PropertyOffset&);
     static Structure* removePropertyTransition(VM&, Structure*, PropertyName, PropertyOffset&);
-    JS_EXPORT_PRIVATE static Structure* changePrototypeTransition(VM&, Structure*, JSValue prototype);
+    static Structure* changePrototypeTransition(VM&, Structure*, JSValue prototype);
     JS_EXPORT_PRIVATE static Structure* attributeChangeTransition(VM&, Structure*, PropertyName, unsigned attributes);
     JS_EXPORT_PRIVATE static Structure* toCacheableDictionaryTransition(VM&, Structure*, DeferredStructureTransitionWatchpointFire* = nullptr);
     static Structure* toUncacheableDictionaryTransition(VM&, Structure*);
@@ -208,7 +222,7 @@ public:
     {
         return !typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence();
     }
-    
+
     bool needImpurePropertyWatchpoint()
     {
         return propertyAccessesAreCacheable()
@@ -240,26 +254,42 @@ public:
         return !!(indexingTypeIncludingHistory() & MayHaveIndexedAccessors);
     }
         
-    JS_EXPORT_PRIVATE bool anyObjectInChainMayInterceptIndexedAccesses() const;
-    bool holesMustForwardToPrototype(VM&) const;
-        
-    bool needsSlowPutIndexing() const;
-    NonPropertyTransition suggestedArrayStorageTransition() const;
+    bool holesMustForwardToPrototype(VM&, JSObject*) const;
         
     JSGlobalObject* globalObject() const { return m_globalObject.get(); }
 
     // NOTE: This method should only be called during the creation of structures, since the global
     // object of a structure is presumed to be immutable in a bunch of places.
     void setGlobalObject(VM& vm, JSGlobalObject* globalObject) { m_globalObject.set(vm, this, globalObject); }
-        
-    JSValue storedPrototype() const { return m_prototype.get(); }
+
+    bool hasMonoProto() const
+    {
+        return !m_prototype.get().isInt32();
+    }
+    bool hasPolyProto() const
+    {
+        return !hasMonoProto();
+    }
+    JSValue storedPrototype() const
+    {
+        RELEASE_ASSERT(hasMonoProto());
+        return m_prototype.get();
+    }
+    PropertyOffset polyProtoOffset() const
+    {
+        RELEASE_ASSERT(hasPolyProto());
+        return m_prototype.get().asInt32();
+    }
+    JSValue storedPrototype(const JSObject*) const;
+    JSObject* storedPrototypeObject(const JSObject*) const;
+    Structure* storedPrototypeStructure(const JSObject*) const;
+
     JSObject* storedPrototypeObject() const;
     Structure* storedPrototypeStructure() const;
-    JSValue prototypeForLookup(ExecState*) const;
     JSValue prototypeForLookup(JSGlobalObject*) const;
-    JSValue prototypeForLookup(CodeBlock*) const;
-    StructureChain* prototypeChain(VM&, JSGlobalObject*) const;
-    StructureChain* prototypeChain(ExecState*) const;
+    JSValue prototypeForLookup(JSGlobalObject*, JSCell* base) const;
+    StructureChain* prototypeChain(VM&, JSGlobalObject*, JSObject* base) const;
+    StructureChain* prototypeChain(ExecState*, JSObject* base) const;
     static void visitChildren(JSCell*, SlotVisitor&);
     
     // A Structure is cheap to mark during GC if doing so would only add a small and bounded amount
@@ -272,14 +302,30 @@ public:
     
     // Returns true if this structure is now marked.
     bool markIfCheap(SlotVisitor&);
-        
-    // Will just the prototype chain intercept this property access?
-    JS_EXPORT_PRIVATE bool prototypeChainMayInterceptStoreTo(VM&, PropertyName);
     
     bool hasRareData() const
     {
         return isRareData(m_previousOrRareData.get());
     }
+
+    StructureRareData* rareData()
+    {
+        ASSERT(hasRareData());
+        return static_cast<StructureRareData*>(m_previousOrRareData.get());
+    }
+
+    const StructureRareData* rareData() const
+    {
+        ASSERT(hasRareData());
+        return static_cast<const StructureRareData*>(m_previousOrRareData.get());
+    }
+
+    StructureRareData* ensureRareData(VM& vm)
+    {
+        if (!hasRareData())
+            allocateRareData(vm);
+        return rareData();
+    }
     
     Structure* previousID() const
     {
@@ -533,12 +579,6 @@ public:
         startWatchingInternalProperties(vm);
     }
     
-    void startWatchingInternalPropertiesIfNecessaryForEntireChain(VM& vm)
-    {
-        for (Structure* structure = this; structure; structure = structure->storedPrototypeStructure())
-            structure->startWatchingInternalPropertiesIfNecessary(vm);
-    }
-
     bool hasInferredTypes() const
     {
         return !!m_inferredTypeTable;
@@ -588,11 +628,7 @@ public:
         willStoreValueSlow(vm, propertyName, value, shouldOptimize, InferredTypeTable::OldProperty);
     }
 
-    Ref<StructureShape> toStructureShape(JSValue);
-    
-    // Determines if the two structures match enough that this one could be used for allocations
-    // of the other one.
-    bool canUseForAllocationsOf(Structure*);
+    Ref<StructureShape> toStructureShape(JSValue, bool& sawPolyProtoStructure);
     
     void dump(PrintStream&) const;
     void dumpInContext(PrintStream&, DumpContext*) const;
@@ -601,6 +637,10 @@ public:
     static void dumpContextHeader(PrintStream&);
     
     ConcurrentJSLock& lock() { return m_lock; }
+
+    unsigned propertyHash() const { return m_propertyHash; }
+
+    static bool shouldConvertToPolyProto(const Structure* a, const Structure* b);
     
     DECLARE_EXPORT_INFO;
 
@@ -720,8 +760,7 @@ private:
         return numberOfSlotsForLastOffset(m_offset, m_inlineCapacity);
     }
 
-    bool isValid(JSGlobalObject*, StructureChain* cachedPrototypeChain) const;
-    bool isValid(ExecState*, StructureChain* cachedPrototypeChain) const;
+    bool isValid(JSGlobalObject*, StructureChain* cachedPrototypeChain, JSObject* base) const;
 
     // You have to hold the structure lock to do these.
     JS_EXPORT_PRIVATE void pin(const AbstractLocker&, VM&, PropertyTable*);
@@ -732,12 +771,6 @@ private:
         return cell && cell->structureID() != structureID();
     }
 
-    StructureRareData* rareData() const
-    {
-        ASSERT(hasRareData());
-        return static_cast<StructureRareData*>(m_previousOrRareData.get());
-    }
-
     template<typename DetailsFunc>
     bool checkOffsetConsistency(PropertyTable*, const DetailsFunc&) const;
     bool checkOffsetConsistency() const;
@@ -787,6 +820,8 @@ private:
 
     // m_offset does not account for anonymous slots
     PropertyOffset m_offset;
+
+    uint32_t m_propertyHash;
 };
 
 } // namespace JSC
index 04eb1c0..93ae33b 100644 (file)
@@ -42,7 +42,7 @@ public:
     typedef JSCell Base;
     static const unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal;
 
-    static StructureChain* create(VM& vm, Structure* head)
+    static StructureChain* create(VM& vm, JSObject* head)
     { 
         StructureChain* chain = new (NotNull, allocateCell<StructureChain>(vm.heap)) StructureChain(vm, vm.structureChainStructure.get());
         chain->finishCreation(vm, head);
@@ -62,21 +62,23 @@ public:
     static void destroy(JSCell*);
 
 protected:
-    void finishCreation(VM& vm, Structure* head)
+    void finishCreation(VM& vm, JSObject* head)
     {
         Base::finishCreation(vm);
+
         size_t size = 0;
-        for (Structure* current = head; current; current = current->storedPrototype().isNull() ? 0 : asObject(current->storedPrototype())->structure())
+        for (JSObject* current = head; current; current = current->structure(vm)->storedPrototypeObject(current))
             ++size;
 
         std::unique_ptr<WriteBarrier<Structure>[]> vector = std::make_unique<WriteBarrier<Structure>[]>(size + 1);
 
         size_t i = 0;
-        for (Structure* current = head; current; current = current->storedPrototype().isNull() ? 0 : asObject(current->storedPrototype())->structure())
-            vector[i++].set(vm, this, current);
+        for (JSObject* current = head; current; current = current->structure(vm)->storedPrototypeObject(current))
+            vector[i++].set(vm, this, current->structure(vm));
         
         vm.heap.mutatorFence();
         m_vector = WTFMove(vector);
+        vm.heap.writeBarrier(this);
     }
 
 private:
index c906976..9fe99d9 100644 (file)
@@ -51,16 +51,17 @@ inline Structure* Structure::createStructure(VM& vm)
     return structure;
 }
 
-inline Structure* Structure::create(VM& vm, Structure* structure, DeferredStructureTransitionWatchpointFire* deferred)
+inline Structure* Structure::create(VM& vm, Structure* previous, DeferredStructureTransitionWatchpointFire* deferred)
 {
     ASSERT(vm.structureStructure);
-    Structure* newStructure = new (NotNull, allocateCell<Structure>(vm.heap)) Structure(vm, structure, deferred);
-    newStructure->finishCreation(vm);
+    Structure* newStructure = new (NotNull, allocateCell<Structure>(vm.heap)) Structure(vm, previous, deferred);
+    newStructure->finishCreation(vm, previous);
     return newStructure;
 }
 
 inline JSObject* Structure::storedPrototypeObject() const
 {
+    RELEASE_ASSERT(hasMonoProto());
     JSValue value = m_prototype.get();
     if (value.isNull())
         return nullptr;
@@ -69,12 +70,41 @@ inline JSObject* Structure::storedPrototypeObject() const
 
 inline Structure* Structure::storedPrototypeStructure() const
 {
+    RELEASE_ASSERT(hasMonoProto());
     JSObject* object = storedPrototypeObject();
     if (!object)
         return nullptr;
     return object->structure();
 }
 
+ALWAYS_INLINE JSValue Structure::storedPrototype(const JSObject* object) const
+{
+    RELEASE_ASSERT(object->structure() == this);
+    if (hasMonoProto())
+        return storedPrototype();
+    RELEASE_ASSERT(m_prototype.get().isInt32());
+    PropertyOffset offset = m_prototype.get().asInt32();
+    return object->getDirect(offset);
+}
+
+ALWAYS_INLINE JSObject* Structure::storedPrototypeObject(const JSObject* object) const
+{
+    RELEASE_ASSERT(object->structure() == this);
+    if (hasMonoProto())
+        return storedPrototypeObject();
+    JSValue proto = object->getDirect(polyProtoOffset());
+    if (proto.isNull())
+        return nullptr;
+    return asObject(proto);
+}
+
+ALWAYS_INLINE Structure* Structure::storedPrototypeStructure(const JSObject* object) const
+{
+    if (JSObject* proto = storedPrototypeObject(object))
+        return proto->structure();
+    return nullptr;
+}
+
 ALWAYS_INLINE PropertyOffset Structure::get(VM& vm, PropertyName propertyName)
 {
     unsigned attributes;
@@ -166,43 +196,56 @@ inline bool Structure::transitivelyTransitionedFrom(Structure* structureToFind)
     return false;
 }
 
+ALWAYS_INLINE JSValue prototypeForLookupPrimitiveImpl(JSGlobalObject* globalObject, const Structure* structure)
+{
+    ASSERT(!structure->isObject());
+
+    if (structure->typeInfo().type() == StringType)
+        return globalObject->stringPrototype();
+
+    ASSERT(structure->typeInfo().type() == SymbolType);
+    return globalObject->symbolPrototype();
+
+}
+
 inline JSValue Structure::prototypeForLookup(JSGlobalObject* globalObject) const
 {
+    RELEASE_ASSERT(hasMonoProto());
     if (isObject())
-        return m_prototype.get();
-    if (typeInfo().type() == SymbolType)
-        return globalObject->symbolPrototype();
-
-    ASSERT(typeInfo().type() == StringType);
-    return globalObject->stringPrototype();
+        return storedPrototype();
+    return prototypeForLookupPrimitiveImpl(globalObject, this);
 }
 
-inline JSValue Structure::prototypeForLookup(ExecState* exec) const
+inline JSValue Structure::prototypeForLookup(JSGlobalObject* globalObject, JSCell* base) const
 {
-    return prototypeForLookup(exec->lexicalGlobalObject());
+    RELEASE_ASSERT(base->structure() == this);
+    if (isObject())
+        return storedPrototype(asObject(base));
+    return prototypeForLookupPrimitiveImpl(globalObject, this);
 }
 
-inline StructureChain* Structure::prototypeChain(VM& vm, JSGlobalObject* globalObject) const
+inline StructureChain* Structure::prototypeChain(VM& vm, JSGlobalObject* globalObject, JSObject* base) const
 {
+    RELEASE_ASSERT(base->structure(vm) == this);
     // We cache our prototype chain so our clients can share it.
-    if (!isValid(globalObject, m_cachedPrototypeChain.get())) {
-        JSValue prototype = prototypeForLookup(globalObject);
-        m_cachedPrototypeChain.set(vm, this, StructureChain::create(vm, prototype.isNull() ? 0 : asObject(prototype)->structure()));
+    if (!isValid(globalObject, m_cachedPrototypeChain.get(), base)) {
+        JSValue prototype = prototypeForLookup(globalObject, base);
+        m_cachedPrototypeChain.set(vm, this, StructureChain::create(vm, prototype.isNull() ? nullptr : asObject(prototype)));
     }
     return m_cachedPrototypeChain.get();
 }
 
-inline StructureChain* Structure::prototypeChain(ExecState* exec) const
+inline StructureChain* Structure::prototypeChain(ExecState* exec, JSObject* base) const
 {
-    return prototypeChain(exec->vm(), exec->lexicalGlobalObject());
+    return prototypeChain(exec->vm(), exec->lexicalGlobalObject(), base);
 }
 
-inline bool Structure::isValid(JSGlobalObject* globalObject, StructureChain* cachedPrototypeChain) const
+inline bool Structure::isValid(JSGlobalObject* globalObject, StructureChain* cachedPrototypeChain, JSObject* base) const
 {
     if (!cachedPrototypeChain)
         return false;
 
-    JSValue prototype = prototypeForLookup(globalObject);
+    JSValue prototype = prototypeForLookup(globalObject, base);
     WriteBarrier<Structure>* cachedStructure = cachedPrototypeChain->head();
     while (*cachedStructure && !prototype.isNull()) {
         if (asObject(prototype)->structure() != cachedStructure->get())
@@ -213,11 +256,6 @@ inline bool Structure::isValid(JSGlobalObject* globalObject, StructureChain* cac
     return prototype.isNull() && !*cachedStructure;
 }
 
-inline bool Structure::isValid(ExecState* exec, StructureChain* cachedPrototypeChain) const
-{
-    return isValid(exec->lexicalGlobalObject(), cachedPrototypeChain);
-}
-
 inline void Structure::didReplaceProperty(PropertyOffset offset)
 {
     if (LIKELY(!hasRareData()))
@@ -341,6 +379,8 @@ inline PropertyOffset Structure::add(VM& vm, PropertyName propertyName, unsigned
     auto rep = propertyName.uid();
 
     PropertyOffset newOffset = table->nextOffset(m_inlineCapacity);
+
+    m_propertyHash = m_propertyHash ^ rep->existingSymbolAwareHash();
     
     PropertyOffset newLastOffset = m_offset;
     table->add(PropertyMapEntry(rep, newOffset, attributes), newLastOffset, PropertyTable::PropertyOffsetMayChange);
@@ -400,9 +440,56 @@ inline PropertyOffset Structure::removePropertyWithoutTransition(VM&, PropertyNa
     return remove(propertyName, func);
 }
 
-inline void Structure::setPropertyTable(VM& vm, PropertyTable* table)
+ALWAYS_INLINE void Structure::setPropertyTable(VM& vm, PropertyTable* table)
 {
     m_propertyTableUnsafe.setMayBeNull(vm, this, table);
 }
+
+ALWAYS_INLINE bool Structure::shouldConvertToPolyProto(const Structure* a, const Structure* b)
+{
+    if (!a || !b)
+        return false;
+
+    if (a == b)
+        return false;
+
+    if (a->propertyHash() != b->propertyHash())
+        return false;
+
+    // We only care about objects created via a constructor's to_this. These
+    // all have Structures with rare data and a sharedPolyProtoWatchpoint.
+    if (!a->hasRareData() || !b->hasRareData())
+        return false;
+
+    // We only care about Structure's generated from functions that share
+    // the same executable.
+    const Box<InlineWatchpointSet>& aInlineWatchpointSet = a->rareData()->sharedPolyProtoWatchpoint();
+    const Box<InlineWatchpointSet>& bInlineWatchpointSet = b->rareData()->sharedPolyProtoWatchpoint();
+    if (aInlineWatchpointSet.get() != bInlineWatchpointSet.get() || !aInlineWatchpointSet)
+        return false;
+    ASSERT(aInlineWatchpointSet && bInlineWatchpointSet && aInlineWatchpointSet.get() == bInlineWatchpointSet.get());
+
+    if (a->hasPolyProto() || b->hasPolyProto())
+        return false;
+
+    if (a->storedPrototype() == b->storedPrototype())
+        return false;
+
+    VM& vm = *a->vm();
+    JSObject* aObj = a->storedPrototypeObject();
+    JSObject* bObj = b->storedPrototypeObject();
+    while (aObj && bObj) {
+        a = aObj->structure(vm);
+        b = bObj->structure(vm);
+
+        if (a->propertyHash() != b->propertyHash())
+            return false;
+
+        aObj = a->storedPrototypeObject(aObj);
+        bObj = b->storedPrototypeObject(bObj);
+    }
+
+    return !aObj && !bObj;
+}
     
 } // namespace JSC
index 6fcab37..a650d94 100644 (file)
@@ -62,6 +62,11 @@ public:
     JSPropertyNameEnumerator* cachedPropertyNameEnumerator() const;
     void setCachedPropertyNameEnumerator(VM&, JSPropertyNameEnumerator*);
 
+    Box<InlineWatchpointSet> copySharedPolyProtoWatchpoint() const { return m_polyProtoWatchpoint; }
+    const Box<InlineWatchpointSet>& sharedPolyProtoWatchpoint() const { return m_polyProtoWatchpoint; }
+    void setSharedPolyProtoWatchpoint(Box<InlineWatchpointSet>&& sharedPolyProtoWatchpoint) { m_polyProtoWatchpoint = WTFMove(sharedPolyProtoWatchpoint); }
+    bool hasSharedPolyProtoWatchpoint() const { return static_cast<bool>(m_polyProtoWatchpoint); }
+
     DECLARE_EXPORT_INFO;
 
 private:
@@ -76,11 +81,12 @@ private:
     WriteBarrier<Structure> m_previous;
     WriteBarrier<JSString> m_objectToStringValue;
     WriteBarrier<JSPropertyNameEnumerator> m_cachedPropertyNameEnumerator;
-    
+
     typedef HashMap<PropertyOffset, RefPtr<WatchpointSet>, WTF::IntHash<PropertyOffset>, WTF::UnsignedWithZeroKeyHashTraits<PropertyOffset>> PropertyWatchpointMap;
     std::unique_ptr<PropertyWatchpointMap> m_replacementWatchpointSets;
     Bag<ObjectToStringAdaptiveStructureWatchpoint> m_objectToStringAdaptiveWatchpointSet;
     std::unique_ptr<ObjectToStringAdaptiveInferredPropertyValueWatchpoint> m_objectToStringAdaptiveInferredValueWatchpoint;
+    Box<InlineWatchpointSet> m_polyProtoWatchpoint;
     bool m_giveUpOnObjectToStringValueCache;
 };
 
index 53d73e8..727bbb9 100644 (file)
@@ -63,19 +63,35 @@ void TypeProfilerLog::processLogEntries(const String& reason)
         before = currentTimeMS();
     }
 
+    HashMap<Structure*, RefPtr<StructureShape>> cachedMonoProtoShapes;
+    HashMap<std::pair<Structure*, JSCell*>, RefPtr<StructureShape>> cachedPolyProtoShapes;
+
     LogEntry* entry = m_logStartPtr;
-    HashMap<Structure*, RefPtr<StructureShape>> seenShapes;
+
     while (entry != m_currentLogEntryPtr) {
         StructureID id = entry->structureID;
-        RefPtr<StructureShape> shape; 
+        RefPtr<StructureShape> shape;
         JSValue value = entry->value;
         Structure* structure = nullptr;
+        bool sawPolyProtoStructure = false;
         if (id) {
-            structure = Heap::heap(value.asCell())->structureIDTable().get(id); 
-            auto iter = seenShapes.find(structure);
-            if (iter == seenShapes.end()) {
-                shape = structure->toStructureShape(value);
-                seenShapes.set(structure, shape);
+            structure = Heap::heap(value.asCell())->structureIDTable().get(id);
+            auto iter = cachedMonoProtoShapes.find(structure);
+            if (iter == cachedMonoProtoShapes.end()) {
+                auto key = std::make_pair(structure, value.asCell());
+                auto iter = cachedPolyProtoShapes.find(key);
+                if (iter != cachedPolyProtoShapes.end()) {
+                    shape = iter->value;
+                    sawPolyProtoStructure = true;
+                }
+
+                if (!shape) {
+                    shape = structure->toStructureShape(value, sawPolyProtoStructure);
+                    if (sawPolyProtoStructure)
+                        cachedPolyProtoShapes.set(key, shape);
+                    else
+                        cachedMonoProtoShapes.set(structure, shape);
+                }
             } else
                 shape = iter->value;
         }
@@ -84,8 +100,8 @@ void TypeProfilerLog::processLogEntries(const String& reason)
         TypeLocation* location = entry->location;
         location->m_lastSeenType = type;
         if (location->m_globalTypeSet)
-            location->m_globalTypeSet->addTypeInformation(type, shape.copyRef(), structure);
-        location->m_instructionTypeSet->addTypeInformation(type, WTFMove(shape), structure);
+            location->m_globalTypeSet->addTypeInformation(type, shape.copyRef(), structure, sawPolyProtoStructure);
+        location->m_instructionTypeSet->addTypeInformation(type, WTFMove(shape), structure, sawPolyProtoStructure);
 
         entry++;
     }
index fb81138..7b32eaf 100644 (file)
@@ -41,14 +41,17 @@ TypeSet::TypeSet()
 {
 }
 
-void TypeSet::addTypeInformation(RuntimeType type, RefPtr<StructureShape>&& passedNewShape, Structure* structure)
+void TypeSet::addTypeInformation(RuntimeType type, RefPtr<StructureShape>&& passedNewShape, Structure* structure, bool sawPolyProtoStructure)
 {
     m_seenTypes = m_seenTypes | type;
 
     if (structure && passedNewShape && !runtimeTypeIsPrimitive(type)) {
         Ref<StructureShape> newShape = passedNewShape.releaseNonNull();
-        if (!m_structureSet.contains(structure)) {
-            {
+        // FIXME: TypeSet should be able to cache poly proto chains
+        // just by caching the prototype chain:
+        // https://bugs.webkit.org/show_bug.cgi?id=177627
+        if (sawPolyProtoStructure || !m_structureSet.contains(structure)) {
+            if (!sawPolyProtoStructure) {
                 ConcurrentJSLocker locker(m_lock);
                 m_structureSet.add(structure);
             }
index 0c37003..161bfe8 100644 (file)
@@ -85,7 +85,7 @@ class TypeSet : public ThreadSafeRefCounted<TypeSet> {
 public:
     static Ref<TypeSet> create() { return adoptRef(*new TypeSet); }
     TypeSet();
-    void addTypeInformation(RuntimeType, RefPtr<StructureShape>&&, Structure*);
+    void addTypeInformation(RuntimeType, RefPtr<StructureShape>&&, Structure*, bool sawPolyProtoStructure);
     void invalidateCache();
     String dumpTypes() const;
     String displayName() const;
index 2698a8c..a991386 100644 (file)
@@ -144,6 +144,7 @@ public:
     void setUndefined() { m_value = JSValue::encode(jsUndefined()); }
     void setStartingValue(JSValue value) { m_value = JSValue::encode(value); }
     bool isNumber() const { return get().isNumber(); }
+    bool isInt32() const { return get().isInt32(); }
     bool isObject() const { return get().isObject(); }
     bool isNull() const { return get().isNull(); }
     bool isGetterSetter() const { return get().isGetterSetter(); }
index c233abd..e1a812c 100644 (file)
@@ -1,3 +1,18 @@
+2017-10-03  Saam Barati  <sbarati@apple.com>
+
+        Implement polymorphic prototypes
+        https://bugs.webkit.org/show_bug.cgi?id=176391
+
+        Reviewed by Filip Pizlo.
+
+        * wtf/Box.h:
+        (WTF::Box::operator bool const):
+        (WTF::Box::operator bool): Deleted.
+        Make Box movable. Also ensure its operator bool doesn't do an atomic increment.
+        * wtf/RefPtr.h:
+        (WTF::RefPtr::operator bool const):
+        Add `explicit operator bool()` for RefPtr.
+
 2017-10-03  Antti Koivisto  <antti@apple.com>
 
         Allow assigning WeakPtr<Derived> to WeakPtr<Base>
index b661321..3d442d5 100644 (file)
@@ -36,14 +36,17 @@ namespace WTF {
 template<typename T>
 class Box {
 public:
-    Box()
-    {
-    }
+    Box() = default;
+    Box(Box&&) = default;
+    Box(const Box&) = default;
 
     Box(std::nullptr_t)
     {
     }
 
+    Box& operator=(Box&&) = default;
+    Box& operator=(const Box&) = default;
+
     template<typename... Arguments>
     static Box create(Arguments&&... arguments)
     {
@@ -57,7 +60,7 @@ public:
     T& operator*() const { return m_data->value; }
     T* operator->() const { return &m_data->value; }
 
-    explicit operator bool() { return m_data; }
+    explicit operator bool() const { return static_cast<bool>(m_data); }
     
 private:
     struct Data : ThreadSafeRefCounted<Data> {
index 07af35f..8d1415f 100644 (file)
@@ -83,6 +83,8 @@ public:
     // This conversion operator allows implicit conversion to bool but not to other integer types.
     typedef T* (RefPtr::*UnspecifiedBoolType);
     operator UnspecifiedBoolType() const { return m_ptr ? &RefPtr::m_ptr : nullptr; }
+
+    explicit operator bool() const { return !!m_ptr; }
     
     RefPtr& operator=(const RefPtr&);
     RefPtr& operator=(T*);
index cf89300..21b62ab 100644 (file)
@@ -1,3 +1,12 @@
+2017-10-03  Saam Barati  <sbarati@apple.com>
+
+        Implement polymorphic prototypes
+        https://bugs.webkit.org/show_bug.cgi?id=176391
+
+        Reviewed by Filip Pizlo.
+
+        * Scripts/run-jsc-stress-tests:
+
 2017-10-03  Myles C. Maxfield  <mmaxfield@apple.com> 
 
         Create a SPIR-V assembler 
index 2e1099b..b49dc88 100755 (executable)
@@ -619,7 +619,7 @@ def runFTLNoCJIT(*optionalTestSpecificOptions)
 end
 
 def runFTLNoCJITB3O1(*optionalTestSpecificOptions)
-    run("ftl-no-cjit-b3o1", "--useArrayAllocationProfiling=false", *(FTL_OPTIONS + NO_CJIT_OPTIONS + B3O1_OPTIONS + optionalTestSpecificOptions))
+    run("ftl-no-cjit-b3o1", "--useArrayAllocationProfiling=false", "--forcePolyProto=true", *(FTL_OPTIONS + NO_CJIT_OPTIONS + B3O1_OPTIONS + optionalTestSpecificOptions))
 end
 
 def runFTLNoCJITValidate(*optionalTestSpecificOptions)
@@ -647,7 +647,7 @@ def runDFGEagerNoCJITValidate(*optionalTestSpecificOptions)
 end
 
 def runFTLEager(*optionalTestSpecificOptions)
-    run("ftl-eager", "--airForceBriggsAllocator=true", *(FTL_OPTIONS + EAGER_OPTIONS + COLLECT_CONTINUOUSLY_OPTIONS + optionalTestSpecificOptions))
+    run("ftl-eager", "--airForceBriggsAllocator=true", "--forcePolyProto=true", *(FTL_OPTIONS + EAGER_OPTIONS + COLLECT_CONTINUOUSLY_OPTIONS + optionalTestSpecificOptions))
 end
 
 def runFTLEagerWatchdog(*optionalTestSpecificOptions)
@@ -864,6 +864,7 @@ def runTypeProfiler
 
     run("ftl-no-cjit-type-profiler", "--useTypeProfiler=true", *(FTL_OPTIONS + NO_CJIT_OPTIONS))
     run("ftl-type-profiler", "--useTypeProfiler=true", *(FTL_OPTIONS))
+    run("ftl-type-profiler-force-poly-proto-ftl-eager", "--useTypeProfiler=true", "--forcePolyProto=true", *(FTL_OPTIONS + EAGER_OPTIONS))
 end
 
 def runControlFlowProfiler