/* -*- 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_JitcodeMap_h #define jit_JitcodeMap_h #include "jit/CompactBuffer.h" #include "jit/CompileInfo.h" #include "jit/ExecutableAllocator.h" #include "jit/OptimizationTracking.h" #include "jit/shared/CodeGenerator-shared.h" namespace js { namespace jit { /* * The Ion jitcode map implements tables to allow mapping from addresses in ion jitcode * to the list of (JSScript*, jsbytecode*) pairs that are implicitly active in the frame at * that point in the native code. * * To represent this information efficiently, a multi-level table is used. * * At the top level, a global splay-tree of JitcodeGlobalEntry describings the mapping for * each individual IonCode script generated by compiles. The entries are ordered by their * nativeStartAddr. * * Every entry in the table is of fixed size, but there are different entry types, * distinguished by the kind field. */ class JitcodeGlobalTable; class JitcodeIonTable; class JitcodeRegionEntry; class JitcodeGlobalEntry; class JitcodeSkiplistTower { public: static const unsigned MAX_HEIGHT = 32; private: uint8_t height_; bool isFree_; JitcodeGlobalEntry* ptrs_[1]; public: explicit JitcodeSkiplistTower(unsigned height) : height_(height), isFree_(false) { MOZ_ASSERT(height >= 1 && height <= MAX_HEIGHT); clearPtrs(); } unsigned height() const { return height_; } JitcodeGlobalEntry** ptrs(unsigned level) { return ptrs_; } JitcodeGlobalEntry* next(unsigned level) const { MOZ_ASSERT(!isFree_); MOZ_ASSERT(level < height()); return ptrs_[level]; } void setNext(unsigned level, JitcodeGlobalEntry* entry) { MOZ_ASSERT(!isFree_); MOZ_ASSERT(level < height()); ptrs_[level] = entry; } // // When stored in a free-list, towers use 'ptrs_[0]' to store a // pointer to the next tower. In this context only, 'ptrs_[0]' // may refer to a |JitcodeSkiplistTower*| instead of a // |JitcodeGlobalEntry*|. // void addToFreeList(JitcodeSkiplistTower** freeList) { JitcodeSkiplistTower* nextFreeTower = *freeList; MOZ_ASSERT_IF(nextFreeTower, nextFreeTower->isFree_ && nextFreeTower->height() == height_); ptrs_[0] = (JitcodeGlobalEntry*) nextFreeTower; isFree_ = true; *freeList = this; } static JitcodeSkiplistTower* PopFromFreeList(JitcodeSkiplistTower** freeList) { if (!*freeList) return nullptr; JitcodeSkiplistTower* tower = *freeList; MOZ_ASSERT(tower->isFree_); JitcodeSkiplistTower* nextFreeTower = (JitcodeSkiplistTower*) tower->ptrs_[0]; tower->clearPtrs(); tower->isFree_ = false; *freeList = nextFreeTower; return tower; } static size_t CalculateSize(unsigned height) { MOZ_ASSERT(height >= 1); return sizeof(JitcodeSkiplistTower) + (sizeof(JitcodeGlobalEntry*) * (height - 1)); } private: void clearPtrs() { for (unsigned i = 0; i < height_; i++) ptrs_[0] = nullptr; } }; class JitcodeGlobalEntry { friend class JitcodeGlobalTable; public: enum Kind { INVALID = 0, Ion, Baseline, IonCache, Dummy, Query, LIMIT }; JS_STATIC_ASSERT(LIMIT <= 8); struct BytecodeLocation { JSScript* script; jsbytecode* pc; BytecodeLocation(JSScript* script, jsbytecode* pc) : script(script), pc(pc) {} }; typedef Vector BytecodeLocationVector; typedef Vector ProfileStringVector; struct BaseEntry { JitCode* jitcode_; void* nativeStartAddr_; void* nativeEndAddr_; uint32_t gen_; Kind kind_ : 7; void init() { jitcode_ = nullptr; nativeStartAddr_ = nullptr; nativeEndAddr_ = nullptr; gen_ = UINT32_MAX; kind_ = INVALID; } void init(Kind kind, JitCode* code, void* nativeStartAddr, void* nativeEndAddr) { MOZ_ASSERT_IF(kind != Query, code); MOZ_ASSERT(nativeStartAddr); MOZ_ASSERT(nativeEndAddr); MOZ_ASSERT(kind > INVALID && kind < LIMIT); jitcode_ = code; nativeStartAddr_ = nativeStartAddr; nativeEndAddr_ = nativeEndAddr; gen_ = UINT32_MAX; kind_ = kind; } uint32_t generation() const { return gen_; } void setGeneration(uint32_t gen) { gen_ = gen; } bool isSampled(uint32_t currentGen, uint32_t lapCount) { if (gen_ == UINT32_MAX || currentGen == UINT32_MAX) return false; MOZ_ASSERT(currentGen >= gen_); return (currentGen - gen_) <= lapCount; } Kind kind() const { return kind_; } JitCode* jitcode() const { return jitcode_; } void* nativeStartAddr() const { return nativeStartAddr_; } void* nativeEndAddr() const { return nativeEndAddr_; } bool startsBelowPointer(void* ptr) const { return ((uint8_t*)nativeStartAddr()) <= ((uint8_t*) ptr); } bool endsAbovePointer(void* ptr) const { return ((uint8_t*)nativeEndAddr()) > ((uint8_t*) ptr); } bool containsPointer(void* ptr) const { return startsBelowPointer(ptr) && endsAbovePointer(ptr); } template bool markJitcode(JSTracer* trc); bool isJitcodeMarkedFromAnyThread(JSRuntime* rt); bool isJitcodeAboutToBeFinalized(); }; struct IonEntry : public BaseEntry { // regionTable_ points to the start of the region table within the // packed map for compile represented by this entry. Since the // region table occurs at the tail of the memory region, this pointer // points somewhere inside the region memory space, and not to the start // of the memory space. JitcodeIonTable* regionTable_; // optsRegionTable_ points to the table within the compact // optimizations map indexing all regions that have tracked // optimization attempts. optsTypesTable_ is the tracked typed info // associated with the attempts vectors; it is the same length as the // attempts table. optsAttemptsTable_ is the table indexing those // attempts vectors. // // All pointers point into the same block of memory; the beginning of // the block is optRegionTable_->payloadStart(). const IonTrackedOptimizationsRegionTable* optsRegionTable_; const IonTrackedOptimizationsTypesTable* optsTypesTable_; const IonTrackedOptimizationsAttemptsTable* optsAttemptsTable_; // The types table above records type sets, which have been gathered // into one vector here. IonTrackedTypeVector* optsAllTypes_; struct ScriptNamePair { JSScript* script; char* str; }; struct SizedScriptList { uint32_t size; ScriptNamePair pairs[1]; SizedScriptList(uint32_t sz, JSScript** scrs, char** strs) : size(sz) { for (uint32_t i = 0; i < size; i++) { pairs[i].script = scrs[i]; pairs[i].str = strs[i]; } } static uint32_t AllocSizeFor(uint32_t nscripts) { return sizeof(SizedScriptList) + ((nscripts - 1) * sizeof(ScriptNamePair)); } }; SizedScriptList* scriptList_; void init(JitCode* code, void* nativeStartAddr, void* nativeEndAddr, SizedScriptList* scriptList, JitcodeIonTable* regionTable) { MOZ_ASSERT(scriptList); MOZ_ASSERT(regionTable); BaseEntry::init(Ion, code, nativeStartAddr, nativeEndAddr); regionTable_ = regionTable; scriptList_ = scriptList; optsRegionTable_ = nullptr; optsTypesTable_ = nullptr; optsAllTypes_ = nullptr; optsAttemptsTable_ = nullptr; } void initTrackedOptimizations(const IonTrackedOptimizationsRegionTable* regionTable, const IonTrackedOptimizationsTypesTable* typesTable, const IonTrackedOptimizationsAttemptsTable* attemptsTable, IonTrackedTypeVector* allTypes) { optsRegionTable_ = regionTable; optsTypesTable_ = typesTable; optsAttemptsTable_ = attemptsTable; optsAllTypes_ = allTypes; } SizedScriptList* sizedScriptList() const { return scriptList_; } unsigned numScripts() const { return scriptList_->size; } JSScript* getScript(unsigned idx) const { MOZ_ASSERT(idx < numScripts()); return sizedScriptList()->pairs[idx].script; } const char* getStr(unsigned idx) const { MOZ_ASSERT(idx < numScripts()); return sizedScriptList()->pairs[idx].str; } void destroy(); JitcodeIonTable* regionTable() const { return regionTable_; } int scriptIndex(JSScript* script) const { unsigned count = numScripts(); for (unsigned i = 0; i < count; i++) { if (getScript(i) == script) return i; } return -1; } void* canonicalNativeAddrFor(JSRuntime*rt, void* ptr) const; MOZ_MUST_USE bool callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results, uint32_t* depth) const; uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const; void youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr, JSScript** script, jsbytecode** pc) const; bool hasTrackedOptimizations() const { return !!optsRegionTable_; } const IonTrackedOptimizationsRegionTable* trackedOptimizationsRegionTable() const { MOZ_ASSERT(hasTrackedOptimizations()); return optsRegionTable_; } uint8_t numOptimizationAttempts() const { MOZ_ASSERT(hasTrackedOptimizations()); return optsAttemptsTable_->numEntries(); } IonTrackedOptimizationsAttempts trackedOptimizationAttempts(uint8_t index) { MOZ_ASSERT(hasTrackedOptimizations()); return optsAttemptsTable_->entry(index); } IonTrackedOptimizationsTypeInfo trackedOptimizationTypeInfo(uint8_t index) { MOZ_ASSERT(hasTrackedOptimizations()); return optsTypesTable_->entry(index); } const IonTrackedTypeVector* allTrackedTypes() { MOZ_ASSERT(hasTrackedOptimizations()); return optsAllTypes_; } mozilla::Maybe trackedOptimizationIndexAtAddr( JSRuntime *rt, void* ptr, uint32_t* entryOffsetOut); void forEachOptimizationAttempt(JSRuntime* rt, uint8_t index, JS::ForEachTrackedOptimizationAttemptOp& op); void forEachOptimizationTypeInfo(JSRuntime* rt, uint8_t index, IonTrackedOptimizationsTypeInfo::ForEachOpAdapter& op); template bool mark(JSTracer* trc); void sweepChildren(); bool isMarkedFromAnyThread(JSRuntime* rt); }; struct BaselineEntry : public BaseEntry { JSScript* script_; const char* str_; // Last location that caused Ion to abort compilation and the reason // therein, if any. Only actionable aborts are tracked. Internal // errors like OOMs are not. jsbytecode* ionAbortPc_; const char* ionAbortMessage_; void init(JitCode* code, void* nativeStartAddr, void* nativeEndAddr, JSScript* script, const char* str) { MOZ_ASSERT(script != nullptr); BaseEntry::init(Baseline, code, nativeStartAddr, nativeEndAddr); script_ = script; str_ = str; } JSScript* script() const { return script_; } const char* str() const { return str_; } void trackIonAbort(jsbytecode* pc, const char* message) { MOZ_ASSERT(script_->containsPC(pc)); MOZ_ASSERT(message); ionAbortPc_ = pc; ionAbortMessage_ = message; } bool hadIonAbort() const { MOZ_ASSERT(!ionAbortPc_ || ionAbortMessage_); return ionAbortPc_ != nullptr; } void destroy(); void* canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const; MOZ_MUST_USE bool callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results, uint32_t* depth) const; uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const; void youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr, JSScript** script, jsbytecode** pc) const; template bool mark(JSTracer* trc); void sweepChildren(); bool isMarkedFromAnyThread(JSRuntime* rt); }; struct IonCacheEntry : public BaseEntry { void* rejoinAddr_; JS::TrackedOutcome trackedOutcome_; void init(JitCode* code, void* nativeStartAddr, void* nativeEndAddr, void* rejoinAddr, JS::TrackedOutcome trackedOutcome) { MOZ_ASSERT(rejoinAddr != nullptr); BaseEntry::init(IonCache, code, nativeStartAddr, nativeEndAddr); rejoinAddr_ = rejoinAddr; trackedOutcome_ = trackedOutcome; } void* rejoinAddr() const { return rejoinAddr_; } JS::TrackedOutcome trackedOutcome() const { return trackedOutcome_; } void destroy() {} void* canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const; MOZ_MUST_USE bool callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results, uint32_t* depth) const; uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const; void youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr, JSScript** script, jsbytecode** pc) const; bool hasTrackedOptimizations() const { return true; } mozilla::Maybe trackedOptimizationIndexAtAddr( JSRuntime *rt, void* ptr, uint32_t* entryOffsetOut); void forEachOptimizationAttempt(JSRuntime* rt, uint8_t index, JS::ForEachTrackedOptimizationAttemptOp& op); void forEachOptimizationTypeInfo(JSRuntime* rt, uint8_t index, IonTrackedOptimizationsTypeInfo::ForEachOpAdapter& op); template bool mark(JSTracer* trc); void sweepChildren(JSRuntime* rt); bool isMarkedFromAnyThread(JSRuntime* rt); }; // Dummy entries are created for jitcode generated when profiling is not turned on, // so that they have representation in the global table if they are on the // stack when profiling is enabled. struct DummyEntry : public BaseEntry { void init(JitCode* code, void* nativeStartAddr, void* nativeEndAddr) { BaseEntry::init(Dummy, code, nativeStartAddr, nativeEndAddr); } void destroy() {} void* canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const { return nullptr; } MOZ_MUST_USE bool callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results, uint32_t* depth) const { return true; } uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const { return 0; } void youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr, JSScript** script, jsbytecode** pc) const { *script = nullptr; *pc = nullptr; } }; // QueryEntry is never stored in the table, just used for queries // where an instance of JitcodeGlobalEntry is required to do tree // lookups. struct QueryEntry : public BaseEntry { void init(void* addr) { BaseEntry::init(Query, nullptr, addr, addr); } uint8_t* addr() const { return reinterpret_cast(nativeStartAddr()); } void destroy() {} }; private: JitcodeSkiplistTower* tower_; union { // Shadowing BaseEntry instance to allow access to base fields // and type extraction. BaseEntry base_; // The most common entry type: describing jitcode generated by // Ion main-line code. IonEntry ion_; // Baseline jitcode. BaselineEntry baseline_; // IonCache stubs. IonCacheEntry ionCache_; // Dummy entries. DummyEntry dummy_; // When doing queries on the SplayTree for particular addresses, // the query addresses are representd using a QueryEntry. QueryEntry query_; }; public: JitcodeGlobalEntry() : tower_(nullptr) { base_.init(); } explicit JitcodeGlobalEntry(const IonEntry& ion) : tower_(nullptr) { ion_ = ion; } explicit JitcodeGlobalEntry(const BaselineEntry& baseline) : tower_(nullptr) { baseline_ = baseline; } explicit JitcodeGlobalEntry(const IonCacheEntry& ionCache) : tower_(nullptr) { ionCache_ = ionCache; } explicit JitcodeGlobalEntry(const DummyEntry& dummy) : tower_(nullptr) { dummy_ = dummy; } explicit JitcodeGlobalEntry(const QueryEntry& query) : tower_(nullptr) { query_ = query; } static JitcodeGlobalEntry MakeQuery(void* ptr) { QueryEntry query; query.init(ptr); return JitcodeGlobalEntry(query); } void destroy() { switch (kind()) { case Ion: ionEntry().destroy(); break; case Baseline: baselineEntry().destroy(); break; case IonCache: ionCacheEntry().destroy(); break; case Dummy: dummyEntry().destroy(); break; case Query: queryEntry().destroy(); break; default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } } JitCode* jitcode() const { return baseEntry().jitcode(); } void* nativeStartAddr() const { return base_.nativeStartAddr(); } void* nativeEndAddr() const { return base_.nativeEndAddr(); } uint32_t generation() const { return baseEntry().generation(); } void setGeneration(uint32_t gen) { baseEntry().setGeneration(gen); } void setAsExpired() { baseEntry().setGeneration(UINT32_MAX); } bool isSampled(uint32_t currentGen, uint32_t lapCount) { return baseEntry().isSampled(currentGen, lapCount); } bool startsBelowPointer(void* ptr) const { return base_.startsBelowPointer(ptr); } bool endsAbovePointer(void* ptr) const { return base_.endsAbovePointer(ptr); } bool containsPointer(void* ptr) const { return base_.containsPointer(ptr); } bool overlapsWith(const JitcodeGlobalEntry& entry) const { // Catch full containment of |entry| within |this|, and partial overlaps. if (containsPointer(entry.nativeStartAddr()) || containsPointer(entry.nativeEndAddr())) return true; // Catch full containment of |this| within |entry|. if (startsBelowPointer(entry.nativeEndAddr()) && endsAbovePointer(entry.nativeStartAddr())) return true; return false; } Kind kind() const { return base_.kind(); } bool isValid() const { return (kind() > INVALID) && (kind() < LIMIT); } bool isIon() const { return kind() == Ion; } bool isBaseline() const { return kind() == Baseline; } bool isIonCache() const { return kind() == IonCache; } bool isDummy() const { return kind() == Dummy; } bool isQuery() const { return kind() == Query; } BaseEntry& baseEntry() { MOZ_ASSERT(isValid()); return base_; } IonEntry& ionEntry() { MOZ_ASSERT(isIon()); return ion_; } BaselineEntry& baselineEntry() { MOZ_ASSERT(isBaseline()); return baseline_; } IonCacheEntry& ionCacheEntry() { MOZ_ASSERT(isIonCache()); return ionCache_; } DummyEntry& dummyEntry() { MOZ_ASSERT(isDummy()); return dummy_; } QueryEntry& queryEntry() { MOZ_ASSERT(isQuery()); return query_; } const BaseEntry& baseEntry() const { MOZ_ASSERT(isValid()); return base_; } const IonEntry& ionEntry() const { MOZ_ASSERT(isIon()); return ion_; } const BaselineEntry& baselineEntry() const { MOZ_ASSERT(isBaseline()); return baseline_; } const IonCacheEntry& ionCacheEntry() const { MOZ_ASSERT(isIonCache()); return ionCache_; } const DummyEntry& dummyEntry() const { MOZ_ASSERT(isDummy()); return dummy_; } const QueryEntry& queryEntry() const { MOZ_ASSERT(isQuery()); return query_; } void* canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const { switch (kind()) { case Ion: return ionEntry().canonicalNativeAddrFor(rt, ptr); case Baseline: return baselineEntry().canonicalNativeAddrFor(rt, ptr); case IonCache: return ionCacheEntry().canonicalNativeAddrFor(rt, ptr); case Dummy: return dummyEntry().canonicalNativeAddrFor(rt, ptr); default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } return nullptr; } // Read the inline call stack at a given point in the native code and append into // the given vector. Innermost (script,pc) pair will be appended first, and // outermost appended last. // // Returns false on memory failure. MOZ_MUST_USE bool callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results, uint32_t* depth) const { switch (kind()) { case Ion: return ionEntry().callStackAtAddr(rt, ptr, results, depth); case Baseline: return baselineEntry().callStackAtAddr(rt, ptr, results, depth); case IonCache: return ionCacheEntry().callStackAtAddr(rt, ptr, results, depth); case Dummy: return dummyEntry().callStackAtAddr(rt, ptr, results, depth); default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } return false; } uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const { switch (kind()) { case Ion: return ionEntry().callStackAtAddr(rt, ptr, results, maxResults); case Baseline: return baselineEntry().callStackAtAddr(rt, ptr, results, maxResults); case IonCache: return ionCacheEntry().callStackAtAddr(rt, ptr, results, maxResults); case Dummy: return dummyEntry().callStackAtAddr(rt, ptr, results, maxResults); default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } return false; } void youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr, JSScript** script, jsbytecode** pc) const { switch (kind()) { case Ion: return ionEntry().youngestFrameLocationAtAddr(rt, ptr, script, pc); case Baseline: return baselineEntry().youngestFrameLocationAtAddr(rt, ptr, script, pc); case IonCache: return ionCacheEntry().youngestFrameLocationAtAddr(rt, ptr, script, pc); case Dummy: return dummyEntry().youngestFrameLocationAtAddr(rt, ptr, script, pc); default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } } // Figure out the number of the (JSScript*, jsbytecode*) pairs that are active // at this location. uint32_t lookupInlineCallDepth(void* ptr); // Compare two global entries. static int compare(const JitcodeGlobalEntry& ent1, const JitcodeGlobalEntry& ent2); int compareTo(const JitcodeGlobalEntry& other) { return compare(*this, other); } // Compute a profiling string for a given script. static char* createScriptString(JSContext* cx, JSScript* script, size_t* length=nullptr); bool hasTrackedOptimizations() const { switch (kind()) { case Ion: return ionEntry().hasTrackedOptimizations(); case IonCache: return ionCacheEntry().hasTrackedOptimizations(); case Baseline: case Dummy: break; default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } return false; } mozilla::Maybe trackedOptimizationIndexAtAddr( JSRuntime *rt, void* addr, uint32_t* entryOffsetOut) { switch (kind()) { case Ion: return ionEntry().trackedOptimizationIndexAtAddr(rt, addr, entryOffsetOut); case IonCache: return ionCacheEntry().trackedOptimizationIndexAtAddr(rt, addr, entryOffsetOut); case Baseline: case Dummy: break; default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } return mozilla::Nothing(); } void forEachOptimizationAttempt(JSRuntime* rt, uint8_t index, JS::ForEachTrackedOptimizationAttemptOp& op) { switch (kind()) { case Ion: ionEntry().forEachOptimizationAttempt(rt, index, op); return; case IonCache: ionCacheEntry().forEachOptimizationAttempt(rt, index, op); return; case Baseline: case Dummy: break; default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } } void forEachOptimizationTypeInfo(JSRuntime* rt, uint8_t index, IonTrackedOptimizationsTypeInfo::ForEachOpAdapter& op) { switch (kind()) { case Ion: ionEntry().forEachOptimizationTypeInfo(rt, index, op); return; case IonCache: ionCacheEntry().forEachOptimizationTypeInfo(rt, index, op); return; case Baseline: case Dummy: break; default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } } IonTrackedOptimizationsAttempts trackedOptimizationAttempts(uint8_t index) { return ionEntry().trackedOptimizationAttempts(index); } IonTrackedOptimizationsTypeInfo trackedOptimizationTypeInfo(uint8_t index) { return ionEntry().trackedOptimizationTypeInfo(index); } const IonTrackedTypeVector* allTrackedTypes() { return ionEntry().allTrackedTypes(); } Zone* zone() { return baseEntry().jitcode()->zone(); } template bool mark(JSTracer* trc) { bool markedAny = baseEntry().markJitcode(trc); switch (kind()) { case Ion: markedAny |= ionEntry().mark(trc); break; case Baseline: markedAny |= baselineEntry().mark(trc); break; case IonCache: markedAny |= ionCacheEntry().mark(trc); break; case Dummy: break; default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } return markedAny; } void sweepChildren(JSRuntime* rt) { switch (kind()) { case Ion: ionEntry().sweepChildren(); break; case Baseline: baselineEntry().sweepChildren(); break; case IonCache: ionCacheEntry().sweepChildren(rt); break; case Dummy: break; default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } } bool isMarkedFromAnyThread(JSRuntime* rt) { if (!baseEntry().isJitcodeMarkedFromAnyThread(rt)) return false; switch (kind()) { case Ion: return ionEntry().isMarkedFromAnyThread(rt); case Baseline: return baselineEntry().isMarkedFromAnyThread(rt); case IonCache: return ionCacheEntry().isMarkedFromAnyThread(rt); case Dummy: break; default: MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); } return true; } // // When stored in a free-list, entries use 'tower_' to store a // pointer to the next entry. In this context only, 'tower_' // may refer to a |JitcodeGlobalEntry*| instead of a // |JitcodeSkiplistTower*|. // void addToFreeList(JitcodeGlobalEntry** freeList) { MOZ_ASSERT(!isValid()); JitcodeGlobalEntry* nextFreeEntry = *freeList; MOZ_ASSERT_IF(nextFreeEntry, !nextFreeEntry->isValid()); tower_ = (JitcodeSkiplistTower*) nextFreeEntry; *freeList = this; } static JitcodeGlobalEntry* PopFromFreeList(JitcodeGlobalEntry** freeList) { if (!*freeList) return nullptr; JitcodeGlobalEntry* entry = *freeList; MOZ_ASSERT(!entry->isValid()); JitcodeGlobalEntry* nextFreeEntry = (JitcodeGlobalEntry*) entry->tower_; entry->tower_ = nullptr; *freeList = nextFreeEntry; return entry; } }; /* * Global table of JitcodeGlobalEntry values sorted by native address range. */ class JitcodeGlobalTable { private: static const size_t LIFO_CHUNK_SIZE = 16 * 1024; LifoAlloc alloc_; JitcodeGlobalEntry* freeEntries_; uint32_t rand_; uint32_t skiplistSize_; JitcodeGlobalEntry* startTower_[JitcodeSkiplistTower::MAX_HEIGHT]; JitcodeSkiplistTower* freeTowers_[JitcodeSkiplistTower::MAX_HEIGHT]; public: JitcodeGlobalTable() : alloc_(LIFO_CHUNK_SIZE), freeEntries_(nullptr), rand_(0), skiplistSize_(0) { for (unsigned i = 0; i < JitcodeSkiplistTower::MAX_HEIGHT; i++) startTower_[i] = nullptr; for (unsigned i = 0; i < JitcodeSkiplistTower::MAX_HEIGHT; i++) freeTowers_[i] = nullptr; } ~JitcodeGlobalTable() {} bool empty() const { return skiplistSize_ == 0; } const JitcodeGlobalEntry* lookup(void* ptr) { return lookupInternal(ptr); } JitcodeGlobalEntry& lookupInfallible(void* ptr) { JitcodeGlobalEntry* entry = lookupInternal(ptr); MOZ_ASSERT(entry); return *entry; } const JitcodeGlobalEntry& lookupForSamplerInfallible(void* ptr, JSRuntime* rt, uint32_t sampleBufferGen); MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::IonEntry& entry, JSRuntime* rt) { return addEntry(JitcodeGlobalEntry(entry), rt); } MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::BaselineEntry& entry, JSRuntime* rt) { return addEntry(JitcodeGlobalEntry(entry), rt); } MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::IonCacheEntry& entry, JSRuntime* rt) { return addEntry(JitcodeGlobalEntry(entry), rt); } MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry::DummyEntry& entry, JSRuntime* rt) { return addEntry(JitcodeGlobalEntry(entry), rt); } void removeEntry(JitcodeGlobalEntry& entry, JitcodeGlobalEntry** prevTower, JSRuntime* rt); void releaseEntry(JitcodeGlobalEntry& entry, JitcodeGlobalEntry** prevTower, JSRuntime* rt); void setAllEntriesAsExpired(JSRuntime* rt); void markUnconditionally(JSTracer* trc); MOZ_MUST_USE bool markIteratively(JSTracer* trc); void sweep(JSRuntime* rt); private: MOZ_MUST_USE bool addEntry(const JitcodeGlobalEntry& entry, JSRuntime* rt); JitcodeGlobalEntry* lookupInternal(void* ptr); // Initialize towerOut such that towerOut[i] (for i in [0, MAX_HEIGHT-1]) // is a JitcodeGlobalEntry that is sorted to be = query. // // If entry with the given properties does not exist for level i, then // towerOut[i] is initialized to nullptr. void searchInternal(const JitcodeGlobalEntry& query, JitcodeGlobalEntry** towerOut); JitcodeGlobalEntry* searchAtHeight(unsigned level, JitcodeGlobalEntry* start, const JitcodeGlobalEntry& query); // Calculate next random tower height. unsigned generateTowerHeight(); JitcodeSkiplistTower* allocateTower(unsigned height); JitcodeGlobalEntry* allocateEntry(); #ifdef DEBUG void verifySkiplist(); #else void verifySkiplist() {} #endif public: class Range { protected: JitcodeGlobalTable& table_; JitcodeGlobalEntry* cur_; public: explicit Range(JitcodeGlobalTable& table) : table_(table), cur_(table.startTower_[0]) { } JitcodeGlobalEntry* front() const { MOZ_ASSERT(!empty()); return cur_; } bool empty() const { return !cur_; } void popFront() { MOZ_ASSERT(!empty()); cur_ = cur_->tower_->next(0); } }; // An enumerator class that can remove entries as it enumerates. If this // functionality is not needed, use Range instead. class Enum : public Range { JSRuntime* rt_; JitcodeGlobalEntry* next_; JitcodeGlobalEntry* prevTower_[JitcodeSkiplistTower::MAX_HEIGHT]; public: Enum(JitcodeGlobalTable& table, JSRuntime* rt); void popFront(); void removeFront(); }; }; /* * Container class for main jitcode table. * The Region table's memory is structured as follows: * * +------------------------------------------------+ | * | Region 1 Run | | * |------------------------------------------------| | * | Region 2 Run | | * | | | * | | | * |------------------------------------------------| | * | Region 3 Run | | * | | | * |------------------------------------------------| |-- Payload * | | | * | ... | | * | | | * |------------------------------------------------| | * | Region M Run | | * | | | * +================================================+ <- RegionTable pointer points here * | uint23_t numRegions = M | | * +------------------------------------------------+ | * | Region 1 | | * | uint32_t entryOffset = size(Payload) | | * +------------------------------------------------+ | * | | |-- Table * | ... | | * | | | * +------------------------------------------------+ | * | Region M | | * | uint32_t entryOffset | | * +------------------------------------------------+ | * * The region table is composed of two sections: a tail section that contains a table of * fixed-size entries containing offsets into the the head section, and a head section that * holds a sequence of variable-sized runs. The table in the tail section serves to * locate the variable-length encoded structures in the head section. * * The entryOffsets in the table indicate the bytes offset to subtract from the regionTable * pointer to arrive at the encoded region in the payload. * * * Variable-length entries in payload * ---------------------------------- * The entryOffsets in the region table's fixed-sized entries refer to a location within the * variable-length payload section. This location contains a compactly encoded "run" of * mappings. * * Each run starts by describing the offset within the native code it starts at, and the * sequence of (JSScript*, jsbytecode*) pairs active at that site. Following that, there * are a number of variable-length entries encoding (nativeOffsetDelta, bytecodeOffsetDelta) * pairs for the run. * * VarUint32 nativeOffset; * - The offset from nativeStartAddr in the global table entry at which * the jitcode for this region starts. * * Uint8_t scriptDepth; * - The depth of inlined scripts for this region. * * List inlineScriptPcStack; * - We encode (2 * scriptDepth) VarUint32s here. Each pair of uint32s are taken * as an index into the scriptList in the global table entry, and a pcOffset * respectively. * * List deltaRun; * - The rest of the entry is a deltaRun that stores a series of variable-length * encoded NativeAndBytecodeDelta datums. */ class JitcodeRegionEntry { private: static const unsigned MAX_RUN_LENGTH = 100; public: static void WriteHead(CompactBufferWriter& writer, uint32_t nativeOffset, uint8_t scriptDepth); static void ReadHead(CompactBufferReader& reader, uint32_t* nativeOffset, uint8_t* scriptDepth); static void WriteScriptPc(CompactBufferWriter& writer, uint32_t scriptIdx, uint32_t pcOffset); static void ReadScriptPc(CompactBufferReader& reader, uint32_t* scriptIdx, uint32_t* pcOffset); static void WriteDelta(CompactBufferWriter& writer, uint32_t nativeDelta, int32_t pcDelta); static void ReadDelta(CompactBufferReader& reader, uint32_t* nativeDelta, int32_t* pcDelta); // Given a pointer into an array of NativeToBytecode (and a pointer to the end of the array), // compute the number of entries that would be consume by outputting a run starting // at this one. static uint32_t ExpectedRunLength(const CodeGeneratorShared::NativeToBytecode* entry, const CodeGeneratorShared::NativeToBytecode* end); // Write a run, starting at the given NativeToBytecode entry, into the given buffer writer. static MOZ_MUST_USE bool WriteRun(CompactBufferWriter& writer, JSScript** scriptList, uint32_t scriptListSize, uint32_t runLength, const CodeGeneratorShared::NativeToBytecode* entry); // Delta Run entry formats are encoded little-endian: // // byte 0 // NNNN-BBB0 // Single byte format. nativeDelta in [0, 15], pcDelta in [0, 7] // static const uint32_t ENC1_MASK = 0x1; static const uint32_t ENC1_MASK_VAL = 0x0; static const uint32_t ENC1_NATIVE_DELTA_MAX = 0xf; static const unsigned ENC1_NATIVE_DELTA_SHIFT = 4; static const uint32_t ENC1_PC_DELTA_MASK = 0x0e; static const int32_t ENC1_PC_DELTA_MAX = 0x7; static const unsigned ENC1_PC_DELTA_SHIFT = 1; // byte 1 byte 0 // NNNN-NNNN BBBB-BB01 // Two-byte format. nativeDelta in [0, 255], pcDelta in [0, 63] // static const uint32_t ENC2_MASK = 0x3; static const uint32_t ENC2_MASK_VAL = 0x1; static const uint32_t ENC2_NATIVE_DELTA_MAX = 0xff; static const unsigned ENC2_NATIVE_DELTA_SHIFT = 8; static const uint32_t ENC2_PC_DELTA_MASK = 0x00fc; static const int32_t ENC2_PC_DELTA_MAX = 0x3f; static const unsigned ENC2_PC_DELTA_SHIFT = 2; // byte 2 byte 1 byte 0 // NNNN-NNNN NNNB-BBBB BBBB-B011 // Three-byte format. nativeDelta in [0, 2047], pcDelta in [-512, 511] // static const uint32_t ENC3_MASK = 0x7; static const uint32_t ENC3_MASK_VAL = 0x3; static const uint32_t ENC3_NATIVE_DELTA_MAX = 0x7ff; static const unsigned ENC3_NATIVE_DELTA_SHIFT = 13; static const uint32_t ENC3_PC_DELTA_MASK = 0x001ff8; static const int32_t ENC3_PC_DELTA_MAX = 0x1ff; static const int32_t ENC3_PC_DELTA_MIN = -ENC3_PC_DELTA_MAX - 1; static const unsigned ENC3_PC_DELTA_SHIFT = 3; // byte 3 byte 2 byte 1 byte 0 // NNNN-NNNN NNNN-NNNN BBBB-BBBB BBBB-B111 // Three-byte format. nativeDelta in [0, 65535], pcDelta in [-4096, 4095] static const uint32_t ENC4_MASK = 0x7; static const uint32_t ENC4_MASK_VAL = 0x7; static const uint32_t ENC4_NATIVE_DELTA_MAX = 0xffff; static const unsigned ENC4_NATIVE_DELTA_SHIFT = 16; static const uint32_t ENC4_PC_DELTA_MASK = 0x0000fff8; static const int32_t ENC4_PC_DELTA_MAX = 0xfff; static const int32_t ENC4_PC_DELTA_MIN = -ENC4_PC_DELTA_MAX - 1; static const unsigned ENC4_PC_DELTA_SHIFT = 3; static bool IsDeltaEncodeable(uint32_t nativeDelta, int32_t pcDelta) { return (nativeDelta <= ENC4_NATIVE_DELTA_MAX) && (pcDelta >= ENC4_PC_DELTA_MIN) && (pcDelta <= ENC4_PC_DELTA_MAX); } private: const uint8_t* data_; const uint8_t* end_; // Unpacked state from jitcode entry. uint32_t nativeOffset_; uint8_t scriptDepth_; const uint8_t* scriptPcStack_; const uint8_t* deltaRun_; void unpack(); public: JitcodeRegionEntry(const uint8_t* data, const uint8_t* end) : data_(data), end_(end), nativeOffset_(0), scriptDepth_(0), scriptPcStack_(nullptr), deltaRun_(nullptr) { MOZ_ASSERT(data_ < end_); unpack(); MOZ_ASSERT(scriptPcStack_ < end_); MOZ_ASSERT(deltaRun_ <= end_); } uint32_t nativeOffset() const { return nativeOffset_; } uint32_t scriptDepth() const { return scriptDepth_; } class ScriptPcIterator { private: uint32_t count_; const uint8_t* start_; const uint8_t* end_; uint32_t idx_; const uint8_t* cur_; public: ScriptPcIterator(uint32_t count, const uint8_t* start, const uint8_t* end) : count_(count), start_(start), end_(end), idx_(0), cur_(start_) {} bool hasMore() const { MOZ_ASSERT((idx_ == count_) == (cur_ == end_)); MOZ_ASSERT((idx_ < count_) == (cur_ < end_)); return cur_ < end_; } void readNext(uint32_t* scriptIdxOut, uint32_t* pcOffsetOut) { MOZ_ASSERT(scriptIdxOut); MOZ_ASSERT(pcOffsetOut); MOZ_ASSERT(hasMore()); CompactBufferReader reader(cur_, end_); ReadScriptPc(reader, scriptIdxOut, pcOffsetOut); cur_ = reader.currentPosition(); MOZ_ASSERT(cur_ <= end_); idx_++; MOZ_ASSERT_IF(idx_ == count_, cur_ == end_); } void reset() { idx_ = 0; cur_ = start_; } }; ScriptPcIterator scriptPcIterator() const { // End of script+pc sequence is the start of the delta run. return ScriptPcIterator(scriptDepth_, scriptPcStack_, deltaRun_); } class DeltaIterator { private: const uint8_t* start_; const uint8_t* end_; const uint8_t* cur_; public: DeltaIterator(const uint8_t* start, const uint8_t* end) : start_(start), end_(end), cur_(start) {} bool hasMore() const { MOZ_ASSERT(cur_ <= end_); return cur_ < end_; } void readNext(uint32_t* nativeDeltaOut, int32_t* pcDeltaOut) { MOZ_ASSERT(nativeDeltaOut != nullptr); MOZ_ASSERT(pcDeltaOut != nullptr); MOZ_ASSERT(hasMore()); CompactBufferReader reader(cur_, end_); ReadDelta(reader, nativeDeltaOut, pcDeltaOut); cur_ = reader.currentPosition(); MOZ_ASSERT(cur_ <= end_); } void reset() { cur_ = start_; } }; DeltaIterator deltaIterator() const { return DeltaIterator(deltaRun_, end_); } uint32_t findPcOffset(uint32_t queryNativeOffset, uint32_t startPcOffset) const; }; class JitcodeIonTable { private: /* Variable length payload section "below" here. */ uint32_t numRegions_; uint32_t regionOffsets_[1]; const uint8_t* payloadEnd() const { return reinterpret_cast(this); } public: explicit JitcodeIonTable(uint32_t numRegions) : numRegions_(numRegions) { for (uint32_t i = 0; i < numRegions; i++) regionOffsets_[i] = 0; } MOZ_MUST_USE bool makeIonEntry(JSContext* cx, JitCode* code, uint32_t numScripts, JSScript** scripts, JitcodeGlobalEntry::IonEntry& out); uint32_t numRegions() const { return numRegions_; } uint32_t regionOffset(uint32_t regionIndex) const { MOZ_ASSERT(regionIndex < numRegions()); return regionOffsets_[regionIndex]; } JitcodeRegionEntry regionEntry(uint32_t regionIndex) const { const uint8_t* regionStart = payloadEnd() - regionOffset(regionIndex); const uint8_t* regionEnd = payloadEnd(); if (regionIndex < numRegions_ - 1) regionEnd -= regionOffset(regionIndex + 1); return JitcodeRegionEntry(regionStart, regionEnd); } bool regionContainsOffset(uint32_t regionIndex, uint32_t nativeOffset) { MOZ_ASSERT(regionIndex < numRegions()); JitcodeRegionEntry ent = regionEntry(regionIndex); if (nativeOffset < ent.nativeOffset()) return false; if (regionIndex == numRegions_ - 1) return true; return nativeOffset < regionEntry(regionIndex + 1).nativeOffset(); } uint32_t findRegionEntry(uint32_t offset) const; const uint8_t* payloadStart() const { // The beginning of the payload the beginning of the first region are the same. return payloadEnd() - regionOffset(0); } static MOZ_MUST_USE bool WriteIonTable(CompactBufferWriter& writer, JSScript** scriptList, uint32_t scriptListSize, const CodeGeneratorShared::NativeToBytecode* start, const CodeGeneratorShared::NativeToBytecode* end, uint32_t* tableOffsetOut, uint32_t* numRegionsOut); }; } // namespace jit } // namespace js #endif /* jit_JitcodeMap_h */