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
/* -*- 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/. */
#ifndef jit_Bailouts_h
#define jit_Bailouts_h
#include "jstypes.h"
#include "jit/JitFrames.h"
#include "jit/JSJitFrameIter.h"
#include "vm/Stack.h"
namespace js {
namespace jit {
// [SMDOC] IonMonkey Bailouts
//
// A "bailout" is the process of recovering a baseline frame from an IonFrame.
// Bailouts are implemented in js::jit::BailoutIonToBaseline, which has the
// following callers:
//
// * js::jit::Bailout - This is used when a guard fails in the Ion code
// itself; for example, an LGuardShape fails or an LAddI overflows. See
// callers of CodeGenerator::bailoutFrom() for more examples.
//
// * js::jit::ExceptionHandlerBailout - Something called from Ion code
// failed. Ion doesn't implement `catch`; it handles all exceptions by
// bailing out.
//
// * js::jit::InvalidationBailout - We returned to Ion code that was
// invalidated while it was on the stack. See "OSI" below. Ion code can be
// invalidated for several reasons: when GC evicts Ion code to save memory,
// for example, or when assumptions baked into the jitted code are
// invalidated by the VM (see callers of IonBuilder::constraints()).
//
// (Some stack inspection can be done without bailing out, including GC stack
// marking, Error object construction, and Gecko profiler sampling.)
//
// Consider the first case. When an Ion guard fails, we can't continue in
// Ion. There's no IC fallback case coming to save us; we've got a broken
// assumption baked into the code we're running. So we jump to an out-of-line
// code path that's responsible for abandoning Ion execution and resuming in
// baseline: the bailout path.
//
// We were in the midst of optimized Ion code, so bits of program state may be
// in registers or spilled to the native stack; values may be unboxed; some
// objects may have been optimized away; thanks to inlining, whole call frames
// may be missing. The bailout path must put all these pieces back together
// into the structure the baseline code expects.
//
// The data structure that makes this possible is called a *snapshot*.
// Snapshots are created during Ion codegen and associated with the IonScript;
// they tell how to recover each value in a BaselineFrame from the current
// machine state at a given point in the Ion JIT code. This is potentially
// different at every place in an Ion script where we might bail out. (See
// Snapshots.h.)
//
// The bailout path performs roughly the following steps:
//
// 1. Push a snapshot index and the frame size to the native stack.
// 2. Spill all registers.
// 3. Call js::jit::Bailout to reconstruct the baseline frame(s).
// 4. memmove() those to the right place on the native stack.
// 5. Jump to baseline code.
//
// (This last step requires baseline JIT code to have an entry point at each pc
// where an eventual Ion guard may be inserted.)
//
// When C++ code invalidates Ion code, we do on-stack invalidation, or OSI, to
// arrange for every affected Ion frame on the stack to bail out as soon as
// control returns to it. OSI patches every instruction in the JIT code that's
// at a return address currently on the stack. See InvalidateActivation.
//
//
// ## Bailout path implementation details
//
// Ion code has a lot of guards, so each bailout path must be small. Steps 2
// and 3 above are therefore implemented by a shared per-Runtime trampoline,
// rt->jitRuntime()->getGenericBailoutHandler().
//
// Naively, we could implement step 1 like:
//
// _bailout_ID_1:
// push 1
// jmp _deopt
// _bailout_ID_2:
// push 2
// jmp _deopt
// ...
// _deopt:
// push imm(FrameSize)
// call _global_bailout_handler
//
// This takes about 10 extra bytes per guard. On some platforms, we can reduce
// this overhead to 4 bytes by creating a global jump table, shared again in
// the compartment:
//
// call _global_bailout_handler
// call _global_bailout_handler
// call _global_bailout_handler
// call _global_bailout_handler
// ...
// _global_bailout_handler:
//
// In the bailout handler, we can recompute which entry in the table was
// selected by subtracting the return addressed pushed by the call, from the
// start of the table, and then dividing by the size of a (call X) entry in the
// table. This gives us a number in [0, TableSize), which we call a
// "BailoutId".
//
// Then, we can provide a per-script mapping from BailoutIds to snapshots,
// which takes only four bytes per entry.
//
// This strategy does not work as given, because the bailout handler has no way
// to compute the location of an IonScript. Currently, we do not use frame
// pointers. To account for this we segregate frames into a limited set of
// "frame sizes", and create a table for each frame size. We also have the
// option of not using bailout tables, for platforms or situations where the
// 10 byte cost is more optimal than a bailout table. See JitFrames.h for more
// detail.
static const BailoutId INVALID_BAILOUT_ID = BailoutId(-1);
// Keep this arbitrarily small for now, for testing.
static const uint32_t BAILOUT_TABLE_SIZE = 16;
// 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 const 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");
// BailoutStack is an architecture specific pointer to the stack, given by the
// bailout handler.
class BailoutStack;
class InvalidationBailoutStack;
// Must be implemented by each architecture.
// This structure is constructed before recovering the baseline frames for a
// bailout. It records all information extracted from the stack, and which are
// needed for the JSJitFrameIter.
class BailoutFrameInfo {
MachineState machine_;
uint8_t* framePointer_;
size_t topFrameSize_;
IonScript* topIonScript_;
uint32_t snapshotOffset_;
JitActivation* activation_;
void attachOnJitActivation(const JitActivationIterator& activations);
public:
BailoutFrameInfo(const JitActivationIterator& activations, BailoutStack* sp);
BailoutFrameInfo(const JitActivationIterator& activations,
InvalidationBailoutStack* sp);
BailoutFrameInfo(const JitActivationIterator& activations,
const JSJitFrameIter& frame);
~BailoutFrameInfo();
uint8_t* fp() const { return framePointer_; }
SnapshotOffset snapshotOffset() const { return snapshotOffset_; }
const MachineState* machineState() const { return &machine_; }
size_t topFrameSize() const { return topFrameSize_; }
IonScript* ionScript() const { return topIonScript_; }
JitActivation* activation() const { return activation_; }
};
MOZ_MUST_USE bool EnsureHasEnvironmentObjects(JSContext* cx,
AbstractFramePtr fp);
struct BaselineBailoutInfo;
// Called from a bailout thunk.
MOZ_MUST_USE bool Bailout(BailoutStack* sp, BaselineBailoutInfo** info);
// Called from the invalidation thunk.
MOZ_MUST_USE bool InvalidationBailout(InvalidationBailoutStack* sp,
size_t* frameSizeOut,
BaselineBailoutInfo** info);
class ExceptionBailoutInfo {
size_t frameNo_;
jsbytecode* resumePC_;
size_t numExprSlots_;
public:
ExceptionBailoutInfo(size_t frameNo, jsbytecode* resumePC,
size_t numExprSlots)
: frameNo_(frameNo), resumePC_(resumePC), numExprSlots_(numExprSlots) {}
ExceptionBailoutInfo() : frameNo_(0), resumePC_(nullptr), numExprSlots_(0) {}
bool catchingException() const { return !!resumePC_; }
bool propagatingIonExceptionForDebugMode() const { return !resumePC_; }
size_t frameNo() const {
MOZ_ASSERT(catchingException());
return frameNo_;
}
jsbytecode* resumePC() const {
MOZ_ASSERT(catchingException());
return resumePC_;
}
size_t numExprSlots() const {
MOZ_ASSERT(catchingException());
return numExprSlots_;
}
};
// Called from the exception handler to enter a catch or finally block.
MOZ_MUST_USE bool ExceptionHandlerBailout(JSContext* cx,
const InlineFrameIterator& frame,
ResumeFromException* rfe,
const ExceptionBailoutInfo& excInfo);
MOZ_MUST_USE bool FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo);
void CheckFrequentBailouts(JSContext* cx, JSScript* script,
BailoutKind bailoutKind);
} // namespace jit
} // namespace js
#endif /* jit_Bailouts_h */