summaryrefslogtreecommitdiffstats
path: root/js/src/jit/Bailouts.cpp
blob: d5172c6a3de3ae4e70ab62fc58424b93b80bfe2a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 * vim: set ts=8 sts=4 et sw=4 tw=99:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "jit/Bailouts.h"

#include "mozilla/ScopeExit.h"

#include "jscntxt.h"

#include "jit/BaselineJIT.h"
#include "jit/Ion.h"
#include "jit/JitCompartment.h"
#include "jit/JitSpewer.h"
#include "jit/Snapshots.h"
#include "vm/TraceLogging.h"

#include "jit/JitFrameIterator-inl.h"
#include "vm/Probes-inl.h"
#include "vm/Stack-inl.h"

using namespace js;
using namespace js::jit;

using mozilla::IsInRange;

uint32_t
jit::Bailout(BailoutStack* sp, BaselineBailoutInfo** bailoutInfo)
{
    JSContext* cx = GetJSContextFromMainThread();
    MOZ_ASSERT(bailoutInfo);

    // We don't have an exit frame.
    MOZ_ASSERT(IsInRange(FAKE_JIT_TOP_FOR_BAILOUT, 0, 0x1000) &&
               IsInRange(FAKE_JIT_TOP_FOR_BAILOUT + sizeof(CommonFrameLayout), 0, 0x1000),
               "Fake jitTop pointer should be within the first page.");
    cx->runtime()->jitTop = FAKE_JIT_TOP_FOR_BAILOUT;

    JitActivationIterator jitActivations(cx->runtime());
    BailoutFrameInfo bailoutData(jitActivations, sp);
    JitFrameIterator iter(jitActivations);
    MOZ_ASSERT(!iter.ionScript()->invalidated());
    CommonFrameLayout* currentFramePtr = iter.current();

    TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
    TraceLogTimestamp(logger, TraceLogger_Bailout);

    JitSpew(JitSpew_IonBailouts, "Took bailout! Snapshot offset: %d", iter.snapshotOffset());

    MOZ_ASSERT(IsBaselineEnabled(cx));

    *bailoutInfo = nullptr;
    uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, false, bailoutInfo,
                                           /* excInfo = */ nullptr);
    MOZ_ASSERT(retval == BAILOUT_RETURN_OK ||
               retval == BAILOUT_RETURN_FATAL_ERROR ||
               retval == BAILOUT_RETURN_OVERRECURSED);
    MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr);

    if (retval != BAILOUT_RETURN_OK) {
        JSScript* script = iter.script();
        probes::ExitScript(cx, script, script->functionNonDelazifying(),
                           /* popSPSFrame = */ false);
    }

    // This condition was wrong when we entered this bailout function, but it
    // might be true now. A GC might have reclaimed all the Jit code and
    // invalidated all frames which are currently on the stack. As we are
    // already in a bailout, we could not switch to an invalidation
    // bailout. When the code of an IonScript which is on the stack is
    // invalidated (see InvalidateActivation), we remove references to it and
    // increment the reference counter for each activation that appear on the
    // stack. As the bailed frame is one of them, we have to decrement it now.
    if (iter.ionScript()->invalidated())
        iter.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp());

    // NB: Commentary on how |lastProfilingFrame| is set from bailouts.
    //
    // Once we return to jitcode, any following frames might get clobbered,
    // but the current frame will not (as it will be clobbered "in-place"
    // with a baseline frame that will share the same frame prefix).
    // However, there may be multiple baseline frames unpacked from this
    // single Ion frame, which means we will need to once again reset
    // |lastProfilingFrame| to point to the correct unpacked last frame
    // in |FinishBailoutToBaseline|.
    //
    // In the case of error, the jitcode will jump immediately to an
    // exception handler, which will unwind the frames and properly set
    // the |lastProfilingFrame| to point to the frame being resumed into
    // (see |AutoResetLastProfilerFrameOnReturnFromException|).
    //
    // In both cases, we want to temporarily set the |lastProfilingFrame|
    // to the current frame being bailed out, and then fix it up later.
    if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime()))
        cx->runtime()->jitActivation->setLastProfilingFrame(currentFramePtr);

    return retval;
}

uint32_t
jit::InvalidationBailout(InvalidationBailoutStack* sp, size_t* frameSizeOut,
                         BaselineBailoutInfo** bailoutInfo)
{
    sp->checkInvariants();

    JSContext* cx = GetJSContextFromMainThread();

    // We don't have an exit frame.
    cx->runtime()->jitTop = FAKE_JIT_TOP_FOR_BAILOUT;

    JitActivationIterator jitActivations(cx->runtime());
    BailoutFrameInfo bailoutData(jitActivations, sp);
    JitFrameIterator iter(jitActivations);
    CommonFrameLayout* currentFramePtr = iter.current();

    TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
    TraceLogTimestamp(logger, TraceLogger_Invalidation);

    JitSpew(JitSpew_IonBailouts, "Took invalidation bailout! Snapshot offset: %d", iter.snapshotOffset());

    // Note: the frame size must be computed before we return from this function.
    *frameSizeOut = iter.frameSize();

    MOZ_ASSERT(IsBaselineEnabled(cx));

    *bailoutInfo = nullptr;
    uint32_t retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true, bailoutInfo,
                                           /* excInfo = */ nullptr);
    MOZ_ASSERT(retval == BAILOUT_RETURN_OK ||
               retval == BAILOUT_RETURN_FATAL_ERROR ||
               retval == BAILOUT_RETURN_OVERRECURSED);
    MOZ_ASSERT_IF(retval == BAILOUT_RETURN_OK, *bailoutInfo != nullptr);

    if (retval != BAILOUT_RETURN_OK) {
        // If the bailout failed, then bailout trampoline will pop the
        // current frame and jump straight to exception handling code when
        // this function returns.  Any SPS entry pushed for this frame will
        // be silently forgotten.
        //
        // We call ExitScript here to ensure that if the ionScript had SPS
        // instrumentation, then the SPS entry for it is popped.
        //
        // However, if the bailout was during argument check, then a
        // pseudostack frame would not have been pushed in the first
        // place, so don't pop anything in that case.
        JSScript* script = iter.script();
        probes::ExitScript(cx, script, script->functionNonDelazifying(),
                           /* popSPSFrame = */ false);

#ifdef JS_JITSPEW
        JitFrameLayout* frame = iter.jsFrame();
        JitSpew(JitSpew_IonInvalidate, "Bailout failed (%s)",
                (retval == BAILOUT_RETURN_FATAL_ERROR) ? "Fatal Error" : "Over Recursion");
        JitSpew(JitSpew_IonInvalidate, "   calleeToken %p", (void*) frame->calleeToken());
        JitSpew(JitSpew_IonInvalidate, "   frameSize %u", unsigned(frame->prevFrameLocalSize()));
        JitSpew(JitSpew_IonInvalidate, "   ra %p", (void*) frame->returnAddress());
#endif
    }

    iter.ionScript()->decrementInvalidationCount(cx->runtime()->defaultFreeOp());

    // Make the frame being bailed out the top profiled frame.
    if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime()))
        cx->runtime()->jitActivation->setLastProfilingFrame(currentFramePtr);

    return retval;
}

BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
                                   const JitFrameIterator& frame)
  : machine_(frame.machineState())
{
    framePointer_ = (uint8_t*) frame.fp();
    topFrameSize_ = frame.frameSize();
    topIonScript_ = frame.ionScript();
    attachOnJitActivation(activations);

    const OsiIndex* osiIndex = frame.osiIndex();
    snapshotOffset_ = osiIndex->snapshotOffset();
}

uint32_t
jit::ExceptionHandlerBailout(JSContext* cx, const InlineFrameIterator& frame,
                             ResumeFromException* rfe,
                             const ExceptionBailoutInfo& excInfo,
                             bool* overrecursed)
{
    // We can be propagating debug mode exceptions without there being an
    // actual exception pending. For instance, when we return false from an
    // operation callback like a timeout handler.
    MOZ_ASSERT_IF(!excInfo.propagatingIonExceptionForDebugMode(), cx->isExceptionPending());

    uint8_t* prevJitTop = cx->runtime()->jitTop;
    auto restoreJitTop = mozilla::MakeScopeExit([&]() { cx->runtime()->jitTop = prevJitTop; });
    cx->runtime()->jitTop = FAKE_JIT_TOP_FOR_BAILOUT;

    gc::AutoSuppressGC suppress(cx);

    JitActivationIterator jitActivations(cx->runtime());
    BailoutFrameInfo bailoutData(jitActivations, frame.frame());
    JitFrameIterator iter(jitActivations);
    CommonFrameLayout* currentFramePtr = iter.current();

    BaselineBailoutInfo* bailoutInfo = nullptr;
    uint32_t retval;

    {
        // Currently we do not tolerate OOM here so as not to complicate the
        // exception handling code further.
        AutoEnterOOMUnsafeRegion oomUnsafe;

        retval = BailoutIonToBaseline(cx, bailoutData.activation(), iter, true,
                                      &bailoutInfo, &excInfo);
        if (retval == BAILOUT_RETURN_FATAL_ERROR && cx->isThrowingOutOfMemory())
            oomUnsafe.crash("ExceptionHandlerBailout");
    }

    if (retval == BAILOUT_RETURN_OK) {
        MOZ_ASSERT(bailoutInfo);

        // Overwrite the kind so HandleException after the bailout returns
        // false, jumping directly to the exception tail.
        if (excInfo.propagatingIonExceptionForDebugMode())
            bailoutInfo->bailoutKind = Bailout_IonExceptionDebugMode;

        rfe->kind = ResumeFromException::RESUME_BAILOUT;
        rfe->target = cx->runtime()->jitRuntime()->getBailoutTail()->raw();
        rfe->bailoutInfo = bailoutInfo;
    } else {
        // Bailout failed. If the overrecursion check failed, clear the
        // exception to turn this into an uncatchable error, continue popping
        // all inline frames and have the caller report the error.
        MOZ_ASSERT(!bailoutInfo);

        if (retval == BAILOUT_RETURN_OVERRECURSED) {
            *overrecursed = true;
            if (!excInfo.propagatingIonExceptionForDebugMode())
                cx->clearPendingException();
        } else {
            MOZ_ASSERT(retval == BAILOUT_RETURN_FATAL_ERROR);

            // Crash for now so as not to complicate the exception handling code
            // further.
            MOZ_CRASH();
        }
    }

    // Make the frame being bailed out the top profiled frame.
    if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime()))
        cx->runtime()->jitActivation->setLastProfilingFrame(currentFramePtr);

    return retval;
}

// Initialize the decl env Object, call object, and any arguments obj of the
// current frame.
bool
jit::EnsureHasEnvironmentObjects(JSContext* cx, AbstractFramePtr fp)
{
    // Ion does not compile eval scripts.
    MOZ_ASSERT(!fp.isEvalFrame());

    if (fp.isFunctionFrame()) {
        // Ion does not handle extra var environments due to parameter
        // expressions yet.
        MOZ_ASSERT(!fp.callee()->needsExtraBodyVarEnvironment());

        if (!fp.hasInitialEnvironment() && fp.callee()->needsFunctionEnvironmentObjects()) {
            if (!fp.initFunctionEnvironmentObjects(cx))
                return false;
        }
    }

    return true;
}

void
jit::CheckFrequentBailouts(JSContext* cx, JSScript* script, BailoutKind bailoutKind)
{
    if (script->hasIonScript()) {
        // Invalidate if this script keeps bailing out without invalidation. Next time
        // we compile this script LICM will be disabled.
        IonScript* ionScript = script->ionScript();

        if (ionScript->bailoutExpected()) {
            // If we bailout because of the first execution of a basic block,
            // then we should record which basic block we are returning in,
            // which should prevent this from happening again.  Also note that
            // the first execution bailout can be related to an inlined script,
            // so there is no need to penalize the caller.
            if (bailoutKind != Bailout_FirstExecution && !script->hadFrequentBailouts())
                script->setHadFrequentBailouts();

            JitSpew(JitSpew_IonInvalidate, "Invalidating due to too many bailouts");

            Invalidate(cx, script);
        }
    }
}

void
BailoutFrameInfo::attachOnJitActivation(const JitActivationIterator& jitActivations)
{
    MOZ_ASSERT(jitActivations.jitTop() == FAKE_JIT_TOP_FOR_BAILOUT);
    activation_ = jitActivations->asJit();
    activation_->setBailoutData(this);
}

BailoutFrameInfo::~BailoutFrameInfo()
{
    activation_->cleanBailoutData();
}