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/RematerializedFrame.h"  // js::jit::RematerializedFrame
17 #include "js/AllocPolicy.h"           // js::ReportOutOfMemory
18 #include "vm/EnvironmentObject.h"     // js::DebugEnvironments
19 #include "vm/FrameIter.h"             // js::jit::InlineFrameIterator
20 #include "vm/JSContext.h"             // JSContext
21 #include "vm/Realm.h"                 // js::AutoRealmUnchecked
22 #include "wasm/WasmCode.h"            // js::wasm::Code
23 #include "wasm/WasmConstants.h"       // js::wasm::Trap
24 #include "wasm/WasmFrameIter.h"  // js::wasm::{RegisterState,StartUnwinding,UnwindState}
25 #include "wasm/WasmInstance.h"  // js::wasm::Instance
26 #include "wasm/WasmProcess.h"   // js::wasm::LookupCode
27 #include "wasm/WasmTlsData.h"   // js::wasm::TlsData
28 
29 #include "vm/Realm-inl.h"  // js::~AutoRealm
30 
31 class JS_PUBLIC_API JSTracer;
32 
JitActivation(JSContext * cx)33 js::jit::JitActivation::JitActivation(JSContext* cx)
34     : Activation(cx, Jit),
35       packedExitFP_(nullptr),
36       encodedWasmExitReason_(0),
37       prevJitActivation_(cx->jitActivation),
38       rematerializedFrames_(),
39       ionRecovery_(cx),
40       bailoutData_(nullptr),
41       lastProfilingFrame_(nullptr),
42       lastProfilingCallSite_(nullptr) {
43   cx->jitActivation = this;
44   registerProfiling();
45 }
46 
~JitActivation()47 js::jit::JitActivation::~JitActivation() {
48   if (isProfiling()) {
49     unregisterProfiling();
50   }
51   cx_->jitActivation = prevJitActivation_;
52 
53   // All reocvered value are taken from activation during the bailout.
54   MOZ_ASSERT(ionRecovery_.empty());
55 
56   // The BailoutFrameInfo should have unregistered itself from the
57   // JitActivations.
58   MOZ_ASSERT(!bailoutData_);
59 
60   // Traps get handled immediately.
61   MOZ_ASSERT(!isWasmTrapping());
62 
63   clearRematerializedFrames();
64 }
65 
setBailoutData(jit::BailoutFrameInfo * bailoutData)66 void js::jit::JitActivation::setBailoutData(
67     jit::BailoutFrameInfo* bailoutData) {
68   MOZ_ASSERT(!bailoutData_);
69   bailoutData_ = bailoutData;
70 }
71 
cleanBailoutData()72 void js::jit::JitActivation::cleanBailoutData() {
73   MOZ_ASSERT(bailoutData_);
74   bailoutData_ = nullptr;
75 }
76 
removeRematerializedFrame(uint8_t * top)77 void js::jit::JitActivation::removeRematerializedFrame(uint8_t* top) {
78   if (!rematerializedFrames_) {
79     return;
80   }
81 
82   if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) {
83     rematerializedFrames_->remove(p);
84   }
85 }
86 
clearRematerializedFrames()87 void js::jit::JitActivation::clearRematerializedFrames() {
88   if (!rematerializedFrames_) {
89     return;
90   }
91 
92   for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty();
93        e.popFront()) {
94     e.removeFront();
95   }
96 }
97 
getRematerializedFrame(JSContext * cx,const JSJitFrameIter & iter,size_t inlineDepth)98 js::jit::RematerializedFrame* js::jit::JitActivation::getRematerializedFrame(
99     JSContext* cx, const JSJitFrameIter& iter, size_t inlineDepth) {
100   MOZ_ASSERT(iter.activation() == this);
101   MOZ_ASSERT(iter.isIonScripted());
102 
103   if (!rematerializedFrames_) {
104     rematerializedFrames_ = cx->make_unique<RematerializedFrameTable>(cx);
105     if (!rematerializedFrames_) {
106       return nullptr;
107     }
108   }
109 
110   uint8_t* top = iter.fp();
111   RematerializedFrameTable::AddPtr p = rematerializedFrames_->lookupForAdd(top);
112   if (!p) {
113     RematerializedFrameVector frames(cx);
114 
115     // The unit of rematerialization is an uninlined frame and its inlined
116     // frames. Since inlined frames do not exist outside of snapshots, it
117     // is impossible to synchronize their rematerialized copies to
118     // preserve identity. Therefore, we always rematerialize an uninlined
119     // frame and all its inlined frames at once.
120     InlineFrameIterator inlineIter(cx, &iter);
121     MaybeReadFallback recover(cx, this, &iter);
122 
123     // Frames are often rematerialized with the cx inside a Debugger's
124     // realm. To recover slots and to create CallObjects, we need to
125     // be in the script's realm.
126     AutoRealmUnchecked ar(cx, iter.script()->realm());
127 
128     if (!RematerializedFrame::RematerializeInlineFrames(cx, top, inlineIter,
129                                                         recover, frames)) {
130       return nullptr;
131     }
132 
133     if (!rematerializedFrames_->add(p, top, std::move(frames))) {
134       ReportOutOfMemory(cx);
135       return nullptr;
136     }
137 
138     // See comment in unsetPrevUpToDateUntil.
139     DebugEnvironments::unsetPrevUpToDateUntil(cx,
140                                               p->value()[inlineDepth].get());
141   }
142 
143   return p->value()[inlineDepth].get();
144 }
145 
lookupRematerializedFrame(uint8_t * top,size_t inlineDepth)146 js::jit::RematerializedFrame* js::jit::JitActivation::lookupRematerializedFrame(
147     uint8_t* top, size_t inlineDepth) {
148   if (!rematerializedFrames_) {
149     return nullptr;
150   }
151   if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) {
152     return inlineDepth < p->value().length() ? p->value()[inlineDepth].get()
153                                              : nullptr;
154   }
155   return nullptr;
156 }
157 
removeRematerializedFramesFromDebugger(JSContext * cx,uint8_t * top)158 void js::jit::JitActivation::removeRematerializedFramesFromDebugger(
159     JSContext* cx, uint8_t* top) {
160   // Ion bailout can fail due to overrecursion and OOM. In such cases we
161   // cannot honor any further Debugger hooks on the frame, and need to
162   // ensure that its Debugger.Frame entry is cleaned up.
163   if (!cx->realm()->isDebuggee() || !rematerializedFrames_) {
164     return;
165   }
166   if (RematerializedFrameTable::Ptr p = rematerializedFrames_->lookup(top)) {
167     for (uint32_t i = 0; i < p->value().length(); i++) {
168       DebugAPI::handleUnrecoverableIonBailoutError(cx, p->value()[i].get());
169     }
170     rematerializedFrames_->remove(p);
171   }
172 }
173 
traceRematerializedFrames(JSTracer * trc)174 void js::jit::JitActivation::traceRematerializedFrames(JSTracer* trc) {
175   if (!rematerializedFrames_) {
176     return;
177   }
178   for (RematerializedFrameTable::Enum e(*rematerializedFrames_); !e.empty();
179        e.popFront()) {
180     e.front().value().trace(trc);
181   }
182 }
183 
registerIonFrameRecovery(RInstructionResults && results)184 bool js::jit::JitActivation::registerIonFrameRecovery(
185     RInstructionResults&& results) {
186   // Check that there is no entry in the vector yet.
187   MOZ_ASSERT(!maybeIonFrameRecovery(results.frame()));
188   if (!ionRecovery_.append(std::move(results))) {
189     return false;
190   }
191 
192   return true;
193 }
194 
maybeIonFrameRecovery(JitFrameLayout * fp)195 js::jit::RInstructionResults* js::jit::JitActivation::maybeIonFrameRecovery(
196     JitFrameLayout* fp) {
197   for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end();
198        it++) {
199     if (it->frame() == fp) {
200       return it;
201     }
202   }
203 
204   return nullptr;
205 }
206 
removeIonFrameRecovery(JitFrameLayout * fp)207 void js::jit::JitActivation::removeIonFrameRecovery(JitFrameLayout* fp) {
208   RInstructionResults* elem = maybeIonFrameRecovery(fp);
209   if (!elem) {
210     return;
211   }
212 
213   ionRecovery_.erase(elem);
214 }
215 
traceIonRecovery(JSTracer * trc)216 void js::jit::JitActivation::traceIonRecovery(JSTracer* trc) {
217   for (RInstructionResults* it = ionRecovery_.begin(); it != ionRecovery_.end();
218        it++) {
219     it->trace(trc);
220   }
221 }
222 
startWasmTrap(wasm::Trap trap,uint32_t bytecodeOffset,const wasm::RegisterState & state)223 void js::jit::JitActivation::startWasmTrap(wasm::Trap trap,
224                                            uint32_t bytecodeOffset,
225                                            const wasm::RegisterState& state) {
226   MOZ_ASSERT(!isWasmTrapping());
227 
228   bool unwound;
229   wasm::UnwindState unwindState;
230   MOZ_RELEASE_ASSERT(wasm::StartUnwinding(state, &unwindState, &unwound));
231   MOZ_ASSERT(unwound == (trap == wasm::Trap::IndirectCallBadSig));
232 
233   void* pc = unwindState.pc;
234   const wasm::Frame* fp = wasm::Frame::fromUntaggedWasmExitFP(unwindState.fp);
235 
236   const wasm::Code& code = wasm::GetNearestEffectiveTls(fp)->instance->code();
237   MOZ_RELEASE_ASSERT(&code == wasm::LookupCode(pc));
238 
239   // If the frame was unwound, the bytecodeOffset must be recovered from the
240   // callsite so that it is accurate.
241   if (unwound) {
242     bytecodeOffset = code.lookupCallSite(pc)->lineOrBytecode();
243   }
244 
245   setWasmExitFP(fp);
246   wasmTrapData_.emplace();
247   wasmTrapData_->resumePC =
248       ((uint8_t*)state.pc) + jit::WasmTrapInstructionLength;
249   wasmTrapData_->unwoundPC = pc;
250   wasmTrapData_->trap = trap;
251   wasmTrapData_->bytecodeOffset = bytecodeOffset;
252 
253   MOZ_ASSERT(isWasmTrapping());
254 }
255 
finishWasmTrap()256 void js::jit::JitActivation::finishWasmTrap() {
257   MOZ_ASSERT(isWasmTrapping());
258   packedExitFP_ = nullptr;
259   wasmTrapData_.reset();
260   MOZ_ASSERT(!isWasmTrapping());
261 }
262