Bug 1874684 - Part 29: Update spec fixme notes. r=mgaudet
[gecko.git] / js / src / jit / Bailouts.cpp
blob3730d8997a554b3f1fdcc7935a9cc8bf382c7253
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"
12 #include "gc/GC.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"
23 #include "vm/Stack.h"
25 #include "vm/JSScript-inl.h"
26 #include "vm/Probes-inl.h"
27 #include "vm/Stack-inl.h"
29 using namespace js;
30 using namespace js::jit;
32 using mozilla::IsInRange;
34 #if defined(_WIN32)
35 # pragma pack(push, 1)
36 #endif
37 class js::jit::BailoutStack {
38 RegisterDump::FPUArray fpregs_;
39 RegisterDump::GPRArray regs_;
40 uintptr_t frameSize_;
41 uintptr_t snapshotOffset_;
43 public:
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);
53 #if defined(_WIN32)
54 # pragma pack(pop)
55 #endif
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));
68 JSScript* script =
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 "
109 "wasm exit fp");
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),
118 0, 0x1000),
119 "Fake exitfp pointer should be within the first page.");
121 #ifdef DEBUG
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();
127 #endif
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;
143 bool success =
144 BailoutIonToBaseline(cx, bailoutData.activation(), frame, bailoutInfo,
145 /*exceptionInfo=*/nullptr, BailoutReason::Normal);
146 MOZ_ASSERT_IF(success, *bailoutInfo != nullptr);
148 if (!success) {
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(
185 cx->runtime())) {
186 cx->jitActivation->setLastProfilingFrame(currentFramePtr);
189 return success;
192 bool jit::InvalidationBailout(InvalidationBailoutStack* sp,
193 BaselineBailoutInfo** bailoutInfo) {
194 sp->checkInvariants();
196 JSContext* cx = TlsContext.get();
198 #ifdef DEBUG
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();
204 #endif
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);
225 if (!success) {
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);
243 #ifdef JS_JITSPEW
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());
251 #endif
254 frame.ionScript()->decrementInvalidationCount(cx->gcContext());
256 // Make the frame being bailed out the top profiled frame.
257 if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(
258 cx->runtime())) {
259 cx->jitActivation->setLastProfilingFrame(currentFramePtr);
262 return success;
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
270 // been captured.
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.
274 MOZ_ASSERT_IF(
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();
282 auto restoreExitFP =
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);
297 if (success) {
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;
312 } else {
313 // Drop the exception that triggered the bailout and instead propagate the
314 // failure caused by processing the bailout (eg. OOM).
315 savedExc.drop();
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(
322 cx->runtime())) {
323 cx->jitActivation->setLastProfilingFrame(currentFramePtr);
326 return success;
329 // Initialize the NamedLambdaObject and CallObject of the current frame if
330 // needed.
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)) {
338 return false;
342 return true;
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(); }