summaryrefslogtreecommitdiffstats
path: root/js/src/jit/JitScript.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/JitScript.h')
-rw-r--r--js/src/jit/JitScript.h541
1 files changed, 541 insertions, 0 deletions
diff --git a/js/src/jit/JitScript.h b/js/src/jit/JitScript.h
new file mode 100644
index 0000000000..d9d480e5c1
--- /dev/null
+++ b/js/src/jit/JitScript.h
@@ -0,0 +1,541 @@
+/* -*- 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/. */
+
+#ifndef jit_JitScript_h
+#define jit_JitScript_h
+
+#include "mozilla/Atomics.h"
+
+#include "jstypes.h"
+#include "jit/BaselineIC.h"
+#include "jit/TrialInlining.h"
+#include "js/UniquePtr.h"
+#include "util/TrailingArray.h"
+#include "vm/EnvironmentObject.h"
+
+class JS_PUBLIC_API JSScript;
+
+namespace js {
+namespace jit {
+
+class JitZone;
+
+// Information about a script's bytecode, used by WarpBuilder. This is cached
+// in JitScript.
+struct IonBytecodeInfo {
+ bool usesEnvironmentChain = false;
+ bool modifiesArguments = false;
+ bool hasTryFinally = false;
+};
+
+// Magic BaselineScript value indicating Baseline compilation has been disabled.
+static constexpr uintptr_t BaselineDisabledScript = 0x1;
+
+static BaselineScript* const BaselineDisabledScriptPtr =
+ reinterpret_cast<BaselineScript*>(BaselineDisabledScript);
+
+// Magic IonScript values indicating Ion compilation has been disabled or the
+// script is being Ion-compiled off-thread.
+static constexpr uintptr_t IonDisabledScript = 0x1;
+static constexpr uintptr_t IonCompilingScript = 0x2;
+
+static IonScript* const IonDisabledScriptPtr =
+ reinterpret_cast<IonScript*>(IonDisabledScript);
+static IonScript* const IonCompilingScriptPtr =
+ reinterpret_cast<IonScript*>(IonCompilingScript);
+
+class JitScript;
+class InliningRoot;
+
+/* [SMDOC] ICScript Lifetimes
+ *
+ * An ICScript owns an array of ICEntries, each of which owns a linked
+ * list of ICStubs.
+ *
+ * A JitScript contains an embedded ICScript. If it has done any trial
+ * inlining, it also owns an InliningRoot. The InliningRoot owns all
+ * of the ICScripts that have been created for inlining into the
+ * corresponding JitScript. This ties the lifetime of the inlined
+ * ICScripts to the lifetime of the JitScript itself.
+ *
+ * We store pointers to ICScripts in two other places: on the stack in
+ * BaselineFrame, and in IC stubs for CallInlinedFunction.
+ *
+ * The ICScript pointer in a BaselineFrame either points to the
+ * ICScript embedded in the JitScript for that frame, or to an inlined
+ * ICScript owned by a caller. In each case, there must be a frame on
+ * the stack corresponding to the JitScript that owns the current
+ * ICScript, which will keep the ICScript alive.
+ *
+ * Each ICStub is owned by an ICScript and, indirectly, a
+ * JitScript. An ICStub that uses CallInlinedFunction contains an
+ * ICScript for use by the callee. The ICStub and the callee ICScript
+ * are always owned by the same JitScript, so the callee ICScript will
+ * not be freed while the ICStub is alive.
+ *
+ * The lifetime of an ICScript is independent of the lifetimes of the
+ * BaselineScript and IonScript/WarpScript to which it
+ * corresponds. They can be destroyed and recreated, and the ICScript
+ * will remain valid.
+ */
+
+class alignas(uintptr_t) ICScript final : public TrailingArray {
+ public:
+ ICScript(uint32_t warmUpCount, Offset endOffset, uint32_t depth,
+ InliningRoot* inliningRoot = nullptr)
+ : inliningRoot_(inliningRoot),
+ warmUpCount_(warmUpCount),
+ endOffset_(endOffset),
+ depth_(depth) {}
+
+ bool isInlined() const { return depth_ > 0; }
+
+ [[nodiscard]] bool initICEntries(JSContext* cx, JSScript* script);
+
+ ICEntry& icEntry(size_t index) {
+ MOZ_ASSERT(index < numICEntries());
+ return icEntries()[index];
+ }
+
+ InliningRoot* inliningRoot() const { return inliningRoot_; }
+ uint32_t depth() const { return depth_; }
+
+ void resetWarmUpCount(uint32_t count) { warmUpCount_ = count; }
+
+ static constexpr size_t offsetOfFirstStub(uint32_t entryIndex) {
+ return sizeof(ICScript) + entryIndex * sizeof(ICEntry) +
+ ICEntry::offsetOfFirstStub();
+ }
+
+ static constexpr Offset offsetOfWarmUpCount() {
+ return offsetof(ICScript, warmUpCount_);
+ }
+ static constexpr Offset offsetOfDepth() { return offsetof(ICScript, depth_); }
+
+ static constexpr Offset offsetOfICEntries() { return sizeof(ICScript); }
+ uint32_t numICEntries() const {
+ return numElements<ICEntry>(icEntriesOffset(), endOffset());
+ }
+
+ ICEntry* interpreterICEntryFromPCOffset(uint32_t pcOffset);
+
+ ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset);
+ ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset,
+ ICEntry* prevLookedUpEntry);
+
+ ICEntry& icEntryFromPCOffset(uint32_t pcOffset);
+ ICEntry& icEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry);
+
+ [[nodiscard]] bool addInlinedChild(JSContext* cx,
+ js::UniquePtr<ICScript> child,
+ uint32_t pcOffset);
+ ICScript* findInlinedChild(uint32_t pcOffset);
+ void removeInlinedChild(uint32_t pcOffset);
+ bool hasInlinedChild(uint32_t pcOffset);
+
+ FallbackICStubSpace* fallbackStubSpace();
+ void purgeOptimizedStubs(Zone* zone);
+
+ void trace(JSTracer* trc);
+
+#ifdef DEBUG
+ mozilla::HashNumber hash();
+#endif
+
+ private:
+ class CallSite {
+ public:
+ CallSite(ICScript* callee, uint32_t pcOffset)
+ : callee_(callee), pcOffset_(pcOffset) {}
+ ICScript* callee_;
+ uint32_t pcOffset_;
+ };
+
+ // If this ICScript was created for trial inlining or has another
+ // ICScript inlined into it, a pointer to the root of the inlining
+ // tree. Otherwise, nullptr.
+ InliningRoot* inliningRoot_ = nullptr;
+
+ // ICScripts that have been inlined into this ICScript.
+ js::UniquePtr<Vector<CallSite>> inlinedChildren_;
+
+ // Number of times this copy of the script has been called or has had
+ // backedges taken. Reset if the script's JIT code is forcibly discarded.
+ // See also the ScriptWarmUpData class.
+ mozilla::Atomic<uint32_t, mozilla::Relaxed> warmUpCount_ = {};
+
+ // The size of this allocation.
+ Offset endOffset_;
+
+ // The inlining depth of this ICScript. 0 for the inlining root.
+ uint32_t depth_;
+
+ Offset icEntriesOffset() const { return offsetOfICEntries(); }
+ Offset endOffset() const { return endOffset_; }
+
+ ICEntry* icEntries() { return offsetToPointer<ICEntry>(icEntriesOffset()); }
+
+ JitScript* outerJitScript();
+
+ friend class JitScript;
+};
+
+// [SMDOC] JitScript
+//
+// JitScript stores type inference data, Baseline ICs and other JIT-related data
+// for a script. Scripts with a JitScript can run in the Baseline Interpreter.
+//
+// IC Data
+// =======
+// All IC data for Baseline (Interpreter and JIT) is stored in an ICScript. Each
+// JitScript contains an ICScript as the last field. Additional free-standing
+// ICScripts may be created during trial inlining. Ion has its own IC chains
+// stored in IonScript.
+//
+// For each IC we store an ICEntry, which points to the first ICStub in the
+// chain. Note that multiple stubs in the same zone can share Baseline IC code.
+// This works because the stub data is stored in the ICStub instead of baked in
+// in the stub code.
+//
+// Storing this separate from BaselineScript allows us to use the same ICs in
+// the Baseline Interpreter and Baseline JIT. It also simplifies debug mode OSR
+// because the JitScript can be reused when we have to recompile the
+// BaselineScript.
+//
+// The JitScript contains a fallback stub space. This stores all fallback stubs
+// and the "can GC" stubs. These stubs are never purged before destroying the
+// JitScript. Other stubs are stored in the optimized stub space stored in
+// JitZone and can be purged more eagerly. See JitScript::purgeOptimizedStubs.
+//
+// An ICScript contains a list of IC entries. There's one IC for each JOF_IC
+// bytecode op.
+//
+// The ICScript also contains the warmUpCount for the script.
+//
+// Inlining Data
+// =============
+// JitScript also contains a list of Warp compilations inlining this script, for
+// invalidation.
+//
+// Memory Layout
+// =============
+// JitScript contains an ICScript as the last field. ICScript has a trailing
+// (variable length) ICEntry array. The memory layout is as follows:
+//
+// Item | Offset
+// ------------------------+------------------------
+// JitScript | 0
+// -->ICScript (field) |
+// ICEntry[] | icEntriesOffset()
+//
+// These offsets are also used to compute numICEntries.
+class alignas(uintptr_t) JitScript final : public TrailingArray {
+ friend class ::JSScript;
+
+ // Allocated space for fallback IC stubs.
+ FallbackICStubSpace fallbackStubSpace_ = {};
+
+ // Profile string used by the profiler for Baseline Interpreter frames.
+ const char* profileString_ = nullptr;
+
+ // Data allocated lazily the first time this script is compiled, inlined, or
+ // analyzed by WarpBuilder. This is done lazily to improve performance and
+ // memory usage as most scripts are never Warp-compiled.
+ struct CachedIonData {
+ // For functions with a call object, template objects to use for the call
+ // object and decl env object (linked via the call object's enclosing
+ // scope).
+ const HeapPtr<EnvironmentObject*> templateEnv = nullptr;
+
+ // Analysis information based on the script and its bytecode.
+ IonBytecodeInfo bytecodeInfo = {};
+
+ CachedIonData(EnvironmentObject* templateEnv, IonBytecodeInfo bytecodeInfo);
+
+ CachedIonData(const CachedIonData&) = delete;
+ void operator=(const CachedIonData&) = delete;
+
+ void trace(JSTracer* trc);
+ };
+ js::UniquePtr<CachedIonData> cachedIonData_;
+
+ // Baseline code for the script. Either nullptr, BaselineDisabledScriptPtr or
+ // a valid BaselineScript*.
+ BaselineScript* baselineScript_ = nullptr;
+
+ // Ion code for this script. Either nullptr, IonDisabledScriptPtr,
+ // IonCompilingScriptPtr or a valid IonScript*.
+ IonScript* ionScript_ = nullptr;
+
+ // The size of this allocation.
+ Offset endOffset_ = 0;
+
+ struct Flags {
+ // Flag set when discarding JIT code to indicate this script is on the stack
+ // and type information and JIT code should not be discarded.
+ bool active : 1;
+
+ // True if this script entered Ion via OSR at a loop header.
+ bool hadIonOSR : 1;
+ };
+ Flags flags_ = {}; // Zero-initialize flags.
+
+ js::UniquePtr<InliningRoot> inliningRoot_;
+
+#ifdef DEBUG
+ // If the last warp compilation invalidated because of TranspiledCacheIR
+ // bailouts, this is a hash of the ICScripts used in that compilation.
+ // When recompiling, we assert that the hash has changed.
+ mozilla::Maybe<mozilla::HashNumber> failedICHash_;
+#endif
+
+ ICScript icScript_;
+ // End of fields.
+
+ Offset icEntriesOffset() const { return offsetOfICEntries(); }
+ Offset endOffset() const { return endOffset_; }
+
+ ICEntry* icEntries() { return icScript_.icEntries(); }
+
+ bool hasCachedIonData() const { return !!cachedIonData_; }
+
+ CachedIonData& cachedIonData() {
+ MOZ_ASSERT(hasCachedIonData());
+ return *cachedIonData_.get();
+ }
+ const CachedIonData& cachedIonData() const {
+ MOZ_ASSERT(hasCachedIonData());
+ return *cachedIonData_.get();
+ }
+
+ public:
+ JitScript(JSScript* script, Offset endOffset, const char* profileString);
+
+#ifdef DEBUG
+ ~JitScript() {
+ // The contents of the fallback stub space are removed and freed
+ // separately after the next minor GC. See prepareForDestruction.
+ MOZ_ASSERT(fallbackStubSpace_.isEmpty());
+
+ // BaselineScript and IonScript must have been destroyed at this point.
+ MOZ_ASSERT(!hasBaselineScript());
+ MOZ_ASSERT(!hasIonScript());
+ }
+#endif
+
+ [[nodiscard]] bool ensureHasCachedIonData(JSContext* cx, HandleScript script);
+
+ void setHadIonOSR() { flags_.hadIonOSR = true; }
+ bool hadIonOSR() const { return flags_.hadIonOSR; }
+
+ uint32_t numICEntries() const { return icScript_.numICEntries(); }
+
+ bool active() const { return flags_.active; }
+ void setActive() { flags_.active = true; }
+ void resetActive() { flags_.active = false; }
+
+ void ensureProfileString(JSContext* cx, JSScript* script);
+
+ const char* profileString() const {
+ MOZ_ASSERT(profileString_);
+ return profileString_;
+ }
+
+ static void Destroy(Zone* zone, JitScript* script);
+
+ static constexpr Offset offsetOfICEntries() { return sizeof(JitScript); }
+
+ static constexpr size_t offsetOfBaselineScript() {
+ return offsetof(JitScript, baselineScript_);
+ }
+ static constexpr size_t offsetOfIonScript() {
+ return offsetof(JitScript, ionScript_);
+ }
+ static constexpr size_t offsetOfICScript() {
+ return offsetof(JitScript, icScript_);
+ }
+ static constexpr size_t offsetOfWarmUpCount() {
+ return offsetOfICScript() + ICScript::offsetOfWarmUpCount();
+ }
+
+ uint32_t warmUpCount() const { return icScript_.warmUpCount_; }
+ void incWarmUpCount(uint32_t amount) { icScript_.warmUpCount_ += amount; }
+ void resetWarmUpCount(uint32_t count);
+
+ void 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.
+ fallbackStubSpace_.freeAllAfterMinorGC(zone);
+ }
+
+ FallbackICStubSpace* fallbackStubSpace() { return &fallbackStubSpace_; }
+
+ void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t* data,
+ size_t* fallbackStubs) const {
+ *data += mallocSizeOf(this);
+
+ // |data| already includes the ICStubSpace itself, so use
+ // sizeOfExcludingThis.
+ *fallbackStubs += fallbackStubSpace_.sizeOfExcludingThis(mallocSizeOf);
+ }
+
+ ICEntry& icEntry(size_t index) { return icScript_.icEntry(index); }
+
+ void trace(JSTracer* trc);
+ void purgeOptimizedStubs(JSScript* script);
+
+ ICEntry* interpreterICEntryFromPCOffset(uint32_t pcOffset) {
+ return icScript_.interpreterICEntryFromPCOffset(pcOffset);
+ }
+
+ ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset) {
+ return icScript_.maybeICEntryFromPCOffset(pcOffset);
+ }
+ ICEntry* maybeICEntryFromPCOffset(uint32_t pcOffset,
+ ICEntry* prevLookedUpEntry) {
+ return icScript_.maybeICEntryFromPCOffset(pcOffset, prevLookedUpEntry);
+ }
+
+ ICEntry& icEntryFromPCOffset(uint32_t pcOffset) {
+ return icScript_.icEntryFromPCOffset(pcOffset);
+ };
+ ICEntry& icEntryFromPCOffset(uint32_t pcOffset, ICEntry* prevLookedUpEntry) {
+ return icScript_.icEntryFromPCOffset(pcOffset, prevLookedUpEntry);
+ }
+
+ size_t allocBytes() const { return endOffset(); }
+
+ EnvironmentObject* templateEnvironment() const {
+ return cachedIonData().templateEnv;
+ }
+
+ bool modifiesArguments() const {
+ return cachedIonData().bytecodeInfo.modifiesArguments;
+ }
+ bool usesEnvironmentChain() const {
+ return cachedIonData().bytecodeInfo.usesEnvironmentChain;
+ }
+ bool hasTryFinally() const {
+ return cachedIonData().bytecodeInfo.hasTryFinally;
+ }
+
+ private:
+ // Methods to set baselineScript_ to a BaselineScript*, nullptr, or
+ // BaselineDisabledScriptPtr.
+ void setBaselineScriptImpl(JSScript* script, BaselineScript* baselineScript);
+ void setBaselineScriptImpl(JSFreeOp* fop, JSScript* script,
+ BaselineScript* baselineScript);
+
+ public:
+ // Methods for getting/setting/clearing a BaselineScript*.
+ bool hasBaselineScript() const {
+ bool res = baselineScript_ && baselineScript_ != BaselineDisabledScriptPtr;
+ MOZ_ASSERT_IF(!res, !hasIonScript());
+ return res;
+ }
+ BaselineScript* baselineScript() const {
+ MOZ_ASSERT(hasBaselineScript());
+ return baselineScript_;
+ }
+ void setBaselineScript(JSScript* script, BaselineScript* baselineScript) {
+ MOZ_ASSERT(!hasBaselineScript());
+ setBaselineScriptImpl(script, baselineScript);
+ MOZ_ASSERT(hasBaselineScript());
+ }
+ [[nodiscard]] BaselineScript* clearBaselineScript(JSFreeOp* fop,
+ JSScript* script) {
+ BaselineScript* baseline = baselineScript();
+ setBaselineScriptImpl(fop, script, nullptr);
+ return baseline;
+ }
+
+ private:
+ // Methods to set ionScript_ to an IonScript*, nullptr, or one of the special
+ // Ion{Disabled,Compiling}ScriptPtr values.
+ void setIonScriptImpl(JSFreeOp* fop, JSScript* script, IonScript* ionScript);
+ void setIonScriptImpl(JSScript* script, IonScript* ionScript);
+
+ public:
+ // Methods for getting/setting/clearing an IonScript*.
+ bool hasIonScript() const {
+ bool res = ionScript_ && ionScript_ != IonDisabledScriptPtr &&
+ ionScript_ != IonCompilingScriptPtr;
+ MOZ_ASSERT_IF(res, baselineScript_);
+ return res;
+ }
+ IonScript* ionScript() const {
+ MOZ_ASSERT(hasIonScript());
+ return ionScript_;
+ }
+ void setIonScript(JSScript* script, IonScript* ionScript) {
+ MOZ_ASSERT(!hasIonScript());
+ setIonScriptImpl(script, ionScript);
+ MOZ_ASSERT(hasIonScript());
+ }
+ [[nodiscard]] IonScript* clearIonScript(JSFreeOp* fop, JSScript* script) {
+ IonScript* ion = ionScript();
+ setIonScriptImpl(fop, script, nullptr);
+ return ion;
+ }
+
+ // Methods for off-thread compilation.
+ bool isIonCompilingOffThread() const {
+ return ionScript_ == IonCompilingScriptPtr;
+ }
+ void setIsIonCompilingOffThread(JSScript* script) {
+ MOZ_ASSERT(ionScript_ == nullptr);
+ setIonScriptImpl(script, IonCompilingScriptPtr);
+ }
+ void clearIsIonCompilingOffThread(JSScript* script) {
+ MOZ_ASSERT(isIonCompilingOffThread());
+ setIonScriptImpl(script, nullptr);
+ }
+ ICScript* icScript() { return &icScript_; }
+
+ bool hasInliningRoot() const { return !!inliningRoot_; }
+ InliningRoot* inliningRoot() const { return inliningRoot_.get(); }
+ InliningRoot* getOrCreateInliningRoot(JSContext* cx, JSScript* script);
+ void clearInliningRoot() { inliningRoot_.reset(); }
+
+#ifdef DEBUG
+ bool hasFailedICHash() const { return failedICHash_.isSome(); }
+ mozilla::HashNumber getFailedICHash() { return failedICHash_.extract(); }
+ void setFailedICHash(mozilla::HashNumber hash) {
+ MOZ_ASSERT(failedICHash_.isNothing());
+ failedICHash_.emplace(hash);
+ }
+#endif
+};
+
+// Ensures no JitScripts are purged in the current zone.
+class MOZ_RAII AutoKeepJitScripts {
+ jit::JitZone* zone_;
+ bool prev_;
+
+ AutoKeepJitScripts(const AutoKeepJitScripts&) = delete;
+ void operator=(const AutoKeepJitScripts&) = delete;
+
+ public:
+ explicit inline AutoKeepJitScripts(JSContext* cx);
+ inline ~AutoKeepJitScripts();
+};
+
+// Mark JitScripts on the stack as active, so that they are not discarded
+// during GC.
+void MarkActiveJitScripts(Zone* zone);
+
+#ifdef JS_STRUCTURED_SPEW
+void JitSpewBaselineICStats(JSScript* script, const char* dumpReason);
+#endif
+
+} // namespace jit
+} // namespace js
+
+#endif /* jit_JitScript_h */