JitActivation.cpp (9922B)
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/JitActivation.h" 8 9 #include "mozilla/Assertions.h" // MOZ_ASSERT, MOZ_RELEASE_ASSERT 10 11 #include <stddef.h> // size_t 12 #include <stdint.h> // uint8_t, uint32_t 13 #include <utility> // std::move 14 15 #include "debugger/DebugAPI.h" // js::DebugAPI 16 #include "jit/Invalidation.h" // js::jit::Invalidate 17 #include "jit/JSJitFrameIter.h" // js::jit::InlineFrameIterator 18 #include "jit/RematerializedFrame.h" // js::jit::RematerializedFrame 19 #include "js/AllocPolicy.h" // js::ReportOutOfMemory 20 #include "vm/EnvironmentObject.h" // js::DebugEnvironments 21 #include "vm/JSContext.h" // JSContext 22 #include "vm/Realm.h" // js::AutoRealmUnchecked 23 #include "wasm/WasmCode.h" // js::wasm::Code 24 #include "wasm/WasmConstants.h" // js::wasm::Trap 25 #include "wasm/WasmFrameIter.h" // js::wasm::{RegisterState,StartUnwinding,UnwindState} 26 #include "wasm/WasmInstance.h" // js::wasm::Instance 27 #include "wasm/WasmProcess.h" // js::wasm::LookupCode 28 29 #include "vm/Realm-inl.h" // js::~AutoRealm 30 31 class JS_PUBLIC_API JSTracer; 32 33 js::jit::JitActivation::JitActivation(JSContext* cx) 34 : Activation(cx, Jit), 35 packedExitFP_(nullptr), 36 encodedWasmExitReason_(0), 37 #ifdef ENABLE_WASM_JSPI 38 wasmExitSuspender_(cx, nullptr), 39 #endif 40 prevJitActivation_(cx->jitActivation), 41 ionRecovery_(cx), 42 bailoutData_(nullptr), 43 lastProfilingFrame_(nullptr), 44 lastProfilingCallSite_(nullptr) { 45 cx->jitActivation = this; 46 registerProfiling(); 47 } 48 49 js::jit::JitActivation::~JitActivation() { 50 if (isProfiling()) { 51 unregisterProfiling(); 52 } 53 cx_->jitActivation = prevJitActivation_; 54 55 // All reocvered value are taken from activation during the bailout. 56 MOZ_ASSERT(ionRecovery_.empty()); 57 58 // The BailoutFrameInfo should have unregistered itself from the 59 // JitActivations. 60 MOZ_ASSERT(!bailoutData_); 61 62 // Traps get handled immediately. 63 MOZ_ASSERT(!isWasmTrapping()); 64 65 // Rematerialized frames must have been removed by either the bailout code or 66 // the exception handler. 67 MOZ_ASSERT_IF(rematerializedFrames_, rematerializedFrames_->empty()); 68 } 69 70 void js::jit::JitActivation::trace(JSTracer* trc) { 71 if (rematerializedFrames_) { 72 for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty(); 73 e.popFront()) { 74 e.front().value().trace(trc); 75 } 76 } 77 78 for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); 79 it++) { 80 it->trace(trc); 81 } 82 } 83 84 void js::jit::JitActivation::setBailoutData( 85 jit::BailoutFrameInfo* bailoutData) { 86 MOZ_ASSERT(!bailoutData_); 87 bailoutData_ = bailoutData; 88 } 89 90 void js::jit::JitActivation::cleanBailoutData() { 91 MOZ_ASSERT(bailoutData_); 92 bailoutData_ = nullptr; 93 } 94 95 void js::jit::JitActivation::removeRematerializedFrame(uint8_t* top) { 96 if (!rematerializedFrames_) { 97 return; 98 } 99 100 if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) { 101 rematerializedFrames_->remove(p); 102 } 103 } 104 105 js::jit::RematerializedFrame* js::jit::JitActivation::getRematerializedFrame( 106 JSContext* cx, const JSJitFrameIter& iter, size_t inlineDepth, 107 IsLeavingFrame leaving) { 108 MOZ_ASSERT(iter.activation() == this); 109 MOZ_ASSERT(iter.isIonScripted()); 110 111 if (!rematerializedFrames_) { 112 rematerializedFrames_ = cx->make_unique<RematerializedFrameTable>(cx); 113 if (!rematerializedFrames_) { 114 return nullptr; 115 } 116 } 117 118 uint8_t* top = iter.fp(); 119 RematerializedFrameTable::AddPtr p = rematerializedFrames_->lookupForAdd(top); 120 if (!p) { 121 RematerializedFrameVector frames(cx); 122 123 // The unit of rematerialization is an uninlined frame and its inlined 124 // frames. Since inlined frames do not exist outside of snapshots, it 125 // is impossible to synchronize their rematerialized copies to 126 // preserve identity. Therefore, we always rematerialize an uninlined 127 // frame and all its inlined frames at once. 128 InlineFrameIterator inlineIter(cx, &iter); 129 130 // We can run recover instructions without invalidating if we're always 131 // leaving the frame. 132 MaybeReadFallback::FallbackConsequence consequence = 133 MaybeReadFallback::Fallback_Invalidate; 134 if (leaving == IsLeavingFrame::Yes) { 135 consequence = MaybeReadFallback::Fallback_DoNothing; 136 } 137 MaybeReadFallback recover(cx, this, &iter, consequence); 138 139 // Frames are often rematerialized with the cx inside a Debugger's 140 // realm. To recover slots and to create CallObjects, we need to 141 // be in the script's realm. 142 AutoRealmUnchecked ar(cx, iter.script()->realm()); 143 144 // The Ion frame must be invalidated to ensure the rematerialized frame will 145 // be removed by the bailout code or the exception handler. If we're always 146 // leaving the frame, the caller is responsible for cleaning up the 147 // rematerialized frame. 148 if (leaving == IsLeavingFrame::No && !iter.checkInvalidation()) { 149 jit::Invalidate(cx, iter.script()); 150 } 151 152 if (!RematerializedFrame::RematerializeInlineFrames(cx, top, inlineIter, 153 recover, frames)) { 154 return nullptr; 155 } 156 157 if (!rematerializedFrames_->add(p, top, std::move(frames))) { 158 ReportOutOfMemory(cx); 159 return nullptr; 160 } 161 162 // See comment in unsetPrevUpToDateUntil. 163 DebugEnvironments::unsetPrevUpToDateUntil(cx, 164 p->value()[inlineDepth].get()); 165 } 166 167 return p->value()[inlineDepth].get(); 168 } 169 170 js::jit::RematerializedFrame* js::jit::JitActivation::lookupRematerializedFrame( 171 uint8_t* top, size_t inlineDepth) { 172 if (!rematerializedFrames_) { 173 return nullptr; 174 } 175 if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) { 176 return inlineDepth < p->value().length() ? p->value()[inlineDepth].get() 177 : nullptr; 178 } 179 return nullptr; 180 } 181 182 void js::jit::JitActivation::removeRematerializedFramesFromDebugger( 183 JSContext* cx, uint8_t* top) { 184 // Ion bailout can fail due to overrecursion and OOM. In such cases we 185 // cannot honor any further Debugger hooks on the frame, and need to 186 // ensure that its Debugger.Frame entry is cleaned up. 187 if (!cx->realm()->isDebuggee() || !rematerializedFrames_) { 188 return; 189 } 190 if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) { 191 for (uint32_t i = 0; i < p->value().length(); i++) { 192 DebugAPI::handleUnrecoverableIonBailoutError(cx, p->value()[i].get()); 193 } 194 rematerializedFrames_->remove(p); 195 } 196 } 197 198 bool js::jit::JitActivation::registerIonFrameRecovery( 199 RInstructionResults&& results) { 200 // Check that there is no entry in the vector yet. 201 MOZ_ASSERT(!maybeIonFrameRecovery(results.frame())); 202 if (!ionRecovery_.append(std::move(results))) { 203 return false; 204 } 205 206 return true; 207 } 208 209 js::jit::RInstructionResults* js::jit::JitActivation::maybeIonFrameRecovery( 210 JitFrameLayout* fp) { 211 for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end(); 212 it++) { 213 if (it->frame() == fp) { 214 return it; 215 } 216 } 217 218 return nullptr; 219 } 220 221 void js::jit::JitActivation::removeIonFrameRecovery(JitFrameLayout* fp) { 222 RInstructionResults* elem = maybeIonFrameRecovery(fp); 223 if (!elem) { 224 return; 225 } 226 227 ionRecovery_.erase(elem); 228 } 229 230 void js::jit::JitActivation::startWasmTrap(wasm::Trap trap, 231 const wasm::TrapSite& trapSite, 232 const wasm::RegisterState& state) { 233 MOZ_ASSERT(!isWasmTrapping()); 234 235 bool unwound; 236 wasm::UnwindState unwindState; 237 MOZ_RELEASE_ASSERT(wasm::StartUnwinding(state, &unwindState, &unwound)); 238 // With return calls, it is possible to not unwind when there is only an 239 // entry left on the stack, e.g. the return call trampoline that is created 240 // to restore realm before returning to the interpreter entry stub. 241 MOZ_ASSERT_IF(unwound, trap == wasm::Trap::IndirectCallBadSig); 242 243 void* pc = unwindState.pc; 244 const wasm::Frame* fp = wasm::Frame::fromUntaggedWasmExitFP(unwindState.fp); 245 246 const wasm::Code& code = wasm::GetNearestEffectiveInstance(fp)->code(); 247 MOZ_RELEASE_ASSERT(&code == wasm::LookupCode(pc)); 248 249 wasm::SuspenderObject* suspender = nullptr; 250 #ifdef ENABLE_WASM_JSPI 251 suspender = cx()->wasm().findSuspenderForStackAddress(fp); 252 #endif 253 setWasmExitFP(fp, suspender); 254 wasmTrapData_.emplace(); 255 wasmTrapData_->resumePC = 256 ((uint8_t*)state.pc) + jit::WasmTrapInstructionLength; 257 wasmTrapData_->unwoundPC = pc; 258 wasmTrapData_->trap = trap; 259 // If the frame was unwound, the source location must be recovered from the 260 // callsite so that it is accurate. 261 if (unwound) { 262 wasm::CallSite site; 263 MOZ_ALWAYS_TRUE(code.lookupCallSite(pc, &site)); 264 wasmTrapData_->trapSite.bytecodeOffset = 265 wasm::BytecodeOffset(site.lineOrBytecode()); 266 wasmTrapData_->trapSite.inlinedCallerOffsets = site.inlinedCallerOffsets(); 267 } else { 268 wasmTrapData_->trapSite = trapSite; 269 } 270 wasmTrapData_->failedUnwindSignatureMismatch = 271 !unwound && trap == wasm::Trap::IndirectCallBadSig; 272 273 MOZ_ASSERT(isWasmTrapping()); 274 } 275 276 void js::jit::JitActivation::finishWasmTrap(bool isResuming) { 277 MOZ_ASSERT(hasWasmExitFP()); 278 MOZ_ASSERT(isWasmTrapping()); 279 wasmTrapData_.reset(); 280 packedExitFP_ = nullptr; 281 // Don't clear the exit suspender if we're resuming, or else the trap stub 282 // won't know that it needs to switch back to the main stack. 283 #ifdef ENABLE_WASM_JSPI 284 if (!isResuming) { 285 wasmExitSuspender_ = nullptr; 286 } 287 #endif 288 MOZ_ASSERT(!isWasmTrapping()); 289 }