diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/src/jit/BaselineJIT.h | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | js/src/jit/BaselineJIT.h | 593 |
1 files changed, 593 insertions, 0 deletions
diff --git a/js/src/jit/BaselineJIT.h b/js/src/jit/BaselineJIT.h new file mode 100644 index 0000000000..b1e48dbb2b --- /dev/null +++ b/js/src/jit/BaselineJIT.h @@ -0,0 +1,593 @@ +/* -*- 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_BaselineJIT_h +#define jit_BaselineJIT_h + +#include "mozilla/Assertions.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Span.h" + +#include <stddef.h> +#include <stdint.h> + +#include "jsfriendapi.h" + +#include "jit/IonTypes.h" +#include "jit/JitCode.h" +#include "jit/JitContext.h" +#include "jit/JitOptions.h" +#include "jit/shared/Assembler-shared.h" +#include "js/Principals.h" +#include "js/TypeDecls.h" +#include "js/Vector.h" +#include "threading/ProtectedData.h" +#include "util/TrailingArray.h" +#include "vm/JSScript.h" + +namespace js { + +class InterpreterFrame; +class RunState; + +namespace jit { + +class BaselineFrame; +class ExceptionBailoutInfo; +class IonCompileTask; +class JitActivation; +class JSJitFrameIter; + +// Base class for entries mapping a pc offset to a native code offset. +class BasePCToNativeEntry { + uint32_t pcOffset_; + uint32_t nativeOffset_; + + public: + BasePCToNativeEntry(uint32_t pcOffset, uint32_t nativeOffset) + : pcOffset_(pcOffset), nativeOffset_(nativeOffset) {} + uint32_t pcOffset() const { return pcOffset_; } + uint32_t nativeOffset() const { return nativeOffset_; } +}; + +// Class used during Baseline compilation to store the native code offset for +// resume offset ops. +class ResumeOffsetEntry : public BasePCToNativeEntry { + public: + using BasePCToNativeEntry::BasePCToNativeEntry; +}; + +using ResumeOffsetEntryVector = + Vector<ResumeOffsetEntry, 16, SystemAllocPolicy>; + +// Largest script that the baseline compiler will attempt to compile. +#if defined(JS_CODEGEN_ARM) +// ARM branches can only reach 32MB, and the macroassembler doesn't mitigate +// that limitation. Use a stricter limit on the acceptable script size to +// avoid crashing when branches go out of range. +static constexpr uint32_t BaselineMaxScriptLength = 1000000u; +#else +static constexpr uint32_t BaselineMaxScriptLength = 0x0fffffffu; +#endif + +// Limit the locals on a given script so that stack check on baseline frames +// doesn't overflow a uint32_t value. +// (BaselineMaxScriptSlots * sizeof(Value)) must fit within a uint32_t. +// +// This also applies to the Baseline Interpreter: it ensures we don't run out +// of stack space (and throw over-recursion exceptions) for scripts with a huge +// number of locals. The C++ interpreter avoids this by having heap-allocated +// stack frames. +static constexpr uint32_t BaselineMaxScriptSlots = 0xffffu; + +// An entry in the BaselineScript return address table. These entries are used +// to determine the bytecode pc for a return address into Baseline code. +// +// There must be an entry for each location where we can end up calling into +// C++ (directly or via script/trampolines) and C++ can request the current +// bytecode pc (this includes anything that may throw an exception, GC, or walk +// the stack). We currently add entries for each: +// +// * callVM +// * IC +// * DebugTrap (trampoline call) +// * JSOp::Resume (because this is like a scripted call) +// +// Note: see also BaselineFrame::HAS_OVERRIDE_PC. +class RetAddrEntry { + // Offset from the start of the JIT code where call instruction is. + uint32_t returnOffset_; + + // The offset of this bytecode op within the JSScript. + uint32_t pcOffset_ : 28; + + public: + enum class Kind : uint32_t { + // An IC for a JOF_IC op. + IC, + + // A callVM for an op. + CallVM, + + // A callVM not for an op (e.g., in the prologue) that can't + // trigger debug mode. + NonOpCallVM, + + // A callVM for the over-recursion check on function entry. + StackCheck, + + // A callVM for an interrupt check. + InterruptCheck, + + // DebugTrapHandler (for debugger breakpoints/stepping). + DebugTrap, + + // A callVM for Debug{Prologue,AfterYield,Epilogue}. + DebugPrologue, + DebugAfterYield, + DebugEpilogue, + + Invalid + }; + + private: + // What this entry is for. + uint32_t kind_ : 4; + + public: + RetAddrEntry(uint32_t pcOffset, Kind kind, CodeOffset retOffset) + : returnOffset_(uint32_t(retOffset.offset())), + pcOffset_(pcOffset), + kind_(uint32_t(kind)) { + MOZ_ASSERT(returnOffset_ == retOffset.offset(), + "retOffset must fit in returnOffset_"); + + // The pc offset must fit in at least 28 bits, since we shave off 4 for + // the Kind enum. + MOZ_ASSERT(pcOffset_ == pcOffset); + static_assert(BaselineMaxScriptLength <= (1u << 28) - 1); + MOZ_ASSERT(pcOffset <= BaselineMaxScriptLength); + + MOZ_ASSERT(kind < Kind::Invalid); + MOZ_ASSERT(this->kind() == kind, "kind must fit in kind_ bit field"); + } + + CodeOffset returnOffset() const { return CodeOffset(returnOffset_); } + + uint32_t pcOffset() const { return pcOffset_; } + + jsbytecode* pc(JSScript* script) const { + return script->offsetToPC(pcOffset_); + } + + Kind kind() const { + MOZ_ASSERT(kind_ < uint32_t(Kind::Invalid)); + return Kind(kind_); + } +}; + +// [SMDOC] BaselineScript +// +// This holds the metadata generated by the BaselineCompiler. The machine code +// associated with this is owned by a JitCode instance. This class instance is +// followed by several arrays: +// +// <BaselineScript itself> +// -- +// uint8_t*[] resumeEntryList() +// RetAddrEntry[] retAddrEntries() +// OSREntry[] osrEntries() +// DebugTrapEntry[] debugTrapEntries() +// +// Note: The arrays are arranged in order of descending alignment requires so +// that padding is not required. +class alignas(uintptr_t) BaselineScript final : public TrailingArray { + private: + // Code pointer containing the actual method. + HeapPtr<JitCode*> method_ = nullptr; + + // An ion compilation that is ready, but isn't linked yet. + MainThreadData<IonCompileTask*> pendingIonCompileTask_{nullptr}; + + // Baseline Interpreter can enter Baseline Compiler code at this address. This + // is right after the warm-up counter check in the prologue. + uint32_t warmUpCheckPrologueOffset_ = 0; + + // The offsets for the toggledJump instructions for profiler instrumentation. + uint32_t profilerEnterToggleOffset_ = 0; + uint32_t profilerExitToggleOffset_ = 0; + + private: + // Offset (in bytes) from `this` to the start of each trailing array. Each + // array ends where following one begins. There is no implicit padding (except + // possible at very end). + Offset resumeEntriesOffset_ = 0; + Offset retAddrEntriesOffset_ = 0; + Offset osrEntriesOffset_ = 0; + Offset debugTrapEntriesOffset_ = 0; + Offset allocBytes_ = 0; + + // See `Flag` type below. + uint8_t flags_ = 0; + + // End of fields. + + public: + enum Flag { + // Flag set when compiled for use with Debugger. Handles various + // Debugger hooks and compiles toggled calls for traps. + HAS_DEBUG_INSTRUMENTATION = 1 << 0, + + // Flag is set if this script has profiling instrumentation turned on. + PROFILER_INSTRUMENTATION_ON = 1 << 1, + }; + + // Native code offset for OSR from Baseline Interpreter into Baseline JIT at + // JSOp::LoopHead ops. + class OSREntry : public BasePCToNativeEntry { + public: + using BasePCToNativeEntry::BasePCToNativeEntry; + }; + + // Native code offset for a debug trap when the script is compiled with debug + // instrumentation. + class DebugTrapEntry : public BasePCToNativeEntry { + public: + using BasePCToNativeEntry::BasePCToNativeEntry; + }; + + private: + // Layout helpers + Offset resumeEntriesOffset() const { return resumeEntriesOffset_; } + Offset retAddrEntriesOffset() const { return retAddrEntriesOffset_; } + Offset osrEntriesOffset() const { return osrEntriesOffset_; } + Offset debugTrapEntriesOffset() const { return debugTrapEntriesOffset_; } + Offset endOffset() const { return allocBytes_; } + + // Use BaselineScript::New to create new instances. It will properly + // allocate trailing objects. + BaselineScript(uint32_t warmUpCheckPrologueOffset, + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset) + : warmUpCheckPrologueOffset_(warmUpCheckPrologueOffset), + profilerEnterToggleOffset_(profilerEnterToggleOffset), + profilerExitToggleOffset_(profilerExitToggleOffset) {} + + template <typename T> + mozilla::Span<T> makeSpan(Offset start, Offset end) { + return mozilla::Span{offsetToPointer<T>(start), numElements<T>(start, end)}; + } + + // We store the native code address corresponding to each bytecode offset in + // the script's resumeOffsets list. + mozilla::Span<uint8_t*> resumeEntryList() { + return makeSpan<uint8_t*>(resumeEntriesOffset(), retAddrEntriesOffset()); + } + + // See each type for documentation of these arrays. + mozilla::Span<RetAddrEntry> retAddrEntries() { + return makeSpan<RetAddrEntry>(retAddrEntriesOffset(), osrEntriesOffset()); + } + mozilla::Span<OSREntry> osrEntries() { + return makeSpan<OSREntry>(osrEntriesOffset(), debugTrapEntriesOffset()); + } + mozilla::Span<DebugTrapEntry> debugTrapEntries() { + return makeSpan<DebugTrapEntry>(debugTrapEntriesOffset(), endOffset()); + } + + public: + static BaselineScript* New(JSContext* cx, uint32_t warmUpCheckPrologueOffset, + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, + size_t retAddrEntries, size_t osrEntries, + size_t debugTrapEntries, size_t resumeEntries); + + static void Destroy(JS::GCContext* gcx, BaselineScript* script); + + void trace(JSTracer* trc); + + static inline size_t offsetOfMethod() { + return offsetof(BaselineScript, method_); + } + + void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, + size_t* data) const { + *data += mallocSizeOf(this); + } + + void setHasDebugInstrumentation() { flags_ |= HAS_DEBUG_INSTRUMENTATION; } + bool hasDebugInstrumentation() const { + return flags_ & HAS_DEBUG_INSTRUMENTATION; + } + + uint8_t* warmUpCheckPrologueAddr() const { + return method_->raw() + warmUpCheckPrologueOffset_; + } + + JitCode* method() const { return method_; } + void setMethod(JitCode* code) { + MOZ_ASSERT(!method_); + method_ = code; + } + + bool containsCodeAddress(uint8_t* addr) const { + return method()->raw() <= addr && + addr <= method()->raw() + method()->instructionsSize(); + } + + uint8_t* returnAddressForEntry(const RetAddrEntry& ent); + + const RetAddrEntry& retAddrEntryFromPCOffset(uint32_t pcOffset, + RetAddrEntry::Kind kind); + const RetAddrEntry& prologueRetAddrEntry(RetAddrEntry::Kind kind); + const RetAddrEntry& retAddrEntryFromReturnOffset(CodeOffset returnOffset); + const RetAddrEntry& retAddrEntryFromReturnAddress(const uint8_t* returnAddr); + + uint8_t* nativeCodeForOSREntry(uint32_t pcOffset); + + void copyRetAddrEntries(const RetAddrEntry* entries); + void copyOSREntries(const OSREntry* entries); + void copyDebugTrapEntries(const DebugTrapEntry* entries); + + // Copy resumeOffsets list from |script| and convert the pcOffsets + // to native addresses in the Baseline code based on |entries|. + void computeResumeNativeOffsets(JSScript* script, + const ResumeOffsetEntryVector& entries); + + // Return the bytecode offset for a given native code address. Be careful + // when using this method: it's an approximation and not guaranteed to be the + // correct pc. + jsbytecode* approximatePcForNativeAddress(JSScript* script, + uint8_t* nativeAddress); + + // Toggle debug traps (used for breakpoints and step mode) in the script. + // If |pc| is nullptr, toggle traps for all ops in the script. Else, only + // toggle traps at |pc|. + void toggleDebugTraps(JSScript* script, jsbytecode* pc); + + void toggleProfilerInstrumentation(bool enable); + bool isProfilerInstrumentationOn() const { + return flags_ & PROFILER_INSTRUMENTATION_ON; + } + + static size_t offsetOfResumeEntriesOffset() { + static_assert(sizeof(Offset) == sizeof(uint32_t), + "JIT expect Offset to be uint32_t"); + return offsetof(BaselineScript, resumeEntriesOffset_); + } + + bool hasPendingIonCompileTask() const { return !!pendingIonCompileTask_; } + + js::jit::IonCompileTask* pendingIonCompileTask() { + MOZ_ASSERT(hasPendingIonCompileTask()); + return pendingIonCompileTask_; + } + void setPendingIonCompileTask(JSRuntime* rt, JSScript* script, + js::jit::IonCompileTask* task); + void removePendingIonCompileTask(JSRuntime* rt, JSScript* script); + + size_t allocBytes() const { return allocBytes_; } +}; +static_assert( + sizeof(BaselineScript) % sizeof(uintptr_t) == 0, + "The data attached to the script must be aligned for fast JIT access."); + +enum class BaselineTier { Interpreter, Compiler }; + +template <BaselineTier Tier> +MethodStatus CanEnterBaselineMethod(JSContext* cx, RunState& state); + +MethodStatus CanEnterBaselineInterpreterAtBranch(JSContext* cx, + InterpreterFrame* fp); + +JitExecStatus EnterBaselineInterpreterAtBranch(JSContext* cx, + InterpreterFrame* fp, + jsbytecode* pc); + +bool CanBaselineInterpretScript(JSScript* script); + +// Called by the Baseline Interpreter to compile a script for the Baseline JIT. +// |res| is set to the native code address in the BaselineScript to jump to, or +// nullptr if we were unable to compile this script. +bool BaselineCompileFromBaselineInterpreter(JSContext* cx, BaselineFrame* frame, + uint8_t** res); + +void FinishDiscardBaselineScript(JS::GCContext* gcx, JSScript* script); + +void AddSizeOfBaselineData(JSScript* script, mozilla::MallocSizeOf mallocSizeOf, + size_t* data); + +void ToggleBaselineProfiling(JSContext* cx, bool enable); + +struct alignas(uintptr_t) BaselineBailoutInfo { + // Pointer into the current C stack, where overwriting will start. + uint8_t* incomingStack = nullptr; + + // The top and bottom heapspace addresses of the reconstructed stack + // which will be copied to the bottom. + uint8_t* copyStackTop = nullptr; + uint8_t* copyStackBottom = nullptr; + + // The value of the frame pointer register on resume. + void* resumeFramePtr = nullptr; + + // The native code address to resume into. + void* resumeAddr = nullptr; + + // The bytecode pc of try block and fault block. + jsbytecode* tryPC = nullptr; + jsbytecode* faultPC = nullptr; + + // Number of baseline frames to push on the stack. + uint32_t numFrames = 0; + + // The bailout kind. + mozilla::Maybe<BailoutKind> bailoutKind = {}; + + BaselineBailoutInfo() = default; + BaselineBailoutInfo(const BaselineBailoutInfo&) = default; + + void operator=(const BaselineBailoutInfo&) = delete; +}; + +enum class BailoutReason { + Normal, + ExceptionHandler, + Invalidate, +}; + +[[nodiscard]] bool BailoutIonToBaseline( + JSContext* cx, JitActivation* activation, const JSJitFrameIter& iter, + BaselineBailoutInfo** bailoutInfo, + const ExceptionBailoutInfo* exceptionInfo, BailoutReason reason); + +MethodStatus BaselineCompile(JSContext* cx, JSScript* script, + bool forceDebugInstrumentation = false); + +// Class storing the generated Baseline Interpreter code for the runtime. +class BaselineInterpreter { + public: + struct CallVMOffsets { + uint32_t debugPrologueOffset = 0; + uint32_t debugEpilogueOffset = 0; + uint32_t debugAfterYieldOffset = 0; + }; + struct ICReturnOffset { + uint32_t offset; + JSOp op; + ICReturnOffset(uint32_t offset, JSOp op) : offset(offset), op(op) {} + }; + using ICReturnOffsetVector = Vector<ICReturnOffset, 0, SystemAllocPolicy>; + + private: + // The interpreter code. + JitCode* code_ = nullptr; + + // Offset of the code to start interpreting a bytecode op. + uint32_t interpretOpOffset_ = 0; + + // Like interpretOpOffset_ but skips the debug trap for the current op. + uint32_t interpretOpNoDebugTrapOffset_ = 0; + + // Early Ion bailouts will enter at this address. This is after frame + // construction and environment initialization. + uint32_t bailoutPrologueOffset_ = 0; + + // The offsets for the toggledJump instructions for profiler instrumentation. + uint32_t profilerEnterToggleOffset_ = 0; + uint32_t profilerExitToggleOffset_ = 0; + + // Offset of the jump (tail call) to the debug trap handler trampoline code. + // When the debugger is enabled, NOPs are patched to calls to this location. + uint32_t debugTrapHandlerOffset_ = 0; + + // The offsets of toggled jumps for debugger instrumentation. + using CodeOffsetVector = Vector<uint32_t, 0, SystemAllocPolicy>; + CodeOffsetVector debugInstrumentationOffsets_; + + // Offsets of toggled calls to the DebugTrapHandler trampoline (for + // breakpoints and stepping). + CodeOffsetVector debugTrapOffsets_; + + // Offsets of toggled jumps for code coverage. + CodeOffsetVector codeCoverageOffsets_; + + // Offsets of IC calls for IsIonInlinableOp ops, for Ion bailouts. + ICReturnOffsetVector icReturnOffsets_; + + // Offsets of some callVMs for BaselineDebugModeOSR. + CallVMOffsets callVMOffsets_; + + uint8_t* codeAtOffset(uint32_t offset) const { + MOZ_ASSERT(offset > 0); + MOZ_ASSERT(offset < code_->instructionsSize()); + return codeRaw() + offset; + } + + public: + BaselineInterpreter() = default; + + BaselineInterpreter(const BaselineInterpreter&) = delete; + void operator=(const BaselineInterpreter&) = delete; + + void init(JitCode* code, uint32_t interpretOpOffset, + uint32_t interpretOpNoDebugTrapOffset, + uint32_t bailoutPrologueOffset, uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, uint32_t debugTrapHandlerOffset, + CodeOffsetVector&& debugInstrumentationOffsets, + CodeOffsetVector&& debugTrapOffsets, + CodeOffsetVector&& codeCoverageOffsets, + ICReturnOffsetVector&& icReturnOffsets, + const CallVMOffsets& callVMOffsets); + + uint8_t* codeRaw() const { return code_->raw(); } + + uint8_t* retAddrForDebugPrologueCallVM() const { + return codeAtOffset(callVMOffsets_.debugPrologueOffset); + } + uint8_t* retAddrForDebugEpilogueCallVM() const { + return codeAtOffset(callVMOffsets_.debugEpilogueOffset); + } + uint8_t* retAddrForDebugAfterYieldCallVM() const { + return codeAtOffset(callVMOffsets_.debugAfterYieldOffset); + } + uint8_t* bailoutPrologueEntryAddr() const { + return codeAtOffset(bailoutPrologueOffset_); + } + + uint8_t* retAddrForIC(JSOp op) const; + + TrampolinePtr interpretOpAddr() const { + return TrampolinePtr(codeAtOffset(interpretOpOffset_)); + } + TrampolinePtr interpretOpNoDebugTrapAddr() const { + return TrampolinePtr(codeAtOffset(interpretOpNoDebugTrapOffset_)); + } + + void toggleProfilerInstrumentation(bool enable); + void toggleDebuggerInstrumentation(bool enable); + + void toggleCodeCoverageInstrumentationUnchecked(bool enable); + void toggleCodeCoverageInstrumentation(bool enable); +}; + +[[nodiscard]] bool GenerateBaselineInterpreter( + JSContext* cx, BaselineInterpreter& interpreter); + +inline bool IsBaselineJitEnabled(JSContext* cx) { + if (MOZ_UNLIKELY(!IsBaselineInterpreterEnabled())) { + return false; + } + if (MOZ_LIKELY(JitOptions.baselineJit)) { + return true; + } + if (JitOptions.jitForTrustedPrincipals) { + JS::Realm* realm = js::GetContextRealm(cx); + return realm && JS::GetRealmPrincipals(realm) && + JS::GetRealmPrincipals(realm)->isSystemOrAddonPrincipal(); + } + return false; +} + +} // namespace jit +} // namespace js + +namespace JS { + +template <> +struct DeletePolicy<js::jit::BaselineScript> { + explicit DeletePolicy(JSRuntime* rt) : rt_(rt) {} + void operator()(const js::jit::BaselineScript* script); + + private: + JSRuntime* rt_; +}; + +} // namespace JS + +#endif /* jit_BaselineJIT_h */ |