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