summaryrefslogtreecommitdiffstats
path: root/js/src/jit/JitScript.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/jit/JitScript.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jit/JitScript.cpp')
-rw-r--r--js/src/jit/JitScript.cpp913
1 files changed, 913 insertions, 0 deletions
diff --git a/js/src/jit/JitScript.cpp b/js/src/jit/JitScript.cpp
new file mode 100644
index 0000000000..f2f6ee2c25
--- /dev/null
+++ b/js/src/jit/JitScript.cpp
@@ -0,0 +1,913 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jit/JitScript-inl.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/CheckedInt.h"
+
+#include <utility>
+
+#include "jit/BaselineIC.h"
+#include "jit/BaselineJIT.h"
+#include "jit/BytecodeAnalysis.h"
+#include "jit/IonScript.h"
+#include "jit/JitFrames.h"
+#include "jit/JitSpewer.h"
+#include "jit/ScriptFromCalleeToken.h"
+#include "jit/TrialInlining.h"
+#include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin
+#include "vm/BytecodeUtil.h"
+#include "vm/Compartment.h"
+#include "vm/FrameIter.h" // js::OnlyJSJitFrameIter
+#include "vm/JitActivation.h"
+#include "vm/JSScript.h"
+
+#include "gc/GCContext-inl.h"
+#include "jit/JSJitFrameIter-inl.h"
+#include "vm/JSContext-inl.h"
+#include "vm/JSScript-inl.h"
+
+using namespace js;
+using namespace js::jit;
+
+using mozilla::CheckedInt;
+
+JitScript::JitScript(JSScript* script, Offset fallbackStubsOffset,
+ Offset endOffset, const char* profileString)
+ : profileString_(profileString),
+ owningScript_(script),
+ endOffset_(endOffset),
+ icScript_(script->getWarmUpCount(),
+ fallbackStubsOffset - offsetOfICScript(),
+ endOffset - offsetOfICScript(),
+ /*depth=*/0, script->length()) {
+ // Ensure the baselineScript_ and ionScript_ fields match the BaselineDisabled
+ // and IonDisabled script flags.
+ if (!script->canBaselineCompile()) {
+ setBaselineScriptImpl(script, BaselineDisabledScriptPtr);
+ }
+ if (!script->canIonCompile()) {
+ setIonScriptImpl(script, IonDisabledScriptPtr);
+ }
+}
+
+ICScript::~ICScript() {
+ // The contents of the AllocSite LifoAlloc are removed and freed separately
+ // after the next minor GC. See prepareForDestruction.
+ MOZ_ASSERT(allocSitesSpace_.isEmpty());
+}
+
+#ifdef DEBUG
+JitScript::~JitScript() {
+ // BaselineScript and IonScript must have been destroyed at this point.
+ MOZ_ASSERT(!hasBaselineScript());
+ MOZ_ASSERT(!hasIonScript());
+
+ MOZ_ASSERT(!isInList());
+}
+#else
+JitScript::~JitScript() = default;
+#endif
+
+bool JSScript::createJitScript(JSContext* cx) {
+ MOZ_ASSERT(!hasJitScript());
+ cx->check(this);
+
+ // Scripts with a JitScript can run in the Baseline Interpreter. Make sure
+ // we don't create a JitScript for scripts we shouldn't Baseline interpret.
+ MOZ_ASSERT_IF(IsBaselineInterpreterEnabled(),
+ CanBaselineInterpretScript(this));
+
+ // Store the profile string in the JitScript if the profiler is enabled.
+ const char* profileString = nullptr;
+ if (cx->runtime()->geckoProfiler().enabled()) {
+ profileString = cx->runtime()->geckoProfiler().profileString(cx, this);
+ if (!profileString) {
+ return false;
+ }
+ }
+
+ static_assert(sizeof(JitScript) % sizeof(uintptr_t) == 0,
+ "Trailing arrays must be aligned properly");
+ static_assert(sizeof(ICEntry) % sizeof(uintptr_t) == 0,
+ "Trailing arrays must be aligned properly");
+
+ static_assert(
+ sizeof(JitScript) == offsetof(JitScript, icScript_) + sizeof(ICScript),
+ "icScript_ must be the last field");
+
+ // Calculate allocation size.
+ CheckedInt<uint32_t> allocSize = sizeof(JitScript);
+ allocSize += CheckedInt<uint32_t>(numICEntries()) * sizeof(ICEntry);
+ allocSize += CheckedInt<uint32_t>(numICEntries()) * sizeof(ICFallbackStub);
+ if (!allocSize.isValid()) {
+ ReportAllocationOverflow(cx);
+ return false;
+ }
+
+ void* raw = cx->pod_malloc<uint8_t>(allocSize.value());
+ MOZ_ASSERT(uintptr_t(raw) % alignof(JitScript) == 0);
+ if (!raw) {
+ return false;
+ }
+
+ size_t fallbackStubsOffset =
+ sizeof(JitScript) + numICEntries() * sizeof(ICEntry);
+
+ UniquePtr<JitScript> jitScript(new (raw) JitScript(
+ this, fallbackStubsOffset, allocSize.value(), profileString));
+
+ // Sanity check the length computation.
+ MOZ_ASSERT(jitScript->numICEntries() == numICEntries());
+
+ jitScript->icScript()->initICEntries(cx, this);
+
+ cx->zone()->jitZone()->registerJitScript(jitScript.get());
+
+ warmUpData_.initJitScript(jitScript.release());
+ AddCellMemory(this, allocSize.value(), MemoryUse::JitScript);
+
+ // We have a JitScript so we can set the script's jitCodeRaw pointer to the
+ // Baseline Interpreter code.
+ updateJitCodeRaw(cx->runtime());
+
+ return true;
+}
+
+void JSScript::maybeReleaseJitScript(JS::GCContext* gcx) {
+ MOZ_ASSERT(hasJitScript());
+
+ if (zone()->jitZone()->keepJitScripts() || jitScript()->hasBaselineScript() ||
+ jitScript()->icScript()->active()) {
+ return;
+ }
+
+ releaseJitScript(gcx);
+}
+
+void JSScript::releaseJitScript(JS::GCContext* gcx) {
+ MOZ_ASSERT(hasJitScript());
+ MOZ_ASSERT(!hasBaselineScript());
+ MOZ_ASSERT(!hasIonScript());
+
+ gcx->removeCellMemory(this, jitScript()->allocBytes(), MemoryUse::JitScript);
+
+ JitScript::Destroy(zone(), jitScript());
+ warmUpData_.clearJitScript();
+ updateJitCodeRaw(gcx->runtime());
+}
+
+void JSScript::releaseJitScriptOnFinalize(JS::GCContext* gcx) {
+ MOZ_ASSERT(hasJitScript());
+
+ if (hasIonScript()) {
+ IonScript* ion = jitScript()->clearIonScript(gcx, this);
+ jit::IonScript::Destroy(gcx, ion);
+ }
+
+ if (hasBaselineScript()) {
+ BaselineScript* baseline = jitScript()->clearBaselineScript(gcx, this);
+ jit::BaselineScript::Destroy(gcx, baseline);
+ }
+
+ releaseJitScript(gcx);
+}
+
+void JitScript::trace(JSTracer* trc) {
+ TraceEdge(trc, &owningScript_, "JitScript::owningScript_");
+
+ icScript_.trace(trc);
+
+ if (hasBaselineScript()) {
+ baselineScript()->trace(trc);
+ }
+
+ if (hasIonScript()) {
+ ionScript()->trace(trc);
+ }
+
+ if (templateEnv_.isSome()) {
+ TraceNullableEdge(trc, templateEnv_.ptr(), "jitscript-template-env");
+ }
+
+ if (hasInliningRoot()) {
+ inliningRoot()->trace(trc);
+ }
+}
+
+void JitScript::traceWeak(JSTracer* trc) {
+ if (!icScript_.traceWeak(trc)) {
+ notePurgedStubs();
+ }
+
+ if (hasInliningRoot()) {
+ if (!inliningRoot()->traceWeak(trc)) {
+ notePurgedStubs();
+ }
+ }
+
+ if (hasIonScript()) {
+ ionScript()->traceWeak(trc);
+ }
+}
+
+void ICScript::trace(JSTracer* trc) {
+ // Mark all IC stub codes hanging off the IC stub entries.
+ for (size_t i = 0; i < numICEntries(); i++) {
+ ICEntry& ent = icEntry(i);
+ ent.trace(trc);
+ }
+
+ for (gc::AllocSite* site : allocSites_) {
+ site->trace(trc);
+ }
+}
+
+bool ICScript::traceWeak(JSTracer* trc) {
+ // Mark all IC stub codes hanging off the IC stub entries.
+ bool allSurvived = true;
+ for (size_t i = 0; i < numICEntries(); i++) {
+ ICEntry& ent = icEntry(i);
+ if (!ent.traceWeak(trc)) {
+ allSurvived = false;
+ }
+ }
+
+ return allSurvived;
+}
+
+bool ICScript::addInlinedChild(JSContext* cx, UniquePtr<ICScript> child,
+ uint32_t pcOffset) {
+ MOZ_ASSERT(!hasInlinedChild(pcOffset));
+
+ if (!inlinedChildren_) {
+ inlinedChildren_ = cx->make_unique<Vector<CallSite>>(cx);
+ if (!inlinedChildren_) {
+ return false;
+ }
+ }
+
+ // First reserve space in inlinedChildren_ to ensure that if the ICScript is
+ // added to the inlining root, it can also be added to inlinedChildren_.
+ CallSite callsite(child.get(), pcOffset);
+ if (!inlinedChildren_->reserve(inlinedChildren_->length() + 1)) {
+ return false;
+ }
+ if (!inliningRoot()->addInlinedScript(std::move(child))) {
+ return false;
+ }
+ inlinedChildren_->infallibleAppend(callsite);
+ return true;
+}
+
+ICScript* ICScript::findInlinedChild(uint32_t pcOffset) {
+ for (auto& callsite : *inlinedChildren_) {
+ if (callsite.pcOffset_ == pcOffset) {
+ return callsite.callee_;
+ }
+ }
+ MOZ_CRASH("Inlined child expected at pcOffset");
+}
+
+void ICScript::removeInlinedChild(uint32_t pcOffset) {
+ MOZ_ASSERT(inliningRoot());
+ inlinedChildren_->eraseIf([pcOffset](const CallSite& callsite) -> bool {
+ return callsite.pcOffset_ == pcOffset;
+ });
+}
+
+bool ICScript::hasInlinedChild(uint32_t pcOffset) {
+ if (!inlinedChildren_) {
+ return false;
+ }
+ for (auto& callsite : *inlinedChildren_) {
+ if (callsite.pcOffset_ == pcOffset) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ICScript::purgeInactiveICScripts() {
+ MOZ_ASSERT(inliningRoot());
+
+ if (!inlinedChildren_) {
+ return;
+ }
+
+ inlinedChildren_->eraseIf(
+ [](const CallSite& callsite) { return !callsite.callee_->active(); });
+
+ if (inlinedChildren_->empty()) {
+ inlinedChildren_.reset();
+ return;
+ }
+
+ // We have an active callee ICScript. This means the current ICScript must be
+ // active too.
+ MOZ_ASSERT(active());
+}
+
+void JitScript::resetWarmUpCount(uint32_t count) {
+ forEachICScript([&](ICScript* script) { script->resetWarmUpCount(count); });
+}
+
+#ifdef DEBUG
+bool JitScript::hasActiveICScript() const {
+ bool hasActive = false;
+ forEachICScript([&](const ICScript* script) {
+ if (script->active()) {
+ hasActive = true;
+ }
+ });
+ return hasActive;
+}
+#endif
+
+void JitScript::resetAllActiveFlags() {
+ forEachICScript([](ICScript* script) { script->resetActive(); });
+}
+
+void JitScript::ensureProfileString(JSContext* cx, JSScript* script) {
+ MOZ_ASSERT(cx->runtime()->geckoProfiler().enabled());
+
+ if (profileString_) {
+ return;
+ }
+
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ profileString_ = cx->runtime()->geckoProfiler().profileString(cx, script);
+ if (!profileString_) {
+ oomUnsafe.crash("Failed to allocate profile string");
+ }
+}
+
+/* static */
+void JitScript::Destroy(Zone* zone, JitScript* script) {
+ script->prepareForDestruction(zone);
+
+ // Remove from JitZone's linked list of JitScripts.
+ script->remove();
+
+ js_delete(script);
+}
+
+template <typename F>
+void JitScript::forEachICScript(const F& f) {
+ f(&icScript_);
+ if (hasInliningRoot()) {
+ inliningRoot()->forEachInlinedScript(f);
+ }
+}
+
+template <typename F>
+void JitScript::forEachICScript(const F& f) const {
+ f(&icScript_);
+ if (hasInliningRoot()) {
+ inliningRoot()->forEachInlinedScript(f);
+ }
+}
+
+void ICScript::prepareForDestruction(Zone* zone) {
+ // Defer freeing AllocSite memory until after the next minor GC, because the
+ // nursery can point to these alloc sites.
+ JSRuntime* rt = zone->runtimeFromMainThread();
+ rt->gc.queueAllLifoBlocksForFreeAfterMinorGC(&allocSitesSpace_);
+
+ // Trigger write barriers.
+ PreWriteBarrier(zone, this);
+}
+
+void JitScript::prepareForDestruction(Zone* zone) {
+ forEachICScript(
+ [&](ICScript* script) { script->prepareForDestruction(zone); });
+
+ // Trigger write barriers.
+ owningScript_ = nullptr;
+ baselineScript_.set(zone, nullptr);
+ ionScript_.set(zone, nullptr);
+}
+
+struct FallbackStubs {
+ ICScript* const icScript_;
+
+ explicit FallbackStubs(ICScript* icScript) : icScript_(icScript) {}
+
+ size_t numEntries() const { return icScript_->numICEntries(); }
+ ICFallbackStub* operator[](size_t index) const {
+ return icScript_->fallbackStub(index);
+ }
+};
+
+static bool ComputeBinarySearchMid(FallbackStubs stubs, uint32_t pcOffset,
+ size_t* loc) {
+ return mozilla::BinarySearchIf(
+ stubs, 0, stubs.numEntries(),
+ [pcOffset](const ICFallbackStub* stub) {
+ if (pcOffset < stub->pcOffset()) {
+ return -1;
+ }
+ if (stub->pcOffset() < pcOffset) {
+ return 1;
+ }
+ return 0;
+ },
+ loc);
+}
+
+ICEntry& ICScript::icEntryFromPCOffset(uint32_t pcOffset) {
+ size_t mid;
+ bool success = ComputeBinarySearchMid(FallbackStubs(this), pcOffset, &mid);
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ if (!success) {
+ MOZ_CRASH_UNSAFE_PRINTF("Missing icEntry for offset %d (max offset: %d)",
+ int(pcOffset),
+ int(fallbackStub(numICEntries() - 1)->pcOffset()));
+ }
+#endif
+ MOZ_ALWAYS_TRUE(success);
+
+ MOZ_ASSERT(mid < numICEntries());
+
+ ICEntry& entry = icEntry(mid);
+ MOZ_ASSERT(fallbackStubForICEntry(&entry)->pcOffset() == pcOffset);
+ return entry;
+}
+
+ICEntry* ICScript::interpreterICEntryFromPCOffset(uint32_t pcOffset) {
+ // We have to return the entry to store in BaselineFrame::interpreterICEntry
+ // when resuming in the Baseline Interpreter at pcOffset. The bytecode op at
+ // pcOffset does not necessarily have an ICEntry, so we want to return the
+ // first ICEntry for which the following is true:
+ //
+ // entry.pcOffset() >= pcOffset
+ //
+ // Fortunately, ComputeBinarySearchMid returns exactly this entry.
+
+ size_t mid;
+ ComputeBinarySearchMid(FallbackStubs(this), pcOffset, &mid);
+
+ if (mid < numICEntries()) {
+ ICEntry& entry = icEntry(mid);
+ MOZ_ASSERT(fallbackStubForICEntry(&entry)->pcOffset() >= pcOffset);
+ return &entry;
+ }
+
+ // Resuming at a pc after the last ICEntry. Just return nullptr:
+ // BaselineFrame::interpreterICEntry will never be used in this case.
+ return nullptr;
+}
+
+void JitScript::purgeInactiveICScripts() {
+ if (!hasInliningRoot()) {
+ return;
+ }
+
+ forEachICScript([](ICScript* script) { script->purgeInactiveICScripts(); });
+
+ inliningRoot()->purgeInactiveICScripts();
+ if (inliningRoot()->numInlinedScripts() == 0) {
+ inliningRoot_.reset();
+ icScript()->inliningRoot_ = nullptr;
+ } else {
+ // If a callee script is active on the stack, the root script must be active
+ // too.
+ MOZ_ASSERT(icScript()->active());
+ }
+}
+
+void JitScript::purgeStubs(JSScript* script, ICStubSpace& newStubSpace) {
+ MOZ_ASSERT(script->jitScript() == this);
+
+ Zone* zone = script->zone();
+ if (IsAboutToBeFinalizedUnbarriered(script)) {
+ // We're sweeping and the script is dead. Don't purge optimized stubs
+ // because (1) accessing CacheIRStubInfo pointers in ICStubs is invalid
+ // because we may have swept them already when we started (incremental)
+ // sweeping and (2) it's unnecessary because this script will be finalized
+ // soon anyway.
+ return;
+ }
+
+ JitSpew(JitSpew_BaselineIC, "Purging optimized stubs");
+
+ forEachICScript(
+ [&](ICScript* script) { script->purgeStubs(zone, newStubSpace); });
+
+ notePurgedStubs();
+}
+
+void ICScript::purgeStubs(Zone* zone, ICStubSpace& newStubSpace) {
+ for (size_t i = 0; i < numICEntries(); i++) {
+ ICEntry& entry = icEntry(i);
+ ICFallbackStub* fallback = fallbackStub(i);
+
+ // If this is a trial inlining call site and the callee's ICScript hasn't
+ // been discarded, clone the IC chain instead of purging stubs. In this case
+ // both the current ICScript and the callee's inlined ICScript must be
+ // active on the stack.
+ //
+ // We can't purge the IC stubs in this case because it'd confuse trial
+ // inlining if we try to inline again later and we already have an ICScript
+ // for this call site.
+ if (fallback->trialInliningState() == TrialInliningState::Inlined &&
+ hasInlinedChild(fallback->pcOffset())) {
+ MOZ_ASSERT(active());
+ MOZ_ASSERT(findInlinedChild(fallback->pcOffset())->active());
+
+ JSRuntime* rt = zone->runtimeFromMainThread();
+ ICCacheIRStub* prev = nullptr;
+ ICStub* stub = entry.firstStub();
+ while (stub != fallback) {
+ ICCacheIRStub* clone = stub->toCacheIRStub()->clone(rt, newStubSpace);
+ if (prev) {
+ prev->setNext(clone);
+ } else {
+ entry.setFirstStub(clone);
+ }
+ MOZ_ASSERT(stub->toCacheIRStub()->next() == clone->next());
+ prev = clone;
+ stub = clone->next();
+ }
+ continue;
+ }
+
+ MOZ_ASSERT(!hasInlinedChild(fallback->pcOffset()));
+
+ fallback->discardStubs(zone, &entry);
+ fallback->state().reset();
+ }
+}
+
+bool JitScript::ensureHasCachedBaselineJitData(JSContext* cx,
+ HandleScript script) {
+ if (templateEnv_.isSome()) {
+ return true;
+ }
+
+ if (!script->function() ||
+ !script->function()->needsFunctionEnvironmentObjects()) {
+ templateEnv_.emplace();
+ return true;
+ }
+
+ Rooted<EnvironmentObject*> templateEnv(cx);
+ Rooted<JSFunction*> fun(cx, script->function());
+
+ if (fun->needsNamedLambdaEnvironment()) {
+ templateEnv = NamedLambdaObject::createTemplateObject(cx, fun);
+ if (!templateEnv) {
+ return false;
+ }
+ }
+
+ if (fun->needsCallObject()) {
+ templateEnv = CallObject::createTemplateObject(cx, script, templateEnv);
+ if (!templateEnv) {
+ return false;
+ }
+ }
+
+ templateEnv_.emplace(templateEnv);
+ return true;
+}
+
+bool JitScript::ensureHasCachedIonData(JSContext* cx, HandleScript script) {
+ MOZ_ASSERT(script->jitScript() == this);
+
+ if (usesEnvironmentChain_.isSome()) {
+ return true;
+ }
+
+ if (!ensureHasCachedBaselineJitData(cx, script)) {
+ return false;
+ }
+
+ usesEnvironmentChain_.emplace(ScriptUsesEnvironmentChain(script));
+ return true;
+}
+
+void JitScript::setBaselineScriptImpl(JSScript* script,
+ BaselineScript* baselineScript) {
+ JSRuntime* rt = script->runtimeFromMainThread();
+ setBaselineScriptImpl(rt->gcContext(), script, baselineScript);
+}
+
+void JitScript::setBaselineScriptImpl(JS::GCContext* gcx, JSScript* script,
+ BaselineScript* baselineScript) {
+ if (hasBaselineScript()) {
+ gcx->removeCellMemory(script, baselineScript_->allocBytes(),
+ MemoryUse::BaselineScript);
+ baselineScript_.set(script->zone(), nullptr);
+ }
+
+ MOZ_ASSERT(ionScript_ == nullptr || ionScript_ == IonDisabledScriptPtr);
+
+ baselineScript_.set(script->zone(), baselineScript);
+ if (hasBaselineScript()) {
+ AddCellMemory(script, baselineScript_->allocBytes(),
+ MemoryUse::BaselineScript);
+ }
+
+ script->resetWarmUpResetCounter();
+ script->updateJitCodeRaw(gcx->runtime());
+}
+
+void JitScript::setIonScriptImpl(JSScript* script, IonScript* ionScript) {
+ JSRuntime* rt = script->runtimeFromMainThread();
+ setIonScriptImpl(rt->gcContext(), script, ionScript);
+}
+
+void JitScript::setIonScriptImpl(JS::GCContext* gcx, JSScript* script,
+ IonScript* ionScript) {
+ MOZ_ASSERT_IF(ionScript != IonDisabledScriptPtr,
+ !baselineScript()->hasPendingIonCompileTask());
+
+ JS::Zone* zone = script->zone();
+ if (hasIonScript()) {
+ gcx->removeCellMemory(script, ionScript_->allocBytes(),
+ MemoryUse::IonScript);
+ ionScript_.set(zone, nullptr);
+ }
+
+ ionScript_.set(zone, ionScript);
+ MOZ_ASSERT_IF(hasIonScript(), hasBaselineScript());
+ if (hasIonScript()) {
+ AddCellMemory(script, ionScript_->allocBytes(), MemoryUse::IonScript);
+ }
+
+ script->updateJitCodeRaw(gcx->runtime());
+}
+
+#ifdef JS_STRUCTURED_SPEW
+static bool HasEnteredCounters(ICEntry& entry) {
+ ICStub* stub = entry.firstStub();
+ if (stub && !stub->isFallback()) {
+ return true;
+ }
+ return false;
+}
+
+void jit::JitSpewBaselineICStats(JSScript* script, const char* dumpReason) {
+ MOZ_ASSERT(script->hasJitScript());
+ JSContext* cx = TlsContext.get();
+ AutoStructuredSpewer spew(cx, SpewChannel::BaselineICStats, script);
+ if (!spew) {
+ return;
+ }
+
+ JitScript* jitScript = script->jitScript();
+ spew->property("reason", dumpReason);
+ spew->beginListProperty("entries");
+ for (size_t i = 0; i < jitScript->numICEntries(); i++) {
+ ICEntry& entry = jitScript->icEntry(i);
+ ICFallbackStub* fallback = jitScript->fallbackStub(i);
+ if (!HasEnteredCounters(entry)) {
+ continue;
+ }
+
+ uint32_t pcOffset = fallback->pcOffset();
+ jsbytecode* pc = script->offsetToPC(pcOffset);
+
+ JS::LimitedColumnNumberOneOrigin column;
+ unsigned int line = PCToLineNumber(script, pc, &column);
+
+ spew->beginObject();
+ spew->property("op", CodeName(JSOp(*pc)));
+ spew->property("pc", pcOffset);
+ spew->property("line", line);
+ spew->property("column", column.oneOriginValue());
+
+ spew->beginListProperty("counts");
+ ICStub* stub = entry.firstStub();
+ while (stub && !stub->isFallback()) {
+ uint32_t count = stub->enteredCount();
+ spew->value(count);
+ stub = stub->toCacheIRStub()->next();
+ }
+ spew->endList();
+ spew->property("fallback_count", fallback->enteredCount());
+ spew->endObject();
+ }
+ spew->endList();
+}
+#endif
+
+static void MarkActiveICScriptsAndCopyStubs(
+ JSContext* cx, const JitActivationIterator& activation,
+ ICStubSpace& newStubSpace) {
+ for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
+ const JSJitFrameIter& frame = iter.frame();
+ switch (frame.type()) {
+ case FrameType::BaselineJS:
+ frame.script()->jitScript()->icScript()->setActive();
+ // If the frame is using a trial-inlining ICScript, we have to preserve
+ // it too.
+ if (frame.baselineFrame()->icScript()->isInlined()) {
+ frame.baselineFrame()->icScript()->setActive();
+ }
+ break;
+ case FrameType::BaselineStub: {
+ auto* layout = reinterpret_cast<BaselineStubFrameLayout*>(frame.fp());
+ if (layout->maybeStubPtr() && !layout->maybeStubPtr()->isFallback()) {
+ ICCacheIRStub* stub = layout->maybeStubPtr()->toCacheIRStub();
+ ICCacheIRStub* newStub = stub->clone(cx->runtime(), newStubSpace);
+ layout->setStubPtr(newStub);
+
+ JSJitFrameIter parentFrame(frame);
+ ++parentFrame;
+ BaselineFrame* blFrame = parentFrame.baselineFrame();
+ jsbytecode* pc;
+ parentFrame.baselineScriptAndPc(nullptr, &pc);
+ uint32_t pcOffset = blFrame->script()->pcToOffset(pc);
+ if (blFrame->icScript()->hasInlinedChild(pcOffset)) {
+ blFrame->icScript()->findInlinedChild(pcOffset)->setActive();
+ }
+ }
+ break;
+ }
+ case FrameType::Exit:
+ if (frame.exitFrame()->is<LazyLinkExitFrameLayout>()) {
+ LazyLinkExitFrameLayout* ll =
+ frame.exitFrame()->as<LazyLinkExitFrameLayout>();
+ JSScript* script =
+ ScriptFromCalleeToken(ll->jsFrame()->calleeToken());
+ script->jitScript()->icScript()->setActive();
+ }
+ break;
+ case FrameType::Bailout:
+ case FrameType::IonJS: {
+ // Keep the JitScript and BaselineScript around, since bailouts from
+ // the ion jitcode need to re-enter into the Baseline code.
+ frame.script()->jitScript()->icScript()->setActive();
+ for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more();
+ ++inlineIter) {
+ inlineIter.script()->jitScript()->icScript()->setActive();
+ }
+ // Because we're purging ICScripts, the bailout machinery should use
+ // the generic ICScript for inlined callees.
+ frame.ionScript()->notePurgedICScripts();
+ break;
+ }
+ default:;
+ }
+ }
+}
+
+void jit::MarkActiveICScriptsAndCopyStubs(Zone* zone,
+ ICStubSpace& newStubSpace) {
+ if (zone->isAtomsZone()) {
+ return;
+ }
+ JSContext* cx = TlsContext.get();
+ for (JitActivationIterator iter(cx); !iter.done(); ++iter) {
+ if (iter->compartment()->zone() == zone) {
+ MarkActiveICScriptsAndCopyStubs(cx, iter, newStubSpace);
+ }
+ }
+}
+
+InliningRoot* JitScript::getOrCreateInliningRoot(JSContext* cx,
+ JSScript* script) {
+ if (!inliningRoot_) {
+ inliningRoot_ = js::MakeUnique<InliningRoot>(cx, script);
+ if (!inliningRoot_) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+ icScript_.inliningRoot_ = inliningRoot_.get();
+ }
+ return inliningRoot_.get();
+}
+
+gc::AllocSite* ICScript::getOrCreateAllocSite(JSScript* outerScript,
+ uint32_t pcOffset) {
+ // The script must be the outer script.
+ MOZ_ASSERT(outerScript->jitScript()->icScript() == this ||
+ (inliningRoot() && inliningRoot()->owningScript() == outerScript));
+
+ // The pcOffset must be for this (maybe inlined) script.
+ MOZ_ASSERT(pcOffset < bytecodeSize());
+
+ for (gc::AllocSite* site : allocSites_) {
+ if (site->pcOffset() == pcOffset) {
+ MOZ_ASSERT(site->isNormal());
+ MOZ_ASSERT(site->script() == outerScript);
+ MOZ_ASSERT(site->traceKind() == JS::TraceKind::Object);
+ return site;
+ }
+ }
+
+ Nursery& nursery = outerScript->runtimeFromMainThread()->gc.nursery();
+ if (!nursery.canCreateAllocSite()) {
+ // Don't block attaching an optimized stub, but don't process allocations
+ // for this site.
+ return outerScript->zone()->unknownAllocSite(JS::TraceKind::Object);
+ }
+
+ if (!allocSites_.reserve(allocSites_.length() + 1)) {
+ return nullptr;
+ }
+
+ auto* site = allocSitesSpace_.new_<gc::AllocSite>(
+ outerScript->zone(), outerScript, pcOffset, JS::TraceKind::Object);
+ if (!site) {
+ return nullptr;
+ }
+
+ allocSites_.infallibleAppend(site);
+
+ nursery.noteAllocSiteCreated();
+
+ return site;
+}
+
+bool JitScript::resetAllocSites(bool resetNurserySites,
+ bool resetPretenuredSites) {
+ MOZ_ASSERT(resetNurserySites || resetPretenuredSites);
+
+ bool anyReset = false;
+
+ forEachICScript([&](ICScript* script) {
+ for (gc::AllocSite* site : script->allocSites_) {
+ if ((resetNurserySites && site->initialHeap() == gc::Heap::Default) ||
+ (resetPretenuredSites && site->initialHeap() == gc::Heap::Tenured)) {
+ if (site->maybeResetState()) {
+ anyReset = true;
+ }
+ }
+ }
+ });
+
+ return anyReset;
+}
+
+void JitScript::addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
+ size_t* data, size_t* allocSites) const {
+ *data += mallocSizeOf(this);
+
+ forEachICScript([=](const ICScript* script) {
+ // |data| already includes the outer ICScript because it's part of the
+ // JitScript.
+ if (script != &icScript_) {
+ *data += mallocSizeOf(script);
+ }
+
+ // |data| already includes the LifoAlloc and Vector, so use
+ // sizeOfExcludingThis.
+ *allocSites += script->allocSitesSpace_.sizeOfExcludingThis(mallocSizeOf);
+ *allocSites += script->allocSites_.sizeOfExcludingThis(mallocSizeOf);
+ });
+}
+
+JitScript* ICScript::outerJitScript() {
+ MOZ_ASSERT(!isInlined());
+ uint8_t* ptr = reinterpret_cast<uint8_t*>(this);
+ return reinterpret_cast<JitScript*>(ptr - JitScript::offsetOfICScript());
+}
+
+#ifdef DEBUG
+// This hash is used to verify that we do not recompile after a
+// TranspiledCacheIR invalidation with the exact same ICs.
+//
+// It should change iff an ICEntry in this ICScript (or an ICScript
+// inlined into this ICScript) is modified such that we will make a
+// different decision in WarpScriptOracle::maybeInlineIC. This means:
+//
+// 1. The hash will change if we attach a new stub.
+// 2. The hash will change if the entered count of any CacheIR stub
+// other than the first changes from 0.
+// 3. The hash will change if the entered count of the fallback stub
+// changes from 0.
+// 4. The hash will change if the failure count of the fallback stub
+// changes from 0.
+HashNumber ICScript::hash() {
+ HashNumber h = 0;
+ for (size_t i = 0; i < numICEntries(); i++) {
+ ICStub* stub = icEntry(i).firstStub();
+
+ // Hash the address of the first stub.
+ h = mozilla::AddToHash(h, stub);
+
+ // Hash whether subsequent stubs have entry count 0.
+ if (!stub->isFallback()) {
+ stub = stub->toCacheIRStub()->next();
+ while (!stub->isFallback()) {
+ h = mozilla::AddToHash(h, stub->enteredCount() == 0);
+ stub = stub->toCacheIRStub()->next();
+ }
+ }
+
+ // Hash whether the fallback has entry count 0 and failure count 0.
+ MOZ_ASSERT(stub->isFallback());
+ h = mozilla::AddToHash(h, stub->enteredCount() == 0);
+ h = mozilla::AddToHash(h, stub->toFallbackStub()->state().hasFailures());
+ }
+
+ return h;
+}
+#endif