forked from Floorp-Projects/Floorp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSPSProfiler.h
447 lines (400 loc) · 17.3 KB
/
SPSProfiler.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99 ft=cpp:
*
* 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/. */
#ifndef SPSProfiler_h__
#define SPSProfiler_h__
#include <stddef.h>
#include "mozilla/HashFunctions.h"
#include "js/Utility.h"
#include "jsscript.h"
/*
* SPS Profiler integration with the JS Engine
* https://developer.mozilla.org/en/Performance/Profiling_with_the_Built-in_Profiler
*
* The SPS profiler (found in tools/profiler) is an implementation of a profiler
* which has the ability to walk the C++ stack as well as use instrumentation to
* gather information. When dealing with JS, however, SPS needs integration
* with the engine because otherwise it is very difficult to figure out what
* javascript is executing.
*
* The current method of integration with SPS is a form of instrumentation:
* every time a JS function is entered, a bit of information is pushed onto a
* stack that SPS owns and maintains. This information is then popped at the end
* of the JS function. SPS informs the JS engine of this stack at runtime, and
* it can by turned on/off dynamically.
*
* The SPS stack has three parameters: a base pointer, a size, and a maximum
* size. The stack is the ProfileEntry stack which will have information written
* to it. The size location is a pointer to an integer which represents the
* current size of the stack (number of valid frames). This size will be
* modified when JS functions are called. The maximum specified is the maximum
* capacity of the ProfileEntry stack.
*
* Throughout execution, the size of the stack recorded in memory may exceed the
* maximum. The JS engine will not write any information past the maximum limit,
* but it will still maintain the size of the stack. SPS code is aware of this
* and iterates the stack accordingly.
*
* There is some information pushed on the SPS stack for every JS function that
* is entered. First is a char* pointer of a description of what function was
* entered. Currently this string is of the form "function (file:line)" if
* there's a function name, or just "file:line" if there's no function name
* available. The other bit of information is the relevant C++ (native) stack
* pointer. This stack pointer is what enables the interleaving of the C++ and
* the JS stack. Finally, throughout execution of the function, some extra
* information may be updated on the ProfileEntry structure.
*
* = Profile Strings
*
* The profile strings' allocations and deallocation must be carefully
* maintained, and ideally at a very low overhead cost. For this reason, the JS
* engine maintains a mapping of all known profile strings. These strings are
* keyed in lookup by a JSScript*, but are serialized with a JSFunction*,
* JSScript* pair. A JSScript will destroy its corresponding profile string when
* the script is finalized.
*
* For this reason, a char* pointer pushed on the SPS stack is valid only while
* it is on the SPS stack. SPS uses sampling to read off information from this
* instrumented stack, and it therefore copies the string byte for byte when a
* JS function is encountered during sampling.
*
* = Native Stack Pointer
*
* The actual value pushed as the native pointer is NULL for most JS functions.
* The reason for this is that there's actually very little correlation between
* the JS stack and the C++ stack because many JS functions all run in the same
* C++ frame, or can even go backwards in C++ when going from the JIT back to
* the interpreter.
*
* To alleviate this problem, all JS functions push NULL as their "native stack
* pointer" to indicate that it's a JS function call. The function RunScript(),
* however, pushes an actual C++ stack pointer onto the SPS stack. This way when
* interleaving C++ and JS, if SPS sees a NULL native stack pointer on the SPS
* stack, it looks backwards for the first non-NULL pointer and uses that for
* all subsequent NULL native stack pointers.
*
* = Line Numbers
*
* One goal of sampling is to get both a backtrace of the JS stack, but also
* know where within each function on the stack execution currently is. For
* this, each ProfileEntry has a 'pc' field to tell where its execution
* currently is. This field is updated whenever a call is made to another JS
* function, and for the JIT it is also updated whenever the JIT is left.
*
* This field is in a union with a uint32_t 'line' so that C++ can make use of
* the field as well. It was observed that tracking 'line' via PCToLineNumber in
* JS was far too expensive, so that is why the pc instead of the translated
* line number is stored.
*
* As an invariant, if the pc is NULL, then the JIT is currently executing
* generated code. Otherwise execution is in another JS function or in C++. With
* this in place, only the top entry of the stack can ever have NULL as its pc.
* Additionally with this invariant, it is possible to maintain mappings of JIT
* code to pc which can be accessed safely because they will only be accessed
* from a signal handler when the JIT code is executing.
*/
struct JSFunction;
namespace js {
class ProfileEntry;
#ifdef JS_METHODJIT
namespace mjit {
struct JITChunk;
struct JITScript;
struct JSActiveFrame;
struct PCLengthEntry;
}
#endif
typedef HashMap<JSScript*, const char*, DefaultHasher<JSScript*>, SystemAllocPolicy>
ProfileStringMap;
class SPSEntryMarker;
class SPSProfiler
{
friend class SPSEntryMarker;
JSRuntime *rt;
ProfileStringMap strings;
ProfileEntry *stack_;
uint32_t *size_;
uint32_t max_;
bool slowAssertions;
bool enabled_;
const char *allocProfileString(JSContext *cx, JSScript *script,
JSFunction *function);
void push(const char *string, void *sp, JSScript *script, jsbytecode *pc);
void pop();
public:
SPSProfiler(JSRuntime *rt);
~SPSProfiler();
uint32_t *sizePointer() { return size_; }
uint32_t maxSize() { return max_; }
ProfileEntry *stack() { return stack_; }
/* management of whether instrumentation is on or off */
bool enabled() { JS_ASSERT_IF(enabled_, installed()); return enabled_; }
bool installed() { return stack_ != NULL && size_ != NULL; }
void enable(bool enabled);
void enableSlowAssertions(bool enabled) { slowAssertions = enabled; }
bool slowAssertionsEnabled() { return slowAssertions; }
/*
* Functions which are the actual instrumentation to track run information
*
* - enter: a function has started to execute
* - updatePC: updates the pc information about where a function
* is currently executing
* - exit: this function has ceased execution, and no further
* entries/exits will be made
*/
bool enter(JSContext *cx, JSScript *script, JSFunction *maybeFun);
void exit(JSContext *cx, JSScript *script, JSFunction *maybeFun);
void updatePC(JSScript *script, jsbytecode *pc) {
if (enabled() && *size_ - 1 < max_) {
JS_ASSERT(*size_ > 0);
JS_ASSERT(stack_[*size_ - 1].script() == script);
stack_[*size_ - 1].setPC(pc);
}
}
#ifdef JS_METHODJIT
struct ICInfo
{
size_t base;
size_t size;
jsbytecode *pc;
ICInfo(void *base, size_t size, jsbytecode *pc)
: base(size_t(base)), size(size), pc(pc)
{}
};
struct JMChunkInfo
{
size_t mainStart; // bounds for the inline code
size_t mainEnd;
size_t stubStart; // bounds of the ool code
size_t stubEnd;
mjit::PCLengthEntry *pcLengths; // pcLengths for this chunk
mjit::JITChunk *chunk; // stored to test when removing
JMChunkInfo(mjit::JSActiveFrame *frame,
mjit::PCLengthEntry *pcLengths,
mjit::JITChunk *chunk);
jsbytecode *convert(JSScript *script, size_t ip);
};
struct JMScriptInfo
{
Vector<ICInfo, 0, SystemAllocPolicy> ics;
Vector<JMChunkInfo, 1, SystemAllocPolicy> chunks;
};
typedef HashMap<JSScript*, JMScriptInfo*, DefaultHasher<JSScript*>,
SystemAllocPolicy> JITInfoMap;
/*
* This is the mapping which facilitates translation from an ip to a
* jsbytecode*. The mapping is from a JSScript* to a set of chunks and ics
* which are associated with the script. This way lookup/translation doesn't
* have to do something like iterate the entire map.
*
* Each IC is easy to test because they all have only one pc associated with
* them, and the range is easy to check. The main chunks of code are a bit
* harder because there are both the inline and out of line streams which
* need to be tested. Each of these streams is described by the pcLengths
* array stored within each chunk. This array describes the width of each
* opcode of the corresponding JSScript, and has the same number of entries
* as script->length.
*/
JITInfoMap jminfo;
bool registerMJITCode(mjit::JITChunk *chunk,
mjit::JSActiveFrame *outerFrame,
mjit::JSActiveFrame **inlineFrames);
void discardMJITCode(mjit::JITScript *jscr,
mjit::JITChunk *chunk, void* address);
bool registerICCode(mjit::JITChunk *chunk, JSScript *script, jsbytecode* pc,
void *start, size_t size);
jsbytecode *ipToPC(JSScript *script, size_t ip);
private:
JMChunkInfo *registerScript(mjit::JSActiveFrame *frame,
mjit::PCLengthEntry *lenths,
mjit::JITChunk *chunk);
void unregisterScript(UnrootedScript script, mjit::JITChunk *chunk);
public:
#else
jsbytecode *ipToPC(JSScript *script, size_t ip) { return NULL; }
#endif
void setProfilingStack(ProfileEntry *stack, uint32_t *size, uint32_t max);
const char *profileString(JSContext *cx, JSScript *script, JSFunction *maybeFun);
void onScriptFinalized(JSScript *script);
/* meant to be used for testing, not recommended to call in normal code */
size_t stringsCount() { return strings.count(); }
void stringsReset() { strings.clear(); }
};
/*
* This class is used in RunScript() to push the marker onto the sampling stack
* that we're about to enter JS function calls. This is the only time in which a
* valid stack pointer is pushed to the sampling stack.
*/
class SPSEntryMarker
{
SPSProfiler *profiler;
mozilla::DebugOnly<uint32_t> size_before;
JS_DECL_USE_GUARD_OBJECT_NOTIFIER
public:
SPSEntryMarker(JSRuntime *rt JS_GUARD_OBJECT_NOTIFIER_PARAM);
~SPSEntryMarker();
};
/*
* SPS is the profiling backend used by the JS engine to enable time profiling.
* More information can be found in vm/SPSProfiler.{h,cpp}. This class manages
* the instrumentation portion of the profiling for JIT code.
*
* The instrumentation tracks entry into functions, leaving those functions via
* a function call, reentering the functions from a function call, and exiting
* the functions from returning. This class also handles inline frames and
* manages the instrumentation which needs to be attached to them as well.
*
* The basic methods which emit instrumentation are at the end of this class,
* and the management functions are all described in the middle.
*/
template<class Assembler, class Register>
class SPSInstrumentation
{
/* Because of inline frames, this is a nested structure in a vector */
struct FrameState {
JSScript *script; // script for this frame, NULL if not pushed yet
bool skipNext; // should the next call to reenter be skipped?
int left; // number of leave() calls made without a matching reenter()
};
SPSProfiler *profiler_; // Instrumentation location management
Vector<FrameState, 1, SystemAllocPolicy> frames;
FrameState *frame;
public:
/*
* Creates instrumentation which writes information out the the specified
* profiler's stack and constituent fields.
*/
SPSInstrumentation(SPSProfiler *profiler)
: profiler_(profiler), frame(NULL)
{
enterInlineFrame();
}
/* Small proxies around SPSProfiler */
bool enabled() { return profiler_ && profiler_->enabled(); }
SPSProfiler *profiler() { JS_ASSERT(enabled()); return profiler_; }
bool slowAssertions() { return enabled() && profiler_->slowAssertionsEnabled(); }
/* Signals an inline function returned, reverting to the previous state */
void leaveInlineFrame() {
if (!enabled())
return;
JS_ASSERT(frame->left == 0);
JS_ASSERT(frame->script != NULL);
frames.shrinkBy(1);
JS_ASSERT(frames.length() > 0);
frame = &frames[frames.length() - 1];
}
/* Saves the current state and assumes a fresh one for the inline function */
bool enterInlineFrame() {
if (!enabled())
return true;
JS_ASSERT_IF(frame != NULL, frame->script != NULL);
JS_ASSERT_IF(frame != NULL, frame->left == 1);
if (!frames.growBy(1))
return false;
frame = &frames[frames.length() - 1];
frame->script = NULL;
frame->skipNext = false;
frame->left = 0;
return true;
}
/* Number of inline frames currently active (doesn't include original one) */
unsigned inliningDepth() {
return frames.length() - 1;
}
/*
* When debugging or with slow assertions, sometimes a C++ method will be
* invoked to perform the pop operation from the SPS stack. When we leave
* JIT code, we need to record the current PC, but upon reentering JIT code,
* no update back to NULL should happen. This method exists to flag this
* behavior. The next leave() will emit instrumentation, but the following
* reenter() will be a no-op.
*/
void skipNextReenter() {
/* If we've left the frame, the reenter will be skipped anyway */
if (!enabled() || frame->left != 0)
return;
JS_ASSERT(frame->script);
JS_ASSERT(!frame->skipNext);
frame->skipNext = true;
}
/*
* In some cases, a frame needs to be flagged as having been pushed, but no
* instrumentation should be emitted. This updates internal state to flag
* that further instrumentation should actually be emitted.
*/
void setPushed(JSScript *script) {
if (!enabled())
return;
JS_ASSERT(frame->left == 0);
frame->script = script;
}
/*
* Flags entry into a JS function for the first time. Before this is called,
* no instrumentation is emitted, but after this instrumentation is emitted.
*/
bool push(JSContext *cx, JSScript *script, Assembler &masm, Register scratch) {
if (!enabled())
return true;
const char *string = profiler_->profileString(cx, script,
script->function());
if (string == NULL)
return false;
masm.spsPushFrame(profiler_, string, script, scratch);
setPushed(script);
return true;
}
/*
* Signifies that C++ performed the push() for this function. C++ always
* sets the current PC to something non-null, however, so as soon as JIT
* code is reentered this updates the current pc to NULL.
*/
void pushManual(JSScript *script, Assembler &masm, Register scratch) {
if (!enabled())
return;
masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch);
setPushed(script);
}
/*
* Signals that the current function is leaving for a function call. This
* can happen both on JS function calls and also calls to C++. This
* internally manages how many leave() calls have been seen, and only the
* first leave() emits instrumentation. Similarly, only the last
* corresponding reenter() actually emits instrumentation.
*/
void leave(jsbytecode *pc, Assembler &masm, Register scratch) {
if (enabled() && frame->script && frame->left++ == 0) {
JS_ASSERT(frame->script->code <= pc &&
pc < frame->script->code + frame->script->length);
masm.spsUpdatePCIdx(profiler_, pc - frame->script->code, scratch);
}
}
/*
* Flags that the leaving of the current function has returned. This tracks
* state with leave() to only emit instrumentation at proper times.
*/
void reenter(Assembler &masm, Register scratch) {
if (!enabled() || !frame->script || frame->left-- != 1)
return;
if (frame->skipNext)
frame->skipNext = false;
else
masm.spsUpdatePCIdx(profiler_, ProfileEntry::NullPCIndex, scratch);
}
/*
* Signifies exiting a JS frame, popping the SPS entry. Because there can be
* multiple return sites of a function, this does not cease instrumentation
* emission.
*/
void pop(Assembler &masm, Register scratch) {
if (enabled()) {
JS_ASSERT(frame->left == 0);
JS_ASSERT(frame->script);
masm.spsPopFrame(profiler_, scratch);
}
}
};
} /* namespace js */
#endif /* SPSProfiler_h__ */