/src/mozilla-central/js/src/jit/BaselineBailouts.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 "mozilla/ScopeExit.h" |
8 | | |
9 | | #include "jsutil.h" |
10 | | |
11 | | #include "jit/arm/Simulator-arm.h" |
12 | | #include "jit/BaselineFrame.h" |
13 | | #include "jit/BaselineIC.h" |
14 | | #include "jit/BaselineJIT.h" |
15 | | #include "jit/CompileInfo.h" |
16 | | #include "jit/JitSpewer.h" |
17 | | #include "jit/mips32/Simulator-mips32.h" |
18 | | #include "jit/mips64/Simulator-mips64.h" |
19 | | #include "jit/Recover.h" |
20 | | #include "jit/RematerializedFrame.h" |
21 | | #include "js/Utility.h" |
22 | | #include "vm/ArgumentsObject.h" |
23 | | #include "vm/Debugger.h" |
24 | | #include "vm/TraceLogging.h" |
25 | | |
26 | | #include "jit/JitFrames-inl.h" |
27 | | #include "vm/JSScript-inl.h" |
28 | | |
29 | | using namespace js; |
30 | | using namespace js::jit; |
31 | | |
32 | | // BaselineStackBuilder may reallocate its buffer if the current one is too |
33 | | // small. To avoid dangling pointers, BufferPointer represents a pointer into |
34 | | // this buffer as a pointer to the header and a fixed offset. |
35 | | template <typename T> |
36 | | class BufferPointer |
37 | | { |
38 | | BaselineBailoutInfo** header_; |
39 | | size_t offset_; |
40 | | bool heap_; |
41 | | |
42 | | public: |
43 | | BufferPointer(BaselineBailoutInfo** header, size_t offset, bool heap) |
44 | | : header_(header), offset_(offset), heap_(heap) |
45 | 24 | { } BufferPointer<js::jit::JitFrameLayout>::BufferPointer(js::jit::BaselineBailoutInfo**, unsigned long, bool) Line | Count | Source | 45 | 8 | { } |
Unexecuted instantiation: BufferPointer<js::jit::RectifierFrameLayout>::BufferPointer(js::jit::BaselineBailoutInfo**, unsigned long, bool) BufferPointer<js::jit::BaselineFrame>::BufferPointer(js::jit::BaselineBailoutInfo**, unsigned long, bool) Line | Count | Source | 45 | 8 | { } |
BufferPointer<JS::Value>::BufferPointer(js::jit::BaselineBailoutInfo**, unsigned long, bool) Line | Count | Source | 45 | 8 | { } |
Unexecuted instantiation: BufferPointer<unsigned char>::BufferPointer(js::jit::BaselineBailoutInfo**, unsigned long, bool) |
46 | | |
47 | 58 | T* get() const { |
48 | 58 | BaselineBailoutInfo* header = *header_; |
49 | 58 | if (!heap_) { |
50 | 16 | return (T*)(header->incomingStack + offset_); |
51 | 16 | } |
52 | 42 | |
53 | 42 | uint8_t* p = header->copyStackTop - offset_; |
54 | 42 | MOZ_ASSERT(p >= header->copyStackBottom && p < header->copyStackTop); |
55 | 42 | return (T*)p; |
56 | 42 | } BufferPointer<js::jit::JitFrameLayout>::get() const Line | Count | Source | 47 | 8 | T* get() const { | 48 | 8 | BaselineBailoutInfo* header = *header_; | 49 | 8 | if (!heap_) { | 50 | 8 | return (T*)(header->incomingStack + offset_); | 51 | 8 | } | 52 | 0 | | 53 | 0 | uint8_t* p = header->copyStackTop - offset_; | 54 | 0 | MOZ_ASSERT(p >= header->copyStackBottom && p < header->copyStackTop); | 55 | 0 | return (T*)p; | 56 | 0 | } |
Unexecuted instantiation: BufferPointer<js::jit::RectifierFrameLayout>::get() const BufferPointer<js::jit::BaselineFrame>::get() const Line | Count | Source | 47 | 42 | T* get() const { | 48 | 42 | BaselineBailoutInfo* header = *header_; | 49 | 42 | if (!heap_) { | 50 | 0 | return (T*)(header->incomingStack + offset_); | 51 | 0 | } | 52 | 42 | | 53 | 42 | uint8_t* p = header->copyStackTop - offset_; | 54 | 42 | MOZ_ASSERT(p >= header->copyStackBottom && p < header->copyStackTop); | 55 | 42 | return (T*)p; | 56 | 42 | } |
BufferPointer<JS::Value>::get() const Line | Count | Source | 47 | 8 | T* get() const { | 48 | 8 | BaselineBailoutInfo* header = *header_; | 49 | 8 | if (!heap_) { | 50 | 8 | return (T*)(header->incomingStack + offset_); | 51 | 8 | } | 52 | 0 | | 53 | 0 | uint8_t* p = header->copyStackTop - offset_; | 54 | 0 | MOZ_ASSERT(p >= header->copyStackBottom && p < header->copyStackTop); | 55 | 0 | return (T*)p; | 56 | 0 | } |
Unexecuted instantiation: BufferPointer<unsigned char>::get() const |
57 | | |
58 | 8 | void set(const T& value) { |
59 | 8 | *get() = value; |
60 | 8 | } |
61 | | |
62 | | // Note: we return a copy instead of a reference, to avoid potential memory |
63 | | // safety hazards when the underlying buffer gets resized. |
64 | 0 | const T operator*() const { return *get(); } |
65 | 50 | T* operator->() const { return get(); } BufferPointer<js::jit::JitFrameLayout>::operator->() const Line | Count | Source | 65 | 8 | T* operator->() const { return get(); } |
Unexecuted instantiation: BufferPointer<js::jit::RectifierFrameLayout>::operator->() const BufferPointer<js::jit::BaselineFrame>::operator->() const Line | Count | Source | 65 | 42 | T* operator->() const { return get(); } |
|
66 | | }; |
67 | | |
68 | | /** |
69 | | * BaselineStackBuilder helps abstract the process of rebuilding the C stack on the heap. |
70 | | * It takes a bailout iterator and keeps track of the point on the C stack from which |
71 | | * the reconstructed frames will be written. |
72 | | * |
73 | | * It exposes methods to write data into the heap memory storing the reconstructed |
74 | | * stack. It also exposes method to easily calculate addresses. This includes both the |
75 | | * virtual address that a particular value will be at when it's eventually copied onto |
76 | | * the stack, as well as the current actual address of that value (whether on the heap |
77 | | * allocated portion being constructed or the existing stack). |
78 | | * |
79 | | * The abstraction handles transparent re-allocation of the heap memory when it |
80 | | * needs to be enlarged to accommodate new data. Similarly to the C stack, the |
81 | | * data that's written to the reconstructed stack grows from high to low in memory. |
82 | | * |
83 | | * The lowest region of the allocated memory contains a BaselineBailoutInfo structure that |
84 | | * points to the start and end of the written data. |
85 | | */ |
86 | | struct BaselineStackBuilder |
87 | | { |
88 | | const JSJitFrameIter& iter_; |
89 | | JitFrameLayout* frame_; |
90 | | |
91 | 8 | static size_t HeaderSize() { |
92 | 8 | return AlignBytes(sizeof(BaselineBailoutInfo), sizeof(void*)); |
93 | 8 | } |
94 | | size_t bufferTotal_; |
95 | | size_t bufferAvail_; |
96 | | size_t bufferUsed_; |
97 | | uint8_t* buffer_; |
98 | | BaselineBailoutInfo* header_; |
99 | | |
100 | | size_t framePushed_; |
101 | | |
102 | | BaselineStackBuilder(const JSJitFrameIter& iter, size_t initialSize) |
103 | | : iter_(iter), |
104 | | frame_(static_cast<JitFrameLayout*>(iter.current())), |
105 | | bufferTotal_(initialSize), |
106 | | bufferAvail_(0), |
107 | | bufferUsed_(0), |
108 | | buffer_(nullptr), |
109 | | header_(nullptr), |
110 | | framePushed_(0) |
111 | 8 | { |
112 | 8 | MOZ_ASSERT(bufferTotal_ >= HeaderSize()); |
113 | 8 | MOZ_ASSERT(iter.isBailoutJS()); |
114 | 8 | } |
115 | | |
116 | 8 | ~BaselineStackBuilder() { |
117 | 8 | js_free(buffer_); |
118 | 8 | } |
119 | | |
120 | 8 | MOZ_MUST_USE bool init() { |
121 | 8 | MOZ_ASSERT(!buffer_); |
122 | 8 | MOZ_ASSERT(bufferUsed_ == 0); |
123 | 8 | buffer_ = js_pod_calloc<uint8_t>(bufferTotal_); |
124 | 8 | if (!buffer_) { |
125 | 0 | return false; |
126 | 0 | } |
127 | 8 | bufferAvail_ = bufferTotal_ - HeaderSize(); |
128 | 8 | bufferUsed_ = 0; |
129 | 8 | |
130 | 8 | header_ = reinterpret_cast<BaselineBailoutInfo*>(buffer_); |
131 | 8 | header_->incomingStack = reinterpret_cast<uint8_t*>(frame_); |
132 | 8 | header_->copyStackTop = buffer_ + bufferTotal_; |
133 | 8 | header_->copyStackBottom = header_->copyStackTop; |
134 | 8 | header_->setR0 = 0; |
135 | 8 | header_->valueR0 = UndefinedValue(); |
136 | 8 | header_->setR1 = 0; |
137 | 8 | header_->valueR1 = UndefinedValue(); |
138 | 8 | header_->resumeFramePtr = nullptr; |
139 | 8 | header_->resumeAddr = nullptr; |
140 | 8 | header_->resumePC = nullptr; |
141 | 8 | header_->tryPC = nullptr; |
142 | 8 | header_->faultPC = nullptr; |
143 | 8 | header_->monitorStub = nullptr; |
144 | 8 | header_->numFrames = 0; |
145 | 8 | header_->checkGlobalDeclarationConflicts = false; |
146 | 8 | return true; |
147 | 8 | } |
148 | | |
149 | 0 | MOZ_MUST_USE bool enlarge() { |
150 | 0 | MOZ_ASSERT(buffer_ != nullptr); |
151 | 0 | if (bufferTotal_ & mozilla::tl::MulOverflowMask<2>::value) { |
152 | 0 | return false; |
153 | 0 | } |
154 | 0 | size_t newSize = bufferTotal_ * 2; |
155 | 0 | uint8_t* newBuffer = js_pod_calloc<uint8_t>(newSize); |
156 | 0 | if (!newBuffer) { |
157 | 0 | return false; |
158 | 0 | } |
159 | 0 | memcpy((newBuffer + newSize) - bufferUsed_, header_->copyStackBottom, bufferUsed_); |
160 | 0 | memcpy(newBuffer, header_, sizeof(BaselineBailoutInfo)); |
161 | 0 | js_free(buffer_); |
162 | 0 | buffer_ = newBuffer; |
163 | 0 | bufferTotal_ = newSize; |
164 | 0 | bufferAvail_ = newSize - (HeaderSize() + bufferUsed_); |
165 | 0 |
|
166 | 0 | header_ = reinterpret_cast<BaselineBailoutInfo*>(buffer_); |
167 | 0 | header_->copyStackTop = buffer_ + bufferTotal_; |
168 | 0 | header_->copyStackBottom = header_->copyStackTop - bufferUsed_; |
169 | 0 | return true; |
170 | 0 | } |
171 | | |
172 | 8 | BaselineBailoutInfo* info() { |
173 | 8 | MOZ_ASSERT(header_ == reinterpret_cast<BaselineBailoutInfo*>(buffer_)); |
174 | 8 | return header_; |
175 | 8 | } |
176 | | |
177 | 8 | BaselineBailoutInfo* takeBuffer() { |
178 | 8 | MOZ_ASSERT(header_ == reinterpret_cast<BaselineBailoutInfo*>(buffer_)); |
179 | 8 | buffer_ = nullptr; |
180 | 8 | return header_; |
181 | 8 | } |
182 | | |
183 | 8 | void resetFramePushed() { |
184 | 8 | framePushed_ = 0; |
185 | 8 | } |
186 | | |
187 | 16 | size_t framePushed() const { |
188 | 16 | return framePushed_; |
189 | 16 | } |
190 | | |
191 | 41 | MOZ_MUST_USE bool subtract(size_t size, const char* info = nullptr) { |
192 | 41 | // enlarge the buffer if need be. |
193 | 41 | while (size > bufferAvail_) { |
194 | 0 | if (!enlarge()) { |
195 | 0 | return false; |
196 | 0 | } |
197 | 0 | } |
198 | 41 | |
199 | 41 | // write out element. |
200 | 41 | header_->copyStackBottom -= size; |
201 | 41 | bufferAvail_ -= size; |
202 | 41 | bufferUsed_ += size; |
203 | 41 | framePushed_ += size; |
204 | 41 | if (info) { |
205 | 8 | JitSpew(JitSpew_BaselineBailouts, |
206 | 8 | " SUB_%03d %p/%p %-15s", |
207 | 8 | (int) size, header_->copyStackBottom, virtualPointerAtStackOffset(0), info); |
208 | 8 | } |
209 | 41 | return true; |
210 | 41 | } |
211 | | |
212 | | template <typename T> |
213 | 33 | MOZ_MUST_USE bool write(const T& t) { |
214 | 33 | MOZ_ASSERT(!(uintptr_t(&t) >= uintptr_t(header_->copyStackBottom) && |
215 | 33 | uintptr_t(&t) < uintptr_t(header_->copyStackTop)), |
216 | 33 | "Should not reference memory that can be freed"); |
217 | 33 | if (!subtract(sizeof(T))) { |
218 | 0 | return false; |
219 | 0 | } |
220 | 33 | memcpy(header_->copyStackBottom, &t, sizeof(T)); |
221 | 33 | return true; |
222 | 33 | } bool BaselineStackBuilder::write<void*>(void* const&) Line | Count | Source | 213 | 8 | MOZ_MUST_USE bool write(const T& t) { | 214 | 8 | MOZ_ASSERT(!(uintptr_t(&t) >= uintptr_t(header_->copyStackBottom) && | 215 | 8 | uintptr_t(&t) < uintptr_t(header_->copyStackTop)), | 216 | 8 | "Should not reference memory that can be freed"); | 217 | 8 | if (!subtract(sizeof(T))) { | 218 | 0 | return false; | 219 | 0 | } | 220 | 8 | memcpy(header_->copyStackBottom, &t, sizeof(T)); | 221 | 8 | return true; | 222 | 8 | } |
bool BaselineStackBuilder::write<JS::Value>(JS::Value const&) Line | Count | Source | 213 | 25 | MOZ_MUST_USE bool write(const T& t) { | 214 | 25 | MOZ_ASSERT(!(uintptr_t(&t) >= uintptr_t(header_->copyStackBottom) && | 215 | 25 | uintptr_t(&t) < uintptr_t(header_->copyStackTop)), | 216 | 25 | "Should not reference memory that can be freed"); | 217 | 25 | if (!subtract(sizeof(T))) { | 218 | 0 | return false; | 219 | 0 | } | 220 | 25 | memcpy(header_->copyStackBottom, &t, sizeof(T)); | 221 | 25 | return true; | 222 | 25 | } |
Unexecuted instantiation: bool BaselineStackBuilder::write<unsigned long>(unsigned long const&) Unexecuted instantiation: bool BaselineStackBuilder::write<unsigned char*>(unsigned char* const&) Unexecuted instantiation: bool BaselineStackBuilder::write<js::jit::ICFallbackStub*>(js::jit::ICFallbackStub* const&) |
223 | | |
224 | | template <typename T> |
225 | 8 | MOZ_MUST_USE bool writePtr(T* t, const char* info) { |
226 | 8 | if (!write<T*>(t)) { |
227 | 0 | return false; |
228 | 0 | } |
229 | 8 | if (info) { |
230 | 8 | JitSpew(JitSpew_BaselineBailouts, |
231 | 8 | " WRITE_PTR %p/%p %-15s %p", |
232 | 8 | header_->copyStackBottom, virtualPointerAtStackOffset(0), info, t); |
233 | 8 | } |
234 | 8 | return true; |
235 | 8 | } bool BaselineStackBuilder::writePtr<void>(void*, char const*) Line | Count | Source | 225 | 8 | MOZ_MUST_USE bool writePtr(T* t, const char* info) { | 226 | 8 | if (!write<T*>(t)) { | 227 | 0 | return false; | 228 | 0 | } | 229 | 8 | if (info) { | 230 | 8 | JitSpew(JitSpew_BaselineBailouts, | 231 | 8 | " WRITE_PTR %p/%p %-15s %p", | 232 | 8 | header_->copyStackBottom, virtualPointerAtStackOffset(0), info, t); | 233 | 8 | } | 234 | 8 | return true; | 235 | 8 | } |
Unexecuted instantiation: bool BaselineStackBuilder::writePtr<unsigned char>(unsigned char*, char const*) Unexecuted instantiation: bool BaselineStackBuilder::writePtr<js::jit::ICFallbackStub>(js::jit::ICFallbackStub*, char const*) |
236 | | |
237 | 0 | MOZ_MUST_USE bool writeWord(size_t w, const char* info) { |
238 | 0 | if (!write<size_t>(w)) { |
239 | 0 | return false; |
240 | 0 | } |
241 | 0 | if (info) { |
242 | 0 | if (sizeof(size_t) == 4) { |
243 | 0 | JitSpew(JitSpew_BaselineBailouts, |
244 | 0 | " WRITE_WRD %p/%p %-15s %08zx", |
245 | 0 | header_->copyStackBottom, virtualPointerAtStackOffset(0), info, w); |
246 | 0 | } else { |
247 | 0 | JitSpew(JitSpew_BaselineBailouts, |
248 | 0 | " WRITE_WRD %p/%p %-15s %016zx", |
249 | 0 | header_->copyStackBottom, virtualPointerAtStackOffset(0), info, w); |
250 | 0 | } |
251 | 0 | } |
252 | 0 | return true; |
253 | 0 | } |
254 | | |
255 | 25 | MOZ_MUST_USE bool writeValue(const Value& val, const char* info) { |
256 | 25 | if (!write<Value>(val)) { |
257 | 0 | return false; |
258 | 0 | } |
259 | 25 | if (info) { |
260 | 25 | JitSpew(JitSpew_BaselineBailouts, |
261 | 25 | " WRITE_VAL %p/%p %-15s %016" PRIx64, |
262 | 25 | header_->copyStackBottom, virtualPointerAtStackOffset(0), info, |
263 | 25 | *((uint64_t*) &val)); |
264 | 25 | } |
265 | 25 | return true; |
266 | 25 | } |
267 | | |
268 | 0 | MOZ_MUST_USE bool maybeWritePadding(size_t alignment, size_t after, const char* info) { |
269 | 0 | MOZ_ASSERT(framePushed_ % sizeof(Value) == 0); |
270 | 0 | MOZ_ASSERT(after % sizeof(Value) == 0); |
271 | 0 | size_t offset = ComputeByteAlignment(after, alignment); |
272 | 0 | while (framePushed_ % alignment != offset) { |
273 | 0 | if (!writeValue(MagicValue(JS_ARG_POISON), info)) { |
274 | 0 | return false; |
275 | 0 | } |
276 | 0 | } |
277 | 0 |
|
278 | 0 | return true; |
279 | 0 | } |
280 | | |
281 | 8 | Value popValue() { |
282 | 8 | MOZ_ASSERT(bufferUsed_ >= sizeof(Value)); |
283 | 8 | MOZ_ASSERT(framePushed_ >= sizeof(Value)); |
284 | 8 | bufferAvail_ += sizeof(Value); |
285 | 8 | bufferUsed_ -= sizeof(Value); |
286 | 8 | framePushed_ -= sizeof(Value); |
287 | 8 | Value result = *((Value*) header_->copyStackBottom); |
288 | 8 | header_->copyStackBottom += sizeof(Value); |
289 | 8 | return result; |
290 | 8 | } |
291 | | |
292 | 8 | void popValueInto(PCMappingSlotInfo::SlotLocation loc) { |
293 | 8 | MOZ_ASSERT(PCMappingSlotInfo::ValidSlotLocation(loc)); |
294 | 8 | switch(loc) { |
295 | 8 | case PCMappingSlotInfo::SlotInR0: |
296 | 8 | header_->setR0 = 1; |
297 | 8 | header_->valueR0 = popValue(); |
298 | 8 | break; |
299 | 8 | case PCMappingSlotInfo::SlotInR1: |
300 | 0 | header_->setR1 = 1; |
301 | 0 | header_->valueR1 = popValue(); |
302 | 0 | break; |
303 | 8 | default: |
304 | 0 | MOZ_ASSERT(loc == PCMappingSlotInfo::SlotIgnore); |
305 | 0 | popValue(); |
306 | 0 | break; |
307 | 8 | } |
308 | 8 | } |
309 | | |
310 | 8 | void setResumeFramePtr(void* resumeFramePtr) { |
311 | 8 | header_->resumeFramePtr = resumeFramePtr; |
312 | 8 | } |
313 | | |
314 | 8 | void setResumeAddr(void* resumeAddr) { |
315 | 8 | header_->resumeAddr = resumeAddr; |
316 | 8 | } |
317 | | |
318 | 8 | void setResumePC(jsbytecode* pc) { |
319 | 8 | header_->resumePC = pc; |
320 | 8 | } |
321 | | |
322 | 8 | void setMonitorStub(ICStub* stub) { |
323 | 8 | header_->monitorStub = stub; |
324 | 8 | } |
325 | | |
326 | | template <typename T> |
327 | 24 | BufferPointer<T> pointerAtStackOffset(size_t offset) { |
328 | 24 | if (offset < bufferUsed_) { |
329 | 8 | // Calculate offset from copyStackTop. |
330 | 8 | offset = header_->copyStackTop - (header_->copyStackBottom + offset); |
331 | 8 | return BufferPointer<T>(&header_, offset, /* heap = */ true); |
332 | 8 | } |
333 | 16 | |
334 | 16 | return BufferPointer<T>(&header_, offset - bufferUsed_, /* heap = */ false); |
335 | 16 | } BufferPointer<js::jit::JitFrameLayout> BaselineStackBuilder::pointerAtStackOffset<js::jit::JitFrameLayout>(unsigned long) Line | Count | Source | 327 | 8 | BufferPointer<T> pointerAtStackOffset(size_t offset) { | 328 | 8 | if (offset < bufferUsed_) { | 329 | 0 | // Calculate offset from copyStackTop. | 330 | 0 | offset = header_->copyStackTop - (header_->copyStackBottom + offset); | 331 | 0 | return BufferPointer<T>(&header_, offset, /* heap = */ true); | 332 | 0 | } | 333 | 8 | | 334 | 8 | return BufferPointer<T>(&header_, offset - bufferUsed_, /* heap = */ false); | 335 | 8 | } |
Unexecuted instantiation: BufferPointer<js::jit::RectifierFrameLayout> BaselineStackBuilder::pointerAtStackOffset<js::jit::RectifierFrameLayout>(unsigned long) BufferPointer<js::jit::BaselineFrame> BaselineStackBuilder::pointerAtStackOffset<js::jit::BaselineFrame>(unsigned long) Line | Count | Source | 327 | 8 | BufferPointer<T> pointerAtStackOffset(size_t offset) { | 328 | 8 | if (offset < bufferUsed_) { | 329 | 8 | // Calculate offset from copyStackTop. | 330 | 8 | offset = header_->copyStackTop - (header_->copyStackBottom + offset); | 331 | 8 | return BufferPointer<T>(&header_, offset, /* heap = */ true); | 332 | 8 | } | 333 | 0 | | 334 | 0 | return BufferPointer<T>(&header_, offset - bufferUsed_, /* heap = */ false); | 335 | 0 | } |
BufferPointer<JS::Value> BaselineStackBuilder::pointerAtStackOffset<JS::Value>(unsigned long) Line | Count | Source | 327 | 8 | BufferPointer<T> pointerAtStackOffset(size_t offset) { | 328 | 8 | if (offset < bufferUsed_) { | 329 | 0 | // Calculate offset from copyStackTop. | 330 | 0 | offset = header_->copyStackTop - (header_->copyStackBottom + offset); | 331 | 0 | return BufferPointer<T>(&header_, offset, /* heap = */ true); | 332 | 0 | } | 333 | 8 | | 334 | 8 | return BufferPointer<T>(&header_, offset - bufferUsed_, /* heap = */ false); | 335 | 8 | } |
Unexecuted instantiation: BufferPointer<unsigned char> BaselineStackBuilder::pointerAtStackOffset<unsigned char>(unsigned long) |
336 | | |
337 | 8 | BufferPointer<Value> valuePointerAtStackOffset(size_t offset) { |
338 | 8 | return pointerAtStackOffset<Value>(offset); |
339 | 8 | } |
340 | | |
341 | 8 | inline uint8_t* virtualPointerAtStackOffset(size_t offset) { |
342 | 8 | if (offset < bufferUsed_) { |
343 | 8 | return reinterpret_cast<uint8_t*>(frame_) - (bufferUsed_ - offset); |
344 | 8 | } |
345 | 0 | return reinterpret_cast<uint8_t*>(frame_) + (offset - bufferUsed_); |
346 | 0 | } |
347 | | |
348 | 8 | inline JitFrameLayout* startFrame() { |
349 | 8 | return frame_; |
350 | 8 | } |
351 | | |
352 | 8 | BufferPointer<JitFrameLayout> topFrameAddress() { |
353 | 8 | return pointerAtStackOffset<JitFrameLayout>(0); |
354 | 8 | } |
355 | | |
356 | | // |
357 | | // This method should only be called when the builder is in a state where it is |
358 | | // starting to construct the stack frame for the next callee. This means that |
359 | | // the lowest value on the constructed stack is the return address for the previous |
360 | | // caller frame. |
361 | | // |
362 | | // This method is used to compute the value of the frame pointer (e.g. ebp on x86) |
363 | | // that would have been saved by the baseline jitcode when it was entered. In some |
364 | | // cases, this value can be bogus since we can ensure that the caller would have saved |
365 | | // it anyway. |
366 | | // |
367 | 8 | void* calculatePrevFramePtr() { |
368 | 8 | // Get the incoming frame. |
369 | 8 | BufferPointer<JitFrameLayout> topFrame = topFrameAddress(); |
370 | 8 | FrameType type = topFrame->prevType(); |
371 | 8 | |
372 | 8 | // For IonJS, IonICCall and Entry frames, the "saved" frame pointer |
373 | 8 | // in the baseline frame is meaningless, since Ion saves all registers |
374 | 8 | // before calling other ion frames, and the entry frame saves all |
375 | 8 | // registers too. |
376 | 8 | if (JSJitFrameIter::isEntry(type) || type == FrameType::IonJS || type == FrameType::IonICCall) { |
377 | 8 | return nullptr; |
378 | 8 | } |
379 | 0 | |
380 | 0 | // BaselineStub - Baseline calling into Ion. |
381 | 0 | // PrevFramePtr needs to point to the BaselineStubFrame's saved frame pointer. |
382 | 0 | // STACK_START_ADDR + JitFrameLayout::Size() + PREV_FRAME_SIZE |
383 | 0 | // - BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr() |
384 | 0 | if (type == FrameType::BaselineStub) { |
385 | 0 | size_t offset = JitFrameLayout::Size() + topFrame->prevFrameLocalSize() + |
386 | 0 | BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr(); |
387 | 0 | return virtualPointerAtStackOffset(offset); |
388 | 0 | } |
389 | 0 | |
390 | 0 | MOZ_ASSERT(type == FrameType::Rectifier); |
391 | 0 | // Rectifier - behaviour depends on the frame preceding the rectifier frame, and |
392 | 0 | // whether the arch is x86 or not. The x86 rectifier frame saves the frame pointer, |
393 | 0 | // so we can calculate it directly. For other archs, the previous frame pointer |
394 | 0 | // is stored on the stack in the frame that precedes the rectifier frame. |
395 | 0 | size_t priorOffset = JitFrameLayout::Size() + topFrame->prevFrameLocalSize(); |
396 | | #if defined(JS_CODEGEN_X86) |
397 | | // On X86, the FramePointer is pushed as the first value in the Rectifier frame. |
398 | | MOZ_ASSERT(BaselineFrameReg == FramePointer); |
399 | | priorOffset -= sizeof(void*); |
400 | | return virtualPointerAtStackOffset(priorOffset); |
401 | | #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ |
402 | | defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) || \ |
403 | | defined(JS_CODEGEN_X64) |
404 | | // On X64, ARM, ARM64, and MIPS, the frame pointer save location depends on |
405 | 0 | // the caller of the rectifier frame. |
406 | 0 | BufferPointer<RectifierFrameLayout> priorFrame = |
407 | 0 | pointerAtStackOffset<RectifierFrameLayout>(priorOffset); |
408 | 0 | FrameType priorType = priorFrame->prevType(); |
409 | 0 | MOZ_ASSERT(JSJitFrameIter::isEntry(priorType) || |
410 | 0 | priorType == FrameType::IonJS || |
411 | 0 | priorType == FrameType::BaselineStub); |
412 | 0 |
|
413 | 0 | // If the frame preceding the rectifier is an IonJS or entry frame, |
414 | 0 | // then once again the frame pointer does not matter. |
415 | 0 | if (priorType == FrameType::IonJS || JSJitFrameIter::isEntry(priorType)) { |
416 | 0 | return nullptr; |
417 | 0 | } |
418 | 0 | |
419 | 0 | // Otherwise, the frame preceding the rectifier is a BaselineStub frame. |
420 | 0 | // let X = STACK_START_ADDR + JitFrameLayout::Size() + PREV_FRAME_SIZE |
421 | 0 | // X + RectifierFrameLayout::Size() |
422 | 0 | // + ((RectifierFrameLayout*) X)->prevFrameLocalSize() |
423 | 0 | // - BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr() |
424 | 0 | size_t extraOffset = RectifierFrameLayout::Size() + priorFrame->prevFrameLocalSize() + |
425 | 0 | BaselineStubFrameLayout::reverseOffsetOfSavedFramePtr(); |
426 | 0 | return virtualPointerAtStackOffset(priorOffset + extraOffset); |
427 | | #elif defined(JS_CODEGEN_NONE) |
428 | | (void) priorOffset; |
429 | | MOZ_CRASH(); |
430 | | #else |
431 | | # error "Bad architecture!" |
432 | | #endif |
433 | | } |
434 | | |
435 | 0 | void setCheckGlobalDeclarationConflicts() { |
436 | 0 | header_->checkGlobalDeclarationConflicts = true; |
437 | 0 | } |
438 | | }; |
439 | | |
440 | | #ifdef DEBUG |
441 | | static inline bool |
442 | | IsInlinableFallback(ICFallbackStub* icEntry) |
443 | | { |
444 | | return icEntry->isCall_Fallback() || icEntry->isGetProp_Fallback() || |
445 | | icEntry->isSetProp_Fallback(); |
446 | | } |
447 | | #endif |
448 | | |
449 | | static inline void* |
450 | | GetStubReturnAddress(JSContext* cx, jsbytecode* pc) |
451 | 0 | { |
452 | 0 | JitRealm* jitRealm = cx->realm()->jitRealm(); |
453 | 0 |
|
454 | 0 | if (IsGetPropPC(pc)) { |
455 | 0 | return jitRealm->bailoutReturnAddr(BailoutReturnStub::GetProp); |
456 | 0 | } |
457 | 0 | if (IsSetPropPC(pc)) { |
458 | 0 | return jitRealm->bailoutReturnAddr(BailoutReturnStub::SetProp); |
459 | 0 | } |
460 | 0 | |
461 | 0 | // This should be a call op of some kind, now. |
462 | 0 | MOZ_ASSERT(IsCallPC(pc) && !IsSpreadCallPC(pc)); |
463 | 0 | if (IsConstructorCallPC(pc)) { |
464 | 0 | return jitRealm->bailoutReturnAddr(BailoutReturnStub::New); |
465 | 0 | } |
466 | 0 | return jitRealm->bailoutReturnAddr(BailoutReturnStub::Call); |
467 | 0 | } |
468 | | |
469 | | static inline jsbytecode* |
470 | | GetNextNonLoopEntryPc(jsbytecode* pc, jsbytecode** skippedLoopEntry) |
471 | 0 | { |
472 | 0 | JSOp op = JSOp(*pc); |
473 | 0 | if (op == JSOP_GOTO) { |
474 | 0 | return pc + GET_JUMP_OFFSET(pc); |
475 | 0 | } |
476 | 0 | if (op == JSOP_LOOPENTRY || op == JSOP_NOP || op == JSOP_LOOPHEAD) { |
477 | 0 | if (op == JSOP_LOOPENTRY) { |
478 | 0 | *skippedLoopEntry = pc; |
479 | 0 | } |
480 | 0 | return GetNextPc(pc); |
481 | 0 | } |
482 | 0 | return pc; |
483 | 0 | } |
484 | | |
485 | | static bool |
486 | | HasLiveStackValueAtDepth(JSScript* script, jsbytecode* pc, uint32_t stackDepth) |
487 | 0 | { |
488 | 0 | if (!script->hasTrynotes()) { |
489 | 0 | return false; |
490 | 0 | } |
491 | 0 | |
492 | 0 | JSTryNote* tn = script->trynotes()->vector; |
493 | 0 | JSTryNote* tnEnd = tn + script->trynotes()->length; |
494 | 0 | uint32_t pcOffset = uint32_t(pc - script->main()); |
495 | 0 | for (; tn != tnEnd; ++tn) { |
496 | 0 | if (pcOffset < tn->start) { |
497 | 0 | continue; |
498 | 0 | } |
499 | 0 | if (pcOffset >= tn->start + tn->length) { |
500 | 0 | continue; |
501 | 0 | } |
502 | 0 | |
503 | 0 | switch (tn->kind) { |
504 | 0 | case JSTRY_FOR_IN: |
505 | 0 | // For-in loops have only the iterator on stack. |
506 | 0 | if (stackDepth == tn->stackDepth) { |
507 | 0 | return true; |
508 | 0 | } |
509 | 0 | break; |
510 | 0 |
|
511 | 0 | case JSTRY_FOR_OF: |
512 | 0 | // For-of loops have the iterator, its next method and the |
513 | 0 | // result.value on stack. |
514 | 0 | // The iterator is below the result.value, the next method below |
515 | 0 | // the iterator. |
516 | 0 | if (stackDepth == tn->stackDepth - 1 || stackDepth == tn->stackDepth - 2) { |
517 | 0 | return true; |
518 | 0 | } |
519 | 0 | break; |
520 | 0 |
|
521 | 0 | case JSTRY_DESTRUCTURING_ITERCLOSE: |
522 | 0 | // Destructuring code that need to call IteratorClose have both |
523 | 0 | // the iterator and the "done" value on the stack. |
524 | 0 | if (stackDepth == tn->stackDepth || stackDepth == tn->stackDepth - 1) { |
525 | 0 | return true; |
526 | 0 | } |
527 | 0 | break; |
528 | 0 |
|
529 | 0 | default: |
530 | 0 | break; |
531 | 0 | } |
532 | 0 | } |
533 | 0 |
|
534 | 0 | return false; |
535 | 0 | } |
536 | | |
537 | | static bool |
538 | | IsPrologueBailout(const SnapshotIterator& iter, const ExceptionBailoutInfo* excInfo) |
539 | 0 | { |
540 | 0 | // If we are propagating an exception for debug mode, we will not resume |
541 | 0 | // into baseline code, but instead into HandleExceptionBaseline (i.e., |
542 | 0 | // never before the prologue). |
543 | 0 | return iter.pcOffset() == 0 && !iter.resumeAfter() && |
544 | 0 | (!excInfo || !excInfo->propagatingIonExceptionForDebugMode()); |
545 | 0 | } |
546 | | |
547 | | // For every inline frame, we write out the following data: |
548 | | // |
549 | | // | ... | |
550 | | // +---------------+ |
551 | | // | Descr(???) | --- Descr size here is (PREV_FRAME_SIZE) |
552 | | // +---------------+ |
553 | | // | ReturnAddr | |
554 | | // -- +===============+ --- OVERWRITE STARTS HERE (START_STACK_ADDR) |
555 | | // | | PrevFramePtr | |
556 | | // | +-> +---------------+ |
557 | | // | | | Baseline | |
558 | | // | | | Frame | |
559 | | // | | +---------------+ |
560 | | // | | | Fixed0 | |
561 | | // | | +---------------+ |
562 | | // +--< | | ... | |
563 | | // | | | +---------------+ |
564 | | // | | | | FixedF | |
565 | | // | | | +---------------+ |
566 | | // | | | | Stack0 | |
567 | | // | | | +---------------+ |
568 | | // | | | | ... | |
569 | | // | | | +---------------+ |
570 | | // | | | | StackS | |
571 | | // | -- | +---------------+ --- IF NOT LAST INLINE FRAME, |
572 | | // +------------| Descr(BLJS) | --- CALLING INFO STARTS HERE |
573 | | // | +---------------+ |
574 | | // | | ReturnAddr | <-- return into main jitcode after IC |
575 | | // -- | +===============+ |
576 | | // | | | StubPtr | |
577 | | // | | +---------------+ |
578 | | // | +---| FramePtr | |
579 | | // | +---------------+ --- The inlined frame might OSR in Ion |
580 | | // | | Padding? | --- Thus the return address should be aligned. |
581 | | // | +---------------+ |
582 | | // +--< | ArgA | |
583 | | // | | +---------------+ |
584 | | // | | | ... | |
585 | | // | | +---------------+ |
586 | | // | | | Arg0 | |
587 | | // | | +---------------+ |
588 | | // | | | ThisV | |
589 | | // | -- +---------------+ |
590 | | // | | ActualArgC | |
591 | | // | +---------------+ |
592 | | // | | CalleeToken | |
593 | | // | +---------------+ |
594 | | // +------------| Descr(BLStub) | |
595 | | // +---------------+ |
596 | | // | ReturnAddr | <-- return into ICCall_Scripted IC |
597 | | // -- +===============+ --- IF CALLEE FORMAL ARGS > ActualArgC |
598 | | // | | Padding? | |
599 | | // | +---------------+ |
600 | | // | | UndefinedU | |
601 | | // | +---------------+ |
602 | | // | | ... | |
603 | | // | +---------------+ |
604 | | // | | Undefined0 | |
605 | | // +--< +---------------+ |
606 | | // | | | ArgA | |
607 | | // | | +---------------+ |
608 | | // | | | ... | |
609 | | // | | +---------------+ |
610 | | // | | | Arg0 | |
611 | | // | | +---------------+ |
612 | | // | | | ThisV | |
613 | | // | -- +---------------+ |
614 | | // | | ActualArgC | |
615 | | // | +---------------+ |
616 | | // | | CalleeToken | |
617 | | // | +---------------+ |
618 | | // +------------| Descr(Rect) | |
619 | | // +---------------+ |
620 | | // | ReturnAddr | <-- return into ArgumentsRectifier after call |
621 | | // +===============+ |
622 | | // |
623 | | static bool |
624 | | InitFromBailout(JSContext* cx, size_t frameNo, |
625 | | HandleFunction fun, HandleScript script, |
626 | | SnapshotIterator& iter, bool invalidate, BaselineStackBuilder& builder, |
627 | | MutableHandle<GCVector<Value>> startFrameFormals, MutableHandleFunction nextCallee, |
628 | | const ExceptionBailoutInfo* excInfo) |
629 | 8 | { |
630 | 8 | // The Baseline frames we will reconstruct on the heap are not rooted, so GC |
631 | 8 | // must be suppressed here. |
632 | 8 | MOZ_ASSERT(cx->suppressGC); |
633 | 8 | |
634 | 8 | MOZ_ASSERT(script->hasBaselineScript()); |
635 | 8 | |
636 | 8 | // Are we catching an exception? |
637 | 8 | bool catchingException = excInfo && excInfo->catchingException(); |
638 | 8 | |
639 | 8 | // If we are catching an exception, we are bailing out to a catch or |
640 | 8 | // finally block and this is the frame where we will resume. Usually the |
641 | 8 | // expression stack should be empty in this case but there can be |
642 | 8 | // iterators on the stack. |
643 | 8 | uint32_t exprStackSlots; |
644 | 8 | if (catchingException) { |
645 | 0 | exprStackSlots = excInfo->numExprSlots(); |
646 | 8 | } else { |
647 | 8 | exprStackSlots = iter.numAllocations() - (script->nfixed() + CountArgSlots(script, fun)); |
648 | 8 | } |
649 | 8 | |
650 | 8 | builder.resetFramePushed(); |
651 | 8 | |
652 | 8 | // Build first baseline frame: |
653 | 8 | // +===============+ |
654 | 8 | // | PrevFramePtr | |
655 | 8 | // +---------------+ |
656 | 8 | // | Baseline | |
657 | 8 | // | Frame | |
658 | 8 | // +---------------+ |
659 | 8 | // | Fixed0 | |
660 | 8 | // +---------------+ |
661 | 8 | // | ... | |
662 | 8 | // +---------------+ |
663 | 8 | // | FixedF | |
664 | 8 | // +---------------+ |
665 | 8 | // | Stack0 | |
666 | 8 | // +---------------+ |
667 | 8 | // | ... | |
668 | 8 | // +---------------+ |
669 | 8 | // | StackS | |
670 | 8 | // +---------------+ --- IF NOT LAST INLINE FRAME, |
671 | 8 | // | Descr(BLJS) | --- CALLING INFO STARTS HERE |
672 | 8 | // +---------------+ |
673 | 8 | // | ReturnAddr | <-- return into main jitcode after IC |
674 | 8 | // +===============+ |
675 | 8 | |
676 | 8 | JitSpew(JitSpew_BaselineBailouts, " Unpacking %s:%u:%u", script->filename(), |
677 | 8 | script->lineno(), |
678 | 8 | script->column()); |
679 | 8 | JitSpew(JitSpew_BaselineBailouts, " [BASELINE-JS FRAME]"); |
680 | 8 | |
681 | 8 | // Calculate and write the previous frame pointer value. |
682 | 8 | // Record the virtual stack offset at this location. Later on, if we end up |
683 | 8 | // writing out a BaselineStub frame for the next callee, we'll need to save the |
684 | 8 | // address. |
685 | 8 | void* prevFramePtr = builder.calculatePrevFramePtr(); |
686 | 8 | if (!builder.writePtr(prevFramePtr, "PrevFramePtr")) { |
687 | 0 | return false; |
688 | 0 | } |
689 | 8 | prevFramePtr = builder.virtualPointerAtStackOffset(0); |
690 | 8 | |
691 | 8 | // Write struct BaselineFrame. |
692 | 8 | if (!builder.subtract(BaselineFrame::Size(), "BaselineFrame")) { |
693 | 0 | return false; |
694 | 0 | } |
695 | 8 | BufferPointer<BaselineFrame> blFrame = builder.pointerAtStackOffset<BaselineFrame>(0); |
696 | 8 | |
697 | 8 | uint32_t flags = 0; |
698 | 8 | |
699 | 8 | // If we are bailing to a script whose execution is observed, mark the |
700 | 8 | // baseline frame as a debuggee frame. This is to cover the case where we |
701 | 8 | // don't rematerialize the Ion frame via the Debugger. |
702 | 8 | if (script->isDebuggee()) { |
703 | 0 | flags |= BaselineFrame::DEBUGGEE; |
704 | 0 | } |
705 | 8 | |
706 | 8 | // Initialize BaselineFrame's envChain and argsObj |
707 | 8 | JSObject* envChain = nullptr; |
708 | 8 | Value returnValue; |
709 | 8 | ArgumentsObject* argsObj = nullptr; |
710 | 8 | BailoutKind bailoutKind = iter.bailoutKind(); |
711 | 8 | if (bailoutKind == Bailout_ArgumentCheck) { |
712 | 0 | // Temporary hack -- skip the (unused) envChain, because it could be |
713 | 0 | // bogus (we can fail before the env chain slot is set). Strip the |
714 | 0 | // hasEnvironmentChain flag and this will be fixed up later in |
715 | 0 | // |FinishBailoutToBaseline|, which calls |
716 | 0 | // |EnsureHasEnvironmentObjects|. |
717 | 0 | JitSpew(JitSpew_BaselineBailouts, " Bailout_ArgumentCheck! (no valid envChain)"); |
718 | 0 | iter.skip(); |
719 | 0 |
|
720 | 0 | // skip |return value| |
721 | 0 | iter.skip(); |
722 | 0 | returnValue = UndefinedValue(); |
723 | 0 |
|
724 | 0 | // Scripts with |argumentsHasVarBinding| have an extra slot. |
725 | 0 | if (script->argumentsHasVarBinding()) { |
726 | 0 | JitSpew(JitSpew_BaselineBailouts, |
727 | 0 | " Bailout_ArgumentCheck for script with argumentsHasVarBinding!" |
728 | 0 | "Using empty arguments object"); |
729 | 0 | iter.skip(); |
730 | 0 | } |
731 | 8 | } else { |
732 | 8 | Value v = iter.read(); |
733 | 8 | if (v.isObject()) { |
734 | 8 | envChain = &v.toObject(); |
735 | 8 | |
736 | 8 | // If Ion has updated env slot from UndefinedValue, it will be the |
737 | 8 | // complete initial environment, so we can set the HAS_INITIAL_ENV |
738 | 8 | // flag if needed. |
739 | 8 | if (fun && fun->needsFunctionEnvironmentObjects()) { |
740 | 0 | MOZ_ASSERT(fun->nonLazyScript()->initialEnvironmentShape()); |
741 | 0 | MOZ_ASSERT(!fun->needsExtraBodyVarEnvironment()); |
742 | 0 | flags |= BaselineFrame::HAS_INITIAL_ENV; |
743 | 0 | } |
744 | 8 | } else { |
745 | 0 | MOZ_ASSERT(v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT)); |
746 | 0 |
|
747 | | #ifdef DEBUG |
748 | | // The |envChain| slot must not be optimized out if the currently |
749 | | // active scope requires any EnvironmentObjects beyond what is |
750 | | // available at body scope. This checks that scope chain does not |
751 | | // require any such EnvironmentObjects. |
752 | | // See also: |CompileInfo::isObservableFrameSlot| |
753 | | jsbytecode* pc = script->offsetToPC(iter.pcOffset()); |
754 | | Scope* scopeIter = script->innermostScope(pc); |
755 | | while (scopeIter != script->bodyScope()) { |
756 | | MOZ_ASSERT(scopeIter); |
757 | | MOZ_ASSERT(!scopeIter->hasEnvironment()); |
758 | | scopeIter = scopeIter->enclosing(); |
759 | | } |
760 | | #endif |
761 | |
|
762 | 0 | // Get env chain from function or script. |
763 | 0 | if (fun) { |
764 | 0 | // If pcOffset == 0, we may have to push a new call object, so |
765 | 0 | // we leave envChain nullptr and enter baseline code before |
766 | 0 | // the prologue. |
767 | 0 | if (!IsPrologueBailout(iter, excInfo)) { |
768 | 0 | envChain = fun->environment(); |
769 | 0 | } |
770 | 0 | } else if (script->module()) { |
771 | 0 | envChain = script->module()->environment(); |
772 | 0 | } else { |
773 | 0 | // For global scripts without a non-syntactic env the env |
774 | 0 | // chain is the script's global lexical environment (Ion does |
775 | 0 | // not compile scripts with a non-syntactic global scope). |
776 | 0 | // Also note that it's invalid to resume into the prologue in |
777 | 0 | // this case because the prologue expects the env chain in R1 |
778 | 0 | // for eval and global scripts. |
779 | 0 | MOZ_ASSERT(!script->isForEval()); |
780 | 0 | MOZ_ASSERT(!script->hasNonSyntacticScope()); |
781 | 0 | envChain = &(script->global().lexicalEnvironment()); |
782 | 0 |
|
783 | 0 | // We have possibly bailed out before Ion could do the global |
784 | 0 | // declaration conflicts check. Since it's invalid to resume |
785 | 0 | // into the prologue, set a flag so FinishBailoutToBaseline |
786 | 0 | // can do the conflict check. |
787 | 0 | if (IsPrologueBailout(iter, excInfo)) { |
788 | 0 | builder.setCheckGlobalDeclarationConflicts(); |
789 | 0 | } |
790 | 0 | } |
791 | 0 | } |
792 | 8 | |
793 | 8 | // Make sure to add HAS_RVAL to flags here because setFlags() below |
794 | 8 | // will clobber it. |
795 | 8 | returnValue = iter.read(); |
796 | 8 | flags |= BaselineFrame::HAS_RVAL; |
797 | 8 | |
798 | 8 | // If script maybe has an arguments object, the third slot will hold it. |
799 | 8 | if (script->argumentsHasVarBinding()) { |
800 | 0 | v = iter.read(); |
801 | 0 | MOZ_ASSERT(v.isObject() || v.isUndefined() || v.isMagic(JS_OPTIMIZED_OUT)); |
802 | 0 | if (v.isObject()) { |
803 | 0 | argsObj = &v.toObject().as<ArgumentsObject>(); |
804 | 0 | } |
805 | 0 | } |
806 | 8 | } |
807 | 8 | JitSpew(JitSpew_BaselineBailouts, " EnvChain=%p", envChain); |
808 | 8 | blFrame->setEnvironmentChain(envChain); |
809 | 8 | JitSpew(JitSpew_BaselineBailouts, " ReturnValue=%016" PRIx64, *((uint64_t*) &returnValue)); |
810 | 8 | blFrame->setReturnValue(returnValue); |
811 | 8 | |
812 | 8 | // Do not need to initialize scratchValue field in BaselineFrame. |
813 | 8 | blFrame->setFlags(flags); |
814 | 8 | |
815 | 8 | // initArgsObjUnchecked modifies the frame's flags, so call it after setFlags. |
816 | 8 | if (argsObj) { |
817 | 0 | blFrame->initArgsObjUnchecked(*argsObj); |
818 | 0 | } |
819 | 8 | |
820 | 8 | if (fun) { |
821 | 8 | // The unpacked thisv and arguments should overwrite the pushed args present |
822 | 8 | // in the calling frame. |
823 | 8 | Value thisv = iter.read(); |
824 | 8 | JitSpew(JitSpew_BaselineBailouts, " Is function!"); |
825 | 8 | JitSpew(JitSpew_BaselineBailouts, " thisv=%016" PRIx64, *((uint64_t*) &thisv)); |
826 | 8 | |
827 | 8 | size_t thisvOffset = builder.framePushed() + JitFrameLayout::offsetOfThis(); |
828 | 8 | builder.valuePointerAtStackOffset(thisvOffset).set(thisv); |
829 | 8 | |
830 | 8 | MOZ_ASSERT(iter.numAllocations() >= CountArgSlots(script, fun)); |
831 | 8 | JitSpew(JitSpew_BaselineBailouts, " frame slots %u, nargs %zu, nfixed %zu", |
832 | 8 | iter.numAllocations(), fun->nargs(), script->nfixed()); |
833 | 8 | |
834 | 8 | bool argsObjAliasesFormals = script->argsObjAliasesFormals(); |
835 | 8 | if (frameNo == 0 && !argsObjAliasesFormals) { |
836 | 8 | // This is the first (outermost) frame and we don't have an |
837 | 8 | // arguments object aliasing the formals. Store the formals in a |
838 | 8 | // Vector until we are done. Due to UCE and phi elimination, we |
839 | 8 | // could store an UndefinedValue() here for formals we think are |
840 | 8 | // unused, but locals may still reference the original argument slot |
841 | 8 | // (MParameter/LArgument) and expect the original Value. |
842 | 8 | MOZ_ASSERT(startFrameFormals.empty()); |
843 | 8 | if (!startFrameFormals.resize(fun->nargs())) { |
844 | 0 | return false; |
845 | 0 | } |
846 | 8 | } |
847 | 8 | |
848 | 32 | for (uint32_t i = 0; i < fun->nargs(); i++) { |
849 | 24 | Value arg = iter.read(); |
850 | 24 | JitSpew(JitSpew_BaselineBailouts, " arg %d = %016" PRIx64, |
851 | 24 | (int) i, *((uint64_t*) &arg)); |
852 | 24 | if (frameNo > 0) { |
853 | 0 | size_t argOffset = builder.framePushed() + JitFrameLayout::offsetOfActualArg(i); |
854 | 0 | builder.valuePointerAtStackOffset(argOffset).set(arg); |
855 | 24 | } else if (argsObjAliasesFormals) { |
856 | 0 | // When the arguments object aliases the formal arguments, then |
857 | 0 | // JSOP_SETARG mutates the argument object. In such cases, the |
858 | 0 | // list of arguments reported by the snapshot are only aliases |
859 | 0 | // of argument object slots which are optimized to only store |
860 | 0 | // differences compared to arguments which are on the stack. |
861 | 24 | } else { |
862 | 24 | startFrameFormals[i].set(arg); |
863 | 24 | } |
864 | 24 | } |
865 | 8 | } |
866 | 8 | |
867 | 16 | for (uint32_t i = 0; i < script->nfixed(); i++) { |
868 | 8 | Value slot = iter.read(); |
869 | 8 | if (!builder.writeValue(slot, "FixedValue")) { |
870 | 0 | return false; |
871 | 0 | } |
872 | 8 | } |
873 | 8 | |
874 | 8 | // Get the pc. If we are handling an exception, resume at the pc of the |
875 | 8 | // catch or finally block. |
876 | 8 | jsbytecode* pc = catchingException ? excInfo->resumePC() : script->offsetToPC(iter.pcOffset()); |
877 | 8 | bool resumeAfter = catchingException ? false : iter.resumeAfter(); |
878 | 8 | |
879 | 8 | // When pgo is enabled, increment the counter of the block in which we |
880 | 8 | // resume, as Ion does not keep track of the code coverage. |
881 | 8 | // |
882 | 8 | // We need to do that when pgo is enabled, as after a specific number of |
883 | 8 | // FirstExecution bailouts, we invalidate and recompile the script with |
884 | 8 | // IonMonkey. Failing to increment the counter of the current basic block |
885 | 8 | // might lead to repeated bailouts and invalidations. |
886 | 8 | if (!JitOptions.disablePgo && script->hasScriptCounts()) { |
887 | 8 | script->incHitCount(pc); |
888 | 8 | } |
889 | 8 | |
890 | 8 | JSOp op = JSOp(*pc); |
891 | 8 | |
892 | 8 | // Inlining of SPREADCALL-like frames not currently supported. |
893 | 8 | MOZ_ASSERT_IF(IsSpreadCallPC(pc), !iter.moreFrames()); |
894 | 8 | |
895 | 8 | // Fixup inlined JSOP_FUNCALL, JSOP_FUNAPPLY, and accessors on the caller side. |
896 | 8 | // On the caller side this must represent like the function wasn't inlined. |
897 | 8 | uint32_t pushedSlots = 0; |
898 | 8 | AutoValueVector savedCallerArgs(cx); |
899 | 8 | bool needToSaveArgs = op == JSOP_FUNAPPLY || IsGetPropPC(pc) || IsSetPropPC(pc); |
900 | 8 | if (iter.moreFrames() && (op == JSOP_FUNCALL || needToSaveArgs)) { |
901 | 0 | uint32_t inlined_args = 0; |
902 | 0 | if (op == JSOP_FUNCALL) { |
903 | 0 | inlined_args = 2 + GET_ARGC(pc) - 1; |
904 | 0 | } else if (op == JSOP_FUNAPPLY) { |
905 | 0 | inlined_args = 2 + blFrame->numActualArgs(); |
906 | 0 | } else { |
907 | 0 | MOZ_ASSERT(IsGetPropPC(pc) || IsSetPropPC(pc)); |
908 | 0 | inlined_args = 2 + IsSetPropPC(pc); |
909 | 0 | } |
910 | 0 |
|
911 | 0 | MOZ_ASSERT(exprStackSlots >= inlined_args); |
912 | 0 | pushedSlots = exprStackSlots - inlined_args; |
913 | 0 |
|
914 | 0 | JitSpew(JitSpew_BaselineBailouts, |
915 | 0 | " pushing %u expression stack slots before fixup", |
916 | 0 | pushedSlots); |
917 | 0 | for (uint32_t i = 0; i < pushedSlots; i++) { |
918 | 0 | Value v = iter.read(); |
919 | 0 | if (!builder.writeValue(v, "StackValue")) { |
920 | 0 | return false; |
921 | 0 | } |
922 | 0 | } |
923 | 0 |
|
924 | 0 | if (op == JSOP_FUNCALL) { |
925 | 0 | // When funcall got inlined and the native js_fun_call was bypassed, |
926 | 0 | // the stack state is incorrect. To restore correctly it must look like |
927 | 0 | // js_fun_call was actually called. This means transforming the stack |
928 | 0 | // from |target, this, args| to |js_fun_call, target, this, args| |
929 | 0 | // The js_fun_call is never read, so just pushing undefined now. |
930 | 0 | JitSpew(JitSpew_BaselineBailouts, " pushing undefined to fixup funcall"); |
931 | 0 | if (!builder.writeValue(UndefinedValue(), "StackValue")) { |
932 | 0 | return false; |
933 | 0 | } |
934 | 0 | } |
935 | 0 | |
936 | 0 | if (needToSaveArgs) { |
937 | 0 | // When an accessor is inlined, the whole thing is a lie. There |
938 | 0 | // should never have been a call there. Fix the caller's stack to |
939 | 0 | // forget it ever happened. |
940 | 0 |
|
941 | 0 | // When funapply gets inlined we take all arguments out of the |
942 | 0 | // arguments array. So the stack state is incorrect. To restore |
943 | 0 | // correctly it must look like js_fun_apply was actually called. |
944 | 0 | // This means transforming the stack from |target, this, arg1, ...| |
945 | 0 | // to |js_fun_apply, target, this, argObject|. |
946 | 0 | // Since the information is never read, we can just push undefined |
947 | 0 | // for all values. |
948 | 0 | if (op == JSOP_FUNAPPLY) { |
949 | 0 | JitSpew(JitSpew_BaselineBailouts, " pushing 4x undefined to fixup funapply"); |
950 | 0 | if (!builder.writeValue(UndefinedValue(), "StackValue")) { |
951 | 0 | return false; |
952 | 0 | } |
953 | 0 | if (!builder.writeValue(UndefinedValue(), "StackValue")) { |
954 | 0 | return false; |
955 | 0 | } |
956 | 0 | if (!builder.writeValue(UndefinedValue(), "StackValue")) { |
957 | 0 | return false; |
958 | 0 | } |
959 | 0 | if (!builder.writeValue(UndefinedValue(), "StackValue")) { |
960 | 0 | return false; |
961 | 0 | } |
962 | 0 | } |
963 | 0 | // Save the actual arguments. They are needed on the callee side |
964 | 0 | // as the arguments. Else we can't recover them. |
965 | 0 | if (!savedCallerArgs.resize(inlined_args)) { |
966 | 0 | return false; |
967 | 0 | } |
968 | 0 | for (uint32_t i = 0; i < inlined_args; i++) { |
969 | 0 | savedCallerArgs[i].set(iter.read()); |
970 | 0 | } |
971 | 0 |
|
972 | 0 | if (IsSetPropPC(pc)) { |
973 | 0 | // We would love to just save all the arguments and leave them |
974 | 0 | // in the stub frame pushed below, but we will lose the inital |
975 | 0 | // argument which the function was called with, which we must |
976 | 0 | // leave on the stack. It's pushed as the result of the SETPROP. |
977 | 0 | Value initialArg = savedCallerArgs[inlined_args - 1]; |
978 | 0 | JitSpew(JitSpew_BaselineBailouts, " pushing setter's initial argument"); |
979 | 0 | if (!builder.writeValue(initialArg, "StackValue")) { |
980 | 0 | return false; |
981 | 0 | } |
982 | 0 | } |
983 | 0 | pushedSlots = exprStackSlots; |
984 | 0 | } |
985 | 0 | } |
986 | 8 | |
987 | 8 | JitSpew(JitSpew_BaselineBailouts, " pushing %u expression stack slots", |
988 | 8 | exprStackSlots - pushedSlots); |
989 | 21 | for (uint32_t i = pushedSlots; i < exprStackSlots; i++) { |
990 | 13 | Value v; |
991 | 13 | |
992 | 13 | if (!iter.moreFrames() && i == exprStackSlots - 1 && |
993 | 13 | cx->hasIonReturnOverride()) |
994 | 2 | { |
995 | 2 | // If coming from an invalidation bailout, and this is the topmost |
996 | 2 | // value, and a value override has been specified, don't read from the |
997 | 2 | // iterator. Otherwise, we risk using a garbage value. |
998 | 2 | MOZ_ASSERT(invalidate); |
999 | 2 | iter.skip(); |
1000 | 2 | JitSpew(JitSpew_BaselineBailouts, " [Return Override]"); |
1001 | 2 | v = cx->takeIonReturnOverride(); |
1002 | 11 | } else if (excInfo && excInfo->propagatingIonExceptionForDebugMode()) { |
1003 | 0 | // If we are in the middle of propagating an exception from Ion by |
1004 | 0 | // bailing to baseline due to debug mode, we might not have all |
1005 | 0 | // the stack if we are at the newest frame. |
1006 | 0 | // |
1007 | 0 | // For instance, if calling |f()| pushed an Ion frame which threw, |
1008 | 0 | // the snapshot expects the return value to be pushed, but it's |
1009 | 0 | // possible nothing was pushed before we threw. We can't drop |
1010 | 0 | // iterators, however, so read them out. They will be closed by |
1011 | 0 | // HandleExceptionBaseline. |
1012 | 0 | MOZ_ASSERT(cx->realm()->isDebuggee()); |
1013 | 0 | if (iter.moreFrames() || HasLiveStackValueAtDepth(script, pc, i + 1)) { |
1014 | 0 | v = iter.read(); |
1015 | 0 | } else { |
1016 | 0 | iter.skip(); |
1017 | 0 | v = MagicValue(JS_OPTIMIZED_OUT); |
1018 | 0 | } |
1019 | 11 | } else { |
1020 | 11 | v = iter.read(); |
1021 | 11 | } |
1022 | 13 | if (!builder.writeValue(v, "StackValue")) { |
1023 | 0 | return false; |
1024 | 0 | } |
1025 | 13 | } |
1026 | 8 | |
1027 | 8 | // BaselineFrame::frameSize is the size of everything pushed since |
1028 | 8 | // the builder.resetFramePushed() call. |
1029 | 8 | uint32_t frameSize = builder.framePushed(); |
1030 | 8 | blFrame->setFrameSize(frameSize); |
1031 | 8 | JitSpew(JitSpew_BaselineBailouts, " FrameSize=%u", frameSize); |
1032 | 8 | |
1033 | 8 | // numValueSlots() is based on the frame size, do some sanity checks. |
1034 | 8 | MOZ_ASSERT(blFrame->numValueSlots() >= script->nfixed()); |
1035 | 8 | MOZ_ASSERT(blFrame->numValueSlots() <= script->nslots()); |
1036 | 8 | |
1037 | 8 | // If we are resuming at a LOOPENTRY op, resume at the next op to avoid |
1038 | 8 | // a bailout -> enter Ion -> bailout loop with --ion-eager. See also |
1039 | 8 | // ThunkToInterpreter. |
1040 | 8 | // |
1041 | 8 | // The algorithm below is the "tortoise and the hare" algorithm. See bug |
1042 | 8 | // 994444 for more explanation. |
1043 | 8 | jsbytecode* skippedLoopEntry = nullptr; |
1044 | 8 | if (!resumeAfter) { |
1045 | 0 | jsbytecode* fasterPc = pc; |
1046 | 0 | while (true) { |
1047 | 0 | pc = GetNextNonLoopEntryPc(pc, &skippedLoopEntry); |
1048 | 0 | fasterPc = GetNextNonLoopEntryPc(GetNextNonLoopEntryPc(fasterPc, &skippedLoopEntry), &skippedLoopEntry); |
1049 | 0 | if (fasterPc == pc) { |
1050 | 0 | break; |
1051 | 0 | } |
1052 | 0 | } |
1053 | 0 | op = JSOp(*pc); |
1054 | 0 | if (skippedLoopEntry && script->trackRecordReplayProgress()) { |
1055 | 0 | mozilla::recordreplay::AdvanceExecutionProgressCounter(); |
1056 | 0 | } |
1057 | 0 | } |
1058 | 8 | |
1059 | 8 | const uint32_t pcOff = script->pcToOffset(pc); |
1060 | 8 | BaselineScript* baselineScript = script->baselineScript(); |
1061 | 8 | |
1062 | | #ifdef DEBUG |
1063 | | uint32_t expectedDepth; |
1064 | | bool reachablePC; |
1065 | | if (!ReconstructStackDepth(cx, script, resumeAfter ? GetNextPc(pc) : pc, &expectedDepth, &reachablePC)) { |
1066 | | return false; |
1067 | | } |
1068 | | |
1069 | | if (reachablePC) { |
1070 | | if (op != JSOP_FUNAPPLY || !iter.moreFrames() || resumeAfter) { |
1071 | | if (op == JSOP_FUNCALL) { |
1072 | | // For fun.call(this, ...); the reconstructStackDepth will |
1073 | | // include the this. When inlining that is not included. |
1074 | | // So the exprStackSlots will be one less. |
1075 | | MOZ_ASSERT(expectedDepth - exprStackSlots <= 1); |
1076 | | } else if (iter.moreFrames() && (IsGetPropPC(pc) || IsSetPropPC(pc))) { |
1077 | | // Accessors coming out of ion are inlined via a complete |
1078 | | // lie perpetrated by the compiler internally. Ion just rearranges |
1079 | | // the stack, and pretends that it looked like a call all along. |
1080 | | // This means that the depth is actually one *more* than expected |
1081 | | // by the interpreter, as there is now a JSFunction, |this| and [arg], |
1082 | | // rather than the expected |this| and [arg] |
1083 | | // Note that none of that was pushed, but it's still reflected |
1084 | | // in exprStackSlots. |
1085 | | MOZ_ASSERT(exprStackSlots - expectedDepth == 1); |
1086 | | } else { |
1087 | | // For fun.apply({}, arguments) the reconstructStackDepth will |
1088 | | // have stackdepth 4, but it could be that we inlined the |
1089 | | // funapply. In that case exprStackSlots, will have the real |
1090 | | // arguments in the slots and not be 4. |
1091 | | MOZ_ASSERT(exprStackSlots == expectedDepth); |
1092 | | } |
1093 | | } |
1094 | | } |
1095 | | #endif |
1096 | | |
1097 | | #ifdef JS_JITSPEW |
1098 | | JitSpew(JitSpew_BaselineBailouts, " Resuming %s pc offset %d (op %s) (line %d) of %s:%u:%u", |
1099 | | resumeAfter ? "after" : "at", (int) pcOff, CodeName[op], |
1100 | | PCToLineNumber(script, pc), script->filename(), script->lineno(), script->column()); |
1101 | | JitSpew(JitSpew_BaselineBailouts, " Bailout kind: %s", |
1102 | | BailoutKindString(bailoutKind)); |
1103 | | #endif |
1104 | | |
1105 | 8 | bool pushedNewTarget = IsConstructorCallPC(pc); |
1106 | 8 | |
1107 | 8 | // If this was the last inline frame, or we are bailing out to a catch or |
1108 | 8 | // finally block in this frame, then unpacking is almost done. |
1109 | 8 | if (!iter.moreFrames() || catchingException) { |
1110 | 8 | // If the bailout was a resumeAfter, and the opcode is monitored, |
1111 | 8 | // then the bailed out state should be in a position to enter |
1112 | 8 | // into the ICTypeMonitor chain for the op. |
1113 | 8 | bool enterMonitorChain = false; |
1114 | 8 | if (resumeAfter && (CodeSpec[op].format & JOF_TYPESET)) { |
1115 | 8 | // Not every monitored op has a monitored fallback stub, e.g. |
1116 | 8 | // JSOP_NEWOBJECT, which always returns the same type for a |
1117 | 8 | // particular script/pc location. |
1118 | 8 | ICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff); |
1119 | 8 | ICFallbackStub* fallbackStub = icEntry.firstStub()->getChainFallback(); |
1120 | 8 | if (fallbackStub->isMonitoredFallback()) { |
1121 | 8 | enterMonitorChain = true; |
1122 | 8 | } |
1123 | 8 | } |
1124 | 8 | |
1125 | 8 | uint32_t numUses = js::StackUses(pc); |
1126 | 8 | |
1127 | 8 | if (resumeAfter && !enterMonitorChain) { |
1128 | 0 | pc = GetNextPc(pc); |
1129 | 0 | } |
1130 | 8 | |
1131 | 8 | builder.setResumePC(pc); |
1132 | 8 | builder.setResumeFramePtr(prevFramePtr); |
1133 | 8 | |
1134 | 8 | if (enterMonitorChain) { |
1135 | 8 | ICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff); |
1136 | 8 | ICFallbackStub* fallbackStub = icEntry.firstStub()->getChainFallback(); |
1137 | 8 | MOZ_ASSERT(fallbackStub->isMonitoredFallback()); |
1138 | 8 | JitSpew(JitSpew_BaselineBailouts, " [TYPE-MONITOR CHAIN]"); |
1139 | 8 | |
1140 | 8 | ICTypeMonitor_Fallback* typeMonitorFallback = |
1141 | 8 | fallbackStub->toMonitoredFallbackStub()->getFallbackMonitorStub(cx, script); |
1142 | 8 | if (!typeMonitorFallback) { |
1143 | 0 | return false; |
1144 | 0 | } |
1145 | 8 | |
1146 | 8 | ICStub* firstMonStub = typeMonitorFallback->firstMonitorStub(); |
1147 | 8 | |
1148 | 8 | // To enter a monitoring chain, we load the top stack value into R0 |
1149 | 8 | JitSpew(JitSpew_BaselineBailouts, " Popping top stack value into R0."); |
1150 | 8 | builder.popValueInto(PCMappingSlotInfo::SlotInR0); |
1151 | 8 | frameSize -= sizeof(Value); |
1152 | 8 | |
1153 | 8 | if (JSOp(*pc) == JSOP_GETELEM_SUPER) { |
1154 | 0 | // Push a fake value so that the stack stays balanced. |
1155 | 0 | if (!builder.writeValue(UndefinedValue(), "GETELEM_SUPER stack balance")) { |
1156 | 0 | return false; |
1157 | 0 | } |
1158 | 0 | frameSize += sizeof(Value); |
1159 | 0 | } |
1160 | 8 | |
1161 | 8 | // Update the frame's frame size. |
1162 | 8 | blFrame->setFrameSize(frameSize); |
1163 | 8 | JitSpew(JitSpew_BaselineBailouts, " Adjusted framesize: %u", unsigned(frameSize)); |
1164 | 8 | |
1165 | 8 | // If resuming into a JSOP_CALL, baseline keeps the arguments on the |
1166 | 8 | // stack and pops them only after returning from the call IC. |
1167 | 8 | // Push undefs onto the stack in anticipation of the popping of the |
1168 | 8 | // callee, thisv, and actual arguments passed from the caller's frame. |
1169 | 8 | if (IsCallPC(pc)) { |
1170 | 2 | uint32_t numCallArgs = numUses - 2 - uint32_t(pushedNewTarget); |
1171 | 2 | if (!builder.writeValue(UndefinedValue(), "CallOp FillerCallee")) { |
1172 | 0 | return false; |
1173 | 0 | } |
1174 | 2 | if (!builder.writeValue(UndefinedValue(), "CallOp FillerThis")) { |
1175 | 0 | return false; |
1176 | 0 | } |
1177 | 2 | for (uint32_t i = 0; i < numCallArgs; i++) { |
1178 | 0 | if (!builder.writeValue(UndefinedValue(), "CallOp FillerArg")) { |
1179 | 0 | return false; |
1180 | 0 | } |
1181 | 0 | } |
1182 | 2 | if (pushedNewTarget) { |
1183 | 0 | if (!builder.writeValue(UndefinedValue(), "CallOp FillerNewTarget")) { |
1184 | 0 | return false; |
1185 | 0 | } |
1186 | 2 | } |
1187 | 2 | |
1188 | 2 | frameSize += numUses * sizeof(Value); |
1189 | 2 | blFrame->setFrameSize(frameSize); |
1190 | 2 | JitSpew(JitSpew_BaselineBailouts, " Adjusted framesize += %d: %d", |
1191 | 2 | (int) (numUses * sizeof(Value)), |
1192 | 2 | (int) frameSize); |
1193 | 2 | } |
1194 | 8 | |
1195 | 8 | // Set the resume address to the return point from the IC, and set |
1196 | 8 | // the monitor stub addr. |
1197 | 8 | builder.setResumeAddr(baselineScript->returnAddressForIC(icEntry)); |
1198 | 8 | builder.setMonitorStub(firstMonStub); |
1199 | 8 | JitSpew(JitSpew_BaselineBailouts, " Set resumeAddr=%p monitorStub=%p", |
1200 | 8 | baselineScript->returnAddressForIC(icEntry), firstMonStub); |
1201 | 8 | |
1202 | 8 | } else { |
1203 | 0 | // If needed, initialize BaselineBailoutInfo's valueR0 and/or valueR1 with the |
1204 | 0 | // top stack values. |
1205 | 0 | // |
1206 | 0 | // Note that we use the 'maybe' variant of nativeCodeForPC because |
1207 | 0 | // of exception propagation for debug mode. See note below. |
1208 | 0 | PCMappingSlotInfo slotInfo; |
1209 | 0 | uint8_t* nativeCodeForPC; |
1210 | 0 |
|
1211 | 0 | if (excInfo && excInfo->propagatingIonExceptionForDebugMode()) { |
1212 | 0 | // When propagating an exception for debug mode, set the |
1213 | 0 | // resume pc to the throwing pc, so that Debugger hooks report |
1214 | 0 | // the correct pc offset of the throwing op instead of its |
1215 | 0 | // successor (this pc will be used as the BaselineFrame's |
1216 | 0 | // override pc). |
1217 | 0 | // |
1218 | 0 | // Note that we never resume at this pc, it is set for the sake |
1219 | 0 | // of frame iterators giving the correct answer. |
1220 | 0 | jsbytecode* throwPC = script->offsetToPC(iter.pcOffset()); |
1221 | 0 | builder.setResumePC(throwPC); |
1222 | 0 | nativeCodeForPC = baselineScript->nativeCodeForPC(script, throwPC); |
1223 | 0 | } else { |
1224 | 0 | nativeCodeForPC = baselineScript->nativeCodeForPC(script, pc, &slotInfo); |
1225 | 0 | } |
1226 | 0 | MOZ_ASSERT(nativeCodeForPC); |
1227 | 0 |
|
1228 | 0 | unsigned numUnsynced = slotInfo.numUnsynced(); |
1229 | 0 |
|
1230 | 0 | MOZ_ASSERT(numUnsynced <= 2); |
1231 | 0 | PCMappingSlotInfo::SlotLocation loc1, loc2; |
1232 | 0 | if (numUnsynced > 0) { |
1233 | 0 | loc1 = slotInfo.topSlotLocation(); |
1234 | 0 | JitSpew(JitSpew_BaselineBailouts, " Popping top stack value into %d.", |
1235 | 0 | (int) loc1); |
1236 | 0 | builder.popValueInto(loc1); |
1237 | 0 | } |
1238 | 0 | if (numUnsynced > 1) { |
1239 | 0 | loc2 = slotInfo.nextSlotLocation(); |
1240 | 0 | JitSpew(JitSpew_BaselineBailouts, " Popping next stack value into %d.", |
1241 | 0 | (int) loc2); |
1242 | 0 | MOZ_ASSERT_IF(loc1 != PCMappingSlotInfo::SlotIgnore, loc1 != loc2); |
1243 | 0 | builder.popValueInto(loc2); |
1244 | 0 | } |
1245 | 0 |
|
1246 | 0 | // Need to adjust the frameSize for the frame to match the values popped |
1247 | 0 | // into registers. |
1248 | 0 | frameSize -= sizeof(Value) * numUnsynced; |
1249 | 0 | blFrame->setFrameSize(frameSize); |
1250 | 0 | JitSpew(JitSpew_BaselineBailouts, " Adjusted framesize -= %d: %d", |
1251 | 0 | int(sizeof(Value) * numUnsynced), int(frameSize)); |
1252 | 0 |
|
1253 | 0 | // If envChain is nullptr, then bailout is occurring during argument check |
1254 | 0 | // or early in the script's execution. In this case, resume into the prologue. |
1255 | 0 | uint8_t* opReturnAddr; |
1256 | 0 | if (envChain == nullptr) { |
1257 | 0 | // Global and eval scripts expect the env chain in R1, so only |
1258 | 0 | // resume into the prologue for function scripts. |
1259 | 0 | MOZ_ASSERT(fun); |
1260 | 0 | MOZ_ASSERT(numUnsynced == 0); |
1261 | 0 | opReturnAddr = baselineScript->prologueEntryAddr(); |
1262 | 0 | JitSpew(JitSpew_BaselineBailouts, " Resuming into prologue."); |
1263 | 0 |
|
1264 | 0 | // Undo the progress for any loop entry we thought we were skipping |
1265 | 0 | // over earlier. |
1266 | 0 | if (skippedLoopEntry && script->trackRecordReplayProgress()) { |
1267 | 0 | --*mozilla::recordreplay::ExecutionProgressCounter(); |
1268 | 0 | } |
1269 | 0 | } else { |
1270 | 0 | opReturnAddr = nativeCodeForPC; |
1271 | 0 | } |
1272 | 0 | builder.setResumeAddr(opReturnAddr); |
1273 | 0 | JitSpew(JitSpew_BaselineBailouts, " Set resumeAddr=%p", opReturnAddr); |
1274 | 0 | } |
1275 | 8 | |
1276 | 8 | if (cx->runtime()->geckoProfiler().enabled()) { |
1277 | 0 | // Register bailout with profiler. |
1278 | 0 | const char* filename = script->filename(); |
1279 | 0 | if (filename == nullptr) { |
1280 | 0 | filename = "<unknown>"; |
1281 | 0 | } |
1282 | 0 | unsigned len = strlen(filename) + 200; |
1283 | 0 | UniqueChars buf(js_pod_malloc<char>(len)); |
1284 | 0 | if (buf == nullptr) { |
1285 | 0 | ReportOutOfMemory(cx); |
1286 | 0 | return false; |
1287 | 0 | } |
1288 | 0 | snprintf(buf.get(), len, "%s %s %s on line %u of %s:%u", |
1289 | 0 | BailoutKindString(bailoutKind), |
1290 | 0 | resumeAfter ? "after" : "at", |
1291 | 0 | CodeName[op], |
1292 | 0 | PCToLineNumber(script, pc), |
1293 | 0 | filename, |
1294 | 0 | script->lineno()); |
1295 | 0 | cx->runtime()->geckoProfiler().markEvent(buf.get()); |
1296 | 0 | } |
1297 | 8 | |
1298 | 8 | return true; |
1299 | 0 | } |
1300 | 0 | |
1301 | 0 | // Write out descriptor of BaselineJS frame. |
1302 | 0 | size_t baselineFrameDescr = MakeFrameDescriptor((uint32_t) builder.framePushed(), |
1303 | 0 | FrameType::BaselineJS, |
1304 | 0 | BaselineStubFrameLayout::Size()); |
1305 | 0 | if (!builder.writeWord(baselineFrameDescr, "Descriptor")) { |
1306 | 0 | return false; |
1307 | 0 | } |
1308 | 0 | |
1309 | 0 | // Calculate and write out return address. |
1310 | 0 | // The icEntry in question MUST have an inlinable fallback stub. |
1311 | 0 | ICEntry& icEntry = baselineScript->icEntryFromPCOffset(pcOff); |
1312 | 0 | MOZ_ASSERT(IsInlinableFallback(icEntry.firstStub()->getChainFallback())); |
1313 | 0 | if (!builder.writePtr(baselineScript->returnAddressForIC(icEntry), "ReturnAddr")) { |
1314 | 0 | return false; |
1315 | 0 | } |
1316 | 0 | |
1317 | 0 | // Build baseline stub frame: |
1318 | 0 | // +===============+ |
1319 | 0 | // | StubPtr | |
1320 | 0 | // +---------------+ |
1321 | 0 | // | FramePtr | |
1322 | 0 | // +---------------+ |
1323 | 0 | // | Padding? | |
1324 | 0 | // +---------------+ |
1325 | 0 | // | ArgA | |
1326 | 0 | // +---------------+ |
1327 | 0 | // | ... | |
1328 | 0 | // +---------------+ |
1329 | 0 | // | Arg0 | |
1330 | 0 | // +---------------+ |
1331 | 0 | // | ThisV | |
1332 | 0 | // +---------------+ |
1333 | 0 | // | ActualArgC | |
1334 | 0 | // +---------------+ |
1335 | 0 | // | CalleeToken | |
1336 | 0 | // +---------------+ |
1337 | 0 | // | Descr(BLStub) | |
1338 | 0 | // +---------------+ |
1339 | 0 | // | ReturnAddr | |
1340 | 0 | // +===============+ |
1341 | 0 | |
1342 | 0 | JitSpew(JitSpew_BaselineBailouts, " [BASELINE-STUB FRAME]"); |
1343 | 0 |
|
1344 | 0 | size_t startOfBaselineStubFrame = builder.framePushed(); |
1345 | 0 |
|
1346 | 0 | // Write stub pointer. |
1347 | 0 | MOZ_ASSERT(IsInlinableFallback(icEntry.fallbackStub())); |
1348 | 0 | if (!builder.writePtr(icEntry.fallbackStub(), "StubPtr")) { |
1349 | 0 | return false; |
1350 | 0 | } |
1351 | 0 | |
1352 | 0 | // Write previous frame pointer (saved earlier). |
1353 | 0 | if (!builder.writePtr(prevFramePtr, "PrevFramePtr")) { |
1354 | 0 | return false; |
1355 | 0 | } |
1356 | 0 | prevFramePtr = builder.virtualPointerAtStackOffset(0); |
1357 | 0 |
|
1358 | 0 | // Write out actual arguments (and thisv), copied from unpacked stack of BaselineJS frame. |
1359 | 0 | // Arguments are reversed on the BaselineJS frame's stack values. |
1360 | 0 | MOZ_ASSERT(IsIonInlinablePC(pc)); |
1361 | 0 | unsigned actualArgc; |
1362 | 0 | Value callee; |
1363 | 0 | if (needToSaveArgs) { |
1364 | 0 | // For FUNAPPLY or an accessor, the arguments are not on the stack anymore, |
1365 | 0 | // but they are copied in a vector and are written here. |
1366 | 0 | if (op == JSOP_FUNAPPLY) { |
1367 | 0 | actualArgc = blFrame->numActualArgs(); |
1368 | 0 | } else { |
1369 | 0 | actualArgc = IsSetPropPC(pc); |
1370 | 0 | } |
1371 | 0 | callee = savedCallerArgs[0]; |
1372 | 0 |
|
1373 | 0 | // Align the stack based on the number of arguments. |
1374 | 0 | size_t afterFrameSize = (actualArgc + 1) * sizeof(Value) + JitFrameLayout::Size(); |
1375 | 0 | if (!builder.maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) { |
1376 | 0 | return false; |
1377 | 0 | } |
1378 | 0 | |
1379 | 0 | // Push arguments. |
1380 | 0 | MOZ_ASSERT(actualArgc + 2 <= exprStackSlots); |
1381 | 0 | MOZ_ASSERT(savedCallerArgs.length() == actualArgc + 2); |
1382 | 0 | for (unsigned i = 0; i < actualArgc + 1; i++) { |
1383 | 0 | size_t arg = savedCallerArgs.length() - (i + 1); |
1384 | 0 | if (!builder.writeValue(savedCallerArgs[arg], "ArgVal")) { |
1385 | 0 | return false; |
1386 | 0 | } |
1387 | 0 | } |
1388 | 0 | } else { |
1389 | 0 | actualArgc = GET_ARGC(pc); |
1390 | 0 | if (op == JSOP_FUNCALL) { |
1391 | 0 | MOZ_ASSERT(actualArgc > 0); |
1392 | 0 | actualArgc--; |
1393 | 0 | } |
1394 | 0 |
|
1395 | 0 | // Align the stack based on the number of arguments. |
1396 | 0 | size_t afterFrameSize = (actualArgc + 1 + pushedNewTarget) * sizeof(Value) + |
1397 | 0 | JitFrameLayout::Size(); |
1398 | 0 | if (!builder.maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) { |
1399 | 0 | return false; |
1400 | 0 | } |
1401 | 0 | |
1402 | 0 | // Copy the arguments and |this| from the BaselineFrame, in reverse order. |
1403 | 0 | size_t valueSlot = blFrame->numValueSlots() - 1; |
1404 | 0 | size_t calleeSlot = valueSlot - actualArgc - 1 - pushedNewTarget; |
1405 | 0 |
|
1406 | 0 | for (size_t i = valueSlot; i > calleeSlot; i--) { |
1407 | 0 | Value v = *blFrame->valueSlot(i); |
1408 | 0 | if (!builder.writeValue(v, "ArgVal")) { |
1409 | 0 | return false; |
1410 | 0 | } |
1411 | 0 | } |
1412 | 0 |
|
1413 | 0 | callee = *blFrame->valueSlot(calleeSlot); |
1414 | 0 | } |
1415 | 0 |
|
1416 | 0 | // In case these arguments need to be copied on the stack again for a rectifier frame, |
1417 | 0 | // save the framePushed values here for later use. |
1418 | 0 | size_t endOfBaselineStubArgs = builder.framePushed(); |
1419 | 0 |
|
1420 | 0 | // Calculate frame size for descriptor. |
1421 | 0 | size_t baselineStubFrameSize = builder.framePushed() - startOfBaselineStubFrame; |
1422 | 0 | size_t baselineStubFrameDescr = MakeFrameDescriptor((uint32_t) baselineStubFrameSize, |
1423 | 0 | FrameType::BaselineStub, |
1424 | 0 | JitFrameLayout::Size()); |
1425 | 0 |
|
1426 | 0 | // Push actual argc |
1427 | 0 | if (!builder.writeWord(actualArgc, "ActualArgc")) { |
1428 | 0 | return false; |
1429 | 0 | } |
1430 | 0 | |
1431 | 0 | // Push callee token (must be a JS Function) |
1432 | 0 | JitSpew(JitSpew_BaselineBailouts, " Callee = %016" PRIx64, callee.asRawBits()); |
1433 | 0 |
|
1434 | 0 | JSFunction* calleeFun = &callee.toObject().as<JSFunction>(); |
1435 | 0 | if (!builder.writePtr(CalleeToToken(calleeFun, pushedNewTarget), "CalleeToken")) { |
1436 | 0 | return false; |
1437 | 0 | } |
1438 | 0 | nextCallee.set(calleeFun); |
1439 | 0 |
|
1440 | 0 | // Push BaselineStub frame descriptor |
1441 | 0 | if (!builder.writeWord(baselineStubFrameDescr, "Descriptor")) { |
1442 | 0 | return false; |
1443 | 0 | } |
1444 | 0 | |
1445 | 0 | // Ensure we have a TypeMonitor fallback stub so we don't crash in JIT code |
1446 | 0 | // when we try to enter it. See callers of offsetOfFallbackMonitorStub. |
1447 | 0 | if (CodeSpec[*pc].format & JOF_TYPESET) { |
1448 | 0 | ICFallbackStub* fallbackStub = icEntry.firstStub()->getChainFallback(); |
1449 | 0 | if (!fallbackStub->toMonitoredFallbackStub()->getFallbackMonitorStub(cx, script)) { |
1450 | 0 | return false; |
1451 | 0 | } |
1452 | 0 | } |
1453 | 0 | |
1454 | 0 | // Push return address into ICCall_Scripted stub, immediately after the call. |
1455 | 0 | void* baselineCallReturnAddr = GetStubReturnAddress(cx, pc); |
1456 | 0 | MOZ_ASSERT(baselineCallReturnAddr); |
1457 | 0 | if (!builder.writePtr(baselineCallReturnAddr, "ReturnAddr")) { |
1458 | 0 | return false; |
1459 | 0 | } |
1460 | 0 | MOZ_ASSERT(builder.framePushed() % JitStackAlignment == 0); |
1461 | 0 |
|
1462 | 0 | // If actualArgc >= fun->nargs, then we are done. Otherwise, we need to push on |
1463 | 0 | // a reconstructed rectifier frame. |
1464 | 0 | if (actualArgc >= calleeFun->nargs()) { |
1465 | 0 | return true; |
1466 | 0 | } |
1467 | 0 | |
1468 | 0 | // Push a reconstructed rectifier frame. |
1469 | 0 | // +===============+ |
1470 | 0 | // | Padding? | |
1471 | 0 | // +---------------+ |
1472 | 0 | // | UndefinedU | |
1473 | 0 | // +---------------+ |
1474 | 0 | // | ... | |
1475 | 0 | // +---------------+ |
1476 | 0 | // | Undefined0 | |
1477 | 0 | // +---------------+ |
1478 | 0 | // | ArgA | |
1479 | 0 | // +---------------+ |
1480 | 0 | // | ... | |
1481 | 0 | // +---------------+ |
1482 | 0 | // | Arg0 | |
1483 | 0 | // +---------------+ |
1484 | 0 | // | ThisV | |
1485 | 0 | // +---------------+ |
1486 | 0 | // | ActualArgC | |
1487 | 0 | // +---------------+ |
1488 | 0 | // | CalleeToken | |
1489 | 0 | // +---------------+ |
1490 | 0 | // | Descr(Rect) | |
1491 | 0 | // +---------------+ |
1492 | 0 | // | ReturnAddr | |
1493 | 0 | // +===============+ |
1494 | 0 | |
1495 | 0 | JitSpew(JitSpew_BaselineBailouts, " [RECTIFIER FRAME]"); |
1496 | 0 |
|
1497 | 0 | size_t startOfRectifierFrame = builder.framePushed(); |
1498 | 0 |
|
1499 | 0 | // On x86-only, the frame pointer is saved again in the rectifier frame. |
1500 | | #if defined(JS_CODEGEN_X86) |
1501 | | if (!builder.writePtr(prevFramePtr, "PrevFramePtr-X86Only")) { |
1502 | | return false; |
1503 | | } |
1504 | | // Follow the same logic as in JitRuntime::generateArgumentsRectifier. |
1505 | | prevFramePtr = builder.virtualPointerAtStackOffset(0); |
1506 | | if (!builder.writePtr(prevFramePtr, "Padding-X86Only")) { |
1507 | | return false; |
1508 | | } |
1509 | | #endif |
1510 | |
|
1511 | 0 | // Align the stack based on the number of arguments. |
1512 | 0 | size_t afterFrameSize = (calleeFun->nargs() + 1 + pushedNewTarget) * sizeof(Value) + |
1513 | 0 | RectifierFrameLayout::Size(); |
1514 | 0 | if (!builder.maybeWritePadding(JitStackAlignment, afterFrameSize, "Padding")) { |
1515 | 0 | return false; |
1516 | 0 | } |
1517 | 0 | |
1518 | 0 | // Copy new.target, if necessary. |
1519 | 0 | if (pushedNewTarget) { |
1520 | 0 | size_t newTargetOffset = (builder.framePushed() - endOfBaselineStubArgs) + |
1521 | 0 | (actualArgc + 1) * sizeof(Value); |
1522 | 0 | Value newTargetValue = *builder.valuePointerAtStackOffset(newTargetOffset); |
1523 | 0 | if (!builder.writeValue(newTargetValue, "CopiedNewTarget")) { |
1524 | 0 | return false; |
1525 | 0 | } |
1526 | 0 | } |
1527 | 0 | |
1528 | 0 | // Push undefined for missing arguments. |
1529 | 0 | for (unsigned i = 0; i < (calleeFun->nargs() - actualArgc); i++) { |
1530 | 0 | if (!builder.writeValue(UndefinedValue(), "FillerVal")) { |
1531 | 0 | return false; |
1532 | 0 | } |
1533 | 0 | } |
1534 | 0 |
|
1535 | 0 | // Copy arguments + thisv from BaselineStub frame. |
1536 | 0 | if (!builder.subtract((actualArgc + 1) * sizeof(Value), "CopiedArgs")) { |
1537 | 0 | return false; |
1538 | 0 | } |
1539 | 0 | BufferPointer<uint8_t> stubArgsEnd = |
1540 | 0 | builder.pointerAtStackOffset<uint8_t>(builder.framePushed() - endOfBaselineStubArgs); |
1541 | 0 | JitSpew(JitSpew_BaselineBailouts, " MemCpy from %p", stubArgsEnd.get()); |
1542 | 0 | memcpy(builder.pointerAtStackOffset<uint8_t>(0).get(), stubArgsEnd.get(), |
1543 | 0 | (actualArgc + 1) * sizeof(Value)); |
1544 | 0 |
|
1545 | 0 | // Calculate frame size for descriptor. |
1546 | 0 | size_t rectifierFrameSize = builder.framePushed() - startOfRectifierFrame; |
1547 | 0 | size_t rectifierFrameDescr = MakeFrameDescriptor((uint32_t) rectifierFrameSize, |
1548 | 0 | FrameType::Rectifier, |
1549 | 0 | JitFrameLayout::Size()); |
1550 | 0 |
|
1551 | 0 | // Push actualArgc |
1552 | 0 | if (!builder.writeWord(actualArgc, "ActualArgc")) { |
1553 | 0 | return false; |
1554 | 0 | } |
1555 | 0 | |
1556 | 0 | // Push calleeToken again. |
1557 | 0 | if (!builder.writePtr(CalleeToToken(calleeFun, pushedNewTarget), "CalleeToken")) { |
1558 | 0 | return false; |
1559 | 0 | } |
1560 | 0 | |
1561 | 0 | // Push rectifier frame descriptor |
1562 | 0 | if (!builder.writeWord(rectifierFrameDescr, "Descriptor")) { |
1563 | 0 | return false; |
1564 | 0 | } |
1565 | 0 | |
1566 | 0 | // Push return address into the ArgumentsRectifier code, immediately after the ioncode |
1567 | 0 | // call. |
1568 | 0 | void* rectReturnAddr = cx->runtime()->jitRuntime()->getArgumentsRectifierReturnAddr().value; |
1569 | 0 | MOZ_ASSERT(rectReturnAddr); |
1570 | 0 | if (!builder.writePtr(rectReturnAddr, "ReturnAddr")) { |
1571 | 0 | return false; |
1572 | 0 | } |
1573 | 0 | MOZ_ASSERT(builder.framePushed() % JitStackAlignment == 0); |
1574 | 0 |
|
1575 | 0 | return true; |
1576 | 0 | } |
1577 | | |
1578 | | uint32_t |
1579 | | jit::BailoutIonToBaseline(JSContext* cx, JitActivation* activation, |
1580 | | const JSJitFrameIter& iter, bool invalidate, |
1581 | | BaselineBailoutInfo** bailoutInfo, |
1582 | | const ExceptionBailoutInfo* excInfo) |
1583 | 8 | { |
1584 | 8 | MOZ_ASSERT(bailoutInfo != nullptr); |
1585 | 8 | MOZ_ASSERT(*bailoutInfo == nullptr); |
1586 | 8 | MOZ_ASSERT(iter.isBailoutJS()); |
1587 | 8 | |
1588 | 8 | TraceLoggerThread* logger = TraceLoggerForCurrentThread(cx); |
1589 | 8 | TraceLogStopEvent(logger, TraceLogger_IonMonkey); |
1590 | 8 | TraceLogStartEvent(logger, TraceLogger_Baseline); |
1591 | 8 | |
1592 | 8 | // Ion bailout can fail due to overrecursion and OOM. In such cases we |
1593 | 8 | // cannot honor any further Debugger hooks on the frame, and need to |
1594 | 8 | // ensure that its Debugger.Frame entry is cleaned up. |
1595 | 8 | auto guardRemoveRematerializedFramesFromDebugger = mozilla::MakeScopeExit([&] { |
1596 | 0 | activation->removeRematerializedFramesFromDebugger(cx, iter.fp()); |
1597 | 0 | }); |
1598 | 8 | |
1599 | 8 | // Always remove the RInstructionResults from the JitActivation, even in |
1600 | 8 | // case of failures as the stack frame is going away after the bailout. |
1601 | 8 | auto removeIonFrameRecovery = mozilla::MakeScopeExit([&] { |
1602 | 8 | activation->removeIonFrameRecovery(iter.jsFrame()); |
1603 | 8 | }); |
1604 | 8 | |
1605 | 8 | // The caller of the top frame must be one of the following: |
1606 | 8 | // IonJS - Ion calling into Ion. |
1607 | 8 | // BaselineStub - Baseline calling into Ion. |
1608 | 8 | // Entry / WasmToJSJit - Interpreter or other (wasm) calling into Ion. |
1609 | 8 | // Rectifier - Arguments rectifier calling into Ion. |
1610 | 8 | MOZ_ASSERT(iter.isBailoutJS()); |
1611 | | #if defined(DEBUG) || defined(JS_JITSPEW) |
1612 | | FrameType prevFrameType = iter.prevType(); |
1613 | | MOZ_ASSERT(JSJitFrameIter::isEntry(prevFrameType) || |
1614 | | prevFrameType == FrameType::IonJS || |
1615 | | prevFrameType == FrameType::BaselineStub || |
1616 | | prevFrameType == FrameType::Rectifier || |
1617 | | prevFrameType == FrameType::IonICCall); |
1618 | | #endif |
1619 | | |
1620 | 8 | // All incoming frames are going to look like this: |
1621 | 8 | // |
1622 | 8 | // +---------------+ |
1623 | 8 | // | ... | |
1624 | 8 | // +---------------+ |
1625 | 8 | // | Args | |
1626 | 8 | // | ... | |
1627 | 8 | // +---------------+ |
1628 | 8 | // | ThisV | |
1629 | 8 | // +---------------+ |
1630 | 8 | // | ActualArgC | |
1631 | 8 | // +---------------+ |
1632 | 8 | // | CalleeToken | |
1633 | 8 | // +---------------+ |
1634 | 8 | // | Descriptor | |
1635 | 8 | // +---------------+ |
1636 | 8 | // | ReturnAddr | |
1637 | 8 | // +---------------+ |
1638 | 8 | // | ||||| | <---- Overwrite starting here. |
1639 | 8 | // | ||||| | |
1640 | 8 | // | ||||| | |
1641 | 8 | // +---------------+ |
1642 | 8 | |
1643 | 8 | JitSpew(JitSpew_BaselineBailouts, "Bailing to baseline %s:%u:%u (IonScript=%p) (FrameType=%d)", |
1644 | 8 | iter.script()->filename(), iter.script()->lineno(), iter.script()->column(), (void*) iter.ionScript(), |
1645 | 8 | (int) prevFrameType); |
1646 | 8 | |
1647 | 8 | bool catchingException; |
1648 | 8 | bool propagatingExceptionForDebugMode; |
1649 | 8 | if (excInfo) { |
1650 | 0 | catchingException = excInfo->catchingException(); |
1651 | 0 | propagatingExceptionForDebugMode = excInfo->propagatingIonExceptionForDebugMode(); |
1652 | 0 |
|
1653 | 0 | if (catchingException) { |
1654 | 0 | JitSpew(JitSpew_BaselineBailouts, "Resuming in catch or finally block"); |
1655 | 0 | } |
1656 | 0 |
|
1657 | 0 | if (propagatingExceptionForDebugMode) { |
1658 | 0 | JitSpew(JitSpew_BaselineBailouts, "Resuming in-place for debug mode"); |
1659 | 0 | } |
1660 | 8 | } else { |
1661 | 8 | catchingException = false; |
1662 | 8 | propagatingExceptionForDebugMode = false; |
1663 | 8 | } |
1664 | 8 | |
1665 | 8 | JitSpew(JitSpew_BaselineBailouts, " Reading from snapshot offset %u size %zu", |
1666 | 8 | iter.snapshotOffset(), iter.ionScript()->snapshotsListSize()); |
1667 | 8 | |
1668 | 8 | if (!excInfo) { |
1669 | 8 | iter.ionScript()->incNumBailouts(); |
1670 | 8 | } |
1671 | 8 | iter.script()->updateJitCodeRaw(cx->runtime()); |
1672 | 8 | |
1673 | 8 | // Allocate buffer to hold stack replacement data. |
1674 | 8 | BaselineStackBuilder builder(iter, 1024); |
1675 | 8 | if (!builder.init()) { |
1676 | 0 | ReportOutOfMemory(cx); |
1677 | 0 | return BAILOUT_RETURN_FATAL_ERROR; |
1678 | 0 | } |
1679 | 8 | JitSpew(JitSpew_BaselineBailouts, " Incoming frame ptr = %p", builder.startFrame()); |
1680 | 8 | |
1681 | 8 | // Under a bailout, there is no need to invalidate the frame after |
1682 | 8 | // evaluating the recover instruction, as the invalidation is only needed in |
1683 | 8 | // cases where the frame is introspected ahead of the bailout. |
1684 | 8 | MaybeReadFallback recoverBailout(cx, activation, &iter, MaybeReadFallback::Fallback_DoNothing); |
1685 | 8 | |
1686 | 8 | // Ensure that all value locations are readable from the SnapshotIterator. |
1687 | 8 | // Get the RInstructionResults from the JitActivation if the frame got |
1688 | 8 | // recovered ahead of the bailout. |
1689 | 8 | SnapshotIterator snapIter(iter, activation->bailoutData()->machineState()); |
1690 | 8 | if (!snapIter.initInstructionResults(recoverBailout)) { |
1691 | 0 | ReportOutOfMemory(cx); |
1692 | 0 | return BAILOUT_RETURN_FATAL_ERROR; |
1693 | 0 | } |
1694 | 8 | |
1695 | | #ifdef TRACK_SNAPSHOTS |
1696 | | snapIter.spewBailingFrom(); |
1697 | | #endif |
1698 | | |
1699 | 8 | RootedFunction callee(cx, iter.maybeCallee()); |
1700 | 8 | RootedScript scr(cx, iter.script()); |
1701 | 8 | if (callee) { |
1702 | 8 | JitSpew(JitSpew_BaselineBailouts, " Callee function (%s:%u:%u)", |
1703 | 8 | scr->filename(), scr->lineno(), scr->column()); |
1704 | 8 | } else { |
1705 | 0 | JitSpew(JitSpew_BaselineBailouts, " No callee!"); |
1706 | 0 | } |
1707 | 8 | |
1708 | 8 | if (iter.isConstructing()) { |
1709 | 0 | JitSpew(JitSpew_BaselineBailouts, " Constructing!"); |
1710 | 8 | } else { |
1711 | 8 | JitSpew(JitSpew_BaselineBailouts, " Not constructing!"); |
1712 | 8 | } |
1713 | 8 | |
1714 | 8 | JitSpew(JitSpew_BaselineBailouts, " Restoring frames:"); |
1715 | 8 | size_t frameNo = 0; |
1716 | 8 | |
1717 | 8 | // Reconstruct baseline frames using the builder. |
1718 | 8 | RootedFunction fun(cx, callee); |
1719 | 8 | Rooted<GCVector<Value>> startFrameFormals(cx, GCVector<Value>(cx)); |
1720 | 8 | |
1721 | 8 | gc::AutoSuppressGC suppress(cx); |
1722 | 8 | |
1723 | 8 | while (true) { |
1724 | 8 | // Skip recover instructions as they are already recovered by |initInstructionResults|. |
1725 | 8 | snapIter.settleOnFrame(); |
1726 | 8 | |
1727 | 8 | if (frameNo > 0) { |
1728 | 0 | // TraceLogger doesn't create entries for inlined frames. But we |
1729 | 0 | // see them in Baseline. Here we create the start events of those |
1730 | 0 | // entries. So they correspond to what we will see in Baseline. |
1731 | 0 | TraceLoggerEvent scriptEvent(TraceLogger_Scripts, scr); |
1732 | 0 | TraceLogStartEvent(logger, scriptEvent); |
1733 | 0 | TraceLogStartEvent(logger, TraceLogger_Baseline); |
1734 | 0 | } |
1735 | 8 | |
1736 | 8 | JitSpew(JitSpew_BaselineBailouts, " FrameNo %zu", frameNo); |
1737 | 8 | |
1738 | 8 | // If we are bailing out to a catch or finally block in this frame, |
1739 | 8 | // pass excInfo to InitFromBailout and don't unpack any other frames. |
1740 | 8 | bool handleException = (catchingException && excInfo->frameNo() == frameNo); |
1741 | 8 | |
1742 | 8 | // We also need to pass excInfo if we're bailing out in place for |
1743 | 8 | // debug mode. |
1744 | 8 | bool passExcInfo = handleException || propagatingExceptionForDebugMode; |
1745 | 8 | |
1746 | 8 | RootedFunction nextCallee(cx, nullptr); |
1747 | 8 | if (!InitFromBailout(cx, frameNo, fun, scr, |
1748 | 8 | snapIter, invalidate, builder, &startFrameFormals, |
1749 | 8 | &nextCallee, passExcInfo ? excInfo : nullptr)) |
1750 | 0 | { |
1751 | 0 | return BAILOUT_RETURN_FATAL_ERROR; |
1752 | 0 | } |
1753 | 8 | |
1754 | 8 | if (!snapIter.moreFrames()) { |
1755 | 8 | MOZ_ASSERT(!nextCallee); |
1756 | 8 | break; |
1757 | 8 | } |
1758 | 0 | |
1759 | 0 | if (handleException) { |
1760 | 0 | break; |
1761 | 0 | } |
1762 | 0 | |
1763 | 0 | MOZ_ASSERT(nextCallee); |
1764 | 0 | fun = nextCallee; |
1765 | 0 | scr = fun->existingScript(); |
1766 | 0 |
|
1767 | 0 | frameNo++; |
1768 | 0 |
|
1769 | 0 | snapIter.nextInstruction(); |
1770 | 0 | } |
1771 | 8 | JitSpew(JitSpew_BaselineBailouts, " Done restoring frames"); |
1772 | 8 | |
1773 | 8 | BailoutKind bailoutKind = snapIter.bailoutKind(); |
1774 | 8 | |
1775 | 8 | if (!startFrameFormals.empty()) { |
1776 | 8 | // Set the first frame's formals, see the comment in InitFromBailout. |
1777 | 8 | Value* argv = builder.startFrame()->argv() + 1; // +1 to skip |this|. |
1778 | 8 | mozilla::PodCopy(argv, startFrameFormals.begin(), startFrameFormals.length()); |
1779 | 8 | } |
1780 | 8 | |
1781 | 8 | // Do stack check. |
1782 | 8 | bool overRecursed = false; |
1783 | 8 | BaselineBailoutInfo *info = builder.info(); |
1784 | 8 | uint8_t* newsp = info->incomingStack - (info->copyStackTop - info->copyStackBottom); |
1785 | | #ifdef JS_SIMULATOR |
1786 | | if (Simulator::Current()->overRecursed(uintptr_t(newsp))) { |
1787 | | overRecursed = true; |
1788 | | } |
1789 | | #else |
1790 | 8 | if (!CheckRecursionLimitWithStackPointerDontReport(cx, newsp)) { |
1791 | 0 | overRecursed = true; |
1792 | 0 | } |
1793 | 8 | #endif |
1794 | 8 | if (overRecursed) { |
1795 | 0 | JitSpew(JitSpew_BaselineBailouts, " Overrecursion check failed!"); |
1796 | 0 | return BAILOUT_RETURN_OVERRECURSED; |
1797 | 0 | } |
1798 | 8 | |
1799 | 8 | // Take the reconstructed baseline stack so it doesn't get freed when builder destructs. |
1800 | 8 | info = builder.takeBuffer(); |
1801 | 8 | info->numFrames = frameNo + 1; |
1802 | 8 | info->bailoutKind = bailoutKind; |
1803 | 8 | *bailoutInfo = info; |
1804 | 8 | guardRemoveRematerializedFramesFromDebugger.release(); |
1805 | 8 | return BAILOUT_RETURN_OK; |
1806 | 8 | } |
1807 | | |
1808 | | static void |
1809 | | InvalidateAfterBailout(JSContext* cx, HandleScript outerScript, const char* reason) |
1810 | 3 | { |
1811 | 3 | // In some cases, the computation of recover instruction can invalidate the |
1812 | 3 | // Ion script before we reach the end of the bailout. Thus, if the outer |
1813 | 3 | // script no longer have any Ion script attached, then we just skip the |
1814 | 3 | // invalidation. |
1815 | 3 | // |
1816 | 3 | // For example, such case can happen if the template object for an unboxed |
1817 | 3 | // objects no longer match the content of its properties (see Bug 1174547) |
1818 | 3 | if (!outerScript->hasIonScript()) { |
1819 | 0 | JitSpew(JitSpew_BaselineBailouts, "Ion script is already invalidated"); |
1820 | 0 | return; |
1821 | 0 | } |
1822 | 3 | |
1823 | 3 | MOZ_ASSERT(!outerScript->ionScript()->invalidated()); |
1824 | 3 | |
1825 | 3 | JitSpew(JitSpew_BaselineBailouts, "Invalidating due to %s", reason); |
1826 | 3 | Invalidate(cx, outerScript); |
1827 | 3 | } |
1828 | | |
1829 | | static void |
1830 | | HandleBoundsCheckFailure(JSContext* cx, HandleScript outerScript, HandleScript innerScript) |
1831 | 0 | { |
1832 | 0 | JitSpew(JitSpew_IonBailouts, "Bounds check failure %s:%u:%u, inlined into %s:%u:%u", |
1833 | 0 | innerScript->filename(), innerScript->lineno(), innerScript->column(), |
1834 | 0 | outerScript->filename(), outerScript->lineno(), outerScript->column()); |
1835 | 0 |
|
1836 | 0 | if (!innerScript->failedBoundsCheck()) { |
1837 | 0 | innerScript->setFailedBoundsCheck(); |
1838 | 0 | } |
1839 | 0 |
|
1840 | 0 | InvalidateAfterBailout(cx, outerScript, "bounds check failure"); |
1841 | 0 | if (innerScript->hasIonScript()) { |
1842 | 0 | Invalidate(cx, innerScript); |
1843 | 0 | } |
1844 | 0 | } |
1845 | | |
1846 | | static void |
1847 | | HandleShapeGuardFailure(JSContext* cx, HandleScript outerScript, HandleScript innerScript) |
1848 | 3 | { |
1849 | 3 | JitSpew(JitSpew_IonBailouts, "Shape guard failure %s:%u:%u, inlined into %s:%u:%u", |
1850 | 3 | innerScript->filename(), innerScript->lineno(), innerScript->column(), |
1851 | 3 | outerScript->filename(), outerScript->lineno(), outerScript->column()); |
1852 | 3 | |
1853 | 3 | // TODO: Currently this mimic's Ion's handling of this case. Investigate setting |
1854 | 3 | // the flag on innerScript as opposed to outerScript, and maybe invalidating both |
1855 | 3 | // inner and outer scripts, instead of just the outer one. |
1856 | 3 | outerScript->setFailedShapeGuard(); |
1857 | 3 | |
1858 | 3 | InvalidateAfterBailout(cx, outerScript, "shape guard failure"); |
1859 | 3 | } |
1860 | | |
1861 | | static void |
1862 | | HandleBaselineInfoBailout(JSContext* cx, HandleScript outerScript, HandleScript innerScript) |
1863 | 0 | { |
1864 | 0 | JitSpew(JitSpew_IonBailouts, "Baseline info failure %s:%u:%u, inlined into %s:%u:%u", |
1865 | 0 | innerScript->filename(), innerScript->lineno(), innerScript->column(), |
1866 | 0 | outerScript->filename(), outerScript->lineno(), outerScript->column()); |
1867 | 0 |
|
1868 | 0 | InvalidateAfterBailout(cx, outerScript, "invalid baseline info"); |
1869 | 0 | } |
1870 | | |
1871 | | static void |
1872 | | HandleLexicalCheckFailure(JSContext* cx, HandleScript outerScript, HandleScript innerScript) |
1873 | 0 | { |
1874 | 0 | JitSpew(JitSpew_IonBailouts, "Lexical check failure %s:%u:%u, inlined into %s:%u:%u", |
1875 | 0 | innerScript->filename(), innerScript->lineno(), innerScript->column(), |
1876 | 0 | outerScript->filename(), outerScript->lineno(), outerScript->column()); |
1877 | 0 |
|
1878 | 0 | if (!innerScript->failedLexicalCheck()) { |
1879 | 0 | innerScript->setFailedLexicalCheck(); |
1880 | 0 | } |
1881 | 0 |
|
1882 | 0 | InvalidateAfterBailout(cx, outerScript, "lexical check failure"); |
1883 | 0 | if (innerScript->hasIonScript()) { |
1884 | 0 | Invalidate(cx, innerScript); |
1885 | 0 | } |
1886 | 0 | } |
1887 | | |
1888 | | static bool |
1889 | | CopyFromRematerializedFrame(JSContext* cx, JitActivation* act, uint8_t* fp, size_t inlineDepth, |
1890 | | BaselineFrame* frame) |
1891 | 0 | { |
1892 | 0 | RematerializedFrame* rematFrame = act->lookupRematerializedFrame(fp, inlineDepth); |
1893 | 0 |
|
1894 | 0 | // We might not have rematerialized a frame if the user never requested a |
1895 | 0 | // Debugger.Frame for it. |
1896 | 0 | if (!rematFrame) { |
1897 | 0 | return true; |
1898 | 0 | } |
1899 | 0 | |
1900 | 0 | MOZ_ASSERT(rematFrame->script() == frame->script()); |
1901 | 0 | MOZ_ASSERT(rematFrame->numActualArgs() == frame->numActualArgs()); |
1902 | 0 |
|
1903 | 0 | frame->setEnvironmentChain(rematFrame->environmentChain()); |
1904 | 0 |
|
1905 | 0 | if (frame->isFunctionFrame()) { |
1906 | 0 | frame->thisArgument() = rematFrame->thisArgument(); |
1907 | 0 | } |
1908 | 0 |
|
1909 | 0 | for (unsigned i = 0; i < frame->numActualArgs(); i++) { |
1910 | 0 | frame->argv()[i] = rematFrame->argv()[i]; |
1911 | 0 | } |
1912 | 0 |
|
1913 | 0 | for (size_t i = 0; i < frame->script()->nfixed(); i++) { |
1914 | 0 | *frame->valueSlot(i) = rematFrame->locals()[i]; |
1915 | 0 | } |
1916 | 0 |
|
1917 | 0 | frame->setReturnValue(rematFrame->returnValue()); |
1918 | 0 |
|
1919 | 0 | // Don't copy over the hasCachedSavedFrame bit. The new BaselineFrame we're |
1920 | 0 | // building has a different AbstractFramePtr, so it won't be found in the |
1921 | 0 | // LiveSavedFrameCache if we look there. |
1922 | 0 |
|
1923 | 0 | JitSpew(JitSpew_BaselineBailouts, |
1924 | 0 | " Copied from rematerialized frame at (%p,%zu)", |
1925 | 0 | fp, inlineDepth); |
1926 | 0 |
|
1927 | 0 | // Propagate the debuggee frame flag. For the case where the Debugger did |
1928 | 0 | // not rematerialize an Ion frame, the baseline frame has its debuggee |
1929 | 0 | // flag set iff its script is considered a debuggee. See the debuggee case |
1930 | 0 | // in InitFromBailout. |
1931 | 0 | if (rematFrame->isDebuggee()) { |
1932 | 0 | frame->setIsDebuggee(); |
1933 | 0 | return Debugger::handleIonBailout(cx, rematFrame, frame); |
1934 | 0 | } |
1935 | 0 | |
1936 | 0 | return true; |
1937 | 0 | } |
1938 | | |
1939 | | uint32_t |
1940 | | jit::FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo) |
1941 | 8 | { |
1942 | 8 | // The caller pushes R0 and R1 on the stack without rooting them. |
1943 | 8 | // Since GC here is very unlikely just suppress it. |
1944 | 8 | JSContext* cx = TlsContext.get(); |
1945 | 8 | js::gc::AutoSuppressGC suppressGC(cx); |
1946 | 8 | |
1947 | 8 | JitSpew(JitSpew_BaselineBailouts, " Done restoring frames"); |
1948 | 8 | |
1949 | 8 | // The current native code pc may not have a corresponding ICEntry, so we |
1950 | 8 | // store the bytecode pc in the frame for frame iterators. This pc is |
1951 | 8 | // cleared at the end of this function. If we return false, we don't clear |
1952 | 8 | // it: the exception handler also needs it and will clear it for us. |
1953 | 8 | BaselineFrame* topFrame = GetTopBaselineFrame(cx); |
1954 | 8 | topFrame->setOverridePc(bailoutInfo->resumePC); |
1955 | 8 | |
1956 | 8 | jsbytecode* faultPC = bailoutInfo->faultPC; |
1957 | 8 | jsbytecode* tryPC = bailoutInfo->tryPC; |
1958 | 8 | uint32_t numFrames = bailoutInfo->numFrames; |
1959 | 8 | MOZ_ASSERT(numFrames > 0); |
1960 | 8 | BailoutKind bailoutKind = bailoutInfo->bailoutKind; |
1961 | 8 | bool checkGlobalDeclarationConflicts = bailoutInfo->checkGlobalDeclarationConflicts; |
1962 | 8 | uint8_t* incomingStack = bailoutInfo->incomingStack; |
1963 | 8 | |
1964 | 8 | // We have to get rid of the rematerialized frame, whether it is |
1965 | 8 | // restored or unwound. |
1966 | 8 | auto guardRemoveRematerializedFramesFromDebugger = mozilla::MakeScopeExit([&] { |
1967 | 8 | JitActivation* act = cx->activation()->asJit(); |
1968 | 8 | act->removeRematerializedFramesFromDebugger(cx, incomingStack); |
1969 | 8 | }); |
1970 | 8 | |
1971 | 8 | // Free the bailout buffer. |
1972 | 8 | js_free(bailoutInfo); |
1973 | 8 | bailoutInfo = nullptr; |
1974 | 8 | |
1975 | 8 | if (topFrame->environmentChain()) { |
1976 | 8 | // Ensure the frame has a call object if it needs one. If the env chain |
1977 | 8 | // is nullptr, we will enter baseline code at the prologue so no need to do |
1978 | 8 | // anything in that case. |
1979 | 8 | if (!EnsureHasEnvironmentObjects(cx, topFrame)) { |
1980 | 0 | return false; |
1981 | 0 | } |
1982 | 8 | |
1983 | 8 | // If we bailed out before Ion could do the global declaration |
1984 | 8 | // conflicts check, because we resume in the body instead of the |
1985 | 8 | // prologue for global frames. |
1986 | 8 | if (checkGlobalDeclarationConflicts) { |
1987 | 0 | Rooted<LexicalEnvironmentObject*> lexicalEnv(cx, &cx->global()->lexicalEnvironment()); |
1988 | 0 | RootedScript script(cx, topFrame->script()); |
1989 | 0 | if (!CheckGlobalDeclarationConflicts(cx, script, lexicalEnv, cx->global())) { |
1990 | 0 | return false; |
1991 | 0 | } |
1992 | 8 | } |
1993 | 8 | } |
1994 | 8 | |
1995 | 8 | // Create arguments objects for bailed out frames, to maintain the invariant |
1996 | 8 | // that script->needsArgsObj() implies frame->hasArgsObj(). |
1997 | 8 | RootedScript innerScript(cx, nullptr); |
1998 | 8 | RootedScript outerScript(cx, nullptr); |
1999 | 8 | |
2000 | 8 | MOZ_ASSERT(cx->currentlyRunningInJit()); |
2001 | 8 | JSJitFrameIter iter(cx->activation()->asJit()); |
2002 | 8 | uint8_t* outerFp = nullptr; |
2003 | 8 | |
2004 | 8 | // Iter currently points at the exit frame. Get the previous frame |
2005 | 8 | // (which must be a baseline frame), and set it as the last profiling |
2006 | 8 | // frame. |
2007 | 8 | if (cx->runtime()->jitRuntime()->isProfilerInstrumentationEnabled(cx->runtime())) { |
2008 | 0 | cx->jitActivation->setLastProfilingFrame(iter.prevFp()); |
2009 | 0 | } |
2010 | 8 | |
2011 | 8 | uint32_t frameno = 0; |
2012 | 24 | while (frameno < numFrames) { |
2013 | 16 | MOZ_ASSERT(!iter.isIonJS()); |
2014 | 16 | |
2015 | 16 | if (iter.isBaselineJS()) { |
2016 | 8 | BaselineFrame* frame = iter.baselineFrame(); |
2017 | 8 | MOZ_ASSERT(frame->script()->hasBaselineScript()); |
2018 | 8 | |
2019 | 8 | // If the frame doesn't even have a env chain set yet, then it's resuming |
2020 | 8 | // into the the prologue before the env chain is initialized. Any |
2021 | 8 | // necessary args object will also be initialized there. |
2022 | 8 | if (frame->environmentChain() && frame->script()->needsArgsObj()) { |
2023 | 0 | ArgumentsObject* argsObj; |
2024 | 0 | if (frame->hasArgsObj()) { |
2025 | 0 | argsObj = &frame->argsObj(); |
2026 | 0 | } else { |
2027 | 0 | argsObj = ArgumentsObject::createExpected(cx, frame); |
2028 | 0 | if (!argsObj) { |
2029 | 0 | return false; |
2030 | 0 | } |
2031 | 0 | } |
2032 | 0 | |
2033 | 0 | // The arguments is a local binding and needsArgsObj does not |
2034 | 0 | // check if it is clobbered. Ensure that the local binding |
2035 | 0 | // restored during bailout before storing the arguments object |
2036 | 0 | // to the slot. |
2037 | 0 | RootedScript script(cx, frame->script()); |
2038 | 0 | SetFrameArgumentsObject(cx, frame, script, argsObj); |
2039 | 0 | } |
2040 | 8 | |
2041 | 8 | if (frameno == 0) { |
2042 | 8 | innerScript = frame->script(); |
2043 | 8 | } |
2044 | 8 | |
2045 | 8 | if (frameno == numFrames - 1) { |
2046 | 8 | outerScript = frame->script(); |
2047 | 8 | outerFp = iter.fp(); |
2048 | 8 | MOZ_ASSERT(outerFp == incomingStack); |
2049 | 8 | } |
2050 | 8 | |
2051 | 8 | frameno++; |
2052 | 8 | } |
2053 | 16 | |
2054 | 16 | ++iter; |
2055 | 16 | } |
2056 | 8 | |
2057 | 8 | MOZ_ASSERT(innerScript); |
2058 | 8 | MOZ_ASSERT(outerScript); |
2059 | 8 | MOZ_ASSERT(outerFp); |
2060 | 8 | |
2061 | 8 | // If we rematerialized Ion frames due to debug mode toggling, copy their |
2062 | 8 | // values into the baseline frame. We need to do this even when debug mode |
2063 | 8 | // is off, as we should respect the mutations made while debug mode was |
2064 | 8 | // on. |
2065 | 8 | JitActivation* act = cx->activation()->asJit(); |
2066 | 8 | if (act->hasRematerializedFrame(outerFp)) { |
2067 | 0 | JSJitFrameIter iter(act); |
2068 | 0 | size_t inlineDepth = numFrames; |
2069 | 0 | bool ok = true; |
2070 | 0 | while (inlineDepth > 0) { |
2071 | 0 | if (iter.isBaselineJS()) { |
2072 | 0 | // We must attempt to copy all rematerialized frames over, |
2073 | 0 | // even if earlier ones failed, to invoke the proper frame |
2074 | 0 | // cleanup in the Debugger. |
2075 | 0 | if (!CopyFromRematerializedFrame(cx, act, outerFp, --inlineDepth, |
2076 | 0 | iter.baselineFrame())) |
2077 | 0 | { |
2078 | 0 | ok = false; |
2079 | 0 | } |
2080 | 0 | } |
2081 | 0 | ++iter; |
2082 | 0 | } |
2083 | 0 |
|
2084 | 0 | if (!ok) { |
2085 | 0 | return false; |
2086 | 0 | } |
2087 | 0 | |
2088 | 0 | // After copying from all the rematerialized frames, remove them from |
2089 | 0 | // the table to keep the table up to date. |
2090 | 0 | guardRemoveRematerializedFramesFromDebugger.release(); |
2091 | 0 | act->removeRematerializedFrame(outerFp); |
2092 | 0 | } |
2093 | 8 | |
2094 | 8 | // If we are catching an exception, we need to unwind scopes. |
2095 | 8 | // See |SettleOnTryNote| |
2096 | 8 | if (cx->isExceptionPending() && faultPC) { |
2097 | 0 | EnvironmentIter ei(cx, topFrame, faultPC); |
2098 | 0 | UnwindEnvironment(cx, ei, tryPC); |
2099 | 0 | } |
2100 | 8 | |
2101 | 8 | JitSpew(JitSpew_BaselineBailouts, |
2102 | 8 | " Restored outerScript=(%s:%u:%u,%u) innerScript=(%s:%u:%u,%u) (bailoutKind=%u)", |
2103 | 8 | outerScript->filename(), outerScript->lineno(), outerScript->column(), outerScript->getWarmUpCount(), |
2104 | 8 | innerScript->filename(), innerScript->lineno(), innerScript->column(), innerScript->getWarmUpCount(), |
2105 | 8 | (unsigned) bailoutKind); |
2106 | 8 | |
2107 | 8 | switch (bailoutKind) { |
2108 | 8 | // Normal bailouts. |
2109 | 8 | case Bailout_Inevitable: |
2110 | 5 | case Bailout_DuringVMCall: |
2111 | 5 | case Bailout_NonJSFunctionCallee: |
2112 | 5 | case Bailout_DynamicNameNotFound: |
2113 | 5 | case Bailout_StringArgumentsEval: |
2114 | 5 | case Bailout_Overflow: |
2115 | 5 | case Bailout_Round: |
2116 | 5 | case Bailout_NonPrimitiveInput: |
2117 | 5 | case Bailout_PrecisionLoss: |
2118 | 5 | case Bailout_TypeBarrierO: |
2119 | 5 | case Bailout_TypeBarrierV: |
2120 | 5 | case Bailout_MonitorTypes: |
2121 | 5 | case Bailout_Hole: |
2122 | 5 | case Bailout_NegativeIndex: |
2123 | 5 | case Bailout_NonInt32Input: |
2124 | 5 | case Bailout_NonNumericInput: |
2125 | 5 | case Bailout_NonBooleanInput: |
2126 | 5 | case Bailout_NonObjectInput: |
2127 | 5 | case Bailout_NonStringInput: |
2128 | 5 | case Bailout_NonSymbolInput: |
2129 | 5 | case Bailout_NonSharedTypedArrayInput: |
2130 | 5 | case Bailout_Debugger: |
2131 | 5 | case Bailout_UninitializedThis: |
2132 | 5 | case Bailout_BadDerivedConstructorReturn: |
2133 | 5 | // Do nothing. |
2134 | 5 | break; |
2135 | 5 | |
2136 | 5 | case Bailout_FirstExecution: |
2137 | 0 | // Do not return directly, as this was not frequent in the first place, |
2138 | 0 | // thus rely on the check for frequent bailouts to recompile the current |
2139 | 0 | // script. |
2140 | 0 | break; |
2141 | 5 | |
2142 | 5 | // Invalid assumption based on baseline code. |
2143 | 5 | case Bailout_OverflowInvalidate: |
2144 | 0 | outerScript->setHadOverflowBailout(); |
2145 | 0 | MOZ_FALLTHROUGH; |
2146 | 0 | case Bailout_DoubleOutput: |
2147 | 0 | case Bailout_ObjectIdentityOrTypeGuard: |
2148 | 0 | HandleBaselineInfoBailout(cx, outerScript, innerScript); |
2149 | 0 | break; |
2150 | 0 |
|
2151 | 0 | case Bailout_ArgumentCheck: |
2152 | 0 | // Do nothing, bailout will resume before the argument monitor ICs. |
2153 | 0 | break; |
2154 | 0 | case Bailout_BoundsCheck: |
2155 | 0 | case Bailout_Detached: |
2156 | 0 | HandleBoundsCheckFailure(cx, outerScript, innerScript); |
2157 | 0 | break; |
2158 | 3 | case Bailout_ShapeGuard: |
2159 | 3 | HandleShapeGuardFailure(cx, outerScript, innerScript); |
2160 | 3 | break; |
2161 | 0 | case Bailout_UninitializedLexical: |
2162 | 0 | HandleLexicalCheckFailure(cx, outerScript, innerScript); |
2163 | 0 | break; |
2164 | 0 | case Bailout_IonExceptionDebugMode: |
2165 | 0 | // Return false to resume in HandleException with reconstructed |
2166 | 0 | // baseline frame. |
2167 | 0 | return false; |
2168 | 0 | default: |
2169 | 0 | MOZ_CRASH("Unknown bailout kind!"); |
2170 | 8 | } |
2171 | 8 | |
2172 | 8 | CheckFrequentBailouts(cx, outerScript, bailoutKind); |
2173 | 8 | |
2174 | 8 | // We're returning to JIT code, so we should clear the override pc. |
2175 | 8 | topFrame->clearOverridePc(); |
2176 | 8 | return true; |
2177 | 8 | } |