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