summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmCode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/wasm/WasmCode.cpp')
-rw-r--r--js/src/wasm/WasmCode.cpp1510
1 files changed, 1510 insertions, 0 deletions
diff --git a/js/src/wasm/WasmCode.cpp b/js/src/wasm/WasmCode.cpp
new file mode 100644
index 0000000000..50a6f20aab
--- /dev/null
+++ b/js/src/wasm/WasmCode.cpp
@@ -0,0 +1,1510 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ *
+ * Copyright 2016 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "wasm/WasmCode.h"
+
+#include "mozilla/BinarySearch.h"
+#include "mozilla/EnumeratedRange.h"
+
+#include <algorithm>
+
+#include "jsnum.h"
+
+#include "jit/ExecutableAllocator.h"
+#ifdef JS_ION_PERF
+# include "jit/PerfSpewer.h"
+#endif
+#include "util/Poison.h"
+#ifdef MOZ_VTUNE
+# include "vtune/VTuneWrapper.h"
+#endif
+#include "wasm/WasmModule.h"
+#include "wasm/WasmProcess.h"
+#include "wasm/WasmSerialize.h"
+#include "wasm/WasmStubs.h"
+
+#include "jit/MacroAssembler-inl.h"
+
+using namespace js;
+using namespace js::jit;
+using namespace js::wasm;
+using mozilla::BinarySearch;
+using mozilla::MakeEnumeratedRange;
+using mozilla::PodAssign;
+
+size_t LinkData::SymbolicLinkArray::serializedSize() const {
+ size_t size = 0;
+ for (const Uint32Vector& offsets : *this) {
+ size += SerializedPodVectorSize(offsets);
+ }
+ return size;
+}
+
+uint8_t* LinkData::SymbolicLinkArray::serialize(uint8_t* cursor) const {
+ for (const Uint32Vector& offsets : *this) {
+ cursor = SerializePodVector(cursor, offsets);
+ }
+ return cursor;
+}
+
+const uint8_t* LinkData::SymbolicLinkArray::deserialize(const uint8_t* cursor) {
+ for (Uint32Vector& offsets : *this) {
+ cursor = DeserializePodVector(cursor, &offsets);
+ if (!cursor) {
+ return nullptr;
+ }
+ }
+ return cursor;
+}
+
+size_t LinkData::SymbolicLinkArray::sizeOfExcludingThis(
+ MallocSizeOf mallocSizeOf) const {
+ size_t size = 0;
+ for (const Uint32Vector& offsets : *this) {
+ size += offsets.sizeOfExcludingThis(mallocSizeOf);
+ }
+ return size;
+}
+
+size_t LinkData::serializedSize() const {
+ return sizeof(pod()) + SerializedPodVectorSize(internalLinks) +
+ symbolicLinks.serializedSize();
+}
+
+uint8_t* LinkData::serialize(uint8_t* cursor) const {
+ MOZ_ASSERT(tier == Tier::Serialized);
+
+ cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
+ cursor = SerializePodVector(cursor, internalLinks);
+ cursor = symbolicLinks.serialize(cursor);
+ return cursor;
+}
+
+const uint8_t* LinkData::deserialize(const uint8_t* cursor) {
+ MOZ_ASSERT(tier == Tier::Serialized);
+
+ (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) &&
+ (cursor = DeserializePodVector(cursor, &internalLinks)) &&
+ (cursor = symbolicLinks.deserialize(cursor));
+ return cursor;
+}
+
+CodeSegment::~CodeSegment() {
+ if (unregisterOnDestroy_) {
+ UnregisterCodeSegment(this);
+ }
+}
+
+static uint32_t RoundupCodeLength(uint32_t codeLength) {
+ // AllocateExecutableMemory() requires a multiple of ExecutableCodePageSize.
+ return RoundUp(codeLength, ExecutableCodePageSize);
+}
+
+/* static */
+UniqueCodeBytes CodeSegment::AllocateCodeBytes(uint32_t codeLength) {
+ if (codeLength > MaxCodeBytesPerProcess) {
+ return nullptr;
+ }
+
+ static_assert(MaxCodeBytesPerProcess <= INT32_MAX, "rounding won't overflow");
+ uint32_t roundedCodeLength = RoundupCodeLength(codeLength);
+
+ void* p =
+ AllocateExecutableMemory(roundedCodeLength, ProtectionSetting::Writable,
+ MemCheckKind::MakeUndefined);
+
+ // If the allocation failed and the embedding gives us a last-ditch attempt
+ // to purge all memory (which, in gecko, does a purging GC/CC/GC), do that
+ // then retry the allocation.
+ if (!p) {
+ if (OnLargeAllocationFailure) {
+ OnLargeAllocationFailure();
+ p = AllocateExecutableMemory(roundedCodeLength,
+ ProtectionSetting::Writable,
+ MemCheckKind::MakeUndefined);
+ }
+ }
+
+ if (!p) {
+ return nullptr;
+ }
+
+ // Zero the padding.
+ memset(((uint8_t*)p) + codeLength, 0, roundedCodeLength - codeLength);
+
+ // We account for the bytes allocated in WasmModuleObject::create, where we
+ // have the necessary JSContext.
+
+ return UniqueCodeBytes((uint8_t*)p, FreeCode(roundedCodeLength));
+}
+
+bool CodeSegment::initialize(const CodeTier& codeTier) {
+ MOZ_ASSERT(!initialized());
+ codeTier_ = &codeTier;
+ MOZ_ASSERT(initialized());
+
+ // In the case of tiering, RegisterCodeSegment() immediately makes this code
+ // segment live to access from other threads executing the containing
+ // module. So only call once the CodeSegment is fully initialized.
+ if (!RegisterCodeSegment(this)) {
+ return false;
+ }
+
+ // This bool is only used by the destructor which cannot be called racily
+ // and so it is not a problem to mutate it after RegisterCodeSegment().
+ MOZ_ASSERT(!unregisterOnDestroy_);
+ unregisterOnDestroy_ = true;
+ return true;
+}
+
+const Code& CodeSegment::code() const {
+ MOZ_ASSERT(codeTier_);
+ return codeTier_->code();
+}
+
+void CodeSegment::addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code) const {
+ *code += RoundupCodeLength(length());
+}
+
+void FreeCode::operator()(uint8_t* bytes) {
+ MOZ_ASSERT(codeLength);
+ MOZ_ASSERT(codeLength == RoundupCodeLength(codeLength));
+
+#ifdef MOZ_VTUNE
+ vtune::UnmarkBytes(bytes, codeLength);
+#endif
+ DeallocateExecutableMemory(bytes, codeLength);
+}
+
+static bool StaticallyLink(const ModuleSegment& ms, const LinkData& linkData) {
+ for (LinkData::InternalLink link : linkData.internalLinks) {
+ CodeLabel label;
+ label.patchAt()->bind(link.patchAtOffset);
+ label.target()->bind(link.targetOffset);
+#ifdef JS_CODELABEL_LINKMODE
+ label.setLinkMode(static_cast<CodeLabel::LinkMode>(link.mode));
+#endif
+ Assembler::Bind(ms.base(), label);
+ }
+
+ if (!EnsureBuiltinThunksInitialized()) {
+ return false;
+ }
+
+ for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
+ const Uint32Vector& offsets = linkData.symbolicLinks[imm];
+ if (offsets.empty()) {
+ continue;
+ }
+
+ void* target = SymbolicAddressTarget(imm);
+ for (uint32_t offset : offsets) {
+ uint8_t* patchAt = ms.base() + offset;
+ Assembler::PatchDataWithValueCheck(CodeLocationLabel(patchAt),
+ PatchedImmPtr(target),
+ PatchedImmPtr((void*)-1));
+ }
+ }
+
+ return true;
+}
+
+static void StaticallyUnlink(uint8_t* base, const LinkData& linkData) {
+ for (LinkData::InternalLink link : linkData.internalLinks) {
+ CodeLabel label;
+ label.patchAt()->bind(link.patchAtOffset);
+ label.target()->bind(-size_t(base)); // to reset immediate to null
+#ifdef JS_CODELABEL_LINKMODE
+ label.setLinkMode(static_cast<CodeLabel::LinkMode>(link.mode));
+#endif
+ Assembler::Bind(base, label);
+ }
+
+ for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
+ const Uint32Vector& offsets = linkData.symbolicLinks[imm];
+ if (offsets.empty()) {
+ continue;
+ }
+
+ void* target = SymbolicAddressTarget(imm);
+ for (uint32_t offset : offsets) {
+ uint8_t* patchAt = base + offset;
+ Assembler::PatchDataWithValueCheck(CodeLocationLabel(patchAt),
+ PatchedImmPtr((void*)-1),
+ PatchedImmPtr(target));
+ }
+ }
+}
+
+#ifdef JS_ION_PERF
+static bool AppendToString(const char* str, UTF8Bytes* bytes) {
+ return bytes->append(str, strlen(str)) && bytes->append('\0');
+}
+#endif
+
+static void SendCodeRangesToProfiler(const ModuleSegment& ms,
+ const Metadata& metadata,
+ const CodeRangeVector& codeRanges) {
+ bool enabled = false;
+#ifdef JS_ION_PERF
+ enabled |= PerfFuncEnabled();
+#endif
+#ifdef MOZ_VTUNE
+ enabled |= vtune::IsProfilingActive();
+#endif
+ if (!enabled) {
+ return;
+ }
+
+ for (const CodeRange& codeRange : codeRanges) {
+ if (!codeRange.hasFuncIndex()) {
+ continue;
+ }
+
+ uintptr_t start = uintptr_t(ms.base() + codeRange.begin());
+ uintptr_t size = codeRange.end() - codeRange.begin();
+
+ UTF8Bytes name;
+ if (!metadata.getFuncNameStandalone(codeRange.funcIndex(), &name)) {
+ return;
+ }
+
+ // Avoid "unused" warnings
+ (void)start;
+ (void)size;
+
+#ifdef JS_ION_PERF
+ if (PerfFuncEnabled()) {
+ const char* file = metadata.filename.get();
+ if (codeRange.isFunction()) {
+ if (!name.append('\0')) {
+ return;
+ }
+ unsigned line = codeRange.funcLineOrBytecode();
+ writePerfSpewerWasmFunctionMap(start, size, file, line, name.begin());
+ } else if (codeRange.isInterpEntry()) {
+ if (!AppendToString(" slow entry", &name)) {
+ return;
+ }
+ writePerfSpewerWasmMap(start, size, file, name.begin());
+ } else if (codeRange.isJitEntry()) {
+ if (!AppendToString(" fast entry", &name)) {
+ return;
+ }
+ writePerfSpewerWasmMap(start, size, file, name.begin());
+ } else if (codeRange.isImportInterpExit()) {
+ if (!AppendToString(" slow exit", &name)) {
+ return;
+ }
+ writePerfSpewerWasmMap(start, size, file, name.begin());
+ } else if (codeRange.isImportJitExit()) {
+ if (!AppendToString(" fast exit", &name)) {
+ return;
+ }
+ writePerfSpewerWasmMap(start, size, file, name.begin());
+ } else {
+ MOZ_CRASH("unhandled perf hasFuncIndex type");
+ }
+ }
+#endif
+#ifdef MOZ_VTUNE
+ if (!vtune::IsProfilingActive()) {
+ continue;
+ }
+ if (!codeRange.isFunction()) {
+ continue;
+ }
+ if (!name.append('\0')) {
+ return;
+ }
+ vtune::MarkWasm(vtune::GenerateUniqueMethodID(), name.begin(), (void*)start,
+ size);
+#endif
+ }
+}
+
+ModuleSegment::ModuleSegment(Tier tier, UniqueCodeBytes codeBytes,
+ uint32_t codeLength, const LinkData& linkData)
+ : CodeSegment(std::move(codeBytes), codeLength, CodeSegment::Kind::Module),
+ tier_(tier),
+ trapCode_(base() + linkData.trapOffset) {}
+
+/* static */
+UniqueModuleSegment ModuleSegment::create(Tier tier, MacroAssembler& masm,
+ const LinkData& linkData) {
+ uint32_t codeLength = masm.bytesNeeded();
+
+ UniqueCodeBytes codeBytes = AllocateCodeBytes(codeLength);
+ if (!codeBytes) {
+ return nullptr;
+ }
+
+ masm.executableCopy(codeBytes.get());
+
+ return js::MakeUnique<ModuleSegment>(tier, std::move(codeBytes), codeLength,
+ linkData);
+}
+
+/* static */
+UniqueModuleSegment ModuleSegment::create(Tier tier, const Bytes& unlinkedBytes,
+ const LinkData& linkData) {
+ uint32_t codeLength = unlinkedBytes.length();
+
+ UniqueCodeBytes codeBytes = AllocateCodeBytes(codeLength);
+ if (!codeBytes) {
+ return nullptr;
+ }
+
+ memcpy(codeBytes.get(), unlinkedBytes.begin(), codeLength);
+
+ return js::MakeUnique<ModuleSegment>(tier, std::move(codeBytes), codeLength,
+ linkData);
+}
+
+bool ModuleSegment::initialize(IsTier2 isTier2, const CodeTier& codeTier,
+ const LinkData& linkData,
+ const Metadata& metadata,
+ const MetadataTier& metadataTier) {
+ if (!StaticallyLink(*this, linkData)) {
+ return false;
+ }
+
+ // Optimized compilation finishes on a background thread, so we must make sure
+ // to flush the icaches of all the executing threads.
+ FlushICacheSpec flushIcacheSpec = isTier2 == IsTier2::Tier2
+ ? FlushICacheSpec::AllThreads
+ : FlushICacheSpec::LocalThreadOnly;
+
+ // Reprotect the whole region to avoid having separate RW and RX mappings.
+ if (!ExecutableAllocator::makeExecutableAndFlushICache(
+ flushIcacheSpec, base(), RoundupCodeLength(length()))) {
+ return false;
+ }
+
+ SendCodeRangesToProfiler(*this, metadata, metadataTier.codeRanges);
+
+ // See comments in CodeSegment::initialize() for why this must be last.
+ return CodeSegment::initialize(codeTier);
+}
+
+size_t ModuleSegment::serializedSize() const {
+ return sizeof(uint32_t) + length();
+}
+
+void ModuleSegment::addSizeOfMisc(mozilla::MallocSizeOf mallocSizeOf,
+ size_t* code, size_t* data) const {
+ CodeSegment::addSizeOfMisc(mallocSizeOf, code);
+ *data += mallocSizeOf(this);
+}
+
+uint8_t* ModuleSegment::serialize(uint8_t* cursor,
+ const LinkData& linkData) const {
+ MOZ_ASSERT(tier() == Tier::Serialized);
+
+ cursor = WriteScalar<uint32_t>(cursor, length());
+ uint8_t* serializedBase = cursor;
+ cursor = WriteBytes(cursor, base(), length());
+ StaticallyUnlink(serializedBase, linkData);
+ return cursor;
+}
+
+/* static */ const uint8_t* ModuleSegment::deserialize(
+ const uint8_t* cursor, const LinkData& linkData,
+ UniqueModuleSegment* segment) {
+ uint32_t length;
+ cursor = ReadScalar<uint32_t>(cursor, &length);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ UniqueCodeBytes bytes = AllocateCodeBytes(length);
+ if (!bytes) {
+ return nullptr;
+ }
+
+ cursor = ReadBytes(cursor, bytes.get(), length);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ *segment = js::MakeUnique<ModuleSegment>(Tier::Serialized, std::move(bytes),
+ length, linkData);
+ if (!*segment) {
+ return nullptr;
+ }
+
+ return cursor;
+}
+
+const CodeRange* ModuleSegment::lookupRange(const void* pc) const {
+ return codeTier().lookupRange(pc);
+}
+
+size_t FuncExport::serializedSize() const {
+ return funcType_.serializedSize() + sizeof(pod);
+}
+
+uint8_t* FuncExport::serialize(uint8_t* cursor) const {
+ cursor = funcType_.serialize(cursor);
+ cursor = WriteBytes(cursor, &pod, sizeof(pod));
+ return cursor;
+}
+
+const uint8_t* FuncExport::deserialize(const uint8_t* cursor) {
+ (cursor = funcType_.deserialize(cursor)) &&
+ (cursor = ReadBytes(cursor, &pod, sizeof(pod)));
+ return cursor;
+}
+
+size_t FuncExport::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
+ return funcType_.sizeOfExcludingThis(mallocSizeOf);
+}
+
+size_t FuncImport::serializedSize() const {
+ return funcType_.serializedSize() + sizeof(pod);
+}
+
+uint8_t* FuncImport::serialize(uint8_t* cursor) const {
+ cursor = funcType_.serialize(cursor);
+ cursor = WriteBytes(cursor, &pod, sizeof(pod));
+ return cursor;
+}
+
+const uint8_t* FuncImport::deserialize(const uint8_t* cursor) {
+ (cursor = funcType_.deserialize(cursor)) &&
+ (cursor = ReadBytes(cursor, &pod, sizeof(pod)));
+ return cursor;
+}
+
+size_t FuncImport::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
+ return funcType_.sizeOfExcludingThis(mallocSizeOf);
+}
+
+static size_t StringLengthWithNullChar(const char* chars) {
+ return chars ? strlen(chars) + 1 : 0;
+}
+
+size_t CacheableChars::serializedSize() const {
+ return sizeof(uint32_t) + StringLengthWithNullChar(get());
+}
+
+uint8_t* CacheableChars::serialize(uint8_t* cursor) const {
+ uint32_t lengthWithNullChar = StringLengthWithNullChar(get());
+ cursor = WriteScalar<uint32_t>(cursor, lengthWithNullChar);
+ cursor = WriteBytes(cursor, get(), lengthWithNullChar);
+ return cursor;
+}
+
+const uint8_t* CacheableChars::deserialize(const uint8_t* cursor) {
+ uint32_t lengthWithNullChar;
+ cursor = ReadBytes(cursor, &lengthWithNullChar, sizeof(uint32_t));
+
+ if (lengthWithNullChar) {
+ reset(js_pod_malloc<char>(lengthWithNullChar));
+ if (!get()) {
+ return nullptr;
+ }
+
+ cursor = ReadBytes(cursor, get(), lengthWithNullChar);
+ } else {
+ MOZ_ASSERT(!get());
+ }
+
+ return cursor;
+}
+
+size_t CacheableChars::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
+ return mallocSizeOf(get());
+}
+
+size_t MetadataTier::serializedSize() const {
+ return SerializedPodVectorSize(funcToCodeRange) +
+ SerializedPodVectorSize(codeRanges) +
+ SerializedPodVectorSize(callSites) + trapSites.serializedSize() +
+ SerializedVectorSize(funcImports) + SerializedVectorSize(funcExports);
+}
+
+size_t MetadataTier::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
+ return funcToCodeRange.sizeOfExcludingThis(mallocSizeOf) +
+ codeRanges.sizeOfExcludingThis(mallocSizeOf) +
+ callSites.sizeOfExcludingThis(mallocSizeOf) +
+ trapSites.sizeOfExcludingThis(mallocSizeOf) +
+ SizeOfVectorExcludingThis(funcImports, mallocSizeOf) +
+ SizeOfVectorExcludingThis(funcExports, mallocSizeOf);
+}
+
+uint8_t* MetadataTier::serialize(uint8_t* cursor) const {
+ cursor = SerializePodVector(cursor, funcToCodeRange);
+ cursor = SerializePodVector(cursor, codeRanges);
+ cursor = SerializePodVector(cursor, callSites);
+ cursor = trapSites.serialize(cursor);
+ cursor = SerializeVector(cursor, funcImports);
+ cursor = SerializeVector(cursor, funcExports);
+ MOZ_ASSERT(debugTrapFarJumpOffsets.empty());
+ return cursor;
+}
+
+/* static */ const uint8_t* MetadataTier::deserialize(const uint8_t* cursor) {
+ (cursor = DeserializePodVector(cursor, &funcToCodeRange)) &&
+ (cursor = DeserializePodVector(cursor, &codeRanges)) &&
+ (cursor = DeserializePodVector(cursor, &callSites)) &&
+ (cursor = trapSites.deserialize(cursor)) &&
+ (cursor = DeserializeVector(cursor, &funcImports)) &&
+ (cursor = DeserializeVector(cursor, &funcExports));
+ MOZ_ASSERT(debugTrapFarJumpOffsets.empty());
+ return cursor;
+}
+
+UniqueLazyStubSegment LazyStubSegment::create(const CodeTier& codeTier,
+ size_t length) {
+ UniqueCodeBytes codeBytes = AllocateCodeBytes(length);
+ if (!codeBytes) {
+ return nullptr;
+ }
+
+ auto segment = js::MakeUnique<LazyStubSegment>(std::move(codeBytes), length);
+ if (!segment || !segment->initialize(codeTier)) {
+ return nullptr;
+ }
+
+ return segment;
+}
+
+bool LazyStubSegment::hasSpace(size_t bytes) const {
+ MOZ_ASSERT(AlignBytesNeeded(bytes) == bytes);
+ return bytes <= length() && usedBytes_ <= length() - bytes;
+}
+
+bool LazyStubSegment::addStubs(size_t codeLength,
+ const Uint32Vector& funcExportIndices,
+ const FuncExportVector& funcExports,
+ const CodeRangeVector& codeRanges,
+ uint8_t** codePtr,
+ size_t* indexFirstInsertedCodeRange) {
+ MOZ_ASSERT(hasSpace(codeLength));
+
+ size_t offsetInSegment = usedBytes_;
+ *codePtr = base() + usedBytes_;
+ usedBytes_ += codeLength;
+
+ *indexFirstInsertedCodeRange = codeRanges_.length();
+
+ if (!codeRanges_.reserve(codeRanges_.length() + 2 * codeRanges.length())) {
+ return false;
+ }
+
+ size_t i = 0;
+ for (uint32_t funcExportIndex : funcExportIndices) {
+ const CodeRange& interpRange = codeRanges[i];
+ MOZ_ASSERT(interpRange.isInterpEntry());
+ MOZ_ASSERT(interpRange.funcIndex() ==
+ funcExports[funcExportIndex].funcIndex());
+
+ codeRanges_.infallibleAppend(interpRange);
+ codeRanges_.back().offsetBy(offsetInSegment);
+ i++;
+
+ if (funcExports[funcExportIndex].funcType().hasUnexposableArgOrRet()) {
+ continue;
+ }
+ if (funcExports[funcExportIndex]
+ .funcType()
+ .temporarilyUnsupportedReftypeForEntry()) {
+ continue;
+ }
+
+ const CodeRange& jitRange = codeRanges[i];
+ MOZ_ASSERT(jitRange.isJitEntry());
+ MOZ_ASSERT(jitRange.funcIndex() == interpRange.funcIndex());
+
+ codeRanges_.infallibleAppend(jitRange);
+ codeRanges_.back().offsetBy(offsetInSegment);
+ i++;
+ }
+
+ return true;
+}
+
+const CodeRange* LazyStubSegment::lookupRange(const void* pc) const {
+ return LookupInSorted(codeRanges_,
+ CodeRange::OffsetInCode((uint8_t*)pc - base()));
+}
+
+void LazyStubSegment::addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code,
+ size_t* data) const {
+ CodeSegment::addSizeOfMisc(mallocSizeOf, code);
+ *data += codeRanges_.sizeOfExcludingThis(mallocSizeOf);
+ *data += mallocSizeOf(this);
+}
+
+struct ProjectLazyFuncIndex {
+ const LazyFuncExportVector& funcExports;
+ explicit ProjectLazyFuncIndex(const LazyFuncExportVector& funcExports)
+ : funcExports(funcExports) {}
+ uint32_t operator[](size_t index) const {
+ return funcExports[index].funcIndex;
+ }
+};
+
+static constexpr unsigned LAZY_STUB_LIFO_DEFAULT_CHUNK_SIZE = 8 * 1024;
+
+bool LazyStubTier::createMany(const Uint32Vector& funcExportIndices,
+ const CodeTier& codeTier,
+ bool flushAllThreadsIcaches,
+ size_t* stubSegmentIndex) {
+ MOZ_ASSERT(funcExportIndices.length());
+
+ LifoAlloc lifo(LAZY_STUB_LIFO_DEFAULT_CHUNK_SIZE);
+ TempAllocator alloc(&lifo);
+ JitContext jitContext(&alloc);
+ WasmMacroAssembler masm(alloc);
+
+ const MetadataTier& metadata = codeTier.metadata();
+ const FuncExportVector& funcExports = metadata.funcExports;
+ uint8_t* moduleSegmentBase = codeTier.segment().base();
+
+ CodeRangeVector codeRanges;
+ DebugOnly<uint32_t> numExpectedRanges = 0;
+ for (uint32_t funcExportIndex : funcExportIndices) {
+ const FuncExport& fe = funcExports[funcExportIndex];
+ // Entries with unsupported types get only the interp exit
+ bool unsupportedType =
+ fe.funcType().hasUnexposableArgOrRet() ||
+ fe.funcType().temporarilyUnsupportedReftypeForEntry();
+ numExpectedRanges += (unsupportedType ? 1 : 2);
+ void* calleePtr =
+ moduleSegmentBase + metadata.codeRange(fe).funcUncheckedCallEntry();
+ Maybe<ImmPtr> callee;
+ callee.emplace(calleePtr, ImmPtr::NoCheckToken());
+ if (!GenerateEntryStubs(masm, funcExportIndex, fe, callee,
+ /* asmjs */ false, &codeRanges)) {
+ return false;
+ }
+ }
+ MOZ_ASSERT(codeRanges.length() == numExpectedRanges,
+ "incorrect number of entries per function");
+
+ masm.finish();
+
+ MOZ_ASSERT(masm.callSites().empty());
+ MOZ_ASSERT(masm.callSiteTargets().empty());
+ MOZ_ASSERT(masm.trapSites().empty());
+
+ if (masm.oom()) {
+ return false;
+ }
+
+ size_t codeLength = LazyStubSegment::AlignBytesNeeded(masm.bytesNeeded());
+
+ if (!stubSegments_.length() ||
+ !stubSegments_[lastStubSegmentIndex_]->hasSpace(codeLength)) {
+ size_t newSegmentSize = std::max(codeLength, ExecutableCodePageSize);
+ UniqueLazyStubSegment newSegment =
+ LazyStubSegment::create(codeTier, newSegmentSize);
+ if (!newSegment) {
+ return false;
+ }
+ lastStubSegmentIndex_ = stubSegments_.length();
+ if (!stubSegments_.emplaceBack(std::move(newSegment))) {
+ return false;
+ }
+ }
+
+ LazyStubSegment* segment = stubSegments_[lastStubSegmentIndex_].get();
+ *stubSegmentIndex = lastStubSegmentIndex_;
+
+ size_t interpRangeIndex;
+ uint8_t* codePtr = nullptr;
+ if (!segment->addStubs(codeLength, funcExportIndices, funcExports, codeRanges,
+ &codePtr, &interpRangeIndex))
+ return false;
+
+ masm.executableCopy(codePtr);
+ PatchDebugSymbolicAccesses(codePtr, masm);
+ memset(codePtr + masm.bytesNeeded(), 0, codeLength - masm.bytesNeeded());
+
+ for (const CodeLabel& label : masm.codeLabels()) {
+ Assembler::Bind(codePtr, label);
+ }
+
+ // Optimized compilation finishes on a background thread, so we must make sure
+ // to flush the icaches of all the executing threads.
+ FlushICacheSpec flushIcacheSpec = flushAllThreadsIcaches
+ ? FlushICacheSpec::AllThreads
+ : FlushICacheSpec::LocalThreadOnly;
+ if (!ExecutableAllocator::makeExecutableAndFlushICache(flushIcacheSpec,
+ codePtr, codeLength)) {
+ return false;
+ }
+
+ // Create lazy function exports for funcIndex -> entry lookup.
+ if (!exports_.reserve(exports_.length() + funcExportIndices.length())) {
+ return false;
+ }
+
+ for (uint32_t funcExportIndex : funcExportIndices) {
+ const FuncExport& fe = funcExports[funcExportIndex];
+
+ DebugOnly<CodeRange> cr = segment->codeRanges()[interpRangeIndex];
+ MOZ_ASSERT(cr.value.isInterpEntry());
+ MOZ_ASSERT(cr.value.funcIndex() == fe.funcIndex());
+
+ LazyFuncExport lazyExport(fe.funcIndex(), *stubSegmentIndex,
+ interpRangeIndex);
+
+ size_t exportIndex;
+ MOZ_ALWAYS_FALSE(BinarySearch(ProjectLazyFuncIndex(exports_), 0,
+ exports_.length(), fe.funcIndex(),
+ &exportIndex));
+ MOZ_ALWAYS_TRUE(
+ exports_.insert(exports_.begin() + exportIndex, std::move(lazyExport)));
+
+ // Functions with unsupported types in their sig have only one entry
+ // (interp). All other functions get an extra jit entry.
+ bool unsupportedType =
+ fe.funcType().hasUnexposableArgOrRet() ||
+ fe.funcType().temporarilyUnsupportedReftypeForEntry();
+ interpRangeIndex += (unsupportedType ? 1 : 2);
+ }
+
+ return true;
+}
+
+bool LazyStubTier::createOne(uint32_t funcExportIndex,
+ const CodeTier& codeTier) {
+ Uint32Vector funcExportIndexes;
+ if (!funcExportIndexes.append(funcExportIndex)) {
+ return false;
+ }
+
+ // This happens on the executing thread (called via GetInterpEntry), so no
+ // need to flush the icaches on all the threads.
+ bool flushAllThreadIcaches = false;
+
+ size_t stubSegmentIndex;
+ if (!createMany(funcExportIndexes, codeTier, flushAllThreadIcaches,
+ &stubSegmentIndex)) {
+ return false;
+ }
+
+ const UniqueLazyStubSegment& segment = stubSegments_[stubSegmentIndex];
+ const CodeRangeVector& codeRanges = segment->codeRanges();
+
+ // Functions that have unsupported types in their sig don't get a jit
+ // entry.
+ if (codeTier.metadata()
+ .funcExports[funcExportIndex]
+ .funcType()
+ .temporarilyUnsupportedReftypeForEntry() ||
+ codeTier.metadata()
+ .funcExports[funcExportIndex]
+ .funcType()
+ .hasUnexposableArgOrRet()) {
+ MOZ_ASSERT(codeRanges.length() >= 1);
+ MOZ_ASSERT(codeRanges.back().isInterpEntry());
+ return true;
+ }
+
+ MOZ_ASSERT(codeRanges.length() >= 2);
+ MOZ_ASSERT(codeRanges[codeRanges.length() - 2].isInterpEntry());
+
+ const CodeRange& cr = codeRanges[codeRanges.length() - 1];
+ MOZ_ASSERT(cr.isJitEntry());
+
+ codeTier.code().setJitEntry(cr.funcIndex(), segment->base() + cr.begin());
+ return true;
+}
+
+bool LazyStubTier::createTier2(const Uint32Vector& funcExportIndices,
+ const CodeTier& codeTier,
+ Maybe<size_t>* outStubSegmentIndex) {
+ if (!funcExportIndices.length()) {
+ return true;
+ }
+
+ // This compilation happens on a background compiler thread, so the icache may
+ // need to be flushed on all the threads.
+ bool flushAllThreadIcaches = true;
+
+ size_t stubSegmentIndex;
+ if (!createMany(funcExportIndices, codeTier, flushAllThreadIcaches,
+ &stubSegmentIndex)) {
+ return false;
+ }
+
+ outStubSegmentIndex->emplace(stubSegmentIndex);
+ return true;
+}
+
+void LazyStubTier::setJitEntries(const Maybe<size_t>& stubSegmentIndex,
+ const Code& code) {
+ if (!stubSegmentIndex) {
+ return;
+ }
+ const UniqueLazyStubSegment& segment = stubSegments_[*stubSegmentIndex];
+ for (const CodeRange& cr : segment->codeRanges()) {
+ if (!cr.isJitEntry()) {
+ continue;
+ }
+ code.setJitEntry(cr.funcIndex(), segment->base() + cr.begin());
+ }
+}
+
+bool LazyStubTier::hasStub(uint32_t funcIndex) const {
+ size_t match;
+ return BinarySearch(ProjectLazyFuncIndex(exports_), 0, exports_.length(),
+ funcIndex, &match);
+}
+
+void* LazyStubTier::lookupInterpEntry(uint32_t funcIndex) const {
+ size_t match;
+ if (!BinarySearch(ProjectLazyFuncIndex(exports_), 0, exports_.length(),
+ funcIndex, &match)) {
+ return nullptr;
+ }
+ const LazyFuncExport& fe = exports_[match];
+ const LazyStubSegment& stub = *stubSegments_[fe.lazyStubSegmentIndex];
+ return stub.base() + stub.codeRanges()[fe.funcCodeRangeIndex].begin();
+}
+
+void LazyStubTier::addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code,
+ size_t* data) const {
+ *data += sizeof(*this);
+ *data += exports_.sizeOfExcludingThis(mallocSizeOf);
+ for (const UniqueLazyStubSegment& stub : stubSegments_) {
+ stub->addSizeOfMisc(mallocSizeOf, code, data);
+ }
+}
+
+bool MetadataTier::clone(const MetadataTier& src) {
+ if (!funcToCodeRange.appendAll(src.funcToCodeRange)) {
+ return false;
+ }
+ if (!codeRanges.appendAll(src.codeRanges)) {
+ return false;
+ }
+ if (!callSites.appendAll(src.callSites)) {
+ return false;
+ }
+ if (!debugTrapFarJumpOffsets.appendAll(src.debugTrapFarJumpOffsets)) {
+ return false;
+ }
+
+ for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
+ if (!trapSites[trap].appendAll(src.trapSites[trap])) {
+ return false;
+ }
+ }
+
+ if (!funcImports.resize(src.funcImports.length())) {
+ return false;
+ }
+ for (size_t i = 0; i < src.funcImports.length(); i++) {
+ funcImports[i].clone(src.funcImports[i]);
+ }
+
+ if (!funcExports.resize(src.funcExports.length())) {
+ return false;
+ }
+ for (size_t i = 0; i < src.funcExports.length(); i++) {
+ funcExports[i].clone(src.funcExports[i]);
+ }
+
+ return true;
+}
+
+size_t Metadata::serializedSize() const {
+ return sizeof(pod()) + SerializedVectorSize(types) +
+ SerializedPodVectorSize(globals) + SerializedPodVectorSize(tables) +
+#ifdef ENABLE_WASM_EXCEPTIONS
+ SerializedPodVectorSize(events) +
+#endif
+ sizeof(moduleName) + SerializedPodVectorSize(funcNames) +
+ filename.serializedSize() + sourceMapURL.serializedSize();
+}
+
+uint8_t* Metadata::serialize(uint8_t* cursor) const {
+ MOZ_ASSERT(!debugEnabled && debugFuncArgTypes.empty() &&
+ debugFuncReturnTypes.empty());
+ cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
+ cursor = SerializeVector(cursor, types);
+ cursor = SerializePodVector(cursor, globals);
+ cursor = SerializePodVector(cursor, tables);
+#ifdef ENABLE_WASM_EXCEPTIONS
+ cursor = SerializePodVector(cursor, events);
+#endif
+ cursor = WriteBytes(cursor, &moduleName, sizeof(moduleName));
+ cursor = SerializePodVector(cursor, funcNames);
+ cursor = filename.serialize(cursor);
+ cursor = sourceMapURL.serialize(cursor);
+ return cursor;
+}
+
+/* static */ const uint8_t* Metadata::deserialize(const uint8_t* cursor) {
+ (cursor = ReadBytes(cursor, &pod(), sizeof(pod()))) &&
+ (cursor = DeserializeVector(cursor, &types)) &&
+ (cursor = DeserializePodVector(cursor, &globals)) &&
+ (cursor = DeserializePodVector(cursor, &tables)) &&
+#ifdef ENABLE_WASM_EXCEPTIONS
+ (cursor = DeserializePodVector(cursor, &events)) &&
+#endif
+ (cursor = ReadBytes(cursor, &moduleName, sizeof(moduleName))) &&
+ (cursor = DeserializePodVector(cursor, &funcNames)) &&
+ (cursor = filename.deserialize(cursor)) &&
+ (cursor = sourceMapURL.deserialize(cursor));
+ debugEnabled = false;
+ debugFuncArgTypes.clear();
+ debugFuncReturnTypes.clear();
+ return cursor;
+}
+
+size_t Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
+ return SizeOfVectorExcludingThis(types, mallocSizeOf) +
+ globals.sizeOfExcludingThis(mallocSizeOf) +
+ tables.sizeOfExcludingThis(mallocSizeOf) +
+#ifdef ENABLE_WASM_EXCEPTIONS
+ events.sizeOfExcludingThis(mallocSizeOf) +
+#endif
+ funcNames.sizeOfExcludingThis(mallocSizeOf) +
+ filename.sizeOfExcludingThis(mallocSizeOf) +
+ sourceMapURL.sizeOfExcludingThis(mallocSizeOf);
+}
+
+struct ProjectFuncIndex {
+ const FuncExportVector& funcExports;
+ explicit ProjectFuncIndex(const FuncExportVector& funcExports)
+ : funcExports(funcExports) {}
+ uint32_t operator[](size_t index) const {
+ return funcExports[index].funcIndex();
+ }
+};
+
+FuncExport& MetadataTier::lookupFuncExport(
+ uint32_t funcIndex, size_t* funcExportIndex /* = nullptr */) {
+ size_t match;
+ if (!BinarySearch(ProjectFuncIndex(funcExports), 0, funcExports.length(),
+ funcIndex, &match)) {
+ MOZ_CRASH("missing function export");
+ }
+ if (funcExportIndex) {
+ *funcExportIndex = match;
+ }
+ return funcExports[match];
+}
+
+const FuncExport& MetadataTier::lookupFuncExport(
+ uint32_t funcIndex, size_t* funcExportIndex) const {
+ return const_cast<MetadataTier*>(this)->lookupFuncExport(funcIndex,
+ funcExportIndex);
+}
+
+static bool AppendName(const Bytes& namePayload, const Name& name,
+ UTF8Bytes* bytes) {
+ MOZ_RELEASE_ASSERT(name.offsetInNamePayload <= namePayload.length());
+ MOZ_RELEASE_ASSERT(name.length <=
+ namePayload.length() - name.offsetInNamePayload);
+ return bytes->append(
+ (const char*)namePayload.begin() + name.offsetInNamePayload, name.length);
+}
+
+static bool AppendFunctionIndexName(uint32_t funcIndex, UTF8Bytes* bytes) {
+ const char beforeFuncIndex[] = "wasm-function[";
+ const char afterFuncIndex[] = "]";
+
+ ToCStringBuf cbuf;
+ const char* funcIndexStr = NumberToCString(nullptr, &cbuf, funcIndex);
+ MOZ_ASSERT(funcIndexStr);
+
+ return bytes->append(beforeFuncIndex, strlen(beforeFuncIndex)) &&
+ bytes->append(funcIndexStr, strlen(funcIndexStr)) &&
+ bytes->append(afterFuncIndex, strlen(afterFuncIndex));
+}
+
+bool Metadata::getFuncName(NameContext ctx, uint32_t funcIndex,
+ UTF8Bytes* name) const {
+ if (moduleName && moduleName->length != 0) {
+ if (!AppendName(namePayload->bytes, *moduleName, name)) {
+ return false;
+ }
+ if (!name->append('.')) {
+ return false;
+ }
+ }
+
+ if (funcIndex < funcNames.length() && funcNames[funcIndex].length != 0) {
+ return AppendName(namePayload->bytes, funcNames[funcIndex], name);
+ }
+
+ if (ctx == NameContext::BeforeLocation) {
+ return true;
+ }
+
+ return AppendFunctionIndexName(funcIndex, name);
+}
+
+bool CodeTier::initialize(IsTier2 isTier2, const Code& code,
+ const LinkData& linkData, const Metadata& metadata) {
+ MOZ_ASSERT(!initialized());
+ code_ = &code;
+
+ MOZ_ASSERT(lazyStubs_.lock()->empty());
+
+ // See comments in CodeSegment::initialize() for why this must be last.
+ if (!segment_->initialize(isTier2, *this, linkData, metadata, *metadata_)) {
+ return false;
+ }
+
+ MOZ_ASSERT(initialized());
+ return true;
+}
+
+size_t CodeTier::serializedSize() const {
+ return segment_->serializedSize() + metadata_->serializedSize();
+}
+
+uint8_t* CodeTier::serialize(uint8_t* cursor, const LinkData& linkData) const {
+ cursor = metadata_->serialize(cursor);
+ cursor = segment_->serialize(cursor, linkData);
+ return cursor;
+}
+
+/* static */ const uint8_t* CodeTier::deserialize(const uint8_t* cursor,
+ const LinkData& linkData,
+ UniqueCodeTier* codeTier) {
+ auto metadata = js::MakeUnique<MetadataTier>(Tier::Serialized);
+ if (!metadata) {
+ return nullptr;
+ }
+ cursor = metadata->deserialize(cursor);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ UniqueModuleSegment segment;
+ cursor = ModuleSegment::deserialize(cursor, linkData, &segment);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ *codeTier = js::MakeUnique<CodeTier>(std::move(metadata), std::move(segment));
+ if (!*codeTier) {
+ return nullptr;
+ }
+
+ return cursor;
+}
+
+void CodeTier::addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code,
+ size_t* data) const {
+ segment_->addSizeOfMisc(mallocSizeOf, code, data);
+ lazyStubs_.lock()->addSizeOfMisc(mallocSizeOf, code, data);
+ *data += metadata_->sizeOfExcludingThis(mallocSizeOf);
+}
+
+const CodeRange* CodeTier::lookupRange(const void* pc) const {
+ CodeRange::OffsetInCode target((uint8_t*)pc - segment_->base());
+ return LookupInSorted(metadata_->codeRanges, target);
+}
+
+bool JumpTables::init(CompileMode mode, const ModuleSegment& ms,
+ const CodeRangeVector& codeRanges) {
+ static_assert(JSScript::offsetOfJitCodeRaw() == 0,
+ "wasm fast jit entry is at (void*) jit[funcIndex]");
+
+ mode_ = mode;
+
+ size_t numFuncs = 0;
+ for (const CodeRange& cr : codeRanges) {
+ if (cr.isFunction()) {
+ numFuncs++;
+ }
+ }
+
+ numFuncs_ = numFuncs;
+
+ if (mode_ == CompileMode::Tier1) {
+ tiering_ = TablePointer(js_pod_calloc<void*>(numFuncs));
+ if (!tiering_) {
+ return false;
+ }
+ }
+
+ // The number of jit entries is overestimated, but it is simpler when
+ // filling/looking up the jit entries and safe (worst case we'll crash
+ // because of a null deref when trying to call the jit entry of an
+ // unexported function).
+ jit_ = TablePointer(js_pod_calloc<void*>(numFuncs));
+ if (!jit_) {
+ return false;
+ }
+
+ uint8_t* codeBase = ms.base();
+ for (const CodeRange& cr : codeRanges) {
+ if (cr.isFunction()) {
+ setTieringEntry(cr.funcIndex(), codeBase + cr.funcTierEntry());
+ } else if (cr.isJitEntry()) {
+ setJitEntry(cr.funcIndex(), codeBase + cr.begin());
+ }
+ }
+ return true;
+}
+
+Code::Code(UniqueCodeTier tier1, const Metadata& metadata,
+ JumpTables&& maybeJumpTables)
+ : tier1_(std::move(tier1)),
+ metadata_(&metadata),
+ profilingLabels_(mutexid::WasmCodeProfilingLabels,
+ CacheableCharsVector()),
+ jumpTables_(std::move(maybeJumpTables)) {}
+
+bool Code::initialize(const LinkData& linkData) {
+ MOZ_ASSERT(!initialized());
+
+ if (!tier1_->initialize(IsTier2::NotTier2, *this, linkData, *metadata_)) {
+ return false;
+ }
+
+ MOZ_ASSERT(initialized());
+ return true;
+}
+
+bool Code::setTier2(UniqueCodeTier tier2, const LinkData& linkData) const {
+ MOZ_RELEASE_ASSERT(!hasTier2());
+ MOZ_RELEASE_ASSERT(tier2->tier() == Tier::Optimized &&
+ tier1_->tier() == Tier::Baseline);
+
+ if (!tier2->initialize(IsTier2::Tier2, *this, linkData, *metadata_)) {
+ return false;
+ }
+
+ tier2_ = std::move(tier2);
+
+ return true;
+}
+
+void Code::commitTier2() const {
+ MOZ_RELEASE_ASSERT(!hasTier2());
+ MOZ_RELEASE_ASSERT(tier2_.get());
+ hasTier2_ = true;
+ MOZ_ASSERT(hasTier2());
+}
+
+uint32_t Code::getFuncIndex(JSFunction* fun) const {
+ MOZ_ASSERT(fun->isWasm() || fun->isAsmJSNative());
+ if (!fun->isWasmWithJitEntry()) {
+ return fun->wasmFuncIndex();
+ }
+ return jumpTables_.funcIndexFromJitEntry(fun->wasmJitEntry());
+}
+
+Tiers Code::tiers() const {
+ if (hasTier2()) {
+ return Tiers(tier1_->tier(), tier2_->tier());
+ }
+ return Tiers(tier1_->tier());
+}
+
+bool Code::hasTier(Tier t) const {
+ if (hasTier2() && tier2_->tier() == t) {
+ return true;
+ }
+ return tier1_->tier() == t;
+}
+
+Tier Code::stableTier() const { return tier1_->tier(); }
+
+Tier Code::bestTier() const {
+ if (hasTier2()) {
+ return tier2_->tier();
+ }
+ return tier1_->tier();
+}
+
+const CodeTier& Code::codeTier(Tier tier) const {
+ switch (tier) {
+ case Tier::Baseline:
+ if (tier1_->tier() == Tier::Baseline) {
+ MOZ_ASSERT(tier1_->initialized());
+ return *tier1_;
+ }
+ MOZ_CRASH("No code segment at this tier");
+ case Tier::Optimized:
+ if (tier1_->tier() == Tier::Optimized) {
+ MOZ_ASSERT(tier1_->initialized());
+ return *tier1_;
+ }
+ if (tier2_) {
+ MOZ_ASSERT(tier2_->initialized());
+ return *tier2_;
+ }
+ MOZ_CRASH("No code segment at this tier");
+ }
+ MOZ_CRASH();
+}
+
+bool Code::containsCodePC(const void* pc) const {
+ for (Tier t : tiers()) {
+ const ModuleSegment& ms = segment(t);
+ if (ms.containsCodePC(pc)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+struct CallSiteRetAddrOffset {
+ const CallSiteVector& callSites;
+ explicit CallSiteRetAddrOffset(const CallSiteVector& callSites)
+ : callSites(callSites) {}
+ uint32_t operator[](size_t index) const {
+ return callSites[index].returnAddressOffset();
+ }
+};
+
+const CallSite* Code::lookupCallSite(void* returnAddress) const {
+ for (Tier t : tiers()) {
+ uint32_t target = ((uint8_t*)returnAddress) - segment(t).base();
+ size_t lowerBound = 0;
+ size_t upperBound = metadata(t).callSites.length();
+
+ size_t match;
+ if (BinarySearch(CallSiteRetAddrOffset(metadata(t).callSites), lowerBound,
+ upperBound, target, &match))
+ return &metadata(t).callSites[match];
+ }
+
+ return nullptr;
+}
+
+const CodeRange* Code::lookupFuncRange(void* pc) const {
+ for (Tier t : tiers()) {
+ const CodeRange* result = codeTier(t).lookupRange(pc);
+ if (result && result->isFunction()) {
+ return result;
+ }
+ }
+ return nullptr;
+}
+
+const StackMap* Code::lookupStackMap(uint8_t* nextPC) const {
+ for (Tier t : tiers()) {
+ const StackMap* result = metadata(t).stackMaps.findMap(nextPC);
+ if (result) {
+ return result;
+ }
+ }
+ return nullptr;
+}
+
+struct TrapSitePCOffset {
+ const TrapSiteVector& trapSites;
+ explicit TrapSitePCOffset(const TrapSiteVector& trapSites)
+ : trapSites(trapSites) {}
+ uint32_t operator[](size_t index) const { return trapSites[index].pcOffset; }
+};
+
+bool Code::lookupTrap(void* pc, Trap* trapOut, BytecodeOffset* bytecode) const {
+ for (Tier t : tiers()) {
+ const TrapSiteVectorArray& trapSitesArray = metadata(t).trapSites;
+ for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
+ const TrapSiteVector& trapSites = trapSitesArray[trap];
+
+ uint32_t target = ((uint8_t*)pc) - segment(t).base();
+ size_t lowerBound = 0;
+ size_t upperBound = trapSites.length();
+
+ size_t match;
+ if (BinarySearch(TrapSitePCOffset(trapSites), lowerBound, upperBound,
+ target, &match)) {
+ MOZ_ASSERT(segment(t).containsCodePC(pc));
+ *trapOut = trap;
+ *bytecode = trapSites[match].bytecode;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+// When enabled, generate profiling labels for every name in funcNames_ that is
+// the name of some Function CodeRange. This involves malloc() so do it now
+// since, once we start sampling, we'll be in a signal-handing context where we
+// cannot malloc.
+void Code::ensureProfilingLabels(bool profilingEnabled) const {
+ auto labels = profilingLabels_.lock();
+
+ if (!profilingEnabled) {
+ labels->clear();
+ return;
+ }
+
+ if (!labels->empty()) {
+ return;
+ }
+
+ // Any tier will do, we only need tier-invariant data that are incidentally
+ // stored with the code ranges.
+
+ for (const CodeRange& codeRange : metadata(stableTier()).codeRanges) {
+ if (!codeRange.isFunction()) {
+ continue;
+ }
+
+ ToCStringBuf cbuf;
+ const char* bytecodeStr =
+ NumberToCString(nullptr, &cbuf, codeRange.funcLineOrBytecode());
+ MOZ_ASSERT(bytecodeStr);
+
+ UTF8Bytes name;
+ if (!metadata().getFuncNameStandalone(codeRange.funcIndex(), &name)) {
+ return;
+ }
+ if (!name.append(" (", 2)) {
+ return;
+ }
+
+ if (const char* filename = metadata().filename.get()) {
+ if (!name.append(filename, strlen(filename))) {
+ return;
+ }
+ } else {
+ if (!name.append('?')) {
+ return;
+ }
+ }
+
+ if (!name.append(':') || !name.append(bytecodeStr, strlen(bytecodeStr)) ||
+ !name.append(")\0", 2)) {
+ return;
+ }
+
+ UniqueChars label(name.extractOrCopyRawBuffer());
+ if (!label) {
+ return;
+ }
+
+ if (codeRange.funcIndex() >= labels->length()) {
+ if (!labels->resize(codeRange.funcIndex() + 1)) {
+ return;
+ }
+ }
+
+ ((CacheableCharsVector&)labels)[codeRange.funcIndex()] = std::move(label);
+ }
+}
+
+const char* Code::profilingLabel(uint32_t funcIndex) const {
+ auto labels = profilingLabels_.lock();
+
+ if (funcIndex >= labels->length() ||
+ !((CacheableCharsVector&)labels)[funcIndex]) {
+ return "?";
+ }
+ return ((CacheableCharsVector&)labels)[funcIndex].get();
+}
+
+void Code::addSizeOfMiscIfNotSeen(MallocSizeOf mallocSizeOf,
+ Metadata::SeenSet* seenMetadata,
+ Code::SeenSet* seenCode, size_t* code,
+ size_t* data) const {
+ auto p = seenCode->lookupForAdd(this);
+ if (p) {
+ return;
+ }
+ bool ok = seenCode->add(p, this);
+ (void)ok; // oh well
+
+ *data += mallocSizeOf(this) +
+ metadata().sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenMetadata) +
+ profilingLabels_.lock()->sizeOfExcludingThis(mallocSizeOf) +
+ jumpTables_.sizeOfMiscExcludingThis();
+
+ for (auto t : tiers()) {
+ codeTier(t).addSizeOfMisc(mallocSizeOf, code, data);
+ }
+}
+
+size_t Code::serializedSize() const {
+ return metadata().serializedSize() +
+ codeTier(Tier::Serialized).serializedSize();
+}
+
+uint8_t* Code::serialize(uint8_t* cursor, const LinkData& linkData) const {
+ MOZ_RELEASE_ASSERT(!metadata().debugEnabled);
+
+ cursor = metadata().serialize(cursor);
+ cursor = codeTier(Tier::Serialized).serialize(cursor, linkData);
+ return cursor;
+}
+
+/* static */ const uint8_t* Code::deserialize(const uint8_t* cursor,
+ const LinkData& linkData,
+ Metadata& metadata,
+ SharedCode* out) {
+ cursor = metadata.deserialize(cursor);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ UniqueCodeTier codeTier;
+ cursor = CodeTier::deserialize(cursor, linkData, &codeTier);
+ if (!cursor) {
+ return nullptr;
+ }
+
+ JumpTables jumpTables;
+ if (!jumpTables.init(CompileMode::Once, codeTier->segment(),
+ codeTier->metadata().codeRanges)) {
+ return nullptr;
+ }
+
+ MutableCode code =
+ js_new<Code>(std::move(codeTier), metadata, std::move(jumpTables));
+ if (!code || !code->initialize(linkData)) {
+ return nullptr;
+ }
+
+ *out = code;
+ return cursor;
+}
+
+void wasm::PatchDebugSymbolicAccesses(uint8_t* codeBase, MacroAssembler& masm) {
+#ifdef WASM_CODEGEN_DEBUG
+ for (auto& access : masm.symbolicAccesses()) {
+ switch (access.target) {
+ case SymbolicAddress::PrintI32:
+ case SymbolicAddress::PrintPtr:
+ case SymbolicAddress::PrintF32:
+ case SymbolicAddress::PrintF64:
+ case SymbolicAddress::PrintText:
+ break;
+ default:
+ MOZ_CRASH("unexpected symbol in PatchDebugSymbolicAccesses");
+ }
+ ABIFunctionType abiType;
+ void* target = AddressOf(access.target, &abiType);
+ uint8_t* patchAt = codeBase + access.patchAt.offset();
+ Assembler::PatchDataWithValueCheck(CodeLocationLabel(patchAt),
+ PatchedImmPtr(target),
+ PatchedImmPtr((void*)-1));
+ }
+#else
+ MOZ_ASSERT(masm.symbolicAccesses().empty());
+#endif
+}