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