/* -*- 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 #include #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; // 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: // // // -- // 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 method_ = nullptr; // An ion compilation that is ready, but isn't linked yet. MainThreadData 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 mozilla::Span makeSpan(Offset start, Offset end) { return mozilla::Span{offsetToPointer(start), numElements(start, end)}; } // We store the native code address corresponding to each bytecode offset in // the script's resumeOffsets list. mozilla::Span resumeEntryList() { return makeSpan(resumeEntriesOffset(), retAddrEntriesOffset()); } // See each type for documentation of these arrays. mozilla::Span retAddrEntries() { return makeSpan(retAddrEntriesOffset(), osrEntriesOffset()); } mozilla::Span osrEntries() { return makeSpan(osrEntriesOffset(), debugTrapEntriesOffset()); } mozilla::Span debugTrapEntries() { return makeSpan(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 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 = {}; 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); static const unsigned BASELINE_MAX_ARGS_LENGTH = 20000; // 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; 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; 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 { explicit DeletePolicy(JSRuntime* rt) : rt_(rt) {} void operator()(const js::jit::BaselineScript* script); private: JSRuntime* rt_; }; } // namespace JS #endif /* jit_BaselineJIT_h */