/* -*- 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((slotInfo_ >> 2) & 0x3); } inline SlotLocation nextSlotLocation() const { return static_cast((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 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 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* 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(this) + icEntriesOffset_); } uint8_t** yieldEntryList() { return (uint8_t**)(reinterpret_cast(this) + yieldEntriesOffset_); } PCMappingIndexEntry* pcMappingIndexEntryList() { return (PCMappingIndexEntry*)(reinterpret_cast(this) + pcMappingIndexOffset_); } uint8_t* pcMappingData() { return reinterpret_cast(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& 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& 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(reinterpret_cast(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(reinterpret_cast(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 { explicit DeletePolicy(JSRuntime* rt) : rt_(rt) {} void operator()(const js::jit::BaselineScript* script); private: JSRuntime* rt_; }; } // namespace JS #endif /* jit_BaselineJIT_h */