summaryrefslogtreecommitdiffstats
path: root/js/src/jit/CacheIRHealth.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/CacheIRHealth.cpp')
-rw-r--r--js/src/jit/CacheIRHealth.cpp416
1 files changed, 416 insertions, 0 deletions
diff --git a/js/src/jit/CacheIRHealth.cpp b/js/src/jit/CacheIRHealth.cpp
new file mode 100644
index 0000000000..a363dba38d
--- /dev/null
+++ b/js/src/jit/CacheIRHealth.cpp
@@ -0,0 +1,416 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifdef JS_CACHEIR_SPEW
+
+# include "jit/CacheIRHealth.h"
+
+# include "mozilla/Maybe.h"
+
+# include "gc/Zone.h"
+# include "jit/BaselineIC.h"
+# include "jit/CacheIRCompiler.h"
+# include "jit/JitScript.h"
+# include "vm/JSScript.h"
+
+# include "vm/JSObject-inl.h"
+# include "vm/Realm-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+// TODO: Refine how we assign happiness based on total health score.
+CacheIRHealth::Happiness CacheIRHealth::determineStubHappiness(
+ uint32_t stubHealthScore) {
+ if (stubHealthScore >= 30) {
+ return Sad;
+ }
+
+ if (stubHealthScore >= 20) {
+ return MediumSad;
+ }
+
+ if (stubHealthScore >= 10) {
+ return MediumHappy;
+ }
+
+ return Happy;
+}
+
+CacheIRHealth::Happiness CacheIRHealth::spewStubHealth(
+ AutoStructuredSpewer& spew, ICCacheIRStub* stub) {
+ const CacheIRStubInfo* stubInfo = stub->stubInfo();
+ CacheIRReader stubReader(stubInfo);
+ uint32_t totalStubHealth = 0;
+ spew->beginListProperty("cacheIROps");
+ while (stubReader.more()) {
+ CacheOp op = stubReader.readOp();
+ uint32_t opHealth = CacheIROpHealth[size_t(op)];
+ uint32_t argLength = CacheIROpInfos[size_t(op)].argLength;
+ const char* opName = CacheIROpNames[size_t(op)];
+
+ spew->beginObject();
+ if (opHealth == UINT32_MAX) {
+ spew->property("unscoredOp", opName);
+ } else {
+ spew->property("cacheIROp", opName);
+ spew->property("opHealth", opHealth);
+ totalStubHealth += opHealth;
+ }
+ spew->endObject();
+
+ stubReader.skip(argLength);
+ }
+ spew->endList(); // cacheIROps
+
+ spew->property("stubHealth", totalStubHealth);
+
+ Happiness stubHappiness = determineStubHappiness(totalStubHealth);
+ spew->property("stubHappiness", stubHappiness);
+
+ return stubHappiness;
+}
+
+BaseScript* CacheIRHealth::maybeExtractBaseScript(JSContext* cx, Shape* shape) {
+ TaggedProto taggedProto = shape->base()->proto();
+ if (!taggedProto.isObject()) {
+ return nullptr;
+ }
+ Value cval;
+ JSObject* proto = taggedProto.toObject();
+ AutoRealm ar(cx, proto);
+ if (!GetPropertyPure(cx, proto, NameToId(cx->names().constructor), &cval)) {
+ return nullptr;
+ }
+ if (!IsFunctionObject(cval)) {
+ return nullptr;
+ }
+ JSFunction& jsfun = cval.toObject().as<JSFunction>();
+ if (!jsfun.hasBaseScript()) {
+ return nullptr;
+ }
+ return jsfun.baseScript();
+}
+
+void CacheIRHealth::spewShapeInformation(AutoStructuredSpewer& spew,
+ JSContext* cx, ICStub* stub) {
+ bool shapesStarted = false;
+ const CacheIRStubInfo* stubInfo = stub->toCacheIRStub()->stubInfo();
+ size_t offset = 0;
+ uint32_t fieldIndex = 0;
+
+ while (stubInfo->fieldType(fieldIndex) != StubField::Type::Limit) {
+ if (stubInfo->fieldType(fieldIndex) == StubField::Type::Shape) {
+ Shape* shape = reinterpret_cast<Shape*>(
+ stubInfo->getStubRawWord(stub->toCacheIRStub(), offset));
+ if (!shapesStarted) {
+ shapesStarted = true;
+ spew->beginListProperty("shapes");
+ }
+
+ const PropMap* propMap =
+ shape->isNative() ? shape->asNative().propMap() : nullptr;
+ if (propMap) {
+ spew->beginObject();
+ {
+ if (!propMap->isDictionary()) {
+ uint32_t mapLength = shape->asNative().propMapLength();
+ if (mapLength) {
+ PropertyKey lastKey = shape->asNative().lastProperty().key();
+ if (lastKey.isInt()) {
+ spew->property("lastProperty", lastKey.toInt());
+ } else if (lastKey.isString()) {
+ JSString* str = lastKey.toString();
+ if (str && str->isLinear()) {
+ spew->property("lastProperty", &str->asLinear());
+ }
+ } else {
+ MOZ_ASSERT(lastKey.isSymbol());
+ JSString* str = lastKey.toSymbol()->description();
+ if (str && str->isLinear()) {
+ spew->property("lastProperty", &str->asLinear());
+ }
+ }
+ }
+ spew->property("totalKeys", propMap->approximateEntryCount());
+ BaseScript* baseScript = maybeExtractBaseScript(cx, shape);
+ if (baseScript) {
+ spew->beginObjectProperty("shapeAllocSite");
+ {
+ spew->property("filename", baseScript->filename());
+ spew->property("line", baseScript->lineno());
+ spew->property("column", baseScript->column());
+ }
+ spew->endObject();
+ }
+ }
+ }
+ spew->endObject();
+ }
+ }
+ offset += StubField::sizeInBytes(stubInfo->fieldType(fieldIndex));
+ fieldIndex++;
+ }
+
+ if (shapesStarted) {
+ spew->endList();
+ }
+}
+
+bool CacheIRHealth::spewNonFallbackICInformation(AutoStructuredSpewer& spew,
+ JSContext* cx,
+ ICStub* firstStub,
+ Happiness* entryHappiness) {
+ const CacheIRStubInfo* stubInfo = firstStub->toCacheIRStub()->stubInfo();
+ Vector<bool, 8, SystemAllocPolicy> sawDistinctValueAtFieldIndex;
+
+ bool sawNonZeroCount = false;
+ bool sawDifferentCacheIRStubs = false;
+ ICStub* stub = firstStub;
+
+ spew->beginListProperty("stubs");
+ while (stub && !stub->isFallback()) {
+ spew->beginObject();
+ {
+ Happiness stubHappiness = spewStubHealth(spew, stub->toCacheIRStub());
+ if (stubHappiness < *entryHappiness) {
+ *entryHappiness = stubHappiness;
+ }
+
+ spewShapeInformation(spew, cx, stub);
+
+ ICStub* nextStub = stub->toCacheIRStub()->next();
+ if (!nextStub->isFallback()) {
+ if (nextStub->enteredCount() > 0) {
+ // More than one stub has a hit count greater than zero.
+ // This is sad because we do not Warp transpile in this case.
+ *entryHappiness = Sad;
+ sawNonZeroCount = true;
+ }
+
+ if (nextStub->toCacheIRStub()->stubInfo() != stubInfo) {
+ sawDifferentCacheIRStubs = true;
+ }
+
+ // If there are multiple stubs with non zero hit counts and if all
+ // of the stubs have equivalent CacheIR, then keep track of how many
+ // distinct stub field values are seen for each field index.
+ if (sawNonZeroCount && !sawDifferentCacheIRStubs) {
+ uint32_t fieldIndex = 0;
+ size_t offset = 0;
+
+ while (stubInfo->fieldType(fieldIndex) != StubField::Type::Limit) {
+ if (sawDistinctValueAtFieldIndex.length() <= fieldIndex) {
+ if (!sawDistinctValueAtFieldIndex.append(false)) {
+ return false;
+ }
+ }
+
+ if (StubField::sizeIsWord(stubInfo->fieldType(fieldIndex))) {
+ uintptr_t firstRaw =
+ stubInfo->getStubRawWord(firstStub->toCacheIRStub(), offset);
+ uintptr_t nextRaw =
+ stubInfo->getStubRawWord(nextStub->toCacheIRStub(), offset);
+ if (firstRaw != nextRaw) {
+ sawDistinctValueAtFieldIndex[fieldIndex] = true;
+ }
+ } else {
+ MOZ_ASSERT(
+ StubField::sizeIsInt64(stubInfo->fieldType(fieldIndex)));
+ int64_t firstRaw =
+ stubInfo->getStubRawInt64(firstStub->toCacheIRStub(), offset);
+ int64_t nextRaw =
+ stubInfo->getStubRawInt64(nextStub->toCacheIRStub(), offset);
+
+ if (firstRaw != nextRaw) {
+ sawDistinctValueAtFieldIndex[fieldIndex] = true;
+ }
+ }
+
+ offset += StubField::sizeInBytes(stubInfo->fieldType(fieldIndex));
+ fieldIndex++;
+ }
+ }
+ }
+
+ spew->property("hitCount", stub->enteredCount());
+ stub = nextStub;
+ }
+ spew->endObject();
+ }
+ spew->endList(); // stubs
+
+ // If more than one CacheIR stub has an entered count greater than
+ // zero and all the stubs have equivalent CacheIR, then spew
+ // the information collected about the stub fields across the IC.
+ if (sawNonZeroCount && !sawDifferentCacheIRStubs) {
+ spew->beginListProperty("stubFields");
+ for (size_t i = 0; i < sawDistinctValueAtFieldIndex.length(); i++) {
+ spew->beginObject();
+ {
+ spew->property("fieldType", uint8_t(stubInfo->fieldType(i)));
+ spew->property("sawDistinctFieldValues",
+ sawDistinctValueAtFieldIndex[i]);
+ }
+ spew->endObject();
+ }
+ spew->endList();
+ }
+
+ return true;
+}
+
+bool CacheIRHealth::spewICEntryHealth(AutoStructuredSpewer& spew, JSContext* cx,
+ HandleScript script, ICEntry* entry,
+ ICFallbackStub* fallback, jsbytecode* pc,
+ JSOp op, Happiness* entryHappiness) {
+ spew->property("op", CodeName(op));
+
+ // TODO: If a perf issue arises, look into improving the SrcNotes
+ // API call below.
+ unsigned column;
+ spew->property("lineno", PCToLineNumber(script, pc, &column));
+ spew->property("column", column);
+
+ ICStub* firstStub = entry->firstStub();
+ if (!firstStub->isFallback()) {
+ if (!spewNonFallbackICInformation(spew, cx, firstStub, entryHappiness)) {
+ return false;
+ }
+ }
+
+ if (fallback->state().mode() != ICState::Mode::Specialized) {
+ *entryHappiness = Sad;
+ }
+
+ spew->property("entryHappiness", uint8_t(*entryHappiness));
+
+ spew->property("mode", uint8_t(fallback->state().mode()));
+
+ spew->property("fallbackCount", fallback->enteredCount());
+
+ return true;
+}
+
+void CacheIRHealth::spewScriptFinalWarmUpCount(JSContext* cx,
+ const char* filename,
+ JSScript* script,
+ uint32_t warmUpCount) {
+ AutoStructuredSpewer spew(cx, SpewChannel::CacheIRHealthReport, nullptr);
+ if (!spew) {
+ return;
+ }
+
+ spew->property("filename", filename);
+ spew->property("line", script->lineno());
+ spew->property("column", script->column());
+ spew->property("finalWarmUpCount", warmUpCount);
+}
+
+static bool addScriptToFinalWarmUpCountMap(JSContext* cx, HandleScript script) {
+ // Create Zone::scriptFilenameMap if necessary.
+ JS::Zone* zone = script->zone();
+ if (!zone->scriptFinalWarmUpCountMap) {
+ auto map = MakeUnique<ScriptFinalWarmUpCountMap>();
+ if (!map) {
+ return false;
+ }
+
+ zone->scriptFinalWarmUpCountMap = std::move(map);
+ }
+
+ SharedImmutableString sfilename =
+ SharedImmutableStringsCache::getSingleton().getOrCreate(
+ script->filename(), strlen(script->filename()));
+ if (!sfilename) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ if (!zone->scriptFinalWarmUpCountMap->put(
+ script, std::make_tuple(uint32_t(0), std::move(sfilename)))) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ script->setNeedsFinalWarmUpCount();
+ return true;
+}
+
+void CacheIRHealth::healthReportForIC(JSContext* cx, ICEntry* entry,
+ ICFallbackStub* fallback,
+ HandleScript script,
+ SpewContext context) {
+ AutoStructuredSpewer spew(cx, SpewChannel::CacheIRHealthReport, script);
+ if (!spew) {
+ return;
+ }
+
+ if (!addScriptToFinalWarmUpCountMap(cx, script)) {
+ cx->recoverFromOutOfMemory();
+ return;
+ }
+ spew->property("spewContext", uint8_t(context));
+
+ jsbytecode* op = script->offsetToPC(fallback->pcOffset());
+ JSOp jsOp = JSOp(*op);
+
+ Happiness entryHappiness = Happy;
+ if (!spewICEntryHealth(spew, cx, script, entry, fallback, op, jsOp,
+ &entryHappiness)) {
+ cx->recoverFromOutOfMemory();
+ return;
+ }
+ MOZ_ASSERT(entryHappiness == Sad);
+}
+
+void CacheIRHealth::healthReportForScript(JSContext* cx, HandleScript script,
+ SpewContext context) {
+ jit::JitScript* jitScript = script->maybeJitScript();
+ if (!jitScript) {
+ return;
+ }
+
+ AutoStructuredSpewer spew(cx, SpewChannel::CacheIRHealthReport, script);
+ if (!spew) {
+ return;
+ }
+
+ if (!addScriptToFinalWarmUpCountMap(cx, script)) {
+ cx->recoverFromOutOfMemory();
+ return;
+ }
+
+ spew->property("spewContext", uint8_t(context));
+
+ spew->beginListProperty("entries");
+
+ Happiness scriptHappiness = Happy;
+
+ for (size_t i = 0; i < jitScript->numICEntries(); i++) {
+ ICEntry& entry = jitScript->icEntry(i);
+ ICFallbackStub* fallback = jitScript->fallbackStub(i);
+ jsbytecode* pc = script->offsetToPC(fallback->pcOffset());
+ JSOp op = JSOp(*pc);
+
+ spew->beginObject();
+ Happiness entryHappiness = Happy;
+ if (!spewICEntryHealth(spew, cx, script, &entry, fallback, pc, op,
+ &entryHappiness)) {
+ cx->recoverFromOutOfMemory();
+ return;
+ }
+ if (entryHappiness < scriptHappiness) {
+ scriptHappiness = entryHappiness;
+ }
+ spew->endObject();
+ }
+
+ spew->endList(); // entries
+
+ spew->property("scriptHappiness", uint8_t(scriptHappiness));
+}
+
+#endif /* JS_CACHEIR_SPEW */