JitScript.cpp (33620B)
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/JitScript-inl.h" 8 9 #include "mozilla/BinarySearch.h" 10 #include "mozilla/CheckedInt.h" 11 12 #include <utility> 13 14 #include "jit/BaselineIC.h" 15 #include "jit/BaselineJIT.h" 16 #include "jit/BytecodeAnalysis.h" 17 #include "jit/CacheIRCompiler.h" 18 #include "jit/IonScript.h" 19 #include "jit/JitFrames.h" 20 #include "jit/JitSpewer.h" 21 #include "jit/ScriptFromCalleeToken.h" 22 #include "jit/ShapeList.h" 23 #include "jit/TrialInlining.h" 24 #include "jit/WarpSnapshot.h" 25 #include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin 26 #include "vm/BytecodeUtil.h" 27 #include "vm/Compartment.h" 28 #include "vm/FrameIter.h" // js::OnlyJSJitFrameIter 29 #include "vm/JitActivation.h" 30 #include "vm/JSScript.h" 31 32 #include "gc/GCContext-inl.h" 33 #include "jit/JSJitFrameIter-inl.h" 34 #include "vm/JSContext-inl.h" 35 #include "vm/JSScript-inl.h" 36 37 using namespace js; 38 using namespace js::jit; 39 40 using mozilla::CheckedInt; 41 42 JitScript::JitScript(JSScript* script, Offset fallbackStubsOffset, 43 Offset endOffset, const char* profileString) 44 : profileString_(profileString), 45 owningScript_(script), 46 endOffset_(endOffset), 47 icScript_(script->getWarmUpCount(), 48 fallbackStubsOffset - offsetOfICScript(), 49 endOffset - offsetOfICScript(), 50 /*depth=*/0, script->length()) { 51 // Ensure the baselineScript_ and ionScript_ fields match the BaselineDisabled 52 // and IonDisabled script flags. 53 if (!script->canBaselineCompile()) { 54 setBaselineScriptImpl(script, BaselineDisabledScriptPtr); 55 } 56 if (!script->canIonCompile()) { 57 setIonScriptImpl(script, IonDisabledScriptPtr); 58 } 59 } 60 61 ICScript::~ICScript() { 62 // The contents of the AllocSite LifoAlloc are removed and freed separately 63 // after the next minor GC. See prepareForDestruction. 64 MOZ_ASSERT(allocSitesSpace_.isEmpty()); 65 MOZ_ASSERT(!envAllocSite_); 66 } 67 68 #ifdef DEBUG 69 JitScript::~JitScript() { 70 // BaselineScript and IonScript must have been destroyed at this point. 71 MOZ_ASSERT(!hasBaselineScript()); 72 MOZ_ASSERT(!hasIonScript()); 73 74 MOZ_ASSERT(!isInList()); 75 } 76 #else 77 JitScript::~JitScript() = default; 78 #endif 79 80 bool JSScript::createJitScript(JSContext* cx) { 81 MOZ_ASSERT(!hasJitScript()); 82 cx->check(this); 83 84 // Scripts with a JitScript can run in the Baseline Interpreter. Make sure 85 // we don't create a JitScript for scripts we shouldn't Baseline interpret. 86 MOZ_ASSERT_IF(IsBaselineInterpreterEnabled(), 87 CanBaselineInterpretScript(this)); 88 89 // Store the profile string in the JitScript if the profiler is enabled. 90 const char* profileString = nullptr; 91 if (cx->runtime()->geckoProfiler().enabled()) { 92 profileString = cx->runtime()->geckoProfiler().profileString(cx, this); 93 if (!profileString) { 94 return false; 95 } 96 97 if (!cx->runtime()->geckoProfiler().insertScriptSource(scriptSource())) { 98 ReportOutOfMemory(cx); 99 return false; 100 } 101 } 102 103 static_assert(sizeof(JitScript) % sizeof(uintptr_t) == 0, 104 "Trailing arrays must be aligned properly"); 105 static_assert(sizeof(ICEntry) % sizeof(uintptr_t) == 0, 106 "Trailing arrays must be aligned properly"); 107 108 static_assert( 109 sizeof(JitScript) == offsetof(JitScript, icScript_) + sizeof(ICScript), 110 "icScript_ must be the last field"); 111 112 // Calculate allocation size. 113 CheckedInt<uint32_t> allocSize = sizeof(JitScript); 114 allocSize += CheckedInt<uint32_t>(numICEntries()) * sizeof(ICEntry); 115 allocSize += CheckedInt<uint32_t>(numICEntries()) * sizeof(ICFallbackStub); 116 if (!allocSize.isValid()) { 117 ReportAllocationOverflow(cx); 118 return false; 119 } 120 121 void* raw = cx->pod_malloc<uint8_t>(allocSize.value()); 122 MOZ_ASSERT(uintptr_t(raw) % alignof(JitScript) == 0); 123 if (!raw) { 124 return false; 125 } 126 127 size_t fallbackStubsOffset = 128 sizeof(JitScript) + numICEntries() * sizeof(ICEntry); 129 130 UniquePtr<JitScript> jitScript(new (raw) JitScript( 131 this, fallbackStubsOffset, allocSize.value(), profileString)); 132 133 // Sanity check the length computation. 134 MOZ_ASSERT(jitScript->numICEntries() == numICEntries()); 135 136 jitScript->icScript()->initICEntries(cx, this); 137 138 cx->zone()->jitZone()->registerJitScript(jitScript.get()); 139 140 warmUpData_.initJitScript(jitScript.release()); 141 AddCellMemory(this, allocSize.value(), MemoryUse::JitScript); 142 143 // We have a JitScript so we can set the script's jitCodeRaw pointer to the 144 // Baseline Interpreter code. 145 updateJitCodeRaw(cx->runtime()); 146 147 return true; 148 } 149 150 void JSScript::maybeReleaseJitScript(JS::GCContext* gcx) { 151 MOZ_ASSERT(hasJitScript()); 152 153 if (zone()->jitZone()->keepJitScripts() || jitScript()->hasBaselineScript() || 154 jitScript()->icScript()->active()) { 155 return; 156 } 157 158 releaseJitScript(gcx); 159 } 160 161 void JSScript::releaseJitScript(JS::GCContext* gcx) { 162 MOZ_ASSERT(hasJitScript()); 163 MOZ_ASSERT(!hasBaselineScript()); 164 MOZ_ASSERT(!hasIonScript()); 165 166 gcx->removeCellMemory(this, jitScript()->allocBytes(), MemoryUse::JitScript); 167 168 JitScript::Destroy(zone(), jitScript()); 169 warmUpData_.clearJitScript(); 170 updateJitCodeRaw(gcx->runtime()); 171 } 172 173 void JSScript::releaseJitScriptOnFinalize(JS::GCContext* gcx) { 174 MOZ_ASSERT(hasJitScript()); 175 176 if (hasIonScript()) { 177 IonScript* ion = jitScript()->clearIonScript(gcx, this); 178 jit::IonScript::Destroy(gcx, ion); 179 } 180 181 if (hasBaselineScript()) { 182 BaselineScript* baseline = jitScript()->clearBaselineScript(gcx, this); 183 jit::BaselineScript::Destroy(gcx, baseline); 184 } 185 186 releaseJitScript(gcx); 187 } 188 189 void JitScript::trace(JSTracer* trc) { 190 TraceEdge(trc, &owningScript_, "JitScript::owningScript_"); 191 192 icScript_.trace(trc); 193 194 if (hasBaselineScript()) { 195 baselineScript()->trace(trc); 196 } 197 198 if (hasIonScript()) { 199 ionScript()->trace(trc); 200 } 201 202 if (templateEnv_.isSome()) { 203 TraceNullableEdge(trc, templateEnv_.ptr(), "jitscript-template-env"); 204 } 205 206 if (hasInliningRoot()) { 207 inliningRoot()->trace(trc); 208 } 209 } 210 211 void JitScript::traceWeak(JSTracer* trc) { 212 if (!icScript_.traceWeak(trc)) { 213 notePurgedStubs(); 214 } 215 216 if (hasInliningRoot()) { 217 if (!inliningRoot()->traceWeak(trc)) { 218 notePurgedStubs(); 219 } 220 } 221 222 if (hasIonScript()) { 223 ionScript()->traceWeak(trc); 224 } 225 } 226 227 void ICScript::trace(JSTracer* trc) { 228 // Mark all IC stub codes hanging off the IC stub entries. 229 for (size_t i = 0; i < numICEntries(); i++) { 230 ICEntry& ent = icEntry(i); 231 ICFallbackStub* fallback = fallbackStub(i); 232 ent.trace(trc, fallback); 233 } 234 235 for (gc::AllocSite* site : allocSites_) { 236 site->trace(trc); 237 } 238 } 239 240 bool ICScript::traceWeak(JSTracer* trc) { 241 // Mark all IC stub codes hanging off the IC stub entries. 242 bool allSurvived = true; 243 for (size_t i = 0; i < numICEntries(); i++) { 244 ICEntry& ent = icEntry(i); 245 ICFallbackStub* fallback = fallbackStub(i); 246 if (!ent.traceWeak(trc, fallback)) { 247 allSurvived = false; 248 } 249 } 250 251 return allSurvived; 252 } 253 254 bool ICScript::addInlinedChild(JSContext* cx, UniquePtr<ICScript> child, 255 uint32_t pcOffset) { 256 MOZ_ASSERT(!hasInlinedChild(pcOffset)); 257 258 if (!inlinedChildren_) { 259 inlinedChildren_ = cx->make_unique<Vector<CallSite>>(cx); 260 if (!inlinedChildren_) { 261 return false; 262 } 263 } 264 265 // First reserve space in inlinedChildren_ to ensure that if the ICScript is 266 // added to the inlining root, it can also be added to inlinedChildren_. 267 CallSite callsite(child.get(), pcOffset); 268 if (!inlinedChildren_->reserve(inlinedChildren_->length() + 1)) { 269 return false; 270 } 271 if (!inliningRoot()->addInlinedScript(std::move(child))) { 272 return false; 273 } 274 inlinedChildren_->infallibleAppend(callsite); 275 return true; 276 } 277 278 ICScript* ICScript::findInlinedChild(uint32_t pcOffset) { 279 for (auto& callsite : *inlinedChildren_) { 280 if (callsite.pcOffset_ == pcOffset) { 281 return callsite.callee_; 282 } 283 } 284 MOZ_CRASH("Inlined child expected at pcOffset"); 285 } 286 287 void ICScript::removeInlinedChild(uint32_t pcOffset) { 288 MOZ_ASSERT(inliningRoot()); 289 inlinedChildren_->eraseIf([pcOffset](const CallSite& callsite) -> bool { 290 return callsite.pcOffset_ == pcOffset; 291 }); 292 } 293 294 bool ICScript::hasInlinedChild(uint32_t pcOffset) { 295 if (!inlinedChildren_) { 296 return false; 297 } 298 for (auto& callsite : *inlinedChildren_) { 299 if (callsite.pcOffset_ == pcOffset) { 300 return true; 301 } 302 } 303 return false; 304 } 305 306 void ICScript::purgeInactiveICScripts() { 307 MOZ_ASSERT(inliningRoot()); 308 309 if (!inlinedChildren_) { 310 return; 311 } 312 313 inlinedChildren_->eraseIf( 314 [](const CallSite& callsite) { return !callsite.callee_->active(); }); 315 316 if (inlinedChildren_->empty()) { 317 inlinedChildren_.reset(); 318 return; 319 } 320 321 // We have an active callee ICScript. This means the current ICScript must be 322 // active too. 323 MOZ_ASSERT(active()); 324 } 325 326 void JitScript::resetWarmUpCount(uint32_t count) { 327 forEachICScript([&](ICScript* script) { script->resetWarmUpCount(count); }); 328 } 329 330 #ifdef DEBUG 331 bool JitScript::hasActiveICScript() const { 332 bool hasActive = false; 333 forEachICScript([&](const ICScript* script) { 334 if (script->active()) { 335 hasActive = true; 336 } 337 }); 338 return hasActive; 339 } 340 #endif 341 342 void JitScript::resetAllActiveFlags() { 343 forEachICScript([](ICScript* script) { script->resetActive(); }); 344 } 345 346 void JitScript::ensureProfileString(JSContext* cx, JSScript* script) { 347 MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled()); 348 349 if (profileString_) { 350 return; 351 } 352 353 AutoEnterOOMUnsafeRegion oomUnsafe; 354 profileString_ = cx->runtime()->geckoProfiler().profileString(cx, script); 355 if (!profileString_) { 356 oomUnsafe.crash("Failed to allocate profile string"); 357 } 358 } 359 360 void JitScript::ensureProfilerScriptSource(JSContext* cx, JSScript* script) { 361 MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled()); 362 363 AutoEnterOOMUnsafeRegion oomUnsafe; 364 if (!cx->runtime()->geckoProfiler().insertScriptSource( 365 script->scriptSource())) { 366 oomUnsafe.crash("Failed to insert profiled script source"); 367 } 368 } 369 370 /* static */ 371 void JitScript::Destroy(Zone* zone, JitScript* script) { 372 script->prepareForDestruction(zone); 373 374 // Remove from JitZone's linked list of JitScripts. 375 script->remove(); 376 377 js_delete(script); 378 } 379 380 template <typename F> 381 void JitScript::forEachICScript(const F& f) { 382 f(&icScript_); 383 if (hasInliningRoot()) { 384 inliningRoot()->forEachInlinedScript(f); 385 } 386 } 387 388 template <typename F> 389 void JitScript::forEachICScript(const F& f) const { 390 f(&icScript_); 391 if (hasInliningRoot()) { 392 inliningRoot()->forEachInlinedScript(f); 393 } 394 } 395 396 void ICScript::prepareForDestruction(Zone* zone) { 397 envAllocSite_ = nullptr; // Points into allocSitesSpace_. 398 399 // Defer freeing AllocSite memory until after the next minor GC, because the 400 // nursery can point to these alloc sites. 401 JSRuntime* rt = zone->runtimeFromMainThread(); 402 rt->gc.queueAllLifoBlocksForFreeAfterMinorGC(&allocSitesSpace_); 403 404 // Trigger write barriers. 405 PreWriteBarrier(zone, this); 406 } 407 408 void JitScript::prepareForDestruction(Zone* zone) { 409 forEachICScript( 410 [&](ICScript* script) { script->prepareForDestruction(zone); }); 411 412 // Trigger write barriers. 413 owningScript_ = nullptr; 414 baselineScript_.set(zone, nullptr); 415 ionScript_.set(zone, nullptr); 416 } 417 418 struct FallbackStubs { 419 ICScript* const icScript_; 420 421 explicit FallbackStubs(ICScript* icScript) : icScript_(icScript) {} 422 423 size_t numEntries() const { return icScript_->numICEntries(); } 424 ICFallbackStub* operator[](size_t index) const { 425 return icScript_->fallbackStub(index); 426 } 427 }; 428 429 static bool ComputeBinarySearchMid(FallbackStubs stubs, uint32_t pcOffset, 430 size_t* loc) { 431 return mozilla::BinarySearchIf( 432 stubs, 0, stubs.numEntries(), 433 [pcOffset](const ICFallbackStub* stub) { 434 if (pcOffset < stub->pcOffset()) { 435 return -1; 436 } 437 if (stub->pcOffset() < pcOffset) { 438 return 1; 439 } 440 return 0; 441 }, 442 loc); 443 } 444 445 ICEntry& ICScript::icEntryFromPCOffset(uint32_t pcOffset) { 446 size_t mid; 447 bool success = ComputeBinarySearchMid(FallbackStubs(this), pcOffset, &mid); 448 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 449 if (!success) { 450 MOZ_CRASH_UNSAFE_PRINTF("Missing icEntry for offset %d (max offset: %d)", 451 int(pcOffset), 452 int(fallbackStub(numICEntries() - 1)->pcOffset())); 453 } 454 #endif 455 MOZ_ALWAYS_TRUE(success); 456 457 MOZ_ASSERT(mid < numICEntries()); 458 459 ICEntry& entry = icEntry(mid); 460 MOZ_ASSERT(fallbackStubForICEntry(&entry)->pcOffset() == pcOffset); 461 return entry; 462 } 463 464 ICEntry* ICScript::interpreterICEntryFromPCOffset(uint32_t pcOffset) { 465 // We have to return the entry to store in BaselineFrame::interpreterICEntry 466 // when resuming in the Baseline Interpreter at pcOffset. The bytecode op at 467 // pcOffset does not necessarily have an ICEntry, so we want to return the 468 // first ICEntry for which the following is true: 469 // 470 // entry.pcOffset() >= pcOffset 471 // 472 // Fortunately, ComputeBinarySearchMid returns exactly this entry. 473 474 size_t mid; 475 ComputeBinarySearchMid(FallbackStubs(this), pcOffset, &mid); 476 477 if (mid < numICEntries()) { 478 ICEntry& entry = icEntry(mid); 479 MOZ_ASSERT(fallbackStubForICEntry(&entry)->pcOffset() >= pcOffset); 480 return &entry; 481 } 482 483 // Resuming at a pc after the last ICEntry. Just return nullptr: 484 // BaselineFrame::interpreterICEntry will never be used in this case. 485 return nullptr; 486 } 487 488 void JitScript::purgeInactiveICScripts() { 489 if (!hasInliningRoot()) { 490 return; 491 } 492 493 forEachICScript([](ICScript* script) { script->purgeInactiveICScripts(); }); 494 495 inliningRoot()->purgeInactiveICScripts(); 496 if (inliningRoot()->numInlinedScripts() == 0) { 497 inliningRoot_.reset(); 498 icScript()->inliningRoot_ = nullptr; 499 } else { 500 // If a callee script is active on the stack, the root script must be active 501 // too. 502 MOZ_ASSERT(icScript()->active()); 503 } 504 } 505 506 void JitScript::purgeStubs(JSScript* script, ICStubSpace& newStubSpace) { 507 MOZ_ASSERT(script->jitScript() == this); 508 509 Zone* zone = script->zone(); 510 if (IsAboutToBeFinalizedUnbarriered(script)) { 511 // We're sweeping and the script is dead. Don't purge optimized stubs 512 // because (1) accessing CacheIRStubInfo pointers in ICStubs is invalid 513 // because we may have swept them already when we started (incremental) 514 // sweeping and (2) it's unnecessary because this script will be finalized 515 // soon anyway. 516 return; 517 } 518 519 JitSpew(JitSpew_BaselineIC, "Purging optimized stubs"); 520 521 forEachICScript( 522 [&](ICScript* script) { script->purgeStubs(zone, newStubSpace); }); 523 524 notePurgedStubs(); 525 } 526 527 void ICScript::purgeStubs(Zone* zone, ICStubSpace& newStubSpace) { 528 for (size_t i = 0; i < numICEntries(); i++) { 529 ICEntry& entry = icEntry(i); 530 ICFallbackStub* fallback = fallbackStub(i); 531 532 // If this is a trial inlining call site and the callee's ICScript hasn't 533 // been discarded, clone the IC chain instead of purging stubs. In this case 534 // both the current ICScript and the callee's inlined ICScript must be 535 // active on the stack. 536 // 537 // We can't purge the IC stubs in this case because it'd confuse trial 538 // inlining if we try to inline again later and we already have an ICScript 539 // for this call site. 540 if (fallback->trialInliningState() == TrialInliningState::Inlined && 541 hasInlinedChild(fallback->pcOffset())) { 542 MOZ_ASSERT(active()); 543 #ifdef DEBUG 544 // The callee script must be active. Also assert its bytecode size field 545 // is valid, because this helps catch memory safety issues (bug 1871947). 546 ICScript* callee = findInlinedChild(fallback->pcOffset()); 547 MOZ_ASSERT(callee->active()); 548 MOZ_ASSERT(callee->bytecodeSize() < inliningRoot()->totalBytecodeSize()); 549 #endif 550 551 JSRuntime* rt = zone->runtimeFromMainThread(); 552 ICCacheIRStub* prev = nullptr; 553 ICStub* stub = entry.firstStub(); 554 while (stub != fallback) { 555 ICCacheIRStub* clone = stub->toCacheIRStub()->clone(rt, newStubSpace); 556 if (prev) { 557 prev->setNext(clone); 558 } else { 559 entry.setFirstStub(clone); 560 } 561 MOZ_ASSERT(stub->toCacheIRStub()->next() == clone->next()); 562 prev = clone; 563 stub = clone->next(); 564 } 565 continue; 566 } 567 568 MOZ_ASSERT(!hasInlinedChild(fallback->pcOffset())); 569 570 fallback->discardStubs(zone, &entry); 571 fallback->state().reset(); 572 } 573 } 574 575 bool JitScript::ensureHasCachedBaselineJitData(JSContext* cx, 576 HandleScript script) { 577 if (templateEnv_.isSome()) { 578 return true; 579 } 580 581 if (!script->function() || 582 !script->function()->needsFunctionEnvironmentObjects()) { 583 templateEnv_.emplace(); 584 return true; 585 } 586 587 Rooted<EnvironmentObject*> templateEnv(cx); 588 Rooted<JSFunction*> fun(cx, script->function()); 589 590 if (fun->needsNamedLambdaEnvironment()) { 591 templateEnv = NamedLambdaObject::createTemplateObject(cx, fun); 592 if (!templateEnv) { 593 return false; 594 } 595 } 596 597 if (fun->needsCallObject()) { 598 templateEnv = CallObject::createTemplateObject(cx, script, templateEnv); 599 if (!templateEnv) { 600 return false; 601 } 602 } 603 604 templateEnv_.emplace(templateEnv); 605 return true; 606 } 607 608 bool JitScript::ensureHasCachedIonData(JSContext* cx, HandleScript script) { 609 MOZ_ASSERT(script->jitScript() == this); 610 611 if (usesEnvironmentChain_.isSome()) { 612 return true; 613 } 614 615 if (!ensureHasCachedBaselineJitData(cx, script)) { 616 return false; 617 } 618 619 usesEnvironmentChain_.emplace(ScriptUsesEnvironmentChain(script)); 620 return true; 621 } 622 623 std::pair<CallObject*, NamedLambdaObject*> 624 JitScript::functionEnvironmentTemplates(JSFunction* fun) const { 625 EnvironmentObject* templateEnv = templateEnvironment(); 626 627 CallObject* callObjectTemplate = nullptr; 628 if (fun->needsCallObject()) { 629 callObjectTemplate = &templateEnv->as<CallObject>(); 630 } 631 632 NamedLambdaObject* namedLambdaTemplate = nullptr; 633 if (fun->needsNamedLambdaEnvironment()) { 634 if (callObjectTemplate) { 635 namedLambdaTemplate = 636 &callObjectTemplate->enclosingEnvironment().as<NamedLambdaObject>(); 637 } else { 638 namedLambdaTemplate = &templateEnv->as<NamedLambdaObject>(); 639 } 640 } 641 642 return {callObjectTemplate, namedLambdaTemplate}; 643 } 644 645 void JitScript::setBaselineScriptImpl(JSScript* script, 646 BaselineScript* baselineScript) { 647 JSRuntime* rt = script->runtimeFromMainThread(); 648 setBaselineScriptImpl(rt->gcContext(), script, baselineScript); 649 } 650 651 void JitScript::setBaselineScriptImpl(JS::GCContext* gcx, JSScript* script, 652 BaselineScript* baselineScript) { 653 if (hasBaselineScript()) { 654 gcx->removeCellMemory(script, baselineScript_->allocBytes(), 655 MemoryUse::BaselineScript); 656 baselineScript_.set(script->zone(), nullptr); 657 } 658 659 MOZ_ASSERT(ionScript_ == nullptr || ionScript_ == IonDisabledScriptPtr); 660 661 baselineScript_.set(script->zone(), baselineScript); 662 if (hasBaselineScript()) { 663 AddCellMemory(script, baselineScript_->allocBytes(), 664 MemoryUse::BaselineScript); 665 } 666 667 script->resetWarmUpResetCounter(); 668 script->updateJitCodeRaw(gcx->runtime()); 669 } 670 671 void JitScript::setIonScriptImpl(JSScript* script, IonScript* ionScript) { 672 JSRuntime* rt = script->runtimeFromMainThread(); 673 setIonScriptImpl(rt->gcContext(), script, ionScript); 674 } 675 676 void JitScript::setIonScriptImpl(JS::GCContext* gcx, JSScript* script, 677 IonScript* ionScript) { 678 MOZ_ASSERT_IF(ionScript != IonDisabledScriptPtr, 679 !baselineScript()->hasPendingIonCompileTask()); 680 681 JS::Zone* zone = script->zone(); 682 if (hasIonScript()) { 683 gcx->removeCellMemory(script, ionScript_->allocBytes(), 684 MemoryUse::IonScript); 685 ionScript_.set(zone, nullptr); 686 } 687 688 ionScript_.set(zone, ionScript); 689 MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript()); 690 if (hasIonScript()) { 691 AddCellMemory(script, ionScript_->allocBytes(), MemoryUse::IonScript); 692 } 693 694 script->updateJitCodeRaw(gcx->runtime()); 695 } 696 697 #ifdef JS_STRUCTURED_SPEW 698 static bool HasEnteredCounters(ICEntry& entry) { 699 ICStub* stub = entry.firstStub(); 700 if (stub && !stub->isFallback()) { 701 return true; 702 } 703 return false; 704 } 705 706 void jit::JitSpewBaselineICStats(JSScript* script, const char* dumpReason) { 707 MOZ_ASSERT(script->hasJitScript()); 708 JSContext* cx = TlsContext.get(); 709 AutoStructuredSpewer spew(cx, SpewChannel::BaselineICStats, script); 710 if (!spew) { 711 return; 712 } 713 714 JitScript* jitScript = script->jitScript(); 715 spew->property("reason", dumpReason); 716 spew->beginListProperty("entries"); 717 for (size_t i = 0; i < jitScript->numICEntries(); i++) { 718 ICEntry& entry = jitScript->icEntry(i); 719 ICFallbackStub* fallback = jitScript->fallbackStub(i); 720 if (!HasEnteredCounters(entry)) { 721 continue; 722 } 723 724 uint32_t pcOffset = fallback->pcOffset(); 725 jsbytecode* pc = script->offsetToPC(pcOffset); 726 727 JS::LimitedColumnNumberOneOrigin column; 728 unsigned int line = PCToLineNumber(script, pc, &column); 729 730 spew->beginObject(); 731 spew->property("op", CodeName(JSOp(*pc))); 732 spew->property("pc", pcOffset); 733 spew->property("line", line); 734 spew->property("column", column.oneOriginValue()); 735 736 spew->beginListProperty("counts"); 737 ICStub* stub = entry.firstStub(); 738 while (stub && !stub->isFallback()) { 739 uint32_t count = stub->enteredCount(); 740 spew->value(count); 741 stub = stub->toCacheIRStub()->next(); 742 } 743 spew->endList(); 744 spew->property("fallback_count", fallback->enteredCount()); 745 spew->endObject(); 746 } 747 spew->endList(); 748 } 749 #endif 750 751 using StubHashMap = HashMap<ICCacheIRStub*, ICCacheIRStub*, 752 DefaultHasher<ICCacheIRStub*>, SystemAllocPolicy>; 753 754 static void MarkActiveICScriptsAndCopyStubs( 755 JSContext* cx, const JitActivationIterator& activation, 756 ICStubSpace& newStubSpace, StubHashMap& alreadyClonedStubs) { 757 for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) { 758 const JSJitFrameIter& frame = iter.frame(); 759 switch (frame.type()) { 760 case FrameType::BaselineJS: 761 frame.script()->jitScript()->icScript()->setActive(); 762 // If the frame is using a trial-inlining ICScript, we have to preserve 763 // it too. 764 if (frame.baselineFrame()->icScript()->isInlined()) { 765 frame.baselineFrame()->icScript()->setActive(); 766 } 767 break; 768 case FrameType::BaselineStub: { 769 auto* layout = reinterpret_cast<BaselineStubFrameLayout*>(frame.fp()); 770 if (layout->maybeStubPtr() && !layout->maybeStubPtr()->isFallback()) { 771 ICCacheIRStub* stub = layout->maybeStubPtr()->toCacheIRStub(); 772 auto lookup = alreadyClonedStubs.lookupForAdd(stub); 773 if (!lookup) { 774 ICCacheIRStub* newStub = stub->clone(cx->runtime(), newStubSpace); 775 AutoEnterOOMUnsafeRegion oomUnsafe; 776 if (!alreadyClonedStubs.add(lookup, stub, newStub)) { 777 oomUnsafe.crash("MarkActiveICScriptsAndCopyStubs"); 778 } 779 } 780 layout->setStubPtr(lookup->value()); 781 782 // If this is a trial-inlining call site, also preserve the callee 783 // ICScript. Inlined constructor calls invoke CreateThisFromIC (which 784 // can trigger GC) before using the inlined ICScript. 785 JSJitFrameIter parentFrame(frame); 786 ++parentFrame; 787 BaselineFrame* blFrame = parentFrame.baselineFrame(); 788 jsbytecode* pc; 789 parentFrame.baselineScriptAndPc(nullptr, &pc); 790 uint32_t pcOffset = blFrame->script()->pcToOffset(pc); 791 if (blFrame->icScript()->hasInlinedChild(pcOffset)) { 792 blFrame->icScript()->findInlinedChild(pcOffset)->setActive(); 793 } 794 } 795 break; 796 } 797 case FrameType::Exit: 798 if (frame.exitFrame()->is<LazyLinkExitFrameLayout>()) { 799 LazyLinkExitFrameLayout* ll = 800 frame.exitFrame()->as<LazyLinkExitFrameLayout>(); 801 JSScript* script = 802 ScriptFromCalleeToken(ll->jsFrame()->calleeToken()); 803 script->jitScript()->icScript()->setActive(); 804 } 805 break; 806 case FrameType::Bailout: 807 case FrameType::IonJS: { 808 // Keep the JitScript and BaselineScript around, since bailouts from 809 // the ion jitcode need to re-enter into the Baseline code. 810 frame.script()->jitScript()->icScript()->setActive(); 811 for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more(); 812 ++inlineIter) { 813 inlineIter.script()->jitScript()->icScript()->setActive(); 814 } 815 // Because we're purging ICScripts, the bailout machinery should use 816 // the generic ICScript for inlined callees. 817 frame.ionScript()->notePurgedICScripts(); 818 break; 819 } 820 default:; 821 } 822 } 823 } 824 825 void jit::MarkActiveICScriptsAndCopyStubs(Zone* zone, 826 ICStubSpace& newStubSpace) { 827 if (zone->isAtomsZone()) { 828 return; 829 } 830 StubHashMap alreadyClonedStubs; 831 JSContext* cx = TlsContext.get(); 832 for (JitActivationIterator iter(cx); !iter.done(); ++iter) { 833 if (iter->compartment()->zone() == zone) { 834 MarkActiveICScriptsAndCopyStubs(cx, iter, newStubSpace, 835 alreadyClonedStubs); 836 } 837 } 838 } 839 840 InliningRoot* JitScript::getOrCreateInliningRoot(JSContext* cx, 841 JSScript* script) { 842 MOZ_ASSERT(script->jitScript() == this); 843 844 if (!inliningRoot_) { 845 inliningRoot_ = js::MakeUnique<InliningRoot>(cx, script); 846 if (!inliningRoot_) { 847 ReportOutOfMemory(cx); 848 return nullptr; 849 } 850 icScript_.inliningRoot_ = inliningRoot_.get(); 851 } 852 return inliningRoot_.get(); 853 } 854 855 gc::AllocSite* ICScript::getOrCreateAllocSite(JSScript* outerScript, 856 uint32_t pcOffset) { 857 // The script must be the outer script. 858 MOZ_ASSERT(outerScript->jitScript()->icScript() == this || 859 (inliningRoot() && inliningRoot()->owningScript() == outerScript)); 860 861 // The pcOffset must be valid for this (maybe inlined) script. 862 MOZ_ASSERT_IF(pcOffset != gc::AllocSite::EnvSitePCOffset, 863 pcOffset < bytecodeSize()); 864 865 for (gc::AllocSite* site : allocSites_) { 866 if (site->pcOffset() == pcOffset) { 867 MOZ_ASSERT(site->isNormal()); 868 MOZ_ASSERT(site->script() == outerScript); 869 MOZ_ASSERT(site->traceKind() == JS::TraceKind::Object); 870 return site; 871 } 872 } 873 874 Nursery& nursery = outerScript->runtimeFromMainThread()->gc.nursery(); 875 if (!nursery.canCreateAllocSite()) { 876 // Don't block attaching an optimized stub, but don't process allocations 877 // for this site. 878 return outerScript->zone()->unknownAllocSite(JS::TraceKind::Object); 879 } 880 881 if (!allocSites_.reserve(allocSites_.length() + 1)) { 882 return nullptr; 883 } 884 885 auto* site = allocSitesSpace_.new_<gc::AllocSite>( 886 outerScript->zone(), outerScript, pcOffset, JS::TraceKind::Object); 887 if (!site) { 888 return nullptr; 889 } 890 891 allocSites_.infallibleAppend(site); 892 893 nursery.noteAllocSiteCreated(); 894 895 return site; 896 } 897 898 void ICScript::ensureEnvAllocSite(JSScript* outerScript) { 899 if (envAllocSite_) { 900 return; 901 } 902 903 // Use a dummy offset for this site. 904 uint32_t pcoffset = gc::AllocSite::EnvSitePCOffset; 905 gc::AllocSite* site = getOrCreateAllocSite(outerScript, pcoffset); 906 if (!site) { 907 // Use the unknown site on failure. 908 site = outerScript->zone()->unknownAllocSite(JS::TraceKind::Object); 909 } 910 911 envAllocSite_ = site; 912 } 913 914 bool JitScript::resetAllocSites(bool resetNurserySites, 915 bool resetPretenuredSites) { 916 MOZ_ASSERT(resetNurserySites || resetPretenuredSites); 917 918 bool anyReset = false; 919 920 forEachICScript([&](ICScript* script) { 921 for (gc::AllocSite* site : script->allocSites_) { 922 if ((resetNurserySites && site->initialHeap() == gc::Heap::Default) || 923 (resetPretenuredSites && site->initialHeap() == gc::Heap::Tenured)) { 924 if (site->maybeResetState()) { 925 anyReset = true; 926 } 927 } 928 } 929 }); 930 931 return anyReset; 932 } 933 934 bool JitScript::hasPretenuredAllocSites() { 935 bool found = false; 936 forEachICScript([&](ICScript* script) { 937 if (!found) { 938 for (gc::AllocSite* site : script->allocSites_) { 939 if (site->initialHeap() == gc::Heap::Tenured) { 940 found = true; 941 } 942 } 943 } 944 }); 945 946 return found; 947 } 948 949 void JitScript::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, 950 size_t* data, size_t* allocSites) const { 951 *data += mallocSizeOf(this); 952 953 forEachICScript([=, this](const ICScript* script) { 954 // |data| already includes the outer ICScript because it's part of the 955 // JitScript. 956 if (script != &icScript_) { 957 *data += mallocSizeOf(script); 958 } 959 960 // |data| already includes the LifoAlloc and Vector, so use 961 // sizeOfExcludingThis. 962 *allocSites += script->allocSitesSpace_.sizeOfExcludingThis(mallocSizeOf); 963 *allocSites += script->allocSites_.sizeOfExcludingThis(mallocSizeOf); 964 }); 965 } 966 967 JitScript* ICScript::outerJitScript() { 968 MOZ_ASSERT(!isInlined()); 969 uint8_t* ptr = reinterpret_cast<uint8_t*>(this); 970 return reinterpret_cast<JitScript*>(ptr - JitScript::offsetOfICScript()); 971 } 972 973 #ifdef DEBUG 974 // This hash is used to verify that we do not recompile after a 975 // TranspiledCacheIR invalidation with the exact same ICs. 976 // 977 // It should change iff an ICEntry in this ICScript (or an ICScript 978 // inlined into this ICScript) is modified such that we will make a 979 // different decision in WarpScriptOracle::maybeInlineIC. This means: 980 // 981 // 1. The hash will change if we attach a new stub. 982 // 2. The hash will change if the entered count of any CacheIR stub 983 // other than the first changes from 0. 984 // 3. The hash will change if the entered count of the fallback stub 985 // changes from 0. 986 // 4. The hash will change if the failure count of the fallback stub 987 // changes from 0. 988 // 5. The hash will change if the set of shapes stored in ShapeListSnapshot 989 // is changed by stub folding or GC (the shapes in ShapeListObject are weak 990 // pointers). 991 HashNumber ICScript::hash(JSContext* cx) { 992 HashNumber h = 0; 993 for (size_t i = 0; i < numICEntries(); i++) { 994 ICStub* stub = icEntry(i).firstStub(); 995 ICFallbackStub* fallback = fallbackStub(i); 996 997 // Hash the address of the first stub. 998 h = mozilla::AddToHash(h, stub); 999 1000 // Hash shapes snapshotted in ShapeListSnapshot for GuardMultipleShapes. 1001 if (!stub->isFallback() && fallback->mayHaveFoldedStub()) { 1002 const CacheIRStubInfo* stubInfo = stub->toCacheIRStub()->stubInfo(); 1003 CacheIRReader reader(stubInfo); 1004 while (reader.more()) { 1005 CacheOp op = reader.readOp(); 1006 switch (op) { 1007 case CacheOp::GuardMultipleShapes: { 1008 auto args = reader.argsForGuardMultipleShapes(); 1009 JSObject* shapes = 1010 stubInfo->getStubField<StubField::Type::JSObject>( 1011 stub->toCacheIRStub(), args.shapesOffset); 1012 auto* shapesObject = &shapes->as<ShapeListObject>(); 1013 size_t numShapes = shapesObject->length(); 1014 if (ShapeListSnapshot::shouldSnapshot(numShapes)) { 1015 for (size_t i = 0; i < numShapes; i++) { 1016 Shape* shape = shapesObject->getUnbarriered(i); 1017 h = mozilla::AddToHash(h, shape); 1018 } 1019 // Also include the GC number to handle the case where we bail 1020 // out, add an additional shape, remove this new shape during GC, 1021 // and then recompile with the current set of shapes. 1022 // See bug 2002447. 1023 h = mozilla::AddToHash(h, cx->runtime()->gc.majorGCCount()); 1024 } 1025 break; 1026 } 1027 case CacheOp::GuardMultipleShapesToOffset: { 1028 auto args = reader.argsForGuardMultipleShapesToOffset(); 1029 JSObject* shapes = 1030 stubInfo->getStubField<StubField::Type::JSObject>( 1031 stub->toCacheIRStub(), args.shapesOffset); 1032 auto* shapesObject = &shapes->as<ShapeListWithOffsetsObject>(); 1033 size_t numShapes = shapesObject->numShapes(); 1034 if (ShapeListSnapshot::shouldSnapshot(numShapes)) { 1035 for (size_t i = 0; i < numShapes; i++) { 1036 Shape* shape = shapesObject->getShapeUnbarriered(i); 1037 h = mozilla::AddToHash(h, shape); 1038 h = mozilla::AddToHash(h, shapesObject->getOffset(i)); 1039 } 1040 } 1041 break; 1042 } 1043 default: 1044 reader.skip(CacheIROpInfos[size_t(op)].argLength); 1045 break; 1046 } 1047 } 1048 } 1049 1050 // Hash whether subsequent stubs have entry count 0. 1051 if (!stub->isFallback()) { 1052 stub = stub->toCacheIRStub()->next(); 1053 while (!stub->isFallback()) { 1054 h = mozilla::AddToHash(h, stub->enteredCount() == 0); 1055 stub = stub->toCacheIRStub()->next(); 1056 } 1057 } 1058 1059 // Hash whether the fallback has entry count 0 and failure count 0. 1060 MOZ_ASSERT(stub->isFallback()); 1061 h = mozilla::AddToHash(h, stub->enteredCount() == 0); 1062 h = mozilla::AddToHash(h, stub->toFallbackStub()->state().hasFailures()); 1063 } 1064 1065 return h; 1066 } 1067 #endif