summaryrefslogtreecommitdiffstats
path: root/js/src/jit/BaselineJIT.h
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit/BaselineJIT.h')
-rw-r--r--js/src/jit/BaselineJIT.h593
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 */