diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index f8a0e3fe2c7..6b1117540e0 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -808,7 +808,7 @@ BaselineCompiler::emitDebugTrap() void BaselineCompiler::emitCoverage(jsbytecode* pc) { - PCCounts* counts = script->getPCCounts(pc); + PCCounts* counts = script->maybeGetPCCounts(pc); if (!counts) return; diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index f8b6d158b02..f1f82855b89 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -693,6 +693,17 @@ HandleExceptionBaseline(JSContext* cx, const JitFrameIterator& frame, ResumeFrom { MOZ_ASSERT(frame.isBaselineJS()); + bool frameOk = false; + RootedScript script(cx, frame.baselineFrame()->script()); + + if (script->hasScriptCounts()) { + PCCounts* counts = script->getThrowCounts(pc); + // If we failed to allocate, then skip the increment and continue to + // handle the exception. + if (counts) + counts->numExec()++; + } + // We may be propagating a forced return from the interrupt // callback, which cannot easily force a return. if (cx->isPropagatingForcedReturn()) { @@ -701,10 +712,7 @@ HandleExceptionBaseline(JSContext* cx, const JitFrameIterator& frame, ResumeFrom return; } - bool frameOk = false; - RootedScript script(cx, frame.baselineFrame()->script()); - -again: + again: if (cx->isExceptionPending()) { if (!cx->isClosingGenerator()) { switch (Debugger::onExceptionUnwind(cx, frame.baselineFrame())) { @@ -733,8 +741,12 @@ again: ScopeIter si(cx, frame.baselineFrame(), pc); if (!ProcessTryNotesBaseline(cx, frame, si, rfe, &pc)) goto again; - if (rfe->kind != ResumeFromException::RESUME_ENTRY_FRAME) + if (rfe->kind != ResumeFromException::RESUME_ENTRY_FRAME) { + // No need to increment the PCCounts number of execution here, + // as the interpreter increments any PCCounts if present. + MOZ_ASSERT_IF(script->hasScriptCounts(), script->maybeGetPCCounts(pc)); return; + } } frameOk = HandleClosingGeneratorReturn(cx, frame.baselineFrame(), frameOk); diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 279990a29f0..76fda2bfe1a 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -170,7 +170,7 @@ js::DumpPCCounts(JSContext* cx, HandleScript script, Sprinter* sp) return; Sprint(sp, " {"); - PCCounts* counts = script->getPCCounts(pc); + PCCounts* counts = script->maybeGetPCCounts(pc); double val = counts ? counts->numExec() : 0.0; if (val) Sprint(sp, "\"%s\": %.0f", PCCounts::numExecName, val); @@ -1746,7 +1746,7 @@ js::GetPCCountScriptSummary(JSContext* cx, size_t index) jsbytecode* codeEnd = script->codeEnd(); for (jsbytecode* pc = script->code(); pc < codeEnd; pc = GetNextPc(pc)) { - const PCCounts* counts = sac.getPCCounts(pc); + const PCCounts* counts = sac.maybeGetPCCounts(pc); if (!counts) continue; total += counts->numExec(); @@ -1846,7 +1846,7 @@ GetPCCountJSON(JSContext* cx, const ScriptAndCounts& sac, StringBuffer& buf) buf.append(str); } - const PCCounts* counts = sac.getPCCounts(pc); + const PCCounts* counts = sac.maybeGetPCCounts(pc); AppendJSONProperty(buf, "counts"); buf.append('{'); diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 4f9ab2dde87..f790d113359 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -1297,6 +1297,22 @@ JSScript::initScriptCounts(JSContext* cx) } } + // Mark catch/finally blocks as being jump targets. + if (hasTrynotes()) { + JSTryNote* tn = trynotes()->vector; + JSTryNote* tnlimit = tn + trynotes()->length; + for (; tn < tnlimit; tn++) { + jsbytecode* tryStart = mainEntry + tn->start; + jsbytecode* tryPc = tryStart - 1; + if (JSOp(*tryPc) != JSOP_TRY) + continue; + + jsbytecode* tryTarget = tryStart + tn->length; + if (!jumpTargets.append(tryTarget)) + return false; + } + } + // Sort all pc, and remove duplicates. std::sort(jumpTargets.begin(), jumpTargets.end()); auto last = std::unique(jumpTargets.begin(), jumpTargets.end()); @@ -1352,7 +1368,18 @@ static inline ScriptCountsMap::Ptr GetScriptCountsMapEntry(JSScript* script) } js::PCCounts* -ScriptCounts::getPCCounts(size_t offset) const { +ScriptCounts::maybeGetPCCounts(size_t offset) { + PCCounts searched = PCCounts(offset); + PCCounts* begin = pcCountsVector; + PCCounts* end = begin + pcCountsSize; + PCCounts* elem = std::lower_bound(begin, end, searched); + if (elem == end || elem->pcOffset() != offset) + return nullptr; + return elem; +} + +const js::PCCounts* +ScriptCounts::maybeGetPCCounts(size_t offset) const { PCCounts searched = PCCounts(offset); PCCounts* begin = pcCountsVector; PCCounts* end = begin + pcCountsSize; @@ -1363,10 +1390,45 @@ ScriptCounts::getPCCounts(size_t offset) const { } js::PCCounts* -JSScript::getPCCounts(jsbytecode* pc) { +ScriptCounts::maybeGetThrowCounts(size_t offset) const { + PCCounts searched = PCCounts(offset); + PCCounts* begin = throwCountsVector; + PCCounts* end = throwCountsVector + throwCountsSize; + PCCounts* elem = std::lower_bound(begin, end, searched); + if (elem == end || elem->pcOffset() != offset) + return nullptr; + return elem; +} + +js::PCCounts* +ScriptCounts::getThrowCounts(size_t offset) { + PCCounts searched = PCCounts(offset); + PCCounts* begin = throwCountsVector; + PCCounts* end = throwCountsVector + throwCountsSize; + PCCounts* elem = std::lower_bound(begin, end, searched); + if (elem == end || elem->pcOffset() != offset) { + size_t index = elem - begin; + + size_t numBytes = (1 + throwCountsSize) * sizeof(PCCounts); + PCCounts* vec = (PCCounts*) js_realloc(throwCountsVector, numBytes); + if (!vec) + return nullptr; + throwCountsVector = vec; + throwCountsSize += 1; + + elem = throwCountsVector + index; + end = throwCountsVector + throwCountsSize; + std::copy_backward(elem, end - 1, end); + *elem = searched; + } + return elem; +} + +js::PCCounts* +JSScript::maybeGetPCCounts(jsbytecode* pc) { MOZ_ASSERT(containsPC(pc)); ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); - return p->value().getPCCounts(pcToOffset(pc)); + return p->value().maybeGetPCCounts(pcToOffset(pc)); } void @@ -1380,6 +1442,25 @@ JSScript::setIonScript(JSContext* maybecx, js::jit::IonScript* ionScript) updateBaselineOrIonRaw(maybecx); } +ScriptCounts& +JSScript::getScriptCounts() +{ + ScriptCountsMap::Ptr p = GetScriptCountsMapEntry(this); + return p->value(); +} + +js::PCCounts* +JSScript::maybeGetThrowCounts(jsbytecode* pc) { + MOZ_ASSERT(containsPC(pc)); + return getScriptCounts().maybeGetThrowCounts(pcToOffset(pc)); +} + +js::PCCounts* +JSScript::getThrowCounts(jsbytecode* pc) { + MOZ_ASSERT(containsPC(pc)); + return getScriptCounts().getThrowCounts(pcToOffset(pc)); +} + void JSScript::addIonCounts(jit::IonScriptCounts* ionCounts) { diff --git a/js/src/jsscript.h b/js/src/jsscript.h index acf2a1db78c..49269483b33 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -453,26 +453,50 @@ class ScriptCounts friend class ::JSScript; friend struct ScriptAndCounts; - /* - * This points to a single block that holds an array of PCCounts followed - * by an array of doubles. Each element in the PCCounts array has a - * pointer into the array of doubles. - */ + // This sorted array is used to map an offset to the number of times a + // branch got visited. PCCounts* pcCountsVector; size_t pcCountsSize; - /* Information about any Ion compilations for the script. */ + // This sorted vector is used to map an offset to the number of times an + // instruction throw. + PCCounts* throwCountsVector; + size_t throwCountsSize; + + // Information about any Ion compilations for the script. jit::IonScriptCounts* ionCounts; public: - ScriptCounts() : pcCountsVector(nullptr), ionCounts(nullptr) { } + ScriptCounts() + : pcCountsVector(nullptr), + pcCountsSize(0), + throwCountsVector(nullptr), + throwCountsSize(0), + ionCounts(nullptr) + { } - PCCounts* getPCCounts(size_t offset) const; + // Return the counter used to count the number of visits. Returns null if + // the element is not found. + PCCounts* maybeGetPCCounts(size_t offset); + const PCCounts* maybeGetPCCounts(size_t offset) const; + + // Return the counter used to count the number of throws. Returns null if + // the element is not found. + PCCounts* maybeGetThrowCounts(size_t offset) const; + + // Return the counter used to count the number of throws. Allocate it if + // none exists yet. Returns null if the allocation failed. + PCCounts* getThrowCounts(size_t offset); inline void destroy(FreeOp* fop); void set(js::ScriptCounts counts) { pcCountsVector = counts.pcCountsVector; + pcCountsSize = counts.pcCountsSize; + + throwCountsVector = counts.throwCountsVector; + throwCountsSize = counts.throwCountsSize; + ionCounts = counts.ionCounts; } }; @@ -1612,9 +1636,12 @@ class JSScript : public js::gc::TenuredCell public: bool initScriptCounts(JSContext* cx); - js::PCCounts* getPCCounts(jsbytecode* pc); + js::PCCounts* maybeGetPCCounts(jsbytecode* pc); + js::PCCounts* maybeGetThrowCounts(jsbytecode* pc); + js::PCCounts* getThrowCounts(jsbytecode* pc); void addIonCounts(js::jit::IonScriptCounts* ionCounts); js::jit::IonScriptCounts* getIonCounts(); + js::ScriptCounts& getScriptCounts(); js::ScriptCounts releaseScriptCounts(); void destroyScriptCounts(js::FreeOp* fop); @@ -2340,8 +2367,8 @@ struct ScriptAndCounts JSScript* script; ScriptCounts scriptCounts; - const PCCounts* getPCCounts(jsbytecode* pc) const { - return scriptCounts.getPCCounts(script->pcToOffset(pc)); + const PCCounts* maybeGetPCCounts(jsbytecode* pc) const { + return scriptCounts.maybeGetPCCounts(script->pcToOffset(pc)); } jit::IonScriptCounts* getIonCounts() const { diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h index 09c0c1684bf..4f6e7c8fe12 100644 --- a/js/src/jsscriptinlines.h +++ b/js/src/jsscriptinlines.h @@ -34,6 +34,7 @@ inline void ScriptCounts::destroy(FreeOp* fop) { fop->free_(pcCountsVector); + fop->free_(throwCountsVector); fop->delete_(ionCounts); } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 6549811cf5f..34416ea63bf 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1451,6 +1451,14 @@ HandleError(JSContext* cx, InterpreterRegs& regs) { MOZ_ASSERT(regs.fp()->script()->containsPC(regs.pc)); + if (regs.fp()->script()->hasScriptCounts()) { + PCCounts* counts = regs.fp()->script()->getThrowCounts(regs.pc); + // If we failed to allocate, then skip the increment and continue to + // handle the exception. + if (counts) + counts->numExec()++; + } + ScopeIter si(cx, regs.fp(), regs.pc); bool ok = false; @@ -1478,15 +1486,19 @@ HandleError(JSContext* cx, InterpreterRegs& regs) } } - switch (ProcessTryNotes(cx, si, regs)) { + HandleErrorContinuation res = ProcessTryNotes(cx, si, regs); + switch (res) { case SuccessfulReturnContinuation: break; case ErrorReturnContinuation: goto again; case CatchContinuation: - return CatchContinuation; case FinallyContinuation: - return FinallyContinuation; + // No need to increment the PCCounts number of execution here, as + // the interpreter increments any PCCounts if present. + MOZ_ASSERT_IF(regs.fp()->script()->hasScriptCounts(), + regs.fp()->script()->maybeGetPCCounts(regs.pc)); + return res; } ok = HandleClosingGeneratorReturn(cx, regs.fp(), ok); @@ -1968,7 +1980,7 @@ CASE(EnableInterruptsPseudoOpcode) } if (script->hasScriptCounts()) { - PCCounts* counts = script->getPCCounts(REGS.pc); + PCCounts* counts = script->maybeGetPCCounts(REGS.pc); if (counts) counts->numExec()++; moreInterrupts = true;