/src/mozilla-central/js/src/jit/Bailouts.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 | | * vim: set ts=8 sts=4 et sw=4 tw=99: |
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/ScopeExit.h" |
10 | | |
11 | | #include "jit/BaselineJIT.h" |
12 | | #include "jit/Ion.h" |
13 | | #include "jit/JitRealm.h" |
14 | | #include "jit/JitSpewer.h" |
15 | | #include "jit/Snapshots.h" |
16 | | #include "vm/JSContext.h" |
17 | | #include "vm/TraceLogging.h" |
18 | | |
19 | | #include "jit/JSJitFrameIter-inl.h" |
20 | | #include "vm/Probes-inl.h" |
21 | | #include "vm/Stack-inl.h" |
22 | | |
23 | | using namespace js; |
24 | | using namespace js::jit; |
25 | | |
26 | | using mozilla::IsInRange; |
27 | | |
28 | | uint32_t |
29 | | jit::Bailout(BailoutStack* sp, BaselineBailoutInfo** bailoutInfo) |
30 | 3 | { |
31 | 3 | JSContext* cx = TlsContext.get(); |
32 | 3 | MOZ_ASSERT(bailoutInfo); |
33 | 3 | |
34 | 3 | // We don't have an exit frame. |
35 | 3 | MOZ_ASSERT(IsInRange(FAKE_EXITFP_FOR_BAILOUT, 0, 0x1000) && |
36 | 3 | IsInRange(FAKE_EXITFP_FOR_BAILOUT + sizeof(CommonFrameLayout), 0, 0x1000), |
37 | 3 | "Fake exitfp pointer should be within the first page."); |
38 | 3 | |
39 | 3 | cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT); |
40 | 3 | |
41 | 3 | JitActivationIterator jitActivations(cx); |
42 | 3 | BailoutFrameInfo bailoutData(jitActivations, sp); |
43 | 3 | JSJitFrameIter frame(jitActivations->asJit()); |
44 | 3 | MOZ_ASSERT(!frame.ionScript()->invalidated()); |
45 | 3 | CommonFrameLayout* currentFramePtr = frame.current(); |
46 | 3 | |
47 | 3 | TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx); |
48 | 3 | TraceLogTimestamp(logger, TraceLogger_Bailout); |
49 | 3 | |
50 | 3 | JitSpew(JitSpew_IonBailouts, "Took bailout! Snapshot offset: %d", frame.snapshotOffset()); |
51 | 3 | |
52 | 3 | MOZ_ASSERT(IsBaselineEnabled(cx)); |
53 | 3 | |
54 | 3 | *bailoutInfo = nullptr; |
55 | 3 | uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), frame, false, bailoutInfo, |
56 | 3 | /* excInfo = */ nullptr); |
57 | 3 | MOZ_ASSERT(retval == BAILOUT_RETURN_OK || |
58 | 3 | retval == BAILOUT_RETURN_FATAL_ERROR || |
59 | 3 | retval == BAILOUT_RETURN_OVERRECURSED); |
60 | 3 | MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr); |
61 | 3 | |
62 | 3 | if (retval != BAILOUT_RETURN_OK) { |
63 | 0 | JSScript* script = frame.script(); |
64 | 0 | probes::ExitScript(cx, script, script->functionNonDelazifying(), |
65 | 0 | /* popProfilerFrame = */ false); |
66 | 0 | } |
67 | 3 | |
68 | 3 | // This condition was wrong when we entered this bailout function, but it |
69 | 3 | // might be true now. A GC might have reclaimed all the Jit code and |
70 | 3 | // invalidated all frames which are currently on the stack. As we are |
71 | 3 | // already in a bailout, we could not switch to an invalidation |
72 | 3 | // bailout. When the code of an IonScript which is on the stack is |
73 | 3 | // invalidated (see InvalidateActivation), we remove references to it and |
74 | 3 | // increment the reference counter for each activation that appear on the |
75 | 3 | // stack. As the bailed frame is one of them, we have to decrement it now. |
76 | 3 | if (frame.ionScript()->invalidated()) { |
77 | 0 | frame.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp()); |
78 | 0 | } |
79 | 3 | |
80 | 3 | // NB: Commentary on how |lastProfilingFrame| is set from bailouts. |
81 | 3 | // |
82 | 3 | // Once we return to jitcode, any following frames might get clobbered, |
83 | 3 | // but the current frame will not (as it will be clobbered "in-place" |
84 | 3 | // with a baseline frame that will share the same frame prefix). |
85 | 3 | // However, there may be multiple baseline frames unpacked from this |
86 | 3 | // single Ion frame, which means we will need to once again reset |
87 | 3 | // |lastProfilingFrame| to point to the correct unpacked last frame |
88 | 3 | // in |FinishBailoutToBaseline|. |
89 | 3 | // |
90 | 3 | // In the case of error, the jitcode will jump immediately to an |
91 | 3 | // exception handler, which will unwind the frames and properly set |
92 | 3 | // the |lastProfilingFrame| to point to the frame being resumed into |
93 | 3 | // (see |AutoResetLastProfilerFrameOnReturnFromException|). |
94 | 3 | // |
95 | 3 | // In both cases, we want to temporarily set the |lastProfilingFrame| |
96 | 3 | // to the current frame being bailed out, and then fix it up later. |
97 | 3 | if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) { |
98 | 0 | cx->jitActivation->setLastProfilingFrame(currentFramePtr); |
99 | 0 | } |
100 | 3 | |
101 | 3 | return retval; |
102 | 3 | } |
103 | | |
104 | | uint32_t |
105 | | jit::InvalidationBailout(InvalidationBailoutStack* sp, size_t* frameSizeOut, |
106 | | BaselineBailoutInfo** bailoutInfo) |
107 | 5 | { |
108 | 5 | sp->checkInvariants(); |
109 | 5 | |
110 | 5 | JSContext* cx = TlsContext.get(); |
111 | 5 | |
112 | 5 | // We don't have an exit frame. |
113 | 5 | cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT); |
114 | 5 | |
115 | 5 | JitActivationIterator jitActivations(cx); |
116 | 5 | BailoutFrameInfo bailoutData(jitActivations, sp); |
117 | 5 | JSJitFrameIter frame(jitActivations->asJit()); |
118 | 5 | CommonFrameLayout* currentFramePtr = frame.current(); |
119 | 5 | |
120 | 5 | TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx); |
121 | 5 | TraceLogTimestamp(logger, TraceLogger_Invalidation); |
122 | 5 | |
123 | 5 | JitSpew(JitSpew_IonBailouts, "Took invalidation bailout! Snapshot offset: %d", |
124 | 5 | frame.snapshotOffset()); |
125 | 5 | |
126 | 5 | // Note: the frame size must be computed before we return from this function. |
127 | 5 | *frameSizeOut = frame.frameSize(); |
128 | 5 | |
129 | 5 | MOZ_ASSERT(IsBaselineEnabled(cx)); |
130 | 5 | |
131 | 5 | *bailoutInfo = nullptr; |
132 | 5 | uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), frame, true, bailoutInfo, |
133 | 5 | /* excInfo = */ nullptr); |
134 | 5 | MOZ_ASSERT(retval == BAILOUT_RETURN_OK || |
135 | 5 | retval == BAILOUT_RETURN_FATAL_ERROR || |
136 | 5 | retval == BAILOUT_RETURN_OVERRECURSED); |
137 | 5 | MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr); |
138 | 5 | |
139 | 5 | if (retval != BAILOUT_RETURN_OK) { |
140 | 0 | // If the bailout failed, then bailout trampoline will pop the |
141 | 0 | // current frame and jump straight to exception handling code when |
142 | 0 | // this function returns. Any Gecko Profiler entry pushed for this |
143 | 0 | // frame will be silently forgotten. |
144 | 0 | // |
145 | 0 | // We call ExitScript here to ensure that if the ionScript had Gecko |
146 | 0 | // Profiler instrumentation, then the entry for it is popped. |
147 | 0 | // |
148 | 0 | // However, if the bailout was during argument check, then a |
149 | 0 | // pseudostack frame would not have been pushed in the first |
150 | 0 | // place, so don't pop anything in that case. |
151 | 0 | JSScript* script = frame.script(); |
152 | 0 | probes::ExitScript(cx, script, script->functionNonDelazifying(), |
153 | 0 | /* popProfilerFrame = */ false); |
154 | 0 |
|
155 | | #ifdef JS_JITSPEW |
156 | | JitFrameLayout* layout = frame.jsFrame(); |
157 | | JitSpew(JitSpew_IonInvalidate, "Bailout failed (%s)", |
158 | | (retval == BAILOUT_RETURN_FATAL_ERROR) ? "Fatal Error" : "Over Recursion"); |
159 | | JitSpew(JitSpew_IonInvalidate, " calleeToken %p", (void*) layout->calleeToken()); |
160 | | JitSpew(JitSpew_IonInvalidate, " frameSize %u", unsigned(layout->prevFrameLocalSize())); |
161 | | JitSpew(JitSpew_IonInvalidate, " ra %p", (void*) layout->returnAddress()); |
162 | | #endif |
163 | | } |
164 | 5 | |
165 | 5 | frame.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp()); |
166 | 5 | |
167 | 5 | // Make the frame being bailed out the top profiled frame. |
168 | 5 | if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) { |
169 | 0 | cx->jitActivation->setLastProfilingFrame(currentFramePtr); |
170 | 0 | } |
171 | 5 | |
172 | 5 | return retval; |
173 | 5 | } |
174 | | |
175 | | BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations, |
176 | | const JSJitFrameIter& frame) |
177 | | : machine_(frame.machineState()) |
178 | 0 | { |
179 | 0 | framePointer_ = (uint8_t*) frame.fp(); |
180 | 0 | topFrameSize_ = frame.frameSize(); |
181 | 0 | topIonScript_ = frame.ionScript(); |
182 | 0 | attachOnJitActivation(activations); |
183 | 0 |
|
184 | 0 | const OsiIndex* osiIndex = frame.osiIndex(); |
185 | 0 | snapshotOffset_ = osiIndex->snapshotOffset(); |
186 | 0 | } |
187 | | |
188 | | uint32_t |
189 | | jit::ExceptionHandlerBailout(JSContext* cx, const InlineFrameIterator& frame, |
190 | | ResumeFromException* rfe, |
191 | | const ExceptionBailoutInfo& excInfo, |
192 | | bool* overrecursed) |
193 | 0 | { |
194 | 0 | // We can be propagating debug mode exceptions without there being an |
195 | 0 | // actual exception pending. For instance, when we return false from an |
196 | 0 | // operation callback like a timeout handler. |
197 | 0 | MOZ_ASSERT_IF(!excInfo.propagatingIonExceptionForDebugMode(), cx->isExceptionPending()); |
198 | 0 |
|
199 | 0 | JitActivation* act = cx->activation()->asJit(); |
200 | 0 | uint8_t* prevExitFP = act->jsExitFP(); |
201 | 0 | auto restoreExitFP = mozilla::MakeScopeExit([&]() { act->setJSExitFP(prevExitFP); }); |
202 | 0 | act->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT); |
203 | 0 |
|
204 | 0 | gc::AutoSuppressGC suppress(cx); |
205 | 0 |
|
206 | 0 | JitActivationIterator jitActivations(cx); |
207 | 0 | BailoutFrameInfo bailoutData(jitActivations, frame.frame()); |
208 | 0 | JSJitFrameIter frameView(jitActivations->asJit()); |
209 | 0 | CommonFrameLayout* currentFramePtr = frameView.current(); |
210 | 0 |
|
211 | 0 | BaselineBailoutInfo* bailoutInfo = nullptr; |
212 | 0 | uint32_t retval; |
213 | 0 |
|
214 | 0 | { |
215 | 0 | // Currently we do not tolerate OOM here so as not to complicate the |
216 | 0 | // exception handling code further. |
217 | 0 | AutoEnterOOMUnsafeRegion oomUnsafe; |
218 | 0 |
|
219 | 0 | retval = BailoutIonToBaseline(cx, bailoutData.activation(), frameView, true, |
220 | 0 | &bailoutInfo, &excInfo); |
221 | 0 | if (retval == BAILOUT_RETURN_FATAL_ERROR && cx->isThrowingOutOfMemory()) { |
222 | 0 | oomUnsafe.crash("ExceptionHandlerBailout"); |
223 | 0 | } |
224 | 0 | } |
225 | 0 |
|
226 | 0 | if (retval == BAILOUT_RETURN_OK) { |
227 | 0 | MOZ_ASSERT(bailoutInfo); |
228 | 0 |
|
229 | 0 | // Overwrite the kind so HandleException after the bailout returns |
230 | 0 | // false, jumping directly to the exception tail. |
231 | 0 | if (excInfo.propagatingIonExceptionForDebugMode()) { |
232 | 0 | bailoutInfo->bailoutKind = Bailout_IonExceptionDebugMode; |
233 | 0 | } |
234 | 0 |
|
235 | 0 | rfe->kind = ResumeFromException::RESUME_BAILOUT; |
236 | 0 | rfe->target = cx->runtime()->jitRuntime()->getBailoutTail().value; |
237 | 0 | rfe->bailoutInfo = bailoutInfo; |
238 | 0 | } else { |
239 | 0 | // Bailout failed. If the overrecursion check failed, clear the |
240 | 0 | // exception to turn this into an uncatchable error, continue popping |
241 | 0 | // all inline frames and have the caller report the error. |
242 | 0 | MOZ_ASSERT(!bailoutInfo); |
243 | 0 |
|
244 | 0 | if (retval == BAILOUT_RETURN_OVERRECURSED) { |
245 | 0 | *overrecursed = true; |
246 | 0 | if (!excInfo.propagatingIonExceptionForDebugMode()) { |
247 | 0 | cx->clearPendingException(); |
248 | 0 | } |
249 | 0 | } else { |
250 | 0 | MOZ_ASSERT(retval == BAILOUT_RETURN_FATAL_ERROR); |
251 | 0 |
|
252 | 0 | // Crash for now so as not to complicate the exception handling code |
253 | 0 | // further. |
254 | 0 | MOZ_CRASH(); |
255 | 0 | } |
256 | 0 | } |
257 | 0 |
|
258 | 0 | // Make the frame being bailed out the top profiled frame. |
259 | 0 | if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) { |
260 | 0 | cx->jitActivation->setLastProfilingFrame(currentFramePtr); |
261 | 0 | } |
262 | 0 |
|
263 | 0 | return retval; |
264 | 0 | } |
265 | | |
266 | | // Initialize the decl env Object, call object, and any arguments obj of the |
267 | | // current frame. |
268 | | bool |
269 | | jit::EnsureHasEnvironmentObjects(JSContext* cx, AbstractFramePtr fp) |
270 | 8 | { |
271 | 8 | // Ion does not compile eval scripts. |
272 | 8 | MOZ_ASSERT(!fp.isEvalFrame()); |
273 | 8 | |
274 | 8 | if (fp.isFunctionFrame()) { |
275 | 8 | // Ion does not handle extra var environments due to parameter |
276 | 8 | // expressions yet. |
277 | 8 | MOZ_ASSERT(!fp.callee()->needsExtraBodyVarEnvironment()); |
278 | 8 | |
279 | 8 | if (!fp.hasInitialEnvironment() && fp.callee()->needsFunctionEnvironmentObjects()) { |
280 | 0 | if (!fp.initFunctionEnvironmentObjects(cx)) { |
281 | 0 | return false; |
282 | 0 | } |
283 | 8 | } |
284 | 8 | } |
285 | 8 | |
286 | 8 | return true; |
287 | 8 | } |
288 | | |
289 | | void |
290 | | jit::CheckFrequentBailouts(JSContext* cx, JSScript* script, BailoutKind bailoutKind) |
291 | 8 | { |
292 | 8 | if (script->hasIonScript()) { |
293 | 0 | // Invalidate if this script keeps bailing out without invalidation. Next time |
294 | 0 | // we compile this script LICM will be disabled. |
295 | 0 | IonScript* ionScript = script->ionScript(); |
296 | 0 |
|
297 | 0 | if (ionScript->bailoutExpected()) { |
298 | 0 | // If we bailout because of the first execution of a basic block, |
299 | 0 | // then we should record which basic block we are returning in, |
300 | 0 | // which should prevent this from happening again. Also note that |
301 | 0 | // the first execution bailout can be related to an inlined script, |
302 | 0 | // so there is no need to penalize the caller. |
303 | 0 | if (bailoutKind != Bailout_FirstExecution && !script->hadFrequentBailouts()) { |
304 | 0 | script->setHadFrequentBailouts(); |
305 | 0 | } |
306 | 0 |
|
307 | 0 | JitSpew(JitSpew_IonInvalidate, "Invalidating due to too many bailouts"); |
308 | 0 |
|
309 | 0 | Invalidate(cx, script); |
310 | 0 | } |
311 | 0 | } |
312 | 8 | } |
313 | | |
314 | | void |
315 | | BailoutFrameInfo::attachOnJitActivation(const JitActivationIterator& jitActivations) |
316 | 8 | { |
317 | 8 | MOZ_ASSERT(jitActivations->asJit()->jsExitFP() == FAKE_EXITFP_FOR_BAILOUT); |
318 | 8 | activation_ = jitActivations->asJit(); |
319 | 8 | activation_->setBailoutData(this); |
320 | 8 | } |
321 | | |
322 | | BailoutFrameInfo::~BailoutFrameInfo() |
323 | 8 | { |
324 | 8 | activation_->cleanBailoutData(); |
325 | 8 | } |