mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 16:28:38 +09:00
1357680 and followup security fixes.
1357680 part 1 - Track Ion-inlined scripts explicitly so we can inline functions with unknown properties. 1357680 part 2 - Remove unnecessary setNewGroupUnknown call in SetClassAndProto. 1357680 part 3 - Don't mark the new group as having unknown properties when changing an object's proto. 1365518 - Fix polymorphic inlining to work correctly for functions that had their group/proto changed. 1368570 - Fix js::FinishCompilation to handle OOM correctly. 1395919 - Don't copy the unknown-properties flag in AddPropertyTypesAfterProtoChange. 1544386 part 1 - Call ElementAccessHasExtraIndexedProperty instead of ArrayPrototypeHasIndexedProperty when inlining array natives. 1538006 - Propagate unknownProperties when changing prototype. 1538006 - Don't emit unbarriered writes to an object if its group might change.
This commit is contained in:
parent
52c12f5041
commit
b77788a7bd
10 changed files with 293 additions and 209 deletions
|
|
@ -315,7 +315,7 @@ IonBuilder::getSingleCallTarget(TemporaryTypeSet* calleeTypes)
|
|||
|
||||
bool
|
||||
IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing,
|
||||
ObjectVector& targets, uint32_t maxTargets)
|
||||
InliningTargets& targets, uint32_t maxTargets)
|
||||
{
|
||||
MOZ_ASSERT(targets.empty());
|
||||
|
||||
|
|
@ -334,10 +334,11 @@ IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing,
|
|||
return false;
|
||||
for (unsigned i = 0; i < objCount; i++) {
|
||||
JSObject* obj = calleeTypes->getSingleton(i);
|
||||
ObjectGroup* group = nullptr;
|
||||
if (obj) {
|
||||
MOZ_ASSERT(obj->isSingleton());
|
||||
} else {
|
||||
ObjectGroup* group = calleeTypes->getGroup(i);
|
||||
group = calleeTypes->getGroup(i);
|
||||
if (!group)
|
||||
continue;
|
||||
|
||||
|
|
@ -358,7 +359,7 @@ IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing,
|
|||
return true;
|
||||
}
|
||||
|
||||
targets.infallibleAppend(obj);
|
||||
targets.infallibleAppend(InliningTarget(obj, group));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -544,12 +545,6 @@ IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo)
|
|||
return DontInline(inlineScript, "Script is debuggee");
|
||||
}
|
||||
|
||||
TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target);
|
||||
if (targetKey->unknownProperties()) {
|
||||
trackOptimizationOutcome(TrackedOutcome::CantInlineUnknownProps);
|
||||
return DontInline(inlineScript, "Target type has unknown properties");
|
||||
}
|
||||
|
||||
return InliningDecision_Inline;
|
||||
}
|
||||
|
||||
|
|
@ -5815,18 +5810,14 @@ IonBuilder::makeInliningDecision(JSObject* targetArg, CallInfo& callInfo)
|
|||
|
||||
// End of heuristics, we will inline this function.
|
||||
|
||||
// TI calls ObjectStateChange to trigger invalidation of the caller.
|
||||
TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target);
|
||||
targetKey->watchStateChangeForInlinedCall(constraints());
|
||||
|
||||
outerBuilder->inlinedBytecodeLength_ += targetScript->length();
|
||||
|
||||
return InliningDecision_Inline;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInfo, BoolVector& choiceSet,
|
||||
uint32_t* numInlineable)
|
||||
AbortReasonOr<Ok>
|
||||
IonBuilder::selectInliningTargets(const InliningTargets& targets, CallInfo& callInfo,
|
||||
BoolVector& choiceSet, uint32_t* numInlineable)
|
||||
{
|
||||
*numInlineable = 0;
|
||||
uint32_t totalSize = 0;
|
||||
|
|
@ -5841,7 +5832,7 @@ IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInf
|
|||
return true;
|
||||
|
||||
for (size_t i = 0; i < targets.length(); i++) {
|
||||
JSObject* target = targets[i];
|
||||
JSObject* target = targets[i].target;
|
||||
|
||||
trackOptimizationAttempt(TrackedStrategy::Call_Inline);
|
||||
trackTypeInfo(TrackedTypeSite::Call_Target, target);
|
||||
|
|
@ -5875,6 +5866,16 @@ IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInf
|
|||
inlineable = false;
|
||||
}
|
||||
|
||||
// Only use a group guard and inline the target if we will recompile when
|
||||
// the target function gets a new group.
|
||||
if (inlineable && targets[i].group) {
|
||||
ObjectGroup* group = targets[i].group;
|
||||
TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(group);
|
||||
if (!key->hasStableClassAndProto(constraints())) {
|
||||
inlineable = false;
|
||||
}
|
||||
}
|
||||
|
||||
choiceSet.infallibleAppend(inlineable);
|
||||
if (inlineable)
|
||||
*numInlineable += 1;
|
||||
|
|
@ -5885,7 +5886,7 @@ IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInf
|
|||
// depend on the types of the arguments and the return value.
|
||||
if (isOptimizationTrackingEnabled()) {
|
||||
for (size_t i = 0; i < targets.length(); i++) {
|
||||
if (choiceSet[i] && targets[i]->as<JSFunction>().isNative()) {
|
||||
if (choiceSet[i] && targets[i].target->as<JSFunction>().isNative()) {
|
||||
trackTypeInfo(callInfo);
|
||||
break;
|
||||
}
|
||||
|
|
@ -6033,8 +6034,8 @@ IonBuilder::inlineSingleCall(CallInfo& callInfo, JSObject* targetArg)
|
|||
return inlineScriptedCall(callInfo, target);
|
||||
}
|
||||
|
||||
IonBuilder::InliningStatus
|
||||
IonBuilder::inlineCallsite(const ObjectVector& targets, CallInfo& callInfo)
|
||||
IonBuilder::InliningResult
|
||||
IonBuilder::inlineCallsite(const InliningTargets& targets, CallInfo& callInfo)
|
||||
{
|
||||
if (targets.empty()) {
|
||||
trackOptimizationAttempt(TrackedStrategy::Call_Inline);
|
||||
|
|
@ -6051,7 +6052,7 @@ IonBuilder::inlineCallsite(const ObjectVector& targets, CallInfo& callInfo)
|
|||
// Inline single targets -- unless they derive from a cache, in which case
|
||||
// avoiding the cache and guarding is still faster.
|
||||
if (!propCache.get() && targets.length() == 1) {
|
||||
JSObject* target = targets[0];
|
||||
JSObject* target = targets[0].target;
|
||||
|
||||
trackOptimizationAttempt(TrackedStrategy::Call_Inline);
|
||||
trackTypeInfo(TrackedTypeSite::Call_Target, target);
|
||||
|
|
@ -6224,8 +6225,8 @@ IonBuilder::inlineObjectGroupFallback(CallInfo& callInfo, MBasicBlock* dispatchB
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::inlineCalls(CallInfo& callInfo, const ObjectVector& targets, BoolVector& choiceSet,
|
||||
AbortReasonOr<Ok>
|
||||
IonBuilder::inlineCalls(CallInfo& callInfo, const InliningTargets& targets, BoolVector& choiceSet,
|
||||
MGetPropertyCache* maybeCache)
|
||||
{
|
||||
// Only handle polymorphic inlining.
|
||||
|
|
@ -6297,7 +6298,7 @@ IonBuilder::inlineCalls(CallInfo& callInfo, const ObjectVector& targets, BoolVec
|
|||
amendOptimizationAttempt(i);
|
||||
|
||||
// Target must be reachable by the MDispatchInstruction.
|
||||
JSFunction* target = &targets[i]->as<JSFunction>();
|
||||
JSFunction* target = &targets[i].target->as<JSFunction>();
|
||||
if (maybeCache && !maybeCache->propTable()->hasFunction(target)) {
|
||||
choiceSet[i] = false;
|
||||
trackOptimizationOutcome(TrackedOutcome::CantInlineNotInDispatch);
|
||||
|
|
@ -6365,9 +6366,8 @@ IonBuilder::inlineCalls(CallInfo& callInfo, const ObjectVector& targets, BoolVec
|
|||
setCurrent(dispatchBlock);
|
||||
|
||||
// Connect the inline path to the returnBlock.
|
||||
ObjectGroup* funcGroup = target->isSingleton() ? nullptr : target->group();
|
||||
if (!dispatch->addCase(target, funcGroup, inlineBlock))
|
||||
return false;
|
||||
if (!dispatch->addCase(target, targets[i].group, inlineBlock))
|
||||
return abort(AbortReason::Alloc);
|
||||
|
||||
MDefinition* retVal = inlineReturnBlock->peek(-1);
|
||||
retPhi->addInput(retVal);
|
||||
|
|
@ -6454,8 +6454,9 @@ IonBuilder::inlineCalls(CallInfo& callInfo, const ObjectVector& targets, BoolVec
|
|||
continue;
|
||||
|
||||
MOZ_ASSERT(!remaining);
|
||||
if (targets[i]->is<JSFunction>() && targets[i]->as<JSFunction>().isSingleton())
|
||||
remaining = &targets[i]->as<JSFunction>();
|
||||
JSObject* target = targets[i].target;
|
||||
if (target->is<JSFunction>() && target->isSingleton())
|
||||
remaining = &target->as<JSFunction>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -7047,7 +7048,7 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing, bool ignoresReturnValue)
|
|||
int calleeDepth = -((int)argc + 2 + constructing);
|
||||
|
||||
// Acquire known call target if existent.
|
||||
ObjectVector targets(alloc());
|
||||
InliningTargets targets(alloc());
|
||||
TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet();
|
||||
if (calleeTypes && !getPolyCallTargets(calleeTypes, constructing, targets, 4))
|
||||
return false;
|
||||
|
|
@ -7068,8 +7069,8 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing, bool ignoresReturnValue)
|
|||
|
||||
// No inline, just make the call.
|
||||
JSFunction* target = nullptr;
|
||||
if (targets.length() == 1 && targets[0]->is<JSFunction>())
|
||||
target = &targets[0]->as<JSFunction>();
|
||||
if (targets.length() == 1 && targets[0].target->is<JSFunction>())
|
||||
target = &targets[0].target->as<JSFunction>();
|
||||
|
||||
if (target && status == InliningStatus_WarmUpCountTooLow) {
|
||||
MRecompileCheck* check =
|
||||
|
|
|
|||
|
|
@ -233,8 +233,8 @@ class IonBuilder
|
|||
void spew(const char* message);
|
||||
|
||||
JSFunction* getSingleCallTarget(TemporaryTypeSet* calleeTypes);
|
||||
MOZ_MUST_USE bool getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing,
|
||||
ObjectVector& targets, uint32_t maxTargets);
|
||||
AbortReasonOr<Ok> getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing,
|
||||
InliningTargets& targets, uint32_t maxTargets);
|
||||
|
||||
void popCfgStack();
|
||||
DeferredEdge* filterDeadDeferredEdges(DeferredEdge* edge);
|
||||
|
|
@ -830,7 +830,7 @@ class IonBuilder
|
|||
// Oracles.
|
||||
InliningDecision canInlineTarget(JSFunction* target, CallInfo& callInfo);
|
||||
InliningDecision makeInliningDecision(JSObject* target, CallInfo& callInfo);
|
||||
MOZ_MUST_USE bool selectInliningTargets(const ObjectVector& targets, CallInfo& callInfo,
|
||||
AbortReasonOr<Ok> selectInliningTargets(const InliningTargets& targets, CallInfo& callInfo,
|
||||
BoolVector& choiceSet, uint32_t* numInlineable);
|
||||
|
||||
// Native inlining helpers.
|
||||
|
|
@ -961,8 +961,8 @@ class IonBuilder
|
|||
InliningStatus inlineSingleCall(CallInfo& callInfo, JSObject* target);
|
||||
|
||||
// Call functions
|
||||
InliningStatus inlineCallsite(const ObjectVector& targets, CallInfo& callInfo);
|
||||
MOZ_MUST_USE bool inlineCalls(CallInfo& callInfo, const ObjectVector& targets,
|
||||
InliningResult inlineCallsite(const InliningTargets& targets, CallInfo& callInfo);
|
||||
AbortReasonOr<Ok> inlineCalls(CallInfo& callInfo, const InliningTargets& targets,
|
||||
BoolVector& choiceSet, MGetPropertyCache* maybeCache);
|
||||
|
||||
// Inlining helpers.
|
||||
|
|
|
|||
|
|
@ -610,10 +610,6 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode)
|
|||
const Class* clasp = thisTypes->getKnownClass(constraints());
|
||||
if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_)
|
||||
return InliningStatus_NotInlined;
|
||||
if (thisTypes->hasObjectFlags(constraints(), unhandledFlags)) {
|
||||
trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags);
|
||||
return InliningStatus_NotInlined;
|
||||
}
|
||||
|
||||
// Watch out for extra indexed properties on the object or its prototype.
|
||||
if (ElementAccessHasExtraIndexedProperty(this, obj)) {
|
||||
|
|
|
|||
|
|
@ -4909,14 +4909,14 @@ MGuardReceiverPolymorphic::congruentTo(const MDefinition* ins) const
|
|||
}
|
||||
|
||||
void
|
||||
InlinePropertyTable::trimTo(const ObjectVector& targets, const BoolVector& choiceSet)
|
||||
InlinePropertyTable::trimTo(const InliningTargets& targets, const BoolVector& choiceSet)
|
||||
{
|
||||
for (size_t i = 0; i < targets.length(); i++) {
|
||||
// If the target was inlined, don't erase the entry.
|
||||
if (choiceSet[i])
|
||||
continue;
|
||||
|
||||
JSFunction* target = &targets[i]->as<JSFunction>();
|
||||
JSFunction* target = &targets[i].target->as<JSFunction>();
|
||||
|
||||
// Eliminate all entries containing the vetoed function from the map.
|
||||
size_t j = 0;
|
||||
|
|
@ -4930,7 +4930,7 @@ InlinePropertyTable::trimTo(const ObjectVector& targets, const BoolVector& choic
|
|||
}
|
||||
|
||||
void
|
||||
InlinePropertyTable::trimToTargets(const ObjectVector& targets)
|
||||
InlinePropertyTable::trimToTargets(const InliningTargets& targets)
|
||||
{
|
||||
JitSpew(JitSpew_Inlining, "Got inlineable property cache with %d cases",
|
||||
(int)numEntries());
|
||||
|
|
@ -4939,7 +4939,7 @@ InlinePropertyTable::trimToTargets(const ObjectVector& targets)
|
|||
while (i < numEntries()) {
|
||||
bool foundFunc = false;
|
||||
for (size_t j = 0; j < targets.length(); j++) {
|
||||
if (entries_[i]->func == targets[j]) {
|
||||
if (entries_[i]->func == targets[j].target) {
|
||||
foundFunc = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -6102,10 +6102,14 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList*
|
|||
bool success = true;
|
||||
for (size_t i = 0; i < types->getObjectCount(); i++) {
|
||||
TypeSet::ObjectKey* key = types->getObject(i);
|
||||
if (!key)
|
||||
continue;
|
||||
if (!key->hasStableClassAndProto(constraints))
|
||||
return true;
|
||||
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!key->hasStableClassAndProto(constraints)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TI doesn't track TypedArray indexes and should never insert a type
|
||||
// barrier for them.
|
||||
|
|
@ -6157,10 +6161,14 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList*
|
|||
TypeSet::ObjectKey* excluded = nullptr;
|
||||
for (size_t i = 0; i < types->getObjectCount(); i++) {
|
||||
TypeSet::ObjectKey* key = types->getObject(i);
|
||||
if (!key)
|
||||
continue;
|
||||
if (!key->hasStableClassAndProto(constraints))
|
||||
return true;
|
||||
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!key->hasStableClassAndProto(constraints)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!name && IsTypedArrayClass(key->clasp()))
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -9539,8 +9539,25 @@ class MStoreFixedSlot
|
|||
ALLOW_CLONE(MStoreFixedSlot)
|
||||
};
|
||||
|
||||
typedef Vector<JSObject*, 4, JitAllocPolicy> ObjectVector;
|
||||
typedef Vector<bool, 4, JitAllocPolicy> BoolVector;
|
||||
struct InliningTarget
|
||||
{
|
||||
JSObject* target;
|
||||
|
||||
// If target is a singleton, group is nullptr. If target is not a singleton,
|
||||
// this is the group we need to guard on when doing a polymorphic inlining
|
||||
// dispatch. Note that this can be different from target->group() due to
|
||||
// proto mutation.
|
||||
ObjectGroup* group;
|
||||
|
||||
InliningTarget(JSObject* target, ObjectGroup* group)
|
||||
: target(target), group(group)
|
||||
{
|
||||
MOZ_ASSERT(target->isSingleton() == !group);
|
||||
}
|
||||
};
|
||||
|
||||
using InliningTargets = Vector<InliningTarget, 4, JitAllocPolicy>;
|
||||
using BoolVector = Vector<bool, 8, JitAllocPolicy>;
|
||||
|
||||
class InlinePropertyTable : public TempObject
|
||||
{
|
||||
|
|
@ -9604,10 +9621,10 @@ class InlinePropertyTable : public TempObject
|
|||
TemporaryTypeSet* buildTypeSetForFunction(JSFunction* func) const;
|
||||
|
||||
// Remove targets that vetoed inlining from the InlinePropertyTable.
|
||||
void trimTo(const ObjectVector& targets, const BoolVector& choiceSet);
|
||||
void trimTo(const InliningTargets& targets, const BoolVector& choiceSet);
|
||||
|
||||
// Ensure that the InlinePropertyTable's domain is a subset of |targets|.
|
||||
void trimToTargets(const ObjectVector& targets);
|
||||
void trimToTargets(const InliningTargets& targets);
|
||||
|
||||
bool appendRoots(MRootList& roots) const;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1892,27 +1892,38 @@ js::SetClassAndProto(JSContext* cx, HandleObject obj,
|
|||
return true;
|
||||
}
|
||||
|
||||
if (proto.isObject()) {
|
||||
RootedObject protoObj(cx, proto.toObject());
|
||||
if (!JSObject::setNewGroupUnknown(cx, clasp, protoObj))
|
||||
RootedObjectGroup oldGroup(cx, obj->group());
|
||||
|
||||
ObjectGroup* newGroup;
|
||||
if (oldGroup->maybeInterpretedFunction()) {
|
||||
// We're changing the group/proto of a scripted function. Create a new
|
||||
// group so we can keep track of the interpreted function for Ion
|
||||
// inlining.
|
||||
MOZ_ASSERT(obj->is<JSFunction>());
|
||||
newGroup = ObjectGroupCompartment::makeGroup(cx, &JSFunction::class_, proto);
|
||||
if (!newGroup)
|
||||
return false;
|
||||
newGroup->setInterpretedFunction(oldGroup->maybeInterpretedFunction());
|
||||
} else {
|
||||
newGroup = ObjectGroup::defaultNewGroup(cx, clasp, proto);
|
||||
if (!newGroup)
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectGroup* group = ObjectGroup::defaultNewGroup(cx, clasp, proto);
|
||||
if (!group)
|
||||
return false;
|
||||
obj->setGroup(newGroup);
|
||||
|
||||
/*
|
||||
* Setting __proto__ on an object that has escaped and may be referenced by
|
||||
* other heap objects can only be done if the properties of both objects
|
||||
* are unknown. Type sets containing this object will contain the original
|
||||
* type but not the new type of the object, so we need to treat all such
|
||||
* type sets as unknown.
|
||||
*/
|
||||
MarkObjectGroupUnknownProperties(cx, obj->group());
|
||||
MarkObjectGroupUnknownProperties(cx, group);
|
||||
// Add the object's property types to the new group.
|
||||
if (!newGroup->unknownProperties()) {
|
||||
if (obj->isNative())
|
||||
AddPropertyTypesAfterProtoChange(cx, &obj->as<NativeObject>(), oldGroup);
|
||||
else
|
||||
MarkObjectGroupUnknownProperties(cx, newGroup);
|
||||
}
|
||||
|
||||
obj->setGroup(group);
|
||||
// Type sets containing this object will contain the old group but not the
|
||||
// new group of the object, so we need to treat all such type sets as
|
||||
// unknown.
|
||||
MarkObjectGroupUnknownProperties(cx, oldGroup);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1130,6 +1130,53 @@ UpdateShapeTypeAndValue(ExclusiveContext* cx, HandleNativeObject obj, HandleShap
|
|||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
js::AddPropertyTypesAfterProtoChange(JSContext* cx, NativeObject* obj, ObjectGroup* oldGroup)
|
||||
{
|
||||
MOZ_ASSERT(obj->group() != oldGroup);
|
||||
MOZ_ASSERT(!obj->group()->unknownProperties());
|
||||
|
||||
if (oldGroup->unknownProperties()) {
|
||||
MarkObjectGroupUnknownProperties(cx, obj->group());
|
||||
return;
|
||||
}
|
||||
|
||||
// First copy the dynamic flags.
|
||||
MarkObjectGroupFlags(cx, obj, oldGroup->flags() & OBJECT_FLAG_DYNAMIC_MASK);
|
||||
|
||||
// Now update all property types. If the object has many properties, this
|
||||
// function may be slow so we mark all properties as unknown.
|
||||
static const size_t MaxPropertyCount = 40;
|
||||
|
||||
size_t nprops = obj->getDenseInitializedLength();
|
||||
if (nprops > MaxPropertyCount) {
|
||||
MarkObjectGroupUnknownProperties(cx, obj->group());
|
||||
return;
|
||||
}
|
||||
|
||||
// Add dense element types.
|
||||
for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
|
||||
Value val = obj->getDenseElement(i);
|
||||
if (!val.isMagic(JS_ELEMENTS_HOLE))
|
||||
AddTypePropertyId(cx, obj, JSID_VOID, val);
|
||||
}
|
||||
|
||||
// Add property types.
|
||||
for (Shape::Range<NoGC> r(obj->lastProperty()); !r.empty(); r.popFront()) {
|
||||
Shape* shape = &r.front();
|
||||
jsid id = shape->propid();
|
||||
if (JSID_IS_EMPTY(id))
|
||||
continue;
|
||||
|
||||
if (nprops++ > MaxPropertyCount) {
|
||||
MarkObjectGroupUnknownProperties(cx, obj->group());
|
||||
return;
|
||||
}
|
||||
|
||||
Value val = shape->hasSlot() ? obj->getSlot(shape->slot()) : UndefinedValue();
|
||||
UpdateShapeTypeAndValue(cx, obj, shape, id, val);
|
||||
}
|
||||
}
|
||||
static bool
|
||||
PurgeProtoChain(ExclusiveContext* cx, JSObject* objArg, HandleId id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1517,6 +1517,9 @@ MaybeNativeObject(JSObject* obj)
|
|||
// Defined in NativeObject-inl.h.
|
||||
bool IsPackedArray(JSObject* obj);
|
||||
|
||||
extern void
|
||||
AddPropertyTypesAfterProtoChange(JSContext* cx, NativeObject* obj, ObjectGroup* oldGroup);
|
||||
|
||||
} // namespace js
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1491,6 +1491,13 @@ js::FinishCompilation(JSContext* cx, HandleScript script, CompilerConstraintList
|
|||
succeeded = false;
|
||||
}
|
||||
|
||||
// Add this compilation to the inlinedCompilations list of each inlined
|
||||
// script, so we can invalidate it on changes to stack type sets.
|
||||
if (entry.script != script) {
|
||||
if (!entry.script->types()->addInlinedCompilation(*precompileInfo))
|
||||
succeeded = false;
|
||||
}
|
||||
|
||||
// If necessary, add constraints to trigger invalidation on the script
|
||||
// after any future changes to the stack type sets.
|
||||
if (entry.script->hasFreezeConstraints())
|
||||
|
|
@ -1880,37 +1887,6 @@ ObjectGroup::initialHeap(CompilerConstraintList* constraints)
|
|||
|
||||
namespace {
|
||||
|
||||
// Constraint which triggers recompilation on any type change in an inlined
|
||||
// script. The freeze constraints added to stack type sets will only directly
|
||||
// invalidate the script containing those stack type sets. To invalidate code
|
||||
// for scripts into which the base script was inlined, ObjectStateChange is used.
|
||||
class ConstraintDataFreezeObjectForInlinedCall
|
||||
{
|
||||
public:
|
||||
ConstraintDataFreezeObjectForInlinedCall()
|
||||
{}
|
||||
|
||||
const char* kind() { return "freezeObjectForInlinedCall"; }
|
||||
|
||||
bool invalidateOnNewType(TypeSet::Type type) { return false; }
|
||||
bool invalidateOnNewPropertyState(TypeSet* property) { return false; }
|
||||
bool invalidateOnNewObjectState(ObjectGroup* group) {
|
||||
// We don't keep track of the exact dependencies the caller has on its
|
||||
// inlined scripts' type sets, so always invalidate the caller.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool constraintHolds(JSContext* cx,
|
||||
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool shouldSweep() { return false; }
|
||||
|
||||
JSCompartment* maybeCompartment() { return nullptr; }
|
||||
};
|
||||
|
||||
// Constraint which triggers recompilation when a typed array's data becomes
|
||||
// invalid.
|
||||
class ConstraintDataFreezeObjectForTypedArrayData
|
||||
|
|
@ -1984,16 +1960,6 @@ class ConstraintDataFreezeObjectForUnboxedConvertedToNative
|
|||
|
||||
} /* anonymous namespace */
|
||||
|
||||
void
|
||||
TypeSet::ObjectKey::watchStateChangeForInlinedCall(CompilerConstraintList* constraints)
|
||||
{
|
||||
HeapTypeSetKey objectProperty = property(JSID_EMPTY);
|
||||
LifoAlloc* alloc = constraints->alloc();
|
||||
|
||||
typedef CompilerConstraintInstance<ConstraintDataFreezeObjectForInlinedCall> T;
|
||||
constraints->add(alloc->new_<T>(alloc, objectProperty, ConstraintDataFreezeObjectForInlinedCall()));
|
||||
}
|
||||
|
||||
void
|
||||
TypeSet::ObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList* constraints)
|
||||
{
|
||||
|
|
@ -2588,11 +2554,12 @@ TypeZone::addPendingRecompile(JSContext* cx, JSScript* script)
|
|||
if (script->hasIonScript())
|
||||
addPendingRecompile(cx, script->ionScript()->recompileInfo());
|
||||
|
||||
// When one script is inlined into another the caller listens to state
|
||||
// changes on the callee's script, so trigger these to force recompilation
|
||||
// of any such callers.
|
||||
if (script->functionNonDelazifying() && !script->functionNonDelazifying()->hasLazyGroup())
|
||||
ObjectStateChange(cx, script->functionNonDelazifying()->group(), false);
|
||||
// Trigger recompilation of any callers inlining this script.
|
||||
if (TypeScript* types = script->types()) {
|
||||
for (RecompileInfo info : types->inlinedCompilations())
|
||||
addPendingRecompile(cx, info);
|
||||
types->inlinedCompilations().clearAndFree();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -2884,6 +2851,9 @@ ObjectGroup::markStateChange(ExclusiveContext* cxArg)
|
|||
void
|
||||
ObjectGroup::setFlags(ExclusiveContext* cx, ObjectGroupFlags flags)
|
||||
{
|
||||
MOZ_ASSERT(!(flags & OBJECT_FLAG_UNKNOWN_PROPERTIES),
|
||||
"Should use markUnknown to set unknownProperties");
|
||||
|
||||
if (hasAllFlags(flags))
|
||||
return;
|
||||
|
||||
|
|
@ -4449,6 +4419,19 @@ JSScript::maybeSweepTypes(AutoClearTypeInferenceStateOnOOM* oom)
|
|||
|
||||
TypeZone& types = zone()->types;
|
||||
|
||||
// Sweep the inlinedCompilations Vector.
|
||||
{
|
||||
RecompileInfoVector& inlinedCompilations = types_->inlinedCompilations();
|
||||
size_t dest = 0;
|
||||
for (size_t i = 0; i < inlinedCompilations.length(); i++) {
|
||||
if (inlinedCompilations[i].shouldSweep(types))
|
||||
continue;
|
||||
inlinedCompilations[dest] = inlinedCompilations[i];
|
||||
dest++;
|
||||
}
|
||||
inlinedCompilations.shrinkTo(dest);
|
||||
}
|
||||
|
||||
// Destroy all type information attached to the script if desired. We can
|
||||
// only do this if nothing has been compiled for the script, which will be
|
||||
// the case unless the script has been compiled since we started sweeping.
|
||||
|
|
@ -4487,7 +4470,7 @@ JSScript::maybeSweepTypes(AutoClearTypeInferenceStateOnOOM* oom)
|
|||
void
|
||||
TypeScript::destroy()
|
||||
{
|
||||
js_free(this);
|
||||
js_delete(this);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -264,7 +264,6 @@ class TypeSet
|
|||
bool unknownProperties();
|
||||
bool hasFlags(CompilerConstraintList* constraints, ObjectGroupFlags flags);
|
||||
bool hasStableClassAndProto(CompilerConstraintList* constraints);
|
||||
void watchStateChangeForInlinedCall(CompilerConstraintList* constraints);
|
||||
void watchStateChangeForTypedArrayData(CompilerConstraintList* constraints);
|
||||
void watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints);
|
||||
HeapTypeSetKey property(jsid id);
|
||||
|
|
@ -1066,15 +1065,122 @@ inline bool isInlinableCall(jsbytecode* pc);
|
|||
bool
|
||||
ClassCanHaveExtraProperties(const Class* clasp);
|
||||
|
||||
|
||||
/*
|
||||
* Information about the result of the compilation of a script. This structure
|
||||
* stored in the TypeCompartment is indexed by the RecompileInfo. This
|
||||
* indirection enables the invalidation of all constraints related to the same
|
||||
* compilation.
|
||||
*/
|
||||
class CompilerOutput
|
||||
{
|
||||
// If this compilation has not been invalidated, the associated script and
|
||||
// kind of compilation being performed.
|
||||
JSScript* script_;
|
||||
|
||||
// Whether this compilation is about to be invalidated.
|
||||
bool pendingInvalidation_ : 1;
|
||||
|
||||
// During sweeping, the list of compiler outputs is compacted and invalidated
|
||||
// outputs are removed. This gives the new index for a valid compiler output.
|
||||
uint32_t sweepIndex_ : 31;
|
||||
|
||||
public:
|
||||
static const uint32_t INVALID_SWEEP_INDEX = static_cast<uint32_t>(1 << 31) - 1;
|
||||
|
||||
CompilerOutput()
|
||||
: script_(nullptr),
|
||||
pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX)
|
||||
{}
|
||||
|
||||
explicit CompilerOutput(JSScript* script)
|
||||
: script_(script),
|
||||
pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX)
|
||||
{}
|
||||
|
||||
JSScript* script() const { return script_; }
|
||||
|
||||
inline jit::IonScript* ion() const;
|
||||
|
||||
bool isValid() const {
|
||||
return script_ != nullptr;
|
||||
}
|
||||
void invalidate() {
|
||||
script_ = nullptr;
|
||||
}
|
||||
|
||||
void setPendingInvalidation() {
|
||||
pendingInvalidation_ = true;
|
||||
}
|
||||
bool pendingInvalidation() {
|
||||
return pendingInvalidation_;
|
||||
}
|
||||
|
||||
void setSweepIndex(uint32_t index) {
|
||||
if (index >= INVALID_SWEEP_INDEX)
|
||||
MOZ_CRASH();
|
||||
sweepIndex_ = index;
|
||||
}
|
||||
uint32_t sweepIndex() {
|
||||
MOZ_ASSERT(sweepIndex_ != INVALID_SWEEP_INDEX);
|
||||
return sweepIndex_;
|
||||
}
|
||||
};
|
||||
|
||||
class RecompileInfo
|
||||
{
|
||||
// Index in the TypeZone's compilerOutputs or sweepCompilerOutputs arrays,
|
||||
// depending on the generation value.
|
||||
uint32_t outputIndex : 31;
|
||||
|
||||
// If out of sync with the TypeZone's generation, this index is for the
|
||||
// zone's sweepCompilerOutputs rather than compilerOutputs.
|
||||
uint32_t generation : 1;
|
||||
|
||||
public:
|
||||
RecompileInfo(uint32_t outputIndex, uint32_t generation)
|
||||
: outputIndex(outputIndex), generation(generation)
|
||||
{}
|
||||
|
||||
RecompileInfo()
|
||||
: outputIndex(JS_BITMASK(31)), generation(0)
|
||||
{}
|
||||
|
||||
bool operator==(const RecompileInfo& other) const {
|
||||
return outputIndex == other.outputIndex && generation == other.generation;
|
||||
}
|
||||
|
||||
CompilerOutput* compilerOutput(TypeZone& types) const;
|
||||
CompilerOutput* compilerOutput(JSContext* cx) const;
|
||||
bool shouldSweep(TypeZone& types);
|
||||
};
|
||||
|
||||
// The RecompileInfoVector has a MinInlineCapacity of one so that invalidating a
|
||||
// single IonScript doesn't require an allocation.
|
||||
typedef Vector<RecompileInfo, 1, SystemAllocPolicy> RecompileInfoVector;
|
||||
/* Persistent type information for a script, retained across GCs. */
|
||||
class TypeScript
|
||||
{
|
||||
friend class ::JSScript;
|
||||
|
||||
// The freeze constraints added to stack type sets will only directly
|
||||
// invalidate the script containing those stack type sets. This Vector
|
||||
// contains compilations that inlined this script, so we can invalidate
|
||||
// them as well.
|
||||
RecompileInfoVector inlinedCompilations_;
|
||||
// Variable-size array
|
||||
StackTypeSet typeArray_[1];
|
||||
|
||||
public:
|
||||
RecompileInfoVector& inlinedCompilations() {
|
||||
return inlinedCompilations_;
|
||||
}
|
||||
[[nodiscard]] bool addInlinedCompilation(RecompileInfo info) {
|
||||
if (!inlinedCompilations_.empty() && inlinedCompilations_.back() == info)
|
||||
return true;
|
||||
return inlinedCompilations_.append(info);
|
||||
}
|
||||
|
||||
/* Array of type sets for variables and JOF_TYPESET ops. */
|
||||
StackTypeSet* typeArray() const {
|
||||
// Ensure typeArray_ is the last data member of TypeScript.
|
||||
|
|
@ -1208,94 +1314,6 @@ class HeapTypeSetKey
|
|||
bool couldBeConstant(CompilerConstraintList* constraints);
|
||||
};
|
||||
|
||||
/*
|
||||
* Information about the result of the compilation of a script. This structure
|
||||
* stored in the TypeCompartment is indexed by the RecompileInfo. This
|
||||
* indirection enables the invalidation of all constraints related to the same
|
||||
* compilation.
|
||||
*/
|
||||
class CompilerOutput
|
||||
{
|
||||
// If this compilation has not been invalidated, the associated script and
|
||||
// kind of compilation being performed.
|
||||
JSScript* script_;
|
||||
|
||||
// Whether this compilation is about to be invalidated.
|
||||
bool pendingInvalidation_ : 1;
|
||||
|
||||
// During sweeping, the list of compiler outputs is compacted and invalidated
|
||||
// outputs are removed. This gives the new index for a valid compiler output.
|
||||
uint32_t sweepIndex_ : 31;
|
||||
|
||||
public:
|
||||
static const uint32_t INVALID_SWEEP_INDEX = static_cast<uint32_t>(1 << 31) - 1;
|
||||
|
||||
CompilerOutput()
|
||||
: script_(nullptr),
|
||||
pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX)
|
||||
{}
|
||||
|
||||
explicit CompilerOutput(JSScript* script)
|
||||
: script_(script),
|
||||
pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX)
|
||||
{}
|
||||
|
||||
JSScript* script() const { return script_; }
|
||||
|
||||
inline jit::IonScript* ion() const;
|
||||
|
||||
bool isValid() const {
|
||||
return script_ != nullptr;
|
||||
}
|
||||
void invalidate() {
|
||||
script_ = nullptr;
|
||||
}
|
||||
|
||||
void setPendingInvalidation() {
|
||||
pendingInvalidation_ = true;
|
||||
}
|
||||
bool pendingInvalidation() {
|
||||
return pendingInvalidation_;
|
||||
}
|
||||
|
||||
void setSweepIndex(uint32_t index) {
|
||||
if (index >= INVALID_SWEEP_INDEX)
|
||||
MOZ_CRASH();
|
||||
sweepIndex_ = index;
|
||||
}
|
||||
uint32_t sweepIndex() {
|
||||
MOZ_ASSERT(sweepIndex_ != INVALID_SWEEP_INDEX);
|
||||
return sweepIndex_;
|
||||
}
|
||||
};
|
||||
|
||||
class RecompileInfo
|
||||
{
|
||||
// Index in the TypeZone's compilerOutputs or sweepCompilerOutputs arrays,
|
||||
// depending on the generation value.
|
||||
uint32_t outputIndex : 31;
|
||||
|
||||
// If out of sync with the TypeZone's generation, this index is for the
|
||||
// zone's sweepCompilerOutputs rather than compilerOutputs.
|
||||
uint32_t generation : 1;
|
||||
|
||||
public:
|
||||
RecompileInfo(uint32_t outputIndex, uint32_t generation)
|
||||
: outputIndex(outputIndex), generation(generation)
|
||||
{}
|
||||
|
||||
RecompileInfo()
|
||||
: outputIndex(JS_BITMASK(31)), generation(0)
|
||||
{}
|
||||
|
||||
CompilerOutput* compilerOutput(TypeZone& types) const;
|
||||
CompilerOutput* compilerOutput(JSContext* cx) const;
|
||||
bool shouldSweep(TypeZone& types);
|
||||
};
|
||||
|
||||
// The RecompileInfoVector has a MinInlineCapacity of one so that invalidating a
|
||||
// single IonScript doesn't require an allocation.
|
||||
typedef Vector<RecompileInfo, 1, SystemAllocPolicy> RecompileInfoVector;
|
||||
|
||||
struct AutoEnterAnalysis;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue