diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index 9471cd891b32..463f48ee8e2d 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -105,7 +105,7 @@ Zone::onTooMuchMalloc() } void -Zone::sweep(FreeOp *fop, bool releaseTypes) +Zone::sweep(FreeOp *fop, bool releaseTypes, bool *oom) { /* * Periodically release observed types for all scripts. This is safe to @@ -116,7 +116,7 @@ Zone::sweep(FreeOp *fop, bool releaseTypes) { gcstats::AutoPhase ap(fop->runtime()->gcStats, gcstats::PHASE_DISCARD_ANALYSIS); - types.sweep(fop, releaseTypes); + types.sweep(fop, releaseTypes, oom); } if (!fop->runtime()->debuggerList.isEmpty()) diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 28728dcd86bf..cbbde6b2e833 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -315,7 +315,7 @@ struct Zone : public JS::shadow::Zone, js::types::TypeZone types; - void sweep(js::FreeOp *fop, bool releaseTypes); + void sweep(js::FreeOp *fop, bool releaseTypes, bool *oom); bool hasMarkedCompartments(); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index aa2c6cee34f6..e44bdc5d7c35 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -2803,7 +2803,11 @@ static void FinishInvalidationOf(FreeOp *fop, JSScript *script, IonScript *ionScript) { types::TypeZone &types = script->zone()->types; - ionScript->recompileInfo().compilerOutput(types)->invalidate(); + + // Note: If the script is about to be swept, the compiler output may have + // already been destroyed. + if (types::CompilerOutput *output = ionScript->recompileInfo().compilerOutput(types)) + output->invalidate(); // If this script has Ion code on the stack, invalidated() will return // true. In this case we have to wait until destroying it. diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 0c70a4d1a6a3..7a96b528c49c 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3978,7 +3978,22 @@ BeginSweepingZoneGroup(JSRuntime *rt) for (GCZoneGroupIter zone(rt); !zone.done(); zone.next()) { gcstats::AutoSCC scc(rt->gcStats, rt->gcZoneGroupIndex); - zone->sweep(&fop, releaseTypes && !zone->isPreservingCode()); + + // If there is an OOM while sweeping types, the type information + // will be deoptimized so that it is still correct (i.e. + // overapproximates the possible types in the zone), but the + // constraints might not have been triggered on the deoptimization + // or even copied over completely. In this case, destroy all JIT + // code and new script addendums in the zone, the only things whose + // correctness depends on the type constraints. + bool oom = false; + zone->sweep(&fop, releaseTypes && !zone->isPreservingCode(), &oom); + + if (oom) { + zone->setPreservingCode(false); + zone->discardJitCode(&fop); + zone->types.clearAllNewScriptAddendumsOnOOM(); + } } } diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index fe42f39ba615..95f0ee050abd 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -37,6 +37,8 @@ #include "jsobjinlines.h" #include "jsscriptinlines.h" +#include "jit/ExecutionModeInlines.h" + using namespace js; using namespace js::gc; using namespace js::types; @@ -3120,6 +3122,29 @@ TypeObject::clearNewScriptAddendum(ExclusiveContext *cx) } } +void +TypeObject::maybeClearNewScriptAddendumOnOOM() +{ + if (!isMarked()) + return; + + if (!addendum || addendum->kind != TypeObjectAddendum::NewScript) + return; + + for (unsigned i = 0; i < getPropertyCount(); i++) { + Property *prop = getProperty(i); + if (!prop) + continue; + if (prop->types.definiteProperty()) + prop->types.setNonDataPropertyIgnoringConstraints(); + } + + // This method is called during GC sweeping, so there is no write barrier + // that needs to be triggered. + js_free(addendum); + addendum.unsafeSet(nullptr); +} + void TypeObject::clearTypedObjectAddendum(ExclusiveContext *cx) { @@ -3943,7 +3968,7 @@ ExclusiveContext::getSingletonType(const Class *clasp, TaggedProto proto) ///////////////////////////////////////////////////////////////////// void -ConstraintTypeSet::sweep(Zone *zone) +ConstraintTypeSet::sweep(Zone *zone, bool *oom) { /* * Purge references to type objects that are no longer live. Type sets hold @@ -3964,9 +3989,14 @@ ConstraintTypeSet::sweep(Zone *zone) TypeObjectKey **pentry = HashSetInsert (zone->types.typeLifoAlloc, objectSet, objectCount, object); - if (!pentry) - CrashAtUnhandlableOOM("OOM in ConstraintTypeSet::sweep"); - *pentry = object; + if (pentry) { + *pentry = object; + } else { + *oom = true; + flags |= TYPE_FLAG_ANYOBJECT; + clearObjects(); + return; + } } } setBaseObjectCount(objectCount); @@ -3987,10 +4017,12 @@ ConstraintTypeSet::sweep(Zone *zone) while (constraint) { TypeConstraint *copy; if (constraint->sweep(zone->types, ©)) { - if (!copy) - CrashAtUnhandlableOOM("OOM in ConstraintTypeSet::sweep"); - copy->next = constraintList; - constraintList = copy; + if (copy) { + copy->next = constraintList; + constraintList = copy; + } else { + *oom = true; + } } constraint = constraint->next; } @@ -4011,7 +4043,7 @@ TypeObject::clearProperties() * so that type objects do not need later finalization. */ inline void -TypeObject::sweep(FreeOp *fop) +TypeObject::sweep(FreeOp *fop, bool *oom) { if (!isMarked()) { if (addendum) @@ -4019,7 +4051,7 @@ TypeObject::sweep(FreeOp *fop) return; } - js::LifoAlloc &typeLifoAlloc = zone()->types.typeLifoAlloc; + LifoAlloc &typeLifoAlloc = zone()->types.typeLifoAlloc; /* * Properties were allocated from the old arena, and need to be copied over @@ -4046,17 +4078,21 @@ TypeObject::sweep(FreeOp *fop) } Property *newProp = typeLifoAlloc.new_(*prop); - if (!newProp) - CrashAtUnhandlableOOM("OOM in TypeObject::sweep"); + if (newProp) { + Property **pentry = + HashSetInsert + (typeLifoAlloc, propertySet, propertyCount, prop->id); + if (pentry) { + *pentry = newProp; + newProp->types.sweep(zone(), oom); + continue; + } + } - Property **pentry = - HashSetInsert - (typeLifoAlloc, propertySet, propertyCount, prop->id); - if (!pentry) - CrashAtUnhandlableOOM("OOM in TypeObject::sweep"); - - *pentry = newProp; - newProp->types.sweep(zone()); + *oom = true; + addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); + clearProperties(); + return; } } setBasePropertyCount(propertyCount); @@ -4067,18 +4103,17 @@ TypeObject::sweep(FreeOp *fop) clearProperties(); } else { Property *newProp = typeLifoAlloc.new_(*prop); - if (!newProp) - CrashAtUnhandlableOOM("OOM in TypeObject::sweep"); - - propertySet = (Property **) newProp; - newProp->types.sweep(zone()); + if (newProp) { + propertySet = (Property **) newProp; + newProp->types.sweep(zone(), oom); + } else { + *oom = true; + addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES); + clearProperties(); + return; + } } } - - if (basePropertyCount() <= SET_ARRAY_SIZE) { - for (unsigned i = 0; i < basePropertyCount(); i++) - JS_ASSERT(propertySet[i]); - } } void @@ -4196,7 +4231,7 @@ TypeCompartment::~TypeCompartment() } /* static */ void -TypeScript::Sweep(FreeOp *fop, JSScript *script) +TypeScript::Sweep(FreeOp *fop, JSScript *script, bool *oom) { JSCompartment *compartment = script->compartment(); JS_ASSERT(compartment->zone()->isGCSweeping()); @@ -4206,7 +4241,7 @@ TypeScript::Sweep(FreeOp *fop, JSScript *script) /* Remove constraints and references to dead objects from the persistent type sets. */ for (unsigned i = 0; i < num; i++) - typeArray[i].sweep(compartment->zone()); + typeArray[i].sweep(compartment->zone(), oom); } void @@ -4278,7 +4313,7 @@ TypeZone::~TypeZone() } void -TypeZone::sweep(FreeOp *fop, bool releaseTypes) +TypeZone::sweep(FreeOp *fop, bool releaseTypes, bool *oom) { JS_ASSERT(zone()->isGCSweeping()); @@ -4298,10 +4333,12 @@ TypeZone::sweep(FreeOp *fop, bool releaseTypes) CompilerOutput &output = (*compilerOutputs)[i]; if (output.isValid()) { JSScript *script = output.script(); - if (IsScriptAboutToBeFinalized(&script)) + if (IsScriptAboutToBeFinalized(&script)) { + jit::GetIonScript(script, output.mode())->recompileInfoRef() = uint32_t(-1); output.invalidate(); - else + } else { output.setSweepIndex(newCompilerOutputCount++); + } } } } @@ -4312,7 +4349,7 @@ TypeZone::sweep(FreeOp *fop, bool releaseTypes) for (CellIterUnderGC i(zone(), FINALIZE_SCRIPT); !i.done(); i.next()) { JSScript *script = i.get(); if (script->types) { - types::TypeScript::Sweep(fop, script); + types::TypeScript::Sweep(fop, script, oom); if (releaseTypes) { if (script->hasParallelIonScript()) { @@ -4351,7 +4388,7 @@ TypeZone::sweep(FreeOp *fop, bool releaseTypes) !iter.done(); iter.next()) { TypeObject *object = iter.get(); - object->sweep(fop); + object->sweep(fop, oom); } for (CompartmentsInZoneIter comp(zone()); !comp.done(); comp.next()) @@ -4386,6 +4423,17 @@ TypeZone::sweep(FreeOp *fop, bool releaseTypes) } } +void +TypeZone::clearAllNewScriptAddendumsOnOOM() +{ + for (gc::CellIterUnderGC iter(zone(), gc::FINALIZE_TYPE_OBJECT); + !iter.done(); iter.next()) + { + TypeObject *object = iter.get(); + object->maybeClearNewScriptAddendumOnOOM(); + } +} + #ifdef DEBUG void TypeScript::printTypes(JSContext *cx, HandleScript script) const diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index 56b1ebe5c6cb..f4ab6c85a269 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -607,7 +607,7 @@ class ConstraintTypeSet : public TypeSet /* Add a new constraint to this set. */ bool addConstraint(JSContext *cx, TypeConstraint *constraint, bool callExisting = true); - inline void sweep(JS::Zone *zone); + inline void sweep(JS::Zone *zone, bool *oom); }; class StackTypeSet : public ConstraintTypeSet @@ -622,6 +622,7 @@ class HeapTypeSet : public ConstraintTypeSet public: /* Mark this type set as representing a non-data property. */ inline void setNonDataProperty(ExclusiveContext *cx); + inline void setNonDataPropertyIgnoringConstraints(); // Variant for use during GC. /* Mark this type set as representing a non-writable property. */ inline void setNonWritableProperty(ExclusiveContext *cx); @@ -1122,13 +1123,14 @@ struct TypeObject : gc::BarrieredCell void clearAddendum(ExclusiveContext *cx); void clearNewScriptAddendum(ExclusiveContext *cx); void clearTypedObjectAddendum(ExclusiveContext *cx); + void maybeClearNewScriptAddendumOnOOM(); bool isPropertyNonData(jsid id); bool isPropertyNonWritable(jsid id); void print(); inline void clearProperties(); - inline void sweep(FreeOp *fop); + inline void sweep(FreeOp *fop, bool *oom); size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; @@ -1284,7 +1286,7 @@ class TypeScript static void Purge(JSContext *cx, HandleScript script); - static void Sweep(FreeOp *fop, JSScript *script); + static void Sweep(FreeOp *fop, JSScript *script, bool *oom); void destroy(); size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { @@ -1576,7 +1578,8 @@ struct TypeZone JS::Zone *zone() const { return zone_; } - void sweep(FreeOp *fop, bool releaseTypes); + void sweep(FreeOp *fop, bool releaseTypes, bool *oom); + void clearAllNewScriptAddendumsOnOOM(); /* Mark a script as needing recompilation once inference has finished. */ void addPendingRecompile(JSContext *cx, const RecompileInfo &info); diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index aa9fbf838445..16939de92965 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -1078,7 +1078,6 @@ TypeSet::addType(Type type, LifoAlloc *alloc) if (false) { unknownObject: - type = Type::AnyObjectType(); flags |= TYPE_FLAG_ANYOBJECT; clearObjects(); } @@ -1128,13 +1127,19 @@ HeapTypeSet::newPropertyState(ExclusiveContext *cxArg) } } +inline void +HeapTypeSet::setNonDataPropertyIgnoringConstraints() +{ + flags |= TYPE_FLAG_NON_DATA_PROPERTY; +} + inline void HeapTypeSet::setNonDataProperty(ExclusiveContext *cx) { if (flags & TYPE_FLAG_NON_DATA_PROPERTY) return; - flags |= TYPE_FLAG_NON_DATA_PROPERTY; + setNonDataPropertyIgnoringConstraints(); newPropertyState(cx); }