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/. */
8 # include "jit/CacheIRHealth.h"
10 # include "mozilla/Maybe.h"
13 # include "jit/BaselineIC.h"
14 # include "jit/CacheIRCompiler.h"
15 # include "jit/JitScript.h"
16 # include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin
17 # include "vm/JSScript.h"
19 # include "vm/JSObject-inl.h"
20 # include "vm/Realm-inl.h"
23 using namespace js::jit
;
25 // TODO: Refine how we assign happiness based on total health score.
26 CacheIRHealth::Happiness
CacheIRHealth::determineStubHappiness(
27 uint32_t stubHealthScore
) {
28 if (stubHealthScore
>= 30) {
32 if (stubHealthScore
>= 20) {
36 if (stubHealthScore
>= 10) {
43 CacheIRHealth::Happiness
CacheIRHealth::spewStubHealth(
44 AutoStructuredSpewer
& spew
, ICCacheIRStub
* stub
) {
45 const CacheIRStubInfo
* stubInfo
= stub
->stubInfo();
46 CacheIRReader
stubReader(stubInfo
);
47 uint32_t totalStubHealth
= 0;
48 spew
->beginListProperty("cacheIROps");
49 while (stubReader
.more()) {
50 CacheOp op
= stubReader
.readOp();
51 uint32_t opHealth
= CacheIROpHealth
[size_t(op
)];
52 uint32_t argLength
= CacheIROpInfos
[size_t(op
)].argLength
;
53 const char* opName
= CacheIROpNames
[size_t(op
)];
56 if (opHealth
== UINT32_MAX
) {
57 spew
->property("unscoredOp", opName
);
59 spew
->property("cacheIROp", opName
);
60 spew
->property("opHealth", opHealth
);
61 totalStubHealth
+= opHealth
;
65 stubReader
.skip(argLength
);
67 spew
->endList(); // cacheIROps
69 spew
->property("stubHealth", totalStubHealth
);
71 Happiness stubHappiness
= determineStubHappiness(totalStubHealth
);
72 spew
->property("stubHappiness", stubHappiness
);
77 BaseScript
* CacheIRHealth::maybeExtractBaseScript(JSContext
* cx
, Shape
* shape
) {
78 TaggedProto taggedProto
= shape
->base()->proto();
79 if (!taggedProto
.isObject()) {
83 JSObject
* proto
= taggedProto
.toObject();
84 AutoRealm
ar(cx
, proto
);
85 if (!GetPropertyPure(cx
, proto
, NameToId(cx
->names().constructor
), &cval
)) {
88 if (!IsFunctionObject(cval
)) {
91 JSFunction
& jsfun
= cval
.toObject().as
<JSFunction
>();
92 if (!jsfun
.hasBaseScript()) {
95 return jsfun
.baseScript();
98 void CacheIRHealth::spewShapeInformation(AutoStructuredSpewer
& spew
,
99 JSContext
* cx
, ICStub
* stub
) {
100 bool shapesStarted
= false;
101 const CacheIRStubInfo
* stubInfo
= stub
->toCacheIRStub()->stubInfo();
103 uint32_t fieldIndex
= 0;
105 while (stubInfo
->fieldType(fieldIndex
) != StubField::Type::Limit
) {
106 if (stubInfo
->fieldType(fieldIndex
) == StubField::Type::Shape
) {
107 Shape
* shape
= reinterpret_cast<Shape
*>(
108 stubInfo
->getStubRawWord(stub
->toCacheIRStub(), offset
));
109 if (!shapesStarted
) {
110 shapesStarted
= true;
111 spew
->beginListProperty("shapes");
114 const PropMap
* propMap
=
115 shape
->isNative() ? shape
->asNative().propMap() : nullptr;
119 if (!propMap
->isDictionary()) {
120 uint32_t mapLength
= shape
->asNative().propMapLength();
122 PropertyKey lastKey
= shape
->asNative().lastProperty().key();
123 if (lastKey
.isInt()) {
124 spew
->property("lastProperty", lastKey
.toInt());
125 } else if (lastKey
.isString()) {
126 JSString
* str
= lastKey
.toString();
127 if (str
&& str
->isLinear()) {
128 spew
->property("lastProperty", &str
->asLinear());
131 MOZ_ASSERT(lastKey
.isSymbol());
132 JSString
* str
= lastKey
.toSymbol()->description();
133 if (str
&& str
->isLinear()) {
134 spew
->property("lastProperty", &str
->asLinear());
138 spew
->property("totalKeys", propMap
->approximateEntryCount());
139 BaseScript
* baseScript
= maybeExtractBaseScript(cx
, shape
);
141 spew
->beginObjectProperty("shapeAllocSite");
143 spew
->property("filename", baseScript
->filename());
144 spew
->property("line", baseScript
->lineno());
145 spew
->property("column", baseScript
->column().oneOriginValue());
154 offset
+= StubField::sizeInBytes(stubInfo
->fieldType(fieldIndex
));
163 bool CacheIRHealth::spewNonFallbackICInformation(AutoStructuredSpewer
& spew
,
166 Happiness
* entryHappiness
) {
167 const CacheIRStubInfo
* stubInfo
= firstStub
->toCacheIRStub()->stubInfo();
168 Vector
<bool, 8, SystemAllocPolicy
> sawDistinctValueAtFieldIndex
;
170 bool sawNonZeroCount
= false;
171 bool sawDifferentCacheIRStubs
= false;
172 ICStub
* stub
= firstStub
;
174 spew
->beginListProperty("stubs");
175 while (stub
&& !stub
->isFallback()) {
178 Happiness stubHappiness
= spewStubHealth(spew
, stub
->toCacheIRStub());
179 if (stubHappiness
< *entryHappiness
) {
180 *entryHappiness
= stubHappiness
;
183 spewShapeInformation(spew
, cx
, stub
);
185 ICStub
* nextStub
= stub
->toCacheIRStub()->next();
186 if (!nextStub
->isFallback()) {
187 if (nextStub
->enteredCount() > 0) {
188 // More than one stub has a hit count greater than zero.
189 // This is sad because we do not Warp transpile in this case.
190 *entryHappiness
= Sad
;
191 sawNonZeroCount
= true;
194 if (nextStub
->toCacheIRStub()->stubInfo() != stubInfo
) {
195 sawDifferentCacheIRStubs
= true;
198 // If there are multiple stubs with non zero hit counts and if all
199 // of the stubs have equivalent CacheIR, then keep track of how many
200 // distinct stub field values are seen for each field index.
201 if (sawNonZeroCount
&& !sawDifferentCacheIRStubs
) {
202 uint32_t fieldIndex
= 0;
205 while (stubInfo
->fieldType(fieldIndex
) != StubField::Type::Limit
) {
206 if (sawDistinctValueAtFieldIndex
.length() <= fieldIndex
) {
207 if (!sawDistinctValueAtFieldIndex
.append(false)) {
212 if (StubField::sizeIsWord(stubInfo
->fieldType(fieldIndex
))) {
214 stubInfo
->getStubRawWord(firstStub
->toCacheIRStub(), offset
);
216 stubInfo
->getStubRawWord(nextStub
->toCacheIRStub(), offset
);
217 if (firstRaw
!= nextRaw
) {
218 sawDistinctValueAtFieldIndex
[fieldIndex
] = true;
222 StubField::sizeIsInt64(stubInfo
->fieldType(fieldIndex
)));
224 stubInfo
->getStubRawInt64(firstStub
->toCacheIRStub(), offset
);
226 stubInfo
->getStubRawInt64(nextStub
->toCacheIRStub(), offset
);
228 if (firstRaw
!= nextRaw
) {
229 sawDistinctValueAtFieldIndex
[fieldIndex
] = true;
233 offset
+= StubField::sizeInBytes(stubInfo
->fieldType(fieldIndex
));
239 spew
->property("hitCount", stub
->enteredCount());
244 spew
->endList(); // stubs
246 // If more than one CacheIR stub has an entered count greater than
247 // zero and all the stubs have equivalent CacheIR, then spew
248 // the information collected about the stub fields across the IC.
249 if (sawNonZeroCount
&& !sawDifferentCacheIRStubs
) {
250 spew
->beginListProperty("stubFields");
251 for (size_t i
= 0; i
< sawDistinctValueAtFieldIndex
.length(); i
++) {
254 spew
->property("fieldType", uint8_t(stubInfo
->fieldType(i
)));
255 spew
->property("sawDistinctFieldValues",
256 sawDistinctValueAtFieldIndex
[i
]);
266 bool CacheIRHealth::spewICEntryHealth(AutoStructuredSpewer
& spew
, JSContext
* cx
,
267 HandleScript script
, ICEntry
* entry
,
268 ICFallbackStub
* fallback
, jsbytecode
* pc
,
269 JSOp op
, Happiness
* entryHappiness
) {
270 spew
->property("op", CodeName(op
));
272 // TODO: If a perf issue arises, look into improving the SrcNotes
274 JS::LimitedColumnNumberOneOrigin column
;
275 spew
->property("lineno", PCToLineNumber(script
, pc
, &column
));
276 spew
->property("column", column
.oneOriginValue());
278 ICStub
* firstStub
= entry
->firstStub();
279 if (!firstStub
->isFallback()) {
280 if (!spewNonFallbackICInformation(spew
, cx
, firstStub
, entryHappiness
)) {
285 if (fallback
->state().mode() != ICState::Mode::Specialized
) {
286 *entryHappiness
= Sad
;
289 spew
->property("entryHappiness", uint8_t(*entryHappiness
));
291 spew
->property("mode", uint8_t(fallback
->state().mode()));
293 spew
->property("fallbackCount", fallback
->enteredCount());
298 void CacheIRHealth::spewScriptFinalWarmUpCount(JSContext
* cx
,
299 const char* filename
,
301 uint32_t warmUpCount
) {
302 AutoStructuredSpewer
spew(cx
, SpewChannel::CacheIRHealthReport
, nullptr);
307 spew
->property("filename", filename
);
308 spew
->property("line", script
->lineno());
309 spew
->property("column", script
->column().oneOriginValue());
310 spew
->property("finalWarmUpCount", warmUpCount
);
313 static bool addScriptToFinalWarmUpCountMap(JSContext
* cx
, HandleScript script
) {
314 // Create Zone::scriptFilenameMap if necessary.
315 JS::Zone
* zone
= script
->zone();
316 if (!zone
->scriptFinalWarmUpCountMap
) {
317 auto map
= MakeUnique
<ScriptFinalWarmUpCountMap
>();
322 zone
->scriptFinalWarmUpCountMap
= std::move(map
);
325 SharedImmutableString sfilename
=
326 SharedImmutableStringsCache::getSingleton().getOrCreate(
327 script
->filename(), strlen(script
->filename()));
329 ReportOutOfMemory(cx
);
333 if (!zone
->scriptFinalWarmUpCountMap
->put(
334 script
, std::make_tuple(uint32_t(0), std::move(sfilename
)))) {
335 ReportOutOfMemory(cx
);
339 script
->setNeedsFinalWarmUpCount();
343 void CacheIRHealth::healthReportForIC(JSContext
* cx
, ICEntry
* entry
,
344 ICFallbackStub
* fallback
,
346 SpewContext context
) {
347 AutoStructuredSpewer
spew(cx
, SpewChannel::CacheIRHealthReport
, script
);
352 if (!addScriptToFinalWarmUpCountMap(cx
, script
)) {
353 cx
->recoverFromOutOfMemory();
356 spew
->property("spewContext", uint8_t(context
));
358 jsbytecode
* op
= script
->offsetToPC(fallback
->pcOffset());
359 JSOp jsOp
= JSOp(*op
);
361 Happiness entryHappiness
= Happy
;
362 if (!spewICEntryHealth(spew
, cx
, script
, entry
, fallback
, op
, jsOp
,
364 cx
->recoverFromOutOfMemory();
367 MOZ_ASSERT(entryHappiness
== Sad
);
370 void CacheIRHealth::healthReportForScript(JSContext
* cx
, HandleScript script
,
371 SpewContext context
) {
372 jit::JitScript
* jitScript
= script
->maybeJitScript();
377 AutoStructuredSpewer
spew(cx
, SpewChannel::CacheIRHealthReport
, script
);
382 if (!addScriptToFinalWarmUpCountMap(cx
, script
)) {
383 cx
->recoverFromOutOfMemory();
387 spew
->property("spewContext", uint8_t(context
));
389 spew
->beginListProperty("entries");
391 Happiness scriptHappiness
= Happy
;
393 for (size_t i
= 0; i
< jitScript
->numICEntries(); i
++) {
394 ICEntry
& entry
= jitScript
->icEntry(i
);
395 ICFallbackStub
* fallback
= jitScript
->fallbackStub(i
);
396 jsbytecode
* pc
= script
->offsetToPC(fallback
->pcOffset());
400 Happiness entryHappiness
= Happy
;
401 if (!spewICEntryHealth(spew
, cx
, script
, &entry
, fallback
, pc
, op
,
403 cx
->recoverFromOutOfMemory();
406 if (entryHappiness
< scriptHappiness
) {
407 scriptHappiness
= entryHappiness
;
412 spew
->endList(); // entries
414 spew
->property("scriptHappiness", uint8_t(scriptHappiness
));
417 #endif /* JS_CACHEIR_SPEW */