BaselineDebugModeOSR.cpp (19381B)
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 32 explicit DebugModeOSREntry(JSScript* script) 33 : script(script), 34 oldBaselineScript(script->baselineScript()), 35 pcOffset(uint32_t(-1)), 36 frameKind(RetAddrEntry::Kind::Invalid) {} 37 38 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 49 DebugModeOSREntry(DebugModeOSREntry&& other) 50 : script(other.script), 51 oldBaselineScript(other.oldBaselineScript), 52 pcOffset(other.pcOffset), 53 frameKind(other.frameKind) {} 54 55 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: 67 explicit UniqueScriptOSREntryIter(const DebugModeOSREntryVector& entries) 68 : entries_(entries), index_(0) {} 69 70 bool done() { return index_ == entries_.length(); } 71 72 const DebugModeOSREntry& entry() { 73 MOZ_ASSERT(!done()); 74 return entries_[index_]; 75 } 76 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 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 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 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 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().oneOriginValue(), 210 RetAddrEntryKindToString(frameKind), CodeName(JSOp(*pc))); 211 } 212 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 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 405 bool js::jit::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(), 415 script->column().oneOriginValue(), 416 observing ? "DEBUGGING" : "NORMAL EXECUTION"); 417 418 AutoKeepJitScripts keepJitScripts(cx); 419 BaselineScript* oldBaselineScript = 420 script->jitScript()->clearBaselineScript(cx->gcContext(), script); 421 422 BaselineOptions options({BaselineOption::ForceMainThreadCompilation}); 423 if (observing) { 424 options.setFlag(BaselineOption::ForceDebugInstrumentation); 425 } 426 MethodStatus status = BaselineCompile(cx, script, options); 427 if (status != Method_Compiled) { 428 // We will only fail to recompile for debug mode due to OOM. Restore 429 // the old baseline script in case something doesn't properly 430 // propagate OOM. 431 MOZ_ASSERT(status == Method_Error); 432 script->jitScript()->setBaselineScript(script, oldBaselineScript); 433 return false; 434 } 435 436 // Don't destroy the old baseline script yet, since if we fail any of the 437 // recompiles we need to rollback all the old baseline scripts. 438 MOZ_ASSERT(script->baselineScript()->hasDebugInstrumentation() == observing); 439 return true; 440 } 441 442 static bool InvalidateScriptsInZone(JSContext* cx, Zone* zone, 443 const Vector<DebugModeOSREntry>& entries) { 444 IonScriptKeyVector invalid; 445 for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) { 446 JSScript* script = iter.entry().script; 447 if (script->zone() != zone) { 448 continue; 449 } 450 451 if (script->hasIonScript()) { 452 if (!invalid.emplaceBack(script, script->ionScript()->compilationId())) { 453 ReportOutOfMemory(cx); 454 return false; 455 } 456 } 457 458 // Cancel off-thread Ion compile for anything that has a 459 // BaselineScript. If we relied on the call to Invalidate below to 460 // cancel off-thread Ion compiles, only those with existing IonScripts 461 // would be cancelled. 462 if (script->hasBaselineScript()) { 463 CancelOffThreadIonCompile(script); 464 } 465 } 466 467 // No need to cancel off-thread Ion compiles again, we already did it 468 // above. 469 Invalidate(cx, invalid, 470 /* resetUses = */ true, /* cancelOffThread = */ false); 471 return true; 472 } 473 474 static void UndoRecompileBaselineScriptsForDebugMode( 475 JSContext* cx, const DebugModeOSREntryVector& entries) { 476 // In case of failure, roll back the entire set of active scripts so that 477 // we don't have to patch return addresses on the stack. 478 for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) { 479 const DebugModeOSREntry& entry = iter.entry(); 480 JSScript* script = entry.script; 481 if (entry.recompiled()) { 482 BaselineScript* baselineScript = 483 script->jitScript()->clearBaselineScript(cx->gcContext(), script); 484 script->jitScript()->setBaselineScript(script, entry.oldBaselineScript); 485 BaselineScript::Destroy(cx->gcContext(), baselineScript); 486 } 487 } 488 } 489 490 bool jit::RecompileOnStackBaselineScriptsForDebugMode( 491 JSContext* cx, const DebugAPI::ExecutionObservableSet& obs, 492 DebugAPI::IsObserving observing) { 493 // First recompile the active scripts on the stack and patch the live 494 // frames. 495 Vector<DebugModeOSREntry> entries(cx); 496 497 for (ActivationIterator iter(cx); !iter.done(); ++iter) { 498 if (iter->isJit()) { 499 if (!CollectJitStackScripts(cx, obs, iter, entries)) { 500 return false; 501 } 502 } else if (iter->isInterpreter()) { 503 if (!CollectInterpreterStackScripts(cx, obs, iter, entries)) { 504 return false; 505 } 506 } 507 } 508 509 if (entries.empty()) { 510 return true; 511 } 512 513 // When the profiler is enabled, we need to have suppressed sampling, 514 // since the basline jit scripts are in a state of flux. 515 MOZ_ASSERT(!cx->isProfilerSamplingEnabled()); 516 517 // Invalidate all scripts we are recompiling. 518 if (Zone* zone = obs.singleZone()) { 519 if (!InvalidateScriptsInZone(cx, zone, entries)) { 520 return false; 521 } 522 } else { 523 using ZoneRange = DebugAPI::ExecutionObservableSet::ZoneRange; 524 for (ZoneRange r = obs.zones()->all(); !r.empty(); r.popFront()) { 525 if (!InvalidateScriptsInZone(cx, r.front(), entries)) { 526 return false; 527 } 528 } 529 } 530 531 // Try to recompile all the scripts. If we encounter an error, we need to 532 // roll back as if none of the compilations happened, so that we don't 533 // crash. 534 for (size_t i = 0; i < entries.length(); i++) { 535 JSScript* script = entries[i].script; 536 AutoRealm ar(cx, script); 537 if (!RecompileBaselineScriptForDebugMode(cx, script, observing)) { 538 UndoRecompileBaselineScriptsForDebugMode(cx, entries); 539 return false; 540 } 541 } 542 543 // If all recompiles succeeded, destroy the old baseline scripts and patch 544 // the live frames. 545 // 546 // After this point the function must be infallible. 547 548 for (UniqueScriptOSREntryIter iter(entries); !iter.done(); ++iter) { 549 const DebugModeOSREntry& entry = iter.entry(); 550 if (entry.recompiled()) { 551 BaselineScript::Destroy(cx->gcContext(), entry.oldBaselineScript); 552 } 553 } 554 555 size_t processed = 0; 556 for (ActivationIterator iter(cx); !iter.done(); ++iter) { 557 if (iter->isJit()) { 558 PatchBaselineFramesForDebugMode(cx, obs, iter, entries, &processed); 559 } else if (iter->isInterpreter()) { 560 SkipInterpreterFrameEntries(obs, iter, &processed); 561 } 562 } 563 MOZ_ASSERT(processed == entries.length()); 564 565 return true; 566 }