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:
win7-7 2025-12-22 15:05:22 +02:00 committed by wuggy
commit b77788a7bd
10 changed files with 293 additions and 209 deletions

View file

@ -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 =

View file

@ -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.

View file

@ -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)) {

View file

@ -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;

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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)
{

View file

@ -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

View file

@ -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

View file

@ -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;