Coverage Report

Created: 2018-09-25 14:53

/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
}