summaryrefslogtreecommitdiffstats
path: root/js/src/jit/JitScript.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/jit/JitScript.cpp
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
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.cpp732
1 files changed, 732 insertions, 0 deletions
diff --git a/js/src/jit/JitScript.cpp b/js/src/jit/JitScript.cpp
new file mode 100644
index 0000000000..ac0a39cbb5
--- /dev/null
+++ b/js/src/jit/JitScript.cpp
@@ -0,0 +1,732 @@
+/* -*- 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 "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),
+ endOffset_(endOffset),
+ icScript_(script->getWarmUpCount(),
+ fallbackStubsOffset - offsetOfICScript(),
+ endOffset - offsetOfICScript(),
+ /*depth=*/0) {
+ // 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);
+ }
+}
+
+#ifdef DEBUG
+JitScript::~JitScript() {
+ // The contents of the stub space are removed and freed separately after the
+ // next minor GC. See prepareForDestruction.
+ MOZ_ASSERT(jitScriptStubSpace_.isEmpty());
+
+ // BaselineScript and IonScript must have been destroyed at this point.
+ MOZ_ASSERT(!hasBaselineScript());
+ MOZ_ASSERT(!hasIonScript());
+}
+#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);
+
+ 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()->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) {
+ 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 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);
+ }
+}
+
+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 JitScript::resetWarmUpCount(uint32_t count) {
+ icScript_.resetWarmUpCount(count);
+ if (hasInliningRoot()) {
+ inliningRoot()->resetWarmUpCounts(count);
+ }
+}
+
+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);
+
+ js_delete(script);
+}
+
+void JitScript::prepareForDestruction(Zone* zone) {
+ // When the script contains pointers to nursery things, the store buffer can
+ // contain entries that point into the fallback stub space. Since we can
+ // destroy scripts outside the context of a GC, this situation could result
+ // in us trying to mark invalid store buffer entries.
+ //
+ // Defer freeing any allocated blocks until after the next minor GC.
+ jitScriptStubSpace_.freeAllAfterMinorGC(zone);
+
+ // Trigger write barriers.
+ 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;
+ MOZ_ALWAYS_TRUE(ComputeBinarySearchMid(FallbackStubs(this), pcOffset, &mid));
+
+ 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::purgeOptimizedStubs(JSScript* script) {
+ 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");
+
+ icScript()->purgeOptimizedStubs(zone);
+ if (hasInliningRoot()) {
+ inliningRoot()->purgeOptimizedStubs(zone);
+ }
+#ifdef DEBUG
+ failedICHash_.reset();
+ hasPurgedStubs_ = true;
+#endif
+}
+
+void ICScript::purgeOptimizedStubs(Zone* zone) {
+ for (size_t i = 0; i < numICEntries(); i++) {
+ ICEntry& entry = icEntry(i);
+ ICStub* lastStub = entry.firstStub();
+ while (!lastStub->isFallback()) {
+ lastStub = lastStub->toCacheIRStub()->next();
+ }
+
+ // Unlink all stubs allocated in the optimized space.
+ ICStub* stub = entry.firstStub();
+ ICCacheIRStub* prev = nullptr;
+
+ while (stub != lastStub) {
+ if (!stub->toCacheIRStub()->allocatedInFallbackSpace()) {
+ lastStub->toFallbackStub()->unlinkStub(zone, &entry, prev,
+ stub->toCacheIRStub());
+ stub = stub->toCacheIRStub()->next();
+ continue;
+ }
+
+ prev = stub->toCacheIRStub();
+ stub = stub->toCacheIRStub()->next();
+ }
+
+ lastStub->toFallbackStub()->clearHasFoldedStub();
+ }
+
+#ifdef DEBUG
+ // All remaining stubs must be allocated in the fallback space.
+ for (size_t i = 0; i < numICEntries(); i++) {
+ ICEntry& entry = icEntry(i);
+ ICStub* stub = entry.firstStub();
+ while (!stub->isFallback()) {
+ MOZ_ASSERT(stub->toCacheIRStub()->allocatedInFallbackSpace());
+ stub = stub->toCacheIRStub()->next();
+ }
+ }
+#endif
+}
+
+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);
+
+ unsigned 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);
+
+ 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 MarkActiveJitScripts(JSContext* cx,
+ const JitActivationIterator& activation) {
+ for (OnlyJSJitFrameIter iter(activation); !iter.done(); ++iter) {
+ const JSJitFrameIter& frame = iter.frame();
+ switch (frame.type()) {
+ case FrameType::BaselineJS:
+ frame.script()->jitScript()->setActive();
+ break;
+ case FrameType::Exit:
+ if (frame.exitFrame()->is<LazyLinkExitFrameLayout>()) {
+ LazyLinkExitFrameLayout* ll =
+ frame.exitFrame()->as<LazyLinkExitFrameLayout>();
+ JSScript* script =
+ ScriptFromCalleeToken(ll->jsFrame()->calleeToken());
+ script->jitScript()->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()->setActive();
+ for (InlineFrameIterator inlineIter(cx, &frame); inlineIter.more();
+ ++inlineIter) {
+ inlineIter.script()->jitScript()->setActive();
+ }
+ break;
+ }
+ default:;
+ }
+ }
+}
+
+void jit::MarkActiveJitScripts(Zone* zone) {
+ if (zone->isAtomsZone()) {
+ return;
+ }
+ JSContext* cx = TlsContext.get();
+ for (JitActivationIterator iter(cx); !iter.done(); ++iter) {
+ if (iter->compartment()->zone() == zone) {
+ MarkActiveJitScripts(cx, iter);
+ }
+ }
+}
+
+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* JitScript::createAllocSite(JSScript* script) {
+ MOZ_ASSERT(script->jitScript() == this);
+
+ Nursery& nursery = script->runtimeFromMainThread()->gc.nursery();
+ if (!nursery.canCreateAllocSite()) {
+ // Don't block attaching an optimized stub, but don't process allocations
+ // for this site.
+ return script->zone()->unknownAllocSite(JS::TraceKind::Object);
+ }
+
+ if (!allocSites_.reserve(allocSites_.length() + 1)) {
+ return nullptr;
+ }
+
+ ICStubSpace* stubSpace = jitScriptStubSpace();
+ auto* site =
+ static_cast<gc::AllocSite*>(stubSpace->alloc(sizeof(gc::AllocSite)));
+ if (!site) {
+ return nullptr;
+ }
+
+ new (site) gc::AllocSite(script->zone(), script, JS::TraceKind::Object);
+
+ allocSites_.infallibleAppend(site);
+
+ nursery.noteAllocSiteCreated();
+
+ return site;
+}
+
+bool JitScript::resetAllocSites(bool resetNurserySites,
+ bool resetPretenuredSites) {
+ MOZ_ASSERT(resetNurserySites || resetPretenuredSites);
+
+ bool anyReset = false;
+
+ for (gc::AllocSite* site : allocSites_) {
+ if ((resetNurserySites && site->initialHeap() == gc::Heap::Default) ||
+ (resetPretenuredSites && site->initialHeap() == gc::Heap::Tenured)) {
+ if (site->maybeResetState()) {
+ anyReset = true;
+ }
+ }
+ }
+
+ return anyReset;
+}
+
+JitScriptICStubSpace* ICScript::jitScriptStubSpace() {
+ if (isInlined()) {
+ return inliningRoot_->jitScriptStubSpace();
+ }
+ return outerJitScript()->jitScriptStubSpace();
+}
+
+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.
+//
+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.
+ MOZ_ASSERT(stub->isFallback());
+ h = mozilla::AddToHash(h, stub->enteredCount() == 0);
+ }
+
+ return h;
+}
+#endif