diff options
Diffstat (limited to 'js/src/jit/JitcodeMap.h')
-rw-r--r-- | js/src/jit/JitcodeMap.h | 1493 |
1 files changed, 1493 insertions, 0 deletions
diff --git a/js/src/jit/JitcodeMap.h b/js/src/jit/JitcodeMap.h new file mode 100644 index 000000000..384a75410 --- /dev/null +++ b/js/src/jit/JitcodeMap.h @@ -0,0 +1,1493 @@ +/* -*- 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<BytecodeLocation, 0, SystemAllocPolicy> BytecodeLocationVector; + typedef Vector<const char*, 0, SystemAllocPolicy> 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 <class ShouldMarkProvider> 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<uint8_t> 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 <class ShouldMarkProvider> 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 <class ShouldMarkProvider> 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<uint8_t> 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 <class ShouldMarkProvider> 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<uint8_t*>(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<uint8_t> 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 <class ShouldMarkProvider> + bool mark(JSTracer* trc) { + bool markedAny = baseEntry().markJitcode<ShouldMarkProvider>(trc); + switch (kind()) { + case Ion: + markedAny |= ionEntry().mark<ShouldMarkProvider>(trc); + break; + case Baseline: + markedAny |= baselineEntry().mark<ShouldMarkProvider>(trc); + break; + case IonCache: + markedAny |= ionCacheEntry().mark<ShouldMarkProvider>(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, whose successor at + // level i is either null, or 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<VarUint32> 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<NativeAndBytecodeDelta> 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<const uint8_t*>(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 */ |