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
7 #include "vm/Stack-inl.h"
8
9 #include "mozilla/Maybe.h" // mozilla::Maybe
10
11 #include <algorithm> // std::max
12 #include <iterator> // std::size
13 #include <stddef.h> // size_t
14 #include <stdint.h> // uint8_t, uint32_t
15 #include <utility> // std::move
16
17 #include "debugger/DebugAPI.h"
18 #include "gc/Marking.h"
19 #include "gc/Tracer.h" // js::TraceRoot
20 #include "jit/JitcodeMap.h"
21 #include "jit/JitRuntime.h"
22 #include "js/friend/ErrorMessages.h" // JSMSG_*
23 #include "js/Value.h" // JS::Value
24 #include "vm/FrameIter.h" // js::FrameIter
25 #include "vm/JSContext.h"
26 #include "vm/Opcodes.h"
27 #include "wasm/WasmInstance.h"
28
29 #include "jit/JSJitFrameIter-inl.h"
30 #include "vm/Compartment-inl.h"
31 #include "vm/EnvironmentObject-inl.h"
32 #include "vm/Interpreter-inl.h"
33 #include "vm/Probes-inl.h"
34
35 using namespace js;
36
37 using mozilla::Maybe;
38
39 using JS::Value;
40
41 /*****************************************************************************/
42
initExecuteFrame(JSContext * cx,HandleScript script,AbstractFramePtr evalInFramePrev,HandleValue newTargetValue,HandleObject envChain)43 void InterpreterFrame::initExecuteFrame(JSContext* cx, HandleScript script,
44 AbstractFramePtr evalInFramePrev,
45 HandleValue newTargetValue,
46 HandleObject envChain) {
47 flags_ = 0;
48 script_ = script;
49
50 Value* dstvp = (Value*)this - 1;
51 dstvp[0] = newTargetValue;
52
53 envChain_ = envChain.get();
54 prev_ = nullptr;
55 prevpc_ = nullptr;
56 prevsp_ = nullptr;
57
58 evalInFramePrev_ = evalInFramePrev;
59 MOZ_ASSERT_IF(evalInFramePrev, isDebuggerEvalFrame());
60
61 if (script->isDebuggee()) {
62 setIsDebuggee();
63 }
64
65 #ifdef DEBUG
66 Debug_SetValueRangeToCrashOnTouch(&rval_, 1);
67 #endif
68 }
69
createRestParameter(JSContext * cx)70 ArrayObject* InterpreterFrame::createRestParameter(JSContext* cx) {
71 MOZ_ASSERT(script()->hasRest());
72 unsigned nformal = callee().nargs() - 1, nactual = numActualArgs();
73 unsigned nrest = (nactual > nformal) ? nactual - nformal : 0;
74 Value* restvp = argv() + nformal;
75 return NewDenseCopiedArray(cx, nrest, restvp);
76 }
77
AssertScopeMatchesEnvironment(Scope * scope,JSObject * originalEnv)78 static inline void AssertScopeMatchesEnvironment(Scope* scope,
79 JSObject* originalEnv) {
80 #ifdef DEBUG
81 JSObject* env = originalEnv;
82 for (ScopeIter si(scope); si; si++) {
83 if (si.kind() == ScopeKind::NonSyntactic) {
84 while (env->is<WithEnvironmentObject>() ||
85 env->is<NonSyntacticVariablesObject>() ||
86 (env->is<LexicalEnvironmentObject>() &&
87 !env->as<LexicalEnvironmentObject>().isSyntactic())) {
88 MOZ_ASSERT(!IsSyntacticEnvironment(env));
89 env = &env->as<EnvironmentObject>().enclosingEnvironment();
90 }
91 } else if (si.hasSyntacticEnvironment()) {
92 switch (si.kind()) {
93 case ScopeKind::Function:
94 MOZ_ASSERT(env->as<CallObject>()
95 .callee()
96 .maybeCanonicalFunction()
97 ->nonLazyScript() ==
98 si.scope()->as<FunctionScope>().script());
99 env = &env->as<CallObject>().enclosingEnvironment();
100 break;
101
102 case ScopeKind::FunctionBodyVar:
103 MOZ_ASSERT(&env->as<VarEnvironmentObject>().scope() == si.scope());
104 env = &env->as<VarEnvironmentObject>().enclosingEnvironment();
105 break;
106
107 case ScopeKind::Lexical:
108 case ScopeKind::SimpleCatch:
109 case ScopeKind::Catch:
110 case ScopeKind::NamedLambda:
111 case ScopeKind::StrictNamedLambda:
112 case ScopeKind::FunctionLexical:
113 case ScopeKind::ClassBody:
114 MOZ_ASSERT(&env->as<ScopedLexicalEnvironmentObject>().scope() ==
115 si.scope());
116 env =
117 &env->as<ScopedLexicalEnvironmentObject>().enclosingEnvironment();
118 break;
119
120 case ScopeKind::With:
121 MOZ_ASSERT(&env->as<WithEnvironmentObject>().scope() == si.scope());
122 env = &env->as<WithEnvironmentObject>().enclosingEnvironment();
123 break;
124
125 case ScopeKind::Eval:
126 case ScopeKind::StrictEval:
127 env = &env->as<VarEnvironmentObject>().enclosingEnvironment();
128 break;
129
130 case ScopeKind::Global:
131 env =
132 &env->as<GlobalLexicalEnvironmentObject>().enclosingEnvironment();
133 MOZ_ASSERT(env->is<GlobalObject>());
134 break;
135
136 case ScopeKind::NonSyntactic:
137 MOZ_CRASH("NonSyntactic should not have a syntactic environment");
138 break;
139
140 case ScopeKind::Module:
141 MOZ_ASSERT(&env->as<ModuleEnvironmentObject>().module() ==
142 si.scope()->as<ModuleScope>().module());
143 env = &env->as<ModuleEnvironmentObject>().enclosingEnvironment();
144 break;
145
146 case ScopeKind::WasmInstance:
147 env =
148 &env->as<WasmInstanceEnvironmentObject>().enclosingEnvironment();
149 break;
150
151 case ScopeKind::WasmFunction:
152 env = &env->as<WasmFunctionCallObject>().enclosingEnvironment();
153 break;
154 }
155 }
156 }
157
158 // In the case of a non-syntactic env chain, the immediate parent of the
159 // outermost non-syntactic env may be the global lexical env, or, if
160 // called from Debugger, a DebugEnvironmentProxy.
161 //
162 // In the case of a syntactic env chain, the outermost env is always a
163 // GlobalObject.
164 MOZ_ASSERT(env->is<GlobalObject>() || IsGlobalLexicalEnvironment(env) ||
165 env->is<DebugEnvironmentProxy>());
166 #endif
167 }
168
AssertScopeMatchesEnvironment(InterpreterFrame * fp,jsbytecode * pc)169 static inline void AssertScopeMatchesEnvironment(InterpreterFrame* fp,
170 jsbytecode* pc) {
171 #ifdef DEBUG
172 // If we OOMed before fully initializing the environment chain, the scope
173 // and environment will definitely mismatch.
174 if (fp->script()->initialEnvironmentShape() && fp->hasInitialEnvironment()) {
175 AssertScopeMatchesEnvironment(fp->script()->innermostScope(pc),
176 fp->environmentChain());
177 }
178 #endif
179 }
180
initFunctionEnvironmentObjects(JSContext * cx)181 bool InterpreterFrame::initFunctionEnvironmentObjects(JSContext* cx) {
182 return js::InitFunctionEnvironmentObjects(cx, this);
183 }
184
prologue(JSContext * cx)185 bool InterpreterFrame::prologue(JSContext* cx) {
186 RootedScript script(cx, this->script());
187
188 MOZ_ASSERT(cx->interpreterRegs().pc == script->code());
189 MOZ_ASSERT(cx->realm() == script->realm());
190
191 if (!isFunctionFrame()) {
192 return probes::EnterScript(cx, script, nullptr, this);
193 }
194
195 // At this point, we've yet to push any environments. Check that they
196 // match the enclosing scope.
197 AssertScopeMatchesEnvironment(script->enclosingScope(), environmentChain());
198
199 if (callee().needsFunctionEnvironmentObjects() &&
200 !initFunctionEnvironmentObjects(cx)) {
201 return false;
202 }
203
204 MOZ_ASSERT_IF(isConstructing(),
205 thisArgument().isObject() ||
206 thisArgument().isMagic(JS_UNINITIALIZED_LEXICAL));
207
208 return probes::EnterScript(cx, script, script->function(), this);
209 }
210
epilogue(JSContext * cx,jsbytecode * pc)211 void InterpreterFrame::epilogue(JSContext* cx, jsbytecode* pc) {
212 RootedScript script(cx, this->script());
213 MOZ_ASSERT(cx->realm() == script->realm());
214 probes::ExitScript(cx, script, script->function(),
215 hasPushedGeckoProfilerFrame());
216
217 // Check that the scope matches the environment at the point of leaving
218 // the frame.
219 AssertScopeMatchesEnvironment(this, pc);
220
221 EnvironmentIter ei(cx, this, pc);
222 UnwindAllEnvironmentsInFrame(cx, ei);
223
224 if (isFunctionFrame()) {
225 if (!callee().isGenerator() && !callee().isAsync() && isConstructing() &&
226 thisArgument().isObject() && returnValue().isPrimitive()) {
227 setReturnValue(thisArgument());
228 }
229
230 return;
231 }
232
233 MOZ_ASSERT(isEvalFrame() || isGlobalFrame() || isModuleFrame());
234 }
235
checkReturn(JSContext * cx,HandleValue thisv)236 bool InterpreterFrame::checkReturn(JSContext* cx, HandleValue thisv) {
237 MOZ_ASSERT(script()->isDerivedClassConstructor());
238 MOZ_ASSERT(isFunctionFrame());
239 MOZ_ASSERT(callee().isClassConstructor());
240
241 HandleValue retVal = returnValue();
242 if (retVal.isObject()) {
243 return true;
244 }
245
246 if (!retVal.isUndefined()) {
247 ReportValueError(cx, JSMSG_BAD_DERIVED_RETURN, JSDVG_IGNORE_STACK, retVal,
248 nullptr);
249 return false;
250 }
251
252 if (thisv.isMagic(JS_UNINITIALIZED_LEXICAL)) {
253 return ThrowUninitializedThis(cx);
254 }
255
256 setReturnValue(thisv);
257 return true;
258 }
259
pushVarEnvironment(JSContext * cx,HandleScope scope)260 bool InterpreterFrame::pushVarEnvironment(JSContext* cx, HandleScope scope) {
261 return js::PushVarEnvironmentObject(cx, scope, this);
262 }
263
pushLexicalEnvironment(JSContext * cx,Handle<LexicalScope * > scope)264 bool InterpreterFrame::pushLexicalEnvironment(JSContext* cx,
265 Handle<LexicalScope*> scope) {
266 BlockLexicalEnvironmentObject* env =
267 BlockLexicalEnvironmentObject::createForFrame(cx, scope, this);
268 if (!env) {
269 return false;
270 }
271
272 pushOnEnvironmentChain(*env);
273 return true;
274 }
275
freshenLexicalEnvironment(JSContext * cx)276 bool InterpreterFrame::freshenLexicalEnvironment(JSContext* cx) {
277 Rooted<BlockLexicalEnvironmentObject*> env(
278 cx, &envChain_->as<BlockLexicalEnvironmentObject>());
279 BlockLexicalEnvironmentObject* fresh =
280 BlockLexicalEnvironmentObject::clone(cx, env);
281 if (!fresh) {
282 return false;
283 }
284
285 replaceInnermostEnvironment(*fresh);
286 return true;
287 }
288
recreateLexicalEnvironment(JSContext * cx)289 bool InterpreterFrame::recreateLexicalEnvironment(JSContext* cx) {
290 Rooted<BlockLexicalEnvironmentObject*> env(
291 cx, &envChain_->as<BlockLexicalEnvironmentObject>());
292 BlockLexicalEnvironmentObject* fresh =
293 BlockLexicalEnvironmentObject::recreate(cx, env);
294 if (!fresh) {
295 return false;
296 }
297
298 replaceInnermostEnvironment(*fresh);
299 return true;
300 }
301
pushClassBodyEnvironment(JSContext * cx,Handle<ClassBodyScope * > scope)302 bool InterpreterFrame::pushClassBodyEnvironment(JSContext* cx,
303 Handle<ClassBodyScope*> scope) {
304 ClassBodyLexicalEnvironmentObject* env =
305 ClassBodyLexicalEnvironmentObject::createForFrame(cx, scope, this);
306 if (!env) {
307 return false;
308 }
309
310 pushOnEnvironmentChain(*env);
311 return true;
312 }
313
trace(JSTracer * trc,Value * sp,jsbytecode * pc)314 void InterpreterFrame::trace(JSTracer* trc, Value* sp, jsbytecode* pc) {
315 TraceRoot(trc, &envChain_, "env chain");
316 TraceRoot(trc, &script_, "script");
317
318 if (flags_ & HAS_ARGS_OBJ) {
319 TraceRoot(trc, &argsObj_, "arguments");
320 }
321
322 if (hasReturnValue()) {
323 TraceRoot(trc, &rval_, "rval");
324 }
325
326 MOZ_ASSERT(sp >= slots());
327
328 if (hasArgs()) {
329 // Trace the callee and |this|. When we're doing a moving GC, we
330 // need to fix up the callee pointer before we use it below, under
331 // numFormalArgs() and script().
332 TraceRootRange(trc, 2, argv_ - 2, "fp callee and this");
333
334 // Trace arguments.
335 unsigned argc = std::max(numActualArgs(), numFormalArgs());
336 TraceRootRange(trc, argc + isConstructing(), argv_, "fp argv");
337 } else {
338 // Trace newTarget.
339 TraceRoot(trc, ((Value*)this) - 1, "stack newTarget");
340 }
341
342 JSScript* script = this->script();
343 size_t nfixed = script->nfixed();
344 size_t nlivefixed = script->calculateLiveFixed(pc);
345
346 if (nfixed == nlivefixed) {
347 // All locals are live.
348 traceValues(trc, 0, sp - slots());
349 } else {
350 // Trace operand stack.
351 traceValues(trc, nfixed, sp - slots());
352
353 // Clear dead block-scoped locals.
354 while (nfixed > nlivefixed) {
355 unaliasedLocal(--nfixed).setUndefined();
356 }
357
358 // Trace live locals.
359 traceValues(trc, 0, nlivefixed);
360 }
361
362 if (auto* debugEnvs = script->realm()->debugEnvs()) {
363 debugEnvs->traceLiveFrame(trc, this);
364 }
365 }
366
traceValues(JSTracer * trc,unsigned start,unsigned end)367 void InterpreterFrame::traceValues(JSTracer* trc, unsigned start,
368 unsigned end) {
369 if (start < end) {
370 TraceRootRange(trc, end - start, slots() + start, "vm_stack");
371 }
372 }
373
TraceInterpreterActivation(JSTracer * trc,InterpreterActivation * act)374 static void TraceInterpreterActivation(JSTracer* trc,
375 InterpreterActivation* act) {
376 for (InterpreterFrameIterator frames(act); !frames.done(); ++frames) {
377 InterpreterFrame* fp = frames.frame();
378 fp->trace(trc, frames.sp(), frames.pc());
379 }
380 }
381
TraceInterpreterActivations(JSContext * cx,JSTracer * trc)382 void js::TraceInterpreterActivations(JSContext* cx, JSTracer* trc) {
383 for (ActivationIterator iter(cx); !iter.done(); ++iter) {
384 Activation* act = iter.activation();
385 if (act->isInterpreter()) {
386 TraceInterpreterActivation(trc, act->asInterpreter());
387 }
388 }
389 }
390
391 /*****************************************************************************/
392
393 // Unlike the other methods of this class, this method is defined here so that
394 // we don't have to #include jsautooplen.h in vm/Stack.h.
setToEndOfScript()395 void InterpreterRegs::setToEndOfScript() { sp = fp()->base(); }
396
397 /*****************************************************************************/
398
pushInvokeFrame(JSContext * cx,const CallArgs & args,MaybeConstruct constructing)399 InterpreterFrame* InterpreterStack::pushInvokeFrame(
400 JSContext* cx, const CallArgs& args, MaybeConstruct constructing) {
401 LifoAlloc::Mark mark = allocator_.mark();
402
403 RootedFunction fun(cx, &args.callee().as<JSFunction>());
404 RootedScript script(cx, fun->nonLazyScript());
405
406 Value* argv;
407 InterpreterFrame* fp = getCallFrame(cx, args, script, constructing, &argv);
408 if (!fp) {
409 return nullptr;
410 }
411
412 fp->mark_ = mark;
413 fp->initCallFrame(nullptr, nullptr, nullptr, *fun, script, argv,
414 args.length(), constructing);
415 return fp;
416 }
417
pushExecuteFrame(JSContext * cx,HandleScript script,HandleValue newTargetValue,HandleObject envChain,AbstractFramePtr evalInFrame)418 InterpreterFrame* InterpreterStack::pushExecuteFrame(
419 JSContext* cx, HandleScript script, HandleValue newTargetValue,
420 HandleObject envChain, AbstractFramePtr evalInFrame) {
421 LifoAlloc::Mark mark = allocator_.mark();
422
423 unsigned nvars = 1 /* newTarget */ + script->nslots();
424 uint8_t* buffer =
425 allocateFrame(cx, sizeof(InterpreterFrame) + nvars * sizeof(Value));
426 if (!buffer) {
427 return nullptr;
428 }
429
430 InterpreterFrame* fp =
431 reinterpret_cast<InterpreterFrame*>(buffer + 1 * sizeof(Value));
432 fp->mark_ = mark;
433 fp->initExecuteFrame(cx, script, evalInFrame, newTargetValue, envChain);
434 fp->initLocals();
435
436 return fp;
437 }
438
439 /*****************************************************************************/
440
operator ++()441 InterpreterFrameIterator& InterpreterFrameIterator::operator++() {
442 MOZ_ASSERT(!done());
443 if (fp_ != activation_->entryFrame_) {
444 pc_ = fp_->prevpc();
445 sp_ = fp_->prevsp();
446 fp_ = fp_->prev();
447 } else {
448 pc_ = nullptr;
449 sp_ = nullptr;
450 fp_ = nullptr;
451 }
452 return *this;
453 }
454
ProfilingFrameIterator(JSContext * cx,const RegisterState & state,const Maybe<uint64_t> & samplePositionInProfilerBuffer)455 JS::ProfilingFrameIterator::ProfilingFrameIterator(
456 JSContext* cx, const RegisterState& state,
457 const Maybe<uint64_t>& samplePositionInProfilerBuffer)
458 : cx_(cx),
459 samplePositionInProfilerBuffer_(samplePositionInProfilerBuffer),
460 activation_(nullptr) {
461 if (!cx->runtime()->geckoProfiler().enabled()) {
462 MOZ_CRASH(
463 "ProfilingFrameIterator called when geckoProfiler not enabled for "
464 "runtime.");
465 }
466
467 if (!cx->profilingActivation()) {
468 return;
469 }
470
471 // If profiler sampling is not enabled, skip.
472 if (!cx->isProfilerSamplingEnabled()) {
473 return;
474 }
475
476 activation_ = cx->profilingActivation();
477
478 MOZ_ASSERT(activation_->isProfiling());
479
480 static_assert(sizeof(wasm::ProfilingFrameIterator) <= StorageSpace &&
481 sizeof(jit::JSJitProfilingFrameIterator) <= StorageSpace,
482 "ProfilingFrameIterator::storage_ is too small");
483 static_assert(alignof(void*) >= alignof(wasm::ProfilingFrameIterator) &&
484 alignof(void*) >= alignof(jit::JSJitProfilingFrameIterator),
485 "ProfilingFrameIterator::storage_ is too weakly aligned");
486
487 iteratorConstruct(state);
488 settle();
489 }
490
~ProfilingFrameIterator()491 JS::ProfilingFrameIterator::~ProfilingFrameIterator() {
492 if (!done()) {
493 MOZ_ASSERT(activation_->isProfiling());
494 iteratorDestroy();
495 }
496 }
497
operator ++()498 void JS::ProfilingFrameIterator::operator++() {
499 MOZ_ASSERT(!done());
500 MOZ_ASSERT(activation_->isJit());
501 if (isWasm()) {
502 ++wasmIter();
503 } else {
504 ++jsJitIter();
505 }
506 settle();
507 }
508
settleFrames()509 void JS::ProfilingFrameIterator::settleFrames() {
510 // Handle transition frames (see comment in JitFrameIter::operator++).
511 if (isJSJit() && !jsJitIter().done() &&
512 jsJitIter().frameType() == jit::FrameType::WasmToJSJit) {
513 wasm::Frame* fp = (wasm::Frame*)jsJitIter().fp();
514 iteratorDestroy();
515 new (storage()) wasm::ProfilingFrameIterator(fp);
516 kind_ = Kind::Wasm;
517 MOZ_ASSERT(!wasmIter().done());
518 return;
519 }
520
521 if (isWasm() && wasmIter().done() && wasmIter().unwoundIonCallerFP()) {
522 uint8_t* fp = wasmIter().unwoundIonCallerFP();
523 iteratorDestroy();
524 // Using this ctor will skip the first ion->wasm frame, which is
525 // needed because the profiling iterator doesn't know how to unwind
526 // when the callee has no script.
527 new (storage())
528 jit::JSJitProfilingFrameIterator((jit::CommonFrameLayout*)fp);
529 kind_ = Kind::JSJit;
530 MOZ_ASSERT(!jsJitIter().done());
531 return;
532 }
533 }
534
settle()535 void JS::ProfilingFrameIterator::settle() {
536 settleFrames();
537 while (iteratorDone()) {
538 iteratorDestroy();
539 activation_ = activation_->prevProfiling();
540 if (!activation_) {
541 return;
542 }
543 iteratorConstruct();
544 settleFrames();
545 }
546 }
547
iteratorConstruct(const RegisterState & state)548 void JS::ProfilingFrameIterator::iteratorConstruct(const RegisterState& state) {
549 MOZ_ASSERT(!done());
550 MOZ_ASSERT(activation_->isJit());
551
552 jit::JitActivation* activation = activation_->asJit();
553
554 // We want to know if we should start with a wasm profiling frame iterator
555 // or not. To determine this, there are three possibilities:
556 // - we've exited to C++ from wasm, in which case the activation
557 // exitFP low bit is tagged and we can test hasWasmExitFP().
558 // - we're in wasm code, so we can do a lookup on PC.
559 // - in all the other cases, we're not in wasm or we haven't exited from
560 // wasm.
561 if (activation->hasWasmExitFP() || wasm::InCompiledCode(state.pc)) {
562 new (storage()) wasm::ProfilingFrameIterator(*activation, state);
563 kind_ = Kind::Wasm;
564 return;
565 }
566
567 new (storage()) jit::JSJitProfilingFrameIterator(cx_, state.pc);
568 kind_ = Kind::JSJit;
569 }
570
iteratorConstruct()571 void JS::ProfilingFrameIterator::iteratorConstruct() {
572 MOZ_ASSERT(!done());
573 MOZ_ASSERT(activation_->isJit());
574
575 jit::JitActivation* activation = activation_->asJit();
576
577 // The same reasoning as in the above iteratorConstruct variant applies
578 // here, except that it's even simpler: since this activation is higher up
579 // on the stack, it can only have exited to C++, through wasm or ion.
580 if (activation->hasWasmExitFP()) {
581 new (storage()) wasm::ProfilingFrameIterator(*activation);
582 kind_ = Kind::Wasm;
583 return;
584 }
585
586 auto* fp = (jit::ExitFrameLayout*)activation->jsExitFP();
587 new (storage()) jit::JSJitProfilingFrameIterator(fp);
588 kind_ = Kind::JSJit;
589 }
590
iteratorDestroy()591 void JS::ProfilingFrameIterator::iteratorDestroy() {
592 MOZ_ASSERT(!done());
593 MOZ_ASSERT(activation_->isJit());
594
595 if (isWasm()) {
596 wasmIter().~ProfilingFrameIterator();
597 return;
598 }
599
600 jsJitIter().~JSJitProfilingFrameIterator();
601 }
602
iteratorDone()603 bool JS::ProfilingFrameIterator::iteratorDone() {
604 MOZ_ASSERT(!done());
605 MOZ_ASSERT(activation_->isJit());
606
607 if (isWasm()) {
608 return wasmIter().done();
609 }
610
611 return jsJitIter().done();
612 }
613
stackAddress() const614 void* JS::ProfilingFrameIterator::stackAddress() const {
615 MOZ_ASSERT(!done());
616 MOZ_ASSERT(activation_->isJit());
617
618 if (isWasm()) {
619 return wasmIter().stackAddress();
620 }
621
622 return jsJitIter().stackAddress();
623 }
624
625 Maybe<JS::ProfilingFrameIterator::Frame>
getPhysicalFrameAndEntry(jit::JitcodeGlobalEntry * entry) const626 JS::ProfilingFrameIterator::getPhysicalFrameAndEntry(
627 jit::JitcodeGlobalEntry* entry) const {
628 void* stackAddr = stackAddress();
629
630 if (isWasm()) {
631 Frame frame;
632 frame.kind = Frame_Wasm;
633 frame.stackAddress = stackAddr;
634 frame.returnAddress_ = nullptr;
635 frame.activation = activation_;
636 frame.label = nullptr;
637 frame.endStackAddress = activation_->asJit()->jsOrWasmExitFP();
638 frame.interpreterScript = nullptr;
639 // TODO: get the realm ID of wasm frames. Bug 1596235.
640 frame.realmID = 0;
641 return mozilla::Some(frame);
642 }
643
644 MOZ_ASSERT(isJSJit());
645
646 // Look up an entry for the return address.
647 void* returnAddr = jsJitIter().resumePCinCurrentFrame();
648 jit::JitcodeGlobalTable* table =
649 cx_->runtime()->jitRuntime()->getJitcodeGlobalTable();
650
651 // NB:
652 // The following lookups should be infallible, but the ad-hoc stackwalking
653 // code rots easily and corner cases where frames can't be looked up
654 // occur too often (e.g. once every day).
655 //
656 // The calls to `lookup*` below have been changed from infallible ones to
657 // fallible ones. The proper solution to this problem is to fix all
658 // the jitcode to use frame-pointers and reliably walk the stack with those.
659 const jit::JitcodeGlobalEntry* lookedUpEntry = nullptr;
660 if (samplePositionInProfilerBuffer_) {
661 lookedUpEntry = table->lookupForSampler(returnAddr, cx_->runtime(),
662 *samplePositionInProfilerBuffer_);
663 } else {
664 lookedUpEntry = table->lookup(returnAddr);
665 }
666
667 // Failed to look up a jitcode entry for the given address, ignore.
668 if (!lookedUpEntry) {
669 return mozilla::Nothing();
670 }
671 *entry = *lookedUpEntry;
672
673 MOZ_ASSERT(entry->isIon() || entry->isBaseline() ||
674 entry->isBaselineInterpreter() || entry->isDummy());
675
676 // Dummy frames produce no stack frames.
677 if (entry->isDummy()) {
678 return mozilla::Nothing();
679 }
680
681 Frame frame;
682 if (entry->isBaselineInterpreter()) {
683 frame.kind = Frame_BaselineInterpreter;
684 } else if (entry->isBaseline()) {
685 frame.kind = Frame_Baseline;
686 } else {
687 frame.kind = Frame_Ion;
688 }
689 frame.stackAddress = stackAddr;
690 if (entry->isBaselineInterpreter()) {
691 frame.label = jsJitIter().baselineInterpreterLabel();
692 jsJitIter().baselineInterpreterScriptPC(
693 &frame.interpreterScript, &frame.interpreterPC_, &frame.realmID);
694 MOZ_ASSERT(frame.interpreterScript);
695 MOZ_ASSERT(frame.interpreterPC_);
696 } else {
697 frame.interpreterScript = nullptr;
698 frame.returnAddress_ = returnAddr;
699 frame.label = nullptr;
700 frame.realmID = 0;
701 }
702 frame.activation = activation_;
703 frame.endStackAddress = activation_->asJit()->jsOrWasmExitFP();
704 return mozilla::Some(frame);
705 }
706
extractStack(Frame * frames,uint32_t offset,uint32_t end) const707 uint32_t JS::ProfilingFrameIterator::extractStack(Frame* frames,
708 uint32_t offset,
709 uint32_t end) const {
710 if (offset >= end) {
711 return 0;
712 }
713
714 jit::JitcodeGlobalEntry entry;
715 Maybe<Frame> physicalFrame = getPhysicalFrameAndEntry(&entry);
716
717 // Dummy frames produce no stack frames.
718 if (physicalFrame.isNothing()) {
719 return 0;
720 }
721
722 if (isWasm()) {
723 frames[offset] = physicalFrame.value();
724 frames[offset].label = wasmIter().label();
725 return 1;
726 }
727
728 if (physicalFrame->kind == Frame_BaselineInterpreter) {
729 frames[offset] = physicalFrame.value();
730 return 1;
731 }
732
733 // Extract the stack for the entry. Assume maximum inlining depth is <64
734 const char* labels[64];
735 uint32_t depth = entry.callStackAtAddr(cx_->runtime(),
736 jsJitIter().resumePCinCurrentFrame(),
737 labels, std::size(labels));
738 MOZ_ASSERT(depth < std::size(labels));
739 for (uint32_t i = 0; i < depth; i++) {
740 if (offset + i >= end) {
741 return i;
742 }
743 frames[offset + i] = physicalFrame.value();
744 frames[offset + i].label = labels[i];
745 }
746
747 return depth;
748 }
749
750 Maybe<JS::ProfilingFrameIterator::Frame>
getPhysicalFrameWithoutLabel() const751 JS::ProfilingFrameIterator::getPhysicalFrameWithoutLabel() const {
752 jit::JitcodeGlobalEntry unused;
753 return getPhysicalFrameAndEntry(&unused);
754 }
755
isWasm() const756 bool JS::ProfilingFrameIterator::isWasm() const {
757 MOZ_ASSERT(!done());
758 return kind_ == Kind::Wasm;
759 }
760
isJSJit() const761 bool JS::ProfilingFrameIterator::isJSJit() const {
762 return kind_ == Kind::JSJit;
763 }
764
765 mozilla::Maybe<JS::ProfilingFrameIterator::RegisterState>
getCppEntryRegisters() const766 JS::ProfilingFrameIterator::getCppEntryRegisters() const {
767 if (!isJSJit()) {
768 return mozilla::Nothing{};
769 }
770 return jit::JitRuntime::getCppEntryRegisters(jsJitIter().framePtr());
771 }
772