CacheIRHealth.cpp (13515B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 #ifdef JS_CACHEIR_SPEW 7 8 # include "jit/CacheIRHealth.h" 9 10 # include "gc/Zone.h" 11 # include "jit/BaselineIC.h" 12 # include "jit/CacheIRCompiler.h" 13 # include "jit/JitScript.h" 14 # include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin 15 # include "vm/JSScript.h" 16 17 # include "vm/JSObject-inl.h" 18 # include "vm/Realm-inl.h" 19 20 using namespace js; 21 using namespace js::jit; 22 23 // TODO: Refine how we assign happiness based on total health score. 24 CacheIRHealth::Happiness CacheIRHealth::determineStubHappiness( 25 uint32_t stubHealthScore) { 26 if (stubHealthScore >= 30) { 27 return Sad; 28 } 29 30 if (stubHealthScore >= 20) { 31 return MediumSad; 32 } 33 34 if (stubHealthScore >= 10) { 35 return MediumHappy; 36 } 37 38 return Happy; 39 } 40 41 CacheIRHealth::Happiness CacheIRHealth::spewStubHealth( 42 AutoStructuredSpewer& spew, ICCacheIRStub* stub) { 43 const CacheIRStubInfo* stubInfo = stub->stubInfo(); 44 CacheIRReader stubReader(stubInfo); 45 uint32_t totalStubHealth = 0; 46 spew->beginListProperty("cacheIROps"); 47 while (stubReader.more()) { 48 CacheOp op = stubReader.readOp(); 49 uint32_t opHealth = CacheIROpHealth[size_t(op)]; 50 uint32_t argLength = CacheIROpInfos[size_t(op)].argLength; 51 const char* opName = CacheIROpNames[size_t(op)]; 52 53 spew->beginObject(); 54 if (opHealth == UINT32_MAX) { 55 spew->property("unscoredOp", opName); 56 } else { 57 spew->property("cacheIROp", opName); 58 spew->property("opHealth", opHealth); 59 totalStubHealth += opHealth; 60 } 61 spew->endObject(); 62 63 stubReader.skip(argLength); 64 } 65 spew->endList(); // cacheIROps 66 67 spew->property("stubHealth", totalStubHealth); 68 69 Happiness stubHappiness = determineStubHappiness(totalStubHealth); 70 spew->property("stubHappiness", stubHappiness); 71 72 return stubHappiness; 73 } 74 75 BaseScript* CacheIRHealth::maybeExtractBaseScript(JSContext* cx, Shape* shape) { 76 TaggedProto taggedProto = shape->base()->proto(); 77 if (!taggedProto.isObject()) { 78 return nullptr; 79 } 80 Value cval; 81 JSObject* proto = taggedProto.toObject(); 82 AutoRealm ar(cx, proto); 83 if (!GetPropertyPure(cx, proto, NameToId(cx->names().constructor), &cval)) { 84 return nullptr; 85 } 86 if (!IsFunctionObject(cval)) { 87 return nullptr; 88 } 89 JSFunction& jsfun = cval.toObject().as<JSFunction>(); 90 if (!jsfun.hasBaseScript()) { 91 return nullptr; 92 } 93 return jsfun.baseScript(); 94 } 95 96 void CacheIRHealth::spewShapeInformation(AutoStructuredSpewer& spew, 97 JSContext* cx, ICStub* stub) { 98 bool shapesStarted = false; 99 const CacheIRStubInfo* stubInfo = stub->toCacheIRStub()->stubInfo(); 100 size_t offset = 0; 101 uint32_t fieldIndex = 0; 102 103 while (stubInfo->fieldType(fieldIndex) != StubField::Type::Limit) { 104 if (stubInfo->fieldType(fieldIndex) == StubField::Type::Shape) { 105 Shape* shape = reinterpret_cast<Shape*>( 106 stubInfo->getStubRawWord(stub->toCacheIRStub(), offset)); 107 if (!shapesStarted) { 108 shapesStarted = true; 109 spew->beginListProperty("shapes"); 110 } 111 112 const PropMap* propMap = 113 shape->isNative() ? shape->asNative().propMap() : nullptr; 114 if (propMap) { 115 spew->beginObject(); 116 { 117 if (!propMap->isDictionary()) { 118 uint32_t mapLength = shape->asNative().propMapLength(); 119 if (mapLength) { 120 PropertyKey lastKey = shape->asNative().lastProperty().key(); 121 if (lastKey.isInt()) { 122 spew->property("lastProperty", lastKey.toInt()); 123 } else if (lastKey.isString()) { 124 JSString* str = lastKey.toString(); 125 if (str && str->isLinear()) { 126 spew->property("lastProperty", &str->asLinear()); 127 } 128 } else { 129 MOZ_ASSERT(lastKey.isSymbol()); 130 JSString* str = lastKey.toSymbol()->description(); 131 if (str && str->isLinear()) { 132 spew->property("lastProperty", &str->asLinear()); 133 } 134 } 135 } 136 spew->property("totalKeys", propMap->approximateEntryCount()); 137 BaseScript* baseScript = maybeExtractBaseScript(cx, shape); 138 if (baseScript) { 139 spew->beginObjectProperty("shapeAllocSite"); 140 { 141 spew->property("filename", baseScript->filename()); 142 spew->property("line", baseScript->lineno()); 143 spew->property("column", baseScript->column().oneOriginValue()); 144 } 145 spew->endObject(); 146 } 147 } 148 } 149 spew->endObject(); 150 } 151 } 152 offset += StubField::sizeInBytes(stubInfo->fieldType(fieldIndex)); 153 fieldIndex++; 154 } 155 156 if (shapesStarted) { 157 spew->endList(); 158 } 159 } 160 161 bool CacheIRHealth::spewNonFallbackICInformation(AutoStructuredSpewer& spew, 162 JSContext* cx, 163 ICStub* firstStub, 164 Happiness* entryHappiness) { 165 const CacheIRStubInfo* stubInfo = firstStub->toCacheIRStub()->stubInfo(); 166 Vector<bool, 8, SystemAllocPolicy> sawDistinctValueAtFieldIndex; 167 168 bool sawNonZeroCount = false; 169 bool sawDifferentCacheIRStubs = false; 170 ICStub* stub = firstStub; 171 172 spew->beginListProperty("stubs"); 173 while (stub && !stub->isFallback()) { 174 spew->beginObject(); 175 { 176 Happiness stubHappiness = spewStubHealth(spew, stub->toCacheIRStub()); 177 if (stubHappiness < *entryHappiness) { 178 *entryHappiness = stubHappiness; 179 } 180 181 spewShapeInformation(spew, cx, stub); 182 183 ICStub* nextStub = stub->toCacheIRStub()->next(); 184 if (!nextStub->isFallback()) { 185 if (nextStub->enteredCount() > 0) { 186 // More than one stub has a hit count greater than zero. 187 // This is sad because we do not Warp transpile in this case. 188 *entryHappiness = Sad; 189 sawNonZeroCount = true; 190 } 191 192 if (nextStub->toCacheIRStub()->stubInfo() != stubInfo) { 193 sawDifferentCacheIRStubs = true; 194 } 195 196 // If there are multiple stubs with non zero hit counts and if all 197 // of the stubs have equivalent CacheIR, then keep track of how many 198 // distinct stub field values are seen for each field index. 199 if (sawNonZeroCount && !sawDifferentCacheIRStubs) { 200 uint32_t fieldIndex = 0; 201 size_t offset = 0; 202 203 while (stubInfo->fieldType(fieldIndex) != StubField::Type::Limit) { 204 if (sawDistinctValueAtFieldIndex.length() <= fieldIndex) { 205 if (!sawDistinctValueAtFieldIndex.append(false)) { 206 return false; 207 } 208 } 209 210 if (StubField::sizeIsWord(stubInfo->fieldType(fieldIndex))) { 211 uintptr_t firstRaw = 212 stubInfo->getStubRawWord(firstStub->toCacheIRStub(), offset); 213 uintptr_t nextRaw = 214 stubInfo->getStubRawWord(nextStub->toCacheIRStub(), offset); 215 if (firstRaw != nextRaw) { 216 sawDistinctValueAtFieldIndex[fieldIndex] = true; 217 } 218 } else { 219 MOZ_ASSERT( 220 StubField::sizeIsInt64(stubInfo->fieldType(fieldIndex))); 221 int64_t firstRaw = 222 stubInfo->getStubRawInt64(firstStub->toCacheIRStub(), offset); 223 int64_t nextRaw = 224 stubInfo->getStubRawInt64(nextStub->toCacheIRStub(), offset); 225 226 if (firstRaw != nextRaw) { 227 sawDistinctValueAtFieldIndex[fieldIndex] = true; 228 } 229 } 230 231 offset += StubField::sizeInBytes(stubInfo->fieldType(fieldIndex)); 232 fieldIndex++; 233 } 234 } 235 } 236 237 spew->property("hitCount", stub->enteredCount()); 238 stub = nextStub; 239 } 240 spew->endObject(); 241 } 242 spew->endList(); // stubs 243 244 // If more than one CacheIR stub has an entered count greater than 245 // zero and all the stubs have equivalent CacheIR, then spew 246 // the information collected about the stub fields across the IC. 247 if (sawNonZeroCount && !sawDifferentCacheIRStubs) { 248 spew->beginListProperty("stubFields"); 249 for (size_t i = 0; i < sawDistinctValueAtFieldIndex.length(); i++) { 250 spew->beginObject(); 251 { 252 spew->property("fieldType", uint8_t(stubInfo->fieldType(i))); 253 spew->property("sawDistinctFieldValues", 254 sawDistinctValueAtFieldIndex[i]); 255 } 256 spew->endObject(); 257 } 258 spew->endList(); 259 } 260 261 return true; 262 } 263 264 bool CacheIRHealth::spewICEntryHealth(AutoStructuredSpewer& spew, JSContext* cx, 265 HandleScript script, ICEntry* entry, 266 ICFallbackStub* fallback, jsbytecode* pc, 267 JSOp op, Happiness* entryHappiness) { 268 spew->property("op", CodeName(op)); 269 270 // TODO: If a perf issue arises, look into improving the SrcNotes 271 // API call below. 272 JS::LimitedColumnNumberOneOrigin column; 273 spew->property("lineno", PCToLineNumber(script, pc, &column)); 274 spew->property("column", column.oneOriginValue()); 275 276 ICStub* firstStub = entry->firstStub(); 277 if (!firstStub->isFallback()) { 278 if (!spewNonFallbackICInformation(spew, cx, firstStub, entryHappiness)) { 279 return false; 280 } 281 } 282 283 if (fallback->state().mode() != ICState::Mode::Specialized) { 284 *entryHappiness = Sad; 285 } 286 287 spew->property("entryHappiness", uint8_t(*entryHappiness)); 288 289 spew->property("mode", uint8_t(fallback->state().mode())); 290 291 spew->property("fallbackCount", fallback->enteredCount()); 292 293 return true; 294 } 295 296 void CacheIRHealth::spewScriptFinalWarmUpCount(JSContext* cx, 297 const char* filename, 298 JSScript* script, 299 uint32_t warmUpCount) { 300 AutoStructuredSpewer spew(cx, SpewChannel::CacheIRHealthReport, nullptr); 301 if (!spew) { 302 return; 303 } 304 305 spew->property("filename", filename); 306 spew->property("line", script->lineno()); 307 spew->property("column", script->column().oneOriginValue()); 308 spew->property("finalWarmUpCount", warmUpCount); 309 } 310 311 static bool addScriptToFinalWarmUpCountMap(JSContext* cx, HandleScript script) { 312 // Create Zone::scriptFilenameMap if necessary. 313 JS::Zone* zone = script->zone(); 314 if (!zone->scriptFinalWarmUpCountMap) { 315 auto map = MakeUnique<ScriptFinalWarmUpCountMap>(); 316 if (!map) { 317 return false; 318 } 319 320 zone->scriptFinalWarmUpCountMap = std::move(map); 321 } 322 323 SharedImmutableString sfilename = 324 SharedImmutableStringsCache::getSingleton().getOrCreate( 325 script->filename(), strlen(script->filename())); 326 if (!sfilename) { 327 ReportOutOfMemory(cx); 328 return false; 329 } 330 331 if (!zone->scriptFinalWarmUpCountMap->put( 332 script, std::make_tuple(uint32_t(0), std::move(sfilename)))) { 333 ReportOutOfMemory(cx); 334 return false; 335 } 336 337 script->setNeedsFinalWarmUpCount(); 338 return true; 339 } 340 341 void CacheIRHealth::healthReportForIC(JSContext* cx, ICEntry* entry, 342 ICFallbackStub* fallback, 343 HandleScript script, 344 SpewContext context) { 345 AutoStructuredSpewer spew(cx, SpewChannel::CacheIRHealthReport, script); 346 if (!spew) { 347 return; 348 } 349 350 if (!addScriptToFinalWarmUpCountMap(cx, script)) { 351 cx->recoverFromOutOfMemory(); 352 return; 353 } 354 spew->property("spewContext", uint8_t(context)); 355 356 jsbytecode* op = script->offsetToPC(fallback->pcOffset()); 357 JSOp jsOp = JSOp(*op); 358 359 Happiness entryHappiness = Happy; 360 if (!spewICEntryHealth(spew, cx, script, entry, fallback, op, jsOp, 361 &entryHappiness)) { 362 cx->recoverFromOutOfMemory(); 363 return; 364 } 365 MOZ_ASSERT(entryHappiness == Sad); 366 } 367 368 void CacheIRHealth::healthReportForScript(JSContext* cx, HandleScript script, 369 SpewContext context) { 370 jit::JitScript* jitScript = script->maybeJitScript(); 371 if (!jitScript) { 372 return; 373 } 374 375 AutoStructuredSpewer spew(cx, SpewChannel::CacheIRHealthReport, script); 376 if (!spew) { 377 return; 378 } 379 380 if (!addScriptToFinalWarmUpCountMap(cx, script)) { 381 cx->recoverFromOutOfMemory(); 382 return; 383 } 384 385 spew->property("spewContext", uint8_t(context)); 386 387 spew->beginListProperty("entries"); 388 389 Happiness scriptHappiness = Happy; 390 391 for (size_t i = 0; i < jitScript->numICEntries(); i++) { 392 ICEntry& entry = jitScript->icEntry(i); 393 ICFallbackStub* fallback = jitScript->fallbackStub(i); 394 jsbytecode* pc = script->offsetToPC(fallback->pcOffset()); 395 JSOp op = JSOp(*pc); 396 397 spew->beginObject(); 398 Happiness entryHappiness = Happy; 399 if (!spewICEntryHealth(spew, cx, script, &entry, fallback, pc, op, 400 &entryHappiness)) { 401 cx->recoverFromOutOfMemory(); 402 return; 403 } 404 if (entryHappiness < scriptHappiness) { 405 scriptHappiness = entryHappiness; 406 } 407 spew->endObject(); 408 } 409 410 spew->endList(); // entries 411 412 spew->property("scriptHappiness", uint8_t(scriptHappiness)); 413 } 414 415 #endif /* JS_CACHEIR_SPEW */