Bailouts.cpp (13398B)
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 "jit/Bailouts.h" 8 9 #include "mozilla/ArrayUtils.h" 10 #include "mozilla/ScopeExit.h" 11 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" 24 25 #include "vm/JSScript-inl.h" 26 #include "vm/Probes-inl.h" 27 #include "vm/Stack-inl.h" 28 29 using namespace js; 30 using namespace js::jit; 31 32 using mozilla::IsInRange; 33 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_; 42 43 public: 44 MachineState machineState() { 45 return MachineState::FromBailout(regs_, fpregs_); 46 } 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); 51 } 52 }; 53 #if defined(_WIN32) 54 # pragma pack(pop) 55 #endif 56 57 #if !defined(JS_CODEGEN_NONE) 58 // Make sure the compiler doesn't add extra padding on 32-bit platforms. 59 static_assert((sizeof(BailoutStack) % 8) == 0, 60 "BailoutStack should be 8-byte aligned."); 61 #endif 62 63 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations, 64 BailoutStack* bailout) 65 : machine_(bailout->machineState()), activation_(nullptr) { 66 uint8_t* sp = bailout->parentStackPointer(); 67 framePointer_ = sp + bailout->frameSize(); 68 MOZ_RELEASE_ASSERT(uintptr_t(framePointer_) == machine_.read(FramePointer)); 69 70 JSScript* script = 71 ScriptFromCalleeToken(((JitFrameLayout*)framePointer_)->calleeToken()); 72 topIonScript_ = script->ionScript(); 73 74 attachOnJitActivation(activations); 75 snapshotOffset_ = bailout->snapshotOffset(); 76 } 77 78 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations, 79 InvalidationBailoutStack* bailout) 80 : machine_(bailout->machine()), activation_(nullptr) { 81 framePointer_ = (uint8_t*)bailout->fp(); 82 MOZ_RELEASE_ASSERT(uintptr_t(framePointer_) == machine_.read(FramePointer)); 83 84 topIonScript_ = bailout->ionScript(); 85 attachOnJitActivation(activations); 86 87 uint8_t* returnAddressToFp_ = bailout->osiPointReturnAddress(); 88 const OsiIndex* osiIndex = topIonScript_->getOsiIndex(returnAddressToFp_); 89 snapshotOffset_ = osiIndex->snapshotOffset(); 90 } 91 92 BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations, 93 const JSJitFrameIter& frame) 94 : machine_(frame.machineState()) { 95 framePointer_ = (uint8_t*)frame.fp(); 96 topIonScript_ = frame.ionScript(); 97 attachOnJitActivation(activations); 98 99 const OsiIndex* osiIndex = frame.osiIndex(); 100 snapshotOffset_ = osiIndex->snapshotOffset(); 101 } 102 103 // This address is a magic number made to cause crashes while indicating that we 104 // are making an attempt to mark the stack during a bailout. 105 static constexpr uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2; 106 static uint8_t* const FAKE_EXITFP_FOR_BAILOUT = 107 reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR); 108 109 static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & wasm::ExitFPTag), 110 "FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged " 111 "wasm exit fp"); 112 113 bool jit::Bailout(BailoutStack* sp, BaselineBailoutInfo** bailoutInfo) { 114 JSContext* cx = TlsContext.get(); 115 MOZ_ASSERT(bailoutInfo); 116 117 // We don't have an exit frame. 118 MOZ_ASSERT(IsInRange(FAKE_EXITFP_FOR_BAILOUT, 0, 0x1000) && 119 IsInRange(FAKE_EXITFP_FOR_BAILOUT + sizeof(CommonFrameLayout), 120 0, 0x1000), 121 "Fake exitfp pointer should be within the first page."); 122 123 #ifdef DEBUG 124 // Reset the counter when we bailed after MDebugEnterGCUnsafeRegion, but 125 // before the matching MDebugLeaveGCUnsafeRegion. 126 // 127 // NOTE: EnterJit ensures the counter is zero when we enter JIT code. 128 cx->resetInUnsafeRegion(); 129 #endif 130 131 cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT); 132 133 JitActivationIterator jitActivations(cx); 134 BailoutFrameInfo bailoutData(jitActivations, sp); 135 JSJitFrameIter frame(jitActivations->asJit()); 136 MOZ_ASSERT(!frame.ionScript()->invalidated()); 137 JitFrameLayout* currentFramePtr = frame.jsFrame(); 138 139 JitSpew(JitSpew_IonBailouts, "Took bailout! Snapshot offset: %u", 140 frame.snapshotOffset()); 141 142 MOZ_ASSERT(IsBaselineJitEnabled(cx)); 143 144 *bailoutInfo = nullptr; 145 bool success = 146 BailoutIonToBaseline(cx, bailoutData.activation(), frame, bailoutInfo, 147 /*exceptionInfo=*/nullptr, BailoutReason::Normal); 148 MOZ_ASSERT_IF(success, *bailoutInfo != nullptr); 149 150 if (!success) { 151 MOZ_ASSERT(cx->isExceptionPending()); 152 JSScript* script = frame.script(); 153 probes::ExitScript(cx, script, script->function(), 154 /* popProfilerFrame = */ false); 155 } 156 157 // This condition was wrong when we entered this bailout function, but it 158 // might be true now. A GC might have reclaimed all the Jit code and 159 // invalidated all frames which are currently on the stack. As we are 160 // already in a bailout, we could not switch to an invalidation 161 // bailout. When the code of an IonScript which is on the stack is 162 // invalidated (see InvalidateActivation), we remove references to it and 163 // increment the reference counter for each activation that appear on the 164 // stack. As the bailed frame is one of them, we have to decrement it now. 165 if (frame.ionScript()->invalidated()) { 166 frame.ionScript()->decrementInvalidationCount(cx->gcContext()); 167 } 168 169 // NB: Commentary on how |lastProfilingFrame| is set from bailouts. 170 // 171 // Once we return to jitcode, any following frames might get clobbered, 172 // but the current frame will not (as it will be clobbered "in-place" 173 // with a baseline frame that will share the same frame prefix). 174 // However, there may be multiple baseline frames unpacked from this 175 // single Ion frame, which means we will need to once again reset 176 // |lastProfilingFrame| to point to the correct unpacked last frame 177 // in |FinishBailoutToBaseline|. 178 // 179 // In the case of error, the jitcode will jump immediately to an 180 // exception handler, which will unwind the frames and properly set 181 // the |lastProfilingFrame| to point to the frame being resumed into 182 // (see |AutoResetLastProfilerFrameOnReturnFromException|). 183 // 184 // In both cases, we want to temporarily set the |lastProfilingFrame| 185 // to the current frame being bailed out, and then fix it up later. 186 if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled( 187 cx->runtime())) { 188 cx->jitActivation->setLastProfilingFrame(currentFramePtr); 189 } 190 191 return success; 192 } 193 194 bool jit::InvalidationBailout(InvalidationBailoutStack* sp, 195 BaselineBailoutInfo** bailoutInfo) { 196 sp->checkInvariants(); 197 198 JSContext* cx = TlsContext.get(); 199 200 #ifdef DEBUG 201 // Reset the counter when we bailed after MDebugEnterGCUnsafeRegion, but 202 // before the matching MDebugLeaveGCUnsafeRegion. 203 // 204 // NOTE: EnterJit ensures the counter is zero when we enter JIT code. 205 cx->resetInUnsafeRegion(); 206 #endif 207 208 // We don't have an exit frame. 209 cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT); 210 211 JitActivationIterator jitActivations(cx); 212 BailoutFrameInfo bailoutData(jitActivations, sp); 213 JSJitFrameIter frame(jitActivations->asJit()); 214 JitFrameLayout* currentFramePtr = frame.jsFrame(); 215 216 JitSpew(JitSpew_IonBailouts, "Took invalidation bailout! Snapshot offset: %u", 217 frame.snapshotOffset()); 218 219 MOZ_ASSERT(IsBaselineJitEnabled(cx)); 220 221 *bailoutInfo = nullptr; 222 bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frame, 223 bailoutInfo, /*exceptionInfo=*/nullptr, 224 BailoutReason::Invalidate); 225 MOZ_ASSERT_IF(success, *bailoutInfo != nullptr); 226 227 if (!success) { 228 MOZ_ASSERT(cx->isExceptionPending()); 229 230 // If the bailout failed, then bailout trampoline will pop the 231 // current frame and jump straight to exception handling code when 232 // this function returns. Any Gecko Profiler entry pushed for this 233 // frame will be silently forgotten. 234 // 235 // We call ExitScript here to ensure that if the ionScript had Gecko 236 // Profiler instrumentation, then the entry for it is popped. 237 // 238 // However, if the bailout was during argument check, then a 239 // pseudostack frame would not have been pushed in the first 240 // place, so don't pop anything in that case. 241 JSScript* script = frame.script(); 242 probes::ExitScript(cx, script, script->function(), 243 /* popProfilerFrame = */ false); 244 245 #ifdef JS_JITSPEW 246 JitFrameLayout* layout = frame.jsFrame(); 247 JitSpew(JitSpew_IonInvalidate, "Bailout failed (Fatal Error)"); 248 JitSpew(JitSpew_IonInvalidate, " calleeToken %p", 249 (void*)layout->calleeToken()); 250 JitSpew(JitSpew_IonInvalidate, " callerFramePtr %p", 251 layout->callerFramePtr()); 252 JitSpew(JitSpew_IonInvalidate, " ra %p", (void*)layout->returnAddress()); 253 #endif 254 } 255 256 frame.ionScript()->decrementInvalidationCount(cx->gcContext()); 257 258 // Make the frame being bailed out the top profiled frame. 259 if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled( 260 cx->runtime())) { 261 cx->jitActivation->setLastProfilingFrame(currentFramePtr); 262 } 263 264 return success; 265 } 266 267 bool jit::ExceptionHandlerBailout(JSContext* cx, 268 const InlineFrameIterator& frame, 269 ResumeFromException* rfe, 270 const ExceptionBailoutInfo& excInfo) { 271 // If we are resuming in a finally block, the exception has already 272 // been captured. 273 // We can also be propagating debug mode exceptions without there being an 274 // actual exception pending. For instance, when we return false from an 275 // operation callback like a timeout handler. 276 MOZ_ASSERT_IF( 277 !cx->isExceptionPending(), 278 excInfo.isFinally() || excInfo.propagatingIonExceptionForDebugMode()); 279 280 JS::AutoSaveExceptionState savedExc(cx); 281 282 JitActivation* act = cx->activation()->asJit(); 283 uint8_t* prevExitFP = act->jsExitFP(); 284 auto restoreExitFP = 285 mozilla::MakeScopeExit([&]() { act->setJSExitFP(prevExitFP); }); 286 act->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT); 287 288 gc::AutoSuppressGC suppress(cx); 289 290 JitActivationIterator jitActivations(cx); 291 BailoutFrameInfo bailoutData(jitActivations, frame.frame()); 292 JSJitFrameIter frameView(jitActivations->asJit()); 293 JitFrameLayout* currentFramePtr = frameView.jsFrame(); 294 295 BaselineBailoutInfo* bailoutInfo = nullptr; 296 bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frameView, 297 &bailoutInfo, &excInfo, 298 BailoutReason::ExceptionHandler); 299 if (success) { 300 MOZ_ASSERT(bailoutInfo); 301 302 // Overwrite the kind so HandleException after the bailout returns 303 // false, jumping directly to the exception tail. 304 if (excInfo.propagatingIonExceptionForDebugMode()) { 305 bailoutInfo->bailoutKind = 306 mozilla::Some(BailoutKind::IonExceptionDebugMode); 307 } else if (excInfo.isFinally()) { 308 bailoutInfo->bailoutKind = mozilla::Some(BailoutKind::Finally); 309 } 310 311 rfe->kind = ExceptionResumeKind::Bailout; 312 rfe->stackPointer = bailoutInfo->incomingStack; 313 rfe->bailoutInfo = bailoutInfo; 314 } else { 315 // Drop the exception that triggered the bailout and instead propagate the 316 // failure caused by processing the bailout (eg. OOM). Note that recover 317 // instructions can do an interrupt check and throw an uncatchable 318 // exception. 319 savedExc.drop(); 320 MOZ_ASSERT(!bailoutInfo); 321 MOZ_ASSERT(cx->isExceptionPending() || cx->hadUncatchableException()); 322 } 323 324 // Make the frame being bailed out the top profiled frame. 325 if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled( 326 cx->runtime())) { 327 cx->jitActivation->setLastProfilingFrame(currentFramePtr); 328 } 329 330 return success; 331 } 332 333 // Initialize the NamedLambdaObject and CallObject of the current frame if 334 // needed. 335 bool jit::EnsureHasEnvironmentObjects(JSContext* cx, AbstractFramePtr fp) { 336 // Ion does not compile eval scripts. 337 MOZ_ASSERT(!fp.isEvalFrame()); 338 339 if (fp.isFunctionFrame() && !fp.hasInitialEnvironment() && 340 fp.callee()->needsFunctionEnvironmentObjects()) { 341 if (!fp.initFunctionEnvironmentObjects(cx)) { 342 return false; 343 } 344 } 345 346 return true; 347 } 348 349 void BailoutFrameInfo::attachOnJitActivation( 350 const JitActivationIterator& jitActivations) { 351 MOZ_ASSERT(jitActivations->asJit()->jsExitFP() == FAKE_EXITFP_FOR_BAILOUT); 352 activation_ = jitActivations->asJit(); 353 activation_->setBailoutData(this); 354 } 355 356 BailoutFrameInfo::~BailoutFrameInfo() { activation_->cleanBailoutData(); }