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/. */
7 #include "jit/Bailouts.h"
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/ScopeExit.h"
13 #include "jit/Assembler.h" // jit::FramePointer
14 #include "jit/BaselineJIT.h"
15 #include "jit/JitFrames.h"
16 #include "jit/JitRuntime.h"
17 #include "jit/JitSpewer.h"
18 #include "jit/JSJitFrameIter.h"
19 #include "jit/SafepointIndex.h"
20 #include "jit/ScriptFromCalleeToken.h"
21 #include "vm/Interpreter.h"
22 #include "vm/JSContext.h"
25 #include "vm/JSScript-inl.h"
26 #include "vm/Probes-inl.h"
27 #include "vm/Stack-inl.h"
30 using namespace js::jit
;
32 using mozilla::IsInRange
;
35 # pragma pack(push, 1)
37 class js::jit::BailoutStack
{
38 RegisterDump::FPUArray fpregs_
;
39 RegisterDump::GPRArray regs_
;
41 uintptr_t snapshotOffset_
;
44 MachineState
machineState() {
45 return MachineState::FromBailout(regs_
, fpregs_
);
47 uint32_t snapshotOffset() const { return snapshotOffset_
; }
48 uint32_t frameSize() const { return frameSize_
; }
49 uint8_t* parentStackPointer() {
50 return (uint8_t*)this + sizeof(BailoutStack
);
57 // Make sure the compiler doesn't add extra padding on 32-bit platforms.
58 static_assert((sizeof(BailoutStack
) % 8) == 0,
59 "BailoutStack should be 8-byte aligned.");
61 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator
& activations
,
62 BailoutStack
* bailout
)
63 : machine_(bailout
->machineState()), activation_(nullptr) {
64 uint8_t* sp
= bailout
->parentStackPointer();
65 framePointer_
= sp
+ bailout
->frameSize();
66 MOZ_RELEASE_ASSERT(uintptr_t(framePointer_
) == machine_
.read(FramePointer
));
69 ScriptFromCalleeToken(((JitFrameLayout
*)framePointer_
)->calleeToken());
70 topIonScript_
= script
->ionScript();
72 attachOnJitActivation(activations
);
73 snapshotOffset_
= bailout
->snapshotOffset();
76 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator
& activations
,
77 InvalidationBailoutStack
* bailout
)
78 : machine_(bailout
->machine()), activation_(nullptr) {
79 framePointer_
= (uint8_t*)bailout
->fp();
80 MOZ_RELEASE_ASSERT(uintptr_t(framePointer_
) == machine_
.read(FramePointer
));
82 topIonScript_
= bailout
->ionScript();
83 attachOnJitActivation(activations
);
85 uint8_t* returnAddressToFp_
= bailout
->osiPointReturnAddress();
86 const OsiIndex
* osiIndex
= topIonScript_
->getOsiIndex(returnAddressToFp_
);
87 snapshotOffset_
= osiIndex
->snapshotOffset();
90 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator
& activations
,
91 const JSJitFrameIter
& frame
)
92 : machine_(frame
.machineState()) {
93 framePointer_
= (uint8_t*)frame
.fp();
94 topIonScript_
= frame
.ionScript();
95 attachOnJitActivation(activations
);
97 const OsiIndex
* osiIndex
= frame
.osiIndex();
98 snapshotOffset_
= osiIndex
->snapshotOffset();
101 // This address is a magic number made to cause crashes while indicating that we
102 // are making an attempt to mark the stack during a bailout.
103 static constexpr uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR
= 0xba2;
104 static uint8_t* const FAKE_EXITFP_FOR_BAILOUT
=
105 reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR
);
107 static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR
& wasm::ExitFPTag
),
108 "FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged "
111 bool jit::Bailout(BailoutStack
* sp
, BaselineBailoutInfo
** bailoutInfo
) {
112 JSContext
* cx
= TlsContext
.get();
113 MOZ_ASSERT(bailoutInfo
);
115 // We don't have an exit frame.
116 MOZ_ASSERT(IsInRange(FAKE_EXITFP_FOR_BAILOUT
, 0, 0x1000) &&
117 IsInRange(FAKE_EXITFP_FOR_BAILOUT
+ sizeof(CommonFrameLayout
),
119 "Fake exitfp pointer should be within the first page.");
122 // Reset the counter when we bailed after MDebugEnterGCUnsafeRegion, but
123 // before the matching MDebugLeaveGCUnsafeRegion.
125 // NOTE: EnterJit ensures the counter is zero when we enter JIT code.
126 cx
->resetInUnsafeRegion();
129 cx
->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT
);
131 JitActivationIterator
jitActivations(cx
);
132 BailoutFrameInfo
bailoutData(jitActivations
, sp
);
133 JSJitFrameIter
frame(jitActivations
->asJit());
134 MOZ_ASSERT(!frame
.ionScript()->invalidated());
135 JitFrameLayout
* currentFramePtr
= frame
.jsFrame();
137 JitSpew(JitSpew_IonBailouts
, "Took bailout! Snapshot offset: %u",
138 frame
.snapshotOffset());
140 MOZ_ASSERT(IsBaselineJitEnabled(cx
));
142 *bailoutInfo
= nullptr;
144 BailoutIonToBaseline(cx
, bailoutData
.activation(), frame
, bailoutInfo
,
145 /*exceptionInfo=*/nullptr, BailoutReason::Normal
);
146 MOZ_ASSERT_IF(success
, *bailoutInfo
!= nullptr);
149 MOZ_ASSERT(cx
->isExceptionPending());
150 JSScript
* script
= frame
.script();
151 probes::ExitScript(cx
, script
, script
->function(),
152 /* popProfilerFrame = */ false);
155 // This condition was wrong when we entered this bailout function, but it
156 // might be true now. A GC might have reclaimed all the Jit code and
157 // invalidated all frames which are currently on the stack. As we are
158 // already in a bailout, we could not switch to an invalidation
159 // bailout. When the code of an IonScript which is on the stack is
160 // invalidated (see InvalidateActivation), we remove references to it and
161 // increment the reference counter for each activation that appear on the
162 // stack. As the bailed frame is one of them, we have to decrement it now.
163 if (frame
.ionScript()->invalidated()) {
164 frame
.ionScript()->decrementInvalidationCount(cx
->gcContext());
167 // NB: Commentary on how |lastProfilingFrame| is set from bailouts.
169 // Once we return to jitcode, any following frames might get clobbered,
170 // but the current frame will not (as it will be clobbered "in-place"
171 // with a baseline frame that will share the same frame prefix).
172 // However, there may be multiple baseline frames unpacked from this
173 // single Ion frame, which means we will need to once again reset
174 // |lastProfilingFrame| to point to the correct unpacked last frame
175 // in |FinishBailoutToBaseline|.
177 // In the case of error, the jitcode will jump immediately to an
178 // exception handler, which will unwind the frames and properly set
179 // the |lastProfilingFrame| to point to the frame being resumed into
180 // (see |AutoResetLastProfilerFrameOnReturnFromException|).
182 // In both cases, we want to temporarily set the |lastProfilingFrame|
183 // to the current frame being bailed out, and then fix it up later.
184 if (cx
->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
186 cx
->jitActivation
->setLastProfilingFrame(currentFramePtr
);
192 bool jit::InvalidationBailout(InvalidationBailoutStack
* sp
,
193 BaselineBailoutInfo
** bailoutInfo
) {
194 sp
->checkInvariants();
196 JSContext
* cx
= TlsContext
.get();
199 // Reset the counter when we bailed after MDebugEnterGCUnsafeRegion, but
200 // before the matching MDebugLeaveGCUnsafeRegion.
202 // NOTE: EnterJit ensures the counter is zero when we enter JIT code.
203 cx
->resetInUnsafeRegion();
206 // We don't have an exit frame.
207 cx
->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT
);
209 JitActivationIterator
jitActivations(cx
);
210 BailoutFrameInfo
bailoutData(jitActivations
, sp
);
211 JSJitFrameIter
frame(jitActivations
->asJit());
212 JitFrameLayout
* currentFramePtr
= frame
.jsFrame();
214 JitSpew(JitSpew_IonBailouts
, "Took invalidation bailout! Snapshot offset: %u",
215 frame
.snapshotOffset());
217 MOZ_ASSERT(IsBaselineJitEnabled(cx
));
219 *bailoutInfo
= nullptr;
220 bool success
= BailoutIonToBaseline(cx
, bailoutData
.activation(), frame
,
221 bailoutInfo
, /*exceptionInfo=*/nullptr,
222 BailoutReason::Invalidate
);
223 MOZ_ASSERT_IF(success
, *bailoutInfo
!= nullptr);
226 MOZ_ASSERT(cx
->isExceptionPending());
228 // If the bailout failed, then bailout trampoline will pop the
229 // current frame and jump straight to exception handling code when
230 // this function returns. Any Gecko Profiler entry pushed for this
231 // frame will be silently forgotten.
233 // We call ExitScript here to ensure that if the ionScript had Gecko
234 // Profiler instrumentation, then the entry for it is popped.
236 // However, if the bailout was during argument check, then a
237 // pseudostack frame would not have been pushed in the first
238 // place, so don't pop anything in that case.
239 JSScript
* script
= frame
.script();
240 probes::ExitScript(cx
, script
, script
->function(),
241 /* popProfilerFrame = */ false);
244 JitFrameLayout
* layout
= frame
.jsFrame();
245 JitSpew(JitSpew_IonInvalidate
, "Bailout failed (Fatal Error)");
246 JitSpew(JitSpew_IonInvalidate
, " calleeToken %p",
247 (void*)layout
->calleeToken());
248 JitSpew(JitSpew_IonInvalidate
, " callerFramePtr %p",
249 layout
->callerFramePtr());
250 JitSpew(JitSpew_IonInvalidate
, " ra %p", (void*)layout
->returnAddress());
254 frame
.ionScript()->decrementInvalidationCount(cx
->gcContext());
256 // Make the frame being bailed out the top profiled frame.
257 if (cx
->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
259 cx
->jitActivation
->setLastProfilingFrame(currentFramePtr
);
265 bool jit::ExceptionHandlerBailout(JSContext
* cx
,
266 const InlineFrameIterator
& frame
,
267 ResumeFromException
* rfe
,
268 const ExceptionBailoutInfo
& excInfo
) {
269 // If we are resuming in a finally block, the exception has already
271 // We can also be propagating debug mode exceptions without there being an
272 // actual exception pending. For instance, when we return false from an
273 // operation callback like a timeout handler.
275 !cx
->isExceptionPending(),
276 excInfo
.isFinally() || excInfo
.propagatingIonExceptionForDebugMode());
278 JS::AutoSaveExceptionState
savedExc(cx
);
280 JitActivation
* act
= cx
->activation()->asJit();
281 uint8_t* prevExitFP
= act
->jsExitFP();
283 mozilla::MakeScopeExit([&]() { act
->setJSExitFP(prevExitFP
); });
284 act
->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT
);
286 gc::AutoSuppressGC
suppress(cx
);
288 JitActivationIterator
jitActivations(cx
);
289 BailoutFrameInfo
bailoutData(jitActivations
, frame
.frame());
290 JSJitFrameIter
frameView(jitActivations
->asJit());
291 JitFrameLayout
* currentFramePtr
= frameView
.jsFrame();
293 BaselineBailoutInfo
* bailoutInfo
= nullptr;
294 bool success
= BailoutIonToBaseline(cx
, bailoutData
.activation(), frameView
,
295 &bailoutInfo
, &excInfo
,
296 BailoutReason::ExceptionHandler
);
298 MOZ_ASSERT(bailoutInfo
);
300 // Overwrite the kind so HandleException after the bailout returns
301 // false, jumping directly to the exception tail.
302 if (excInfo
.propagatingIonExceptionForDebugMode()) {
303 bailoutInfo
->bailoutKind
=
304 mozilla::Some(BailoutKind::IonExceptionDebugMode
);
305 } else if (excInfo
.isFinally()) {
306 bailoutInfo
->bailoutKind
= mozilla::Some(BailoutKind::Finally
);
309 rfe
->kind
= ExceptionResumeKind::Bailout
;
310 rfe
->stackPointer
= bailoutInfo
->incomingStack
;
311 rfe
->bailoutInfo
= bailoutInfo
;
313 // Drop the exception that triggered the bailout and instead propagate the
314 // failure caused by processing the bailout (eg. OOM).
316 MOZ_ASSERT(!bailoutInfo
);
317 MOZ_ASSERT(cx
->isExceptionPending());
320 // Make the frame being bailed out the top profiled frame.
321 if (cx
->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
323 cx
->jitActivation
->setLastProfilingFrame(currentFramePtr
);
329 // Initialize the NamedLambdaObject and CallObject of the current frame if
331 bool jit::EnsureHasEnvironmentObjects(JSContext
* cx
, AbstractFramePtr fp
) {
332 // Ion does not compile eval scripts.
333 MOZ_ASSERT(!fp
.isEvalFrame());
335 if (fp
.isFunctionFrame() && !fp
.hasInitialEnvironment() &&
336 fp
.callee()->needsFunctionEnvironmentObjects()) {
337 if (!fp
.initFunctionEnvironmentObjects(cx
)) {
345 void BailoutFrameInfo::attachOnJitActivation(
346 const JitActivationIterator
& jitActivations
) {
347 MOZ_ASSERT(jitActivations
->asJit()->jsExitFP() == FAKE_EXITFP_FOR_BAILOUT
);
348 activation_
= jitActivations
->asJit();
349 activation_
->setBailoutData(this);
352 BailoutFrameInfo::~BailoutFrameInfo() { activation_
->cleanBailoutData(); }