author | Brian Hackett <bhackett1024@gmail.com> |
Wed, 22 Aug 2012 12:28:34 -0600 | |
changeset 103038 | bf07c6253287bb55cb346002959048c15822ed67 |
parent 103037 | 98fc3d40910c19cbc1ab131488f81546ac089b06 |
child 103039 | 753d5e8c80640b35cd5bfd1b2b1cd8d009ac56ef |
push id | 13790 |
push user | bhackett@mozilla.com |
push date | Wed, 22 Aug 2012 18:28:52 +0000 |
treeherder | mozilla-inbound@bf07c6253287 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | luke |
bugs | 778724 |
milestone | 17.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -142,16 +142,17 @@ static nsITimer *sFullGCTimer; static nsITimer *sInterSliceGCTimer; static PRTime sLastCCEndTime; static bool sCCLockedOut; static PRTime sCCLockedOutTime; static js::GCSliceCallback sPrevGCSliceCallback; +static js::AnalysisPurgeCallback sPrevAnalysisPurgeCallback; // The number of currently pending document loads. This count isn't // guaranteed to always reflect reality and can't easily as we don't // have an easy place to know when a load ends or is interrupted in // all cases. This counter also gets reset if we end up GC'ing while // we're waiting for a slow page to load. IOW, this count may be 0 // even when there are pending loads. static uint32_t sPendingLoadCount; @@ -194,16 +195,27 @@ static PRTime sMaxChromeScriptRunTime; static nsIScriptSecurityManager *sSecurityManager; // nsMemoryPressureObserver observes the memory-pressure notifications // and forces a garbage collection and cycle collection when it happens, if // the appropriate pref is set. static bool sGCOnMemoryPressure; +static PRTime +GetCollectionTimeDelta() +{ + PRTime now = PR_Now(); + if (sFirstCollectionTime) { + return now - sFirstCollectionTime; + } + sFirstCollectionTime = now; + return 0; +} + class nsMemoryPressureObserver MOZ_FINAL : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER }; NS_IMPL_ISUPPORTS1(nsMemoryPressureObserver, nsIObserver) @@ -3112,22 +3124,17 @@ nsJSContext::CycleCollectNow(nsICycleCol uint32_t timeBetween = (uint32_t)(start - sLastCCEndTime) / PR_USEC_PER_SEC; Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_TIME_BETWEEN, timeBetween); } sLastCCEndTime = now; Telemetry::Accumulate(Telemetry::FORGET_SKIPPABLE_MAX, sMaxForgetSkippableTime / PR_USEC_PER_MSEC); - PRTime delta = 0; - if (sFirstCollectionTime) { - delta = now - sFirstCollectionTime; - } else { - sFirstCollectionTime = now; - } + PRTime delta = GetCollectionTimeDelta(); uint32_t cleanups = sForgetSkippableBeforeCC ? sForgetSkippableBeforeCC : 1; uint32_t minForgetSkippableTime = (sMinForgetSkippableTime == PR_UINT32_MAX) ? 0 : sMinForgetSkippableTime; if (sPostGCEventsToConsole) { nsCString mergeMsg; if (mergingCC) { @@ -3503,40 +3510,34 @@ NotifyGCEndRunnable::Run() } static void DOMGCSliceCallback(JSRuntime *aRt, js::GCProgress aProgress, const js::GCDescription &aDesc) { NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread"); if (aProgress == js::GC_CYCLE_END) { - PRTime now = PR_Now(); - PRTime delta = 0; - if (sFirstCollectionTime) { - delta = now - sFirstCollectionTime; - } else { - sFirstCollectionTime = now; - } + PRTime delta = GetCollectionTimeDelta(); if (sPostGCEventsToConsole) { NS_NAMED_LITERAL_STRING(kFmt, "GC(T+%.1f) "); nsString prefix, gcstats; gcstats.Adopt(aDesc.formatMessage(aRt)); prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(), double(delta) / PR_USEC_PER_SEC)); nsString msg = prefix + gcstats; nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); if (cs) { cs->LogStringMessage(msg.get()); } } if (sPostGCEventsToConsole || sPostGCEventsToObserver) { nsString json; - json.Adopt(aDesc.formatJSON(aRt, now)); + json.Adopt(aDesc.formatJSON(aRt, PR_Now())); nsRefPtr<NotifyGCEndRunnable> notify = new NotifyGCEndRunnable(json); NS_DispatchToMainThread(notify); } } // Prevent cycle collections and shrinking during incremental GC. if (aProgress == js::GC_CYCLE_BEGIN) { sCCLockedOut = true; @@ -3583,16 +3584,42 @@ DOMGCSliceCallback(JSRuntime *aRt, js::G nsJSContext::PokeShrinkGCBuffers(); } } if (sPrevGCSliceCallback) (*sPrevGCSliceCallback)(aRt, aProgress, aDesc); } +static void +DOMAnalysisPurgeCallback(JSRuntime *aRt, JSFlatString *aDesc) +{ + NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread"); + + PRTime delta = GetCollectionTimeDelta(); + + if (sPostGCEventsToConsole) { + NS_NAMED_LITERAL_STRING(kFmt, "Analysis Purge (T+%.1f) "); + nsString prefix; + prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(), + double(delta) / PR_USEC_PER_SEC)); + + nsDependentJSString stats(aDesc); + nsString msg = prefix + stats; + + nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (cs) { + cs->LogStringMessage(msg.get()); + } + } + + if (sPrevAnalysisPurgeCallback) + (*sPrevAnalysisPurgeCallback)(aRt, aDesc); +} + // Script object mananagement - note duplicate implementation // in nsJSRuntime below... nsresult nsJSContext::HoldScriptObject(void* aScriptObject) { NS_ASSERTION(sIsInitialized, "runtime not initialized"); if (! nsJSRuntime::sRuntime) { NS_NOTREACHED("couldn't add GC root - no runtime"); @@ -3999,16 +4026,17 @@ nsJSRuntime::Init() // JS_CompileFunction*). In practice, this means content scripts and event // handlers. JS_SetSourceHook(sRuntime, SourceHook); // Let's make sure that our main thread is the same as the xpcom main thread. NS_ASSERTION(NS_IsMainThread(), "bad"); sPrevGCSliceCallback = js::SetGCSliceCallback(sRuntime, DOMGCSliceCallback); + sPrevAnalysisPurgeCallback = js::SetAnalysisPurgeCallback(sRuntime, DOMAnalysisPurgeCallback); // Set up the structured clone callbacks. static JSStructuredCloneCallbacks cloneCallbacks = { NS_DOMReadStructuredClone, NS_DOMWriteStructuredClone, NS_DOMStructuredCloneError }; JS_SetStructuredCloneCallbacks(sRuntime, &cloneCallbacks); @@ -4093,16 +4121,22 @@ nsJSRuntime::Init() SetMemoryGCPrefChangedCallback("javascript.options.mem.gc_high_frequency_low_limit_mb", (void *)JSGC_HIGH_FREQUENCY_LOW_LIMIT); Preferences::RegisterCallback(SetMemoryGCPrefChangedCallback, "javascript.options.mem.gc_high_frequency_high_limit_mb"); SetMemoryGCPrefChangedCallback("javascript.options.mem.gc_high_frequency_high_limit_mb", (void *)JSGC_HIGH_FREQUENCY_HIGH_LIMIT); + Preferences::RegisterCallback(SetMemoryGCPrefChangedCallback, + "javascript.options.mem.analysis_purge_mb", + (void *)JSGC_ANALYSIS_PURGE_TRIGGER); + SetMemoryGCPrefChangedCallback("javascript.options.mem.analysis_purge_mb", + (void *)JSGC_ANALYSIS_PURGE_TRIGGER); + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); if (!obs) return NS_ERROR_FAILURE; Preferences::AddBoolVarCache(&sGCOnMemoryPressure, "javascript.options.gc_on_memory_pressure", true);
--- a/js/src/builtin/ParallelArray.cpp +++ b/js/src/builtin/ParallelArray.cpp @@ -958,18 +958,18 @@ ParallelArrayObject::create(JSContext *c return false; // Propagate element types. if (cx->typeInferenceEnabled()) { AutoEnterTypeInference enter(cx); TypeObject *bufferType = buffer->getType(cx); TypeObject *resultType = result->getType(cx); if (!bufferType->unknownProperties() && !resultType->unknownProperties()) { - TypeSet *bufferIndexTypes = bufferType->getProperty(cx, JSID_VOID, false); - TypeSet *resultIndexTypes = resultType->getProperty(cx, JSID_VOID, true); + HeapTypeSet *bufferIndexTypes = bufferType->getProperty(cx, JSID_VOID, false); + HeapTypeSet *resultIndexTypes = resultType->getProperty(cx, JSID_VOID, true); bufferIndexTypes->addSubset(cx, resultIndexTypes); } } // Store the dimension vector into a dense array for better GC / layout. RootedObject dimArray(cx, NewDenseArrayWithType(cx, dims.length())); if (!dimArray) return false;
--- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -801,16 +801,17 @@ static JSFunctionSpecWithHelp TestingFun " 5: Verify pre write barriers between paints\n" " 6: Verify stack rooting (ignoring XML and Reflect)\n" " 7: Verify stack rooting (all roots)\n" " 8: Incremental GC in two slices: 1) mark roots 2) finish collection\n" " 9: Incremental GC in two slices: 1) mark all 2) new marking and finish\n" " 10: Incremental GC in multiple slices\n" " 11: Verify post write barriers between instructions\n" " 12: Verify post write barriers between paints\n" +" 13: Purge analysis state when memory is allocated\n" " Period specifies that collection happens every n allocations.\n"), JS_FN_HELP("schedulegc", ScheduleGC, 1, 0, "schedulegc(num | obj)", " If num is given, schedule a GC after num allocations.\n" " If obj is given, schedule a GC of obj's compartment."), JS_FN_HELP("selectforgc", SelectForGC, 0, 0,
--- a/js/src/ds/LifoAlloc.h +++ b/js/src/ds/LifoAlloc.h @@ -258,22 +258,31 @@ class LifoAlloc break; JS_ASSERT(container != latest); container = container->next(); } latest = container; latest->release(mark); } + void releaseAll() { + JS_ASSERT(!markCount); + latest = first; + if (latest) + latest->resetBump(); + } + /* Get the total "used" (occupied bytes) count for the arena chunks. */ size_t used() const { size_t accum = 0; BumpChunk *it = first; while (it) { accum += it->used(); + if (it == latest) + break; it = it->next(); } return accum; } /* Get the total size of the arena chunks (including unused space). */ size_t sizeOfExcludingThis(JSMallocSizeOfFun mallocSizeOf) const { size_t accum = 0;
--- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2637,17 +2637,21 @@ frontend::EmitFunctionScript(JSContext * bce->switchToProlog(); if (Emit1(cx, bce, JSOP_GENERATOR) < 0) return false; bce->switchToMain(); } if (!EmitTree(cx, bce, body)) return false; - + + /* + * Always end the script with a JSOP_STOP. Some other parts of the codebase + * depend on this opcode, e.g. js_InternalInterpret. + */ if (Emit1(cx, bce, JSOP_STOP) < 0) return false; if (!JSScript::fullyInitFromEmitter(cx, bce->script, bce)) return false; /* Mark functions which will only be executed once as singletons. */
--- a/js/src/jsanalyze.cpp +++ b/js/src/jsanalyze.cpp @@ -42,17 +42,17 @@ inline bool ScriptAnalysis::addJump(JSContext *cx, unsigned offset, unsigned *currentOffset, unsigned *forwardJump, unsigned *forwardLoop, unsigned stackDepth) { JS_ASSERT(offset < script->length); Bytecode *&code = codeArray[offset]; if (!code) { - code = cx->typeLifoAlloc().new_<Bytecode>(); + code = cx->analysisLifoAlloc().new_<Bytecode>(); if (!code) { setOOM(cx); return false; } code->stackDepth = stackDepth; } JS_ASSERT(code->stackDepth == stackDepth); @@ -91,23 +91,23 @@ ScriptAnalysis::addJump(JSContext *cx, u return true; } void ScriptAnalysis::analyzeBytecode(JSContext *cx) { JS_ASSERT(cx->compartment->activeAnalysis); JS_ASSERT(!ranBytecode()); - LifoAlloc &tla = cx->typeLifoAlloc(); + LifoAlloc &alloc = cx->analysisLifoAlloc(); numSlots = TotalSlots(script); unsigned length = script->length; - codeArray = tla.newArray<Bytecode*>(length); - escapedSlots = tla.newArray<bool>(numSlots); + codeArray = alloc.newArray<Bytecode*>(length); + escapedSlots = alloc.newArray<bool>(numSlots); if (!codeArray || !escapedSlots) { setOOM(cx); return; } PodZero(codeArray, length); @@ -168,17 +168,17 @@ ScriptAnalysis::analyzeBytecode(JSContex /* * If we are in the middle of a try block, the offset of the highest * catch/finally/enditer. */ unsigned forwardCatch = 0; /* Fill in stack depth and definitions at initial bytecode. */ - Bytecode *startcode = tla.new_<Bytecode>(); + Bytecode *startcode = alloc.new_<Bytecode>(); if (!startcode) { setOOM(cx); return; } startcode->stackDepth = 0; codeArray[0] = startcode; @@ -265,20 +265,20 @@ ScriptAnalysis::analyzeBytecode(JSContex * Assign an observed type set to each reachable JOF_TYPESET opcode. * This may be less than the number of type sets in the script if some * are unreachable, and may be greater in case the number of type sets * overflows a uint16. In the latter case a single type set will be * used for the observed types of all ops after the overflow. */ if ((js_CodeSpec[op].format & JOF_TYPESET) && cx->typeInferenceEnabled()) { if (nTypeSets < script->nTypeSets) { - code->observedTypes = &typeArray[nTypeSets++]; + code->observedTypes = typeArray[nTypeSets++].toStackTypeSet(); } else { JS_ASSERT(nTypeSets == UINT16_MAX); - code->observedTypes = &typeArray[nTypeSets - 1]; + code->observedTypes = typeArray[nTypeSets - 1].toStackTypeSet(); } } switch (op) { case JSOP_RETURN: case JSOP_STOP: numReturnSites_++; @@ -455,16 +455,24 @@ ScriptAnalysis::analyzeBytecode(JSContex case JSOP_INCARG: case JSOP_DECARG: case JSOP_ARGINC: case JSOP_ARGDEC: modifiesArguments_ = true; isInlineable = false; break; + case JSOP_GETPROP: + case JSOP_CALLPROP: + case JSOP_LENGTH: + case JSOP_GETELEM: + case JSOP_CALLELEM: + numPropertyReads_++; + break; + /* Additional opcodes which can be compiled but which can't be inlined. */ case JSOP_ARGUMENTS: case JSOP_THROW: case JSOP_EXCEPTION: case JSOP_LAMBDA: case JSOP_DEBUGGER: case JSOP_FUNCALL: case JSOP_FUNAPPLY: @@ -504,21 +512,16 @@ ScriptAnalysis::analyzeBytecode(JSContex case JSOP_BITNOT: case JSOP_NEG: case JSOP_POS: case JSOP_DELPROP: case JSOP_DELELEM: case JSOP_TYPEOF: case JSOP_TYPEOFEXPR: case JSOP_VOID: - case JSOP_GETPROP: - case JSOP_CALLPROP: - case JSOP_LENGTH: - case JSOP_GETELEM: - case JSOP_CALLELEM: case JSOP_TOID: case JSOP_SETELEM: case JSOP_IMPLICITTHIS: case JSOP_DOUBLE: case JSOP_STRING: case JSOP_ZERO: case JSOP_ONE: case JSOP_NULL: @@ -597,17 +600,17 @@ ScriptAnalysis::analyzeBytecode(JSContex /* Handle any fallthrough from this opcode. */ if (!BytecodeNoFallThrough(op)) { JS_ASSERT(successorOffset < script->length); Bytecode *&nextcode = codeArray[successorOffset]; if (!nextcode) { - nextcode = tla.new_<Bytecode>(); + nextcode = alloc.new_<Bytecode>(); if (!nextcode) { setOOM(cx); return; } nextcode->stackDepth = stackDepth; } JS_ASSERT(nextcode->stackDepth == stackDepth); @@ -629,16 +632,35 @@ ScriptAnalysis::analyzeBytecode(JSContex /* * Always ensure that a script's arguments usage has been analyzed before * entering the script. This allows the functionPrologue to ensure that * arguments are always created eagerly which simplifies interp logic. */ if (!script->analyzedArgsUsage()) analyzeSSA(cx); + + /* + * If the script has JIT information (we are reanalyzing the script after + * a purge), add safepoints for the targets of any cross chunk edges in + * the script. These safepoints are normally added when the JITScript is + * constructed, but will have been lost during the purge. + */ +#ifdef JS_METHODJIT + mjit::JITScript *jit = NULL; + for (int constructing = 0; constructing <= 1 && !jit; constructing++) { + for (int barriers = 0; barriers <= 1 && !jit; barriers++) + jit = script->getJIT((bool) constructing, (bool) barriers); + } + if (jit) { + mjit::CrossChunkEdge *edges = jit->edges(); + for (size_t i = 0; i < jit->nedges; i++) + getCode(edges[i].target).safePoint = true; + } +#endif } ///////////////////////////////////////////////////////////////////// // Lifetime Analysis ///////////////////////////////////////////////////////////////////// void ScriptAnalysis::analyzeLifetimes(JSContext *cx) @@ -646,19 +668,19 @@ ScriptAnalysis::analyzeLifetimes(JSConte JS_ASSERT(cx->compartment->activeAnalysis && !ranLifetimes() && !failed()); if (!ranBytecode()) { analyzeBytecode(cx); if (failed()) return; } - LifoAlloc &tla = cx->typeLifoAlloc(); + LifoAlloc &alloc = cx->analysisLifoAlloc(); - lifetimes = tla.newArray<LifetimeVariable>(numSlots); + lifetimes = alloc.newArray<LifetimeVariable>(numSlots); if (!lifetimes) { setOOM(cx); return; } PodZero(lifetimes, numSlots); /* * Variables which are currently dead. On forward branches to locations @@ -770,17 +792,17 @@ ScriptAnalysis::analyzeLifetimes(JSConte break; } case JSOP_LOOKUPSWITCH: case JSOP_TABLESWITCH: /* Restore all saved variables. :FIXME: maybe do this precisely. */ for (unsigned i = 0; i < savedCount; i++) { LifetimeVariable &var = *saved[i]; - var.lifetime = tla.new_<Lifetime>(offset, var.savedEnd, var.saved); + var.lifetime = alloc.new_<Lifetime>(offset, var.savedEnd, var.saved); if (!var.lifetime) { cx->free_(saved); setOOM(cx); return; } var.saved = NULL; saved[i--] = saved[--savedCount]; } @@ -837,17 +859,17 @@ ScriptAnalysis::analyzeLifetimes(JSConte /* * If we already have a loop, it is an outer loop and we * need to prune the last block in the loop --- we do not * track 'continue' statements for outer loops. */ if (loop && loop->entry > loop->lastBlock) loop->lastBlock = loop->entry; - LoopAnalysis *nloop = tla.new_<LoopAnalysis>(); + LoopAnalysis *nloop = alloc.new_<LoopAnalysis>(); if (!nloop) { cx->free_(saved); setOOM(cx); return; } PodZero(nloop); if (loop) @@ -887,17 +909,17 @@ ScriptAnalysis::analyzeLifetimes(JSConte for (unsigned i = 0; i < savedCount; i++) { LifetimeVariable &var = *saved[i]; JS_ASSERT(!var.lifetime && var.saved); if (var.live(targetOffset)) { /* * Jumping to a place where this variable is live. Make a new * lifetime segment for the variable. */ - var.lifetime = tla.new_<Lifetime>(offset, var.savedEnd, var.saved); + var.lifetime = alloc.new_<Lifetime>(offset, var.savedEnd, var.saved); if (!var.lifetime) { cx->free_(saved); setOOM(cx); return; } var.saved = NULL; saved[i--] = saved[--savedCount]; } else if (loop && !var.savedEnd) { @@ -951,32 +973,32 @@ ScriptAnalysis::addVariable(JSContext *c for (unsigned i = 0; i < savedCount; i++) { if (saved[i] == &var) { JS_ASSERT(savedCount); saved[i--] = saved[--savedCount]; break; } } } - var.lifetime = cx->typeLifoAlloc().new_<Lifetime>(offset, var.savedEnd, var.saved); + var.lifetime = cx->analysisLifoAlloc().new_<Lifetime>(offset, var.savedEnd, var.saved); if (!var.lifetime) { setOOM(cx); return; } var.saved = NULL; } } inline void ScriptAnalysis::killVariable(JSContext *cx, LifetimeVariable &var, unsigned offset, LifetimeVariable **&saved, unsigned &savedCount) { if (!var.lifetime) { /* Make a point lifetime indicating the write. */ - Lifetime *lifetime = cx->typeLifoAlloc().new_<Lifetime>(offset, var.savedEnd, var.saved); + Lifetime *lifetime = cx->analysisLifoAlloc().new_<Lifetime>(offset, var.savedEnd, var.saved); if (!lifetime) { setOOM(cx); return; } if (!var.saved) saved[savedCount++] = &var; var.saved = lifetime; var.saved->write = true; @@ -997,17 +1019,17 @@ ScriptAnalysis::killVariable(JSContext * if (var.ensured) { /* * The variable is live even before the write, due to an enclosing try * block. We need to split the lifetime to indicate there was a write. * We set the new interval's savedEnd to 0, since it will always be * adjacent to the old interval, so it never needs to be extended. */ - var.lifetime = cx->typeLifoAlloc().new_<Lifetime>(start, 0, var.lifetime); + var.lifetime = cx->analysisLifoAlloc().new_<Lifetime>(start, 0, var.lifetime); if (!var.lifetime) { setOOM(cx); return; } var.lifetime->end = offset; } else { var.saved = var.lifetime; var.savedEnd = 0; @@ -1086,17 +1108,17 @@ ScriptAnalysis::extendVariable(JSContext if (segment->end >= end) { /* Variable known to be live after the loop finishes. */ break; } savedEnd = end; } JS_ASSERT(savedEnd <= end); if (savedEnd > segment->end) { - Lifetime *tail = cx->typeLifoAlloc().new_<Lifetime>(savedEnd, 0, segment->next); + Lifetime *tail = cx->analysisLifoAlloc().new_<Lifetime>(savedEnd, 0, segment->next); if (!tail) { setOOM(cx); return; } tail->start = segment->end; tail->loopTail = true; /* @@ -1163,17 +1185,17 @@ ScriptAnalysis::analyzeSSA(JSContext *cx JS_ASSERT(cx->compartment->activeAnalysis && !ranSSA() && !failed()); if (!ranLifetimes()) { analyzeLifetimes(cx); if (failed()) return; } - LifoAlloc &tla = cx->typeLifoAlloc(); + LifoAlloc &alloc = cx->analysisLifoAlloc(); unsigned maxDepth = script->nslots - script->nfixed; /* * Current value of each variable and stack value. Empty for missing or * untracked entries, i.e. escaping locals and arguments. */ SSAValueInfo *values = (SSAValueInfo *) cx->calloc_((numSlots + maxDepth) * sizeof(SSAValueInfo)); @@ -1347,17 +1369,17 @@ ScriptAnalysis::analyzeSSA(JSContext *cx unsigned nuses = GetUseCount(script, offset); unsigned ndefs = GetDefCount(script, offset); JS_ASSERT(stackDepth >= nuses); unsigned xuses = ExtendedUse(pc) ? nuses + 1 : nuses; if (xuses) { - code->poppedValues = tla.newArray<SSAValue>(xuses); + code->poppedValues = alloc.newArray<SSAValue>(xuses); if (!code->poppedValues) { setOOM(cx); return; } for (unsigned i = 0; i < nuses; i++) { SSAValue &v = stack[stackDepth - 1 - i].v; code->poppedValues[i] = v; v.clear(); @@ -1370,17 +1392,17 @@ ScriptAnalysis::analyzeSSA(JSContext *cx uint32_t slot = GetBytecodeSlot(script, pc); if (trackSlot(slot)) code->poppedValues[nuses] = values[slot].v; else code->poppedValues[nuses].clear(); } if (xuses) { - SSAUseChain *useChains = tla.newArray<SSAUseChain>(xuses); + SSAUseChain *useChains = alloc.newArray<SSAUseChain>(xuses); if (!useChains) { setOOM(cx); return; } PodZero(useChains, xuses); for (unsigned i = 0; i < xuses; i++) { const SSAValue &v = code->poppedValues[i]; if (trackUseChain(v)) { @@ -1397,17 +1419,17 @@ ScriptAnalysis::analyzeSSA(JSContext *cx stackDepth -= nuses; for (unsigned i = 0; i < ndefs; i++) stack[stackDepth + i].v.initPushed(offset, i); unsigned xdefs = ExtendedDef(pc) ? ndefs + 1 : ndefs; if (xdefs) { - code->pushedUses = tla.newArray<SSAUseChain *>(xdefs); + code->pushedUses = alloc.newArray<SSAUseChain *>(xdefs); if (!code->pushedUses) { setOOM(cx); return; } PodZero(code->pushedUses, xdefs); } stackDepth += ndefs; @@ -1578,18 +1600,18 @@ PhiNodeCapacity(unsigned length) unsigned log2; JS_FLOOR_LOG2(log2, length - 1); return 1 << (log2 + 1); } bool ScriptAnalysis::makePhi(JSContext *cx, uint32_t slot, uint32_t offset, SSAValue *pv) { - SSAPhiNode *node = cx->typeLifoAlloc().new_<SSAPhiNode>(); - SSAValue *options = cx->typeLifoAlloc().newArray<SSAValue>(PhiNodeCapacity(0)); + SSAPhiNode *node = cx->analysisLifoAlloc().new_<SSAPhiNode>(); + SSAValue *options = cx->analysisLifoAlloc().newArray<SSAValue>(PhiNodeCapacity(0)); if (!node || !options) { setOOM(cx); return false; } node->slot = slot; node->options = options; pv->initPhi(offset, node); return true; @@ -1611,17 +1633,17 @@ ScriptAnalysis::insertPhi(JSContext *cx, if (v == node->options[i]) return; } } if (trackUseChain(v)) { SSAUseChain *&uses = useChain(v); - SSAUseChain *use = cx->typeLifoAlloc().new_<SSAUseChain>(); + SSAUseChain *use = cx->analysisLifoAlloc().new_<SSAUseChain>(); if (!use) { setOOM(cx); return; } use->popped = false; use->offset = phi.phiOffset(); use->u.phi = node; @@ -1630,17 +1652,17 @@ ScriptAnalysis::insertPhi(JSContext *cx, } if (node->length < PhiNodeCapacity(node->length)) { node->options[node->length++] = v; return; } SSAValue *newOptions = - cx->typeLifoAlloc().newArray<SSAValue>(PhiNodeCapacity(node->length + 1)); + cx->analysisLifoAlloc().newArray<SSAValue>(PhiNodeCapacity(node->length + 1)); if (!newOptions) { setOOM(cx); return; } PodCopy(newOptions, node->options, node->length); node->options = newOptions; node->options[node->length++] = v; @@ -1830,17 +1852,17 @@ ScriptAnalysis::freezeNewValues(JSContex code.pendingValues = NULL; unsigned count = pending->length(); if (count == 0) { cx->delete_(pending); return; } - code.newValues = cx->typeLifoAlloc().newArray<SlotValue>(count + 1); + code.newValues = cx->analysisLifoAlloc().newArray<SlotValue>(count + 1); if (!code.newValues) { setOOM(cx); return; } for (unsigned i = 0; i < count; i++) code.newValues[i] = (*pending)[i]; code.newValues[count].slot = 0; @@ -2009,17 +2031,17 @@ CrossScriptSSA::foldValue(const CrossSSA case JSOP_TOID: { /* * TOID acts as identity for integers, so to get better precision * we should propagate its popped values forward if it acted as * identity. */ ScriptAnalysis *analysis = frame.script->analysis(); SSAValue toidv = analysis->poppedValue(pc, 0); - if (analysis->getValueTypes(toidv)->getKnownTypeTag(cx) == JSVAL_TYPE_INT32) + if (analysis->getValueTypes(toidv)->getKnownTypeTag() == JSVAL_TYPE_INT32) return foldValue(CrossSSAValue(cv.frame, toidv)); break; } default:; } }
--- a/js/src/jsanalyze.h +++ b/js/src/jsanalyze.h @@ -117,17 +117,17 @@ class Bytecode /* Stack depth before this opcode. */ uint32_t stackDepth; private: union { /* If this is a JOF_TYPESET opcode, index into the observed types for the op. */ - types::TypeSet *observedTypes; + types::StackTypeSet *observedTypes; /* If this is a loop head (TRACE or NOTRACE), information about the loop. */ LoopAnalysis *loop; }; /* --------- Lifetime analysis --------- */ /* Any allocation computed downstream for this bytecode. */ @@ -159,17 +159,17 @@ class Bytecode * for variables and entries live at the head of the loop. */ Vector<SlotValue> *pendingValues; }; /* --------- Type inference --------- */ /* Types for all values pushed by this bytecode. */ - types::TypeSet *pushedTypes; + types::StackTypeSet *pushedTypes; /* Any type barriers in place at this bytecode. */ types::TypeBarrier *typeBarriers; }; static inline unsigned GetDefCount(JSScript *script, unsigned offset) { @@ -734,17 +734,17 @@ class SSAValue /* * Mutable component of a phi node, with the possible values of the phi * and the possible types of the node as determined by type inference. * When phi nodes are copied around, any updates to the original will * be seen by all copies made. */ struct SSAPhiNode { - types::TypeSet types; + types::StackTypeSet types; uint32_t slot; uint32_t length; SSAValue *options; SSAUseChain *uses; SSAPhiNode() { PodZero(this); } }; inline uint32_t @@ -804,16 +804,17 @@ class ScriptAnalysis { friend class Bytecode; JSScript *script; Bytecode **codeArray; uint32_t numSlots; + uint32_t numPropertyReads_; bool outOfMemory; bool hadFailure; bool *escapedSlots; /* Which analyses have been performed. */ bool ranBytecode_; @@ -867,16 +868,19 @@ class ScriptAnalysis /* Analyze the effect of invoking 'new' on script. */ void analyzeTypesNew(JSContext *cx); bool OOM() { return outOfMemory; } bool failed() { return hadFailure; } bool inlineable(uint32_t argc) { return isInlineable && argc == script->function()->nargs; } bool jaegerCompileable() { return isJaegerCompileable; } + /* Number of property read opcodes in the script. */ + uint32_t numPropertyReads() const { return numPropertyReads_; } + /* Whether there are POPV/SETRVAL bytecodes which can write to the frame's rval. */ bool usesReturnValue() const { return usesReturnValue_; } /* Whether there are NAME bytecodes which can access the frame's scope chain. */ bool usesScopeChain() const { return usesScopeChain_; } bool usesThisValue() const { return usesThisValue_; } bool hasFunctionCalls() const { return hasFunctionCalls_; } @@ -920,17 +924,17 @@ class ScriptAnalysis return JSOp(*next) == JSOP_POP && !jumpTarget(next); } bool incrementInitialValueObserved(jsbytecode *pc) { const JSCodeSpec *cs = &js_CodeSpec[*pc]; return (cs->format & JOF_POST) && !popGuaranteed(pc); } - types::TypeSet *bytecodeTypes(const jsbytecode *pc) { + types::StackTypeSet *bytecodeTypes(const jsbytecode *pc) { JS_ASSERT(js_CodeSpec[*pc].format & JOF_TYPESET); return getCode(pc).observedTypes; } const SSAValue &poppedValue(uint32_t offset, uint32_t which) { JS_ASSERT(offset < script->length); JS_ASSERT(which < GetUseCount(script, offset) + (ExtendedUse(script->code + offset) ? 1 : 0)); @@ -941,25 +945,25 @@ class ScriptAnalysis } const SlotValue *newValues(uint32_t offset) { JS_ASSERT(offset < script->length); return getCode(offset).newValues; } const SlotValue *newValues(const jsbytecode *pc) { return newValues(pc - script->code); } - types::TypeSet *pushedTypes(uint32_t offset, uint32_t which = 0) { + types::StackTypeSet *pushedTypes(uint32_t offset, uint32_t which = 0) { JS_ASSERT(offset < script->length); JS_ASSERT(which < GetDefCount(script, offset) + (ExtendedDef(script->code + offset) ? 1 : 0)); - types::TypeSet *array = getCode(offset).pushedTypes; + types::StackTypeSet *array = getCode(offset).pushedTypes; JS_ASSERT(array); return array + which; } - types::TypeSet *pushedTypes(const jsbytecode *pc, uint32_t which) { + types::StackTypeSet *pushedTypes(const jsbytecode *pc, uint32_t which) { return pushedTypes(pc - script->code, which); } bool hasPushedTypes(const jsbytecode *pc) { return getCode(pc).pushedTypes != NULL; } types::TypeBarrier *typeBarriers(JSContext *cx, uint32_t offset) { if (getCode(offset).typeBarriers) pruneTypeBarriers(cx, offset); @@ -983,17 +987,17 @@ class ScriptAnalysis */ void breakTypeBarriers(JSContext *cx, uint32_t offset, bool all); /* Break all type barriers used in computing v. */ void breakTypeBarriersSSA(JSContext *cx, const SSAValue &v); inline void addPushedType(JSContext *cx, uint32_t offset, uint32_t which, types::Type type); - types::TypeSet *getValueTypes(const SSAValue &v) { + types::StackTypeSet *getValueTypes(const SSAValue &v) { switch (v.kind()) { case SSAValue::PUSHED: return pushedTypes(v.pushedOffset(), v.pushedIndex()); case SSAValue::VAR: JS_ASSERT(!slotEscapes(v.varSlot())); if (v.varInitial()) { return types::TypeScript::SlotTypes(script, v.varSlot()); } else { @@ -1009,20 +1013,20 @@ class ScriptAnalysis return &v.phiNode()->types; default: /* Cannot compute types for empty SSA values. */ JS_NOT_REACHED("Bad SSA value"); return NULL; } } - types::TypeSet *poppedTypes(uint32_t offset, uint32_t which) { + types::StackTypeSet *poppedTypes(uint32_t offset, uint32_t which) { return getValueTypes(poppedValue(offset, which)); } - types::TypeSet *poppedTypes(const jsbytecode *pc, uint32_t which) { + types::StackTypeSet *poppedTypes(const jsbytecode *pc, uint32_t which) { return getValueTypes(poppedValue(pc, which)); } /* Whether an arithmetic operation is operating on integers, with an integer result. */ bool integerOperation(JSContext *cx, jsbytecode *pc); bool trackUseChain(const SSAValue &v) { JS_ASSERT_IF(v.kind() == SSAValue::VAR, trackSlot(v.varSlot())); @@ -1152,19 +1156,22 @@ class ScriptAnalysis void mergeAllExceptionTargets(JSContext *cx, SSAValueInfo *values, const Vector<uint32_t> &exceptionTargets); void freezeNewValues(JSContext *cx, uint32_t offset); struct TypeInferenceState { Vector<SSAPhiNode *> phiNodes; bool hasGetSet; bool hasHole; - types::TypeSet *forTypes; + types::StackTypeSet *forTypes; + bool hasPropertyReadTypes; + uint32_t propertyReadIndex; TypeInferenceState(JSContext *cx) - : phiNodes(cx), hasGetSet(false), hasHole(false), forTypes(NULL) + : phiNodes(cx), hasGetSet(false), hasHole(false), forTypes(NULL), + hasPropertyReadTypes(false), propertyReadIndex(0) {} }; /* Type inference helpers */ bool analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeInferenceState &state); typedef Vector<SSAValue, 16> SeenVector; bool needsArgsObj(JSContext *cx, SeenVector &seen, const SSAValue &v); @@ -1264,17 +1271,17 @@ class CrossScriptSSA if (index == OUTER_FRAME) return 0; size_t res = outerFrame.script->length; for (unsigned i = 0; i < index; i++) res += inlineFrames[i].script->length; return res; } - types::TypeSet *getValueTypes(const CrossSSAValue &cv) { + types::StackTypeSet *getValueTypes(const CrossSSAValue &cv) { return getFrame(cv.frame).script->analysis()->getValueTypes(cv.v); } bool addInlineFrame(JSScript *script, uint32_t depth, uint32_t parent, jsbytecode *parentpc) { uint32_t index = inlineFrames.length(); return inlineFrames.append(Frame(index, script, depth, parent, parentpc)); }
--- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -802,16 +802,18 @@ JSRuntime::JSRuntime() gcNextScheduled(0), gcDeterministicOnly(false), gcIncrementalLimit(0), #endif gcValidate(true), gcCallback(NULL), gcSliceCallback(NULL), gcFinalizeCallback(NULL), + analysisPurgeCallback(NULL), + analysisPurgeTriggerBytes(0), gcMallocBytes(0), gcBlackRootsTraceOp(NULL), gcBlackRootsData(NULL), gcGrayRootsTraceOp(NULL), gcGrayRootsData(NULL), autoGCRooters(NULL), scriptAndCountsVector(NULL), NaNValue(UndefinedValue()), @@ -3057,16 +3059,19 @@ JS_SetGCParameter(JSRuntime *rt, JSGCPar rt->gcLowFrequencyHeapGrowth = value / 100.0; break; case JSGC_DYNAMIC_HEAP_GROWTH: rt->gcDynamicHeapGrowth = value; break; case JSGC_DYNAMIC_MARK_SLICE: rt->gcDynamicMarkSlice = value; break; + case JSGC_ANALYSIS_PURGE_TRIGGER: + rt->analysisPurgeTriggerBytes = value * 1024 * 1024; + break; default: JS_ASSERT(key == JSGC_MODE); rt->gcMode = JSGCMode(value); JS_ASSERT(rt->gcMode == JSGC_MODE_GLOBAL || rt->gcMode == JSGC_MODE_COMPARTMENT || rt->gcMode == JSGC_MODE_INCREMENTAL); return; } @@ -3103,16 +3108,18 @@ JS_GetGCParameter(JSRuntime *rt, JSGCPar case JSGC_HIGH_FREQUENCY_HEAP_GROWTH_MIN: return uint32_t(rt->gcHighFrequencyHeapGrowthMin * 100); case JSGC_LOW_FREQUENCY_HEAP_GROWTH: return uint32_t(rt->gcLowFrequencyHeapGrowth * 100); case JSGC_DYNAMIC_HEAP_GROWTH: return rt->gcDynamicHeapGrowth; case JSGC_DYNAMIC_MARK_SLICE: return rt->gcDynamicMarkSlice; + case JSGC_ANALYSIS_PURGE_TRIGGER: + return rt->analysisPurgeTriggerBytes / 1024 / 1024; default: JS_ASSERT(key == JSGC_NUMBER); return uint32_t(rt->gcNumber); } } JS_PUBLIC_API(void) JS_SetGCParameterForThread(JSContext *cx, JSGCParamKey key, uint32_t value) @@ -7096,17 +7103,18 @@ JS_SetGCZeal(JSContext *cx, uint8_t zeal " 4: Verify pre write barriers between instructions\n" " 5: Verify pre write barriers between paints\n" " 6: Verify stack rooting (ignoring XML and Reflect)\n" " 7: Verify stack rooting (all roots)\n" " 8: Incremental GC in two slices: 1) mark roots 2) finish collection\n" " 9: Incremental GC in two slices: 1) mark all 2) new marking and finish\n" " 10: Incremental GC in multiple slices\n" " 11: Verify post write barriers between instructions\n" - " 12: Verify post write barriers between paints\n"); + " 12: Verify post write barriers between paints\n" + " 13: Purge analysis state every F allocations (default: 100)\n"); } const char *p = strchr(env, ','); zeal = atoi(env); frequency = p ? atoi(p + 1) : JS_DEFAULT_ZEAL_FREQ; } JSRuntime *rt = cx->runtime;
--- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -4091,17 +4091,20 @@ typedef enum JSGCParamKey { /* * If false, the heap growth factor is fixed at 3. If true, it is determined * based on whether GCs are high- or low- frequency. */ JSGC_DYNAMIC_HEAP_GROWTH = 17, /* If true, high-frequency GCs will use a longer mark slice. */ - JSGC_DYNAMIC_MARK_SLICE = 18 + JSGC_DYNAMIC_MARK_SLICE = 18, + + /* Number of megabytes of analysis data to allocate before purging. */ + JSGC_ANALYSIS_PURGE_TRIGGER = 19 } JSGCParamKey; typedef enum JSGCMode { /* Perform only global GCs. */ JSGC_MODE_GLOBAL = 0, /* Perform per-compartment GCs until too much garbage has accumulated. */ JSGC_MODE_COMPARTMENT = 1,
--- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -682,16 +682,17 @@ struct JSRuntime : js::RuntimeFriendFiel js::Vector<JSObject *, 0, js::SystemAllocPolicy> gcSelectedForMarking; int gcZeal() { return gcZeal_; } bool needZealousGC() { if (gcNextScheduled > 0 && --gcNextScheduled == 0) { if (gcZeal() == js::gc::ZealAllocValue || + gcZeal() == js::gc::ZealPurgeAnalysisValue || (gcZeal() >= js::gc::ZealIncrementalRootsThenFinish && gcZeal() <= js::gc::ZealIncrementalMultipleSlices)) { gcNextScheduled = gcZealFrequency; } return true; } return false; @@ -702,16 +703,19 @@ struct JSRuntime : js::RuntimeFriendFiel #endif bool gcValidate; JSGCCallback gcCallback; js::GCSliceCallback gcSliceCallback; JSFinalizeCallback gcFinalizeCallback; + js::AnalysisPurgeCallback analysisPurgeCallback; + uint64_t analysisPurgeTriggerBytes; + private: /* * Malloc counter to measure memory pressure for GC scheduling. It runs * from gcMaxMallocBytes down to zero. */ volatile ptrdiff_t gcMallocBytes; public: @@ -1287,16 +1291,17 @@ struct JSContext : js::ContextFriendFiel return !!(runOptions & ropt); } bool hasStrictOption() const { return hasRunOption(JSOPTION_STRICT); } bool hasWErrorOption() const { return hasRunOption(JSOPTION_WERROR); } bool hasAtLineOption() const { return hasRunOption(JSOPTION_ATLINE); } js::LifoAlloc &tempLifoAlloc() { return runtime->tempLifoAlloc; } + inline js::LifoAlloc &analysisLifoAlloc(); inline js::LifoAlloc &typeLifoAlloc(); inline js::PropertyTree &propertyTree(); #ifdef JS_THREADSAFE unsigned outstandingRequests;/* number of JS_BeginRequest calls without the corresponding JS_EndRequest. */
--- a/js/src/jscntxtinlines.h +++ b/js/src/jscntxtinlines.h @@ -544,16 +544,21 @@ JSContext::setCompileOptions(unsigned ne JS_ASSERT((newcopts & JSCOMPILEOPTION_MASK) == newcopts); if (JS_LIKELY(getCompileOptions() == newcopts)) return; JSVersion version = findVersion(); JSVersion newVersion = js::OptionFlagsToVersion(newcopts, version); maybeOverrideVersion(newVersion); } +inline js::LifoAlloc & +JSContext::analysisLifoAlloc() +{ + return compartment->analysisLifoAlloc; +} inline js::LifoAlloc & JSContext::typeLifoAlloc() { return compartment->typeLifoAlloc; } inline void
--- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -50,17 +50,18 @@ JSCompartment::JSCompartment(JSRuntime * gcPreserveCode(false), gcBytes(0), gcTriggerBytes(0), gcHeapGrowthFactor(3.0), gcNextCompartment(NULL), hold(false), isSystemCompartment(false), lastCodeRelease(0), - typeLifoAlloc(TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE), + analysisLifoAlloc(LIFO_ALLOC_PRIMARY_CHUNK_SIZE), + typeLifoAlloc(LIFO_ALLOC_PRIMARY_CHUNK_SIZE), data(NULL), active(false), lastAnimationTime(0), regExps(rt), propertyTree(thisForCtor()), emptyTypeObject(NULL), gcMallocAndFreeBytes(0), gcTriggerMallocAndFreeBytes(0), @@ -574,21 +575,23 @@ JSCompartment::sweep(FreeOp *fop, bool r types.sweep(fop); } { gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_CLEAR_SCRIPT_ANALYSIS); for (CellIterUnderGC i(this, FINALIZE_SCRIPT); !i.done(); i.next()) { JSScript *script = i.get<JSScript>(); script->clearAnalysis(); + script->clearPropertyReadTypes(); } } { gcstats::AutoPhase ap2(rt->gcStats, gcstats::PHASE_FREE_TI_ARENA); + rt->freeLifoAlloc.transferFrom(&analysisLifoAlloc); rt->freeLifoAlloc.transferFrom(&oldAlloc); } } active = false; } /*
--- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -243,23 +243,21 @@ struct JSCompartment double gcHeapGrowthFactor; JSCompartment *gcNextCompartment; bool hold; bool isSystemCompartment; int64_t lastCodeRelease; - /* - * Pool for analysis and intermediate type information in this compartment. - * Cleared on every GC, unless the GC happens during analysis (indicated - * by activeAnalysis, which is implied by activeInference). - */ - static const size_t TYPE_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 128 * 1024; + /* Pools for analysis and type information in this compartment. */ + static const size_t LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 128 * 1024; + js::LifoAlloc analysisLifoAlloc; js::LifoAlloc typeLifoAlloc; + bool activeAnalysis; bool activeInference; /* Type information about the scripts and objects in this compartment. */ js::types::TypeCompartment types; void *data; bool active; // GC flag, whether there are active frames
--- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -793,16 +793,24 @@ GCDescription::formatMessage(JSRuntime * } jschar * GCDescription::formatJSON(JSRuntime *rt, uint64_t timestamp) const { return rt->gcStats.formatJSON(timestamp); } +JS_FRIEND_API(AnalysisPurgeCallback) +SetAnalysisPurgeCallback(JSRuntime *rt, AnalysisPurgeCallback callback) +{ + AnalysisPurgeCallback old = rt->analysisPurgeCallback; + rt->analysisPurgeCallback = callback; + return old; +} + JS_FRIEND_API(void) NotifyDidPaint(JSRuntime *rt) { if (rt->gcZeal() == gc::ZealFrameVerifierPreValue) { gc::VerifyBarriers(rt, gc::PreBarrierVerifier); return; }
--- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -794,24 +794,29 @@ struct JS_FRIEND_API(GCDescription) { }; typedef void (* GCSliceCallback)(JSRuntime *rt, GCProgress progress, const GCDescription &desc); extern JS_FRIEND_API(GCSliceCallback) SetGCSliceCallback(JSRuntime *rt, GCSliceCallback callback); +typedef void +(* AnalysisPurgeCallback)(JSRuntime *rt, JSFlatString *desc); + +extern JS_FRIEND_API(AnalysisPurgeCallback) +SetAnalysisPurgeCallback(JSRuntime *rt, AnalysisPurgeCallback callback); + /* Was the most recent GC run incrementally? */ extern JS_FRIEND_API(bool) WasIncrementalGC(JSRuntime *rt); typedef JSBool (* DOMInstanceClassMatchesProto)(JSHandleObject protoObject, uint32_t protoID, uint32_t depth); - struct JSDOMCallbacks { DOMInstanceClassMatchesProto instanceClassMatchesProto; }; typedef struct JSDOMCallbacks DOMCallbacks; extern JS_FRIEND_API(void) SetDOMCallbacks(JSRuntime *rt, const DOMCallbacks *callbacks);
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -4788,16 +4788,19 @@ RunDebugGC(JSContext *cx) * For multi-slice zeal, reset the slice size when we get to the sweep * phase. */ if (type == ZealIncrementalMultipleSlices && initialState == MARK && rt->gcIncrementalState == SWEEP) { rt->gcIncrementalLimit = rt->gcZealFrequency / 2; } + } else if (type == ZealPurgeAnalysisValue) { + if (!cx->compartment->activeAnalysis) + cx->compartment->types.maybePurgeAnalysis(cx, /* force = */ true); } else { Collect(rt, false, SliceBudget::Unlimited, GC_NORMAL, gcreason::DEBUG_GC); } #endif } void
--- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -1160,16 +1160,17 @@ const int ZealVerifierPreValue = 4; const int ZealFrameVerifierPreValue = 5; const int ZealStackRootingSafeValue = 6; const int ZealStackRootingValue = 7; const int ZealIncrementalRootsThenFinish = 8; const int ZealIncrementalMarkAllThenFinish = 9; const int ZealIncrementalMultipleSlices = 10; const int ZealVerifierPostValue = 11; const int ZealFrameVerifierPostValue = 12; +const int ZealPurgeAnalysisValue = 13; enum VerifierType { PreBarrierVerifier, PostBarrierVerifier }; #ifdef JS_GC_ZEAL
--- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -325,32 +325,58 @@ types::TypeFailure(JSContext *cx, const MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__); MOZ_CRASH(); } ///////////////////////////////////////////////////////////////////// // TypeSet ///////////////////////////////////////////////////////////////////// -TypeSet * -TypeSet::make(JSContext *cx, const char *name) -{ - JS_ASSERT(cx->compartment->activeInference); - - TypeSet *res = cx->typeLifoAlloc().new_<TypeSet>(); - if (!res) { - cx->compartment->types.setPendingNukeTypes(cx); - return NULL; - } - - InferSpew(ISpewOps, "typeSet: %sT%p%s intermediate %s", - InferSpewColor(res), res, InferSpewColorReset(), - name); - - return res; +inline void +TypeSet::addTypesToConstraint(JSContext *cx, TypeConstraint *constraint) +{ + /* + * Build all types in the set into a vector before triggering the + * constraint, as doing so may modify this type set. + */ + Vector<Type> types(cx); + + /* If any type is possible, there's no need to worry about specifics. */ + if (flags & TYPE_FLAG_UNKNOWN) { + if (!types.append(Type::UnknownType())) + cx->compartment->types.setPendingNukeTypes(cx); + } else { + /* Enqueue type set members stored as bits. */ + for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) { + if (flags & flag) { + Type type = Type::PrimitiveType(TypeFlagPrimitive(flag)); + if (!types.append(type)) + cx->compartment->types.setPendingNukeTypes(cx); + } + } + + /* If any object is possible, skip specifics. */ + if (flags & TYPE_FLAG_ANYOBJECT) { + if (!types.append(Type::AnyObjectType())) + cx->compartment->types.setPendingNukeTypes(cx); + } else { + /* Enqueue specific object types. */ + unsigned count = getObjectCount(); + for (unsigned i = 0; i < count; i++) { + TypeObjectKey *object = getObject(i); + if (object) { + if (!types.append(Type::ObjectType(object))) + cx->compartment->types.setPendingNukeTypes(cx); + } + } + } + } + + for (unsigned i = 0; i < types.length(); i++) + constraint->newType(cx, this, types[i]); } inline void TypeSet::add(JSContext *cx, TypeConstraint *constraint, bool callExisting) { if (!constraint) { /* OOM failure while constructing the constraint. */ cx->compartment->types.setPendingNukeTypes(cx); @@ -363,58 +389,29 @@ TypeSet::add(JSContext *cx, TypeConstrai InferSpewColor(this), this, InferSpewColorReset(), InferSpewColor(constraint), constraint, InferSpewColorReset(), constraint->kind()); JS_ASSERT(constraint->next == NULL); constraint->next = constraintList; constraintList = constraint; - if (!callExisting) - return; - - /* If any type is possible, there's no need to worry about specifics. */ - if (flags & TYPE_FLAG_UNKNOWN) { - cx->compartment->types.addPending(cx, constraint, this, Type::UnknownType()); - } else { - /* Enqueue type set members stored as bits. */ - for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) { - if (flags & flag) { - Type type = Type::PrimitiveType(TypeFlagPrimitive(flag)); - cx->compartment->types.addPending(cx, constraint, this, type); - } - } - - /* If any object is possible, skip specifics. */ - if (flags & TYPE_FLAG_ANYOBJECT) { - cx->compartment->types.addPending(cx, constraint, this, Type::AnyObjectType()); - } else { - /* Enqueue specific object types. */ - unsigned count = getObjectCount(); - for (unsigned i = 0; i < count; i++) { - TypeObjectKey *object = getObject(i); - if (object) - cx->compartment->types.addPending(cx, constraint, this, - Type::ObjectType(object)); - } - } - } - - cx->compartment->types.resolvePending(cx); + if (callExisting) + addTypesToConstraint(cx, constraint); } void TypeSet::print(JSContext *cx) { if (flags & TYPE_FLAG_OWN_PROPERTY) printf(" [own]"); if (flags & TYPE_FLAG_CONFIGURED_PROPERTY) printf(" [configured]"); - if (isDefiniteProperty()) + if (definiteProperty()) printf(" [definite:%d]", definiteSlot()); if (baseFlags() == 0 && !baseObjectCount()) { printf(" missing"); return; } if (flags & TYPE_FLAG_UNKNOWN) @@ -445,331 +442,357 @@ TypeSet::print(JSContext *cx) for (unsigned i = 0; i < count; i++) { TypeObjectKey *object = getObject(i); if (object) printf(" %s", TypeString(Type::ObjectType(object))); } } } -bool -TypeSet::propertyNeedsBarrier(JSContext *cx, jsid id) -{ - id = MakeTypeId(cx, id); - - if (unknownObject()) - return true; - - for (unsigned i = 0; i < getObjectCount(); i++) { - if (getSingleObject(i)) - return true; - - if (types::TypeObject *otype = getTypeObject(i)) { - if (otype->unknownProperties()) - return true; - - if (types::TypeSet *propTypes = otype->maybeGetProperty(cx, id)) { - if (propTypes->needsBarrier(cx)) - return true; - } - } - } - - addFreeze(cx); - return false; +StackTypeSet * +StackTypeSet::make(JSContext *cx, const char *name) +{ + JS_ASSERT(cx->compartment->activeInference); + + StackTypeSet *res = cx->analysisLifoAlloc().new_<StackTypeSet>(); + if (!res) { + cx->compartment->types.setPendingNukeTypes(cx); + return NULL; + } + + InferSpew(ISpewOps, "typeSet: %sT%p%s intermediate %s", + InferSpewColor(res), res, InferSpewColorReset(), + name); + res->setPurged(); + + return res; } ///////////////////////////////////////////////////////////////////// // TypeSet constraints ///////////////////////////////////////////////////////////////////// /* Standard subset constraint, propagate all types from one set to another. */ class TypeConstraintSubset : public TypeConstraint { public: TypeSet *target; TypeConstraintSubset(TypeSet *target) - : TypeConstraint("subset"), target(target) + : target(target) { JS_ASSERT(target); } + const char *kind() { return "subset"; } + void newType(JSContext *cx, TypeSet *source, Type type) { /* Basic subset constraint, move all types to the target. */ target->addType(cx, type); } }; void -TypeSet::addSubset(JSContext *cx, TypeSet *target) -{ +StackTypeSet::addSubset(JSContext *cx, TypeSet *target) +{ + add(cx, cx->analysisLifoAlloc().new_<TypeConstraintSubset>(target)); +} + +void +HeapTypeSet::addSubset(JSContext *cx, TypeSet *target) +{ + JS_ASSERT(!target->purged()); add(cx, cx->typeLifoAlloc().new_<TypeConstraintSubset>(target)); } +enum PropertyAccessKind { + PROPERTY_WRITE, + PROPERTY_READ, + PROPERTY_READ_EXISTING +}; + /* Constraints for reads/writes on object properties. */ +template <PropertyAccessKind access> class TypeConstraintProp : public TypeConstraint { public: JSScript *script; jsbytecode *pc; /* * If assign is true, the target is used to update a property of the object. * If assign is false, the target is assigned the value of the property. */ - bool assign; - TypeSet *target; + StackTypeSet *target; /* Property being accessed. */ jsid id; - TypeConstraintProp(JSScript *script, jsbytecode *pc, - TypeSet *target, jsid id, bool assign) - : TypeConstraint("prop"), script(script), pc(pc), - assign(assign), target(target), id(id) + TypeConstraintProp(JSScript *script, jsbytecode *pc, StackTypeSet *target, jsid id) + : script(script), pc(pc), target(target), id(id) { JS_ASSERT(script && pc && target); } + const char *kind() { return "prop"; } + void newType(JSContext *cx, TypeSet *source, Type type); }; +typedef TypeConstraintProp<PROPERTY_WRITE> TypeConstraintSetProperty; +typedef TypeConstraintProp<PROPERTY_READ> TypeConstraintGetProperty; +typedef TypeConstraintProp<PROPERTY_READ_EXISTING> TypeConstraintGetPropertyExisting; + void -TypeSet::addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, - TypeSet *target, jsid id) -{ - add(cx, cx->typeLifoAlloc().new_<TypeConstraintProp>(script, pc, target, id, false)); +StackTypeSet::addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, + StackTypeSet *target, jsid id) +{ + /* + * GetProperty constraints are normally used with property read input type + * sets, except for array_pop/array_shift special casing. + */ + JS_ASSERT(js_CodeSpec[*pc].format & JOF_INVOKE); + + add(cx, cx->analysisLifoAlloc().new_<TypeConstraintGetProperty>(script, pc, target, id)); } void -TypeSet::addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, - TypeSet *target, jsid id) -{ - add(cx, cx->typeLifoAlloc().new_<TypeConstraintProp>(script, pc, target, id, true)); +StackTypeSet::addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, + StackTypeSet *target, jsid id) +{ + add(cx, cx->analysisLifoAlloc().new_<TypeConstraintSetProperty>(script, pc, target, id)); +} + +void +HeapTypeSet::addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, + StackTypeSet *target, jsid id) +{ + JS_ASSERT(!target->purged()); + add(cx, cx->typeLifoAlloc().new_<TypeConstraintGetProperty>(script, pc, target, id)); } /* * Constraints for updating the 'this' types of callees on CALLPROP/CALLELEM. * These are derived from the types on the properties themselves, rather than * those pushed in the 'this' slot at the call site, which allows us to retain * correlations between the type of the 'this' object and the associated * callee scripts at polymorphic call sites. */ +template <PropertyAccessKind access> class TypeConstraintCallProp : public TypeConstraint { public: JSScript *script; jsbytecode *callpc; /* Property being accessed. */ jsid id; TypeConstraintCallProp(JSScript *script, jsbytecode *callpc, jsid id) - : TypeConstraint("callprop"), script(script), callpc(callpc), id(id) + : script(script), callpc(callpc), id(id) { JS_ASSERT(script && callpc); } + const char *kind() { return "callprop"; } + void newType(JSContext *cx, TypeSet *source, Type type); }; +typedef TypeConstraintCallProp<PROPERTY_READ> TypeConstraintCallProperty; +typedef TypeConstraintCallProp<PROPERTY_READ_EXISTING> TypeConstraintCallPropertyExisting; + void -TypeSet::addCallProperty(JSContext *cx, JSScript *script, jsbytecode *pc, jsid id) +HeapTypeSet::addCallProperty(JSContext *cx, JSScript *script, jsbytecode *pc, jsid id) { /* * For calls which will go through JSOP_NEW, don't add any constraints to * modify the 'this' types of callees. The initial 'this' value will be * outright ignored. */ jsbytecode *callpc = script->analysis()->getCallPC(pc); if (JSOp(*callpc) == JSOP_NEW) return; - add(cx, cx->typeLifoAlloc().new_<TypeConstraintCallProp>(script, callpc, id)); + add(cx, cx->typeLifoAlloc().new_<TypeConstraintCallProperty>(script, callpc, id)); } /* * Constraints for generating 'set' property constraints on a SETELEM only if * the element type may be a number. For SETELEM we only account for integer * indexes, and if the element cannot be an integer (e.g. it must be a string) * then we lose precision by treating it like one. */ class TypeConstraintSetElement : public TypeConstraint { public: JSScript *script; jsbytecode *pc; - TypeSet *objectTypes; - TypeSet *valueTypes; + StackTypeSet *objectTypes; + StackTypeSet *valueTypes; TypeConstraintSetElement(JSScript *script, jsbytecode *pc, - TypeSet *objectTypes, TypeSet *valueTypes) - : TypeConstraint("setelement"), script(script), pc(pc), + StackTypeSet *objectTypes, StackTypeSet *valueTypes) + : script(script), pc(pc), objectTypes(objectTypes), valueTypes(valueTypes) { JS_ASSERT(script && pc); } + const char *kind() { return "setelement"; } + void newType(JSContext *cx, TypeSet *source, Type type); }; void -TypeSet::addSetElement(JSContext *cx, JSScript *script, jsbytecode *pc, - TypeSet *objectTypes, TypeSet *valueTypes) -{ - add(cx, cx->typeLifoAlloc().new_<TypeConstraintSetElement>(script, pc, objectTypes, - valueTypes)); +StackTypeSet::addSetElement(JSContext *cx, JSScript *script, jsbytecode *pc, + StackTypeSet *objectTypes, StackTypeSet *valueTypes) +{ + add(cx, cx->analysisLifoAlloc().new_<TypeConstraintSetElement>(script, pc, objectTypes, + valueTypes)); } /* * Constraints for watching call edges as they are discovered and invoking native * function handlers, adding constraints for arguments, receiver objects and the * return value, and updating script foundOffsets. */ class TypeConstraintCall : public TypeConstraint { public: /* Call site being tracked. */ TypeCallsite *callsite; TypeConstraintCall(TypeCallsite *callsite) - : TypeConstraint("call"), callsite(callsite) + : callsite(callsite) {} + const char *kind() { return "call"; } + void newType(JSContext *cx, TypeSet *source, Type type); }; void -TypeSet::addCall(JSContext *cx, TypeCallsite *site) -{ - add(cx, cx->typeLifoAlloc().new_<TypeConstraintCall>(site)); +StackTypeSet::addCall(JSContext *cx, TypeCallsite *site) +{ + add(cx, cx->analysisLifoAlloc().new_<TypeConstraintCall>(site)); } /* Constraints for arithmetic operations. */ class TypeConstraintArith : public TypeConstraint { public: JSScript *script; jsbytecode *pc; /* Type set receiving the result of the arithmetic. */ TypeSet *target; /* For addition operations, the other operand. */ TypeSet *other; TypeConstraintArith(JSScript *script, jsbytecode *pc, TypeSet *target, TypeSet *other) - : TypeConstraint("arith"), script(script), pc(pc), target(target), other(other) + : script(script), pc(pc), target(target), other(other) { JS_ASSERT(target); } + const char *kind() { return "arith"; } + void newType(JSContext *cx, TypeSet *source, Type type); }; void -TypeSet::addArith(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target, TypeSet *other) -{ - add(cx, cx->typeLifoAlloc().new_<TypeConstraintArith>(script, pc, target, other)); +StackTypeSet::addArith(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target, TypeSet *other) +{ + add(cx, cx->analysisLifoAlloc().new_<TypeConstraintArith>(script, pc, target, other)); } /* Subset constraint which transforms primitive values into appropriate objects. */ class TypeConstraintTransformThis : public TypeConstraint { public: JSScript *script; TypeSet *target; TypeConstraintTransformThis(JSScript *script, TypeSet *target) - : TypeConstraint("transformthis"), script(script), target(target) + : script(script), target(target) {} + const char *kind() { return "transformthis"; } + void newType(JSContext *cx, TypeSet *source, Type type); }; void -TypeSet::addTransformThis(JSContext *cx, JSScript *script, TypeSet *target) -{ - add(cx, cx->typeLifoAlloc().new_<TypeConstraintTransformThis>(script, target)); +StackTypeSet::addTransformThis(JSContext *cx, JSScript *script, TypeSet *target) +{ + add(cx, cx->analysisLifoAlloc().new_<TypeConstraintTransformThis>(script, target)); } /* * Constraint which adds a particular type to the 'this' types of all * discovered scripted functions. */ class TypeConstraintPropagateThis : public TypeConstraint { public: JSScript *script; jsbytecode *callpc; Type type; - TypeSet *types; - - TypeConstraintPropagateThis(JSScript *script, jsbytecode *callpc, Type type, TypeSet *types) - : TypeConstraint("propagatethis"), script(script), callpc(callpc), type(type), types(types) + StackTypeSet *types; + + TypeConstraintPropagateThis(JSScript *script, jsbytecode *callpc, Type type, StackTypeSet *types) + : script(script), callpc(callpc), type(type), types(types) {} + const char *kind() { return "propagatethis"; } + void newType(JSContext *cx, TypeSet *source, Type type); }; void -TypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, Type type, TypeSet *types) +StackTypeSet::addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, + Type type, StackTypeSet *types) { /* Don't add constraints when the call will be 'new' (see addCallProperty). */ jsbytecode *callpc = script->analysis()->getCallPC(pc); if (JSOp(*callpc) == JSOP_NEW) return; - add(cx, cx->typeLifoAlloc().new_<TypeConstraintPropagateThis>(script, callpc, type, types)); + add(cx, cx->analysisLifoAlloc().new_<TypeConstraintPropagateThis>(script, callpc, type, types)); } /* Subset constraint which filters out primitive types. */ class TypeConstraintFilterPrimitive : public TypeConstraint { public: TypeSet *target; - TypeSet::FilterKind filter; - - TypeConstraintFilterPrimitive(TypeSet *target, TypeSet::FilterKind filter) - : TypeConstraint("filter"), target(target), filter(filter) + + TypeConstraintFilterPrimitive(TypeSet *target) + : target(target) {} + const char *kind() { return "filter"; } + void newType(JSContext *cx, TypeSet *source, Type type) { - switch (filter) { - case TypeSet::FILTER_ALL_PRIMITIVES: - if (type.isPrimitive()) - return; - break; - - case TypeSet::FILTER_NULL_VOID: - if (type.isPrimitive(JSVAL_TYPE_NULL) || type.isPrimitive(JSVAL_TYPE_UNDEFINED)) - return; - break; - - case TypeSet::FILTER_VOID: - if (type.isPrimitive(JSVAL_TYPE_UNDEFINED)) - return; - break; - - default: - JS_NOT_REACHED("Bad filter"); - } + if (type.isPrimitive()) + return; target->addType(cx, type); } }; void -TypeSet::addFilterPrimitives(JSContext *cx, TypeSet *target, FilterKind filter) -{ - add(cx, cx->typeLifoAlloc().new_<TypeConstraintFilterPrimitive>(target, filter)); +HeapTypeSet::addFilterPrimitives(JSContext *cx, TypeSet *target) +{ + add(cx, cx->typeLifoAlloc().new_<TypeConstraintFilterPrimitive>(target)); } /* If id is a normal slotful 'own' property of an object, get its shape. */ static inline Shape * GetSingletonShape(JSContext *cx, JSObject *obj, jsid id) { if (!obj->isNative()) return NULL; @@ -877,29 +900,41 @@ void ScriptAnalysis::breakTypeBarriersSS class TypeConstraintSubsetBarrier : public TypeConstraint { public: JSScript *script; jsbytecode *pc; TypeSet *target; TypeConstraintSubsetBarrier(JSScript *script, jsbytecode *pc, TypeSet *target) - : TypeConstraint("subsetBarrier"), script(script), pc(pc), target(target) + : script(script), pc(pc), target(target) {} + const char *kind() { return "subsetBarrier"; } + void newType(JSContext *cx, TypeSet *source, Type type) { - if (!target->hasType(type)) + if (!target->hasType(type)) { + if (!script->ensureRanAnalysis(cx)) + return; script->analysis()->addTypeBarrier(cx, pc, target, type); + } } }; void -TypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target) -{ +StackTypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target) +{ + add(cx, cx->analysisLifoAlloc().new_<TypeConstraintSubsetBarrier>(script, pc, target)); +} + +void +HeapTypeSet::addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target) +{ + JS_ASSERT(!target->purged()); add(cx, cx->typeLifoAlloc().new_<TypeConstraintSubsetBarrier>(script, pc, target)); } ///////////////////////////////////////////////////////////////////// // TypeConstraint ///////////////////////////////////////////////////////////////////// /* Get the object to use for a property access on type. */ @@ -959,104 +994,186 @@ MarkPropertyAccessUnknown(JSContext *cx, { if (UsePropertyTypeBarrier(pc)) script->analysis()->addTypeBarrier(cx, pc, target, Type::UnknownType()); else target->addType(cx, Type::UnknownType()); } /* + * Get a value for reading id from obj or its prototypes according to the + * current VM state, returning the unknown type on failure or an undefined + * property. + */ +static inline Type +GetSingletonPropertyType(JSContext *cx, JSObject *objArg, jsid id) +{ + JS_ASSERT(id == MakeTypeId(cx, id)); + + RootedObject obj(cx, objArg); + + if (JSID_IS_VOID(id)) + return Type::UnknownType(); + + if (obj->isTypedArray()) { + if (id == id_length(cx)) + return Type::Int32Type(); + obj = obj->getProto(); + } + + while (obj) { + if (!obj->isNative()) + return Type::UnknownType(); + + Value v; + if (HasDataProperty(cx, obj, id, &v)) { + if (v.isUndefined()) + return Type::UnknownType(); + return GetValueType(cx, v); + } + + obj = obj->getProto(); + } + + return Type::UnknownType(); +} + +/* * Handle a property access on a specific object. All property accesses go through * here, whether via x.f, x[f], or global name accesses. */ +template <PropertyAccessKind access> static inline void -PropertyAccess(JSContext *cx, JSScript *script_, jsbytecode *pc, TypeObject *object_, - bool assign, TypeSet *target, jsid id) -{ - RootedScript script(cx, script_); - Rooted<TypeObject*> object(cx, object_); - +PropertyAccess(JSContext *cx, JSScript *script, jsbytecode *pc, TypeObject *object, + StackTypeSet *target, jsid id) +{ /* Reads from objects with unknown properties are unknown, writes to such objects are ignored. */ if (object->unknownProperties()) { - if (!assign) + if (access != PROPERTY_WRITE) MarkPropertyAccessUnknown(cx, script, pc, target); return; } + /* + * Short circuit indexed accesses on objects which are definitely typed + * arrays. Inference only covers the behavior of indexed accesses when + * getting integer properties, and the types for these are known ahead of + * time for typed arrays. Propagate the possible element types of the array + * to sites reading from it. + */ + if (object->singleton && object->singleton->isTypedArray() && JSID_IS_VOID(id)) { + if (access != PROPERTY_WRITE) { + int arrayKind = object->proto->getClass() - TypedArray::protoClasses; + JS_ASSERT(arrayKind >= 0 && arrayKind < TypedArray::TYPE_MAX); + + bool maybeDouble = (arrayKind == TypedArray::TYPE_UINT32 || + arrayKind == TypedArray::TYPE_FLOAT32 || + arrayKind == TypedArray::TYPE_FLOAT64); + target->addType(cx, maybeDouble ? Type::DoubleType() : Type::Int32Type()); + } + return; + } + + /* + * Try to resolve reads from the VM state ahead of time, e.g. for reads + * of defined global variables or from the prototype of the object. This + * reduces the need to monitor cold code as it first executes. + * + * This is speculating that the type of a defined property in a singleton + * object or prototype will not change between analysis and execution. + */ + if (access != PROPERTY_WRITE) { + if (JSObject *singleton = object->singleton ? object->singleton : object->proto) { + Type type = GetSingletonPropertyType(cx, singleton, id); + if (!type.isUnknown()) + target->addType(cx, type); + } + } + /* Capture the effects of a standard property access. */ - TypeSet *types = object->getProperty(cx, id, assign); + HeapTypeSet *types = object->getProperty(cx, id, access == PROPERTY_WRITE); if (!types) return; - if (assign) { + if (access == PROPERTY_WRITE) { target->addSubset(cx, types); } else { + JS_ASSERT_IF(script->hasAnalysis(), + target == script->analysis()->bytecodeTypes(pc)); if (!types->hasPropagatedProperty()) object->getFromPrototypes(cx, id, types); if (UsePropertyTypeBarrier(pc)) { - types->addSubsetBarrier(cx, script, pc, target); + if (access == PROPERTY_READ) { + types->addSubsetBarrier(cx, script, pc, target); + } else { + TypeConstraintSubsetBarrier constraint(script, pc, target); + types->addTypesToConstraint(cx, &constraint); + } if (object->singleton && !JSID_IS_VOID(id)) { /* * Add a singleton type barrier on the object if it has an * 'own' property which is currently undefined. We'll be able * to remove the barrier after the property becomes defined, * even if no undefined value is ever observed at pc. */ Shape *shape = GetSingletonShape(cx, object->singleton, id); if (shape && object->singleton->nativeGetSlot(shape->slot()).isUndefined()) script->analysis()->addSingletonTypeBarrier(cx, pc, target, object->singleton, id); } } else { + JS_ASSERT(access == PROPERTY_READ); types->addSubset(cx, target); } } } /* Whether the JSObject/TypeObject referent of an access on type cannot be determined. */ static inline bool UnknownPropertyAccess(JSScript *script, Type type) { return type.isUnknown() || type.isAnyObject() || (!type.isObject() && !script->hasGlobal()); } +template <PropertyAccessKind access> void -TypeConstraintProp::newType(JSContext *cx, TypeSet *source, Type type) +TypeConstraintProp<access>::newType(JSContext *cx, TypeSet *source, Type type) { if (UnknownPropertyAccess(script, type)) { /* * Access on an unknown object. Reads produce an unknown result, writes * need to be monitored. */ - if (assign) + if (access == PROPERTY_WRITE) cx->compartment->types.monitorBytecode(cx, script, pc - script->code); else MarkPropertyAccessUnknown(cx, script, pc, target); return; } if (type.isPrimitive(JSVAL_TYPE_MAGIC)) { /* Ignore cases which will be accounted for by the followEscapingArguments analysis. */ - if (assign || (id != JSID_VOID && id != id_length(cx))) + if (access == PROPERTY_WRITE || (id != JSID_VOID && id != id_length(cx))) return; if (id == JSID_VOID) MarkPropertyAccessUnknown(cx, script, pc, target); else target->addType(cx, Type::Int32Type()); return; } TypeObject *object = GetPropertyObject(cx, script, type); if (object) - PropertyAccess(cx, script, pc, object, assign, target, id); -} - + PropertyAccess<access>(cx, script, pc, object, target, id); +} + +template <PropertyAccessKind access> void -TypeConstraintCallProp::newType(JSContext *cx, TypeSet *source, Type type) +TypeConstraintCallProp<access>::newType(JSContext *cx, TypeSet *source, Type type) { /* * For CALLPROP, we need to update not just the pushed types but also the * 'this' types of possible callees. If we can't figure out that set of * callees, monitor the call to make sure discovered callees get their * 'this' types updated. */ @@ -1071,18 +1188,23 @@ TypeConstraintCallProp::newType(JSContex cx->compartment->types.monitorBytecode(cx, script, callpc - script->code); } else { TypeSet *types = object->getProperty(cx, id, false); if (!types) return; if (!types->hasPropagatedProperty()) object->getFromPrototypes(cx, id, types); /* Bypass addPropagateThis, we already have the callpc. */ - types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintPropagateThis>( - script, callpc, type, (TypeSet *) NULL)); + if (access == PROPERTY_READ) { + types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintPropagateThis>( + script, callpc, type, (StackTypeSet *) NULL)); + } else { + TypeConstraintPropagateThis constraint(script, callpc, type, NULL); + types->addTypesToConstraint(cx, &constraint); + } } } } void TypeConstraintSetElement::newType(JSContext *cx, TypeSet *source, Type type) { if (type.isUnknown() || @@ -1093,16 +1215,19 @@ TypeConstraintSetElement::newType(JSCont } void TypeConstraintCall::newType(JSContext *cx, TypeSet *source, Type type) { JSScript *script = callsite->script; jsbytecode *pc = callsite->pc; + JS_ASSERT_IF(script->hasAnalysis(), + callsite->returnTypes == script->analysis()->bytecodeTypes(pc)); + if (type.isUnknown() || type.isAnyObject()) { /* Monitor calls on unknown functions. */ cx->compartment->types.monitorBytecode(cx, script, pc - script->code); return; } JSFunction *callee = NULL; @@ -1146,18 +1271,18 @@ TypeConstraintCall::newType(JSContext *c TypeObject *res = TypeScript::InitObject(cx, script, pc, JSProto_Array); if (!res) return; callsite->returnTypes->addType(cx, Type::ObjectType(res)); if (callsite->argumentCount >= 2) { for (unsigned i = 0; i < callsite->argumentCount; i++) { - PropertyAccess(cx, script, pc, res, true, - callsite->argumentTypes[i], JSID_VOID); + PropertyAccess<PROPERTY_WRITE>(cx, script, pc, res, + callsite->argumentTypes[i], JSID_VOID); } } } return; } callee = obj->toFunction(); @@ -1172,40 +1297,39 @@ TypeConstraintCall::newType(JSContext *c if (!callee->script()->ensureHasTypes(cx)) return; unsigned nargs = callee->nargs; /* Add bindings for the arguments of the call. */ for (unsigned i = 0; i < callsite->argumentCount && i < nargs; i++) { - TypeSet *argTypes = callsite->argumentTypes[i]; - TypeSet *types = TypeScript::ArgTypes(callee->script(), i); + StackTypeSet *argTypes = callsite->argumentTypes[i]; + StackTypeSet *types = TypeScript::ArgTypes(callee->script(), i); argTypes->addSubsetBarrier(cx, script, pc, types); } /* Add void type for any formals in the callee not supplied at the call site. */ for (unsigned i = callsite->argumentCount; i < nargs; i++) { TypeSet *types = TypeScript::ArgTypes(callee->script(), i); types->addType(cx, Type::UndefinedType()); } - TypeSet *thisTypes = TypeScript::ThisTypes(callee->script()); - TypeSet *returnTypes = TypeScript::ReturnTypes(callee->script()); + StackTypeSet *thisTypes = TypeScript::ThisTypes(callee->script()); + HeapTypeSet *returnTypes = TypeScript::ReturnTypes(callee->script()); if (callsite->isNew) { /* * If the script does not return a value then the pushed value is the * new object (typical case). Note that we don't model construction of * the new value, which is done dynamically; we don't keep track of the * possible 'new' types for a given prototype type object. */ - thisTypes->addSubset(cx, callsite->returnTypes); - returnTypes->addFilterPrimitives(cx, callsite->returnTypes, - TypeSet::FILTER_ALL_PRIMITIVES); + thisTypes->addSubset(cx, returnTypes); + returnTypes->addFilterPrimitives(cx, callsite->returnTypes); } else { /* * Add a binding for the return value of the call. We don't add a * binding for the receiver object, as this is done with PropagateThis * constraints added by the original JSOP_CALL* op. The type sets we * manipulate here have lost any correlations between particular types * in the 'this' and 'callee' sets, which we want to maintain for * polymorphic JSOP_CALLPROP invocations. @@ -1359,71 +1483,38 @@ class TypeConstraintFreeze : public Type { public: RecompileInfo info; /* Whether a new type has already been added, triggering recompilation. */ bool typeAdded; TypeConstraintFreeze(RecompileInfo info) - : TypeConstraint("freeze"), info(info), typeAdded(false) + : info(info), typeAdded(false) {} + const char *kind() { return "freeze"; } + void newType(JSContext *cx, TypeSet *source, Type type) { if (typeAdded) return; typeAdded = true; cx->compartment->types.addPendingRecompile(cx, info); } }; void -TypeSet::addFreeze(JSContext *cx) +HeapTypeSet::addFreeze(JSContext *cx) { add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreeze>( cx->compartment->types.compiledInfo), false); } -/* - * Constraint which triggers recompilation of a script if a possible new JSValueType - * tag is realized for a type set. - */ -class TypeConstraintFreezeTypeTag : public TypeConstraint -{ -public: - RecompileInfo info; - - /* - * Whether the type tag has been marked unknown due to a type change which - * occurred after this constraint was generated (and which triggered recompilation). - */ - bool typeUnknown; - - TypeConstraintFreezeTypeTag(RecompileInfo info) - : TypeConstraint("freezeTypeTag"), info(info), typeUnknown(false) - {} - - void newType(JSContext *cx, TypeSet *source, Type type) - { - if (typeUnknown) - return; - - if (!type.isUnknown() && !type.isAnyObject() && type.isObject()) { - /* Ignore new objects when the type set already has other objects. */ - if (source->getObjectCount() >= 2) - return; - } - - typeUnknown = true; - cx->compartment->types.addPendingRecompile(cx, info); - } -}; - static inline JSValueType GetValueTypeFromTypeFlags(TypeFlags flags) { switch (flags) { case TYPE_FLAG_UNDEFINED: return JSVAL_TYPE_UNDEFINED; case TYPE_FLAG_NULL: return JSVAL_TYPE_NULL; @@ -1440,174 +1531,115 @@ GetValueTypeFromTypeFlags(TypeFlags flag case TYPE_FLAG_ANYOBJECT: return JSVAL_TYPE_OBJECT; default: return JSVAL_TYPE_UNKNOWN; } } JSValueType -TypeSet::getKnownTypeTag(JSContext *cx) +StackTypeSet::getKnownTypeTag() { TypeFlags flags = baseFlags(); JSValueType type; if (baseObjectCount()) type = flags ? JSVAL_TYPE_UNKNOWN : JSVAL_TYPE_OBJECT; else type = GetValueTypeFromTypeFlags(flags); /* * If the type set is totally empty then it will be treated as unknown, * but we still need to record the dependency as adding a new type can give * it a definite type tag. This is not needed if there are enough types * that the exact tag is unknown, as it will stay unknown as more types are * added to the set. */ - bool empty = flags == 0 && baseObjectCount() == 0; + DebugOnly<bool> empty = flags == 0 && baseObjectCount() == 0; JS_ASSERT_IF(empty, type == JSVAL_TYPE_UNKNOWN); - if (cx->compartment->types.compiledInfo.compilerOutput(cx)->script && - (empty || type != JSVAL_TYPE_UNKNOWN)) - { - add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeTypeTag>( - cx->compartment->types.compiledInfo), false); - } - return type; } /* Constraint which triggers recompilation if an object acquires particular flags. */ class TypeConstraintFreezeObjectFlags : public TypeConstraint { public: RecompileInfo info; /* Flags we are watching for on this object. */ TypeObjectFlags flags; /* Whether the object has already been marked as having one of the flags. */ - bool *pmarked; - bool localMarked; - - TypeConstraintFreezeObjectFlags(RecompileInfo info, TypeObjectFlags flags, bool *pmarked) - : TypeConstraint("freezeObjectFlags"), info(info), flags(flags), - pmarked(pmarked), localMarked(false) - {} + bool marked; TypeConstraintFreezeObjectFlags(RecompileInfo info, TypeObjectFlags flags) - : TypeConstraint("freezeObjectFlags"), info(info), flags(flags), - pmarked(&localMarked), localMarked(false) + : info(info), flags(flags), + marked(false) {} + const char *kind() { return "freezeObjectFlags"; } + void newType(JSContext *cx, TypeSet *source, Type type) {} void newObjectState(JSContext *cx, TypeObject *object, bool force) { - if (object->hasAnyFlags(flags) && !*pmarked) { - *pmarked = true; - cx->compartment->types.addPendingRecompile(cx, info); - } else if (force) { + if (!marked && (object->hasAnyFlags(flags) || (!flags && force))) { + marked = true; cx->compartment->types.addPendingRecompile(cx, info); } } }; -/* - * Constraint which triggers recompilation if any object in a type set acquire - * particular flags. - */ -class TypeConstraintFreezeObjectFlagsSet : public TypeConstraint -{ -public: - RecompileInfo info; - - TypeObjectFlags flags; - bool marked; - - TypeConstraintFreezeObjectFlagsSet(RecompileInfo info, TypeObjectFlags flags) - : TypeConstraint("freezeObjectKindSet"), info(info), flags(flags), marked(false) - {} - - void newType(JSContext *cx, TypeSet *source, Type type) - { - if (marked) { - /* Despecialized the kind we were interested in due to recompilation. */ - return; - } - - if (type.isUnknown() || type.isAnyObject()) { - /* Fallthrough and recompile. */ - } else if (type.isObject()) { - TypeObject *object = type.isSingleObject() - ? type.singleObject()->getType(cx) - : type.typeObject(); - if (!object->hasAnyFlags(flags)) { - /* - * Add a constraint on the the object to pick up changes in the - * object's properties. - */ - TypeSet *types = object->getProperty(cx, JSID_EMPTY, false); - if (!types) - return; - types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeObjectFlags>( - info, flags, &marked), false); - return; - } - } else { - return; - } - - marked = true; - cx->compartment->types.addPendingRecompile(cx, info); - } -}; - bool -TypeSet::hasObjectFlags(JSContext *cx, TypeObjectFlags flags) +StackTypeSet::hasObjectFlags(JSContext *cx, TypeObjectFlags flags) { if (unknownObject()) return true; /* * Treat type sets containing no objects as having all object flags, * to spare callers from having to check this. */ if (baseObjectCount() == 0) return true; unsigned count = getObjectCount(); for (unsigned i = 0; i < count; i++) { TypeObject *object = getTypeObject(i); if (!object) { JSObject *obj = getSingleObject(i); - if (obj) - object = obj->getType(cx); + if (!obj) + continue; + object = obj->getType(cx); } - if (object && object->hasAnyFlags(flags)) + if (object->hasAnyFlags(flags)) return true; - } - - /* - * Watch for new objects of different kind, and re-traverse existing types - * in this set to add any needed FreezeArray constraints. - */ - add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeObjectFlagsSet>( - cx->compartment->types.compiledInfo, flags)); + + /* + * Add a constraint on the the object to pick up changes in the + * object's properties. + */ + TypeSet *types = object->getProperty(cx, JSID_EMPTY, false); + if (!types) + return true; + types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeObjectFlags>( + cx->compartment->types.compiledInfo, flags), false); + } return false; } bool -TypeSet::HasObjectFlags(JSContext *cx, TypeObject *object, TypeObjectFlags flags) +HeapTypeSet::HasObjectFlags(JSContext *cx, TypeObject *object, TypeObjectFlags flags) { if (object->hasAnyFlags(flags)) return true; - TypeSet *types = object->getProperty(cx, JSID_EMPTY, false); + HeapTypeSet *types = object->getProperty(cx, JSID_EMPTY, false); if (!types) return true; types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeObjectFlags>( cx->compartment->types.compiledInfo, flags), false); return false; } static inline void @@ -1628,20 +1660,20 @@ ObjectStateChange(JSContext *cx, TypeObj while (constraint) { constraint->newObjectState(cx, object, force); constraint = constraint->next; } } } void -TypeSet::WatchObjectStateChange(JSContext *cx, TypeObject *obj) +HeapTypeSet::WatchObjectStateChange(JSContext *cx, TypeObject *obj) { JS_ASSERT(!obj->unknownProperties()); - TypeSet *types = obj->getProperty(cx, JSID_EMPTY, false); + HeapTypeSet *types = obj->getProperty(cx, JSID_EMPTY, false); if (!types) return; /* * Use a constraint which triggers recompilation when markStateChange is * called, which will set 'force' to true. */ types->add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeObjectFlags>( @@ -1653,38 +1685,39 @@ class TypeConstraintFreezeOwnProperty : { public: RecompileInfo info; bool updated; bool configurable; TypeConstraintFreezeOwnProperty(RecompileInfo info, bool configurable) - : TypeConstraint("freezeOwnProperty"), - info(info), updated(false), configurable(configurable) + : info(info), updated(false), configurable(configurable) {} + const char *kind() { return "freezeOwnProperty"; } + void newType(JSContext *cx, TypeSet *source, Type type) {} void newPropertyState(JSContext *cx, TypeSet *source) { if (updated) return; - if (source->isOwnProperty(configurable)) { + if (source->ownProperty(configurable)) { updated = true; cx->compartment->types.addPendingRecompile(cx, info); } } }; static void CheckNewScriptProperties(JSContext *cx, HandleTypeObject type, JSFunction *fun); bool -TypeSet::isOwnProperty(JSContext *cx, TypeObject *object, bool configurable) +HeapTypeSet::isOwnProperty(JSContext *cx, TypeObject *object, bool configurable) { /* * Everywhere compiled code depends on definite properties associated with * a type object's newScript, we need to make sure there are constraints * in place which will mark those properties as configured should the * definite properties be invalidated. */ if (object->flags & OBJECT_FLAG_NEW_SCRIPT_REGENERATE) { @@ -1692,39 +1725,41 @@ TypeSet::isOwnProperty(JSContext *cx, Ty Rooted<TypeObject*> typeObj(cx, object); CheckNewScriptProperties(cx, typeObj, object->newScript->fun); } else { JS_ASSERT(object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED); object->flags &= ~OBJECT_FLAG_NEW_SCRIPT_REGENERATE; } } - if (isOwnProperty(configurable)) + if (ownProperty(configurable)) return true; add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreezeOwnProperty>( cx->compartment->types.compiledInfo, configurable), false); return false; } bool -TypeSet::knownNonEmpty(JSContext *cx) +HeapTypeSet::knownNonEmpty(JSContext *cx) { if (baseFlags() != 0 || baseObjectCount() != 0) return true; addFreeze(cx); return false; } bool -TypeSet::knownSubset(JSContext *cx, TypeSet *other) -{ +HeapTypeSet::knownSubset(JSContext *cx, TypeSet *other) +{ + JS_ASSERT(!other->constraintsPurged()); + if ((baseFlags() & other->baseFlags()) != baseFlags()) return false; if (unknownObject()) { JS_ASSERT(other->unknownObject()); } else { for (unsigned i = 0; i < getObjectCount(); i++) { TypeObjectKey *obj = getObject(i); @@ -1736,17 +1771,17 @@ TypeSet::knownSubset(JSContext *cx, Type } addFreeze(cx); return true; } int -TypeSet::getTypedArrayType(JSContext *cx) +StackTypeSet::getTypedArrayType() { int arrayType = TypedArray::TYPE_MAX; unsigned count = getObjectCount(); for (unsigned i = 0; i < count; i++) { JSObject *proto = NULL; if (JSObject *object = getSingleObject(i)) { proto = object->getProto(); @@ -1772,51 +1807,177 @@ TypeSet::getTypedArrayType(JSContext *cx /* * Assume the caller checked that OBJECT_FLAG_NON_TYPED_ARRAY is not set. * This means the set contains at least one object because sets with no * objects have all object flags. */ JS_ASSERT(arrayType != TypedArray::TYPE_MAX); - /* Recompile when another typed array is added to this set. */ - addFreeze(cx); - return arrayType; } JSObject * -TypeSet::getSingleton(JSContext *cx, bool freeze) +StackTypeSet::getSingleton() +{ + if (baseFlags() != 0 || baseObjectCount() != 1) + return NULL; + + return getSingleObject(0); +} + +JSObject * +HeapTypeSet::getSingleton(JSContext *cx) { if (baseFlags() != 0 || baseObjectCount() != 1) return NULL; JSObject *obj = getSingleObject(0); - if (!obj) - return NULL; - - if (freeze) { - add(cx, cx->typeLifoAlloc().new_<TypeConstraintFreeze>( - cx->compartment->types.compiledInfo), false); - } + + if (obj) + addFreeze(cx); return obj; } bool -TypeSet::needsBarrier(JSContext *cx) +HeapTypeSet::needsBarrier(JSContext *cx) { bool result = unknownObject() || getObjectCount() > 0 || hasAnyFlag(TYPE_FLAG_STRING); if (!result) addFreeze(cx); return result; } +bool +StackTypeSet::propertyNeedsBarrier(JSContext *cx, jsid id) +{ + id = MakeTypeId(cx, id); + + if (unknownObject()) + return true; + + for (unsigned i = 0; i < getObjectCount(); i++) { + if (getSingleObject(i)) + return true; + + if (types::TypeObject *otype = getTypeObject(i)) { + if (otype->unknownProperties()) + return true; + + if (types::HeapTypeSet *propTypes = otype->maybeGetProperty(cx, id)) { + if (propTypes->needsBarrier(cx)) + return true; + } + } + } + + return false; +} + +enum RecompileKind { + RECOMPILE_CHECK_MONITORED, + RECOMPILE_CHECK_BARRIERS, + RECOMPILE_NONE +}; + +/* + * Whether all jitcode for a given pc was compiled with monitoring or barriers. + * If we reanalyze the script after generating jitcode, new monitoring and + * barriers will be added which may be duplicating information available when + * the script was originally compiled, and which should not invalidate that + * compilation. + */ +static inline bool +JITCodeHasCheck(JSScript *script, jsbytecode *pc, RecompileKind kind) +{ + if (kind == RECOMPILE_NONE) + return false; + +#ifdef JS_METHODJIT + for (int constructing = 0; constructing <= 1; constructing++) { + for (int barriers = 0; barriers <= 1; barriers++) { + mjit::JITScript *jit = script->getJIT((bool) constructing, (bool) barriers); + if (!jit) + continue; + mjit::JITChunk *chunk = jit->chunk(pc); + if (!chunk) + continue; + bool found = false; + uint32_t count = (kind == RECOMPILE_CHECK_MONITORED) + ? chunk->nMonitoredBytecodes + : chunk->nTypeBarrierBytecodes; + uint32_t *bytecodes = (kind == RECOMPILE_CHECK_MONITORED) + ? chunk->monitoredBytecodes() + : chunk->typeBarrierBytecodes(); + for (size_t i = 0; i < count; i++) { + if (bytecodes[i] == uint32_t(pc - script->code)) + found = true; + } + if (!found) + return false; + } + } +#endif + + return true; +} + +/* + * Force recompilation of any jitcode for script at pc, or of any other script + * which this script was inlined into. + */ +static inline void +AddPendingRecompile(JSContext *cx, JSScript *script, jsbytecode *pc, + RecompileKind kind = RECOMPILE_NONE) +{ + /* + * Trigger recompilation of the script itself, if code was not previously + * compiled with the specified information. + */ + if (!JITCodeHasCheck(script, pc, kind)) + cx->compartment->types.addPendingRecompile(cx, script, pc); + + /* + * 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->function() && !script->function()->hasLazyType()) + ObjectStateChange(cx, script->function()->type(), false, true); +} + +/* + * As for TypeConstraintFreeze, but describes an implicit freeze constraint + * added for stack types within a script. Applies to all compilations of the + * script, not just a single one. + */ +class TypeConstraintFreezeStack : public TypeConstraint +{ +public: + JSScript *script; + + TypeConstraintFreezeStack(JSScript *script) + : script(script) + {} + + const char *kind() { return "freezeStack"; } + + void newType(JSContext *cx, TypeSet *source, Type type) + { + /* + * Unlike TypeConstraintFreeze, triggering this constraint once does + * not disable it on future changes to the type set. + */ + AddPendingRecompile(cx, script, NULL); + } +}; + ///////////////////////////////////////////////////////////////////// // TypeCompartment ///////////////////////////////////////////////////////////////////// void TypeCompartment::init(JSContext *cx) { PodZero(this); @@ -1982,17 +2143,17 @@ types::ArrayPrototypeHasIndexedProperty( JSObject *proto = script->global().getOrCreateArrayPrototype(cx); if (!proto) return true; do { TypeObject *type = proto->getType(cx); if (type->unknownProperties()) return true; - TypeSet *indexTypes = type->getProperty(cx, JSID_VOID, false); + HeapTypeSet *indexTypes = type->getProperty(cx, JSID_VOID, false); if (!indexTypes || indexTypes->isOwnProperty(cx, type, true) || indexTypes->knownNonEmpty(cx)) return true; proto = proto->getProto(); } while (proto); return false; } @@ -2156,58 +2317,63 @@ TypeCompartment::addPendingRecompile(JSC #ifdef JS_METHODJIT for (int constructing = 0; constructing <= 1; constructing++) { for (int barriers = 0; barriers <= 1; barriers++) { mjit::JITScript *jit = script->getJIT((bool) constructing, (bool) barriers); if (!jit) continue; - unsigned int chunkIndex = jit->chunkIndex(pc); - mjit::JITChunk *chunk = jit->chunkDescriptor(chunkIndex).chunk; - if (!chunk) - continue; - - addPendingRecompile(cx, chunk->recompileInfo); + if (pc) { + unsigned int chunkIndex = jit->chunkIndex(pc); + mjit::JITChunk *chunk = jit->chunkDescriptor(chunkIndex).chunk; + if (chunk) + addPendingRecompile(cx, chunk->recompileInfo); + } else { + for (size_t chunkIndex = 0; chunkIndex < jit->nchunks; chunkIndex++) { + mjit::JITChunk *chunk = jit->chunkDescriptor(chunkIndex).chunk; + if (chunk) + addPendingRecompile(cx, chunk->recompileInfo); + } + } } } #endif } void TypeCompartment::monitorBytecode(JSContext *cx, JSScript *script, uint32_t offset, bool returnOnly) { + if (!script->ensureRanInference(cx)) + return; + ScriptAnalysis *analysis = script->analysis(); - JS_ASSERT(analysis->ranInference()); - jsbytecode *pc = script->code + offset; JS_ASSERT_IF(returnOnly, js_CodeSpec[*pc].format & JOF_INVOKE); Bytecode &code = analysis->getCode(pc); if (returnOnly ? code.monitoredTypesReturn : code.monitoredTypes) return; InferSpew(ISpewOps, "addMonitorNeeded:%s #%u:%05u", returnOnly ? " returnOnly" : "", script->id(), offset); /* Dynamically monitor this call to keep track of its result types. */ if (js_CodeSpec[*pc].format & JOF_INVOKE) code.monitoredTypesReturn = true; - if (!returnOnly) - code.monitoredTypes = true; - - cx->compartment->types.addPendingRecompile(cx, script, pc); - - /* Trigger recompilation of any inline callers. */ - if (script->function() && !script->function()->hasLazyType()) - ObjectStateChange(cx, script->function()->type(), false, true); + if (returnOnly) + return; + + code.monitoredTypes = true; + + AddPendingRecompile(cx, script, pc, RECOMPILE_CHECK_MONITORED); } void TypeCompartment::markSetsUnknown(JSContext *cx, TypeObject *target) { JS_ASSERT(this == &cx->compartment->types); JS_ASSERT(!(target->flags & OBJECT_FLAG_SETS_MARKED_UNKNOWN)); JS_ASSERT(!target->singleton); @@ -2287,37 +2453,33 @@ ScriptAnalysis::addTypeBarrier(JSContext if (!code.typeBarriers) { /* * Adding type barriers at a bytecode which did not have them before * will trigger recompilation. If there were already type barriers, * however, do not trigger recompilation (the script will be recompiled * if any of the barriers is ever violated). */ - cx->compartment->types.addPendingRecompile(cx, script, const_cast<jsbytecode*>(pc)); - - /* Trigger recompilation of any inline callers. */ - if (script->function() && !script->function()->hasLazyType()) - ObjectStateChange(cx, script->function()->type(), false, true); + AddPendingRecompile(cx, script, const_cast<jsbytecode*>(pc), RECOMPILE_CHECK_BARRIERS); } /* Ignore duplicate barriers. */ TypeBarrier *barrier = code.typeBarriers; while (barrier) { if (barrier->target == target && barrier->type == type && !barrier->singleton) return; barrier = barrier->next; } InferSpew(ISpewOps, "typeBarrier: #%u:%05u: %sT%p%s %s", script->id(), pc - script->code, InferSpewColor(target), target, InferSpewColorReset(), TypeString(type)); - barrier = cx->typeLifoAlloc().new_<TypeBarrier>(target, type, (JSObject *) NULL, JSID_VOID); + barrier = cx->analysisLifoAlloc().new_<TypeBarrier>(target, type, (JSObject *) NULL, JSID_VOID); if (!barrier) { cx->compartment->types.setPendingNukeTypes(cx); return; } barrier->next = code.typeBarriers; code.typeBarriers = barrier; @@ -2327,27 +2489,25 @@ void ScriptAnalysis::addSingletonTypeBarrier(JSContext *cx, const jsbytecode *pc, TypeSet *target, JSObject *singleton, jsid singletonId) { JS_ASSERT(singletonId == MakeTypeId(cx, singletonId) && !JSID_IS_VOID(singletonId)); Bytecode &code = getCode(pc); if (!code.typeBarriers) { /* Trigger recompilation as for normal type barriers. */ - cx->compartment->types.addPendingRecompile(cx, script, const_cast<jsbytecode*>(pc)); - if (script->function() && !script->function()->hasLazyType()) - ObjectStateChange(cx, script->function()->type(), false, true); + AddPendingRecompile(cx, script, const_cast<jsbytecode*>(pc), RECOMPILE_CHECK_BARRIERS); } InferSpew(ISpewOps, "singletonTypeBarrier: #%u:%05u: %sT%p%s %p %s", script->id(), pc - script->code, InferSpewColor(target), target, InferSpewColorReset(), (void *) singleton, TypeIdString(singletonId)); - TypeBarrier *barrier = cx->typeLifoAlloc().new_<TypeBarrier>(target, Type::UndefinedType(), + TypeBarrier *barrier = cx->analysisLifoAlloc().new_<TypeBarrier>(target, Type::UndefinedType(), singleton, singletonId); if (!barrier) { cx->compartment->types.setPendingNukeTypes(cx); return; } barrier->next = code.typeBarriers; @@ -2682,29 +2842,28 @@ TypeObject::getFromPrototypes(JSContext if (!force && types->hasPropagatedProperty()) return; types->setPropagatedProperty(); if (!proto) return; - RootedTypeObject self(cx, this); if (proto->getType(cx)->unknownProperties()) { types->addType(cx, Type::UnknownType()); return; } - TypeSet *protoTypes = self->proto->getType(cx)->getProperty(cx, id, false); + HeapTypeSet *protoTypes = proto->getType(cx)->getProperty(cx, id, false); if (!protoTypes) return; protoTypes->addSubset(cx, types); - self->proto->getType(cx)->getFromPrototypes(cx, id, protoTypes); + proto->getType(cx)->getFromPrototypes(cx, id, protoTypes); } static inline void UpdatePropertyType(JSContext *cx, TypeSet *types, JSObject *obj, Shape *shape, bool force) { types->setOwnProperty(cx, false); if (!shape->writable()) types->setOwnProperty(cx, true); @@ -2805,17 +2964,17 @@ TypeObject::addDefiniteProperties(JSCont bool TypeObject::matchDefiniteProperties(JSObject *obj) { unsigned count = getPropertyCount(); for (unsigned i = 0; i < count; i++) { Property *prop = getProperty(i); if (!prop) continue; - if (prop->types.isDefiniteProperty()) { + if (prop->types.definiteProperty()) { unsigned slot = prop->types.definiteSlot(); bool found = false; Shape *shape = obj->lastProperty(); while (!shape->isEmptyShape()) { if (shape->slot() == slot && shape->propid() == prop->id) { found = true; break; @@ -2993,17 +3152,17 @@ TypeObject::clearNewScript(JSContext *cx * bits on the object's properties, just mark such properties as having * been deleted/reconfigured, which will have the same effect on JITs * wanting to use the definite bits to optimize property accesses. */ for (unsigned i = 0; i < getPropertyCount(); i++) { Property *prop = getProperty(i); if (!prop) continue; - if (prop->types.isDefiniteProperty()) + if (prop->types.definiteProperty()) prop->types.setOwnProperty(cx, true); } /* * If we cleared the new script while in the middle of initializing an * object, it will still have the new script's shape and reflect the no * longer correct state of the object once its initialization is completed. * We can't really detect the possibility of this statically, but the new @@ -3167,16 +3326,26 @@ GetInitializerType(JSContext *cx, JSScri JSProtoKey key = isArray ? JSProto_Array : JSProto_Object; if (UseNewTypeForInitializer(cx, script, pc, key)) return NULL; return TypeScript::InitObject(cx, script, pc, key); } +static inline Type +GetCalleeThisType(jsbytecode *pc) +{ + pc += GetBytecodeLength(pc); + if (*pc == JSOP_UNDEFINED) + return Type::UndefinedType(); + JS_ASSERT(*pc == JSOP_IMPLICITTHIS); + return Type::UnknownType(); +} + /* Analyze type information for a single bytecode. */ bool ScriptAnalysis::analyzeTypesBytecode(JSContext *cx, unsigned offset, TypeInferenceState &state) { jsbytecode *pc = script->code + offset; JSOp op = (JSOp)*pc; @@ -3184,17 +3353,17 @@ ScriptAnalysis::analyzeTypesBytecode(JSC JS_ASSERT(!code.pushedTypes); InferSpew(ISpewOps, "analyze: #%u:%05u", script->id(), offset); unsigned defCount = GetDefCount(script, offset); if (ExtendedDef(pc)) defCount++; - TypeSet *pushed = cx->typeLifoAlloc().newArrayUninitialized<TypeSet>(defCount); + StackTypeSet *pushed = cx->analysisLifoAlloc().newArrayUninitialized<StackTypeSet>(defCount); if (!pushed) return false; PodZero(pushed, defCount); code.pushedTypes = pushed; /* * Add phi nodes introduced at this point to the list of all phi nodes in * the script. Types for these are not generated until after the script has @@ -3214,31 +3383,34 @@ ScriptAnalysis::analyzeTypesBytecode(JSC * node created should be in the phiValues list on some bytecode. */ if (!state.phiNodes.append(newv->value.phiNode())) return false; TypeSet &types = newv->value.phiNode()->types; InferSpew(ISpewOps, "typeSet: %sT%p%s phi #%u:%05u:%u", InferSpewColor(&types), &types, InferSpewColorReset(), script->id(), offset, newv->slot); + types.setPurged(); + newv++; } } /* * Treat decomposed ops as no-ops, we will analyze the decomposed version * instead. (We do, however, need to look at introduced phi nodes). */ if (js_CodeSpec[*pc].format & JOF_DECOMPOSE) return true; for (unsigned i = 0; i < defCount; i++) { InferSpew(ISpewOps, "typeSet: %sT%p%s pushed%u #%u:%05u", InferSpewColor(&pushed[i]), &pushed[i], InferSpewColorReset(), i, script->id(), offset); + pushed[i].setPurged(); } /* Add type constraints for the various opcodes. */ switch (op) { /* Nop bytecodes. */ case JSOP_POP: case JSOP_NOP: @@ -3376,74 +3548,79 @@ ScriptAnalysis::analyzeTypesBytecode(JSC poppedTypes(pc, i)->addSubset(cx, &pushed[pickedDepth - 1 - i]); break; } case JSOP_GETGNAME: case JSOP_CALLGNAME: { jsid id = GetAtomId(cx, script, pc, 0); - TypeSet *seen = bytecodeTypes(pc); + StackTypeSet *seen = bytecodeTypes(pc); seen->addSubset(cx, &pushed[0]); /* * Normally we rely on lazy standard class initialization to fill in * the types of global properties the script can access. In a few cases * the method JIT will bypass this, and we need to add the types direclty. */ if (id == NameToId(cx->runtime->atomState.typeAtoms[JSTYPE_VOID])) seen->addType(cx, Type::UndefinedType()); if (id == NameToId(cx->runtime->atomState.NaNAtom)) seen->addType(cx, Type::DoubleType()); if (id == NameToId(cx->runtime->atomState.InfinityAtom)) seen->addType(cx, Type::DoubleType()); + TypeObject *global = script->global().getType(cx); + /* Handle as a property access. */ - PropertyAccess(cx, script, pc, script->global().getType(cx), false, seen, id); + if (state.hasPropertyReadTypes) + PropertyAccess<PROPERTY_READ_EXISTING>(cx, script, pc, global, seen, id); + else + PropertyAccess<PROPERTY_READ>(cx, script, pc, global, seen, id); if (op == JSOP_CALLGNAME) - pushed[0].addPropagateThis(cx, script, pc, Type::UnknownType()); + pushed[0].addPropagateThis(cx, script, pc, GetCalleeThisType(pc)); if (CheckNextTest(pc)) pushed[0].addType(cx, Type::UndefinedType()); break; } case JSOP_NAME: case JSOP_INTRINSICNAME: case JSOP_CALLNAME: case JSOP_CALLINTRINSIC: { - TypeSet *seen = bytecodeTypes(pc); + StackTypeSet *seen = bytecodeTypes(pc); addTypeBarrier(cx, pc, seen, Type::UnknownType()); seen->addSubset(cx, &pushed[0]); if (op == JSOP_CALLNAME || op == JSOP_CALLINTRINSIC) - pushed[0].addPropagateThis(cx, script, pc, Type::UnknownType()); + pushed[0].addPropagateThis(cx, script, pc, GetCalleeThisType(pc)); break; } case JSOP_BINDGNAME: case JSOP_BINDNAME: break; case JSOP_SETGNAME: { jsid id = GetAtomId(cx, script, pc, 0); - PropertyAccess(cx, script, pc, script->global().getType(cx), - true, poppedTypes(pc, 0), id); + TypeObject *global = script->global().getType(cx); + PropertyAccess<PROPERTY_WRITE>(cx, script, pc, global, poppedTypes(pc, 0), id); poppedTypes(pc, 0)->addSubset(cx, &pushed[0]); break; } case JSOP_SETNAME: case JSOP_SETCONST: cx->compartment->types.monitorBytecode(cx, script, offset); poppedTypes(pc, 0)->addSubset(cx, &pushed[0]); break; case JSOP_GETXPROP: { - TypeSet *seen = bytecodeTypes(pc); + StackTypeSet *seen = bytecodeTypes(pc); addTypeBarrier(cx, pc, seen, Type::UnknownType()); seen->addSubset(cx, &pushed[0]); break; } case JSOP_GETARG: case JSOP_CALLARG: case JSOP_GETLOCAL: @@ -3452,17 +3629,17 @@ ScriptAnalysis::analyzeTypesBytecode(JSC if (trackSlot(slot)) { /* * Normally these opcodes don't pop anything, but they are given * an extended use holding the variable's SSA value before the * access. Use the types from here. */ poppedTypes(pc, 0)->addSubset(cx, &pushed[0]); } else if (slot < TotalSlots(script)) { - TypeSet *types = TypeScript::SlotTypes(script, slot); + StackTypeSet *types = TypeScript::SlotTypes(script, slot); types->addSubset(cx, &pushed[0]); } else { /* Local 'let' variable. Punt on types for these, for now. */ pushed[0].addType(cx, Type::UnknownType()); } if (op == JSOP_CALLARG || op == JSOP_CALLLOCAL) pushed[0].addPropagateThis(cx, script, pc, Type::UndefinedType()); break; @@ -3510,17 +3687,17 @@ ScriptAnalysis::analyzeTypesBytecode(JSC case JSOP_INCLOCAL: case JSOP_DECLOCAL: case JSOP_LOCALINC: case JSOP_LOCALDEC: { uint32_t slot = GetBytecodeSlot(script, pc); if (trackSlot(slot)) { poppedTypes(pc, 0)->addArith(cx, script, pc, &pushed[0]); } else if (slot < TotalSlots(script)) { - TypeSet *types = TypeScript::SlotTypes(script, slot); + StackTypeSet *types = TypeScript::SlotTypes(script, slot); types->addArith(cx, script, pc, types); types->addSubset(cx, &pushed[0]); } else { pushed[0].addType(cx, Type::UnknownType()); } break; } @@ -3528,25 +3705,25 @@ ScriptAnalysis::analyzeTypesBytecode(JSC /* Compute a precise type only when we know the arguments won't escape. */ if (script->needsArgsObj()) pushed[0].addType(cx, Type::UnknownType()); else pushed[0].addType(cx, Type::MagicArgType()); break; case JSOP_REST: { - TypeSet *types = script->analysis()->bytecodeTypes(pc); + StackTypeSet *types = script->analysis()->bytecodeTypes(pc); if (script->hasGlobal()) { TypeObject *rest = TypeScript::InitObject(cx, script, pc, JSProto_Array); if (!rest) return false; types->addType(cx, Type::ObjectType(rest)); // Simulate setting a element. - TypeSet *propTypes = rest->getProperty(cx, JSID_VOID, true); + HeapTypeSet *propTypes = rest->getProperty(cx, JSID_VOID, true); if (!propTypes) return false; propTypes->addType(cx, Type::UnknownType()); } else { types->addType(cx, Type::UnknownType()); } types->addSubset(cx, &pushed[0]); break; @@ -3559,38 +3736,62 @@ ScriptAnalysis::analyzeTypesBytecode(JSC poppedTypes(pc, 0)->addSubset(cx, &pushed[0]); break; } case JSOP_LENGTH: case JSOP_GETPROP: case JSOP_CALLPROP: { jsid id = GetAtomId(cx, script, pc, 0); - TypeSet *seen = script->analysis()->bytecodeTypes(pc); - - poppedTypes(pc, 0)->addGetProperty(cx, script, pc, seen, id); - if (op == JSOP_CALLPROP) - poppedTypes(pc, 0)->addCallProperty(cx, script, pc, id); + StackTypeSet *seen = script->analysis()->bytecodeTypes(pc); + + HeapTypeSet *input = &script->types->propertyReadTypes[state.propertyReadIndex++]; + poppedTypes(pc, 0)->addSubset(cx, input); + + if (state.hasPropertyReadTypes) { + TypeConstraintGetPropertyExisting getProp(script, pc, seen, id); + input->addTypesToConstraint(cx, &getProp); + if (op == JSOP_CALLPROP) { + TypeConstraintCallPropertyExisting callProp(script, pc, id); + input->addTypesToConstraint(cx, &callProp); + } + } else { + input->addGetProperty(cx, script, pc, seen, id); + if (op == JSOP_CALLPROP) + input->addCallProperty(cx, script, pc, id); + } seen->addSubset(cx, &pushed[0]); if (CheckNextTest(pc)) pushed[0].addType(cx, Type::UndefinedType()); break; } /* * We only consider ELEM accesses on integers below. Any element access * which is accessing a non-integer property must be monitored. */ case JSOP_GETELEM: case JSOP_CALLELEM: { - TypeSet *seen = script->analysis()->bytecodeTypes(pc); - - poppedTypes(pc, 1)->addGetProperty(cx, script, pc, seen, JSID_VOID); + StackTypeSet *seen = script->analysis()->bytecodeTypes(pc); + + /* Don't try to compute a precise callee for CALLELEM. */ + if (op == JSOP_CALLELEM) + seen->addType(cx, Type::AnyObjectType()); + + HeapTypeSet *input = &script->types->propertyReadTypes[state.propertyReadIndex++]; + poppedTypes(pc, 1)->addSubset(cx, input); + + if (state.hasPropertyReadTypes) { + TypeConstraintGetPropertyExisting getProp(script, pc, seen, JSID_VOID); + input->addTypesToConstraint(cx, &getProp); + } else { + input->addGetProperty(cx, script, pc, seen, JSID_VOID); + } seen->addSubset(cx, &pushed[0]); if (op == JSOP_CALLELEM) pushed[0].addPropagateThis(cx, script, pc, Type::UndefinedType(), poppedTypes(pc, 1)); if (CheckNextTest(pc)) pushed[0].addType(cx, Type::UndefinedType()); break; } @@ -3658,22 +3859,22 @@ ScriptAnalysis::analyzeTypesBytecode(JSC case JSOP_DEFVAR: break; case JSOP_CALL: case JSOP_EVAL: case JSOP_FUNCALL: case JSOP_FUNAPPLY: case JSOP_NEW: { - TypeSet *seen = script->analysis()->bytecodeTypes(pc); + StackTypeSet *seen = script->analysis()->bytecodeTypes(pc); seen->addSubset(cx, &pushed[0]); /* Construct the base call information about this site. */ unsigned argCount = GetUseCount(script, offset) - 2; - TypeCallsite *callsite = cx->typeLifoAlloc().new_<TypeCallsite>( + TypeCallsite *callsite = cx->analysisLifoAlloc().new_<TypeCallsite>( cx, script, pc, op == JSOP_NEW, argCount); if (!callsite || (argCount && !callsite->argumentTypes)) { cx->compartment->types.setPendingNukeTypes(cx); break; } callsite->thisTypes = poppedTypes(pc, argCount); callsite->returnTypes = seen; @@ -3683,24 +3884,28 @@ ScriptAnalysis::analyzeTypesBytecode(JSC /* * Mark FUNCALL and FUNAPPLY sites as monitored. The method JIT may * lower these into normal calls, and we need to make sure the * callee's argument types are checked on entry. */ if (op == JSOP_FUNCALL || op == JSOP_FUNAPPLY) cx->compartment->types.monitorBytecode(cx, script, pc - script->code); + /* Speculate that calls whose result is ignored may return undefined. */ + if (JSOP_POP == *(pc + GetBytecodeLength(pc))) + seen->addType(cx, Type::UndefinedType()); + poppedTypes(pc, argCount + 1)->addCall(cx, callsite); break; } case JSOP_NEWINIT: case JSOP_NEWARRAY: case JSOP_NEWOBJECT: { - TypeSet *types = script->analysis()->bytecodeTypes(pc); + StackTypeSet *types = script->analysis()->bytecodeTypes(pc); types->addSubset(cx, &pushed[0]); bool isArray = (op == JSOP_NEWARRAY || (op == JSOP_NEWINIT && GET_UINT8(pc) == JSProto_Array)); JSProtoKey key = isArray ? JSProto_Array : JSProto_Object; if (UseNewTypeForInitializer(cx, script, pc, key)) { /* Defer types pushed by this bytecode until runtime. */ break; @@ -3828,17 +4033,17 @@ ScriptAnalysis::analyzeTypesBytecode(JSC * Use a per-script type set to unify the possible target types of all * 'for in' or 'for each' loops in the script. We need to mark the * value pushed by the ITERNEXT appropriately, but don't track the SSA * information to connect that ITERNEXT with the appropriate ITER. * This loses some precision when a script mixes 'for in' and * 'for each' loops together, oh well. */ if (!state.forTypes) { - state.forTypes = TypeSet::make(cx, "forTypes"); + state.forTypes = StackTypeSet::make(cx, "forTypes"); if (!state.forTypes) return false; } if (GET_UINT8(pc) == JSITER_ENUMERATE) state.forTypes->addType(cx, Type::StringType()); else state.forTypes->addType(cx, Type::UnknownType()); @@ -3994,30 +4199,60 @@ ScriptAnalysis::analyzeTypes(JSContext * ranInference_ = true; /* Make sure the initial type set of all local vars includes void. */ for (unsigned i = 0; i < script->nfixed; i++) TypeScript::LocalTypes(script, i)->addType(cx, Type::UndefinedType()); TypeInferenceState state(cx); + /* + * Generate type sets for the inputs to property reads in the script, + * unless it already has them. If we purge analysis information and end up + * reanalyzing types in the script, we don't want to regenerate constraints + * on these property inputs as they will be duplicating information on the + * property type sets previously added. + */ + if (script->types->propertyReadTypes) { + state.hasPropertyReadTypes = true; + } else { + HeapTypeSet *typeArray = + (HeapTypeSet*) cx->typeLifoAlloc().alloc(sizeof(HeapTypeSet) * numPropertyReads()); + if (!typeArray) { + cx->compartment->types.setPendingNukeTypes(cx); + return; + } + script->types->propertyReadTypes = typeArray; + PodZero(typeArray, numPropertyReads()); + +#ifdef DEBUG + for (unsigned i = 0; i < numPropertyReads(); i++) { + InferSpew(ISpewOps, "typeSet: %sT%p%s propertyRead%u #%u", + InferSpewColor(&typeArray[i]), &typeArray[i], InferSpewColorReset(), + i, script->id()); + } +#endif + } + unsigned offset = 0; while (offset < script->length) { Bytecode *code = maybeCode(offset); jsbytecode *pc = script->code + offset; if (code && !analyzeTypesBytecode(cx, offset, state)) { cx->compartment->types.setPendingNukeTypes(cx); return; } offset += GetBytecodeLength(pc); } + JS_ASSERT(state.propertyReadIndex == numPropertyReads()); + for (unsigned i = 0; i < state.phiNodes.length(); i++) { SSAPhiNode *node = state.phiNodes[i]; for (unsigned j = 0; j < node->length; j++) { const SSAValue &v = node->options[j]; getValueTypes(v)->addSubset(cx, &node->types); } } @@ -4031,16 +4266,21 @@ ScriptAnalysis::analyzeTypes(JSContext * if (result->offset != UINT32_MAX) { pushedTypes(result->offset)->addType(cx, result->type); } else { /* Custom for-in loop iteration has happened in this script. */ state.forTypes->addType(cx, Type::UnknownType()); } result = result->next; } + + if (!script->hasFreezeConstraints) { + TypeScript::AddFreezeConstraints(cx, script); + script->hasFreezeConstraints = true; + } } bool ScriptAnalysis::integerOperation(JSContext *cx, jsbytecode *pc) { JS_ASSERT(uint32_t(pc - script->code) < script->length); switch (JSOp(*pc)) { @@ -4048,35 +4288,35 @@ ScriptAnalysis::integerOperation(JSConte case JSOP_INCARG: case JSOP_DECARG: case JSOP_ARGINC: case JSOP_ARGDEC: case JSOP_INCLOCAL: case JSOP_DECLOCAL: case JSOP_LOCALINC: case JSOP_LOCALDEC: { - if (pushedTypes(pc, 0)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) + if (pushedTypes(pc, 0)->getKnownTypeTag() != JSVAL_TYPE_INT32) return false; uint32_t slot = GetBytecodeSlot(script, pc); if (trackSlot(slot)) { - if (poppedTypes(pc, 0)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) + if (poppedTypes(pc, 0)->getKnownTypeTag() != JSVAL_TYPE_INT32) return false; } return true; } case JSOP_ADD: case JSOP_SUB: case JSOP_MUL: case JSOP_DIV: - if (pushedTypes(pc, 0)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) + if (pushedTypes(pc, 0)->getKnownTypeTag() != JSVAL_TYPE_INT32) return false; - if (poppedTypes(pc, 0)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) + if (poppedTypes(pc, 0)->getKnownTypeTag() != JSVAL_TYPE_INT32) return false; - if (poppedTypes(pc, 1)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) + if (poppedTypes(pc, 1)->getKnownTypeTag() != JSVAL_TYPE_INT32) return false; return true; default: return true; } } @@ -4085,49 +4325,53 @@ ScriptAnalysis::integerOperation(JSConte * an object should a property on another object get a setter. */ class TypeConstraintClearDefiniteSetter : public TypeConstraint { public: TypeObject *object; TypeConstraintClearDefiniteSetter(TypeObject *object) - : TypeConstraint("clearDefiniteSetter"), object(object) + : object(object) {} + const char *kind() { return "clearDefiniteSetter"; } + void newPropertyState(JSContext *cx, TypeSet *source) { if (!object->newScript) return; /* * Clear out the newScript shape and definite property information from * an object if the source type set could be a setter or could be * non-writable, both of which are indicated by the source type set * being marked as configured. */ - if (!(object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED) && source->isOwnProperty(true)) + if (!(object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED) && source->ownProperty(true)) object->clearNewScript(cx); } void newType(JSContext *cx, TypeSet *source, Type type) {} }; /* * Constraint which clears definite properties on an object should a type set * contain any types other than a single object. */ class TypeConstraintClearDefiniteSingle : public TypeConstraint { public: TypeObject *object; TypeConstraintClearDefiniteSingle(TypeObject *object) - : TypeConstraint("clearDefiniteSingle"), object(object) + : object(object) {} + const char *kind() { return "clearDefiniteSingle"; } + void newType(JSContext *cx, TypeSet *source, Type type) { if (object->flags & OBJECT_FLAG_NEW_SCRIPT_CLEARED) return; if (source->baseFlags() || source->getObjectCount() > 1) object->clearNewScript(cx); } }; @@ -4329,18 +4573,18 @@ AnalyzePoppedThis(JSContext *cx, Vector< * a permanent property in any transitive prototype, the definite * properties get cleared from the shape. */ JSObject *parent = type->proto; while (parent) { TypeObject *parentObject = parent->getType(cx); if (parentObject->unknownProperties()) return false; - TypeSet *parentTypes = parentObject->getProperty(cx, id, false); - if (!parentTypes || parentTypes->isOwnProperty(true)) + HeapTypeSet *parentTypes = parentObject->getProperty(cx, id, false); + if (!parentTypes || parentTypes->ownProperty(true)) return false; parentTypes->add(cx, cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteSetter>(type)); parent = parent->getProto(); } unsigned slotSpan = obj->slotSpan(); RootedValue value(cx, UndefinedValue()); if (!DefineNativeProperty(cx, obj, id, value, NULL, NULL, @@ -4394,37 +4638,37 @@ AnalyzePoppedThis(JSContext *cx, Vector< /* * This code may not have run yet, break any type barriers involved * in performing the call (for the greater good!). */ analysis->breakTypeBarriersSSA(cx, analysis->poppedValue(calleepc, 0)); analysis->breakTypeBarriers(cx, calleepc - script->code, true); - TypeSet *funcallTypes = analysis->poppedTypes(pc, GET_ARGC(pc) + 1); - TypeSet *scriptTypes = analysis->poppedTypes(pc, GET_ARGC(pc)); + StackTypeSet *funcallTypes = analysis->poppedTypes(pc, GET_ARGC(pc) + 1); + StackTypeSet *scriptTypes = analysis->poppedTypes(pc, GET_ARGC(pc)); /* Need to definitely be calling Function.call on a specific script. */ - JSObject *funcallObj = funcallTypes->getSingleton(cx, false); - JSObject *scriptObj = scriptTypes->getSingleton(cx, false); + JSObject *funcallObj = funcallTypes->getSingleton(); + JSObject *scriptObj = scriptTypes->getSingleton(); if (!funcallObj || !scriptObj || !scriptObj->isFunction() || !scriptObj->toFunction()->isInterpreted()) { return false; } JSFunction *function = scriptObj->toFunction(); /* * Generate constraints to clear definite properties from the type * should the Function.call or callee itself change in the future. */ funcallTypes->add(cx, - cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteSingle>(type)); + cx->analysisLifoAlloc().new_<TypeConstraintClearDefiniteSingle>(type)); scriptTypes->add(cx, - cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteSingle>(type)); + cx->analysisLifoAlloc().new_<TypeConstraintClearDefiniteSingle>(type)); TypeNewScript::Initializer pushframe(TypeNewScript::Initializer::FRAME_PUSH, uses->offset); if (!initializerList->append(pushframe)) { cx->compartment->types.setPendingNukeTypes(cx); *pbaseobj = NULL; return false; } @@ -4712,32 +4956,30 @@ MarkIteratorUnknownSlow(JSContext *cx) result = cx->new_<TypeResult>(UINT32_MAX, Type::UnknownType()); if (!result) { cx->compartment->types.setPendingNukeTypes(cx); return; } result->next = script->types->dynamicList; script->types->dynamicList = result; + AddPendingRecompile(cx, script, NULL); + if (!script->hasAnalysis() || !script->analysis()->ranInference()) return; ScriptAnalysis *analysis = script->analysis(); for (unsigned i = 0; i < script->length; i++) { jsbytecode *pc = script->code + i; if (!analysis->maybeCode(pc)) continue; if (JSOp(*pc) == JSOP_ITERNEXT) analysis->pushedTypes(pc, 0)->addType(cx, Type::UnknownType()); } - - /* Trigger recompilation of any inline callers. */ - if (script->function() && !script->function()->hasLazyType()) - ObjectStateChange(cx, script->function()->type(), false, true); } void TypeMonitorCallSlow(JSContext *cx, JSObject *callee, const CallArgs &args, bool constructing) { unsigned nargs = callee->toFunction()->nargs; JSScript *script = callee->toFunction()->script(); @@ -4859,24 +5101,55 @@ TypeDynamicResult(JSContext *cx, JSScrip TypeResult *result = cx->new_<TypeResult>(pc - script->code, type); if (!result) { cx->compartment->types.setPendingNukeTypes(cx); return; } result->next = script->types->dynamicList; script->types->dynamicList = result; + /* + * New type information normally requires all code in the entire script to + * be recompiled, as changes to types can flow through variables etc. into + * other chunks in the compiled script. + * + * We can do better than this, though, when we can prove the new type will + * only be visible at certain points in the script. Namely, for arithmetic + * operations which might produce doubles and are then passed to an + * expression that cancels out integer overflow, i.e.'OP & -1' or 'OP | 0', + * the new type will only affect OP and the bitwise operation. + * + * This can prevent a significant amount of recompilation in scripts which + * use these operations extensively, principally autotranslated code. + */ + + jsbytecode *ignorePC = pc + GetBytecodeLength(pc); + if (*ignorePC == JSOP_INT8 && GET_INT8(ignorePC) == -1) { + ignorePC += JSOP_INT8_LENGTH; + if (*ignorePC != JSOP_BITAND) + ignorePC = NULL; + } else if (*ignorePC == JSOP_ZERO) { + ignorePC += JSOP_ZERO_LENGTH; + if (*ignorePC != JSOP_BITOR) + ignorePC = NULL; + } else { + ignorePC = NULL; + } + + if (ignorePC) { + AddPendingRecompile(cx, script, pc); + AddPendingRecompile(cx, script, ignorePC); + } else { + AddPendingRecompile(cx, script, NULL); + } + if (script->hasAnalysis() && script->analysis()->ranInference()) { TypeSet *pushed = script->analysis()->pushedTypes(pc, 0); pushed->addType(cx, type); } - - /* Trigger recompilation of any inline callers. */ - if (script->function() && !script->function()->hasLazyType()) - ObjectStateChange(cx, script->function()->type(), false, true); } void TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval) { /* Allow the non-TYPESET scenario to simplify stubs used in compound opcodes. */ if (!(js_CodeSpec[*pc].format & JOF_TYPESET)) return; @@ -5000,23 +5273,30 @@ JSScript::makeTypes(JSContext *cx) types = (TypeScript *) cx->calloc_(sizeof(TypeScript) + (sizeof(TypeSet) * count)); if (!types) { cx->compartment->types.setPendingNukeTypes(cx); return false; } new(types) TypeScript(); + TypeSet *typeArray = types->typeArray(); + TypeSet *returnTypes = TypeScript::ReturnTypes(this); + + for (unsigned i = 0; i < count; i++) { + TypeSet *types = &typeArray[i]; + if (types != returnTypes) + types->setConstraintsPurged(); + } + #ifdef DEBUG - TypeSet *typeArray = types->typeArray(); for (unsigned i = 0; i < nTypeSets; i++) InferSpew(ISpewOps, "typeSet: %sT%p%s bytecode%u #%u", InferSpewColor(&typeArray[i]), &typeArray[i], InferSpewColorReset(), i, id()); - TypeSet *returnTypes = TypeScript::ReturnTypes(this); InferSpew(ISpewOps, "typeSet: %sT%p%s return #%u", InferSpewColor(returnTypes), returnTypes, InferSpewColorReset(), id()); TypeSet *thisTypes = TypeScript::ThisTypes(this); InferSpew(ISpewOps, "typeSet: %sT%p%s this #%u", InferSpewColor(thisTypes), thisTypes, InferSpewColorReset(), id()); unsigned nargs = function() ? function()->nargs : 0; @@ -5039,17 +5319,17 @@ JSScript::makeTypes(JSContext *cx) bool JSScript::makeAnalysis(JSContext *cx) { JS_ASSERT(types && !types->analysis); AutoEnterAnalysis enter(cx); - types->analysis = cx->typeLifoAlloc().new_<ScriptAnalysis>(this); + types->analysis = cx->analysisLifoAlloc().new_<ScriptAnalysis>(this); if (!types->analysis) return false; Rooted<JSScript*> self(cx, this); types->analysis->analyzeBytecode(cx); @@ -5438,16 +5718,18 @@ JSCompartment::getLazyType(JSContext *cx ///////////////////////////////////////////////////////////////////// // Tracing ///////////////////////////////////////////////////////////////////// void TypeSet::sweep(JSCompartment *compartment) { + JS_ASSERT(!purged()); + /* * Purge references to type objects that are no longer live. Type sets hold * only weak references. For type sets containing more than one object, * live entries in the object hash need to be copied to the compartment's * new arena. */ unsigned objectCount = baseObjectCount(); if (objectCount >= 2) { @@ -5456,17 +5738,17 @@ TypeSet::sweep(JSCompartment *compartmen clearObjects(); objectCount = 0; for (unsigned i = 0; i < oldCapacity; i++) { TypeObjectKey *object = oldArray[i]; if (object && !IsAboutToBeFinalized(object)) { TypeObjectKey **pentry = HashSetInsert<TypeObjectKey *,TypeObjectKey,TypeObjectKey> - (compartment, objectSet, objectCount, object); + (compartment->typeLifoAlloc, objectSet, objectCount, object); if (pentry) *pentry = object; else compartment->types.setPendingNukeTypesNoReport(); } } setBaseObjectCount(objectCount); } else if (objectCount == 1) { @@ -5538,37 +5820,37 @@ TypeObject::sweep(FreeOp *fop) if (propertyCount >= 2) { unsigned oldCapacity = HashSetCapacity(propertyCount); Property **oldArray = propertySet; clearProperties(); propertyCount = 0; for (unsigned i = 0; i < oldCapacity; i++) { Property *prop = oldArray[i]; - if (prop && prop->types.isOwnProperty(false)) { + if (prop && prop->types.ownProperty(false)) { Property *newProp = compartment->typeLifoAlloc.new_<Property>(*prop); if (newProp) { Property **pentry = HashSetInsert<jsid,Property,Property> - (compartment, propertySet, propertyCount, prop->id); + (compartment->typeLifoAlloc, propertySet, propertyCount, prop->id); if (pentry) { *pentry = newProp; newProp->types.sweep(compartment); } else { compartment->types.setPendingNukeTypesNoReport(); } } else { compartment->types.setPendingNukeTypesNoReport(); } } } setBasePropertyCount(propertyCount); } else if (propertyCount == 1) { Property *prop = (Property *) propertySet; - if (prop->types.isOwnProperty(false)) { + if (prop->types.ownProperty(false)) { Property *newProp = compartment->typeLifoAlloc.new_<Property>(*prop); if (newProp) { propertySet = (Property **) newProp; newProp->types.sweep(compartment); } else { compartment->types.setPendingNukeTypesNoReport(); } } else { @@ -5781,30 +6063,163 @@ TypeScript::Sweep(FreeOp *fop, JSScript IsAboutToBeFinalized(type.objectKey())) { *presult = result->next; fop->delete_(result); } else { presult = &result->next; } } + + /* + * Freeze constraints on stack type sets need to be regenerated the next + * time the script is analyzed. + */ + script->hasFreezeConstraints = false; } void TypeScript::destroy() { while (dynamicList) { TypeResult *next = dynamicList->next; Foreground::delete_(dynamicList); dynamicList = next; } Foreground::free_(this); } +/* static */ void +TypeScript::AddFreezeConstraints(JSContext *cx, JSScript *script) +{ + /* + * Adding freeze constraints to a script ensures that code for the script + * will be recompiled any time any type set for stack values in the script + * change: these type sets are implicitly frozen during compilation. + * + * To ensure this occurs, we don't need to add freeze constraints to the + * type sets for every stack value, but rather only the input type sets + * to analysis of the stack in a script. The contents of the stack sets + * are completely determined by these input sets and by any dynamic types + * in the script (for which TypeDynamicResult will trigger recompilation). + * + * Add freeze constraints to each input type set, which includes sets for + * all arguments, locals, and monitored type sets in the script. This + * includes all type sets in the TypeScript except the script's return + * value types. + */ + + size_t count = TypeScript::NumTypeSets(script); + TypeSet *returnTypes = TypeScript::ReturnTypes(script); + + TypeSet *array = script->types->typeArray(); + for (size_t i = 0; i < count; i++) { + TypeSet *types = &array[i]; + if (types == returnTypes) + continue; + JS_ASSERT(types->constraintsPurged()); + types->add(cx, cx->analysisLifoAlloc().new_<TypeConstraintFreezeStack>(script), false); + } +} + +/* static */ void +TypeScript::Purge(JSContext *cx, JSScript *script) +{ + if (!script->types) + return; + + unsigned num = NumTypeSets(script); + TypeSet *typeArray = script->types->typeArray(); + TypeSet *returnTypes = ReturnTypes(script); + + bool ranInference = script->hasAnalysis() && script->analysis()->ranInference(); + + script->clearAnalysis(); + + if (!ranInference && !script->hasFreezeConstraints) { + /* + * Even if the script hasn't been analyzed by TI, TypeConstraintCall + * can still add constraints on 'this' for 'new' calls. + */ + ThisTypes(script)->constraintList = NULL; +#ifdef DEBUG + for (size_t i = 0; i < num; i++) { + TypeSet *types = &typeArray[i]; + JS_ASSERT_IF(types != returnTypes, !types->constraintList); + } +#endif + return; + } + + for (size_t i = 0; i < num; i++) { + TypeSet *types = &typeArray[i]; + if (types != returnTypes) + types->constraintList = NULL; + } + + if (script->hasFreezeConstraints) + TypeScript::AddFreezeConstraints(cx, script); +} + +void +TypeCompartment::maybePurgeAnalysis(JSContext *cx, bool force) +{ + // FIXME bug 781657 + return; + + JS_ASSERT(this == &cx->compartment->types); + JS_ASSERT(!cx->compartment->activeAnalysis); + + if (!cx->typeInferenceEnabled()) + return; + + size_t triggerBytes = cx->runtime->analysisPurgeTriggerBytes; + size_t beforeUsed = cx->compartment->analysisLifoAlloc.used(); + + if (!force) { + if (!triggerBytes || triggerBytes >= beforeUsed) + return; + } + + AutoEnterTypeInference enter(cx); + + /* Reset the analysis pool, making its memory available for reuse. */ + cx->compartment->analysisLifoAlloc.releaseAll(); + + uint64_t start = PRMJ_Now(); + + for (gc::CellIter i(cx->compartment, gc::FINALIZE_SCRIPT); !i.done(); i.next()) { + JSScript *script = i.get<JSScript>(); + TypeScript::Purge(cx, script); + } + + uint64_t done = PRMJ_Now(); + + if (cx->runtime->analysisPurgeCallback) { + size_t afterUsed = cx->compartment->analysisLifoAlloc.used(); + size_t typeUsed = cx->compartment->typeLifoAlloc.used(); + + char buf[1000]; + JS_snprintf(buf, sizeof(buf), + "Total Time %.2f ms, %d bytes before, %d bytes after\n", + (done - start) / double(PRMJ_USEC_PER_MSEC), + (int) (beforeUsed + typeUsed), + (int) (afterUsed + typeUsed)); + + JSString *desc = JS_NewStringCopyZ(cx, buf); + if (!desc) { + cx->clearPendingException(); + return; + } + + cx->runtime->analysisPurgeCallback(cx->runtime, &desc->asFlat()); + } +} + inline size_t TypeSet::computedSizeOfExcludingThis() { /* * This memory is allocated within the temp pool (but accounted for * elsewhere) so we can't use a JSMallocSizeOfFun to measure it. We must * compute its size analytically. */ @@ -5876,16 +6291,17 @@ SizeOfScriptTypeInferenceData(JSScript * void JSCompartment::sizeOfTypeInferenceData(TypeInferenceSizes *sizes, JSMallocSizeOfFun mallocSizeOf) { /* * Note: not all data in the pool is temporary, and some will survive GCs * by being copied to the replacement pool. This memory will be counted * elsewhere and deducted from the amount of temporary data. */ + sizes->temporary += analysisLifoAlloc.sizeOfExcludingThis(mallocSizeOf); sizes->temporary += typeLifoAlloc.sizeOfExcludingThis(mallocSizeOf); /* Pending arrays are cleared on GC along with the analysis pool. */ sizes->temporary += mallocSizeOf(types.pendingArray); /* TypeCompartment::pendingRecompiles is non-NULL only while inference code is running. */ JS_ASSERT(!types.pendingRecompiles);
--- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -130,53 +130,63 @@ class Type inline Type GetValueType(JSContext *cx, const Value &val); /* * Type inference memory management overview. * * Inference constructs a global web of constraints relating the contents of * type sets particular to various scripts and type objects within a * compartment. This data can consume a significant amount of memory, and to - * avoid this building up we try to clear it with some regularity. On each GC - * which occurs while we are not actively working with inference or other - * analysis information, we clear out all generated constraints, all type sets - * describing stack types within scripts, and (normally) all data describing - * type objects for particular JS objects (see the lazy type objects overview - * below). JIT code depends on this data and is cleared as well. + * avoid this building up we try to clear it with some regularity. + * + * There are two operations which can clear inference and analysis data. + * + * - Analysis purges clear analysis information while retaining jitcode. + * + * - GCs may clear both analysis information and jitcode. Sometimes GCs will + * preserve all information and code, and will not collect any scripts, + * type objects or singleton JS objects. + * + * There are several categories of data affected differently by the above + * operations. * - * All this data is allocated into compartment->pool. Some type inference data - * lives across GCs: type sets for scripts and non-singleton type objects, and - * propeties for such type objects. This data is also allocated into - * compartment->pool, but everything still live is copied to a new arena on GC. + * - Data cleared by every analysis purge and non-preserving GC. This includes + * the ScriptAnalysis for each analyzed script and data from each analysis + * pass performed, type sets for stack values, and all type constraints for + * such type sets and for observed/argument/local type sets on scripts + * (TypeSet::constraintsPurged, aka StackTypeSet). This is exactly the data + * allocated using compartment->analysisLifoAlloc. + * + * - Data cleared by non-preserving GCs. This includes property type sets for + * singleton JS objects, property read input type sets, type constraints on + * all type sets, and dead references in all type sets. This data is all + * allocated using compartment->typeLifoAlloc; the GC copies live data into a + * new allocator and clears the old one. + * + * - Data cleared occasionally by non-preserving GCs. TypeScripts and the data + * in their sets are occasionally destroyed during GC. When a JSScript or + * TypeObject is swept, type information for its contents is destroyed. */ /* * A constraint which listens to additions to a type set and propagates those * changes to other type sets. */ class TypeConstraint { public: -#ifdef DEBUG - const char *kind_; - const char *kind() const { return kind_; } -#else - const char *kind() const { return NULL; } -#endif - /* Next constraint listening to the same type set. */ TypeConstraint *next; - TypeConstraint(const char *kind) + TypeConstraint() : next(NULL) - { -#ifdef DEBUG - this->kind_ = kind; -#endif - } + {} + + /* Debugging name for this kind of constraint. */ + virtual const char *kind() = 0; /* Register a new type for the set this constraint is listening to. */ virtual void newType(JSContext *cx, TypeSet *source, Type type) = 0; /* * For constraints attached to an object property's type set, mark the * property as having been configured or received an own property. */ @@ -208,40 +218,55 @@ enum { TYPE_FLAG_OBJECT_COUNT_MASK >> TYPE_FLAG_OBJECT_COUNT_SHIFT, /* Whether the contents of this type set are totally unknown. */ TYPE_FLAG_UNKNOWN = 0x00010000, /* Mask of normal type flags on a type set. */ TYPE_FLAG_BASE_MASK = 0x000100ff, + /* Flags describing the kind of type set this is. */ + + /* + * Flag for type sets which describe stack values and are cleared on + * analysis purges. + */ + TYPE_FLAG_PURGED = 0x00020000, + + /* + * Flag for type sets whose constraints are cleared on analysis purges. + * This includes all temporary type sets, as well as sets in TypeScript + * which propagate into temporary type sets. + */ + TYPE_FLAG_CONSTRAINTS_PURGED = 0x00040000, + /* Flags for type sets which are on object properties. */ /* * Whether there are subset constraints propagating the possible types * for this property inherited from the object's prototypes. Reset on GC. */ - TYPE_FLAG_PROPAGATED_PROPERTY = 0x00020000, + TYPE_FLAG_PROPAGATED_PROPERTY = 0x00080000, /* Whether this property has ever been directly written. */ - TYPE_FLAG_OWN_PROPERTY = 0x00040000, + TYPE_FLAG_OWN_PROPERTY = 0x00100000, /* * Whether the property has ever been deleted or reconfigured to behave * differently from a normal native property (e.g. made non-writable or * given a scripted getter or setter). */ - TYPE_FLAG_CONFIGURED_PROPERTY = 0x00080000, + TYPE_FLAG_CONFIGURED_PROPERTY = 0x00200000, /* * Whether the property is definitely in a particular inline slot on all * objects from which it has not been deleted or reconfigured. Implies * OWN_PROPERTY and unlike OWN/CONFIGURED property, this cannot change. */ - TYPE_FLAG_DEFINITE_PROPERTY = 0x00100000, + TYPE_FLAG_DEFINITE_PROPERTY = 0x00400000, /* If the property is definite, mask and shift storing the slot. */ TYPE_FLAG_DEFINITE_MASK = 0x0f000000, TYPE_FLAG_DEFINITE_SHIFT = 24 }; typedef uint32_t TypeFlags; /* Flags and other state stored in TypeObject::flags */ @@ -310,16 +335,19 @@ enum { /* Mask for objects created with unknown properties. */ OBJECT_FLAG_UNKNOWN_MASK = OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES | OBJECT_FLAG_SETS_MARKED_UNKNOWN }; typedef uint32_t TypeObjectFlags; +class StackTypeSet; +class HeapTypeSet; + /* Information about the set of types associated with an lvalue. */ class TypeSet { /* Flags for this type set. */ TypeFlags flags; /* Possible objects this type set can represent. */ TypeObjectKey **objectSet; @@ -347,22 +375,22 @@ class TypeSet bool empty() const { return !baseFlags() && !baseObjectCount(); } bool hasAnyFlag(TypeFlags flags) const { JS_ASSERT((flags & TYPE_FLAG_BASE_MASK) == flags); return !!(baseFlags() & flags); } - bool isOwnProperty(bool configurable) const { + bool ownProperty(bool configurable) const { return flags & (configurable ? TYPE_FLAG_CONFIGURED_PROPERTY : TYPE_FLAG_OWN_PROPERTY); } - bool isDefiniteProperty() const { return flags & TYPE_FLAG_DEFINITE_PROPERTY; } + bool definiteProperty() const { return flags & TYPE_FLAG_DEFINITE_PROPERTY; } unsigned definiteSlot() const { - JS_ASSERT(isDefiniteProperty()); + JS_ASSERT(definiteProperty()); return flags >> TYPE_FLAG_DEFINITE_SHIFT; } /* * Add a type to this set, calling any constraint handlers if this is a new * possible type. */ inline void addType(JSContext *cx, Type type); @@ -388,114 +416,174 @@ class TypeSet void setDefinite(unsigned slot) { JS_ASSERT(slot <= (TYPE_FLAG_DEFINITE_MASK >> TYPE_FLAG_DEFINITE_SHIFT)); flags |= TYPE_FLAG_DEFINITE_PROPERTY | (slot << TYPE_FLAG_DEFINITE_SHIFT); } bool hasPropagatedProperty() { return !!(flags & TYPE_FLAG_PROPAGATED_PROPERTY); } void setPropagatedProperty() { flags |= TYPE_FLAG_PROPAGATED_PROPERTY; } - enum FilterKind { - FILTER_ALL_PRIMITIVES, - FILTER_NULL_VOID, - FILTER_VOID - }; + bool constraintsPurged() { return !!(flags & TYPE_FLAG_CONSTRAINTS_PURGED); } + void setConstraintsPurged() { flags |= TYPE_FLAG_CONSTRAINTS_PURGED; } + + bool purged() { return !!(flags & TYPE_FLAG_PURGED); } + void setPurged() { flags |= TYPE_FLAG_PURGED | TYPE_FLAG_CONSTRAINTS_PURGED; } + + inline StackTypeSet *toStackTypeSet(); + inline HeapTypeSet *toHeapTypeSet(); + + inline void addTypesToConstraint(JSContext *cx, TypeConstraint *constraint); + inline void add(JSContext *cx, TypeConstraint *constraint, bool callExisting = true); + + protected: + uint32_t baseObjectCount() const { + return (flags & TYPE_FLAG_OBJECT_COUNT_MASK) >> TYPE_FLAG_OBJECT_COUNT_SHIFT; + } + inline void setBaseObjectCount(uint32_t count); - /* Add specific kinds of constraints to this set. */ - inline void add(JSContext *cx, TypeConstraint *constraint, bool callExisting = true); + inline void clearObjects(); +}; + +/* + * Type set for a stack value manipulated in a script, or the argument or + * local types of said script. Constraints on these type sets are cleared + * during analysis purges; the contents of the sets are implicitly frozen + * during compilation to ensure that changes to the sets trigger recompilation + * of the associated script. + */ +class StackTypeSet : public TypeSet +{ + public: + + /* + * Make a type set with the specified debugging name, not embedded in + * another structure. + */ + static StackTypeSet *make(JSContext *cx, const char *name); + + /* Constraints for type inference. */ + void addSubset(JSContext *cx, TypeSet *target); void addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, - TypeSet *target, jsid id); + StackTypeSet *target, jsid id); void addSetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, - TypeSet *target, jsid id); - void addCallProperty(JSContext *cx, JSScript *script, jsbytecode *pc, jsid id); + StackTypeSet *target, jsid id); void addSetElement(JSContext *cx, JSScript *script, jsbytecode *pc, - TypeSet *objectTypes, TypeSet *valueTypes); + StackTypeSet *objectTypes, StackTypeSet *valueTypes); void addCall(JSContext *cx, TypeCallsite *site); void addArith(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target, TypeSet *other = NULL); void addTransformThis(JSContext *cx, JSScript *script, TypeSet *target); void addPropagateThis(JSContext *cx, JSScript *script, jsbytecode *pc, - Type type, TypeSet *types = NULL); - void addFilterPrimitives(JSContext *cx, TypeSet *target, FilterKind filter); + Type type, StackTypeSet *types = NULL); void addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target); /* - * Make an type set with the specified debugging name, not embedded in - * another structure. + * Constraints for JIT compilation. + * + * Methods for JIT compilation. These must be used when a script is + * currently being compiled (see AutoEnterCompilation) and will add + * constraints ensuring that if the return value change in the future due + * to new type information, the script's jitcode will be discarded. */ - static TypeSet *make(JSContext *cx, const char *name); + + /* Get any type tag which all values in this set must have. */ + JSValueType getKnownTypeTag(); + + bool isMagicArguments() { return getKnownTypeTag() == JSVAL_TYPE_MAGIC; } + + /* Whether the type set contains objects with any of a set of flags. */ + bool hasObjectFlags(JSContext *cx, TypeObjectFlags flags); /* - * Methods for JIT compilation. If a script is currently being compiled - * (see AutoEnterCompilation) these will add constraints ensuring that if - * the return value change in the future due to new type information, the - * currently compiled script will be marked for recompilation. + * Get the typed array type of all objects in this set. Returns + * TypedArray::TYPE_MAX if the set contains different array types. */ + int getTypedArrayType(); + + /* Get the single value which can appear in this type set, otherwise NULL. */ + JSObject *getSingleton(); + + /* Whether any objects in the type set needs a barrier on id. */ + bool propertyNeedsBarrier(JSContext *cx, jsid id); +}; + +/* + * Type set for a property of a TypeObject, or for the return value or property + * read inputs of a script. In contrast with stack type sets, constraints on + * these sets are not cleared during analysis purges, and are not implicitly + * frozen during compilation. + */ +class HeapTypeSet : public TypeSet +{ + public: + + /* Constraints for type inference. */ + + void addSubset(JSContext *cx, TypeSet *target); + void addGetProperty(JSContext *cx, JSScript *script, jsbytecode *pc, + StackTypeSet *target, jsid id); + void addCallProperty(JSContext *cx, JSScript *script, jsbytecode *pc, jsid id); + void addFilterPrimitives(JSContext *cx, TypeSet *target); + void addSubsetBarrier(JSContext *cx, JSScript *script, jsbytecode *pc, TypeSet *target); + + /* Constraints for JIT compilation. */ /* Completely freeze the contents of this type set. */ void addFreeze(JSContext *cx); - /* Get any type tag which all values in this set must have. */ - JSValueType getKnownTypeTag(JSContext *cx); - - bool isMagicArguments(JSContext *cx) { return getKnownTypeTag(cx) == JSVAL_TYPE_MAGIC; } - - /* Whether the type set or a particular object has any of a set of flags. */ - bool hasObjectFlags(JSContext *cx, TypeObjectFlags flags); - static bool HasObjectFlags(JSContext *cx, TypeObject *object, TypeObjectFlags flags); /* * Watch for a generic object state change on a type object. This currently * includes reallocations of slot pointers for global objects, and changes * to newScript data on types. */ static void WatchObjectStateChange(JSContext *cx, TypeObject *object); + /* Whether an object has any of a set of flags. */ + static bool HasObjectFlags(JSContext *cx, TypeObject *object, TypeObjectFlags flags); + /* * For type sets on a property, return true if the property has any 'own' * values assigned. If configurable is set, return 'true' if the property * has additionally been reconfigured as non-configurable, non-enumerable * or non-writable (this only applies to properties that have changed after * having been created, not to e.g. properties non-writable on creation). */ bool isOwnProperty(JSContext *cx, TypeObject *object, bool configurable); /* Get whether this type set is non-empty. */ bool knownNonEmpty(JSContext *cx); /* Get whether this type set is known to be a subset of other. */ bool knownSubset(JSContext *cx, TypeSet *other); - /* - * Get the typed array type of all objects in this set. Returns - * TypedArray::TYPE_MAX if the set contains different array types. - */ - int getTypedArrayType(JSContext *cx); - /* Get the single value which can appear in this type set, otherwise NULL. */ - JSObject *getSingleton(JSContext *cx, bool freeze = true); - - inline void clearObjects(); + JSObject *getSingleton(JSContext *cx); /* * Whether a location with this TypeSet needs a write barrier (i.e., whether * it can hold GC things). The type set is frozen if no barrier is needed. */ bool needsBarrier(JSContext *cx); - - /* The type set is frozen if no barrier is needed. */ - bool propertyNeedsBarrier(JSContext *cx, jsid id); +}; - private: - uint32_t baseObjectCount() const { - return (flags & TYPE_FLAG_OBJECT_COUNT_MASK) >> TYPE_FLAG_OBJECT_COUNT_SHIFT; - } - inline void setBaseObjectCount(uint32_t count); -}; +inline StackTypeSet * +TypeSet::toStackTypeSet() +{ + JS_ASSERT(constraintsPurged()); + return (StackTypeSet *) this; +} + +inline HeapTypeSet * +TypeSet::toHeapTypeSet() +{ + JS_ASSERT(!constraintsPurged()); + return (HeapTypeSet *) this; +} /* * Handler which persists information about dynamic types pushed within a * script which can affect its behavior and are not covered by JOF_TYPESET ops, * such as integer operations which overflow to a double. These persist across * GCs, and are used to re-seed script types when they are reanalyzed. */ struct TypeResult @@ -596,17 +684,17 @@ struct TypeBarrier /* Type information about a property. */ struct Property { /* Identifier for this property, JSID_VOID for the aggregate integer index property. */ HeapId id; /* Possible types for this property, including types inherited from prototypes. */ - TypeSet types; + HeapTypeSet types; inline Property(jsid id); inline Property(const Property &o); static uint32_t keyBits(jsid id) { return uint32_t(JSID_BITS(id)); } static jsid getKey(Property *p) { return p->id; } }; @@ -788,20 +876,20 @@ struct TypeObject : gc::Cell } /* * Get or create a property of this object. Only call this for properties which * a script accesses explicitly. 'assign' indicates whether this is for an * assignment, and the own types of the property will be used instead of * aggregate types. */ - inline TypeSet *getProperty(JSContext *cx, jsid id, bool assign); + inline HeapTypeSet *getProperty(JSContext *cx, jsid id, bool assign); /* Get a property only if it already exists. */ - inline TypeSet *maybeGetProperty(JSContext *cx, jsid id); + inline HeapTypeSet *maybeGetProperty(JSContext *cx, jsid id); inline unsigned getPropertyCount(); inline Property *getProperty(unsigned i); /* Set flags on this object which are implied by the specified key. */ inline void setFlagsFromKey(JSContext *cx, JSProtoKey kind); /* @@ -896,23 +984,23 @@ struct TypeCallsite JSScript *script; jsbytecode *pc; /* Whether this is a 'NEW' call. */ bool isNew; /* Types of each argument to the call. */ unsigned argumentCount; - TypeSet **argumentTypes; + StackTypeSet **argumentTypes; /* Types of the this variable. */ - TypeSet *thisTypes; + StackTypeSet *thisTypes; /* Type set receiving the return value of this call. */ - TypeSet *returnTypes; + StackTypeSet *returnTypes; inline TypeCallsite(JSContext *cx, JSScript *script, jsbytecode *pc, bool isNew, unsigned argumentCount); }; /* Persistent type information for a script, retained across GCs. */ class TypeScript { @@ -920,28 +1008,35 @@ class TypeScript /* Analysis information for the script, cleared on each GC. */ analyze::ScriptAnalysis *analysis; public: /* Dynamic types generated at points within this script. */ TypeResult *dynamicList; + /* + * Array of type sets storing the possible inputs to property reads. + * Generated the first time the script is analyzed by inference and kept + * after analysis purges. + */ + HeapTypeSet *propertyReadTypes; + /* Array of type type sets for variables and JOF_TYPESET ops. */ TypeSet *typeArray() { return (TypeSet *) (uintptr_t(this) + sizeof(TypeScript)); } static inline unsigned NumTypeSets(JSScript *script); - static inline TypeSet *ReturnTypes(JSScript *script); - static inline TypeSet *ThisTypes(JSScript *script); - static inline TypeSet *ArgTypes(JSScript *script, unsigned i); - static inline TypeSet *LocalTypes(JSScript *script, unsigned i); + static inline HeapTypeSet *ReturnTypes(JSScript *script); + static inline StackTypeSet *ThisTypes(JSScript *script); + static inline StackTypeSet *ArgTypes(JSScript *script, unsigned i); + static inline StackTypeSet *LocalTypes(JSScript *script, unsigned i); /* Follows slot layout in jsanalyze.h, can get this/arg/local type sets. */ - static inline TypeSet *SlotTypes(JSScript *script, unsigned slot); + static inline StackTypeSet *SlotTypes(JSScript *script, unsigned slot); #ifdef DEBUG /* Check that correct types were inferred for the values pushed by this bytecode. */ static void CheckBytecode(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value *sp); #endif /* Get the default 'new' object for a given standard class, per the script's global. */ static inline TypeObject *StandardType(JSContext *cx, JSScript *script, JSProtoKey kind); @@ -979,16 +1074,19 @@ class TypeScript /* Add a type for a variable in a script. */ static inline void SetThis(JSContext *cx, JSScript *script, Type type); static inline void SetThis(JSContext *cx, JSScript *script, const js::Value &value); static inline void SetLocal(JSContext *cx, JSScript *script, unsigned local, Type type); static inline void SetLocal(JSContext *cx, JSScript *script, unsigned local, const js::Value &value); static inline void SetArgument(JSContext *cx, JSScript *script, unsigned arg, Type type); static inline void SetArgument(JSContext *cx, JSScript *script, unsigned arg, const js::Value &value); + static void AddFreezeConstraints(JSContext *cx, JSScript *script); + static void Purge(JSContext *cx, JSScript *script); + static void Sweep(FreeOp *fop, JSScript *script); void destroy(); }; struct ArrayTableKey; typedef HashMap<ArrayTableKey,ReadBarriered<TypeObject>,ArrayTableKey,SystemAllocPolicy> ArrayTypeTable; struct ObjectTableKey; @@ -1161,16 +1259,19 @@ struct TypeCompartment void monitorBytecode(JSContext *cx, JSScript *script, uint32_t offset, bool returnOnly = false); /* Mark any type set containing obj as having a generic object type. */ void markSetsUnknown(JSContext *cx, TypeObject *obj); void sweep(FreeOp *fop); void sweepCompilerOutputs(FreeOp *fop); + + void maybePurgeAnalysis(JSContext *cx, bool force = false); + void finalizeObjects(); }; enum SpewChannel { ISpewOps, /* ops: New constraints and types. */ ISpewResult, /* result: Final type sets. */ SPEW_COUNT };
--- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -597,53 +597,58 @@ UseNewTypeForClone(JSFunction *fun) ///////////////////////////////////////////////////////////////////// /* static */ inline unsigned TypeScript::NumTypeSets(JSScript *script) { return script->nTypeSets + analyze::TotalSlots(script); } -/* static */ inline TypeSet * +/* static */ inline HeapTypeSet * TypeScript::ReturnTypes(JSScript *script) { - return script->types->typeArray() + script->nTypeSets + js::analyze::CalleeSlot(); + TypeSet *types = script->types->typeArray() + script->nTypeSets + js::analyze::CalleeSlot(); + return types->toHeapTypeSet(); } -/* static */ inline TypeSet * +/* static */ inline StackTypeSet * TypeScript::ThisTypes(JSScript *script) { - return script->types->typeArray() + script->nTypeSets + js::analyze::ThisSlot(); + TypeSet *types = script->types->typeArray() + script->nTypeSets + js::analyze::ThisSlot(); + return types->toStackTypeSet(); } /* * Note: for non-escaping arguments and locals, argTypes/localTypes reflect * only the initial type of the variable (e.g. passed values for argTypes, * or undefined for localTypes) and not types from subsequent assignments. */ -/* static */ inline TypeSet * +/* static */ inline StackTypeSet * TypeScript::ArgTypes(JSScript *script, unsigned i) { JS_ASSERT(i < script->function()->nargs); - return script->types->typeArray() + script->nTypeSets + js::analyze::ArgSlot(i); + TypeSet *types = script->types->typeArray() + script->nTypeSets + js::analyze::ArgSlot(i); + return types->toStackTypeSet(); } -/* static */ inline TypeSet * +/* static */ inline StackTypeSet * TypeScript::LocalTypes(JSScript *script, unsigned i) { JS_ASSERT(i < script->nfixed); - return script->types->typeArray() + script->nTypeSets + js::analyze::LocalSlot(script, i); + TypeSet *types = script->types->typeArray() + script->nTypeSets + js::analyze::LocalSlot(script, i); + return types->toStackTypeSet(); } -/* static */ inline TypeSet * +/* static */ inline StackTypeSet * TypeScript::SlotTypes(JSScript *script, unsigned slot) { JS_ASSERT(slot < js::analyze::TotalSlots(script)); - return script->types->typeArray() + script->nTypeSets + slot; + TypeSet *types = script->types->typeArray() + script->nTypeSets + slot; + return types->toStackTypeSet(); } /* static */ inline TypeObject * TypeScript::StandardType(JSContext *cx, JSScript *script, JSProtoKey key) { RootedObject proto(cx); RootedObject global(cx, &script->global()); if (!js_GetClassPrototype(cx, global, key, &proto, NULL)) @@ -992,17 +997,17 @@ HashKey(T v) } /* * Insert space for an element into the specified set and grow its capacity if needed. * returned value is an existing or new entry (NULL if new). */ template <class T, class U, class KEY> static U ** -HashSetInsertTry(JSCompartment *compartment, U **&values, unsigned &count, T key) +HashSetInsertTry(LifoAlloc &alloc, U **&values, unsigned &count, T key) { unsigned capacity = HashSetCapacity(count); unsigned insertpos = HashKey<T,KEY>(key) & (capacity - 1); /* Whether we are converting from a fixed array to hashtable. */ bool converting = (count == SET_ARRAY_SIZE); if (!converting) { @@ -1016,17 +1021,17 @@ HashSetInsertTry(JSCompartment *compartm count++; unsigned newCapacity = HashSetCapacity(count); if (newCapacity == capacity) { JS_ASSERT(!converting); return &values[insertpos]; } - U **newValues = compartment->typeLifoAlloc.newArray<U*>(newCapacity); + U **newValues = alloc.newArray<U*>(newCapacity); if (!newValues) return NULL; PodZero(newValues, newCapacity); for (unsigned i = 0; i < capacity; i++) { if (values[i]) { unsigned pos = HashKey<T,KEY>(KEY::getKey(values[i])) & (newCapacity - 1); while (newValues[pos] != NULL) @@ -1044,30 +1049,30 @@ HashSetInsertTry(JSCompartment *compartm } /* * Insert an element into the specified set if it is not already there, returning * an entry which is NULL if the element was not there. */ template <class T, class U, class KEY> static inline U ** -HashSetInsert(JSCompartment *compartment, U **&values, unsigned &count, T key) +HashSetInsert(LifoAlloc &alloc, U **&values, unsigned &count, T key) { if (count == 0) { JS_ASSERT(values == NULL); count++; return (U **) &values; } if (count == 1) { U *oldData = (U*) values; if (KEY::getKey(oldData) == key) return (U **) &values; - values = compartment->typeLifoAlloc.newArray<U*>(SET_ARRAY_SIZE); + values = alloc.newArray<U*>(SET_ARRAY_SIZE); if (!values) { values = (U **) oldData; return NULL; } PodZero(values, SET_ARRAY_SIZE); count++; values[0] = oldData; @@ -1081,17 +1086,17 @@ HashSetInsert(JSCompartment *compartment } if (count < SET_ARRAY_SIZE) { count++; return &values[count - 1]; } } - return HashSetInsertTry<T,U,KEY>(compartment, values, count, key); + return HashSetInsertTry<T,U,KEY>(alloc, values, count, key); } /* Lookup an entry in a hash set, return NULL if it does not exist. */ template <class T, class U, class KEY> static inline U * HashSetLookup(U **values, unsigned count, T key) { if (count == 0) @@ -1203,20 +1208,24 @@ TypeSet::addType(JSContext *cx, Type typ flag |= TYPE_FLAG_INT32; flags |= flag; } else { if (flags & TYPE_FLAG_ANYOBJECT) return; if (type.isAnyObject()) goto unknownObject; + + LifoAlloc &alloc = + purged() ? cx->compartment->analysisLifoAlloc : cx->compartment->typeLifoAlloc; + uint32_t objectCount = baseObjectCount(); TypeObjectKey *object = type.objectKey(); TypeObjectKey **pentry = HashSetInsert<TypeObjectKey *,TypeObjectKey,TypeObjectKey> - (cx->compartment, objectSet, objectCount, object); + (alloc, objectSet, objectCount, object); if (!pentry) { cx->compartment->types.setPendingNukeTypes(cx); return; } if (*pentry) return; *pentry = object; @@ -1321,17 +1330,17 @@ TypeSet::getTypeObject(unsigned i) inline TypeCallsite::TypeCallsite(JSContext *cx, JSScript *script, jsbytecode *pc, bool isNew, unsigned argumentCount) : script(script), pc(pc), isNew(isNew), argumentCount(argumentCount), thisTypes(NULL), returnTypes(NULL) { /* Caller must check for failure. */ - argumentTypes = cx->typeLifoAlloc().newArray<TypeSet*>(argumentCount); + argumentTypes = cx->analysisLifoAlloc().newArray<StackTypeSet*>(argumentCount); } ///////////////////////////////////////////////////////////////////// // TypeObject ///////////////////////////////////////////////////////////////////// inline TypeObject::TypeObject(JSObject *proto, bool function, bool unknown) { @@ -1359,50 +1368,60 @@ TypeObject::basePropertyCount() const inline void TypeObject::setBasePropertyCount(uint32_t count) { JS_ASSERT(count <= OBJECT_FLAG_PROPERTY_COUNT_LIMIT); flags = (flags & ~OBJECT_FLAG_PROPERTY_COUNT_MASK) | (count << OBJECT_FLAG_PROPERTY_COUNT_SHIFT); } -inline TypeSet * +inline HeapTypeSet * TypeObject::getProperty(JSContext *cx, jsid id, bool assign) { JS_ASSERT(cx->compartment->activeInference); JS_ASSERT(JSID_IS_VOID(id) || JSID_IS_EMPTY(id) || JSID_IS_STRING(id)); JS_ASSERT_IF(!JSID_IS_EMPTY(id), id == MakeTypeId(cx, id)); JS_ASSERT(!unknownProperties()); uint32_t propertyCount = basePropertyCount(); Property **pprop = HashSetInsert<jsid,Property,Property> - (cx->compartment, propertySet, propertyCount, id); + (cx->compartment->typeLifoAlloc, propertySet, propertyCount, id); if (!pprop) { cx->compartment->types.setPendingNukeTypes(cx); return NULL; } if (!*pprop) { setBasePropertyCount(propertyCount); if (!addProperty(cx, id, pprop)) { setBasePropertyCount(0); propertySet = NULL; return NULL; } if (propertyCount == OBJECT_FLAG_PROPERTY_COUNT_LIMIT) { markUnknown(cx); - TypeSet *types = TypeSet::make(cx, "propertyOverflow"); - types->addType(cx, Type::UnknownType()); - return types; + + /* + * Return an arbitrary property in the object, as all have unknown + * type and are treated as configured. + */ + unsigned count = getPropertyCount(); + for (unsigned i = 0; i < count; i++) { + if (Property *prop = getProperty(i)) + return &prop->types; + } + + JS_NOT_REACHED("Missing property"); + return NULL; } } - TypeSet *types = &(*pprop)->types; + HeapTypeSet *types = &(*pprop)->types; - if (assign && !types->isOwnProperty(false)) { + if (assign && !types->ownProperty(false)) { /* * Normally, we just want to set the property as being an own property * when we got a set to it. The exception is when the set is actually * calling a setter higher on the prototype chain. Check to see if there * is a setter higher on the prototype chain, setter the property as an * own property if that is not the case. */ bool foundSetter = false; @@ -1426,17 +1445,17 @@ TypeObject::getProperty(JSContext *cx, j if (!foundSetter) types->setOwnProperty(cx, false); } return types; } -inline TypeSet * +inline HeapTypeSet * TypeObject::maybeGetProperty(JSContext *cx, jsid id) { JS_ASSERT(JSID_IS_VOID(id) || JSID_IS_EMPTY(id) || JSID_IS_STRING(id)); JS_ASSERT_IF(!JSID_IS_EMPTY(id), id == MakeTypeId(cx, id)); JS_ASSERT(!unknownProperties()); Property *prop = HashSetLookup<jsid,Property,Property> (propertySet, basePropertyCount(), id); @@ -1620,16 +1639,23 @@ JSScript::analysis() inline void JSScript::clearAnalysis() { if (types) types->analysis = NULL; } inline void +JSScript::clearPropertyReadTypes() +{ + if (types && types->propertyReadTypes) + types->propertyReadTypes = NULL; +} + +inline void js::analyze::ScriptAnalysis::addPushedType(JSContext *cx, uint32_t offset, uint32_t which, js::types::Type type) { js::types::TypeSet *pushed = pushedTypes(offset, which); pushed->addType(cx, type); } inline js::types::TypeObject *
--- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -464,16 +464,19 @@ struct JSScript : public js::gc::Cell bool failedBoundsCheck:1; /* script has had hoisted bounds checks fail */ #endif bool isGenerator:1; /* is a generator */ bool isGeneratorExp:1; /* is a generator expression */ bool hasScriptCounts:1;/* script has an entry in JSCompartment::scriptCountsMap */ bool hasDebugScript:1; /* script has an entry in JSCompartment::debugScriptMap */ + bool hasFreezeConstraints:1; /* freeze constraints for stack + * type sets have been generated */ + private: /* See comments below. */ bool argsHasVarBinding_:1; bool needsArgsAnalysis_:1; bool needsArgsObj_:1; // // End of fields. Start methods. @@ -570,16 +573,18 @@ struct JSScript : public js::gc::Cell /* Ensure the script has type inference analysis information. */ inline bool ensureRanInference(JSContext *cx); inline bool hasAnalysis(); inline void clearAnalysis(); inline js::analyze::ScriptAnalysis *analysis(); + inline void clearPropertyReadTypes(); + inline bool hasGlobal() const; inline bool hasClearedGlobal() const; inline js::GlobalObject &global() const; /* See StaticScopeIter comment. */ JSObject *enclosingStaticScope() const { JS_ASSERT(enclosingScriptsCompiledSuccessfully());
--- a/js/src/methodjit/Compiler.cpp +++ b/js/src/methodjit/Compiler.cpp @@ -78,16 +78,18 @@ mjit::Compiler::Compiler(JSContext *cx, getElemICs(CompilerAllocPolicy(cx, *thisFromCtor())), setElemICs(CompilerAllocPolicy(cx, *thisFromCtor())), #endif callPatches(CompilerAllocPolicy(cx, *thisFromCtor())), callSites(CompilerAllocPolicy(cx, *thisFromCtor())), doubleList(CompilerAllocPolicy(cx, *thisFromCtor())), rootedTemplates(CompilerAllocPolicy(cx, *thisFromCtor())), rootedRegExps(CompilerAllocPolicy(cx, *thisFromCtor())), + monitoredBytecodes(CompilerAllocPolicy(cx, *thisFromCtor())), + typeBarrierBytecodes(CompilerAllocPolicy(cx, *thisFromCtor())), fixedIntToDoubleEntries(CompilerAllocPolicy(cx, *thisFromCtor())), fixedDoubleToAnyEntries(CompilerAllocPolicy(cx, *thisFromCtor())), jumpTables(CompilerAllocPolicy(cx, *thisFromCtor())), jumpTableEdges(CompilerAllocPolicy(cx, *thisFromCtor())), loopEntries(CompilerAllocPolicy(cx, *thisFromCtor())), chunkEdges(CompilerAllocPolicy(cx, *thisFromCtor())), stubcc(cx, *thisFromCtor(), frame), debugMode_(cx->compartment->debugMode()), @@ -220,19 +222,19 @@ mjit::Compiler::scanInlineCalls(uint32_t if (JSOp(*pc) != JSOP_CALL) continue; /* Not inlining at monitored call sites or those with type barriers. */ if (code->monitoredTypes || code->monitoredTypesReturn || analysis->typeBarriers(cx, pc) != NULL) continue; uint32_t argc = GET_ARGC(pc); - types::TypeSet *calleeTypes = analysis->poppedTypes(pc, argc + 1); - - if (calleeTypes->getKnownTypeTag(cx) != JSVAL_TYPE_OBJECT) + types::StackTypeSet *calleeTypes = analysis->poppedTypes(pc, argc + 1); + + if (calleeTypes->getKnownTypeTag() != JSVAL_TYPE_OBJECT) continue; if (calleeTypes->getObjectCount() >= INLINE_SITE_LIMIT) continue; /* * Compute the maximum height we can grow the stack for inlined frames. * We always reserve space for loop temporaries, for an extra stack @@ -327,39 +329,44 @@ mjit::Compiler::scanInlineCalls(uint32_t if (status != Compile_Okay) return status; if (!script->analysis()->inlineable(argc)) { okay = false; break; } - if (types::TypeSet::HasObjectFlags(cx, fun->getType(cx), - types::OBJECT_FLAG_UNINLINEABLE)) { + if (types::HeapTypeSet::HasObjectFlags(cx, fun->getType(cx), + types::OBJECT_FLAG_UNINLINEABLE)) { okay = false; break; } /* + * Watch for a generic state change in the callee's type, so that + * the outer script will be recompiled if any type information + * changes in stack values within the callee. + */ + types::HeapTypeSet::WatchObjectStateChange(cx, fun->getType(cx)); + + /* * Don't inline scripts which use 'this' if it is possible they * could be called with a 'this' value requiring wrapping. During * inlining we do not want to modify frame entries belonging to the * caller. */ if (script->analysis()->usesThisValue() && - types::TypeScript::ThisTypes(script)->getKnownTypeTag(cx) != JSVAL_TYPE_OBJECT) { + types::TypeScript::ThisTypes(script)->getKnownTypeTag() != JSVAL_TYPE_OBJECT) { okay = false; break; } } if (!okay) continue; - calleeTypes->addFreeze(cx); - /* * Add the inline frames to the cross script SSA. We will pick these * back up when compiling the call site. */ for (unsigned i = 0; i < count; i++) { JSObject *obj = calleeTypes->getSingleObject(i); if (!obj) continue; @@ -1003,16 +1010,23 @@ mjit::CanMethodJIT(JSContext *cx, JSScri CompileStatus status; { types::AutoEnterTypeInference enter(cx, true); Compiler cc(cx, script, chunkIndex, construct); status = cc.compile(); } + /* + * Check if we have hit the threshold for purging analysis data. This is + * done after compilation, rather than after another analysis stage, to + * ensure we don't throw away the work just performed. + */ + cx->compartment->types.maybePurgeAnalysis(cx); + if (status == Compile_Okay) { /* * Compiling a script can occasionally trigger its own recompilation, * so go back through the compilation logic. */ goto restart; } @@ -1178,17 +1192,17 @@ mjit::Compiler::generatePrologue() } void mjit::Compiler::ensureDoubleArguments() { /* Convert integer arguments which were inferred as (int|double) to doubles. */ for (uint32_t i = 0; script->function() && i < script->function()->nargs; i++) { uint32_t slot = ArgSlot(i); - if (a->varTypes[slot].getTypeTag(cx) == JSVAL_TYPE_DOUBLE && analysis->trackSlot(slot)) + if (a->varTypes[slot].getTypeTag() == JSVAL_TYPE_DOUBLE && analysis->trackSlot(slot)) frame.ensureDouble(frame.getArg(i)); } } void mjit::Compiler::markUndefinedLocal(uint32_t offset, uint32_t i) { uint32_t depth = ssa.getFrame(a->inlineIndex).depth; @@ -1326,16 +1340,18 @@ mjit::Compiler::finishThisUp() /* Please keep in sync with JITChunk::sizeOfIncludingThis! */ size_t dataSize = sizeof(JITChunk) + sizeof(NativeMapEntry) * nNmapLive + sizeof(InlineFrame) * inlineFrames.length() + sizeof(CallSite) * callSites.length() + sizeof(JSObject*) * rootedTemplates.length() + sizeof(RegExpShared*) * rootedRegExps.length() + + sizeof(uint32_t) * monitoredBytecodes.length() + + sizeof(uint32_t) * typeBarrierBytecodes.length() + #if defined JS_MONOIC sizeof(ic::GetGlobalNameIC) * getGlobalNames.length() + sizeof(ic::SetGlobalNameIC) * setGlobalNames.length() + sizeof(ic::CallICInfo) * callICs.length() + sizeof(ic::EqualityICInfo) * equalityICs.length() + #endif #if defined JS_POLYIC sizeof(ic::PICInfo) * pics.length() + @@ -1469,16 +1485,28 @@ mjit::Compiler::finishThisUp() RegExpShared **jitRootedRegExps = (RegExpShared **)cursor; chunk->nRootedRegExps = rootedRegExps.length(); cursor += sizeof(RegExpShared*) * chunk->nRootedRegExps; for (size_t i = 0; i < chunk->nRootedRegExps; i++) { jitRootedRegExps[i] = rootedRegExps[i]; jitRootedRegExps[i]->incRef(); } + uint32_t *jitMonitoredBytecodes = (uint32_t *)cursor; + chunk->nMonitoredBytecodes = monitoredBytecodes.length(); + cursor += sizeof(uint32_t) * chunk->nMonitoredBytecodes; + for (size_t i = 0; i < chunk->nMonitoredBytecodes; i++) + jitMonitoredBytecodes[i] = monitoredBytecodes[i]; + + uint32_t *jitTypeBarrierBytecodes = (uint32_t *)cursor; + chunk->nTypeBarrierBytecodes = typeBarrierBytecodes.length(); + cursor += sizeof(uint32_t) * chunk->nTypeBarrierBytecodes; + for (size_t i = 0; i < chunk->nTypeBarrierBytecodes; i++) + jitTypeBarrierBytecodes[i] = typeBarrierBytecodes[i]; + #if defined JS_MONOIC if (chunkIndex == 0 && script->function()) { JS_ASSERT(jit->argsCheckPool == NULL); if (cx->typeInferenceEnabled()) { jit->argsCheckStub = stubCode.locationOf(argsCheckStub); jit->argsCheckFallthrough = stubCode.locationOf(argsCheckFallthrough); jit->argsCheckJump = stubCode.locationOf(argsCheckJump); } @@ -2086,17 +2114,17 @@ mjit::Compiler::generateMethod() * double type have the correct in memory representation. */ const SlotValue *newv = analysis->newValues(PC); if (newv) { while (newv->slot) { if (newv->value.kind() == SSAValue::PHI && newv->value.phiOffset() == uint32_t(PC - script->code) && analysis->trackSlot(newv->slot) && - a->varTypes[newv->slot].getTypeTag(cx) == JSVAL_TYPE_DOUBLE) { + a->varTypes[newv->slot].getTypeTag() == JSVAL_TYPE_DOUBLE) { FrameEntry *fe = frame.getSlotEntry(newv->slot); masm.ensureInMemoryDouble(frame.addressOf(fe)); } newv++; } } } @@ -2108,17 +2136,17 @@ mjit::Compiler::generateMethod() SPEW_OPCODE(); JS_ASSERT(frame.stackDepth() == opinfo->stackDepth); if (op == JSOP_LOOPHEAD && analysis->getLoop(PC)) { jsbytecode *backedge = script->code + analysis->getLoop(PC)->backedge; if (!bytecodeInChunk(backedge)){ for (uint32_t slot = ArgSlot(0); slot < TotalSlots(script); slot++) { - if (a->varTypes[slot].getTypeTag(cx) == JSVAL_TYPE_DOUBLE) { + if (a->varTypes[slot].getTypeTag() == JSVAL_TYPE_DOUBLE) { FrameEntry *fe = frame.getSlotEntry(slot); masm.ensureInMemoryDouble(frame.addressOf(fe)); } } } } // If this is an exception entry point, then jsl_InternalThrow has set @@ -3501,19 +3529,19 @@ mjit::Compiler::updateElemCounts(jsbytec default: count = PCCounts::ELEM_ID_OTHER; break; } } else { count = PCCounts::ELEM_ID_UNKNOWN; } masm.bumpCount(&counts.get(count), reg); if (obj->mightBeType(JSVAL_TYPE_OBJECT)) { - types::TypeSet *types = frame.extra(obj).types; + types::StackTypeSet *types = frame.extra(obj).types; if (types && !types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_TYPED_ARRAY) && - types->getTypedArrayType(cx) != TypedArray::TYPE_MAX) { + types->getTypedArrayType() != TypedArray::TYPE_MAX) { count = PCCounts::ELEM_OBJECT_TYPED; } else if (types && !types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_DENSE_ARRAY)) { if (!types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_PACKED_ARRAY)) count = PCCounts::ELEM_OBJECT_PACKED; else count = PCCounts::ELEM_OBJECT_DENSE; } else { count = PCCounts::ELEM_OBJECT_OTHER; @@ -4225,16 +4253,23 @@ mjit::Compiler::inlineCallHelper(uint32_ callIC.frameSize.initStatic(frame.totalDepth(), argc); } } callFrameSize = callIC.frameSize; callIC.typeMonitored = monitored(PC) || hasTypeBarriers(PC); + if (script == outerScript) { + if (monitored(PC)) + monitoredBytecodes.append(PC - script->code); + if (hasTypeBarriers(PC)) + typeBarrierBytecodes.append(PC - script->code); + } + /* Test the type if necessary. Failing this always takes a really slow path. */ MaybeJump notObjectJump; if (icCalleeType.isSet()) notObjectJump = masm.testObject(Assembler::NotEqual, icCalleeType.reg()); Registers tempRegs(Registers::AvailRegs); tempRegs.takeReg(icCalleeData); @@ -4839,17 +4874,17 @@ mjit::Compiler::jsop_getprop(PropertyNam frame.pushTypedPayload(JSVAL_TYPE_INT32, str); } return true; } /* Handle lenth accesses of optimize 'arguments'. */ if (name == cx->runtime->atomState.lengthAtom && cx->typeInferenceEnabled() && - analysis->poppedTypes(PC, 0)->isMagicArguments(cx) && + analysis->poppedTypes(PC, 0)->isMagicArguments() && knownPushedType(0) == JSVAL_TYPE_INT32) { frame.pop(); RegisterID reg = frame.allocReg(); masm.load32(Address(JSFrameReg, StackFrame::offsetOfNumActual()), reg); frame.pushTypedPayload(JSVAL_TYPE_INT32, reg); if (script->hasScriptCounts) bumpPropCount(PC, PCCounts::PROP_DEFINITE); @@ -4868,17 +4903,17 @@ mjit::Compiler::jsop_getprop(PropertyNam frame.pop(); frame.pushCopyOf(fe); if (script->hasScriptCounts) bumpPropCount(PC, PCCounts::PROP_STATIC); return true; } } - types::TypeSet *types = analysis->poppedTypes(PC, 0); + types::StackTypeSet *types = analysis->poppedTypes(PC, 0); /* * Check if we are accessing the 'length' property of a known dense array. * Note that if the types are known to indicate dense arrays, their lengths * must fit in an int32. */ if (!types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_DENSE_ARRAY)) { bool isObject = top->isTypeKnown(); @@ -4998,22 +5033,21 @@ mjit::Compiler::jsop_getprop(PropertyNam types::TypeSet *types = frame.extra(top).types; if (types && !types->unknownObject() && types->getObjectCount() == 1 && types->getTypeObject(0) != NULL && !types->getTypeObject(0)->unknownProperties() && id == types::MakeTypeId(cx, id)) { JS_ASSERT(!forPrototype); types::TypeObject *object = types->getTypeObject(0); - types::TypeSet *propertyTypes = object->getProperty(cx, id, false); + types::HeapTypeSet *propertyTypes = object->getProperty(cx, id, false); if (!propertyTypes) return false; - if (propertyTypes->isDefiniteProperty() && + if (propertyTypes->definiteProperty() && !propertyTypes->isOwnProperty(cx, object, true)) { - types->addFreeze(cx); uint32_t slot = propertyTypes->definiteSlot(); bool isObject = top->isTypeKnown(); if (!isObject) { Jump notObject = frame.testObject(Assembler::NotEqual, top); stubcc.linkExit(notObject, Uses(1)); stubcc.leave(); stubcc.masm.move(ImmPtr(name), Registers::ArgReg1); OOL_STUBCALL(stubs::GetProp, rejoin); @@ -5202,29 +5236,29 @@ mjit::Compiler::testSingletonProperty(Ha return true; } bool mjit::Compiler::testSingletonPropertyTypes(FrameEntry *top, HandleId id, bool *testObject) { *testObject = false; - types::TypeSet *types = frame.extra(top).types; + types::StackTypeSet *types = frame.extra(top).types; if (!types || types->unknownObject()) return false; - RootedObject singleton(cx, types->getSingleton(cx)); + RootedObject singleton(cx, types->getSingleton()); if (singleton) return testSingletonProperty(singleton, id); if (!globalObj) return false; JSProtoKey key; - JSValueType type = types->getKnownTypeTag(cx); + JSValueType type = types->getKnownTypeTag(); switch (type) { case JSVAL_TYPE_STRING: key = JSProto_String; break; case JSVAL_TYPE_INT32: case JSVAL_TYPE_DOUBLE: key = JSProto_Number; @@ -5238,17 +5272,16 @@ mjit::Compiler::testSingletonPropertyTyp case JSVAL_TYPE_UNKNOWN: if (types->getObjectCount() == 1 && !top->isNotType(JSVAL_TYPE_OBJECT)) { JS_ASSERT_IF(top->isTypeKnown(), top->isType(JSVAL_TYPE_OBJECT)); types::TypeObject *object = types->getTypeObject(0); if (object && object->proto) { Rooted<JSObject*> proto(cx, object->proto); if (!testSingletonProperty(proto, id)) return false; - types->addFreeze(cx); /* If we don't know this is an object, we will need a test. */ *testObject = (type != JSVAL_TYPE_OBJECT) && !top->isTypeKnown(); return true; } } return false; @@ -5289,18 +5322,16 @@ mjit::Compiler::jsop_getprop_dispatch(Pr if (pushedTypes->getTypeObject(i) != NULL) return false; } types::TypeSet *objTypes = analysis->poppedTypes(PC, 0); if (objTypes->unknownObject() || objTypes->getObjectCount() == 0) return false; - pushedTypes->addFreeze(cx); - /* Map each type in the object to the resulting pushed value. */ Vector<JSObject *> results(CompilerAllocPolicy(cx, *this)); /* * For each type of the base object, check it has no 'own' property for the * accessed id and that its prototype does have such a property. */ uint32_t last = 0; @@ -5309,42 +5340,40 @@ mjit::Compiler::jsop_getprop_dispatch(Pr return false; types::TypeObject *object = objTypes->getTypeObject(i); if (!object) { results.append((JSObject *) NULL); continue; } if (object->unknownProperties() || !object->proto) return false; - types::TypeSet *ownTypes = object->getProperty(cx, id, false); + types::HeapTypeSet *ownTypes = object->getProperty(cx, id, false); if (ownTypes->isOwnProperty(cx, object, false)) return false; Rooted<JSObject*> proto(cx, object->proto); if (!testSingletonProperty(proto, id)) return false; if (proto->getType(cx)->unknownProperties()) return false; - types::TypeSet *protoTypes = proto->type()->getProperty(cx, id, false); + types::HeapTypeSet *protoTypes = proto->type()->getProperty(cx, id, false); if (!protoTypes) return false; JSObject *singleton = protoTypes->getSingleton(cx); if (!singleton) return false; results.append(singleton); last = i; } if (oomInVector) return false; - objTypes->addFreeze(cx); - /* Done filtering, now generate code which dispatches on the type. */ frame.forgetMismatchedObject(top); if (!top->isType(JSVAL_TYPE_OBJECT)) { Jump notObject = frame.testObject(Assembler::NotEqual, top); stubcc.linkExit(notObject, Uses(1)); } @@ -5425,30 +5454,29 @@ mjit::Compiler::jsop_setprop(PropertyNam return true; } /* * Set the property directly if we are accessing a known object which * always has the property in a particular inline slot. */ jsid id = NameToId(name); - types::TypeSet *types = frame.extra(lhs).types; + types::StackTypeSet *types = frame.extra(lhs).types; if (JSOp(*PC) == JSOP_SETPROP && id == types::MakeTypeId(cx, id) && types && !types->unknownObject() && types->getObjectCount() == 1 && types->getTypeObject(0) != NULL && !types->getTypeObject(0)->unknownProperties()) { types::TypeObject *object = types->getTypeObject(0); - types::TypeSet *propertyTypes = object->getProperty(cx, id, false); + types::HeapTypeSet *propertyTypes = object->getProperty(cx, id, false); if (!propertyTypes) return false; - if (propertyTypes->isDefiniteProperty() && + if (propertyTypes->definiteProperty() && !propertyTypes->isOwnProperty(cx, object, true)) { - types->addFreeze(cx); uint32_t slot = propertyTypes->definiteSlot(); RegisterID reg = frame.tempRegForData(lhs); frame.pinReg(reg); bool isObject = lhs->isTypeKnown(); MaybeJump notObject; if (!isObject) notObject = frame.testObject(Assembler::NotEqual, lhs); #ifdef JSGC_INCREMENTAL_MJ @@ -5492,29 +5520,21 @@ mjit::Compiler::jsop_setprop(PropertyNam return true; } #endif PICGenInfo pic(ic::PICInfo::SET, PC); pic.name = name; if (monitored(PC)) { + if (script == outerScript) + monitoredBytecodes.append(PC - script->code); pic.typeMonitored = true; - types::TypeSet *types = frame.extra(rhs).types; - if (!types) { - /* Handle FORNAME and other compound opcodes. Yuck. */ - types = types::TypeSet::make(cx, "unknownRHS"); - if (!types) - return false; - types->addType(cx, types::Type::UnknownType()); - } - pic.rhsTypes = types; } else { pic.typeMonitored = false; - pic.rhsTypes = NULL; } RESERVE_IC_SPACE(masm); RESERVE_OOL_SPACE(stubcc.masm); /* Guard that the type is an object. */ Jump typeCheck; if (!lhs->isTypeKnown()) { @@ -5940,17 +5960,17 @@ mjit::Compiler::jsop_this() */ if (cx->typeInferenceEnabled() && knownPushedType(0) != JSVAL_TYPE_OBJECT) { prepareStubCall(Uses(1)); INLINE_STUBCALL(stubs::This, REJOIN_FALLTHROUGH); return; } JSValueType type = cx->typeInferenceEnabled() - ? types::TypeScript::ThisTypes(script)->getKnownTypeTag(cx) + ? types::TypeScript::ThisTypes(script)->getKnownTypeTag() : JSVAL_TYPE_UNKNOWN; if (type != JSVAL_TYPE_OBJECT) { Jump notObj = frame.testObject(Assembler::NotEqual, thisFe); stubcc.linkExit(notObj, Uses(1)); stubcc.leave(); OOL_STUBCALL(stubs::This, REJOIN_FALLTHROUGH); stubcc.rejoin(Changes(1)); } @@ -6287,17 +6307,17 @@ mjit::Compiler::jsop_getgname(uint32_t i return true; } } jsid id = NameToId(name); JSValueType type = knownPushedType(0); if (cx->typeInferenceEnabled() && globalObj->isGlobal() && id == types::MakeTypeId(cx, id) && !globalObj->getType(cx)->unknownProperties()) { - types::TypeSet *propertyTypes = globalObj->getType(cx)->getProperty(cx, id, false); + types::HeapTypeSet *propertyTypes = globalObj->getType(cx)->getProperty(cx, id, false); if (!propertyTypes) return false; /* * If we are accessing a defined global which is a normal data property * then bake its address into the jitcode and guard against future * reallocation of the global object's slots. */ @@ -6402,31 +6422,34 @@ mjit::Compiler::jsop_setgname_slow(Prope frame.popn(2); pushSyncedEntry(0); } bool mjit::Compiler::jsop_setgname(PropertyName *name, bool popGuaranteed) { if (monitored(PC)) { + if (script == outerScript) + monitoredBytecodes.append(PC - script->code); + /* Global accesses are monitored only for a few names like __proto__. */ jsop_setgname_slow(name); return true; } jsid id = NameToId(name); if (cx->typeInferenceEnabled() && globalObj->isGlobal() && id == types::MakeTypeId(cx, id) && !globalObj->getType(cx)->unknownProperties()) { /* * Note: object branding is disabled when inference is enabled. With * branding there is no way to ensure that a non-function property * can't get a function later and cause the global object to become * branded, requiring a shape change if it changes again. */ - types::TypeSet *types = globalObj->getType(cx)->getProperty(cx, id, false); + types::HeapTypeSet *types = globalObj->getType(cx)->getProperty(cx, id, false); if (!types) return false; js::Shape *shape = globalObj->nativeLookup(cx, NameToId(name)); if (shape && shape->hasDefaultSetter() && shape->writable() && shape->hasSlot() && !types->isOwnProperty(cx, globalObj->getType(cx), true)) { watchGlobalReallocation(); HeapSlot *value = &globalObj->getSlotRef(shape->slot()); @@ -6772,18 +6795,18 @@ mjit::Compiler::jsop_regexp() { JSObject *obj = script->getRegExp(GET_UINT32_INDEX(PC)); RegExpStatics *res = globalObj ? globalObj->getRegExpStatics() : NULL; if (!globalObj || &obj->global() != globalObj || !cx->typeInferenceEnabled() || analysis->localsAliasStack() || - types::TypeSet::HasObjectFlags(cx, globalObj->getType(cx), - types::OBJECT_FLAG_REGEXP_FLAGS_SET)) + types::HeapTypeSet::HasObjectFlags(cx, globalObj->getType(cx), + types::OBJECT_FLAG_REGEXP_FLAGS_SET)) { prepareStubCall(Uses(0)); masm.move(ImmPtr(obj), Registers::ArgReg1); INLINE_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH); frame.pushSynced(JSVAL_TYPE_OBJECT); return true; } @@ -6803,27 +6826,27 @@ mjit::Compiler::jsop_regexp() * these cases. */ analyze::SSAUseChain *uses = analysis->useChain(analyze::SSAValue::PushedValue(PC - script->code, 0)); if (uses && uses->popped && !uses->next && !reobj->global() && !reobj->sticky()) { jsbytecode *use = script->code + uses->offset; uint32_t which = uses->u.which; if (JSOp(*use) == JSOP_CALLPROP) { - JSObject *callee = analysis->pushedTypes(use, 0)->getSingleton(cx); + JSObject *callee = analysis->pushedTypes(use, 0)->getSingleton(); if (callee && callee->isFunction()) { Native native = callee->toFunction()->maybeNative(); if (native == js::regexp_exec || native == js::regexp_test) { frame.push(ObjectValue(*obj)); return true; } } } else if (JSOp(*use) == JSOP_CALL && which == 0) { uint32_t argc = GET_ARGC(use); - JSObject *callee = analysis->poppedTypes(use, argc + 1)->getSingleton(cx); + JSObject *callee = analysis->poppedTypes(use, argc + 1)->getSingleton(); if (callee && callee->isFunction() && argc >= 1 && which == argc - 1) { Native native = callee->toFunction()->maybeNative(); if (native == js::str_match || native == js::str_search || native == js::str_replace || native == js::str_split) { frame.push(ObjectValue(*obj)); return true; @@ -6968,17 +6991,17 @@ mjit::Compiler::finishLoop(jsbytecode *h /* * The interpreter may store integers in slots we assume are doubles, * make sure state is consistent before joining. Note that we don't * need any handling for other safe points the interpreter can enter * from, i.e. from switch and try blocks, as we don't assume double * variables are coherent in such cases. */ for (uint32_t slot = ArgSlot(0); slot < TotalSlots(script); slot++) { - if (a->varTypes[slot].getTypeTag(cx) == JSVAL_TYPE_DOUBLE) { + if (a->varTypes[slot].getTypeTag() == JSVAL_TYPE_DOUBLE) { FrameEntry *fe = frame.getSlotEntry(slot); stubcc.masm.ensureInMemoryDouble(frame.addressOf(fe)); } } /* * Also watch for slots which we assume are doubles at the loop head, * but are known to be int32s at this point. @@ -6986,18 +7009,18 @@ mjit::Compiler::finishLoop(jsbytecode *h const SlotValue *newv = analysis->newValues(head); if (newv) { while (newv->slot) { if (newv->value.kind() == SSAValue::PHI && newv->value.phiOffset() == uint32_t(head - script->code) && analysis->trackSlot(newv->slot)) { JS_ASSERT(newv->slot < TotalSlots(script)); - types::TypeSet *targetTypes = analysis->getValueTypes(newv->value); - if (targetTypes->getKnownTypeTag(cx) == JSVAL_TYPE_DOUBLE) { + types::StackTypeSet *targetTypes = analysis->getValueTypes(newv->value); + if (targetTypes->getKnownTypeTag() == JSVAL_TYPE_DOUBLE) { FrameEntry *fe = frame.getSlotEntry(newv->slot); stubcc.masm.ensureInMemoryDouble(frame.addressOf(fe)); } } newv++; } } @@ -7072,17 +7095,17 @@ mjit::Compiler::jumpAndRun(Jump j, jsbyt * Unless we are coming from a branch which synced everything, syncForBranch * must have been called and ensured an allocation at the target. */ RegisterAllocation *lvtarget = NULL; bool consistent = true; if (cx->typeInferenceEnabled()) { RegisterAllocation *&alloc = analysis->getAllocation(target); if (!alloc) { - alloc = cx->typeLifoAlloc().new_<RegisterAllocation>(false); + alloc = cx->analysisLifoAlloc().new_<RegisterAllocation>(false); if (!alloc) { js_ReportOutOfMemory(cx); return false; } } lvtarget = alloc; consistent = frame.consistentRegisters(target); } @@ -7176,19 +7199,19 @@ mjit::Compiler::constructThis() if (!cx->typeInferenceEnabled() || !fun->hasSingletonType() || fun->getType(cx)->unknownProperties()) { break; } jsid id = NameToId(cx->runtime->atomState.classPrototypeAtom); - types::TypeSet *protoTypes = fun->getType(cx)->getProperty(cx, id, false); - - JSObject *proto = protoTypes->getSingleton(cx, true); + types::HeapTypeSet *protoTypes = fun->getType(cx)->getProperty(cx, id, false); + + JSObject *proto = protoTypes->getSingleton(cx); if (!proto) break; /* * Generate an inline path to create a 'this' object with the given * prototype. Only do this if the type is actually known as a possible * 'this' type of the script. */ @@ -7203,26 +7226,26 @@ mjit::Compiler::constructThis() return false; /* * The template incorporates a shape and/or fixed slots from any * newScript on its type, so make sure recompilation is triggered * should this information change later. */ if (templateObject->type()->newScript) - types::TypeSet::WatchObjectStateChange(cx, templateObject->type()); + types::HeapTypeSet::WatchObjectStateChange(cx, templateObject->type()); RegisterID result = frame.allocReg(); Jump emptyFreeList = getNewObject(cx, result, templateObject); stubcc.linkExit(emptyFreeList, Uses(0)); stubcc.leave(); stubcc.masm.move(ImmPtr(proto), Registers::ArgReg1); - OOL_STUBCALL(stubs::CreateThis, REJOIN_RESUME); + OOL_STUBCALL(stubs::CreateThis, REJOIN_THIS_CREATED); frame.setThis(result); stubcc.rejoin(Changes(1)); return true; } while (false); // Load the callee. @@ -7246,17 +7269,17 @@ mjit::Compiler::constructThis() } // Done with the protoFe. frame.pop(); prepareStubCall(Uses(0)); if (protoReg != Registers::ArgReg1) masm.move(protoReg, Registers::ArgReg1); - INLINE_STUBCALL(stubs::CreateThis, REJOIN_RESUME); + INLINE_STUBCALL(stubs::CreateThis, REJOIN_THIS_CREATED); frame.freeReg(protoReg); return true; } bool mjit::Compiler::jsop_tableswitch(jsbytecode *pc) { #if defined JS_CPU_ARM @@ -7371,17 +7394,17 @@ mjit::Compiler::jsop_toid() void mjit::Compiler::jsop_in() { FrameEntry *obj = frame.peek(-1); FrameEntry *id = frame.peek(-2); if (cx->typeInferenceEnabled() && id->isType(JSVAL_TYPE_INT32)) { - types::TypeSet *types = analysis->poppedTypes(PC, 0); + types::StackTypeSet *types = analysis->poppedTypes(PC, 0); if (obj->mightBeType(JSVAL_TYPE_OBJECT) && !types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_DENSE_ARRAY) && !types::ArrayPrototypeHasIndexedProperty(cx, outerScript)) { bool isPacked = !types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_PACKED_ARRAY); if (!obj->isTypeKnown()) { @@ -7473,21 +7496,21 @@ mjit::Compiler::fixDoubleTypes(jsbytecod while (newv->slot) { if (newv->value.kind() != SSAValue::PHI || newv->value.phiOffset() != uint32_t(target - script->code) || !analysis->trackSlot(newv->slot)) { newv++; continue; } JS_ASSERT(newv->slot < TotalSlots(script)); - types::TypeSet *targetTypes = analysis->getValueTypes(newv->value); + types::StackTypeSet *targetTypes = analysis->getValueTypes(newv->value); FrameEntry *fe = frame.getSlotEntry(newv->slot); VarType &vt = a->varTypes[newv->slot]; - JSValueType type = vt.getTypeTag(cx); - if (targetTypes->getKnownTypeTag(cx) == JSVAL_TYPE_DOUBLE) { + JSValueType type = vt.getTypeTag(); + if (targetTypes->getKnownTypeTag() == JSVAL_TYPE_DOUBLE) { if (type == JSVAL_TYPE_INT32) { fixedIntToDoubleEntries.append(newv->slot); frame.ensureDouble(fe); frame.forgetLoopReg(fe); } else if (type == JSVAL_TYPE_UNKNOWN) { /* * Unknown here but a double at the target. The type * set for the existing value must be empty, so this @@ -7509,65 +7532,65 @@ mjit::Compiler::fixDoubleTypes(jsbytecod } void mjit::Compiler::watchGlobalReallocation() { JS_ASSERT(cx->typeInferenceEnabled()); if (hasGlobalReallocation) return; - types::TypeSet::WatchObjectStateChange(cx, globalObj->getType(cx)); + types::HeapTypeSet::WatchObjectStateChange(cx, globalObj->getType(cx)); hasGlobalReallocation = true; } void mjit::Compiler::updateVarType() { if (!cx->typeInferenceEnabled()) return; /* * For any non-escaping variable written at the current opcode, update the * associated type sets according to the written type, keeping the type set * for each variable in sync with what the SSA analysis has determined * (see prepareInferenceTypes). */ - types::TypeSet *types = pushedTypeSet(0); + types::StackTypeSet *types = pushedTypeSet(0); uint32_t slot = GetBytecodeSlot(script, PC); if (analysis->trackSlot(slot)) { VarType &vt = a->varTypes[slot]; vt.setTypes(types); /* * Variables whose type has been inferred as a double need to be * maintained by the frame as a double. We might forget the exact * representation used by the next call to fixDoubleTypes, fix it now. */ - if (vt.getTypeTag(cx) == JSVAL_TYPE_DOUBLE) + if (vt.getTypeTag() == JSVAL_TYPE_DOUBLE) frame.ensureDouble(frame.getSlotEntry(slot)); } } void mjit::Compiler::updateJoinVarTypes() { if (!cx->typeInferenceEnabled()) return; /* Update variable types for all new values at this bytecode. */ const SlotValue *newv = analysis->newValues(PC); if (newv) { while (newv->slot) { if (newv->slot < TotalSlots(script)) { VarType &vt = a->varTypes[newv->slot]; - JSValueType type = vt.getTypeTag(cx); + JSValueType type = vt.getTypeTag(); vt.setTypes(analysis->getValueTypes(newv->value)); - if (vt.getTypeTag(cx) != type) { + if (vt.getTypeTag() != type) { /* * If the known type of a variable changes (even if the * variable itself has not been reassigned) then we can't * carry a loop register for the var. */ FrameEntry *fe = frame.getSlotEntry(newv->slot); frame.forgetLoopReg(fe); } @@ -7588,33 +7611,33 @@ mjit::Compiler::restoreVarType() if (slot >= analyze::TotalSlots(script)) return; /* * Restore the known type of a live local or argument. We ensure that types * of tracked variables match their inferred type (as tracked in varTypes), * but may have forgotten it due to a branch or syncAndForgetEverything. */ - JSValueType type = a->varTypes[slot].getTypeTag(cx); + JSValueType type = a->varTypes[slot].getTypeTag(); if (type != JSVAL_TYPE_UNKNOWN && (type != JSVAL_TYPE_DOUBLE || analysis->trackSlot(slot))) { FrameEntry *fe = frame.getSlotEntry(slot); JS_ASSERT_IF(fe->isTypeKnown(), fe->isType(type)); if (!fe->isTypeKnown()) frame.learnType(fe, type, false); } } JSValueType mjit::Compiler::knownPushedType(uint32_t pushed) { if (!cx->typeInferenceEnabled()) return JSVAL_TYPE_UNKNOWN; - types::TypeSet *types = analysis->pushedTypes(PC, pushed); - return types->getKnownTypeTag(cx); + types::StackTypeSet *types = analysis->pushedTypes(PC, pushed); + return types->getKnownTypeTag(); } bool mjit::Compiler::mayPushUndefined(uint32_t pushed) { JS_ASSERT(cx->typeInferenceEnabled()); /* @@ -7622,17 +7645,17 @@ mjit::Compiler::mayPushUndefined(uint32_ * undefined without going to a stub that can trigger recompilation. * If this returns false and undefined subsequently becomes a feasible * value pushed by the bytecode, recompilation will *NOT* be triggered. */ types::TypeSet *types = analysis->pushedTypes(PC, pushed); return types->hasType(types::Type::UndefinedType()); } -types::TypeSet * +types::StackTypeSet * mjit::Compiler::pushedTypeSet(uint32_t pushed) { if (!cx->typeInferenceEnabled()) return NULL; return analysis->pushedTypes(PC, pushed); } bool @@ -7659,18 +7682,18 @@ mjit::Compiler::pushSyncedEntry(uint32_t } JSObject * mjit::Compiler::pushedSingleton(unsigned pushed) { if (!cx->typeInferenceEnabled()) return NULL; - types::TypeSet *types = analysis->pushedTypes(PC, pushed); - return types->getSingleton(cx); + types::StackTypeSet *types = analysis->pushedTypes(PC, pushed); + return types->getSingleton(); } /* * Barriers overview. * * After a property fetch finishes, we may need to do type checks on it to make * sure it matches the pushed type set for this bytecode. This can be either * because there is a type barrier at the bytecode, or because we cannot rule @@ -7714,27 +7737,27 @@ mjit::Compiler::pushAddressMaybeBarrier( RegisterID typeReg, dataReg; frame.loadIntoRegisters(address, reuseBase, &typeReg, &dataReg); frame.pushRegs(typeReg, dataReg, type); return testBarrier(typeReg, dataReg, testUndefined); } MaybeJump -mjit::Compiler::trySingleTypeTest(types::TypeSet *types, RegisterID typeReg) +mjit::Compiler::trySingleTypeTest(types::StackTypeSet *types, RegisterID typeReg) { /* * If a type set we have a barrier on is monomorphic, generate a single * jump taken if a type register has a match. This doesn't handle type sets * containing objects, as these require two jumps regardless (test for * object, then test the type of the object). */ MaybeJump res; - switch (types->getKnownTypeTag(cx)) { + switch (types->getKnownTypeTag()) { case JSVAL_TYPE_INT32: res.setJump(masm.testInt32(Assembler::NotEqual, typeReg)); return res; case JSVAL_TYPE_DOUBLE: res.setJump(masm.testNumber(Assembler::NotEqual, typeReg)); return res; @@ -7747,17 +7770,17 @@ mjit::Compiler::trySingleTypeTest(types: return res; default: return res; } } JSC::MacroAssembler::Jump -mjit::Compiler::addTypeTest(types::TypeSet *types, RegisterID typeReg, RegisterID dataReg) +mjit::Compiler::addTypeTest(types::StackTypeSet *types, RegisterID typeReg, RegisterID dataReg) { /* * :TODO: It would be good to merge this with GenerateTypeCheck, but the * two methods have a different format for the tested value (in registers * vs. in memory). */ Vector<Jump> matches(CompilerAllocPolicy(cx, *this)); @@ -7817,17 +7840,17 @@ mjit::Compiler::testBarrier(RegisterID t { BarrierState state; state.typeReg = typeReg; state.dataReg = dataReg; if (!cx->typeInferenceEnabled() || !(js_CodeSpec[*PC].format & JOF_TYPESET)) return state; - types::TypeSet *types = analysis->bytecodeTypes(PC); + types::StackTypeSet *types = analysis->bytecodeTypes(PC); if (types->unknown()) { /* * If the result of this opcode is already unknown, there is no way for * a type barrier to fail. */ return state; } @@ -7836,17 +7859,18 @@ mjit::Compiler::testBarrier(RegisterID t if (!analysis->getCode(PC).monitoredTypesReturn) return state; } else if (!hasTypeBarriers(PC) && !force) { if (testUndefined && !types->hasType(types::Type::UndefinedType())) state.jump.setJump(masm.testUndefined(Assembler::Equal, typeReg)); return state; } - types->addFreeze(cx); + if (hasTypeBarriers(PC)) + typeBarrierBytecodes.append(PC - script->code); /* Cannot have type barriers when the result of the operation is already unknown. */ JS_ASSERT(!types->unknown()); state.jump = trySingleTypeTest(types, typeReg); if (!state.jump.isSet()) state.jump.setJump(addTypeTest(types, typeReg, dataReg));
--- a/js/src/methodjit/Compiler.h +++ b/js/src/methodjit/Compiler.h @@ -204,17 +204,16 @@ class Compiler : public BaseCompiler RegisterID objReg; RegisterID typeReg; Label shapeGuard; jsbytecode *pc; PropertyName *name; bool hasTypeCheck; bool typeMonitored; bool cached; - types::TypeSet *rhsTypes; ValueRemat vr; union { ic::GetPropLabels getPropLabels_; ic::SetPropLabels setPropLabels_; ic::BindNameLabels bindNameLabels_; ic::ScopeNameLabels scopeNameLabels_; }; @@ -244,17 +243,16 @@ class Compiler : public BaseCompiler if (ic.isSet()) { ic.u.vr = vr; } else if (ic.isGet()) { ic.u.get.typeReg = typeReg; ic.u.get.hasTypeCheck = hasTypeCheck; } ic.typeMonitored = typeMonitored; ic.cached = cached; - ic.rhsTypes = rhsTypes; if (ic.isGet()) ic.setLabels(getPropLabels()); else if (ic.isSet()) ic.setLabels(setPropLabels()); else if (ic.isBind()) ic.setLabels(bindNameLabels()); else if (ic.isScopeName()) ic.setLabels(scopeNameLabels()); @@ -316,29 +314,29 @@ class Compiler : public BaseCompiler /* * Information about the current type of an argument or local in the * script. The known type tag of these types is cached when possible to * avoid generating duplicate dependency constraints. */ class VarType { JSValueType type; - types::TypeSet *types; + types::StackTypeSet *types; public: - void setTypes(types::TypeSet *types) { + void setTypes(types::StackTypeSet *types) { this->types = types; this->type = JSVAL_TYPE_MISSING; } types::TypeSet *getTypes() { return types; } - JSValueType getTypeTag(JSContext *cx) { + JSValueType getTypeTag() { if (type == JSVAL_TYPE_MISSING) - type = types ? types->getKnownTypeTag(cx) : JSVAL_TYPE_UNKNOWN; + type = types ? types->getKnownTypeTag() : JSVAL_TYPE_UNKNOWN; return type; } }; struct OutgoingChunkEdge { uint32_t source; uint32_t target; @@ -428,16 +426,18 @@ private: js::Vector<GetElementICInfo, 16, CompilerAllocPolicy> getElemICs; js::Vector<SetElementICInfo, 16, CompilerAllocPolicy> setElemICs; #endif js::Vector<CallPatchInfo, 64, CompilerAllocPolicy> callPatches; js::Vector<InternalCallSite, 64, CompilerAllocPolicy> callSites; js::Vector<DoublePatch, 16, CompilerAllocPolicy> doubleList; js::Vector<JSObject*, 0, CompilerAllocPolicy> rootedTemplates; js::Vector<RegExpShared*, 0, CompilerAllocPolicy> rootedRegExps; + js::Vector<uint32_t> monitoredBytecodes; + js::Vector<uint32_t> typeBarrierBytecodes; js::Vector<uint32_t> fixedIntToDoubleEntries; js::Vector<uint32_t> fixedDoubleToAnyEntries; js::Vector<JumpTable, 16> jumpTables; js::Vector<JumpTableEdge, 16> jumpTableEdges; js::Vector<LoopEntry, 16> loopEntries; js::Vector<OutgoingChunkEdge, 16> chunkEdges; StubCompiler stubcc; Label fastEntryLabel; @@ -542,33 +542,33 @@ private: void markUndefinedLocals(); void fixDoubleTypes(jsbytecode *target); void watchGlobalReallocation(); void updateVarType(); void updateJoinVarTypes(); void restoreVarType(); JSValueType knownPushedType(uint32_t pushed); bool mayPushUndefined(uint32_t pushed); - types::TypeSet *pushedTypeSet(uint32_t which); + types::StackTypeSet *pushedTypeSet(uint32_t which); bool monitored(jsbytecode *pc); bool hasTypeBarriers(jsbytecode *pc); bool testSingletonProperty(HandleObject obj, HandleId id); bool testSingletonPropertyTypes(FrameEntry *top, HandleId id, bool *testObject); CompileStatus addInlineFrame(HandleScript script, uint32_t depth, uint32_t parent, jsbytecode *parentpc); CompileStatus scanInlineCalls(uint32_t index, uint32_t depth); CompileStatus checkAnalysis(HandleScript script); struct BarrierState { MaybeJump jump; RegisterID typeReg; RegisterID dataReg; }; - MaybeJump trySingleTypeTest(types::TypeSet *types, RegisterID typeReg); - Jump addTypeTest(types::TypeSet *types, RegisterID typeReg, RegisterID dataReg); + MaybeJump trySingleTypeTest(types::StackTypeSet *types, RegisterID typeReg); + Jump addTypeTest(types::StackTypeSet *types, RegisterID typeReg, RegisterID dataReg); BarrierState pushAddressMaybeBarrier(Address address, JSValueType type, bool reuseBase, bool testUndefined = false); BarrierState testBarrier(RegisterID typeReg, RegisterID dataReg, bool testUndefined = false, bool testReturn = false, bool force = false); void finishBarrier(const BarrierState &barrier, RejoinState rejoin, uint32_t which); void testPushedType(RejoinState rejoin, int which, bool ool = true);
--- a/js/src/methodjit/FastBuiltins.cpp +++ b/js/src/methodjit/FastBuiltins.cpp @@ -610,30 +610,28 @@ mjit::Compiler::compileArrayConcat(types if (!thisType || &thisType->proto->global() != globalObj) return Compile_InlineAbort; /* * Constraints modeling this concat have not been generated by inference, * so check that type information already reflects possible side effects of * this call. */ - thisTypes->addFreeze(cx); - argTypes->addFreeze(cx); - types::TypeSet *thisElemTypes = thisType->getProperty(cx, JSID_VOID, false); + types::HeapTypeSet *thisElemTypes = thisType->getProperty(cx, JSID_VOID, false); if (!thisElemTypes) return Compile_Error; if (!pushedTypeSet(0)->hasType(types::Type::ObjectType(thisType))) return Compile_InlineAbort; for (unsigned i = 0; i < argTypes->getObjectCount(); i++) { if (argTypes->getSingleObject(i)) return Compile_InlineAbort; types::TypeObject *argType = argTypes->getTypeObject(i); if (!argType) continue; - types::TypeSet *elemTypes = argType->getProperty(cx, JSID_VOID, false); + types::HeapTypeSet *elemTypes = argType->getProperty(cx, JSID_VOID, false); if (!elemTypes) return Compile_Error; if (!elemTypes->knownSubset(cx, thisElemTypes)) return Compile_InlineAbort; } /* Test for 'length == initializedLength' on both arrays. */ @@ -870,17 +868,17 @@ mjit::Compiler::compileParseInt(JSValueT CompileStatus mjit::Compiler::inlineNativeFunction(uint32_t argc, bool callingNew) { if (!cx->typeInferenceEnabled()) return Compile_InlineAbort; FrameEntry *origCallee = frame.peek(-((int)argc + 2)); FrameEntry *thisValue = frame.peek(-((int)argc + 1)); - types::TypeSet *thisTypes = analysis->poppedTypes(PC, argc); + types::StackTypeSet *thisTypes = analysis->poppedTypes(PC, argc); if (!origCallee->isConstant() || !origCallee->isType(JSVAL_TYPE_OBJECT)) return Compile_InlineAbort; JSObject *callee = &origCallee->getValue().toObject(); if (!callee->isFunction()) return Compile_InlineAbort; @@ -942,17 +940,17 @@ mjit::Compiler::inlineNativeFunction(uin types::OBJECT_FLAG_ITERATED) && !types::ArrayPrototypeHasIndexedProperty(cx, outerScript)) { bool packed = !thisTypes->hasObjectFlags(cx, types::OBJECT_FLAG_NON_PACKED_ARRAY); return compileArrayPopShift(thisValue, packed, native == js::array_pop); } } } else if (argc == 1) { FrameEntry *arg = frame.peek(-1); - types::TypeSet *argTypes = frame.extra(arg).types; + types::StackTypeSet *argTypes = frame.extra(arg).types; if (!argTypes) return Compile_InlineAbort; JSValueType argType = arg->isTypeKnown() ? arg->getKnownType() : JSVAL_TYPE_UNKNOWN; if (native == js_math_abs) { if (argType == JSVAL_TYPE_INT32 && type == JSVAL_TYPE_INT32) return compileMathAbsInt(arg);
--- a/js/src/methodjit/FastOps.cpp +++ b/js/src/methodjit/FastOps.cpp @@ -382,18 +382,18 @@ mjit::Compiler::jsop_equality_obj_obj(JS JS_ASSERT(cx->typeInferenceEnabled() && lhs->isType(JSVAL_TYPE_OBJECT) && rhs->isType(JSVAL_TYPE_OBJECT)); /* * Handle equality between two objects. We have to ensure there is no * special equality operator on either object, if that passes then * this is a pointer comparison. */ - types::TypeSet *lhsTypes = analysis->poppedTypes(PC, 1); - types::TypeSet *rhsTypes = analysis->poppedTypes(PC, 0); + types::StackTypeSet *lhsTypes = analysis->poppedTypes(PC, 1); + types::StackTypeSet *rhsTypes = analysis->poppedTypes(PC, 0); if (!lhsTypes->hasObjectFlags(cx, types::OBJECT_FLAG_SPECIAL_EQUALITY) && !rhsTypes->hasObjectFlags(cx, types::OBJECT_FLAG_SPECIAL_EQUALITY)) { /* :TODO: Merge with jsop_relational_int? */ JS_ASSERT_IF(!target, fused != JSOP_IFEQ); frame.forgetMismatchedObject(lhs); frame.forgetMismatchedObject(rhs); Assembler::Condition cond = GetCompareCondition(op, fused); if (target) { @@ -891,18 +891,18 @@ mjit::Compiler::jsop_andor(JSOp op, jsby return booleanJumpScript(op, target); } bool mjit::Compiler::jsop_localinc(JSOp op, uint32_t slot) { restoreVarType(); - types::TypeSet *types = pushedTypeSet(0); - JSValueType type = types ? types->getKnownTypeTag(cx) : JSVAL_TYPE_UNKNOWN; + types::StackTypeSet *types = pushedTypeSet(0); + JSValueType type = types ? types->getKnownTypeTag() : JSVAL_TYPE_UNKNOWN; int amt = (op == JSOP_LOCALINC || op == JSOP_INCLOCAL) ? 1 : -1; if (!analysis->incrementInitialValueObserved(PC)) { // Before: // After: V frame.pushLocal(slot); @@ -954,18 +954,18 @@ mjit::Compiler::jsop_localinc(JSOp op, u return true; } bool mjit::Compiler::jsop_arginc(JSOp op, uint32_t slot) { restoreVarType(); - types::TypeSet *types = pushedTypeSet(0); - JSValueType type = types ? types->getKnownTypeTag(cx) : JSVAL_TYPE_UNKNOWN; + types::StackTypeSet *types = pushedTypeSet(0); + JSValueType type = types ? types->getKnownTypeTag() : JSVAL_TYPE_UNKNOWN; int amt = (op == JSOP_ARGINC || op == JSOP_INCARG) ? 1 : -1; if (!analysis->incrementInitialValueObserved(PC)) { // Before: // After: V if (script->argsObjAliasesFormals()) jsop_aliasedArg(slot, /* get = */ true); @@ -1164,17 +1164,17 @@ mjit::Compiler::jsop_setelem_dense() #ifdef JSGC_INCREMENTAL_MJ /* * Write barrier. * We skip over the barrier if we incremented initializedLength above, * because in that case the slot we're overwriting was previously * undefined. */ - types::TypeSet *types = frame.extra(obj).types; + types::StackTypeSet *types = frame.extra(obj).types; if (cx->compartment->compileBarriers() && (!types || types->propertyNeedsBarrier(cx, JSID_VOID))) { Label barrierStart = stubcc.masm.label(); stubcc.linkExitDirect(masm.jump(), barrierStart); /* * The sync call below can potentially clobber key.reg() and slotsReg. * We pin key.reg() to avoid it being clobbered. If |hoisted| is true, * we can also pin slotsReg. If not, then slotsReg is owned by the @@ -1533,37 +1533,40 @@ GetDenseArrayShape(JSContext *cx, JSObje bool mjit::Compiler::jsop_setelem(bool popGuaranteed) { FrameEntry *obj = frame.peek(-3); FrameEntry *id = frame.peek(-2); FrameEntry *value = frame.peek(-1); if (!IsCacheableSetElem(obj, id, value) || monitored(PC)) { + if (monitored(PC) && script == outerScript) + monitoredBytecodes.append(PC - script->code); + jsop_setelem_slow(); return true; } // If the object is definitely a dense array or a typed array we can generate // code directly without using an inline cache. if (cx->typeInferenceEnabled()) { - types::TypeSet *types = analysis->poppedTypes(PC, 2); + types::StackTypeSet *types = analysis->poppedTypes(PC, 2); if (!types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_DENSE_ARRAY) && !types::ArrayPrototypeHasIndexedProperty(cx, outerScript)) { // Inline dense array path. jsop_setelem_dense(); return true; } #ifdef JS_METHODJIT_TYPED_ARRAY if ((value->mightBeType(JSVAL_TYPE_INT32) || value->mightBeType(JSVAL_TYPE_DOUBLE)) && !types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_TYPED_ARRAY)) { // Inline typed array path. - int atype = types->getTypedArrayType(cx); + int atype = types->getTypedArrayType(); if (atype != TypedArray::TYPE_MAX) { jsop_setelem_typed(atype); return true; } } #endif } @@ -2140,18 +2143,18 @@ mjit::Compiler::jsop_getelem() if (!IsCacheableGetElem(obj, id)) { jsop_getelem_slow(); return true; } // If the object is definitely an arguments object, a dense array or a typed array // we can generate code directly without using an inline cache. if (cx->typeInferenceEnabled() && !id->isType(JSVAL_TYPE_STRING)) { - types::TypeSet *types = analysis->poppedTypes(PC, 1); - if (types->isMagicArguments(cx) && !outerScript->analysis()->modifiesArguments()) { + types::StackTypeSet *types = analysis->poppedTypes(PC, 1); + if (types->isMagicArguments() && !outerScript->analysis()->modifiesArguments()) { // Inline arguments path. jsop_getelem_args(); return true; } if (obj->mightBeType(JSVAL_TYPE_OBJECT) && !types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_DENSE_ARRAY) && !types::ArrayPrototypeHasIndexedProperty(cx, outerScript)) { @@ -2160,17 +2163,17 @@ mjit::Compiler::jsop_getelem() jsop_getelem_dense(packed); return true; } #ifdef JS_METHODJIT_TYPED_ARRAY if (obj->mightBeType(JSVAL_TYPE_OBJECT) && !types->hasObjectFlags(cx, types::OBJECT_FLAG_NON_TYPED_ARRAY)) { // Inline typed array path. - int atype = types->getTypedArrayType(cx); + int atype = types->getTypedArrayType(); if (atype != TypedArray::TYPE_MAX) { if (jsop_getelem_typed(atype)) return true; // Fallthrough to the normal GETELEM path. } } #endif } @@ -2681,16 +2684,19 @@ mjit::Compiler::jsop_initprop() { FrameEntry *obj = frame.peek(-2); FrameEntry *fe = frame.peek(-1); PropertyName *name = script->getName(GET_UINT32_INDEX(PC)); RootedObject baseobj(cx, frame.extra(obj).initObject); if (!baseobj || monitored(PC) || cx->compartment->compileBarriers()) { + if (monitored(PC) && script == outerScript) + monitoredBytecodes.append(PC - script->code); + prepareStubCall(Uses(2)); masm.move(ImmPtr(name), Registers::ArgReg1); INLINE_STUBCALL(stubs::InitProp, REJOIN_FALLTHROUGH); return; } RootedObject holder(cx); RootedShape shape(cx);
--- a/js/src/methodjit/FrameState.cpp +++ b/js/src/methodjit/FrameState.cpp @@ -24,17 +24,18 @@ FrameState::FrameState(JSContext *cx, mj loop(NULL), inTryBlock(false) { } FrameState::~FrameState() { while (a) { ActiveFrame *parent = a->parent; - a->script->analysis()->clearAllocations(); + if (a->script->hasAnalysis()) + a->script->analysis()->clearAllocations(); cx->free_(a); a = parent; } cx->free_(entries); } void FrameState::pruneDeadEntries() @@ -537,17 +538,17 @@ FrameState::dumpAllocation(RegisterAlloc printf("\n"); } #endif RegisterAllocation * FrameState::computeAllocation(jsbytecode *target) { JS_ASSERT(cx->typeInferenceEnabled()); - RegisterAllocation *alloc = cx->typeLifoAlloc().new_<RegisterAllocation>(false); + RegisterAllocation *alloc = cx->analysisLifoAlloc().new_<RegisterAllocation>(false); if (!alloc) { js_ReportOutOfMemory(cx); return NULL; } /* * State must be synced at exception and switch targets, at traps and when * crossing between compilation chunks. @@ -587,18 +588,18 @@ FrameState::computeAllocation(jsbytecode if (!a->analysis->trackSlot(entrySlot(fe))) continue; bool nonDoubleTarget = false; const SlotValue *newv = a->analysis->newValues(target); while (newv && newv->slot) { if (newv->value.kind() == SSAValue::PHI && newv->value.phiOffset() == uint32_t(target - a->script->code) && newv->slot == entrySlot(fe)) { - types::TypeSet *types = a->analysis->getValueTypes(newv->value); - if (types->getKnownTypeTag(cx) != JSVAL_TYPE_DOUBLE) + types::StackTypeSet *types = a->analysis->getValueTypes(newv->value); + if (types->getKnownTypeTag() != JSVAL_TYPE_DOUBLE) nonDoubleTarget = true; } newv++; } if (nonDoubleTarget) continue; } alloc->set(reg, fe - entries, fe->data.synced()); @@ -820,17 +821,17 @@ FrameState::discardForJoin(RegisterAlloc return true; } if (!alloc) { /* * This shows up for loop entries which are not reachable from the * loop head, and for exception, switch target and trap safe points. */ - alloc = cx->typeLifoAlloc().new_<RegisterAllocation>(false); + alloc = cx->analysisLifoAlloc().new_<RegisterAllocation>(false); if (!alloc) { js_ReportOutOfMemory(cx); return false; } } resetInternalState(); PodArrayZero(regstate_);
--- a/js/src/methodjit/FrameState.h +++ b/js/src/methodjit/FrameState.h @@ -693,17 +693,17 @@ class FrameState * Discards a FrameEntry, tricking the FS into thinking it's synced. */ void discardFe(FrameEntry *fe); /* Compiler-owned metadata about stack entries, reset on push/pop/copy. */ struct StackEntryExtra { bool initArray; JSObject *initObject; - types::TypeSet *types; + types::StackTypeSet *types; JSAtom *name; void reset() { PodZero(this); } }; StackEntryExtra& extra(const FrameEntry *fe) { JS_ASSERT(fe >= a->args && fe < a->sp); return extraArray[fe - entries]; } StackEntryExtra& extra(uint32_t slot) { return extra(entries + slot); }
--- a/js/src/methodjit/InvokeHelpers.cpp +++ b/js/src/methodjit/InvokeHelpers.cpp @@ -862,27 +862,34 @@ js_InternalInterpret(void *returnData, v case REJOIN_THIS_PROTOTYPE: { RootedObject callee(cx, &fp->callee()); JSObject *proto = f.regs.sp[0].isObject() ? &f.regs.sp[0].toObject() : NULL; JSObject *obj = js_CreateThisForFunctionWithProto(cx, callee, proto); if (!obj) return js_InternalThrow(f); fp->thisValue() = ObjectValue(*obj); + /* FALLTHROUGH */ + } + case REJOIN_THIS_CREATED: { Probes::enterScript(f.cx, f.script(), f.script()->function(), fp); if (script->debugMode) { JSTrapStatus status = js::ScriptDebugPrologue(f.cx, f.fp()); switch (status) { case JSTRAP_CONTINUE: break; - case JSTRAP_RETURN: - *f.returnAddressLocation() = f.cx->jaegerRuntime().forceReturnFromExternC(); - return NULL; + case JSTRAP_RETURN: { + /* Advance to the JSOP_STOP at the end of the script. */ + f.regs.pc = script->code + script->length - 1; + nextDepth = 0; + JS_ASSERT(*f.regs.pc == JSOP_STOP); + break; + } case JSTRAP_THROW: case JSTRAP_ERROR: return js_InternalThrow(f); default: JS_NOT_REACHED("bad ScriptDebugPrologue status"); } }
--- a/js/src/methodjit/LoopState.cpp +++ b/js/src/methodjit/LoopState.cpp @@ -121,31 +121,31 @@ LoopState::init(jsbytecode *head, Jump e JaegerSpew(JSpew_Analysis, "loop modified property at %u: %s %s\n", lifetime->head, types::TypeString(types::Type::ObjectType(modifiedProperties[i].object)), TypeIdString(modifiedProperties[i].id)); } RegisterAllocation *&alloc = outerAnalysis->getAllocation(head); JS_ASSERT(!alloc); - alloc = cx->typeLifoAlloc().new_<RegisterAllocation>(true); + alloc = cx->analysisLifoAlloc().new_<RegisterAllocation>(true); if (!alloc) { js_ReportOutOfMemory(cx); return false; } this->alloc = alloc; this->loopRegs = Registers::AvailAnyRegs; /* * Don't hoist bounds checks or loop invariant code in scripts that have * had indirect modification of their arguments. */ if (outerScript->function()) { - if (TypeSet::HasObjectFlags(cx, outerScript->function()->getType(cx), OBJECT_FLAG_UNINLINEABLE)) + if (HeapTypeSet::HasObjectFlags(cx, outerScript->function()->getType(cx), OBJECT_FLAG_UNINLINEABLE)) this->skipAnalysis = true; } /* * Don't hoist bounds checks or loop invariant code in loops with safe * points in the middle, which the interpreter can join at directly without * performing hoisted bounds checks or doing initial computation of loop * invariant terms. @@ -773,20 +773,20 @@ LoopState::invariantLength(const CrossSS { if (skipAnalysis) return NULL; uint32_t objSlot; int32_t objConstant; if (!getEntryValue(obj, &objSlot, &objConstant) || objSlot == UNASSIGNED || objConstant != 0) return NULL; - TypeSet *objTypes = ssa->getValueTypes(obj); + StackTypeSet *objTypes = ssa->getValueTypes(obj); /* Check for 'length' on the lazy arguments for the current frame. */ - if (objTypes->isMagicArguments(cx)) { + if (objTypes->isMagicArguments()) { JS_ASSERT(obj.frame == CrossScriptSSA::OUTER_FRAME); for (unsigned i = 0; i < invariantEntries.length(); i++) { InvariantEntry &entry = invariantEntries[i]; if (entry.kind == InvariantEntry::INVARIANT_ARGS_LENGTH) return frame.getTemporary(entry.u.array.temporary); } @@ -819,19 +819,16 @@ LoopState::invariantLength(const CrossSS } } if (!loopInvariantEntry(objSlot)) return NULL; /* Hoist 'length' access on typed arrays. */ if (!objTypes->hasObjectFlags(cx, OBJECT_FLAG_NON_TYPED_ARRAY)) { - /* Recompile if object type changes. */ - objTypes->addFreeze(cx); - uint32_t which = frame.allocTemporary(); if (which == UINT32_MAX) return NULL; FrameEntry *fe = frame.getTemporary(which); JaegerSpew(JSpew_Analysis, "Using %s for loop invariant typed array length of %s\n", frame.entryName(fe), frame.entryName(objSlot)); @@ -856,17 +853,16 @@ LoopState::invariantLength(const CrossSS */ for (unsigned i = 0; i < objTypes->getObjectCount(); i++) { if (objTypes->getSingleObject(i) != NULL) return NULL; TypeObject *object = objTypes->getTypeObject(i); if (object && hasModifiedProperty(object, JSID_VOID)) return NULL; } - objTypes->addFreeze(cx); uint32_t which = frame.allocTemporary(); if (which == UINT32_MAX) return NULL; FrameEntry *fe = frame.getTemporary(which); JaegerSpew(JSpew_Analysis, "Using %s for loop invariant dense array length of %s\n", frame.entryName(fe), frame.entryName(objSlot)); @@ -908,22 +904,21 @@ LoopState::invariantProperty(const Cross /* Check that the property is definite and not written anywhere in the loop. */ TypeSet *objTypes = ssa->getValueTypes(obj); if (objTypes->unknownObject() || objTypes->getObjectCount() != 1) return NULL; TypeObject *object = objTypes->getTypeObject(0); if (!object || object->unknownProperties() || hasModifiedProperty(object, id) || id != MakeTypeId(cx, id)) return NULL; - TypeSet *propertyTypes = object->getProperty(cx, id, false); + HeapTypeSet *propertyTypes = object->getProperty(cx, id, false); if (!propertyTypes) return NULL; - if (!propertyTypes->isDefiniteProperty() || propertyTypes->isOwnProperty(cx, object, true)) + if (!propertyTypes->definiteProperty() || propertyTypes->isOwnProperty(cx, object, true)) return NULL; - objTypes->addFreeze(cx); uint32_t which = frame.allocTemporary(); if (which == UINT32_MAX) return NULL; FrameEntry *fe = frame.getTemporary(which); JaegerSpew(JSpew_Analysis, "Using %s for loop invariant property of %s\n", frame.entryName(fe), frame.entryName(objSlot)); @@ -1173,18 +1168,18 @@ LoopState::ignoreIntegerOverflow(const C * Only ignore negative zero if this is the RHS of an addition. * Otherwise the result of the other side could change to a double * after the first LHS has been computed, and be affected by a * negative zero LHS. */ return false; } - TypeSet *lhsTypes = outerAnalysis->poppedTypes(use->offset, 1); - if (lhsTypes->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) + StackTypeSet *lhsTypes = outerAnalysis->poppedTypes(use->offset, 1); + if (lhsTypes->getKnownTypeTag() != JSVAL_TYPE_INT32) return false; JaegerSpew(JSpew_Analysis, "Integer result is RHS in integer addition\n"); return true; } return false; } @@ -1566,18 +1561,18 @@ LoopState::analyzeLoopTest() default: return; } SSAValue one = outerAnalysis->poppedValue(test.pushedOffset(), 1); SSAValue two = outerAnalysis->poppedValue(test.pushedOffset(), 0); /* The test must be comparing known integers. */ - if (outerAnalysis->getValueTypes(one)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32 || - outerAnalysis->getValueTypes(two)->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) { + if (outerAnalysis->getValueTypes(one)->getKnownTypeTag() != JSVAL_TYPE_INT32 || + outerAnalysis->getValueTypes(two)->getKnownTypeTag() != JSVAL_TYPE_INT32) { return; } /* Reverse the condition if the RHS is modified by the loop. */ uint32_t swapRHS; int32_t swapConstant; if (getLoopTestAccess(two, &swapRHS, &swapConstant)) { if (swapRHS != UNASSIGNED && outerAnalysis->liveness(swapRHS).firstWrite(lifetime) != UINT32_MAX) { @@ -1670,21 +1665,21 @@ LoopState::definiteArrayAccess(const SSA * integer. * * This is used to determine if we can ignore possible integer overflow in * an operation; if this site could read a non-integer element out of the * array or invoke a scripted getter/setter, it could produce a string or * other value by which the overflow could be observed. */ - TypeSet *objTypes = outerAnalysis->getValueTypes(obj); - TypeSet *elemTypes = outerAnalysis->getValueTypes(index); + StackTypeSet *objTypes = outerAnalysis->getValueTypes(obj); + StackTypeSet *elemTypes = outerAnalysis->getValueTypes(index); - if (objTypes->getKnownTypeTag(cx) != JSVAL_TYPE_OBJECT || - elemTypes->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) { + if (objTypes->getKnownTypeTag() != JSVAL_TYPE_OBJECT || + elemTypes->getKnownTypeTag() != JSVAL_TYPE_INT32) { return false; } if (objTypes->hasObjectFlags(cx, OBJECT_FLAG_NON_DENSE_ARRAY)) return false; if (ArrayPrototypeHasIndexedProperty(cx, outerScript)) return false; @@ -1796,29 +1791,28 @@ LoopState::analyzeLoopBody(unsigned fram case JSOP_NEW: skipAnalysis = true; break; case JSOP_SETELEM: { SSAValue objValue = analysis->poppedValue(pc, 2); SSAValue elemValue = analysis->poppedValue(pc, 1); - TypeSet *objTypes = analysis->getValueTypes(objValue); - TypeSet *elemTypes = analysis->getValueTypes(elemValue); + StackTypeSet *objTypes = analysis->getValueTypes(objValue); + StackTypeSet *elemTypes = analysis->getValueTypes(elemValue); /* * Mark the modset as unknown if the index might be non-integer, * we don't want to consider the SETELEM PIC here. */ - if (objTypes->unknownObject() || elemTypes->getKnownTypeTag(cx) != JSVAL_TYPE_INT32) { + if (objTypes->unknownObject() || elemTypes->getKnownTypeTag() != JSVAL_TYPE_INT32) { unknownModset = true; break; } - objTypes->addFreeze(cx); for (unsigned i = 0; i < objTypes->getObjectCount(); i++) { TypeObject *object = objTypes->getTypeObject(i); if (!object) continue; if (!addModifiedProperty(object, JSID_VOID)) return; if (analysis->getCode(pc).arrayWriteHole && !addGrowArray(object)) return; @@ -1843,17 +1837,16 @@ LoopState::analyzeLoopBody(unsigned fram jsid id = MakeTypeId(cx, NameToId(name)); TypeSet *objTypes = analysis->poppedTypes(pc, 1); if (objTypes->unknownObject()) { unknownModset = true; break; } - objTypes->addFreeze(cx); for (unsigned i = 0; i < objTypes->getObjectCount(); i++) { TypeObject *object = objTypes->getTypeObject(i); if (!object) continue; if (!addModifiedProperty(object, id)) continue; } @@ -1911,26 +1904,26 @@ LoopState::analyzeLoopBody(unsigned fram case JSOP_EQ: case JSOP_NE: case JSOP_LT: case JSOP_LE: case JSOP_GT: case JSOP_GE: case JSOP_STRICTEQ: case JSOP_STRICTNE: { - JSValueType type = analysis->poppedTypes(pc, 1)->getKnownTypeTag(cx); + JSValueType type = analysis->poppedTypes(pc, 1)->getKnownTypeTag(); if (type != JSVAL_TYPE_INT32 && type != JSVAL_TYPE_DOUBLE) constrainedLoop = false; } /* FALLTHROUGH */ case JSOP_POS: case JSOP_NEG: case JSOP_BITNOT: { - JSValueType type = analysis->poppedTypes(pc, 0)->getKnownTypeTag(cx); + JSValueType type = analysis->poppedTypes(pc, 0)->getKnownTypeTag(); if (type != JSVAL_TYPE_INT32 && type != JSVAL_TYPE_DOUBLE) constrainedLoop = false; break; } default: constrainedLoop = false; break;
--- a/js/src/methodjit/MethodJIT.cpp +++ b/js/src/methodjit/MethodJIT.cpp @@ -1117,20 +1117,32 @@ JITChunk::rootedTemplates() const } RegExpShared ** JITChunk::rootedRegExps() const { return (RegExpShared **)&rootedTemplates()[nRootedTemplates]; } +uint32_t * +JITChunk::monitoredBytecodes() const +{ + return (uint32_t *)&rootedRegExps()[nRootedRegExps]; +} + +uint32_t * +JITChunk::typeBarrierBytecodes() const +{ + return (uint32_t *)&monitoredBytecodes()[nMonitoredBytecodes]; +} + char * JITChunk::commonSectionLimit() const { - return (char *)&rootedRegExps()[nRootedRegExps]; + return (char *)&typeBarrierBytecodes()[nTypeBarrierBytecodes]; } #ifdef JS_MONOIC ic::GetGlobalNameIC * JITChunk::getGlobalNames() const { return (ic::GetGlobalNameIC *) commonSectionLimit(); } @@ -1252,16 +1264,19 @@ JITChunk::~JITChunk() } void JITScript::destroy(FreeOp *fop) { for (unsigned i = 0; i < nchunks; i++) destroyChunk(fop, i); + if (liveness) + fop->free_(liveness); + if (shimPool) shimPool->release(); } void JITScript::destroyChunk(FreeOp *fop, unsigned chunkIndex, bool resetUses) { ChunkDescriptor &desc = chunkDescriptor(chunkIndex); @@ -1379,16 +1394,18 @@ JSScript::sizeOfJitScripts(JSMallocSizeO } return n; } size_t mjit::JITScript::sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf) { size_t n = mallocSizeOf(this); + if (liveness) + n += mallocSizeOf(liveness); for (unsigned i = 0; i < nchunks; i++) { const ChunkDescriptor &desc = chunkDescriptor(i); if (desc.chunk) n += desc.chunk->sizeOfIncludingThis(mallocSizeOf); } return n; } @@ -1397,16 +1414,18 @@ size_t mjit::JITChunk::computedSizeOfIncludingThis() { return sizeof(JITChunk) + sizeof(NativeMapEntry) * nNmapPairs + sizeof(InlineFrame) * nInlineFrames + sizeof(CallSite) * nCallSites + sizeof(JSObject*) * nRootedTemplates + sizeof(RegExpShared*) * nRootedRegExps + + sizeof(uint32_t) * nMonitoredBytecodes + + sizeof(uint32_t) * nTypeBarrierBytecodes + #if defined JS_MONOIC sizeof(ic::GetGlobalNameIC) * nGetGlobalNames + sizeof(ic::SetGlobalNameIC) * nSetGlobalNames + sizeof(ic::CallICInfo) * nCallICs + sizeof(ic::EqualityICInfo) * nEqualityICs + #endif #if defined JS_POLYIC sizeof(ic::PICInfo) * nPICs +
--- a/js/src/methodjit/MethodJIT.h +++ b/js/src/methodjit/MethodJIT.h @@ -32,16 +32,20 @@ namespace js { namespace mjit { struct JITChunk; struct JITScript; } +namespace analyze { + struct ScriptLiveness; +} + struct VMFrame { #if defined(JS_CPU_SPARC) void *savedL0; void *savedL1; void *savedL2; void *savedL3; void *savedL4; @@ -295,16 +299,19 @@ enum RejoinState { REJOIN_PUSH_OBJECT, /* * During the prologue of constructing scripts, after the function's * .prototype property has been fetched. */ REJOIN_THIS_PROTOTYPE, + /* As above, after the 'this' object has been created. */ + REJOIN_THIS_CREATED, + /* * Type check on arguments failed during prologue, need stack check and * the rest of the JIT prologue before the script can execute. */ REJOIN_CHECK_ARGUMENTS, /* * The script's jitcode was discarded during one of the following steps of @@ -644,16 +651,18 @@ struct JITChunk * changing nMICs() et al as well. */ uint32_t nNmapPairs : 31; /* The NativeMapEntrys are sorted by .bcOff. .ncode values may not be NULL. */ uint32_t nInlineFrames; uint32_t nCallSites; uint32_t nRootedTemplates; uint32_t nRootedRegExps; + uint32_t nMonitoredBytecodes; + uint32_t nTypeBarrierBytecodes; #ifdef JS_MONOIC uint32_t nGetGlobalNames; uint32_t nSetGlobalNames; uint32_t nCallICs; uint32_t nEqualityICs; #endif #ifdef JS_POLYIC uint32_t nGetElems; @@ -672,16 +681,25 @@ struct JITChunk // Additional ExecutablePools for native call and getter stubs. Vector<NativeCallStub, 0, SystemAllocPolicy> nativeCallStubs; NativeMapEntry *nmap() const; js::mjit::InlineFrame *inlineFrames() const; js::mjit::CallSite *callSites() const; JSObject **rootedTemplates() const; RegExpShared **rootedRegExps() const; + + /* + * Offsets of bytecodes which were monitored or had type barriers at the + * point of compilation. Used to avoid unnecessary recompilation after + * analysis purges. + */ + uint32_t *monitoredBytecodes() const; + uint32_t *typeBarrierBytecodes() const; + #ifdef JS_MONOIC ic::GetGlobalNameIC *getGlobalNames() const; ic::SetGlobalNameIC *setGlobalNames() const; ic::CallICInfo *callICs() const; ic::EqualityICInfo *equalityICs() const; #endif #ifdef JS_POLYIC ic::GetElementIC *getElems() const; @@ -781,16 +799,22 @@ struct JITScript uint32_t nedges; /* * Pool for shims which transfer control to the interpreter on cross chunk * edges to chunks which do not have compiled code. */ JSC::ExecutablePool *shimPool; + /* + * Optional liveness information attached to the JITScript if the analysis + * information is purged while retaining JIT info. + */ + analyze::ScriptLiveness *liveness; + #ifdef JS_MONOIC /* Inline cache at function entry for checking this/argument types. */ JSC::CodeLocationLabel argsCheckStub; JSC::CodeLocationLabel argsCheckFallthrough; JSC::CodeLocationJump argsCheckJump; JSC::ExecutablePool *argsCheckPool; void resetArgsCheck(); #endif
--- a/js/src/methodjit/PolyIC.cpp +++ b/js/src/methodjit/PolyIC.cpp @@ -405,17 +405,27 @@ class SetPropCompiler : public PICStubCo if (monitor.recompiled()) return false; if (!type->unknownProperties()) { types::AutoEnterTypeInference enter(cx); types::TypeSet *types = type->getProperty(cx, types::MakeTypeId(cx, id), true); if (!types) return false; - pic.rhsTypes->addSubset(cx, types); + + jsbytecode *pc; + JSScript *script = cx->stack.currentScript(&pc); + + if (!script->ensureRanInference(cx) || monitor.recompiled()) + return false; + + JS_ASSERT(*pc == JSOP_SETPROP || *pc == JSOP_SETNAME); + + types::StackTypeSet *rhsTypes = script->analysis()->poppedTypes(pc, 0); + rhsTypes->addSubset(cx, types); } return !monitor.recompiled(); } LookupStatus update() { JS_ASSERT(pic.hit);
--- a/js/src/methodjit/PolyIC.h +++ b/js/src/methodjit/PolyIC.h @@ -404,19 +404,16 @@ struct PICInfo : public BasePolyIC { bool typeMonitored : 1; // For GET caches, whether the access may use the property cache. bool cached : 1; // Offset from start of fast path to initial shape guard. uint32_t shapeGuard; - // Possible types of the RHS, for monitored SETPROP PICs. - types::TypeSet *rhsTypes; - inline bool isSet() const { return kind == SET; } inline bool isGet() const { return kind == GET; } inline bool isBind() const { return kind == BIND;
--- a/modules/libpref/src/init/all.js +++ b/modules/libpref/src/init/all.js @@ -720,16 +720,18 @@ pref("javascript.options.mem.gc_high_fre pref("javascript.options.mem.gc_high_frequency_low_limit_mb", 100); pref("javascript.options.mem.gc_high_frequency_high_limit_mb", 500); pref("javascript.options.mem.gc_high_frequency_heap_growth_max", 300); pref("javascript.options.mem.gc_high_frequency_heap_growth_min", 150); pref("javascript.options.mem.gc_low_frequency_heap_growth", 150); pref("javascript.options.mem.gc_dynamic_heap_growth", true); pref("javascript.options.mem.gc_dynamic_mark_slice", true); +pref("javascript.options.mem.analysis_purge_mb", 100); + // advanced prefs pref("advanced.mailftp", false); pref("image.animation_mode", "normal"); // Same-origin policy for file URIs, "false" is traditional pref("security.fileuri.strict_origin_policy", true); // If there is ever a security firedrill that requires