diff options
Diffstat (limited to 'js/src/jit/BaselineJIT.h')
-rw-r--r-- | js/src/jit/BaselineJIT.h | 635 |
1 files changed, 635 insertions, 0 deletions
diff --git a/js/src/jit/BaselineJIT.h b/js/src/jit/BaselineJIT.h new file mode 100644 index 000000000..5e7775a61 --- /dev/null +++ b/js/src/jit/BaselineJIT.h @@ -0,0 +1,635 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/MemoryReporting.h" + +#include "jscntxt.h" +#include "jscompartment.h" + +#include "ds/LifoAlloc.h" +#include "jit/Bailouts.h" +#include "jit/IonCode.h" +#include "jit/MacroAssembler.h" +#include "vm/TraceLogging.h" + +namespace js { +namespace jit { + +class StackValue; +class BaselineICEntry; +class ICStub; + +class PCMappingSlotInfo +{ + uint8_t slotInfo_; + + public: + // SlotInfo encoding: + // Bits 0 & 1: number of slots at top of stack which are unsynced. + // Bits 2 & 3: SlotLocation of top slot value (only relevant if numUnsynced > 0). + // Bits 3 & 4: SlotLocation of next slot value (only relevant if numUnsynced > 1). + enum SlotLocation { SlotInR0 = 0, SlotInR1 = 1, SlotIgnore = 3 }; + + PCMappingSlotInfo() + : slotInfo_(0) + { } + + explicit PCMappingSlotInfo(uint8_t slotInfo) + : slotInfo_(slotInfo) + { } + + static inline bool ValidSlotLocation(SlotLocation loc) { + return (loc == SlotInR0) || (loc == SlotInR1) || (loc == SlotIgnore); + } + + static SlotLocation ToSlotLocation(const StackValue* stackVal); + + inline static PCMappingSlotInfo MakeSlotInfo() { return PCMappingSlotInfo(0); } + + inline static PCMappingSlotInfo MakeSlotInfo(SlotLocation topSlotLoc) { + MOZ_ASSERT(ValidSlotLocation(topSlotLoc)); + return PCMappingSlotInfo(1 | (topSlotLoc << 2)); + } + + inline static PCMappingSlotInfo MakeSlotInfo(SlotLocation topSlotLoc, SlotLocation nextSlotLoc) { + MOZ_ASSERT(ValidSlotLocation(topSlotLoc)); + MOZ_ASSERT(ValidSlotLocation(nextSlotLoc)); + return PCMappingSlotInfo(2 | (topSlotLoc << 2) | (nextSlotLoc) << 4); + } + + inline unsigned numUnsynced() const { + return slotInfo_ & 0x3; + } + inline SlotLocation topSlotLocation() const { + return static_cast<SlotLocation>((slotInfo_ >> 2) & 0x3); + } + inline SlotLocation nextSlotLocation() const { + return static_cast<SlotLocation>((slotInfo_ >> 4) & 0x3); + } + inline uint8_t toByte() const { + return slotInfo_; + } +}; + +// A CompactBuffer is used to store native code offsets (relative to the +// previous pc) and PCMappingSlotInfo bytes. To allow binary search into this +// table, we maintain a second table of "index" entries. Every X ops, the +// compiler will add an index entry, so that from the index entry to the +// actual native code offset, we have to iterate at most X times. +struct PCMappingIndexEntry +{ + // jsbytecode offset. + uint32_t pcOffset; + + // Native code offset. + uint32_t nativeOffset; + + // Offset in the CompactBuffer where data for pcOffset starts. + uint32_t bufferOffset; +}; + +// Describes a single wasm::ImportExit which jumps (via an import with +// the given index) directly to a BaselineScript or IonScript. +struct DependentWasmImport +{ + wasm::Instance* instance; + size_t importIndex; + + DependentWasmImport(wasm::Instance& instance, size_t importIndex) + : instance(&instance), + importIndex(importIndex) + { } +}; + +struct BaselineScript +{ + public: + // 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 const uint32_t MAX_JSSCRIPT_LENGTH = 1000000u; +#else + static const uint32_t MAX_JSSCRIPT_LENGTH = 0x0fffffffu; +#endif + + // Limit the locals on a given script so that stack check on baseline frames + // doesn't overflow a uint32_t value. + // (MAX_JSSCRIPT_SLOTS * sizeof(Value)) must fit within a uint32_t. + static const uint32_t MAX_JSSCRIPT_SLOTS = 0xffffu; + + private: + // Code pointer containing the actual method. + HeapPtr<JitCode*> method_; + + // 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). + HeapPtr<EnvironmentObject*> templateEnv_; + + // Allocated space for fallback stubs. + FallbackICStubSpace fallbackStubSpace_; + + // If non-null, the list of wasm::Modules that contain an optimized call + // directly to this script. + Vector<DependentWasmImport>* dependentWasmImports_; + + // Native code offset right before the scope chain is initialized. + uint32_t prologueOffset_; + + // Native code offset right before the frame is popped and the method + // returned from. + uint32_t epilogueOffset_; + + // The offsets for the toggledJump instructions for profiler instrumentation. + uint32_t profilerEnterToggleOffset_; + uint32_t profilerExitToggleOffset_; + + // The offsets and event used for Tracelogger toggling. +#ifdef JS_TRACE_LOGGING +# ifdef DEBUG + bool traceLoggerScriptsEnabled_; + bool traceLoggerEngineEnabled_; +# endif + TraceLoggerEvent traceLoggerScriptEvent_; +#endif + + // Native code offsets right after the debug prologue VM call returns, or + // would have returned. This offset is recorded even when debug mode is + // off to aid on-stack debug mode recompilation. + // + // We don't need one for the debug epilogue because that always happens + // right before the epilogue, so we just use the epilogue offset. + uint32_t postDebugPrologueOffset_; + + public: + enum Flag { + // Flag set by JSScript::argumentsOptimizationFailed. Similar to + // JSScript::needsArgsObj_, but can be read from JIT code. + NEEDS_ARGS_OBJ = 1 << 0, + + // Flag set when discarding JIT code, to indicate this script is + // on the stack and should not be discarded. + ACTIVE = 1 << 1, + + // Flag set when the script contains any writes to its on-stack + // (rather than call object stored) arguments. + MODIFIES_ARGUMENTS = 1 << 2, + + // Flag set when compiled for use with Debugger. Handles various + // Debugger hooks and compiles toggled calls for traps. + HAS_DEBUG_INSTRUMENTATION = 1 << 3, + + // Flag set if this script has ever been Ion compiled, either directly + // or inlined into another script. This is cleared when the script's + // type information or caches are cleared. + ION_COMPILED_OR_INLINED = 1 << 4, + + // Flag is set if this script has profiling instrumentation turned on. + PROFILER_INSTRUMENTATION_ON = 1 << 5 + }; + + private: + uint32_t flags_; + + private: + void trace(JSTracer* trc); + + uint32_t icEntriesOffset_; + uint32_t icEntries_; + + uint32_t pcMappingIndexOffset_; + uint32_t pcMappingIndexEntries_; + + uint32_t pcMappingOffset_; + uint32_t pcMappingSize_; + + // List mapping indexes of bytecode type sets to the offset of the opcode + // they correspond to, for use by TypeScript::BytecodeTypes. + uint32_t bytecodeTypeMapOffset_; + + // For generator scripts, we store the native code address for each yield + // instruction. + uint32_t yieldEntriesOffset_; + + // By default tracelogger is disabled. Therefore we disable the logging code + // by default. We store the offsets we must patch to enable the logging. + uint32_t traceLoggerToggleOffsetsOffset_; + uint32_t numTraceLoggerToggleOffsets_; + + // The total bytecode length of all scripts we inlined when we Ion-compiled + // this script. 0 if Ion did not compile this script or if we didn't inline + // anything. + uint16_t inlinedBytecodeLength_; + + // The max inlining depth where we can still inline all functions we inlined + // when we Ion-compiled this script. This starts as UINT8_MAX, since we have + // no data yet, and won't affect inlining heuristics in that case. The value + // is updated when we Ion-compile this script. See makeInliningDecision for + // more info. + uint8_t maxInliningDepth_; + + // An ion compilation that is ready, but isn't linked yet. + IonBuilder *pendingBuilder_; + + public: + // Do not call directly, use BaselineScript::New. This is public for cx->new_. + BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset, + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, + uint32_t postDebugPrologueOffset); + + ~BaselineScript() { + // The contents of the fallback stub space are removed and freed + // separately after the next minor GC. See BaselineScript::Destroy. + MOZ_ASSERT(fallbackStubSpace_.isEmpty()); + } + + static BaselineScript* New(JSScript* jsscript, + uint32_t prologueOffset, uint32_t epilogueOffset, + uint32_t profilerEnterToggleOffset, + uint32_t profilerExitToggleOffset, + uint32_t postDebugPrologueOffset, + size_t icEntries, + size_t pcMappingIndexEntries, size_t pcMappingSize, + size_t bytecodeTypeMapEntries, + size_t yieldEntries, + size_t traceLoggerToggleOffsetEntries); + + static void Trace(JSTracer* trc, BaselineScript* script); + static void Destroy(FreeOp* fop, BaselineScript* script); + + void purgeOptimizedStubs(Zone* zone); + + static inline size_t offsetOfMethod() { + return offsetof(BaselineScript, method_); + } + + 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); + } + + bool active() const { + return flags_ & ACTIVE; + } + void setActive() { + flags_ |= ACTIVE; + } + void resetActive() { + flags_ &= ~ACTIVE; + } + + void setNeedsArgsObj() { + flags_ |= NEEDS_ARGS_OBJ; + } + + void setModifiesArguments() { + flags_ |= MODIFIES_ARGUMENTS; + } + bool modifiesArguments() { + return flags_ & MODIFIES_ARGUMENTS; + } + + void setHasDebugInstrumentation() { + flags_ |= HAS_DEBUG_INSTRUMENTATION; + } + bool hasDebugInstrumentation() const { + return flags_ & HAS_DEBUG_INSTRUMENTATION; + } + + void setIonCompiledOrInlined() { + flags_ |= ION_COMPILED_OR_INLINED; + } + void clearIonCompiledOrInlined() { + flags_ &= ~ION_COMPILED_OR_INLINED; + } + bool ionCompiledOrInlined() const { + return flags_ & ION_COMPILED_OR_INLINED; + } + + uint32_t prologueOffset() const { + return prologueOffset_; + } + uint8_t* prologueEntryAddr() const { + return method_->raw() + prologueOffset_; + } + + uint32_t epilogueOffset() const { + return epilogueOffset_; + } + uint8_t* epilogueEntryAddr() const { + return method_->raw() + epilogueOffset_; + } + + uint32_t postDebugPrologueOffset() const { + return postDebugPrologueOffset_; + } + uint8_t* postDebugPrologueAddr() const { + return method_->raw() + postDebugPrologueOffset_; + } + + BaselineICEntry* icEntryList() { + return (BaselineICEntry*)(reinterpret_cast<uint8_t*>(this) + icEntriesOffset_); + } + uint8_t** yieldEntryList() { + return (uint8_t**)(reinterpret_cast<uint8_t*>(this) + yieldEntriesOffset_); + } + PCMappingIndexEntry* pcMappingIndexEntryList() { + return (PCMappingIndexEntry*)(reinterpret_cast<uint8_t*>(this) + pcMappingIndexOffset_); + } + uint8_t* pcMappingData() { + return reinterpret_cast<uint8_t*>(this) + pcMappingOffset_; + } + FallbackICStubSpace* fallbackStubSpace() { + return &fallbackStubSpace_; + } + + JitCode* method() const { + return method_; + } + void setMethod(JitCode* code) { + MOZ_ASSERT(!method_); + method_ = code; + } + + EnvironmentObject* templateEnvironment() const { + return templateEnv_; + } + void setTemplateEnvironment(EnvironmentObject* templateEnv) { + MOZ_ASSERT(!templateEnv_); + templateEnv_ = templateEnv; + } + + void toggleBarriers(bool enabled, ReprotectCode reprotect = Reprotect) { + method()->togglePreBarriers(enabled, reprotect); + } + + bool containsCodeAddress(uint8_t* addr) const { + return method()->raw() <= addr && addr <= method()->raw() + method()->instructionsSize(); + } + + BaselineICEntry& icEntry(size_t index); + BaselineICEntry& icEntryFromReturnOffset(CodeOffset returnOffset); + BaselineICEntry& icEntryFromPCOffset(uint32_t pcOffset); + BaselineICEntry& icEntryFromPCOffset(uint32_t pcOffset, BaselineICEntry* prevLookedUpEntry); + BaselineICEntry& callVMEntryFromPCOffset(uint32_t pcOffset); + BaselineICEntry& stackCheckICEntry(bool earlyCheck); + BaselineICEntry& warmupCountICEntry(); + BaselineICEntry& icEntryFromReturnAddress(uint8_t* returnAddr); + uint8_t* returnAddressForIC(const BaselineICEntry& ent); + + size_t numICEntries() const { + return icEntries_; + } + + void copyICEntries(JSScript* script, const BaselineICEntry* entries, MacroAssembler& masm); + void adoptFallbackStubs(FallbackICStubSpace* stubSpace); + + void copyYieldEntries(JSScript* script, Vector<uint32_t>& yieldOffsets); + + PCMappingIndexEntry& pcMappingIndexEntry(size_t index); + CompactBufferReader pcMappingReader(size_t indexEntry); + + size_t numPCMappingIndexEntries() const { + return pcMappingIndexEntries_; + } + + void copyPCMappingIndexEntries(const PCMappingIndexEntry* entries); + void copyPCMappingEntries(const CompactBufferWriter& entries); + + uint8_t* nativeCodeForPC(JSScript* script, jsbytecode* pc, + PCMappingSlotInfo* slotInfo = nullptr); + + // Return the bytecode offset for a given native code address. Be careful + // when using this method: we don't emit code for some bytecode ops, so + // the result may not be accurate. + jsbytecode* approximatePcForNativeAddress(JSScript* script, uint8_t* nativeAddress); + + MOZ_MUST_USE bool addDependentWasmImport(JSContext* cx, wasm::Instance& instance, uint32_t idx); + void removeDependentWasmImport(wasm::Instance& instance, uint32_t idx); + void unlinkDependentWasmImports(FreeOp* fop); + void clearDependentWasmImports(); + + // 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; + } + +#ifdef JS_TRACE_LOGGING + void initTraceLogger(JSRuntime* runtime, JSScript* script, const Vector<CodeOffset>& offsets); + void toggleTraceLoggerScripts(JSRuntime* runtime, JSScript* script, bool enable); + void toggleTraceLoggerEngine(bool enable); + + static size_t offsetOfTraceLoggerScriptEvent() { + return offsetof(BaselineScript, traceLoggerScriptEvent_); + } + + uint32_t* traceLoggerToggleOffsets() { + MOZ_ASSERT(traceLoggerToggleOffsetsOffset_); + return reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(this) + + traceLoggerToggleOffsetsOffset_); + } +#endif + + void noteAccessedGetter(uint32_t pcOffset); + void noteArrayWriteHole(uint32_t pcOffset); + + static size_t offsetOfFlags() { + return offsetof(BaselineScript, flags_); + } + static size_t offsetOfYieldEntriesOffset() { + return offsetof(BaselineScript, yieldEntriesOffset_); + } + + static void writeBarrierPre(Zone* zone, BaselineScript* script); + + uint32_t* bytecodeTypeMap() { + MOZ_ASSERT(bytecodeTypeMapOffset_); + return reinterpret_cast<uint32_t*>(reinterpret_cast<uint8_t*>(this) + bytecodeTypeMapOffset_); + } + + uint8_t maxInliningDepth() const { + return maxInliningDepth_; + } + void setMaxInliningDepth(uint32_t depth) { + MOZ_ASSERT(depth <= UINT8_MAX); + maxInliningDepth_ = depth; + } + void resetMaxInliningDepth() { + maxInliningDepth_ = UINT8_MAX; + } + + uint16_t inlinedBytecodeLength() const { + return inlinedBytecodeLength_; + } + void setInlinedBytecodeLength(uint32_t len) { + if (len > UINT16_MAX) + len = UINT16_MAX; + inlinedBytecodeLength_ = len; + } + + bool hasPendingIonBuilder() const { + return !!pendingBuilder_; + } + + js::jit::IonBuilder* pendingIonBuilder() { + MOZ_ASSERT(hasPendingIonBuilder()); + return pendingBuilder_; + } + void setPendingIonBuilder(JSRuntime* maybeRuntime, JSScript* script, js::jit::IonBuilder* builder) { + MOZ_ASSERT(script->baselineScript() == this); + MOZ_ASSERT(!builder || !hasPendingIonBuilder()); + + if (script->isIonCompilingOffThread()) + script->setIonScript(maybeRuntime, ION_PENDING_SCRIPT); + + pendingBuilder_ = builder; + + // lazy linking cannot happen during asmjs to ion. + clearDependentWasmImports(); + + script->updateBaselineOrIonRaw(maybeRuntime); + } + void removePendingIonBuilder(JSScript* script) { + setPendingIonBuilder(nullptr, script, nullptr); + if (script->maybeIonScript() == ION_PENDING_SCRIPT) + script->setIonScript(nullptr, nullptr); + } + +}; +static_assert(sizeof(BaselineScript) % sizeof(uintptr_t) == 0, + "The data attached to the script must be aligned for fast JIT access."); + +inline bool +IsBaselineEnabled(JSContext* cx) +{ +#ifdef JS_CODEGEN_NONE + return false; +#else + return cx->options().baseline(); +#endif +} + +MethodStatus +CanEnterBaselineMethod(JSContext* cx, RunState& state); + +MethodStatus +CanEnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp, bool newType); + +JitExecStatus +EnterBaselineMethod(JSContext* cx, RunState& state); + +JitExecStatus +EnterBaselineAtBranch(JSContext* cx, InterpreterFrame* fp, jsbytecode* pc); + +void +FinishDiscardBaselineScript(FreeOp* fop, JSScript* script); + +void +AddSizeOfBaselineData(JSScript* script, mozilla::MallocSizeOf mallocSizeOf, size_t* data, + size_t* fallbackStubs); + +void +ToggleBaselineProfiling(JSRuntime* runtime, bool enable); + +void +ToggleBaselineTraceLoggerScripts(JSRuntime* runtime, bool enable); +void +ToggleBaselineTraceLoggerEngine(JSRuntime* runtime, bool enable); + +struct BaselineBailoutInfo +{ + // Pointer into the current C stack, where overwriting will start. + uint8_t* incomingStack; + + // The top and bottom heapspace addresses of the reconstructed stack + // which will be copied to the bottom. + uint8_t* copyStackTop; + uint8_t* copyStackBottom; + + // Fields to store the top-of-stack baseline values that are held + // in registers. The setR0 and setR1 fields are flags indicating + // whether each one is initialized. + uint32_t setR0; + Value valueR0; + uint32_t setR1; + Value valueR1; + + // The value of the frame pointer register on resume. + void* resumeFramePtr; + + // The native code address to resume into. + void* resumeAddr; + + // The bytecode pc where we will resume. + jsbytecode* resumePC; + + // If resuming into a TypeMonitor IC chain, this field holds the + // address of the first stub in that chain. If this field is + // set, then the actual jitcode resumed into is the jitcode for + // the first stub, not the resumeAddr above. The resumeAddr + // above, in this case, is pushed onto the stack so that the + // TypeMonitor chain can tail-return into the main jitcode when done. + ICStub* monitorStub; + + // Number of baseline frames to push on the stack. + uint32_t numFrames; + + // If Ion bailed out on a global script before it could perform the global + // declaration conflicts check. In such cases the baseline script is + // resumed at the first pc instead of the prologue, so an extra flag is + // needed to perform the check. + bool checkGlobalDeclarationConflicts; + + // The bailout kind. + BailoutKind bailoutKind; +}; + +uint32_t +BailoutIonToBaseline(JSContext* cx, JitActivation* activation, JitFrameIterator& iter, + bool invalidate, BaselineBailoutInfo** bailoutInfo, + const ExceptionBailoutInfo* exceptionInfo); + +// Mark baseline scripts on the stack as active, so that they are not discarded +// during GC. +void +MarkActiveBaselineScripts(Zone* zone); + +MethodStatus +BaselineCompile(JSContext* cx, JSScript* script, bool forceDebugInstrumentation = 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 */ |