summaryrefslogtreecommitdiffstats
path: root/js/src/jit/JitcodeMap.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /js/src/jit/JitcodeMap.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'js/src/jit/JitcodeMap.cpp')
-rw-r--r--js/src/jit/JitcodeMap.cpp1662
1 files changed, 1662 insertions, 0 deletions
diff --git a/js/src/jit/JitcodeMap.cpp b/js/src/jit/JitcodeMap.cpp
new file mode 100644
index 000000000..d08218520
--- /dev/null
+++ b/js/src/jit/JitcodeMap.cpp
@@ -0,0 +1,1662 @@
+/* -*- 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/. */
+
+#include "jit/JitcodeMap.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/SizePrintfMacros.h"
+#include "mozilla/Sprintf.h"
+
+#include <algorithm>
+
+#include "jsprf.h"
+
+#include "gc/Marking.h"
+#include "gc/Statistics.h"
+#include "jit/BaselineJIT.h"
+#include "jit/JitSpewer.h"
+#include "js/Vector.h"
+#include "vm/SPSProfiler.h"
+
+#include "jsscriptinlines.h"
+
+#include "vm/TypeInference-inl.h"
+
+using mozilla::Maybe;
+
+namespace js {
+namespace jit {
+
+static inline JitcodeRegionEntry
+RegionAtAddr(const JitcodeGlobalEntry::IonEntry& entry, void* ptr,
+ uint32_t* ptrOffset)
+{
+ MOZ_ASSERT(entry.containsPointer(ptr));
+ *ptrOffset = reinterpret_cast<uint8_t*>(ptr) -
+ reinterpret_cast<uint8_t*>(entry.nativeStartAddr());
+
+ uint32_t regionIdx = entry.regionTable()->findRegionEntry(*ptrOffset);
+ MOZ_ASSERT(regionIdx < entry.regionTable()->numRegions());
+
+ return entry.regionTable()->regionEntry(regionIdx);
+}
+
+void*
+JitcodeGlobalEntry::IonEntry::canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const
+{
+ uint32_t ptrOffset;
+ JitcodeRegionEntry region = RegionAtAddr(*this, ptr, &ptrOffset);
+ return (void*)(((uint8_t*) nativeStartAddr()) + region.nativeOffset());
+}
+
+bool
+JitcodeGlobalEntry::IonEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
+ BytecodeLocationVector& results,
+ uint32_t* depth) const
+{
+ uint32_t ptrOffset;
+ JitcodeRegionEntry region = RegionAtAddr(*this, ptr, &ptrOffset);
+ *depth = region.scriptDepth();
+
+ JitcodeRegionEntry::ScriptPcIterator locationIter = region.scriptPcIterator();
+ MOZ_ASSERT(locationIter.hasMore());
+ bool first = true;
+ while (locationIter.hasMore()) {
+ uint32_t scriptIdx, pcOffset;
+ locationIter.readNext(&scriptIdx, &pcOffset);
+ // For the first entry pushed (innermost frame), the pcOffset is obtained
+ // from the delta-run encodings.
+ if (first) {
+ pcOffset = region.findPcOffset(ptrOffset, pcOffset);
+ first = false;
+ }
+ JSScript* script = getScript(scriptIdx);
+ jsbytecode* pc = script->offsetToPC(pcOffset);
+ if (!results.append(BytecodeLocation(script, pc)))
+ return false;
+ }
+
+ return true;
+}
+
+uint32_t
+JitcodeGlobalEntry::IonEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
+ const char** results,
+ uint32_t maxResults) const
+{
+ MOZ_ASSERT(maxResults >= 1);
+
+ uint32_t ptrOffset;
+ JitcodeRegionEntry region = RegionAtAddr(*this, ptr, &ptrOffset);
+
+ JitcodeRegionEntry::ScriptPcIterator locationIter = region.scriptPcIterator();
+ MOZ_ASSERT(locationIter.hasMore());
+ uint32_t count = 0;
+ while (locationIter.hasMore()) {
+ uint32_t scriptIdx, pcOffset;
+
+ locationIter.readNext(&scriptIdx, &pcOffset);
+ MOZ_ASSERT(getStr(scriptIdx));
+
+ results[count++] = getStr(scriptIdx);
+ if (count >= maxResults)
+ break;
+ }
+
+ return count;
+}
+
+void
+JitcodeGlobalEntry::IonEntry::youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr,
+ JSScript** script, jsbytecode** pc) const
+{
+ uint32_t ptrOffset;
+ JitcodeRegionEntry region = RegionAtAddr(*this, ptr, &ptrOffset);
+
+ JitcodeRegionEntry::ScriptPcIterator locationIter = region.scriptPcIterator();
+ MOZ_ASSERT(locationIter.hasMore());
+ uint32_t scriptIdx, pcOffset;
+ locationIter.readNext(&scriptIdx, &pcOffset);
+ pcOffset = region.findPcOffset(ptrOffset, pcOffset);
+
+ *script = getScript(scriptIdx);
+ *pc = (*script)->offsetToPC(pcOffset);
+}
+
+void
+JitcodeGlobalEntry::IonEntry::destroy()
+{
+ // The region table is stored at the tail of the compacted data,
+ // which means the start of the region table is a pointer to
+ // the _middle_ of the memory space allocated for it.
+ //
+ // When freeing it, obtain the payload start pointer first.
+ if (regionTable_)
+ js_free((void*) (regionTable_->payloadStart()));
+ regionTable_ = nullptr;
+
+ // Free the scriptList strs.
+ for (uint32_t i = 0; i < scriptList_->size; i++) {
+ js_free(scriptList_->pairs[i].str);
+ scriptList_->pairs[i].str = nullptr;
+ }
+
+ // Free the script list
+ js_free(scriptList_);
+ scriptList_ = nullptr;
+
+ // The optimizations region and attempts table is in the same block of
+ // memory, the beginning of which is pointed to by
+ // optimizationsRegionTable_->payloadStart().
+ if (optsRegionTable_) {
+ MOZ_ASSERT(optsAttemptsTable_);
+ js_free((void*) optsRegionTable_->payloadStart());
+ }
+ optsRegionTable_ = nullptr;
+ optsTypesTable_ = nullptr;
+ optsAttemptsTable_ = nullptr;
+ js_delete(optsAllTypes_);
+ optsAllTypes_ = nullptr;
+}
+
+void*
+JitcodeGlobalEntry::BaselineEntry::canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const
+{
+ // TODO: We can't yet normalize Baseline addresses until we unify
+ // BaselineScript's PCMappingEntries with JitcodeGlobalMap.
+ return ptr;
+}
+
+bool
+JitcodeGlobalEntry::BaselineEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
+ BytecodeLocationVector& results,
+ uint32_t* depth) const
+{
+ MOZ_ASSERT(containsPointer(ptr));
+ MOZ_ASSERT(script_->hasBaselineScript());
+
+ uint8_t* addr = reinterpret_cast<uint8_t*>(ptr);
+ jsbytecode* pc = script_->baselineScript()->approximatePcForNativeAddress(script_, addr);
+ if (!results.append(BytecodeLocation(script_, pc)))
+ return false;
+
+ *depth = 1;
+
+ return true;
+}
+
+uint32_t
+JitcodeGlobalEntry::BaselineEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
+ const char** results,
+ uint32_t maxResults) const
+{
+ MOZ_ASSERT(containsPointer(ptr));
+ MOZ_ASSERT(maxResults >= 1);
+
+ results[0] = str();
+ return 1;
+}
+
+void
+JitcodeGlobalEntry::BaselineEntry::youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr,
+ JSScript** script,
+ jsbytecode** pc) const
+{
+ uint8_t* addr = reinterpret_cast<uint8_t*>(ptr);
+ *script = script_;
+ *pc = script_->baselineScript()->approximatePcForNativeAddress(script_, addr);
+}
+
+void
+JitcodeGlobalEntry::BaselineEntry::destroy()
+{
+ if (!str_)
+ return;
+ js_free((void*) str_);
+ str_ = nullptr;
+}
+
+static inline JitcodeGlobalEntry&
+RejoinEntry(JSRuntime* rt, const JitcodeGlobalEntry::IonCacheEntry& cache, void* ptr)
+{
+ MOZ_ASSERT(cache.containsPointer(ptr));
+
+ // There must exist an entry for the rejoin addr if this entry exists.
+ JitRuntime* jitrt = rt->jitRuntime();
+ JitcodeGlobalEntry& entry =
+ jitrt->getJitcodeGlobalTable()->lookupInfallible(cache.rejoinAddr());
+ MOZ_ASSERT(entry.isIon());
+ return entry;
+}
+
+void*
+JitcodeGlobalEntry::IonCacheEntry::canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const
+{
+ return nativeStartAddr_;
+}
+
+bool
+JitcodeGlobalEntry::IonCacheEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
+ BytecodeLocationVector& results,
+ uint32_t* depth) const
+{
+ const JitcodeGlobalEntry& entry = RejoinEntry(rt, *this, ptr);
+ return entry.callStackAtAddr(rt, rejoinAddr(), results, depth);
+}
+
+uint32_t
+JitcodeGlobalEntry::IonCacheEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
+ const char** results,
+ uint32_t maxResults) const
+{
+ const JitcodeGlobalEntry& entry = RejoinEntry(rt, *this, ptr);
+ return entry.callStackAtAddr(rt, rejoinAddr(), results, maxResults);
+}
+
+void
+JitcodeGlobalEntry::IonCacheEntry::youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr,
+ JSScript** script,
+ jsbytecode** pc) const
+{
+ const JitcodeGlobalEntry& entry = RejoinEntry(rt, *this, ptr);
+ return entry.youngestFrameLocationAtAddr(rt, rejoinAddr(), script, pc);
+}
+
+
+static int ComparePointers(const void* a, const void* b) {
+ const uint8_t* a_ptr = reinterpret_cast<const uint8_t*>(a);
+ const uint8_t* b_ptr = reinterpret_cast<const uint8_t*>(b);
+ if (a_ptr < b_ptr)
+ return -1;
+ if (a_ptr > b_ptr)
+ return 1;
+ return 0;
+}
+
+/* static */ int
+JitcodeGlobalEntry::compare(const JitcodeGlobalEntry& ent1, const JitcodeGlobalEntry& ent2)
+{
+ // Both parts of compare cannot be a query.
+ MOZ_ASSERT(!(ent1.isQuery() && ent2.isQuery()));
+
+ // Ensure no overlaps for non-query lookups.
+ MOZ_ASSERT_IF(!ent1.isQuery() && !ent2.isQuery(), !ent1.overlapsWith(ent2));
+
+ // For two non-query entries, just comapare the start addresses.
+ if (!ent1.isQuery() && !ent2.isQuery())
+ return ComparePointers(ent1.nativeStartAddr(), ent2.nativeStartAddr());
+
+ void* ptr = ent1.isQuery() ? ent1.nativeStartAddr() : ent2.nativeStartAddr();
+ const JitcodeGlobalEntry& ent = ent1.isQuery() ? ent2 : ent1;
+ int flip = ent1.isQuery() ? 1 : -1;
+
+ if (ent.startsBelowPointer(ptr)) {
+ if (ent.endsAbovePointer(ptr))
+ return 0;
+
+ // query ptr > entry
+ return flip * 1;
+ }
+
+ // query ptr < entry
+ return flip * -1;
+}
+
+/* static */ char*
+JitcodeGlobalEntry::createScriptString(JSContext* cx, JSScript* script, size_t* length)
+{
+ // If the script has a function, try calculating its name.
+ bool hasName = false;
+ size_t nameLength = 0;
+ UniqueChars nameStr;
+ JSFunction* func = script->functionDelazifying();
+ if (func && func->displayAtom()) {
+ nameStr = StringToNewUTF8CharsZ(cx, *func->displayAtom());
+ if (!nameStr)
+ return nullptr;
+
+ nameLength = strlen(nameStr.get());
+ hasName = true;
+ }
+
+ // Calculate filename length
+ const char* filenameStr = script->filename() ? script->filename() : "(null)";
+ size_t filenameLength = strlen(filenameStr);
+
+ // Calculate lineno length
+ bool hasLineno = false;
+ size_t linenoLength = 0;
+ char linenoStr[15];
+ if (hasName || (script->functionNonDelazifying() || script->isForEval())) {
+ linenoLength = SprintfLiteral(linenoStr, "%" PRIuSIZE, script->lineno());
+ hasLineno = true;
+ }
+
+ // Full profile string for scripts with functions is:
+ // FuncName (FileName:Lineno)
+ // Full profile string for scripts without functions is:
+ // FileName:Lineno
+ // Full profile string for scripts without functions and without linenos is:
+ // FileName
+
+ // Calculate full string length.
+ size_t fullLength = 0;
+ if (hasName) {
+ MOZ_ASSERT(hasLineno);
+ fullLength = nameLength + 2 + filenameLength + 1 + linenoLength + 1;
+ } else if (hasLineno) {
+ fullLength = filenameLength + 1 + linenoLength;
+ } else {
+ fullLength = filenameLength;
+ }
+
+ // Allocate string.
+ char* str = cx->pod_malloc<char>(fullLength + 1);
+ if (!str)
+ return nullptr;
+
+ size_t cur = 0;
+
+ // Fill string with func name if needed.
+ if (hasName) {
+ memcpy(str + cur, nameStr.get(), nameLength);
+ cur += nameLength;
+ str[cur++] = ' ';
+ str[cur++] = '(';
+ }
+
+ // Fill string with filename chars.
+ memcpy(str + cur, filenameStr, filenameLength);
+ cur += filenameLength;
+
+ // Fill lineno chars.
+ if (hasLineno) {
+ str[cur++] = ':';
+ memcpy(str + cur, linenoStr, linenoLength);
+ cur += linenoLength;
+ }
+
+ // Terminal ')' if necessary.
+ if (hasName)
+ str[cur++] = ')';
+
+ MOZ_ASSERT(cur == fullLength);
+ str[cur] = 0;
+
+ if (length)
+ *length = fullLength;
+
+ return str;
+}
+
+
+JitcodeGlobalTable::Enum::Enum(JitcodeGlobalTable& table, JSRuntime* rt)
+ : Range(table),
+ rt_(rt),
+ next_(cur_ ? cur_->tower_->next(0) : nullptr)
+{
+ for (int level = JitcodeSkiplistTower::MAX_HEIGHT - 1; level >= 0; level--)
+ prevTower_[level] = nullptr;
+}
+
+void
+JitcodeGlobalTable::Enum::popFront()
+{
+ MOZ_ASSERT(!empty());
+
+ // Did not remove current entry; advance prevTower_.
+ if (cur_ != table_.freeEntries_) {
+ for (int level = cur_->tower_->height() - 1; level >= 0; level--) {
+ JitcodeGlobalEntry* prevTowerEntry = prevTower_[level];
+
+ if (prevTowerEntry) {
+ if (prevTowerEntry->tower_->next(level) == cur_)
+ prevTower_[level] = cur_;
+ } else {
+ prevTower_[level] = table_.startTower_[level];
+ }
+ }
+ }
+
+ cur_ = next_;
+ if (!empty())
+ next_ = cur_->tower_->next(0);
+}
+
+void
+JitcodeGlobalTable::Enum::removeFront()
+{
+ MOZ_ASSERT(!empty());
+ table_.releaseEntry(*cur_, prevTower_, rt_);
+}
+
+const JitcodeGlobalEntry&
+JitcodeGlobalTable::lookupForSamplerInfallible(void* ptr, JSRuntime* rt, uint32_t sampleBufferGen)
+{
+ JitcodeGlobalEntry* entry = lookupInternal(ptr);
+ MOZ_ASSERT(entry);
+
+ entry->setGeneration(sampleBufferGen);
+
+ // IonCache entries must keep their corresponding Ion entries alive.
+ if (entry->isIonCache()) {
+ JitcodeGlobalEntry& rejoinEntry = RejoinEntry(rt, entry->ionCacheEntry(), ptr);
+ rejoinEntry.setGeneration(sampleBufferGen);
+ }
+
+ // JitcodeGlobalEntries are marked at the end of the mark phase. A read
+ // barrier is not needed. Any JS frames sampled during the sweep phase of
+ // the GC must be on stack, and on-stack frames must already be marked at
+ // the beginning of the sweep phase. It's not possible to assert this here
+ // as we may not be running on the main thread when called from the gecko
+ // profiler.
+
+ return *entry;
+}
+
+JitcodeGlobalEntry*
+JitcodeGlobalTable::lookupInternal(void* ptr)
+{
+ JitcodeGlobalEntry query = JitcodeGlobalEntry::MakeQuery(ptr);
+ JitcodeGlobalEntry* searchTower[JitcodeSkiplistTower::MAX_HEIGHT];
+ searchInternal(query, searchTower);
+
+ if (searchTower[0] == nullptr) {
+ // Check startTower
+ if (startTower_[0] == nullptr)
+ return nullptr;
+
+ MOZ_ASSERT(startTower_[0]->compareTo(query) >= 0);
+ int cmp = startTower_[0]->compareTo(query);
+ MOZ_ASSERT(cmp >= 0);
+ return (cmp == 0) ? startTower_[0] : nullptr;
+ }
+
+ JitcodeGlobalEntry* bottom = searchTower[0];
+ MOZ_ASSERT(bottom->compareTo(query) < 0);
+
+ JitcodeGlobalEntry* bottomNext = bottom->tower_->next(0);
+ if (bottomNext == nullptr)
+ return nullptr;
+
+ int cmp = bottomNext->compareTo(query);
+ MOZ_ASSERT(cmp >= 0);
+ return (cmp == 0) ? bottomNext : nullptr;
+}
+
+bool
+JitcodeGlobalTable::addEntry(const JitcodeGlobalEntry& entry, JSRuntime* rt)
+{
+ MOZ_ASSERT(entry.isIon() || entry.isBaseline() || entry.isIonCache() || entry.isDummy());
+
+ JitcodeGlobalEntry* searchTower[JitcodeSkiplistTower::MAX_HEIGHT];
+ searchInternal(entry, searchTower);
+
+ // Allocate a new entry and tower.
+ JitcodeSkiplistTower* newTower = allocateTower(generateTowerHeight());
+ if (!newTower)
+ return false;
+
+ JitcodeGlobalEntry* newEntry = allocateEntry();
+ if (!newEntry)
+ return false;
+
+ *newEntry = entry;
+ newEntry->tower_ = newTower;
+
+ // Suppress profiler sampling while skiplist is being mutated.
+ AutoSuppressProfilerSampling suppressSampling(rt);
+
+ // Link up entry with forward entries taken from tower.
+ for (int level = newTower->height() - 1; level >= 0; level--) {
+ JitcodeGlobalEntry* searchTowerEntry = searchTower[level];
+ if (searchTowerEntry) {
+ MOZ_ASSERT(searchTowerEntry->compareTo(*newEntry) < 0);
+ JitcodeGlobalEntry* searchTowerNextEntry = searchTowerEntry->tower_->next(level);
+
+ MOZ_ASSERT_IF(searchTowerNextEntry, searchTowerNextEntry->compareTo(*newEntry) > 0);
+
+ newTower->setNext(level, searchTowerNextEntry);
+ searchTowerEntry->tower_->setNext(level, newEntry);
+ } else {
+ newTower->setNext(level, startTower_[level]);
+ startTower_[level] = newEntry;
+ }
+ }
+ skiplistSize_++;
+ // verifySkiplist(); - disabled for release.
+ return true;
+}
+
+void
+JitcodeGlobalTable::removeEntry(JitcodeGlobalEntry& entry, JitcodeGlobalEntry** prevTower,
+ JSRuntime* rt)
+{
+ MOZ_ASSERT(!rt->isProfilerSamplingEnabled());
+
+ // Unlink query entry.
+ for (int level = entry.tower_->height() - 1; level >= 0; level--) {
+ JitcodeGlobalEntry* prevTowerEntry = prevTower[level];
+ if (prevTowerEntry) {
+ MOZ_ASSERT(prevTowerEntry->tower_->next(level) == &entry);
+ prevTowerEntry->tower_->setNext(level, entry.tower_->next(level));
+ } else {
+ startTower_[level] = entry.tower_->next(level);
+ }
+ }
+ skiplistSize_--;
+ // verifySkiplist(); - disabled for release.
+
+ // Entry has been unlinked.
+ entry.destroy();
+ entry.tower_->addToFreeList(&(freeTowers_[entry.tower_->height() - 1]));
+ entry.tower_ = nullptr;
+ entry = JitcodeGlobalEntry();
+ entry.addToFreeList(&freeEntries_);
+}
+
+void
+JitcodeGlobalTable::releaseEntry(JitcodeGlobalEntry& entry, JitcodeGlobalEntry** prevTower,
+ JSRuntime* rt)
+{
+ mozilla::DebugOnly<uint32_t> gen = rt->profilerSampleBufferGen();
+ mozilla::DebugOnly<uint32_t> lapCount = rt->profilerSampleBufferLapCount();
+ MOZ_ASSERT_IF(gen != UINT32_MAX, !entry.isSampled(gen, lapCount));
+ removeEntry(entry, prevTower, rt);
+}
+
+void
+JitcodeGlobalTable::searchInternal(const JitcodeGlobalEntry& query, JitcodeGlobalEntry** towerOut)
+{
+ JitcodeGlobalEntry* cur = nullptr;
+ for (int level = JitcodeSkiplistTower::MAX_HEIGHT - 1; level >= 0; level--) {
+ JitcodeGlobalEntry* entry = searchAtHeight(level, cur, query);
+ MOZ_ASSERT_IF(entry == nullptr, cur == nullptr);
+ towerOut[level] = entry;
+ cur = entry;
+ }
+
+ // Validate the resulting tower.
+#ifdef DEBUG
+ for (int level = JitcodeSkiplistTower::MAX_HEIGHT - 1; level >= 0; level--) {
+ if (towerOut[level] == nullptr) {
+ // If we got NULL for a given level, then we should have gotten NULL
+ // for the level above as well.
+ MOZ_ASSERT_IF(unsigned(level) < (JitcodeSkiplistTower::MAX_HEIGHT - 1),
+ towerOut[level + 1] == nullptr);
+ continue;
+ }
+
+ JitcodeGlobalEntry* cur = towerOut[level];
+
+ // Non-null result at a given level must sort < query.
+ MOZ_ASSERT(cur->compareTo(query) < 0);
+
+ // The entry must have a tower height that accomodates level.
+ if (!cur->tower_->next(level))
+ continue;
+
+ JitcodeGlobalEntry* next = cur->tower_->next(level);
+
+ // Next entry must have tower height that accomodates level.
+ MOZ_ASSERT(unsigned(level) < next->tower_->height());
+
+ // Next entry must sort >= query.
+ MOZ_ASSERT(next->compareTo(query) >= 0);
+ }
+#endif // DEBUG
+}
+
+JitcodeGlobalEntry*
+JitcodeGlobalTable::searchAtHeight(unsigned level, JitcodeGlobalEntry* start,
+ const JitcodeGlobalEntry& query)
+{
+ JitcodeGlobalEntry* cur = start;
+
+ // If starting with nullptr, use the start tower.
+ if (start == nullptr) {
+ cur = startTower_[level];
+ if (cur == nullptr || cur->compareTo(query) >= 0)
+ return nullptr;
+ }
+
+ // Keep skipping at |level| until we reach an entry < query whose
+ // successor is an entry >= query.
+ for (;;) {
+ JitcodeGlobalEntry* next = cur->tower_->next(level);
+ if (next == nullptr || next->compareTo(query) >= 0)
+ return cur;
+
+ cur = next;
+ }
+}
+
+unsigned
+JitcodeGlobalTable::generateTowerHeight()
+{
+ // Implementation taken from Hars L. and Pteruska G.,
+ // "Pseudorandom Recursions: Small and fast Pseudorandom number generators for
+ // embedded applications."
+ rand_ ^= mozilla::RotateLeft(rand_, 5) ^ mozilla::RotateLeft(rand_, 24);
+ rand_ += 0x37798849;
+
+ // Return number of lowbit zeros in new randval.
+ unsigned result = 0;
+ for (unsigned i = 0; i < 32; i++) {
+ if ((rand_ >> i) & 0x1)
+ break;
+ result++;
+ }
+ return (std::max)(1U, result);
+}
+
+JitcodeSkiplistTower*
+JitcodeGlobalTable::allocateTower(unsigned height)
+{
+ MOZ_ASSERT(height >= 1);
+ JitcodeSkiplistTower* tower = JitcodeSkiplistTower::PopFromFreeList(&freeTowers_[height - 1]);
+ if (tower)
+ return tower;
+
+ size_t size = JitcodeSkiplistTower::CalculateSize(height);
+ tower = (JitcodeSkiplistTower*) alloc_.alloc(size);
+ if (!tower)
+ return nullptr;
+
+ return new (tower) JitcodeSkiplistTower(height);
+}
+
+JitcodeGlobalEntry*
+JitcodeGlobalTable::allocateEntry()
+{
+ JitcodeGlobalEntry* entry = JitcodeGlobalEntry::PopFromFreeList(&freeEntries_);
+ if (entry)
+ return entry;
+
+ return alloc_.new_<JitcodeGlobalEntry>();
+}
+
+#ifdef DEBUG
+void
+JitcodeGlobalTable::verifySkiplist()
+{
+ JitcodeGlobalEntry* curTower[JitcodeSkiplistTower::MAX_HEIGHT];
+ for (unsigned i = 0; i < JitcodeSkiplistTower::MAX_HEIGHT; i++)
+ curTower[i] = startTower_[i];
+
+ uint32_t count = 0;
+ JitcodeGlobalEntry* curEntry = startTower_[0];
+ while (curEntry) {
+ count++;
+ unsigned curHeight = curEntry->tower_->height();
+ MOZ_ASSERT(curHeight >= 1);
+
+ for (unsigned i = 0; i < JitcodeSkiplistTower::MAX_HEIGHT; i++) {
+ if (i < curHeight) {
+ MOZ_ASSERT(curTower[i] == curEntry);
+ JitcodeGlobalEntry* nextEntry = curEntry->tower_->next(i);
+ MOZ_ASSERT_IF(nextEntry, curEntry->compareTo(*nextEntry) < 0);
+ curTower[i] = nextEntry;
+ } else {
+ MOZ_ASSERT_IF(curTower[i], curTower[i]->compareTo(*curEntry) > 0);
+ }
+ }
+ curEntry = curEntry->tower_->next(0);
+ }
+
+ MOZ_ASSERT(count == skiplistSize_);
+}
+#endif // DEBUG
+
+void
+JitcodeGlobalTable::setAllEntriesAsExpired(JSRuntime* rt)
+{
+ AutoSuppressProfilerSampling suppressSampling(rt);
+ for (Range r(*this); !r.empty(); r.popFront())
+ r.front()->setAsExpired();
+}
+
+struct Unconditionally
+{
+ template <typename T>
+ static bool ShouldMark(JSRuntime* rt, T* thingp) { return true; }
+};
+
+void
+JitcodeGlobalTable::markUnconditionally(JSTracer* trc)
+{
+ // Mark all entries unconditionally. This is done during minor collection
+ // to account for tenuring.
+
+ MOZ_ASSERT(trc->runtime()->spsProfiler.enabled());
+
+ AutoSuppressProfilerSampling suppressSampling(trc->runtime());
+ for (Range r(*this); !r.empty(); r.popFront())
+ r.front()->mark<Unconditionally>(trc);
+}
+
+struct IfUnmarked
+{
+ template <typename T>
+ static bool ShouldMark(JSRuntime* rt, T* thingp) { return !IsMarkedUnbarriered(rt, thingp); }
+};
+
+template <>
+bool IfUnmarked::ShouldMark<TypeSet::Type>(JSRuntime* rt, TypeSet::Type* type)
+{
+ return !TypeSet::IsTypeMarked(rt, type);
+}
+
+bool
+JitcodeGlobalTable::markIteratively(JSTracer* trc)
+{
+ // JitcodeGlobalTable must keep entries that are in the sampler buffer
+ // alive. This conditionality is akin to holding the entries weakly.
+ //
+ // If this table were marked at the beginning of the mark phase, then
+ // sampling would require a read barrier for sampling in between
+ // incremental GC slices. However, invoking read barriers from the sampler
+ // is wildly unsafe. The sampler may run at any time, including during GC
+ // itself.
+ //
+ // Instead, JitcodeGlobalTable is marked at the beginning of the sweep
+ // phase, along with weak references. The key assumption is the
+ // following. At the beginning of the sweep phase, any JS frames that the
+ // sampler may put in its buffer that are not already there at the
+ // beginning of the mark phase must have already been marked, as either 1)
+ // the frame was on-stack at the beginning of the sweep phase, or 2) the
+ // frame was pushed between incremental sweep slices. Frames of case 1)
+ // are already marked. Frames of case 2) must have been reachable to have
+ // been newly pushed, and thus are already marked.
+ //
+ // The approach above obviates the need for read barriers. The assumption
+ // above is checked in JitcodeGlobalTable::lookupForSampler.
+
+ MOZ_ASSERT(!trc->runtime()->isHeapMinorCollecting());
+
+ AutoSuppressProfilerSampling suppressSampling(trc->runtime());
+ uint32_t gen = trc->runtime()->profilerSampleBufferGen();
+ uint32_t lapCount = trc->runtime()->profilerSampleBufferLapCount();
+
+ // If the profiler is off, all entries are considered to be expired.
+ if (!trc->runtime()->spsProfiler.enabled())
+ gen = UINT32_MAX;
+
+ bool markedAny = false;
+ for (Range r(*this); !r.empty(); r.popFront()) {
+ JitcodeGlobalEntry* entry = r.front();
+
+ // If an entry is not sampled, reset its generation to the invalid
+ // generation, and conditionally mark the rest of the entry if its
+ // JitCode is not already marked. This conditional marking ensures
+ // that so long as the JitCode *may* be sampled, we keep any
+ // information that may be handed out to the sampler, like tracked
+ // types used by optimizations and scripts used for pc to line number
+ // mapping, alive as well.
+ if (!entry->isSampled(gen, lapCount)) {
+ entry->setAsExpired();
+ if (!entry->baseEntry().isJitcodeMarkedFromAnyThread(trc->runtime()))
+ continue;
+ }
+
+ // The table is runtime-wide. Not all zones may be participating in
+ // the GC.
+ if (!entry->zone()->isCollecting() || entry->zone()->isGCFinished())
+ continue;
+
+ markedAny |= entry->mark<IfUnmarked>(trc);
+ }
+
+ return markedAny;
+}
+
+void
+JitcodeGlobalTable::sweep(JSRuntime* rt)
+{
+ AutoSuppressProfilerSampling suppressSampling(rt);
+ for (Enum e(*this, rt); !e.empty(); e.popFront()) {
+ JitcodeGlobalEntry* entry = e.front();
+
+ if (!entry->zone()->isCollecting() || entry->zone()->isGCFinished())
+ continue;
+
+ if (entry->baseEntry().isJitcodeAboutToBeFinalized())
+ e.removeFront();
+ else
+ entry->sweepChildren(rt);
+ }
+}
+
+template <class ShouldMarkProvider>
+bool
+JitcodeGlobalEntry::BaseEntry::markJitcode(JSTracer* trc)
+{
+ if (ShouldMarkProvider::ShouldMark(trc->runtime(), &jitcode_)) {
+ TraceManuallyBarrieredEdge(trc, &jitcode_, "jitcodglobaltable-baseentry-jitcode");
+ return true;
+ }
+ return false;
+}
+
+bool
+JitcodeGlobalEntry::BaseEntry::isJitcodeMarkedFromAnyThread(JSRuntime* rt)
+{
+ return IsMarkedUnbarriered(rt, &jitcode_) ||
+ jitcode_->arena()->allocatedDuringIncremental;
+}
+
+bool
+JitcodeGlobalEntry::BaseEntry::isJitcodeAboutToBeFinalized()
+{
+ return IsAboutToBeFinalizedUnbarriered(&jitcode_);
+}
+
+template <class ShouldMarkProvider>
+bool
+JitcodeGlobalEntry::BaselineEntry::mark(JSTracer* trc)
+{
+ if (ShouldMarkProvider::ShouldMark(trc->runtime(), &script_)) {
+ TraceManuallyBarrieredEdge(trc, &script_, "jitcodeglobaltable-baselineentry-script");
+ return true;
+ }
+ return false;
+}
+
+void
+JitcodeGlobalEntry::BaselineEntry::sweepChildren()
+{
+ MOZ_ALWAYS_FALSE(IsAboutToBeFinalizedUnbarriered(&script_));
+}
+
+bool
+JitcodeGlobalEntry::BaselineEntry::isMarkedFromAnyThread(JSRuntime* rt)
+{
+ return IsMarkedUnbarriered(rt, &script_) ||
+ script_->arena()->allocatedDuringIncremental;
+}
+
+template <class ShouldMarkProvider>
+bool
+JitcodeGlobalEntry::IonEntry::mark(JSTracer* trc)
+{
+ bool markedAny = false;
+
+ JSRuntime* rt = trc->runtime();
+ for (unsigned i = 0; i < numScripts(); i++) {
+ if (ShouldMarkProvider::ShouldMark(rt, &sizedScriptList()->pairs[i].script)) {
+ TraceManuallyBarrieredEdge(trc, &sizedScriptList()->pairs[i].script,
+ "jitcodeglobaltable-ionentry-script");
+ markedAny = true;
+ }
+ }
+
+ if (!optsAllTypes_)
+ return markedAny;
+
+ for (IonTrackedTypeWithAddendum* iter = optsAllTypes_->begin();
+ iter != optsAllTypes_->end(); iter++)
+ {
+ if (ShouldMarkProvider::ShouldMark(rt, &iter->type)) {
+ iter->type.trace(trc);
+ markedAny = true;
+ }
+ if (iter->hasAllocationSite() && ShouldMarkProvider::ShouldMark(rt, &iter->script)) {
+ TraceManuallyBarrieredEdge(trc, &iter->script,
+ "jitcodeglobaltable-ionentry-type-addendum-script");
+ markedAny = true;
+ } else if (iter->hasConstructor() && ShouldMarkProvider::ShouldMark(rt, &iter->constructor)) {
+ TraceManuallyBarrieredEdge(trc, &iter->constructor,
+ "jitcodeglobaltable-ionentry-type-addendum-constructor");
+ markedAny = true;
+ }
+ }
+
+ return markedAny;
+}
+
+void
+JitcodeGlobalEntry::IonEntry::sweepChildren()
+{
+ for (unsigned i = 0; i < numScripts(); i++)
+ MOZ_ALWAYS_FALSE(IsAboutToBeFinalizedUnbarriered(&sizedScriptList()->pairs[i].script));
+
+ if (!optsAllTypes_)
+ return;
+
+ for (IonTrackedTypeWithAddendum* iter = optsAllTypes_->begin();
+ iter != optsAllTypes_->end(); iter++)
+ {
+ // Types may move under compacting GC. This method is only called on
+ // entries that are sampled, and thus are not about to be finalized.
+ MOZ_ALWAYS_FALSE(TypeSet::IsTypeAboutToBeFinalized(&iter->type));
+ if (iter->hasAllocationSite())
+ MOZ_ALWAYS_FALSE(IsAboutToBeFinalizedUnbarriered(&iter->script));
+ else if (iter->hasConstructor())
+ MOZ_ALWAYS_FALSE(IsAboutToBeFinalizedUnbarriered(&iter->constructor));
+ }
+}
+
+bool
+JitcodeGlobalEntry::IonEntry::isMarkedFromAnyThread(JSRuntime* rt)
+{
+ for (unsigned i = 0; i < numScripts(); i++) {
+ if (!IsMarkedUnbarriered(rt, &sizedScriptList()->pairs[i].script) &&
+ !sizedScriptList()->pairs[i].script->arena()->allocatedDuringIncremental)
+ {
+ return false;
+ }
+ }
+
+ if (!optsAllTypes_)
+ return true;
+
+ for (IonTrackedTypeWithAddendum* iter = optsAllTypes_->begin();
+ iter != optsAllTypes_->end(); iter++)
+ {
+ if (!TypeSet::IsTypeMarked(rt, &iter->type) &&
+ !TypeSet::IsTypeAllocatedDuringIncremental(iter->type))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <class ShouldMarkProvider>
+bool
+JitcodeGlobalEntry::IonCacheEntry::mark(JSTracer* trc)
+{
+ JitcodeGlobalEntry& entry = RejoinEntry(trc->runtime(), *this, nativeStartAddr());
+ return entry.mark<ShouldMarkProvider>(trc);
+}
+
+void
+JitcodeGlobalEntry::IonCacheEntry::sweepChildren(JSRuntime* rt)
+{
+ JitcodeGlobalEntry& entry = RejoinEntry(rt, *this, nativeStartAddr());
+ entry.sweepChildren(rt);
+}
+
+bool
+JitcodeGlobalEntry::IonCacheEntry::isMarkedFromAnyThread(JSRuntime* rt)
+{
+ JitcodeGlobalEntry& entry = RejoinEntry(rt, *this, nativeStartAddr());
+ return entry.isMarkedFromAnyThread(rt);
+}
+
+Maybe<uint8_t>
+JitcodeGlobalEntry::IonCacheEntry::trackedOptimizationIndexAtAddr(
+ JSRuntime *rt,
+ void* ptr,
+ uint32_t* entryOffsetOut)
+{
+ MOZ_ASSERT(hasTrackedOptimizations());
+ MOZ_ASSERT(containsPointer(ptr));
+ JitcodeGlobalEntry& entry = RejoinEntry(rt, *this, ptr);
+
+ if (!entry.hasTrackedOptimizations())
+ return mozilla::Nothing();
+
+ uint32_t mainEntryOffsetOut;
+ Maybe<uint8_t> maybeIndex =
+ entry.trackedOptimizationIndexAtAddr(rt, rejoinAddr(), &mainEntryOffsetOut);
+ if (maybeIndex.isNothing())
+ return mozilla::Nothing();
+
+ // For IonCache, the canonical address is just the start of the addr.
+ *entryOffsetOut = 0;
+ return maybeIndex;
+}
+
+void
+JitcodeGlobalEntry::IonCacheEntry::forEachOptimizationAttempt(
+ JSRuntime *rt, uint8_t index, JS::ForEachTrackedOptimizationAttemptOp& op)
+{
+ JitcodeGlobalEntry& entry = RejoinEntry(rt, *this, nativeStartAddr());
+ if (!entry.hasTrackedOptimizations())
+ return;
+ entry.forEachOptimizationAttempt(rt, index, op);
+
+ // Record the outcome associated with the stub.
+ op(JS::TrackedStrategy::InlineCache_OptimizedStub, trackedOutcome_);
+}
+
+void
+JitcodeGlobalEntry::IonCacheEntry::forEachOptimizationTypeInfo(
+ JSRuntime *rt, uint8_t index,
+ IonTrackedOptimizationsTypeInfo::ForEachOpAdapter& op)
+{
+ JitcodeGlobalEntry& entry = RejoinEntry(rt, *this, nativeStartAddr());
+ if (!entry.hasTrackedOptimizations())
+ return;
+ entry.forEachOptimizationTypeInfo(rt, index, op);
+}
+
+/* static */ void
+JitcodeRegionEntry::WriteHead(CompactBufferWriter& writer,
+ uint32_t nativeOffset, uint8_t scriptDepth)
+{
+ writer.writeUnsigned(nativeOffset);
+ writer.writeByte(scriptDepth);
+}
+
+/* static */ void
+JitcodeRegionEntry::ReadHead(CompactBufferReader& reader,
+ uint32_t* nativeOffset, uint8_t* scriptDepth)
+{
+ *nativeOffset = reader.readUnsigned();
+ *scriptDepth = reader.readByte();
+}
+
+/* static */ void
+JitcodeRegionEntry::WriteScriptPc(CompactBufferWriter& writer,
+ uint32_t scriptIdx, uint32_t pcOffset)
+{
+ writer.writeUnsigned(scriptIdx);
+ writer.writeUnsigned(pcOffset);
+}
+
+/* static */ void
+JitcodeRegionEntry::ReadScriptPc(CompactBufferReader& reader,
+ uint32_t* scriptIdx, uint32_t* pcOffset)
+{
+ *scriptIdx = reader.readUnsigned();
+ *pcOffset = reader.readUnsigned();
+}
+
+/* static */ void
+JitcodeRegionEntry::WriteDelta(CompactBufferWriter& writer,
+ uint32_t nativeDelta, int32_t pcDelta)
+{
+ if (pcDelta >= 0) {
+ // 1 and 2-byte formats possible.
+
+ // NNNN-BBB0
+ if (pcDelta <= ENC1_PC_DELTA_MAX && nativeDelta <= ENC1_NATIVE_DELTA_MAX) {
+ uint8_t encVal = ENC1_MASK_VAL | (pcDelta << ENC1_PC_DELTA_SHIFT) |
+ (nativeDelta << ENC1_NATIVE_DELTA_SHIFT);
+ writer.writeByte(encVal);
+ return;
+ }
+
+ // NNNN-NNNN BBBB-BB01
+ if (pcDelta <= ENC2_PC_DELTA_MAX && nativeDelta <= ENC2_NATIVE_DELTA_MAX) {
+ uint16_t encVal = ENC2_MASK_VAL | (pcDelta << ENC2_PC_DELTA_SHIFT) |
+ (nativeDelta << ENC2_NATIVE_DELTA_SHIFT);
+ writer.writeByte(encVal & 0xff);
+ writer.writeByte((encVal >> 8) & 0xff);
+ return;
+ }
+ }
+
+ // NNNN-NNNN NNNB-BBBB BBBB-B011
+ if (pcDelta >= ENC3_PC_DELTA_MIN && pcDelta <= ENC3_PC_DELTA_MAX &&
+ nativeDelta <= ENC3_NATIVE_DELTA_MAX)
+ {
+ uint32_t encVal = ENC3_MASK_VAL |
+ ((pcDelta << ENC3_PC_DELTA_SHIFT) & ENC3_PC_DELTA_MASK) |
+ (nativeDelta << ENC3_NATIVE_DELTA_SHIFT);
+ writer.writeByte(encVal & 0xff);
+ writer.writeByte((encVal >> 8) & 0xff);
+ writer.writeByte((encVal >> 16) & 0xff);
+ return;
+ }
+
+ // NNNN-NNNN NNNN-NNNN BBBB-BBBB BBBB-B111
+ if (pcDelta >= ENC4_PC_DELTA_MIN && pcDelta <= ENC4_PC_DELTA_MAX &&
+ nativeDelta <= ENC4_NATIVE_DELTA_MAX)
+ {
+ uint32_t encVal = ENC4_MASK_VAL |
+ ((pcDelta << ENC4_PC_DELTA_SHIFT) & ENC4_PC_DELTA_MASK) |
+ (nativeDelta << ENC4_NATIVE_DELTA_SHIFT);
+ writer.writeByte(encVal & 0xff);
+ writer.writeByte((encVal >> 8) & 0xff);
+ writer.writeByte((encVal >> 16) & 0xff);
+ writer.writeByte((encVal >> 24) & 0xff);
+ return;
+ }
+
+ // Should never get here.
+ MOZ_CRASH("pcDelta/nativeDelta values are too large to encode.");
+}
+
+/* static */ void
+JitcodeRegionEntry::ReadDelta(CompactBufferReader& reader,
+ uint32_t* nativeDelta, int32_t* pcDelta)
+{
+ // NB:
+ // It's possible to get nativeDeltas with value 0 in two cases:
+ //
+ // 1. The last region's run. This is because the region table's start
+ // must be 4-byte aligned, and we must insert padding bytes to align the
+ // payload section before emitting the table.
+ //
+ // 2. A zero-offset nativeDelta with a negative pcDelta.
+ //
+ // So if nativeDelta is zero, then pcDelta must be <= 0.
+
+ // NNNN-BBB0
+ const uint32_t firstByte = reader.readByte();
+ if ((firstByte & ENC1_MASK) == ENC1_MASK_VAL) {
+ uint32_t encVal = firstByte;
+ *nativeDelta = encVal >> ENC1_NATIVE_DELTA_SHIFT;
+ *pcDelta = (encVal & ENC1_PC_DELTA_MASK) >> ENC1_PC_DELTA_SHIFT;
+ MOZ_ASSERT_IF(*nativeDelta == 0, *pcDelta <= 0);
+ return;
+ }
+
+ // NNNN-NNNN BBBB-BB01
+ const uint32_t secondByte = reader.readByte();
+ if ((firstByte & ENC2_MASK) == ENC2_MASK_VAL) {
+ uint32_t encVal = firstByte | secondByte << 8;
+ *nativeDelta = encVal >> ENC2_NATIVE_DELTA_SHIFT;
+ *pcDelta = (encVal & ENC2_PC_DELTA_MASK) >> ENC2_PC_DELTA_SHIFT;
+ MOZ_ASSERT(*pcDelta != 0);
+ MOZ_ASSERT_IF(*nativeDelta == 0, *pcDelta <= 0);
+ return;
+ }
+
+ // NNNN-NNNN NNNB-BBBB BBBB-B011
+ const uint32_t thirdByte = reader.readByte();
+ if ((firstByte & ENC3_MASK) == ENC3_MASK_VAL) {
+ uint32_t encVal = firstByte | secondByte << 8 | thirdByte << 16;
+ *nativeDelta = encVal >> ENC3_NATIVE_DELTA_SHIFT;
+
+ uint32_t pcDeltaU = (encVal & ENC3_PC_DELTA_MASK) >> ENC3_PC_DELTA_SHIFT;
+ // Fix sign if necessary.
+ if (pcDeltaU > static_cast<uint32_t>(ENC3_PC_DELTA_MAX))
+ pcDeltaU |= ~ENC3_PC_DELTA_MAX;
+ *pcDelta = pcDeltaU;
+ MOZ_ASSERT(*pcDelta != 0);
+ MOZ_ASSERT_IF(*nativeDelta == 0, *pcDelta <= 0);
+ return;
+ }
+
+ // NNNN-NNNN NNNN-NNNN BBBB-BBBB BBBB-B111
+ MOZ_ASSERT((firstByte & ENC4_MASK) == ENC4_MASK_VAL);
+ const uint32_t fourthByte = reader.readByte();
+ uint32_t encVal = firstByte | secondByte << 8 | thirdByte << 16 | fourthByte << 24;
+ *nativeDelta = encVal >> ENC4_NATIVE_DELTA_SHIFT;
+
+ uint32_t pcDeltaU = (encVal & ENC4_PC_DELTA_MASK) >> ENC4_PC_DELTA_SHIFT;
+ // fix sign if necessary
+ if (pcDeltaU > static_cast<uint32_t>(ENC4_PC_DELTA_MAX))
+ pcDeltaU |= ~ENC4_PC_DELTA_MAX;
+ *pcDelta = pcDeltaU;
+
+ MOZ_ASSERT(*pcDelta != 0);
+ MOZ_ASSERT_IF(*nativeDelta == 0, *pcDelta <= 0);
+}
+
+/* static */ uint32_t
+JitcodeRegionEntry::ExpectedRunLength(const CodeGeneratorShared::NativeToBytecode* entry,
+ const CodeGeneratorShared::NativeToBytecode* end)
+{
+ MOZ_ASSERT(entry < end);
+
+ // We always use the first entry, so runLength starts at 1
+ uint32_t runLength = 1;
+
+ uint32_t curNativeOffset = entry->nativeOffset.offset();
+ uint32_t curBytecodeOffset = entry->tree->script()->pcToOffset(entry->pc);
+
+ for (auto nextEntry = entry + 1; nextEntry != end; nextEntry += 1) {
+ // If the next run moves to a different inline site, stop the run.
+ if (nextEntry->tree != entry->tree)
+ break;
+
+ uint32_t nextNativeOffset = nextEntry->nativeOffset.offset();
+ uint32_t nextBytecodeOffset = nextEntry->tree->script()->pcToOffset(nextEntry->pc);
+ MOZ_ASSERT(nextNativeOffset >= curNativeOffset);
+
+ uint32_t nativeDelta = nextNativeOffset - curNativeOffset;
+ int32_t bytecodeDelta = int32_t(nextBytecodeOffset) - int32_t(curBytecodeOffset);
+
+ // If deltas are too large (very unlikely), stop the run.
+ if (!IsDeltaEncodeable(nativeDelta, bytecodeDelta))
+ break;
+
+ runLength++;
+
+ // If the run has grown to its maximum length, stop the run.
+ if (runLength == MAX_RUN_LENGTH)
+ break;
+
+ curNativeOffset = nextNativeOffset;
+ curBytecodeOffset = nextBytecodeOffset;
+ }
+
+ return runLength;
+}
+
+struct JitcodeMapBufferWriteSpewer
+{
+#ifdef JS_JITSPEW
+ CompactBufferWriter* writer;
+ uint32_t startPos;
+
+ static const uint32_t DumpMaxBytes = 50;
+
+ explicit JitcodeMapBufferWriteSpewer(CompactBufferWriter& w)
+ : writer(&w), startPos(writer->length())
+ {}
+
+ void spewAndAdvance(const char* name) {
+ if (writer->oom())
+ return;
+
+ uint32_t curPos = writer->length();
+ const uint8_t* start = writer->buffer() + startPos;
+ const uint8_t* end = writer->buffer() + curPos;
+ const char* MAP = "0123456789ABCDEF";
+ uint32_t bytes = end - start;
+
+ char buffer[DumpMaxBytes * 3];
+ for (uint32_t i = 0; i < bytes; i++) {
+ buffer[i*3] = MAP[(start[i] >> 4) & 0xf];
+ buffer[i*3 + 1] = MAP[(start[i] >> 0) & 0xf];
+ buffer[i*3 + 2] = ' ';
+ }
+ if (bytes >= DumpMaxBytes)
+ buffer[DumpMaxBytes*3 - 1] = '\0';
+ else
+ buffer[bytes*3 - 1] = '\0';
+
+ JitSpew(JitSpew_Profiling, "%s@%d[%d bytes] - %s", name, int(startPos), int(bytes), buffer);
+
+ // Move to the end of the current buffer.
+ startPos = writer->length();
+ }
+#else // !JS_JITSPEW
+ explicit JitcodeMapBufferWriteSpewer(CompactBufferWriter& w) {}
+ void spewAndAdvance(const char* name) {}
+#endif // JS_JITSPEW
+};
+
+// Write a run, starting at the given NativeToBytecode entry, into the given buffer writer.
+/* static */ bool
+JitcodeRegionEntry::WriteRun(CompactBufferWriter& writer,
+ JSScript** scriptList, uint32_t scriptListSize,
+ uint32_t runLength, const CodeGeneratorShared::NativeToBytecode* entry)
+{
+ MOZ_ASSERT(runLength > 0);
+ MOZ_ASSERT(runLength <= MAX_RUN_LENGTH);
+
+ // Calculate script depth.
+ MOZ_ASSERT(entry->tree->depth() <= 0xff);
+ uint8_t scriptDepth = entry->tree->depth();
+ uint32_t regionNativeOffset = entry->nativeOffset.offset();
+
+ JitcodeMapBufferWriteSpewer spewer(writer);
+
+ // Write the head info.
+ JitSpew(JitSpew_Profiling, " Head Info: nativeOffset=%d scriptDepth=%d",
+ int(regionNativeOffset), int(scriptDepth));
+ WriteHead(writer, regionNativeOffset, scriptDepth);
+ spewer.spewAndAdvance(" ");
+
+ // Write each script/pc pair.
+ {
+ InlineScriptTree* curTree = entry->tree;
+ jsbytecode* curPc = entry->pc;
+ for (uint8_t i = 0; i < scriptDepth; i++) {
+ // Find the index of the script within the list.
+ // NB: scriptList is guaranteed to contain curTree->script()
+ uint32_t scriptIdx = 0;
+ for (; scriptIdx < scriptListSize; scriptIdx++) {
+ if (scriptList[scriptIdx] == curTree->script())
+ break;
+ }
+ MOZ_ASSERT(scriptIdx < scriptListSize);
+
+ uint32_t pcOffset = curTree->script()->pcToOffset(curPc);
+
+ JitSpew(JitSpew_Profiling, " Script/PC %d: scriptIdx=%d pcOffset=%d",
+ int(i), int(scriptIdx), int(pcOffset));
+ WriteScriptPc(writer, scriptIdx, pcOffset);
+ spewer.spewAndAdvance(" ");
+
+ MOZ_ASSERT_IF(i < scriptDepth - 1, curTree->hasCaller());
+ curPc = curTree->callerPc();
+ curTree = curTree->caller();
+ }
+ }
+
+ // Start writing runs.
+ uint32_t curNativeOffset = entry->nativeOffset.offset();
+ uint32_t curBytecodeOffset = entry->tree->script()->pcToOffset(entry->pc);
+
+ JitSpew(JitSpew_Profiling, " Writing Delta Run from nativeOffset=%d bytecodeOffset=%d",
+ int(curNativeOffset), int(curBytecodeOffset));
+
+ // Skip first entry because it is implicit in the header. Start at subsequent entry.
+ for (uint32_t i = 1; i < runLength; i++) {
+ MOZ_ASSERT(entry[i].tree == entry->tree);
+
+ uint32_t nextNativeOffset = entry[i].nativeOffset.offset();
+ uint32_t nextBytecodeOffset = entry[i].tree->script()->pcToOffset(entry[i].pc);
+ MOZ_ASSERT(nextNativeOffset >= curNativeOffset);
+
+ uint32_t nativeDelta = nextNativeOffset - curNativeOffset;
+ int32_t bytecodeDelta = int32_t(nextBytecodeOffset) - int32_t(curBytecodeOffset);
+ MOZ_ASSERT(IsDeltaEncodeable(nativeDelta, bytecodeDelta));
+
+ JitSpew(JitSpew_Profiling, " RunEntry native: %d-%d [%d] bytecode: %d-%d [%d]",
+ int(curNativeOffset), int(nextNativeOffset), int(nativeDelta),
+ int(curBytecodeOffset), int(nextBytecodeOffset), int(bytecodeDelta));
+ WriteDelta(writer, nativeDelta, bytecodeDelta);
+
+ // Spew the bytecode in these ranges.
+ if (curBytecodeOffset < nextBytecodeOffset) {
+ JitSpewStart(JitSpew_Profiling, " OPS: ");
+ uint32_t curBc = curBytecodeOffset;
+ while (curBc < nextBytecodeOffset) {
+ jsbytecode* pc = entry[i].tree->script()->offsetToPC(curBc);
+#ifdef JS_JITSPEW
+ JSOp op = JSOp(*pc);
+ JitSpewCont(JitSpew_Profiling, "%s ", CodeName[op]);
+#endif
+ curBc += GetBytecodeLength(pc);
+ }
+ JitSpewFin(JitSpew_Profiling);
+ }
+ spewer.spewAndAdvance(" ");
+
+ curNativeOffset = nextNativeOffset;
+ curBytecodeOffset = nextBytecodeOffset;
+ }
+
+ if (writer.oom())
+ return false;
+
+ return true;
+}
+
+void
+JitcodeRegionEntry::unpack()
+{
+ CompactBufferReader reader(data_, end_);
+ ReadHead(reader, &nativeOffset_, &scriptDepth_);
+ MOZ_ASSERT(scriptDepth_ > 0);
+
+ scriptPcStack_ = reader.currentPosition();
+ // Skip past script/pc stack
+ for (unsigned i = 0; i < scriptDepth_; i++) {
+ uint32_t scriptIdx, pcOffset;
+ ReadScriptPc(reader, &scriptIdx, &pcOffset);
+ }
+
+ deltaRun_ = reader.currentPosition();
+}
+
+uint32_t
+JitcodeRegionEntry::findPcOffset(uint32_t queryNativeOffset, uint32_t startPcOffset) const
+{
+ DeltaIterator iter = deltaIterator();
+ uint32_t curNativeOffset = nativeOffset();
+ uint32_t curPcOffset = startPcOffset;
+ while (iter.hasMore()) {
+ uint32_t nativeDelta;
+ int32_t pcDelta;
+ iter.readNext(&nativeDelta, &pcDelta);
+
+ // The start address of the next delta-run entry is counted towards
+ // the current delta-run entry, because return addresses should
+ // associate with the bytecode op prior (the call) not the op after.
+ if (queryNativeOffset <= curNativeOffset + nativeDelta)
+ break;
+ curNativeOffset += nativeDelta;
+ curPcOffset += pcDelta;
+ }
+ return curPcOffset;
+}
+
+typedef js::Vector<char*, 32, SystemAllocPolicy> ProfilingStringVector;
+
+struct AutoFreeProfilingStrings {
+ ProfilingStringVector& profilingStrings_;
+ bool keep_;
+ explicit AutoFreeProfilingStrings(ProfilingStringVector& vec)
+ : profilingStrings_(vec),
+ keep_(false)
+ {}
+
+ void keepStrings() { keep_ = true; }
+
+ ~AutoFreeProfilingStrings() {
+ if (keep_)
+ return;
+ for (size_t i = 0; i < profilingStrings_.length(); i++)
+ js_free(profilingStrings_[i]);
+ }
+};
+
+bool
+JitcodeIonTable::makeIonEntry(JSContext* cx, JitCode* code,
+ uint32_t numScripts, JSScript** scripts,
+ JitcodeGlobalEntry::IonEntry& out)
+{
+ typedef JitcodeGlobalEntry::IonEntry::SizedScriptList SizedScriptList;
+
+ MOZ_ASSERT(numScripts > 0);
+
+ // Create profiling strings for script, within vector.
+ typedef js::Vector<char*, 32, SystemAllocPolicy> ProfilingStringVector;
+
+ ProfilingStringVector profilingStrings;
+ if (!profilingStrings.reserve(numScripts))
+ return false;
+
+ AutoFreeProfilingStrings autoFreeProfilingStrings(profilingStrings);
+ for (uint32_t i = 0; i < numScripts; i++) {
+ char* str = JitcodeGlobalEntry::createScriptString(cx, scripts[i]);
+ if (!str)
+ return false;
+ if (!profilingStrings.append(str))
+ return false;
+ }
+
+ // Create SizedScriptList
+ void* mem = (void*)cx->pod_malloc<uint8_t>(SizedScriptList::AllocSizeFor(numScripts));
+ if (!mem)
+ return false;
+
+ // Keep allocated profiling strings on destruct.
+ autoFreeProfilingStrings.keepStrings();
+
+ SizedScriptList* scriptList = new (mem) SizedScriptList(numScripts, scripts,
+ &profilingStrings[0]);
+ out.init(code, code->raw(), code->rawEnd(), scriptList, this);
+ return true;
+}
+
+uint32_t
+JitcodeIonTable::findRegionEntry(uint32_t nativeOffset) const
+{
+ static const uint32_t LINEAR_SEARCH_THRESHOLD = 8;
+ uint32_t regions = numRegions();
+ MOZ_ASSERT(regions > 0);
+
+ // For small region lists, just search linearly.
+ if (regions <= LINEAR_SEARCH_THRESHOLD) {
+ JitcodeRegionEntry previousEntry = regionEntry(0);
+ for (uint32_t i = 1; i < regions; i++) {
+ JitcodeRegionEntry nextEntry = regionEntry(i);
+ MOZ_ASSERT(nextEntry.nativeOffset() >= previousEntry.nativeOffset());
+
+ // See note in binary-search code below about why we use '<=' here instead of
+ // '<'. Short explanation: regions are closed at their ending addresses,
+ // and open at their starting addresses.
+ if (nativeOffset <= nextEntry.nativeOffset())
+ return i-1;
+
+ previousEntry = nextEntry;
+ }
+ // If nothing found, assume it falls within last region.
+ return regions - 1;
+ }
+
+ // For larger ones, binary search the region table.
+ uint32_t idx = 0;
+ uint32_t count = regions;
+ while (count > 1) {
+ uint32_t step = count/2;
+ uint32_t mid = idx + step;
+ JitcodeRegionEntry midEntry = regionEntry(mid);
+
+ // A region memory range is closed at its ending address, not starting
+ // address. This is because the return address for calls must associate
+ // with the call's bytecode PC, not the PC of the bytecode operator after
+ // the call.
+ //
+ // So a query is < an entry if the query nativeOffset is <= the start address
+ // of the entry, and a query is >= an entry if the query nativeOffset is > the
+ // start address of an entry.
+ if (nativeOffset <= midEntry.nativeOffset()) {
+ // Target entry is below midEntry.
+ count = step;
+ } else { // if (nativeOffset > midEntry.nativeOffset())
+ // Target entry is at midEntry or above.
+ idx = mid;
+ count -= step;
+ }
+ }
+ return idx;
+}
+
+/* static */ bool
+JitcodeIonTable::WriteIonTable(CompactBufferWriter& writer,
+ JSScript** scriptList, uint32_t scriptListSize,
+ const CodeGeneratorShared::NativeToBytecode* start,
+ const CodeGeneratorShared::NativeToBytecode* end,
+ uint32_t* tableOffsetOut, uint32_t* numRegionsOut)
+{
+ MOZ_ASSERT(tableOffsetOut != nullptr);
+ MOZ_ASSERT(numRegionsOut != nullptr);
+ MOZ_ASSERT(writer.length() == 0);
+ MOZ_ASSERT(scriptListSize > 0);
+
+ JitSpew(JitSpew_Profiling, "Writing native to bytecode map for %s:%" PRIuSIZE " (%" PRIuSIZE " entries)",
+ scriptList[0]->filename(), scriptList[0]->lineno(),
+ mozilla::PointerRangeSize(start, end));
+
+ JitSpew(JitSpew_Profiling, " ScriptList of size %d", int(scriptListSize));
+ for (uint32_t i = 0; i < scriptListSize; i++) {
+ JitSpew(JitSpew_Profiling, " Script %d - %s:%" PRIuSIZE,
+ int(i), scriptList[i]->filename(), scriptList[i]->lineno());
+ }
+
+ // Write out runs first. Keep a vector tracking the positive offsets from payload
+ // start to the run.
+ const CodeGeneratorShared::NativeToBytecode* curEntry = start;
+ js::Vector<uint32_t, 32, SystemAllocPolicy> runOffsets;
+
+ while (curEntry != end) {
+ // Calculate the length of the next run.
+ uint32_t runLength = JitcodeRegionEntry::ExpectedRunLength(curEntry, end);
+ MOZ_ASSERT(runLength > 0);
+ MOZ_ASSERT(runLength <= uintptr_t(end - curEntry));
+ JitSpew(JitSpew_Profiling, " Run at entry %d, length %d, buffer offset %d",
+ int(curEntry - start), int(runLength), int(writer.length()));
+
+ // Store the offset of the run.
+ if (!runOffsets.append(writer.length()))
+ return false;
+
+ // Encode the run.
+ if (!JitcodeRegionEntry::WriteRun(writer, scriptList, scriptListSize, runLength, curEntry))
+ return false;
+
+ curEntry += runLength;
+ }
+
+ // Done encoding regions. About to start table. Ensure we are aligned to 4 bytes
+ // since table is composed of uint32_t values.
+ uint32_t padding = sizeof(uint32_t) - (writer.length() % sizeof(uint32_t));
+ if (padding == sizeof(uint32_t))
+ padding = 0;
+ JitSpew(JitSpew_Profiling, " Padding %d bytes after run @%d",
+ int(padding), int(writer.length()));
+ for (uint32_t i = 0; i < padding; i++)
+ writer.writeByte(0);
+
+ // Now at start of table.
+ uint32_t tableOffset = writer.length();
+
+ // The table being written at this point will be accessed directly via uint32_t
+ // pointers, so all writes below use native endianness.
+
+ // Write out numRegions
+ JitSpew(JitSpew_Profiling, " Writing numRuns=%d", int(runOffsets.length()));
+ writer.writeNativeEndianUint32_t(runOffsets.length());
+
+ // Write out region offset table. The offsets in |runOffsets| are currently forward
+ // offsets from the beginning of the buffer. We convert them to backwards offsets
+ // from the start of the table before writing them into their table entries.
+ for (uint32_t i = 0; i < runOffsets.length(); i++) {
+ JitSpew(JitSpew_Profiling, " Run %d offset=%d backOffset=%d @%d",
+ int(i), int(runOffsets[i]), int(tableOffset - runOffsets[i]), int(writer.length()));
+ writer.writeNativeEndianUint32_t(tableOffset - runOffsets[i]);
+ }
+
+ if (writer.oom())
+ return false;
+
+ *tableOffsetOut = tableOffset;
+ *numRegionsOut = runOffsets.length();
+ return true;
+}
+
+
+} // namespace jit
+} // namespace js
+
+JS::ForEachProfiledFrameOp::FrameHandle::FrameHandle(JSRuntime* rt, js::jit::JitcodeGlobalEntry& entry,
+ void* addr, const char* label, uint32_t depth)
+ : rt_(rt),
+ entry_(entry),
+ addr_(addr),
+ canonicalAddr_(nullptr),
+ label_(label),
+ depth_(depth),
+ optsIndex_()
+{
+ updateHasTrackedOptimizations();
+
+ if (!canonicalAddr_) {
+ // If the entry has tracked optimizations, updateHasTrackedOptimizations
+ // would have updated the canonical address.
+ MOZ_ASSERT_IF(entry_.isIon(), !hasTrackedOptimizations());
+ canonicalAddr_ = entry_.canonicalNativeAddrFor(rt_, addr_);
+ }
+}
+
+JS_PUBLIC_API(JS::ProfilingFrameIterator::FrameKind)
+JS::ForEachProfiledFrameOp::FrameHandle::frameKind() const
+{
+ if (entry_.isBaseline())
+ return JS::ProfilingFrameIterator::Frame_Baseline;
+ return JS::ProfilingFrameIterator::Frame_Ion;
+}
+
+JS_PUBLIC_API(void)
+JS::ForEachProfiledFrame(JSContext* cx, void* addr, ForEachProfiledFrameOp& op)
+{
+ js::jit::JitcodeGlobalTable* table = cx->jitRuntime()->getJitcodeGlobalTable();
+ js::jit::JitcodeGlobalEntry& entry = table->lookupInfallible(addr);
+
+ // Extract the stack for the entry. Assume maximum inlining depth is <64
+ const char* labels[64];
+ uint32_t depth = entry.callStackAtAddr(cx, addr, labels, 64);
+ MOZ_ASSERT(depth < 64);
+ for (uint32_t i = depth; i != 0; i--) {
+ JS::ForEachProfiledFrameOp::FrameHandle handle(cx, entry, addr, labels[i - 1], i - 1);
+ op(handle);
+ }
+}