summaryrefslogtreecommitdiffstats
path: root/js/src/jit/Bailouts.cpp
blob: 160ad3dcb3efd8523265ab00bcaf806090c655a9 (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
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * 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/ArrayUtils.h"
#include "mozilla/ScopeExit.h"

#include "jit/BaselineJIT.h"
#include "jit/Invalidation.h"
#include "jit/Ion.h"
#include "jit/JitFrames.h"
#include "jit/JitRuntime.h"
#include "jit/JitSpewer.h"
#include "jit/JSJitFrameIter.h"
#include "jit/SafepointIndex.h"
#include "jit/Snapshots.h"
#include "vm/JSContext.h"
#include "vm/Stack.h"
#include "vm/TraceLogging.h"

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

using namespace js;
using namespace js::jit;

using mozilla::IsInRange;

// This address is a magic number made to cause crashes while indicating that we
// are making an attempt to mark the stack during a bailout.
static constexpr uint32_t FAKE_EXITFP_FOR_BAILOUT_ADDR = 0xba2;
static uint8_t* const FAKE_EXITFP_FOR_BAILOUT =
    reinterpret_cast<uint8_t*>(FAKE_EXITFP_FOR_BAILOUT_ADDR);

static_assert(!(FAKE_EXITFP_FOR_BAILOUT_ADDR & wasm::ExitOrJitEntryFPTag),
              "FAKE_EXITFP_FOR_BAILOUT could be mistaken as a low-bit tagged "
              "wasm exit fp");

bool jit::Bailout(BailoutStack* sp, BaselineBailoutInfo** bailoutInfo) {
  JSContext* cx = TlsContext.get();
  MOZ_ASSERT(bailoutInfo);

  // We don't have an exit frame.
  MOZ_ASSERT(IsInRange(FAKE_EXITFP_FOR_BAILOUT, 0, 0x1000) &&
                 IsInRange(FAKE_EXITFP_FOR_BAILOUT + sizeof(CommonFrameLayout),
                           0, 0x1000),
             "Fake exitfp pointer should be within the first page.");

  cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT);

  JitActivationIterator jitActivations(cx);
  BailoutFrameInfo bailoutData(jitActivations, sp);
  JSJitFrameIter frame(jitActivations->asJit());
  MOZ_ASSERT(!frame.ionScript()->invalidated());
  CommonFrameLayout* currentFramePtr = frame.current();

  TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
  TraceLogTimestamp(logger, TraceLogger_Bailout);

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

  MOZ_ASSERT(IsBaselineJitEnabled(cx));

  *bailoutInfo = nullptr;
  bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frame,
                                      bailoutInfo, /*exceptionInfo=*/nullptr);
  MOZ_ASSERT_IF(success, *bailoutInfo != nullptr);

  if (!success) {
    MOZ_ASSERT(cx->isExceptionPending());
    JSScript* script = frame.script();
    probes::ExitScript(cx, script, script->function(),
                       /* popProfilerFrame = */ 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 (frame.ionScript()->invalidated()) {
    frame.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->jitActivation->setLastProfilingFrame(currentFramePtr);
  }

  return success;
}

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

  JSContext* cx = TlsContext.get();

  // We don't have an exit frame.
  cx->activation()->asJit()->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT);

  JitActivationIterator jitActivations(cx);
  BailoutFrameInfo bailoutData(jitActivations, sp);
  JSJitFrameIter frame(jitActivations->asJit());
  CommonFrameLayout* currentFramePtr = frame.current();

  TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx);
  TraceLogTimestamp(logger, TraceLogger_Invalidation);

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

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

  MOZ_ASSERT(IsBaselineJitEnabled(cx));

  *bailoutInfo = nullptr;
  bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frame,
                                      bailoutInfo, /*exceptionInfo=*/nullptr);
  MOZ_ASSERT_IF(success, *bailoutInfo != nullptr);

  if (success) {
    // Update the bailout kind.
    (*bailoutInfo)->bailoutKind =
        mozilla::Some(BailoutKind::OnStackInvalidation);
  } else {
    MOZ_ASSERT(cx->isExceptionPending());

    // If the bailout failed, then bailout trampoline will pop the
    // current frame and jump straight to exception handling code when
    // this function returns.  Any Gecko Profiler entry pushed for this
    // frame will be silently forgotten.
    //
    // We call ExitScript here to ensure that if the ionScript had Gecko
    // Profiler instrumentation, then the 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 = frame.script();
    probes::ExitScript(cx, script, script->function(),
                       /* popProfilerFrame = */ false);

#ifdef JS_JITSPEW
    JitFrameLayout* layout = frame.jsFrame();
    JitSpew(JitSpew_IonInvalidate, "Bailout failed (Fatal Error)");
    JitSpew(JitSpew_IonInvalidate, "   calleeToken %p",
            (void*)layout->calleeToken());
    JitSpew(JitSpew_IonInvalidate, "   frameSize %u",
            unsigned(layout->prevFrameLocalSize()));
    JitSpew(JitSpew_IonInvalidate, "   ra %p", (void*)layout->returnAddress());
#endif
  }

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

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

  return success;
}

BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations,
                                   const JSJitFrameIter& 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();
}

bool jit::ExceptionHandlerBailout(JSContext* cx,
                                  const InlineFrameIterator& frame,
                                  ResumeFromException* rfe,
                                  const ExceptionBailoutInfo& excInfo) {
  // 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());

  JitActivation* act = cx->activation()->asJit();
  uint8_t* prevExitFP = act->jsExitFP();
  auto restoreExitFP =
      mozilla::MakeScopeExit([&]() { act->setJSExitFP(prevExitFP); });
  act->setJSExitFP(FAKE_EXITFP_FOR_BAILOUT);

  gc::AutoSuppressGC suppress(cx);

  JitActivationIterator jitActivations(cx);
  BailoutFrameInfo bailoutData(jitActivations, frame.frame());
  JSJitFrameIter frameView(jitActivations->asJit());
  CommonFrameLayout* currentFramePtr = frameView.current();

  BaselineBailoutInfo* bailoutInfo = nullptr;
  bool success = BailoutIonToBaseline(cx, bailoutData.activation(), frameView,
                                      &bailoutInfo, &excInfo);
  if (success) {
    MOZ_ASSERT(bailoutInfo);

    // Overwrite the kind so HandleException after the bailout returns
    // false, jumping directly to the exception tail.
    if (excInfo.propagatingIonExceptionForDebugMode()) {
      bailoutInfo->bailoutKind =
          mozilla::Some(BailoutKind::IonExceptionDebugMode);
    }

    rfe->kind = ResumeFromException::RESUME_BAILOUT;
    rfe->target = cx->runtime()->jitRuntime()->getBailoutTail().value;
    rfe->bailoutInfo = bailoutInfo;
  } else {
    MOZ_ASSERT(!bailoutInfo);
    MOZ_ASSERT(cx->isExceptionPending());
  }

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

  return success;
}

// 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 BailoutFrameInfo::attachOnJitActivation(
    const JitActivationIterator& jitActivations) {
  MOZ_ASSERT(jitActivations->asJit()->jsExitFP() == FAKE_EXITFP_FOR_BAILOUT);
  activation_ = jitActivations->asJit();
  activation_->setBailoutData(this);
}

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