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