1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * vim: set ts=8 sts=2 et sw=2 tw=80:
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 "jit/BaselineFrame.h"
10 #include "jit/BaselineIC.h"
11 #include "jit/BaselineJIT.h"
12 #include "jit/Invalidation.h"
13 #include "jit/IonScript.h"
14 #include "jit/JitFrames.h"
15 #include "jit/JitRuntime.h"
16 #include "jit/JSJitFrameIter.h"
17 
18 #include "jit/JitScript-inl.h"
19 #include "jit/JSJitFrameIter-inl.h"
20 #include "vm/JSScript-inl.h"
21 #include "vm/Realm-inl.h"
22 
23 using namespace js;
24 using namespace js::jit;
25 
26 struct DebugModeOSREntry {
27   JSScript* script;
28   BaselineScript* oldBaselineScript;
29   uint32_t pcOffset;
30   RetAddrEntry::Kind frameKind;
31 
DebugModeOSREntryDebugModeOSREntry32   explicit DebugModeOSREntry(JSScript* script)
33       : script(script),
34         oldBaselineScript(script->baselineScript()),
35         pcOffset(uint32_t(-1)),
36         frameKind(RetAddrEntry::Kind::Invalid) {}
37 
DebugModeOSREntryDebugModeOSREntry38   DebugModeOSREntry(JSScript* script, const RetAddrEntry& retAddrEntry)
39       : script(script),
40         oldBaselineScript(script->baselineScript()),
41         pcOffset(retAddrEntry.pcOffset()),
42         frameKind(retAddrEntry.kind()) {
43 #ifdef DEBUG
44     MOZ_ASSERT(pcOffset == retAddrEntry.pcOffset());
45     MOZ_ASSERT(frameKind == retAddrEntry.kind());
46 #endif
47   }
48 
DebugModeOSREntryDebugModeOSREntry49   DebugModeOSREntry(DebugModeOSREntry&& other)
50       : script(other.script),
51         oldBaselineScript(other.oldBaselineScript),
52         pcOffset(other.pcOffset),
53         frameKind(other.frameKind) {}
54 
recompiledDebugModeOSREntry55   bool recompiled() const {
56     return oldBaselineScript != script->baselineScript();
57   }
58 };
59 
60 using DebugModeOSREntryVector = Vector<DebugModeOSREntry>;
61 
62 class UniqueScriptOSREntryIter {
63   const DebugModeOSREntryVector& entries_;
64   size_t index_;
65 
66  public:
UniqueScriptOSREntryIter(const DebugModeOSREntryVector & entries)67   explicit UniqueScriptOSREntryIter(const DebugModeOSREntryVector& entries)
68       : entries_(entries), index_(0) {}
69 
done()70   bool done() { return index_ == entries_.length(); }
71 
entry()72   const DebugModeOSREntry& entry() {
73     MOZ_ASSERT(!done());
74     return entries_[index_];
75   }
76 
operator ++()77   UniqueScriptOSREntryIter& operator++() {
78     MOZ_ASSERT(!done());
79     while (++index_ < entries_.length()) {
80       bool unique = true;
81       for (size_t i = 0; i < index_; i++) {
82         if (entries_[i].script == entries_[index_].script) {
83           unique = false;
84           break;
85         }
86       }
87       if (unique) {
88         break;
89       }
90     }
91     return *this;
92   }
93 };
94 
CollectJitStackScripts(JSContext * cx,const DebugAPI::ExecutionObservableSet & obs,const ActivationIterator & activation,DebugModeOSREntryVector & entries)95 static bool CollectJitStackScripts(JSContext* cx,
96                                    const DebugAPI::ExecutionObservableSet& obs,
97                                    const ActivationIterator& activation,
98                                    DebugModeOSREntryVector& entries) {
99   for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
100     const JSJitFrameIter& frame = iter.frame();
101     switch (frame.type()) {
102       case FrameType::BaselineJS: {
103         JSScript* script = frame.script();
104 
105         if (!obs.shouldRecompileOrInvalidate(script)) {
106           break;
107         }
108 
109         BaselineFrame* baselineFrame = frame.baselineFrame();
110 
111         if (baselineFrame->runningInInterpreter()) {
112           // Baseline Interpreter frames for scripts that have a BaselineScript
113           // or IonScript don't need to be patched but they do need to be
114           // invalidated and recompiled. See also CollectInterpreterStackScripts
115           // for C++ interpreter frames.
116           if (!entries.append(DebugModeOSREntry(script))) {
117             return false;
118           }
119         } else {
120           // The frame must be settled on a pc with a RetAddrEntry.
121           uint8_t* retAddr = frame.resumePCinCurrentFrame();
122           const RetAddrEntry& retAddrEntry =
123               script->baselineScript()->retAddrEntryFromReturnAddress(retAddr);
124           if (!entries.append(DebugModeOSREntry(script, retAddrEntry))) {
125             return false;
126           }
127         }
128 
129         break;
130       }
131 
132       case FrameType::BaselineStub:
133         break;
134 
135       case FrameType::IonJS: {
136         InlineFrameIterator inlineIter(cx, &frame);
137         while (true) {
138           if (obs.shouldRecompileOrInvalidate(inlineIter.script())) {
139             if (!entries.append(DebugModeOSREntry(inlineIter.script()))) {
140               return false;
141             }
142           }
143           if (!inlineIter.more()) {
144             break;
145           }
146           ++inlineIter;
147         }
148         break;
149       }
150 
151       default:;
152     }
153   }
154 
155   return true;
156 }
157 
CollectInterpreterStackScripts(JSContext * cx,const DebugAPI::ExecutionObservableSet & obs,const ActivationIterator & activation,DebugModeOSREntryVector & entries)158 static bool CollectInterpreterStackScripts(
159     JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
160     const ActivationIterator& activation, DebugModeOSREntryVector& entries) {
161   // Collect interpreter frame stacks with IonScript or BaselineScript as
162   // well. These do not need to be patched, but do need to be invalidated
163   // and recompiled.
164   InterpreterActivation* act = activation.activation()->asInterpreter();
165   for (InterpreterFrameIterator iter(act); !iter.done(); ++iter) {
166     JSScript* script = iter.frame()->script();
167     if (obs.shouldRecompileOrInvalidate(script)) {
168       if (!entries.append(DebugModeOSREntry(iter.frame()->script()))) {
169         return false;
170       }
171     }
172   }
173   return true;
174 }
175 
176 #ifdef JS_JITSPEW
RetAddrEntryKindToString(RetAddrEntry::Kind kind)177 static const char* RetAddrEntryKindToString(RetAddrEntry::Kind kind) {
178   switch (kind) {
179     case RetAddrEntry::Kind::IC:
180       return "IC";
181     case RetAddrEntry::Kind::CallVM:
182       return "callVM";
183     case RetAddrEntry::Kind::StackCheck:
184       return "stack check";
185     case RetAddrEntry::Kind::InterruptCheck:
186       return "interrupt check";
187     case RetAddrEntry::Kind::DebugTrap:
188       return "debug trap";
189     case RetAddrEntry::Kind::DebugPrologue:
190       return "debug prologue";
191     case RetAddrEntry::Kind::DebugAfterYield:
192       return "debug after yield";
193     case RetAddrEntry::Kind::DebugEpilogue:
194       return "debug epilogue";
195     default:
196       MOZ_CRASH("bad RetAddrEntry kind");
197   }
198 }
199 #endif  // JS_JITSPEW
200 
SpewPatchBaselineFrame(const uint8_t * oldReturnAddress,const uint8_t * newReturnAddress,JSScript * script,RetAddrEntry::Kind frameKind,const jsbytecode * pc)201 static void SpewPatchBaselineFrame(const uint8_t* oldReturnAddress,
202                                    const uint8_t* newReturnAddress,
203                                    JSScript* script,
204                                    RetAddrEntry::Kind frameKind,
205                                    const jsbytecode* pc) {
206   JitSpew(JitSpew_BaselineDebugModeOSR,
207           "Patch return %p -> %p on BaselineJS frame (%s:%u:%u) from %s at %s",
208           oldReturnAddress, newReturnAddress, script->filename(),
209           script->lineno(), script->column(),
210           RetAddrEntryKindToString(frameKind), CodeName(JSOp(*pc)));
211 }
212 
PatchBaselineFramesForDebugMode(JSContext * cx,const DebugAPI::ExecutionObservableSet & obs,const ActivationIterator & activation,DebugModeOSREntryVector & entries,size_t * start)213 static void PatchBaselineFramesForDebugMode(
214     JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
215     const ActivationIterator& activation, DebugModeOSREntryVector& entries,
216     size_t* start) {
217   //
218   // Recompile Patching Overview
219   //
220   // When toggling debug mode with live baseline scripts on the stack, we
221   // could have entered the VM via the following ways from the baseline
222   // script.
223   //
224   // Off to On:
225   //  A. From a non-prologue IC (fallback stub or "can call" stub).
226   //  B. From a VM call.
227   //  C. From inside the interrupt handler via the prologue stack check.
228   //
229   // On to Off:
230   //  - All the ways above.
231   //  D. From the debug trap handler.
232   //  E. From the debug prologue.
233   //  F. From the debug epilogue.
234   //  G. From a JSOp::AfterYield instruction.
235   //
236   // In general, we patch the return address from VM calls and ICs to the
237   // corresponding entry in the recompiled BaselineScript. For entries that are
238   // not present in the recompiled script (cases D to G above) we switch the
239   // frame to interpreter mode and resume in the Baseline Interpreter.
240   //
241   // Specifics on what needs to be done are documented below.
242   //
243 
244   const BaselineInterpreter& baselineInterp =
245       cx->runtime()->jitRuntime()->baselineInterpreter();
246 
247   CommonFrameLayout* prev = nullptr;
248   size_t entryIndex = *start;
249 
250   for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
251     const JSJitFrameIter& frame = iter.frame();
252     switch (frame.type()) {
253       case FrameType::BaselineJS: {
254         // If the script wasn't recompiled or is not observed, there's
255         // nothing to patch.
256         if (!obs.shouldRecompileOrInvalidate(frame.script())) {
257           break;
258         }
259 
260         DebugModeOSREntry& entry = entries[entryIndex];
261 
262         if (!entry.recompiled()) {
263           entryIndex++;
264           break;
265         }
266 
267         BaselineFrame* baselineFrame = frame.baselineFrame();
268         if (baselineFrame->runningInInterpreter()) {
269           // We recompiled the script's BaselineScript but Baseline Interpreter
270           // frames don't need to be patched.
271           entryIndex++;
272           break;
273         }
274 
275         JSScript* script = entry.script;
276         uint32_t pcOffset = entry.pcOffset;
277         jsbytecode* pc = script->offsetToPC(pcOffset);
278 
279         MOZ_ASSERT(script == frame.script());
280         MOZ_ASSERT(pcOffset < script->length());
281 
282         BaselineScript* bl = script->baselineScript();
283         RetAddrEntry::Kind kind = entry.frameKind;
284         uint8_t* retAddr = nullptr;
285         switch (kind) {
286           case RetAddrEntry::Kind::IC:
287           case RetAddrEntry::Kind::CallVM:
288           case RetAddrEntry::Kind::InterruptCheck:
289           case RetAddrEntry::Kind::StackCheck: {
290             // Cases A, B, C above.
291             //
292             // For the baseline frame here, we resume right after the CallVM or
293             // IC returns.
294             //
295             // For CallVM (case B) the assumption is that all callVMs which can
296             // trigger debug mode OSR are the *only* callVMs generated for their
297             // respective pc locations in the Baseline JIT code.
298             const RetAddrEntry* retAddrEntry = nullptr;
299             switch (kind) {
300               case RetAddrEntry::Kind::IC:
301               case RetAddrEntry::Kind::CallVM:
302               case RetAddrEntry::Kind::InterruptCheck:
303                 retAddrEntry = &bl->retAddrEntryFromPCOffset(pcOffset, kind);
304                 break;
305               case RetAddrEntry::Kind::StackCheck:
306                 retAddrEntry = &bl->prologueRetAddrEntry(kind);
307                 break;
308               default:
309                 MOZ_CRASH("Unexpected kind");
310             }
311             retAddr = bl->returnAddressForEntry(*retAddrEntry);
312             SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind,
313                                    pc);
314             break;
315           }
316           case RetAddrEntry::Kind::DebugPrologue:
317           case RetAddrEntry::Kind::DebugEpilogue:
318           case RetAddrEntry::Kind::DebugTrap:
319           case RetAddrEntry::Kind::DebugAfterYield: {
320             // Cases D, E, F, G above.
321             //
322             // Resume in the Baseline Interpreter because these callVMs are not
323             // present in the new BaselineScript if we recompiled without debug
324             // instrumentation.
325             if (kind == RetAddrEntry::Kind::DebugPrologue) {
326               frame.baselineFrame()->switchFromJitToInterpreterAtPrologue(cx);
327             } else {
328               frame.baselineFrame()->switchFromJitToInterpreter(cx, pc);
329             }
330             switch (kind) {
331               case RetAddrEntry::Kind::DebugTrap:
332                 // DebugTrap handling is different from the ones below because
333                 // it's not a callVM but a trampoline call at the start of the
334                 // bytecode op. When we return to the frame we can resume at the
335                 // interpretOp label.
336                 retAddr = baselineInterp.interpretOpAddr().value;
337                 break;
338               case RetAddrEntry::Kind::DebugPrologue:
339                 retAddr = baselineInterp.retAddrForDebugPrologueCallVM();
340                 break;
341               case RetAddrEntry::Kind::DebugEpilogue:
342                 retAddr = baselineInterp.retAddrForDebugEpilogueCallVM();
343                 break;
344               case RetAddrEntry::Kind::DebugAfterYield:
345                 retAddr = baselineInterp.retAddrForDebugAfterYieldCallVM();
346                 break;
347               default:
348                 MOZ_CRASH("Unexpected kind");
349             }
350             SpewPatchBaselineFrame(prev->returnAddress(), retAddr, script, kind,
351                                    pc);
352             break;
353           }
354           case RetAddrEntry::Kind::NonOpCallVM:
355           case RetAddrEntry::Kind::Invalid:
356             // These cannot trigger BaselineDebugModeOSR.
357             MOZ_CRASH("Unexpected RetAddrEntry Kind");
358         }
359 
360         prev->setReturnAddress(retAddr);
361         entryIndex++;
362         break;
363       }
364 
365       case FrameType::IonJS: {
366         // Nothing to patch.
367         InlineFrameIterator inlineIter(cx, &frame);
368         while (true) {
369           if (obs.shouldRecompileOrInvalidate(inlineIter.script())) {
370             entryIndex++;
371           }
372           if (!inlineIter.more()) {
373             break;
374           }
375           ++inlineIter;
376         }
377         break;
378       }
379 
380       default:;
381     }
382 
383     prev = frame.current();
384   }
385 
386   *start = entryIndex;
387 }
388 
SkipInterpreterFrameEntries(const DebugAPI::ExecutionObservableSet & obs,const ActivationIterator & activation,size_t * start)389 static void SkipInterpreterFrameEntries(
390     const DebugAPI::ExecutionObservableSet& obs,
391     const ActivationIterator& activation, size_t* start) {
392   size_t entryIndex = *start;
393 
394   // Skip interpreter frames, which do not need patching.
395   InterpreterActivation* act = activation.activation()->asInterpreter();
396   for (InterpreterFrameIterator iter(act); !iter.done(); ++iter) {
397     if (obs.shouldRecompileOrInvalidate(iter.frame()->script())) {
398       entryIndex++;
399     }
400   }
401 
402   *start = entryIndex;
403 }
404 
RecompileBaselineScriptForDebugMode(JSContext * cx,JSScript * script,DebugAPI::IsObserving observing)405 static bool RecompileBaselineScriptForDebugMode(
406     JSContext* cx, JSScript* script, DebugAPI::IsObserving observing) {
407   // If a script is on the stack multiple times, it may have already
408   // been recompiled.
409   if (script->baselineScript()->hasDebugInstrumentation() == observing) {
410     return true;
411   }
412 
413   JitSpew(JitSpew_BaselineDebugModeOSR, "Recompiling (%s:%u:%u) for %s",
414           script->filename(), script->lineno(), script->column(),
415           observing ? "DEBUGGING" : "NORMAL EXECUTION");
416 
417   AutoKeepJitScripts keepJitScripts(cx);
418   BaselineScript* oldBaselineScript =
419       script->jitScript()->clearBaselineScript(cx->defaultFreeOp(), script);
420 
421   MethodStatus status =
422       BaselineCompile(cx, script, /* forceDebugMode = */ observing);
423   if (status != Method_Compiled) {
424     // We will only fail to recompile for debug mode due to OOM. Restore
425     // the old baseline script in case something doesn't properly
426     // propagate OOM.
427     MOZ_ASSERT(status == Method_Error);
428     script->jitScript()->setBaselineScript(script, oldBaselineScript);
429     return false;
430   }
431 
432   // Don't destroy the old baseline script yet, since if we fail any of the
433   // recompiles we need to rollback all the old baseline scripts.
434   MOZ_ASSERT(script->baselineScript()->hasDebugInstrumentation() == observing);
435   return true;
436 }
437 
InvalidateScriptsInZone(JSContext * cx,Zone * zone,const Vector<DebugModeOSREntry> & entries)438 static bool InvalidateScriptsInZone(JSContext* cx, Zone* zone,
439                                     const Vector<DebugModeOSREntry>& entries) {
440   RecompileInfoVector invalid;
441   for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
442     JSScript* script = iter.entry().script;
443     if (script->zone() != zone) {
444       continue;
445     }
446 
447     if (script->hasIonScript()) {
448       if (!invalid.emplaceBack(script, script->ionScript()->compilationId())) {
449         ReportOutOfMemory(cx);
450         return false;
451       }
452     }
453 
454     // Cancel off-thread Ion compile for anything that has a
455     // BaselineScript. If we relied on the call to Invalidate below to
456     // cancel off-thread Ion compiles, only those with existing IonScripts
457     // would be cancelled.
458     if (script->hasBaselineScript()) {
459       CancelOffThreadIonCompile(script);
460     }
461   }
462 
463   // No need to cancel off-thread Ion compiles again, we already did it
464   // above.
465   Invalidate(cx, invalid,
466              /* resetUses = */ true, /* cancelOffThread = */ false);
467   return true;
468 }
469 
UndoRecompileBaselineScriptsForDebugMode(JSContext * cx,const DebugModeOSREntryVector & entries)470 static void UndoRecompileBaselineScriptsForDebugMode(
471     JSContext* cx, const DebugModeOSREntryVector& entries) {
472   // In case of failure, roll back the entire set of active scripts so that
473   // we don't have to patch return addresses on the stack.
474   for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
475     const DebugModeOSREntry& entry = iter.entry();
476     JSScript* script = entry.script;
477     BaselineScript* baselineScript = script->baselineScript();
478     if (entry.recompiled()) {
479       script->jitScript()->setBaselineScript(script, entry.oldBaselineScript);
480       BaselineScript::Destroy(cx->runtime()->defaultFreeOp(), baselineScript);
481     }
482   }
483 }
484 
RecompileOnStackBaselineScriptsForDebugMode(JSContext * cx,const DebugAPI::ExecutionObservableSet & obs,DebugAPI::IsObserving observing)485 bool jit::RecompileOnStackBaselineScriptsForDebugMode(
486     JSContext* cx, const DebugAPI::ExecutionObservableSet& obs,
487     DebugAPI::IsObserving observing) {
488   // First recompile the active scripts on the stack and patch the live
489   // frames.
490   Vector<DebugModeOSREntry> entries(cx);
491 
492   for (ActivationIterator iter(cx); !iter.done(); ++iter) {
493     if (iter->isJit()) {
494       if (!CollectJitStackScripts(cx, obs, iter, entries)) {
495         return false;
496       }
497     } else if (iter->isInterpreter()) {
498       if (!CollectInterpreterStackScripts(cx, obs, iter, entries)) {
499         return false;
500       }
501     }
502   }
503 
504   if (entries.empty()) {
505     return true;
506   }
507 
508   // When the profiler is enabled, we need to have suppressed sampling,
509   // since the basline jit scripts are in a state of flux.
510   MOZ_ASSERT(!cx->isProfilerSamplingEnabled());
511 
512   // Invalidate all scripts we are recompiling.
513   if (Zone* zone = obs.singleZone()) {
514     if (!InvalidateScriptsInZone(cx, zone, entries)) {
515       return false;
516     }
517   } else {
518     using ZoneRange = DebugAPI::ExecutionObservableSet::ZoneRange;
519     for (ZoneRange r = obs.zones()->all(); !r.empty(); r.popFront()) {
520       if (!InvalidateScriptsInZone(cx, r.front(), entries)) {
521         return false;
522       }
523     }
524   }
525 
526   // Try to recompile all the scripts. If we encounter an error, we need to
527   // roll back as if none of the compilations happened, so that we don't
528   // crash.
529   for (size_t i = 0; i < entries.length(); i++) {
530     JSScript* script = entries[i].script;
531     AutoRealm ar(cx, script);
532     if (!RecompileBaselineScriptForDebugMode(cx, script, observing)) {
533       UndoRecompileBaselineScriptsForDebugMode(cx, entries);
534       return false;
535     }
536   }
537 
538   // If all recompiles succeeded, destroy the old baseline scripts and patch
539   // the live frames.
540   //
541   // After this point the function must be infallible.
542 
543   for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) {
544     const DebugModeOSREntry& entry = iter.entry();
545     if (entry.recompiled()) {
546       BaselineScript::Destroy(cx->runtime()->defaultFreeOp(),
547                               entry.oldBaselineScript);
548     }
549   }
550 
551   size_t processed = 0;
552   for (ActivationIterator iter(cx); !iter.done(); ++iter) {
553     if (iter->isJit()) {
554       PatchBaselineFramesForDebugMode(cx, obs, iter, entries, &processed);
555     } else if (iter->isInterpreter()) {
556       SkipInterpreterFrameEntries(obs, iter, &processed);
557     }
558   }
559   MOZ_ASSERT(processed == entries.length());
560 
561   return true;
562 }
563