|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "jit/BaselineDebugModeOSR.h" |
|
8 |
|
9 #include "mozilla/DebugOnly.h" |
|
10 |
|
11 #include "jit/IonLinker.h" |
|
12 #include "jit/PerfSpewer.h" |
|
13 |
|
14 #include "jit/IonFrames-inl.h" |
|
15 #include "vm/Stack-inl.h" |
|
16 |
|
17 using namespace mozilla; |
|
18 using namespace js; |
|
19 using namespace js::jit; |
|
20 |
|
21 struct DebugModeOSREntry |
|
22 { |
|
23 JSScript *script; |
|
24 BaselineScript *oldBaselineScript; |
|
25 BaselineDebugModeOSRInfo *recompInfo; |
|
26 uint32_t pcOffset; |
|
27 ICEntry::Kind frameKind; |
|
28 |
|
29 // Used for sanity asserts in debug builds. |
|
30 DebugOnly<ICStub *> stub; |
|
31 |
|
32 DebugModeOSREntry(JSScript *script) |
|
33 : script(script), |
|
34 oldBaselineScript(script->baselineScript()), |
|
35 recompInfo(nullptr), |
|
36 pcOffset(uint32_t(-1)), |
|
37 frameKind(ICEntry::Kind_NonOp), |
|
38 stub(nullptr) |
|
39 { } |
|
40 |
|
41 DebugModeOSREntry(JSScript *script, const ICEntry &icEntry) |
|
42 : script(script), |
|
43 oldBaselineScript(script->baselineScript()), |
|
44 recompInfo(nullptr), |
|
45 pcOffset(icEntry.pcOffset()), |
|
46 frameKind(icEntry.kind()), |
|
47 stub(nullptr) |
|
48 { |
|
49 #ifdef DEBUG |
|
50 MOZ_ASSERT(pcOffset == icEntry.pcOffset()); |
|
51 MOZ_ASSERT(frameKind == icEntry.kind()); |
|
52 |
|
53 // Assert that if we have a NonOp ICEntry, that there are no unsynced |
|
54 // slots, since such a recompile could have only been triggered from |
|
55 // either an interrupt check or a debug trap handler. |
|
56 // |
|
57 // If triggered from an interrupt check, the stack should be fully |
|
58 // synced. |
|
59 // |
|
60 // If triggered from a debug trap handler, we must be recompiling for |
|
61 // toggling debug mode on->off, in which case the old baseline script |
|
62 // should have fully synced stack at every bytecode. |
|
63 if (frameKind == ICEntry::Kind_NonOp) { |
|
64 PCMappingSlotInfo slotInfo; |
|
65 jsbytecode *pc = script->offsetToPC(pcOffset); |
|
66 oldBaselineScript->nativeCodeForPC(script, pc, &slotInfo); |
|
67 MOZ_ASSERT(slotInfo.numUnsynced() == 0); |
|
68 } |
|
69 #endif |
|
70 } |
|
71 |
|
72 DebugModeOSREntry(DebugModeOSREntry &&other) |
|
73 : script(other.script), |
|
74 oldBaselineScript(other.oldBaselineScript), |
|
75 recompInfo(other.recompInfo ? other.takeRecompInfo() : nullptr), |
|
76 pcOffset(other.pcOffset), |
|
77 frameKind(other.frameKind), |
|
78 stub(other.stub) |
|
79 { } |
|
80 |
|
81 ~DebugModeOSREntry() { |
|
82 // Note that this is nulled out when the recompInfo is taken by the |
|
83 // frame. The frame then has the responsibility of freeing the |
|
84 // recompInfo. |
|
85 js_delete(recompInfo); |
|
86 } |
|
87 |
|
88 bool needsRecompileInfo() const { |
|
89 return (frameKind == ICEntry::Kind_CallVM || |
|
90 frameKind == ICEntry::Kind_DebugTrap || |
|
91 frameKind == ICEntry::Kind_DebugPrologue || |
|
92 frameKind == ICEntry::Kind_DebugEpilogue); |
|
93 } |
|
94 |
|
95 BaselineDebugModeOSRInfo *takeRecompInfo() { |
|
96 MOZ_ASSERT(recompInfo); |
|
97 BaselineDebugModeOSRInfo *tmp = recompInfo; |
|
98 recompInfo = nullptr; |
|
99 return tmp; |
|
100 } |
|
101 |
|
102 bool allocateRecompileInfo(JSContext *cx) { |
|
103 MOZ_ASSERT(needsRecompileInfo()); |
|
104 |
|
105 // If we are returning to a frame which needs a continuation fixer, |
|
106 // allocate the recompile info up front so that the patching function |
|
107 // is infallible. |
|
108 jsbytecode *pc = script->offsetToPC(pcOffset); |
|
109 |
|
110 // XXX: Work around compiler error disallowing using bitfields |
|
111 // with the template magic of new_. |
|
112 ICEntry::Kind kind = frameKind; |
|
113 recompInfo = cx->new_<BaselineDebugModeOSRInfo>(pc, kind); |
|
114 return !!recompInfo; |
|
115 } |
|
116 }; |
|
117 |
|
118 typedef js::Vector<DebugModeOSREntry> DebugModeOSREntryVector; |
|
119 |
|
120 static bool |
|
121 CollectOnStackScripts(JSContext *cx, const JitActivationIterator &activation, |
|
122 DebugModeOSREntryVector &entries) |
|
123 { |
|
124 DebugOnly<ICStub *> prevFrameStubPtr = nullptr; |
|
125 bool needsRecompileHandler = false; |
|
126 for (JitFrameIterator iter(activation); !iter.done(); ++iter) { |
|
127 switch (iter.type()) { |
|
128 case JitFrame_BaselineJS: { |
|
129 JSScript *script = iter.script(); |
|
130 uint8_t *retAddr = iter.returnAddressToFp(); |
|
131 ICEntry &entry = script->baselineScript()->icEntryFromReturnAddress(retAddr); |
|
132 |
|
133 if (!entries.append(DebugModeOSREntry(script, entry))) |
|
134 return false; |
|
135 |
|
136 if (entries.back().needsRecompileInfo()) { |
|
137 if (!entries.back().allocateRecompileInfo(cx)) |
|
138 return false; |
|
139 |
|
140 needsRecompileHandler |= true; |
|
141 } |
|
142 |
|
143 entries.back().stub = prevFrameStubPtr; |
|
144 prevFrameStubPtr = nullptr; |
|
145 break; |
|
146 } |
|
147 |
|
148 case JitFrame_BaselineStub: |
|
149 prevFrameStubPtr = |
|
150 reinterpret_cast<IonBaselineStubFrameLayout *>(iter.fp())->maybeStubPtr(); |
|
151 break; |
|
152 |
|
153 case JitFrame_IonJS: { |
|
154 JSScript *script = iter.script(); |
|
155 if (!entries.append(DebugModeOSREntry(script))) |
|
156 return false; |
|
157 for (InlineFrameIterator inlineIter(cx, &iter); inlineIter.more(); ++inlineIter) { |
|
158 if (!entries.append(DebugModeOSREntry(inlineIter.script()))) |
|
159 return false; |
|
160 } |
|
161 break; |
|
162 } |
|
163 |
|
164 default:; |
|
165 } |
|
166 } |
|
167 |
|
168 // Initialize the on-stack recompile handler, which may fail, so that |
|
169 // patching the stack is infallible. |
|
170 if (needsRecompileHandler) { |
|
171 JitRuntime *rt = cx->runtime()->jitRuntime(); |
|
172 if (!rt->getBaselineDebugModeOSRHandlerAddress(cx, true)) |
|
173 return false; |
|
174 } |
|
175 |
|
176 return true; |
|
177 } |
|
178 |
|
179 static inline uint8_t * |
|
180 GetStubReturnFromStubAddress(JSContext *cx, jsbytecode *pc) |
|
181 { |
|
182 JitCompartment *comp = cx->compartment()->jitCompartment(); |
|
183 void *addr; |
|
184 if (IsGetPropPC(pc)) { |
|
185 addr = comp->baselineGetPropReturnFromStubAddr(); |
|
186 } else if (IsSetPropPC(pc)) { |
|
187 addr = comp->baselineSetPropReturnFromStubAddr(); |
|
188 } else { |
|
189 JS_ASSERT(IsCallPC(pc)); |
|
190 addr = comp->baselineCallReturnFromStubAddr(); |
|
191 } |
|
192 return reinterpret_cast<uint8_t *>(addr); |
|
193 } |
|
194 |
|
195 static const char * |
|
196 ICEntryKindToString(ICEntry::Kind kind) |
|
197 { |
|
198 switch (kind) { |
|
199 case ICEntry::Kind_Op: |
|
200 return "IC"; |
|
201 case ICEntry::Kind_NonOp: |
|
202 return "non-op IC"; |
|
203 case ICEntry::Kind_CallVM: |
|
204 return "callVM"; |
|
205 case ICEntry::Kind_DebugTrap: |
|
206 return "debug trap"; |
|
207 case ICEntry::Kind_DebugPrologue: |
|
208 return "debug prologue"; |
|
209 case ICEntry::Kind_DebugEpilogue: |
|
210 return "debug epilogue"; |
|
211 default: |
|
212 MOZ_ASSUME_UNREACHABLE("bad ICEntry kind"); |
|
213 } |
|
214 } |
|
215 |
|
216 static void |
|
217 SpewPatchBaselineFrame(uint8_t *oldReturnAddress, uint8_t *newReturnAddress, |
|
218 JSScript *script, ICEntry::Kind frameKind, jsbytecode *pc) |
|
219 { |
|
220 IonSpew(IonSpew_BaselineDebugModeOSR, |
|
221 "Patch return %#016llx -> %#016llx to BaselineJS (%s:%d) from %s at %s", |
|
222 uintptr_t(oldReturnAddress), uintptr_t(newReturnAddress), |
|
223 script->filename(), script->lineno(), |
|
224 ICEntryKindToString(frameKind), js_CodeName[(JSOp)*pc]); |
|
225 } |
|
226 |
|
227 static void |
|
228 SpewPatchStubFrame(uint8_t *oldReturnAddress, uint8_t *newReturnAddress, |
|
229 ICStub *oldStub, ICStub *newStub) |
|
230 { |
|
231 IonSpew(IonSpew_BaselineDebugModeOSR, |
|
232 "Patch return %#016llx -> %#016llx", |
|
233 uintptr_t(oldReturnAddress), uintptr_t(newReturnAddress)); |
|
234 IonSpew(IonSpew_BaselineDebugModeOSR, |
|
235 "Patch stub %#016llx -> %#016llx to BaselineStub", |
|
236 uintptr_t(oldStub), uintptr_t(newStub)); |
|
237 } |
|
238 |
|
239 static void |
|
240 PatchBaselineFramesForDebugMode(JSContext *cx, const JitActivationIterator &activation, |
|
241 DebugModeOSREntryVector &entries, size_t *start) |
|
242 { |
|
243 // |
|
244 // Recompile Patching Overview |
|
245 // |
|
246 // When toggling debug mode with live baseline scripts on the stack, we |
|
247 // could have entered the VM via the following ways from the baseline |
|
248 // script. |
|
249 // |
|
250 // Off to On: |
|
251 // A. From a "can call" stub. |
|
252 // B. From a VM call (interrupt handler, debugger statement handler). |
|
253 // |
|
254 // On to Off: |
|
255 // - All the ways above. |
|
256 // C. From the debug trap handler. |
|
257 // D. From the debug prologue. |
|
258 // E. From the debug epilogue. |
|
259 // |
|
260 // In general, we patch the return address from the VM call to return to a |
|
261 // "continuation fixer" to fix up machine state (registers and stack |
|
262 // state). Specifics on what need to be done are documented below. |
|
263 // |
|
264 |
|
265 IonCommonFrameLayout *prev = nullptr; |
|
266 size_t entryIndex = *start; |
|
267 DebugOnly<bool> expectedDebugMode = cx->compartment()->debugMode(); |
|
268 |
|
269 for (JitFrameIterator iter(activation); !iter.done(); ++iter) { |
|
270 switch (iter.type()) { |
|
271 case JitFrame_BaselineJS: { |
|
272 JSScript *script = entries[entryIndex].script; |
|
273 uint32_t pcOffset = entries[entryIndex].pcOffset; |
|
274 jsbytecode *pc = script->offsetToPC(pcOffset); |
|
275 |
|
276 MOZ_ASSERT(script == iter.script()); |
|
277 MOZ_ASSERT(pcOffset < script->length()); |
|
278 MOZ_ASSERT(script->baselineScript()->debugMode() == expectedDebugMode); |
|
279 |
|
280 BaselineScript *bl = script->baselineScript(); |
|
281 ICEntry::Kind kind = entries[entryIndex].frameKind; |
|
282 |
|
283 if (kind == ICEntry::Kind_Op) { |
|
284 // Case A above. |
|
285 // |
|
286 // Patching this case needs to patch both the stub frame and |
|
287 // the baseline frame. The stub frame is patched below. For |
|
288 // the baseline frame here, we resume right after the IC |
|
289 // returns. |
|
290 // |
|
291 // Since we're using the IC-specific k-fixer, we can resume |
|
292 // directly to the IC resume address. |
|
293 uint8_t *retAddr = bl->returnAddressForIC(bl->icEntryFromPCOffset(pcOffset)); |
|
294 SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind, pc); |
|
295 prev->setReturnAddress(retAddr); |
|
296 entryIndex++; |
|
297 break; |
|
298 } |
|
299 |
|
300 bool popFrameReg; |
|
301 |
|
302 // The RecompileInfo must already be allocated so that this |
|
303 // function may be infallible. |
|
304 BaselineDebugModeOSRInfo *recompInfo = entries[entryIndex].takeRecompInfo(); |
|
305 |
|
306 switch (kind) { |
|
307 case ICEntry::Kind_CallVM: |
|
308 // Case B above. |
|
309 // |
|
310 // Patching returns from an interrupt handler or the debugger |
|
311 // statement handler is similar in that we can resume at the |
|
312 // next op. |
|
313 pc += GetBytecodeLength(pc); |
|
314 recompInfo->resumeAddr = bl->nativeCodeForPC(script, pc, &recompInfo->slotInfo); |
|
315 popFrameReg = true; |
|
316 break; |
|
317 |
|
318 case ICEntry::Kind_DebugTrap: |
|
319 // Case C above. |
|
320 // |
|
321 // Debug traps are emitted before each op, so we resume at the |
|
322 // same op. Calling debug trap handlers is done via a toggled |
|
323 // call to a thunk (DebugTrapHandler) that takes care tearing |
|
324 // down its own stub frame so we don't need to worry about |
|
325 // popping the frame reg. |
|
326 recompInfo->resumeAddr = bl->nativeCodeForPC(script, pc, &recompInfo->slotInfo); |
|
327 popFrameReg = false; |
|
328 break; |
|
329 |
|
330 case ICEntry::Kind_DebugPrologue: |
|
331 // Case D above. |
|
332 // |
|
333 // We patch a jump directly to the right place in the prologue |
|
334 // after popping the frame reg and checking for forced return. |
|
335 recompInfo->resumeAddr = bl->postDebugPrologueAddr(); |
|
336 popFrameReg = true; |
|
337 break; |
|
338 |
|
339 default: |
|
340 // Case E above. |
|
341 // |
|
342 // We patch a jump directly to the epilogue after popping the |
|
343 // frame reg and checking for forced return. |
|
344 MOZ_ASSERT(kind == ICEntry::Kind_DebugEpilogue); |
|
345 recompInfo->resumeAddr = bl->epilogueEntryAddr(); |
|
346 popFrameReg = true; |
|
347 break; |
|
348 } |
|
349 |
|
350 SpewPatchBaselineFrame(prev->returnAddress(), recompInfo->resumeAddr, |
|
351 script, kind, recompInfo->pc); |
|
352 |
|
353 // The recompile handler must already be created so that this |
|
354 // function may be infallible. |
|
355 JitRuntime *rt = cx->runtime()->jitRuntime(); |
|
356 void *handlerAddr = rt->getBaselineDebugModeOSRHandlerAddress(cx, popFrameReg); |
|
357 MOZ_ASSERT(handlerAddr); |
|
358 |
|
359 prev->setReturnAddress(reinterpret_cast<uint8_t *>(handlerAddr)); |
|
360 iter.baselineFrame()->setDebugModeOSRInfo(recompInfo); |
|
361 |
|
362 entryIndex++; |
|
363 break; |
|
364 } |
|
365 |
|
366 case JitFrame_BaselineStub: { |
|
367 JSScript *script = entries[entryIndex].script; |
|
368 IonBaselineStubFrameLayout *layout = |
|
369 reinterpret_cast<IonBaselineStubFrameLayout *>(iter.fp()); |
|
370 MOZ_ASSERT(script->baselineScript()->debugMode() == expectedDebugMode); |
|
371 MOZ_ASSERT(layout->maybeStubPtr() == entries[entryIndex].stub); |
|
372 |
|
373 // Patch baseline stub frames for case A above. |
|
374 // |
|
375 // We need to patch the stub frame return address to go to the |
|
376 // k-fixer that is at the end of fallback stubs of all such |
|
377 // can-call ICs. These k-fixers share code with bailout-from-Ion |
|
378 // fixers, but in this case we are returning from VM and not |
|
379 // Ion. See e.g., JitCompartment::baselineCallReturnFromStubAddr() |
|
380 // |
|
381 // Subtlety here: the debug trap handler of case C above pushes a |
|
382 // stub frame with a null stub pointer. This handler will exist |
|
383 // across recompiling the script, so we don't patch anything for |
|
384 // such stub frames. We will return to that handler, which takes |
|
385 // care of cleaning up the stub frame. |
|
386 // |
|
387 // Note that for stub pointers that are already on the C stack |
|
388 // (i.e. fallback calls), we need to check for recompilation using |
|
389 // DebugModeOSRVolatileStub. |
|
390 if (layout->maybeStubPtr()) { |
|
391 MOZ_ASSERT(layout->maybeStubPtr() == entries[entryIndex].stub); |
|
392 uint32_t pcOffset = entries[entryIndex].pcOffset; |
|
393 uint8_t *retAddr = GetStubReturnFromStubAddress(cx, script->offsetToPC(pcOffset)); |
|
394 |
|
395 // Get the fallback stub for the IC in the recompiled |
|
396 // script. The fallback stub is guaranteed to exist. |
|
397 ICEntry &entry = script->baselineScript()->icEntryFromPCOffset(pcOffset); |
|
398 ICStub *newStub = entry.fallbackStub(); |
|
399 SpewPatchStubFrame(prev->returnAddress(), retAddr, layout->maybeStubPtr(), newStub); |
|
400 prev->setReturnAddress(retAddr); |
|
401 layout->setStubPtr(newStub); |
|
402 } |
|
403 |
|
404 break; |
|
405 } |
|
406 |
|
407 case JitFrame_IonJS: |
|
408 // Nothing to patch. |
|
409 entryIndex++; |
|
410 for (InlineFrameIterator inlineIter(cx, &iter); inlineIter.more(); ++inlineIter) |
|
411 entryIndex++; |
|
412 break; |
|
413 |
|
414 default:; |
|
415 } |
|
416 |
|
417 prev = iter.current(); |
|
418 } |
|
419 |
|
420 *start = entryIndex; |
|
421 } |
|
422 |
|
423 static bool |
|
424 RecompileBaselineScriptForDebugMode(JSContext *cx, JSScript *script) |
|
425 { |
|
426 BaselineScript *oldBaselineScript = script->baselineScript(); |
|
427 |
|
428 // If a script is on the stack multiple times, it may have already |
|
429 // been recompiled. |
|
430 bool expectedDebugMode = cx->compartment()->debugMode(); |
|
431 if (oldBaselineScript->debugMode() == expectedDebugMode) |
|
432 return true; |
|
433 |
|
434 IonSpew(IonSpew_BaselineDebugModeOSR, "Recompiling (%s:%d) for debug mode %s", |
|
435 script->filename(), script->lineno(), expectedDebugMode ? "ON" : "OFF"); |
|
436 |
|
437 if (script->hasIonScript()) |
|
438 Invalidate(cx, script, /* resetUses = */ false); |
|
439 |
|
440 script->setBaselineScript(cx, nullptr); |
|
441 |
|
442 MethodStatus status = BaselineCompile(cx, script); |
|
443 if (status != Method_Compiled) { |
|
444 // We will only fail to recompile for debug mode due to OOM. Restore |
|
445 // the old baseline script in case something doesn't properly |
|
446 // propagate OOM. |
|
447 MOZ_ASSERT(status == Method_Error); |
|
448 script->setBaselineScript(cx, oldBaselineScript); |
|
449 return false; |
|
450 } |
|
451 |
|
452 // Don't destroy the old baseline script yet, since if we fail any of the |
|
453 // recompiles we need to rollback all the old baseline scripts. |
|
454 MOZ_ASSERT(script->baselineScript()->debugMode() == expectedDebugMode); |
|
455 return true; |
|
456 } |
|
457 |
|
458 static void |
|
459 UndoRecompileBaselineScriptsForDebugMode(JSContext *cx, |
|
460 const DebugModeOSREntryVector &entries) |
|
461 { |
|
462 // In case of failure, roll back the entire set of active scripts so that |
|
463 // we don't have to patch return addresses on the stack. |
|
464 for (size_t i = 0; i < entries.length(); i++) { |
|
465 JSScript *script = entries[i].script; |
|
466 BaselineScript *baselineScript = script->baselineScript(); |
|
467 if (baselineScript != entries[i].oldBaselineScript) { |
|
468 script->setBaselineScript(cx, entries[i].oldBaselineScript); |
|
469 BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), baselineScript); |
|
470 } |
|
471 } |
|
472 } |
|
473 |
|
474 bool |
|
475 jit::RecompileOnStackBaselineScriptsForDebugMode(JSContext *cx, JSCompartment *comp) |
|
476 { |
|
477 AutoCompartment ac(cx, comp); |
|
478 |
|
479 // First recompile the active scripts on the stack and patch the live |
|
480 // frames. |
|
481 Vector<DebugModeOSREntry> entries(cx); |
|
482 |
|
483 for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) { |
|
484 if (iter.activation()->compartment() == comp) { |
|
485 if (!CollectOnStackScripts(cx, iter, entries)) |
|
486 return false; |
|
487 } |
|
488 } |
|
489 |
|
490 #ifdef JSGC_GENERATIONAL |
|
491 // Scripts can entrain nursery things. See note in js::ReleaseAllJITCode. |
|
492 if (!entries.empty()) |
|
493 MinorGC(cx->runtime(), JS::gcreason::EVICT_NURSERY); |
|
494 #endif |
|
495 |
|
496 // Try to recompile all the scripts. If we encounter an error, we need to |
|
497 // roll back as if none of the compilations happened, so that we don't |
|
498 // crash. |
|
499 for (size_t i = 0; i < entries.length(); i++) { |
|
500 JSScript *script = entries[i].script; |
|
501 if (!RecompileBaselineScriptForDebugMode(cx, script)) { |
|
502 UndoRecompileBaselineScriptsForDebugMode(cx, entries); |
|
503 return false; |
|
504 } |
|
505 } |
|
506 |
|
507 // If all recompiles succeeded, destroy the old baseline scripts and patch |
|
508 // the live frames. |
|
509 // |
|
510 // After this point the function must be infallible. |
|
511 |
|
512 for (size_t i = 0; i < entries.length(); i++) |
|
513 BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), entries[i].oldBaselineScript); |
|
514 |
|
515 size_t processed = 0; |
|
516 for (JitActivationIterator iter(cx->runtime()); !iter.done(); ++iter) { |
|
517 if (iter.activation()->compartment() == comp) |
|
518 PatchBaselineFramesForDebugMode(cx, iter, entries, &processed); |
|
519 } |
|
520 MOZ_ASSERT(processed == entries.length()); |
|
521 |
|
522 return true; |
|
523 } |
|
524 |
|
525 void |
|
526 BaselineDebugModeOSRInfo::popValueInto(PCMappingSlotInfo::SlotLocation loc, Value *vp) |
|
527 { |
|
528 switch (loc) { |
|
529 case PCMappingSlotInfo::SlotInR0: |
|
530 valueR0 = vp[stackAdjust]; |
|
531 break; |
|
532 case PCMappingSlotInfo::SlotInR1: |
|
533 valueR1 = vp[stackAdjust]; |
|
534 break; |
|
535 case PCMappingSlotInfo::SlotIgnore: |
|
536 break; |
|
537 default: |
|
538 MOZ_ASSUME_UNREACHABLE("Bad slot location"); |
|
539 } |
|
540 |
|
541 stackAdjust++; |
|
542 } |
|
543 |
|
544 static inline bool |
|
545 HasForcedReturn(BaselineDebugModeOSRInfo *info, bool rv) |
|
546 { |
|
547 ICEntry::Kind kind = info->frameKind; |
|
548 |
|
549 // The debug epilogue always checks its resumption value, so we don't need |
|
550 // to check rv. |
|
551 if (kind == ICEntry::Kind_DebugEpilogue) |
|
552 return true; |
|
553 |
|
554 // |rv| is the value in ReturnReg. If true, in the case of the prologue, |
|
555 // debug trap, and debugger statement handler, it means a forced return. |
|
556 if (kind == ICEntry::Kind_DebugPrologue || |
|
557 (kind == ICEntry::Kind_CallVM && JSOp(*info->pc) == JSOP_DEBUGGER)) |
|
558 { |
|
559 return rv; |
|
560 } |
|
561 |
|
562 // N.B. The debug trap handler handles its own forced return, so no |
|
563 // need to deal with it here. |
|
564 return false; |
|
565 } |
|
566 |
|
567 static void |
|
568 SyncBaselineDebugModeOSRInfo(BaselineFrame *frame, Value *vp, bool rv) |
|
569 { |
|
570 BaselineDebugModeOSRInfo *info = frame->debugModeOSRInfo(); |
|
571 MOZ_ASSERT(info); |
|
572 MOZ_ASSERT(frame->script()->baselineScript()->containsCodeAddress(info->resumeAddr)); |
|
573 |
|
574 if (HasForcedReturn(info, rv)) { |
|
575 // Load the frame's rval and overwrite the resume address to go to the |
|
576 // epilogue. |
|
577 MOZ_ASSERT(R0 == JSReturnOperand); |
|
578 info->valueR0 = frame->returnValue(); |
|
579 info->resumeAddr = frame->script()->baselineScript()->epilogueEntryAddr(); |
|
580 return; |
|
581 } |
|
582 |
|
583 // Read stack values and make sure R0 and R1 have the right values. |
|
584 unsigned numUnsynced = info->slotInfo.numUnsynced(); |
|
585 MOZ_ASSERT(numUnsynced <= 2); |
|
586 if (numUnsynced > 0) |
|
587 info->popValueInto(info->slotInfo.topSlotLocation(), vp); |
|
588 if (numUnsynced > 1) |
|
589 info->popValueInto(info->slotInfo.nextSlotLocation(), vp); |
|
590 |
|
591 // Scale stackAdjust. |
|
592 info->stackAdjust *= sizeof(Value); |
|
593 } |
|
594 |
|
595 static void |
|
596 FinishBaselineDebugModeOSR(BaselineFrame *frame) |
|
597 { |
|
598 frame->deleteDebugModeOSRInfo(); |
|
599 } |
|
600 |
|
601 void |
|
602 BaselineFrame::deleteDebugModeOSRInfo() |
|
603 { |
|
604 js_delete(getDebugModeOSRInfo()); |
|
605 flags_ &= ~HAS_DEBUG_MODE_OSR_INFO; |
|
606 } |
|
607 |
|
608 JitCode * |
|
609 JitRuntime::getBaselineDebugModeOSRHandler(JSContext *cx) |
|
610 { |
|
611 if (!baselineDebugModeOSRHandler_) { |
|
612 AutoLockForExclusiveAccess lock(cx); |
|
613 AutoCompartment ac(cx, cx->runtime()->atomsCompartment()); |
|
614 uint32_t offset; |
|
615 if (JitCode *code = generateBaselineDebugModeOSRHandler(cx, &offset)) { |
|
616 baselineDebugModeOSRHandler_ = code; |
|
617 baselineDebugModeOSRHandlerNoFrameRegPopAddr_ = code->raw() + offset; |
|
618 } |
|
619 } |
|
620 |
|
621 return baselineDebugModeOSRHandler_; |
|
622 } |
|
623 |
|
624 void * |
|
625 JitRuntime::getBaselineDebugModeOSRHandlerAddress(JSContext *cx, bool popFrameReg) |
|
626 { |
|
627 if (!getBaselineDebugModeOSRHandler(cx)) |
|
628 return nullptr; |
|
629 return (popFrameReg |
|
630 ? baselineDebugModeOSRHandler_->raw() |
|
631 : baselineDebugModeOSRHandlerNoFrameRegPopAddr_); |
|
632 } |
|
633 |
|
634 JitCode * |
|
635 JitRuntime::generateBaselineDebugModeOSRHandler(JSContext *cx, uint32_t *noFrameRegPopOffsetOut) |
|
636 { |
|
637 MacroAssembler masm(cx); |
|
638 |
|
639 GeneralRegisterSet regs(GeneralRegisterSet::All()); |
|
640 regs.take(BaselineFrameReg); |
|
641 regs.take(ReturnReg); |
|
642 Register temp = regs.takeAny(); |
|
643 Register syncedStackStart = regs.takeAny(); |
|
644 |
|
645 // Pop the frame reg. |
|
646 masm.pop(BaselineFrameReg); |
|
647 |
|
648 // Not all patched baseline frames are returning from a situation where |
|
649 // the frame reg is already fixed up. |
|
650 CodeOffsetLabel noFrameRegPopOffset = masm.currentOffset(); |
|
651 |
|
652 // Record the stack pointer for syncing. |
|
653 masm.movePtr(StackPointer, syncedStackStart); |
|
654 masm.push(BaselineFrameReg); |
|
655 |
|
656 // Call a stub to fully initialize the info. |
|
657 masm.setupUnalignedABICall(3, temp); |
|
658 masm.loadBaselineFramePtr(BaselineFrameReg, temp); |
|
659 masm.passABIArg(temp); |
|
660 masm.passABIArg(syncedStackStart); |
|
661 masm.passABIArg(ReturnReg); |
|
662 masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, SyncBaselineDebugModeOSRInfo)); |
|
663 |
|
664 // Discard stack values depending on how many were unsynced, as we always |
|
665 // have a fully synced stack in the recompile handler. See assert in |
|
666 // DebugModeOSREntry constructor. |
|
667 masm.pop(BaselineFrameReg); |
|
668 masm.loadPtr(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValue()), temp); |
|
669 masm.addPtr(Address(temp, offsetof(BaselineDebugModeOSRInfo, stackAdjust)), StackPointer); |
|
670 |
|
671 // Save real return address on the stack temporarily. |
|
672 masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR0))); |
|
673 masm.pushValue(Address(temp, offsetof(BaselineDebugModeOSRInfo, valueR1))); |
|
674 masm.push(BaselineFrameReg); |
|
675 masm.push(Address(temp, offsetof(BaselineDebugModeOSRInfo, resumeAddr))); |
|
676 |
|
677 // Call a stub to free the allocated info. |
|
678 masm.setupUnalignedABICall(1, temp); |
|
679 masm.loadBaselineFramePtr(BaselineFrameReg, temp); |
|
680 masm.passABIArg(temp); |
|
681 masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, FinishBaselineDebugModeOSR)); |
|
682 |
|
683 // Restore saved values. |
|
684 GeneralRegisterSet jumpRegs(GeneralRegisterSet::All()); |
|
685 jumpRegs.take(R0); |
|
686 jumpRegs.take(R1); |
|
687 jumpRegs.take(BaselineFrameReg); |
|
688 Register target = jumpRegs.takeAny(); |
|
689 |
|
690 masm.pop(target); |
|
691 masm.pop(BaselineFrameReg); |
|
692 masm.popValue(R1); |
|
693 masm.popValue(R0); |
|
694 |
|
695 masm.jump(target); |
|
696 |
|
697 Linker linker(masm); |
|
698 AutoFlushICache afc("BaselineDebugModeOSRHandler"); |
|
699 JitCode *code = linker.newCode<NoGC>(cx, JSC::OTHER_CODE); |
|
700 if (!code) |
|
701 return nullptr; |
|
702 |
|
703 noFrameRegPopOffset.fixup(&masm); |
|
704 *noFrameRegPopOffsetOut = noFrameRegPopOffset.offset(); |
|
705 |
|
706 #ifdef JS_ION_PERF |
|
707 writePerfSpewerJitCodeProfile(code, "BaselineDebugModeOSRHandler"); |
|
708 #endif |
|
709 |
|
710 return code; |
|
711 } |