Files
palemoon27/js/src/asmjs/WasmModule.cpp
T
roytam1 cc394d8cae import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1253094, part 8 - Stop using DebugOnly for class/struct members in uriloader/. r=bz (15566e1146)
- Bug 1253094, part 9 - Stop using DebugOnly for class/struct members in xpcom/. r=froydnj (9fb881be79)
- Bug 1253094, part 10 - Stop using DebugOnly for class/struct members in memory/. r=njn (5fd563e632)
- Bug 1248843 - Make it clearer that DebugOnly uses up space even in optimized, non-DEBUG builds. r=Waldo (8e5e6e6a01)
- Bug 1253094, part 11 - Make DebugOnly a MOZ_STACK_CLASS. r=Waldo (7cee0c3c03)
- Bug 1246116 - BaldrMonkey: Wasm validation for block and loop. r=luke (0da84fb8fe)
- Bug 1252498 - Baldr: add Wasm object behind pref, default off (r=jorendorff) (b554912a96)
- Bug 1256988 - Fix #endif comments for MOZ_WIDGET_GTK. r=chmanchester (28928d1d58)
- Bug 724538 - Regenerate Unicode property data with updated script. r=emk (50e43bb897)
- Bug 1232665 - initialize class members: mLastPrefLang and mLastPrefFirstFont. r=jfkthame (8a62f92809)
- Bug 1248248 - Don't break glyph run for orientation mismatch before a cluster-extender. r=xidorn (f114f65903)
- Bug 1252432 part 1 - Implement wasm i32.wrap. r=luke (54d1e634b6)
- Bug 1252432 part 2 - Implement wasm i64.extend_s and i64.extend_u. r=bbouvier (d673455188)
- Bug 1252432 part 3 - Implement wasm i64.trunc_s and i64.trunc_u. r=sunfish (41dd8d7272)
- Bug 1253115 - BaldrMonkey: Convert AsmJSHeapAccess offsets to unsigned. r=luke (5cb02e4832)
- Bug 1253115 - BaldrMonkey: Refactor AsmJS load/store infrastructure. r=luke (ef75bae281)
- Bug 1243583 - ensure transition events are dispatched to all the relevant subdocuments, r=dholbert (05026b75bb)
- Bug 1240985 - IPC fuzzer (r=gabor) (e825e77187)
- Bug 1248750 - Eliminate intentional IPC crashes (r=dvander) (0ace690c3b)
- Bug 1242609 - Implement PeekMessage to get some messages earlier. r=billm (4985fc8394)
- Bug 1257314 - Properly lock in IPC PeekMessages. r=dvander a=topcrash (6fe1db48f4)
- Bug 1242609 - Use PeekMessages to get the most recent DisplayPort request. r=kats (12374eafba)
- Bug 1254471 - Fix MessageChannel.cpp error unused variable transaction. r=billm (6a74186673)
- Bug 1251482 - Remove remaining references to MOZILLA_XPCOMRT_API from xpcom. r=froydnj (b691ca31f4)
- Bug 1251473 - Remove libxpcomrt library. r=froydnj (faed80b0ed)
- Bug 1249787 - BaldrMonkey: Add the testcase, which was mistakenly omitted from the main push. r=luke (1ef533365a)
- Bug 1250556: Require Store value expression to have the opcode's type; r=sunfish (b8363b4fc6)
- Bug 1250955: Guard against unimplemented i64 opcodes; r=jandem (98689ea7da)
- Bug 1253137 - Baldr: update version uint32 to match BinaryEncoding.md (r=sunfish) (c75d60370a)
- Bug 1253681 - BaldrMonkey: Update to the current official opcode encodings. r=luke (2e69d5780b)
- Bug 1252019: Don't patch profiling entries for the BadIndirectCall exit; r=luke (6f336d796c)
- Bug 1253137 - Baldr: update section header structure to match BinaryEncoding.md, part 1 (r=sunfish) (cd3e204373)
- Bug 1246116 - BaldrMonkey: Wasm validation for block and loop. r=luke (c594d15189)
- Bug 1253137 - Baldr: update memory exports to match BinaryEncoding.md (r=sunfish) (73fd37ee3b)
2024-02-12 09:50:49 +08:00

1613 lines
50 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
*
* Copyright 2015 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 "asmjs/WasmModule.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/EnumeratedRange.h"
#include "mozilla/PodOperations.h"
#include "jsprf.h"
#include "asmjs/WasmSerialize.h"
#include "builtin/AtomicsObject.h"
#include "builtin/SIMD.h"
#ifdef JS_ION_PERF
# include "jit/PerfSpewer.h"
#endif
#include "jit/BaselineJIT.h"
#include "jit/ExecutableAllocator.h"
#include "jit/JitCommon.h"
#include "js/MemoryMetrics.h"
#ifdef MOZ_VTUNE
# include "vtune/VTuneWrapper.h"
#endif
#include "jsobjinlines.h"
#include "jit/MacroAssembler-inl.h"
#include "vm/ArrayBufferObject-inl.h"
#include "vm/TypeInference-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::BinarySearch;
using mozilla::MakeEnumeratedRange;
using mozilla::PodCopy;
using mozilla::PodZero;
using mozilla::Swap;
using JS::GenericNaN;
const uint32_t ExportMap::MemoryExport;
UniqueCodePtr
wasm::AllocateCode(ExclusiveContext* cx, size_t bytes)
{
// On most platforms, this will allocate RWX memory. On iOS, or when
// --non-writable-jitcode is used, this will allocate RW memory. In this
// case, DynamicallyLinkModule will reprotect the code as RX.
unsigned permissions =
ExecutableAllocator::initialProtectionFlags(ExecutableAllocator::Writable);
void* p = AllocateExecutableMemory(nullptr, bytes, permissions, "asm-js-code", gc::SystemPageSize());
if (!p)
ReportOutOfMemory(cx);
return UniqueCodePtr((uint8_t*)p, CodeDeleter(bytes));
}
void
CodeDeleter::operator()(uint8_t* p)
{
MOZ_ASSERT(bytes_ != 0);
DeallocateExecutableMemory(p, bytes_, gc::SystemPageSize());
}
#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
// On MIPS, CodeLabels are instruction immediates so InternalLinks only
// patch instruction immediates.
StaticLinkData::InternalLink::InternalLink(Kind kind)
{
MOZ_ASSERT(kind == CodeLabel || kind == InstructionImmediate);
}
bool
StaticLinkData::InternalLink::isRawPointerPatch()
{
return false;
}
#else
// On the rest, CodeLabels are raw pointers so InternalLinks only patch
// raw pointers.
StaticLinkData::InternalLink::InternalLink(Kind kind)
{
MOZ_ASSERT(kind == CodeLabel || kind == RawPointer);
}
bool
StaticLinkData::InternalLink::isRawPointerPatch()
{
return true;
}
#endif
size_t
StaticLinkData::SymbolicLinkArray::serializedSize() const
{
size_t size = 0;
for (const Uint32Vector& offsets : *this)
size += SerializedPodVectorSize(offsets);
return size;
}
uint8_t*
StaticLinkData::SymbolicLinkArray::serialize(uint8_t* cursor) const
{
for (const Uint32Vector& offsets : *this)
cursor = SerializePodVector(cursor, offsets);
return cursor;
}
const uint8_t*
StaticLinkData::SymbolicLinkArray::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
{
for (Uint32Vector& offsets : *this) {
cursor = DeserializePodVector(cx, cursor, &offsets);
if (!cursor)
return nullptr;
}
return cursor;
}
bool
StaticLinkData::SymbolicLinkArray::clone(JSContext* cx, SymbolicLinkArray* out) const
{
for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
if (!ClonePodVector(cx, (*this)[imm], &(*out)[imm]))
return false;
}
return true;
}
size_t
StaticLinkData::SymbolicLinkArray::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
size_t size = 0;
for (const Uint32Vector& offsets : *this)
size += offsets.sizeOfExcludingThis(mallocSizeOf);
return size;
}
size_t
StaticLinkData::FuncPtrTable::serializedSize() const
{
return sizeof(globalDataOffset) +
SerializedPodVectorSize(elemOffsets);
}
uint8_t*
StaticLinkData::FuncPtrTable::serialize(uint8_t* cursor) const
{
cursor = WriteBytes(cursor, &globalDataOffset, sizeof(globalDataOffset));
cursor = SerializePodVector(cursor, elemOffsets);
return cursor;
}
const uint8_t*
StaticLinkData::FuncPtrTable::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
{
(cursor = ReadBytes(cursor, &globalDataOffset, sizeof(globalDataOffset))) &&
(cursor = DeserializePodVector(cx, cursor, &elemOffsets));
return cursor;
}
bool
StaticLinkData::FuncPtrTable::clone(JSContext* cx, FuncPtrTable* out) const
{
out->globalDataOffset = globalDataOffset;
return ClonePodVector(cx, elemOffsets, &out->elemOffsets);
}
size_t
StaticLinkData::FuncPtrTable::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
return elemOffsets.sizeOfExcludingThis(mallocSizeOf);
}
size_t
StaticLinkData::serializedSize() const
{
return sizeof(pod) +
SerializedPodVectorSize(internalLinks) +
symbolicLinks.serializedSize() +
SerializedVectorSize(funcPtrTables);
}
uint8_t*
StaticLinkData::serialize(uint8_t* cursor) const
{
cursor = WriteBytes(cursor, &pod, sizeof(pod));
cursor = SerializePodVector(cursor, internalLinks);
cursor = symbolicLinks.serialize(cursor);
cursor = SerializeVector(cursor, funcPtrTables);
return cursor;
}
const uint8_t*
StaticLinkData::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
{
(cursor = ReadBytes(cursor, &pod, sizeof(pod))) &&
(cursor = DeserializePodVector(cx, cursor, &internalLinks)) &&
(cursor = symbolicLinks.deserialize(cx, cursor)) &&
(cursor = DeserializeVector(cx, cursor, &funcPtrTables));
return cursor;
}
bool
StaticLinkData::clone(JSContext* cx, StaticLinkData* out) const
{
out->pod = pod;
return ClonePodVector(cx, internalLinks, &out->internalLinks) &&
symbolicLinks.clone(cx, &out->symbolicLinks) &&
CloneVector(cx, funcPtrTables, &out->funcPtrTables);
}
size_t
StaticLinkData::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
return internalLinks.sizeOfExcludingThis(mallocSizeOf) +
symbolicLinks.sizeOfExcludingThis(mallocSizeOf) +
SizeOfVectorExcludingThis(funcPtrTables, mallocSizeOf);
}
static size_t
SerializedSigSize(const Sig& sig)
{
return sizeof(ExprType) +
SerializedPodVectorSize(sig.args());
}
static uint8_t*
SerializeSig(uint8_t* cursor, const Sig& sig)
{
cursor = WriteScalar<ExprType>(cursor, sig.ret());
cursor = SerializePodVector(cursor, sig.args());
return cursor;
}
static const uint8_t*
DeserializeSig(ExclusiveContext* cx, const uint8_t* cursor, Sig* sig)
{
ExprType ret;
cursor = ReadScalar<ExprType>(cursor, &ret);
ValTypeVector args;
cursor = DeserializePodVector(cx, cursor, &args);
if (!cursor)
return nullptr;
*sig = Sig(Move(args), ret);
return cursor;
}
static size_t
SizeOfSigExcludingThis(const Sig& sig, MallocSizeOf mallocSizeOf)
{
return sig.args().sizeOfExcludingThis(mallocSizeOf);
}
size_t
Export::serializedSize() const
{
return SerializedSigSize(sig_) +
sizeof(pod);
}
uint8_t*
Export::serialize(uint8_t* cursor) const
{
cursor = SerializeSig(cursor, sig_);
cursor = WriteBytes(cursor, &pod, sizeof(pod));
return cursor;
}
const uint8_t*
Export::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
{
(cursor = DeserializeSig(cx, cursor, &sig_)) &&
(cursor = ReadBytes(cursor, &pod, sizeof(pod)));
return cursor;
}
bool
Export::clone(JSContext* cx, Export* out) const
{
out->pod = pod;
return out->sig_.clone(sig_);
}
size_t
Export::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
return SizeOfSigExcludingThis(sig_, mallocSizeOf);
}
size_t
Import::serializedSize() const
{
return SerializedSigSize(sig_) +
sizeof(pod);
}
uint8_t*
Import::serialize(uint8_t* cursor) const
{
cursor = SerializeSig(cursor, sig_);
cursor = WriteBytes(cursor, &pod, sizeof(pod));
return cursor;
}
const uint8_t*
Import::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
{
(cursor = DeserializeSig(cx, cursor, &sig_)) &&
(cursor = ReadBytes(cursor, &pod, sizeof(pod)));
return cursor;
}
bool
Import::clone(JSContext* cx, Import* out) const
{
out->pod = pod;
return out->sig_.clone(sig_);
}
size_t
Import::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
return SizeOfSigExcludingThis(sig_, mallocSizeOf);
}
CodeRange::CodeRange(Kind kind, Offsets offsets)
: funcIndex_(0),
funcLineOrBytecode_(0),
begin_(offsets.begin),
profilingReturn_(0),
end_(offsets.end)
{
PodZero(&u); // zero padding for Valgrind
u.kind_ = kind;
MOZ_ASSERT(begin_ <= end_);
MOZ_ASSERT(u.kind_ == Entry || u.kind_ == Inline || u.kind_ == CallThunk);
}
CodeRange::CodeRange(Kind kind, ProfilingOffsets offsets)
: funcIndex_(0),
funcLineOrBytecode_(0),
begin_(offsets.begin),
profilingReturn_(offsets.profilingReturn),
end_(offsets.end)
{
PodZero(&u); // zero padding for Valgrind
u.kind_ = kind;
MOZ_ASSERT(begin_ < profilingReturn_);
MOZ_ASSERT(profilingReturn_ < end_);
MOZ_ASSERT(u.kind_ == ImportJitExit || u.kind_ == ImportInterpExit || u.kind_ == ErrorExit);
}
CodeRange::CodeRange(uint32_t funcIndex, uint32_t funcLineOrBytecode, FuncOffsets offsets)
: funcIndex_(funcIndex),
funcLineOrBytecode_(funcLineOrBytecode)
{
PodZero(&u); // zero padding for Valgrind
u.kind_ = Function;
MOZ_ASSERT(offsets.nonProfilingEntry - offsets.begin <= UINT8_MAX);
begin_ = offsets.begin;
u.func.beginToEntry_ = offsets.nonProfilingEntry - begin_;
MOZ_ASSERT(offsets.nonProfilingEntry < offsets.profilingReturn);
MOZ_ASSERT(offsets.profilingReturn - offsets.profilingJump <= UINT8_MAX);
MOZ_ASSERT(offsets.profilingReturn - offsets.profilingEpilogue <= UINT8_MAX);
profilingReturn_ = offsets.profilingReturn;
u.func.profilingJumpToProfilingReturn_ = profilingReturn_ - offsets.profilingJump;
u.func.profilingEpilogueToProfilingReturn_ = profilingReturn_ - offsets.profilingEpilogue;
MOZ_ASSERT(offsets.nonProfilingEntry < offsets.end);
end_ = offsets.end;
}
static size_t
NullableStringLength(const char* chars)
{
return chars ? strlen(chars) : 0;
}
size_t
CacheableChars::serializedSize() const
{
return sizeof(uint32_t) + NullableStringLength(get());
}
uint8_t*
CacheableChars::serialize(uint8_t* cursor) const
{
uint32_t length = NullableStringLength(get());
cursor = WriteBytes(cursor, &length, sizeof(uint32_t));
cursor = WriteBytes(cursor, get(), length);
return cursor;
}
const uint8_t*
CacheableChars::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
{
uint32_t length;
cursor = ReadBytes(cursor, &length, sizeof(uint32_t));
reset(cx->pod_calloc<char>(length + 1));
if (!get())
return nullptr;
cursor = ReadBytes(cursor, get(), length);
return cursor;
}
bool
CacheableChars::clone(JSContext* cx, CacheableChars* out) const
{
uint32_t length = NullableStringLength(get());
UniqueChars chars(cx->pod_calloc<char>(length + 1));
if (!chars)
return false;
PodCopy(chars.get(), get(), length);
*out = Move(chars);
return true;
}
size_t
CacheableChars::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
return mallocSizeOf(get());
}
size_t
ExportMap::serializedSize() const
{
return SerializedVectorSize(fieldNames) +
SerializedPodVectorSize(fieldsToExports) +
SerializedPodVectorSize(exportFuncIndices);
}
uint8_t*
ExportMap::serialize(uint8_t* cursor) const
{
cursor = SerializeVector(cursor, fieldNames);
cursor = SerializePodVector(cursor, fieldsToExports);
cursor = SerializePodVector(cursor, exportFuncIndices);
return cursor;
}
const uint8_t*
ExportMap::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
{
(cursor = DeserializeVector(cx, cursor, &fieldNames)) &&
(cursor = DeserializePodVector(cx, cursor, &fieldsToExports)) &&
(cursor = DeserializePodVector(cx, cursor, &exportFuncIndices));
return cursor;
}
bool
ExportMap::clone(JSContext* cx, ExportMap* map) const
{
return CloneVector(cx, fieldNames, &map->fieldNames) &&
ClonePodVector(cx, fieldsToExports, &map->fieldsToExports) &&
ClonePodVector(cx, exportFuncIndices, &map->exportFuncIndices);
}
size_t
ExportMap::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
return SizeOfVectorExcludingThis(fieldNames, mallocSizeOf) &&
fieldsToExports.sizeOfExcludingThis(mallocSizeOf) &&
exportFuncIndices.sizeOfExcludingThis(mallocSizeOf);
}
size_t
ModuleData::serializedSize() const
{
return sizeof(pod()) +
codeBytes +
SerializedVectorSize(imports) +
SerializedVectorSize(exports) +
SerializedPodVectorSize(heapAccesses) +
SerializedPodVectorSize(codeRanges) +
SerializedPodVectorSize(callSites) +
SerializedPodVectorSize(callThunks) +
SerializedVectorSize(prettyFuncNames) +
filename.serializedSize();
}
uint8_t*
ModuleData::serialize(uint8_t* cursor) const
{
cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
cursor = WriteBytes(cursor, code.get(), codeBytes);
cursor = SerializeVector(cursor, imports);
cursor = SerializeVector(cursor, exports);
cursor = SerializePodVector(cursor, heapAccesses);
cursor = SerializePodVector(cursor, codeRanges);
cursor = SerializePodVector(cursor, callSites);
cursor = SerializePodVector(cursor, callThunks);
cursor = SerializeVector(cursor, prettyFuncNames);
cursor = filename.serialize(cursor);
return cursor;
}
/* static */ const uint8_t*
ModuleData::deserialize(ExclusiveContext* cx, const uint8_t* cursor)
{
cursor = ReadBytes(cursor, &pod(), sizeof(pod()));
code = AllocateCode(cx, totalBytes());
if (!code)
return nullptr;
cursor = ReadBytes(cursor, code.get(), codeBytes);
(cursor = DeserializeVector(cx, cursor, &imports)) &&
(cursor = DeserializeVector(cx, cursor, &exports)) &&
(cursor = DeserializePodVector(cx, cursor, &heapAccesses)) &&
(cursor = DeserializePodVector(cx, cursor, &codeRanges)) &&
(cursor = DeserializePodVector(cx, cursor, &callSites)) &&
(cursor = DeserializePodVector(cx, cursor, &callThunks)) &&
(cursor = DeserializeVector(cx, cursor, &prettyFuncNames)) &&
(cursor = filename.deserialize(cx, cursor));
return cursor;
}
bool
ModuleData::clone(JSContext* cx, ModuleData* out) const
{
out->pod() = pod();
out->code = AllocateCode(cx, totalBytes());
if (!out->code)
return false;
memcpy(out->code.get(), code.get(), codeBytes);
return CloneVector(cx, imports, &out->imports) &&
CloneVector(cx, exports, &out->exports) &&
ClonePodVector(cx, heapAccesses, &out->heapAccesses) &&
ClonePodVector(cx, codeRanges, &out->codeRanges) &&
ClonePodVector(cx, callSites, &out->callSites) &&
ClonePodVector(cx, callThunks, &out->callThunks) &&
CloneVector(cx, prettyFuncNames, &out->prettyFuncNames) &&
filename.clone(cx, &out->filename);
}
size_t
ModuleData::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
{
// Module::addSizeOfMisc takes care of code and global memory.
return SizeOfVectorExcludingThis(imports, mallocSizeOf) +
SizeOfVectorExcludingThis(exports, mallocSizeOf) +
heapAccesses.sizeOfExcludingThis(mallocSizeOf) +
codeRanges.sizeOfExcludingThis(mallocSizeOf) +
callSites.sizeOfExcludingThis(mallocSizeOf) +
callThunks.sizeOfExcludingThis(mallocSizeOf) +
prettyFuncNames.sizeOfExcludingThis(mallocSizeOf) +
filename.sizeOfExcludingThis(mallocSizeOf);
}
uint8_t*
Module::rawHeapPtr() const
{
return const_cast<Module*>(this)->rawHeapPtr();
}
uint8_t*&
Module::rawHeapPtr()
{
return *(uint8_t**)(globalData() + HeapGlobalDataOffset);
}
WasmActivation*&
Module::activation()
{
MOZ_ASSERT(dynamicallyLinked_);
return *reinterpret_cast<WasmActivation**>(globalData() + ActivationGlobalDataOffset);
}
void
Module::specializeToHeap(ArrayBufferObjectMaybeShared* heap)
{
MOZ_ASSERT(usesHeap());
MOZ_ASSERT_IF(heap->is<ArrayBufferObject>(), heap->as<ArrayBufferObject>().isWasm());
MOZ_ASSERT(!heap_);
MOZ_ASSERT(!rawHeapPtr());
uint8_t* ptrBase = heap->dataPointerEither().unwrap(/*safe - protected by Module methods*/);
uint32_t heapLength = heap->byteLength();
#if defined(JS_CODEGEN_X86)
// An access is out-of-bounds iff
// ptr + offset + data-type-byte-size > heapLength
// i.e. ptr > heapLength - data-type-byte-size - offset. data-type-byte-size
// and offset are already included in the addend so we
// just have to add the heap length here.
for (const HeapAccess& access : module_->heapAccesses) {
if (access.hasLengthCheck())
X86Encoding::AddInt32(access.patchLengthAt(code()), heapLength);
void* addr = access.patchHeapPtrImmAt(code());
uint32_t disp = reinterpret_cast<uint32_t>(X86Encoding::GetPointer(addr));
MOZ_ASSERT(disp <= INT32_MAX);
X86Encoding::SetPointer(addr, (void*)(ptrBase + disp));
}
#elif defined(JS_CODEGEN_X64)
// Even with signal handling being used for most bounds checks, there may be
// atomic operations that depend on explicit checks.
//
// If we have any explicit bounds checks, we need to patch the heap length
// checks at the right places. All accesses that have been recorded are the
// only ones that need bound checks (see also
// CodeGeneratorX64::visitAsmJS{Load,Store,CompareExchange,Exchange,AtomicBinop}Heap)
for (const HeapAccess& access : module_->heapAccesses) {
// See comment above for x86 codegen.
if (access.hasLengthCheck())
X86Encoding::AddInt32(access.patchLengthAt(code()), heapLength);
}
#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \
defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
for (const HeapAccess& access : module_->heapAccesses)
Assembler::UpdateBoundsCheck(heapLength, (Instruction*)(access.insnOffset() + code()));
#endif
heap_ = heap;
rawHeapPtr() = ptrBase;
}
void
Module::despecializeFromHeap(ArrayBufferObjectMaybeShared* heap)
{
// heap_/rawHeapPtr can be null if this module holds cloned code from
// another dynamically-linked module which we are despecializing from that
// module's heap.
MOZ_ASSERT_IF(heap_, heap_ == heap);
MOZ_ASSERT_IF(rawHeapPtr(), rawHeapPtr() == heap->dataPointerEither().unwrap());
#if defined(JS_CODEGEN_X86)
uint32_t heapLength = heap->byteLength();
uint8_t* ptrBase = heap->dataPointerEither().unwrap(/*safe - used for value*/);
for (unsigned i = 0; i < module_->heapAccesses.length(); i++) {
const HeapAccess& access = module_->heapAccesses[i];
if (access.hasLengthCheck())
X86Encoding::AddInt32(access.patchLengthAt(code()), -heapLength);
void* addr = access.patchHeapPtrImmAt(code());
uint8_t* ptr = reinterpret_cast<uint8_t*>(X86Encoding::GetPointer(addr));
MOZ_ASSERT(ptr >= ptrBase);
X86Encoding::SetPointer(addr, reinterpret_cast<void*>(ptr - ptrBase));
}
#elif defined(JS_CODEGEN_X64)
uint32_t heapLength = heap->byteLength();
for (unsigned i = 0; i < module_->heapAccesses.length(); i++) {
const HeapAccess& access = module_->heapAccesses[i];
if (access.hasLengthCheck())
X86Encoding::AddInt32(access.patchLengthAt(code()), -heapLength);
}
#endif
heap_ = nullptr;
rawHeapPtr() = nullptr;
}
bool
Module::sendCodeRangesToProfiler(JSContext* cx)
{
bool enabled = false;
#ifdef JS_ION_PERF
enabled |= PerfFuncEnabled();
#endif
#ifdef MOZ_VTUNE
enabled |= IsVTuneProfilingActive();
#endif
if (!enabled)
return true;
for (const CodeRange& codeRange : module_->codeRanges) {
if (!codeRange.isFunction())
continue;
uintptr_t start = uintptr_t(code() + codeRange.begin());
uintptr_t end = uintptr_t(code() + codeRange.end());
uintptr_t size = end - start;
UniqueChars owner;
const char* name = getFuncName(cx, codeRange.funcIndex(), &owner);
if (!name)
return false;
// Avoid "unused" warnings
(void)start;
(void)size;
(void)name;
#ifdef JS_ION_PERF
if (PerfFuncEnabled()) {
const char* file = module_->filename.get();
unsigned line = codeRange.funcLineOrBytecode();
unsigned column = 0;
writePerfSpewerAsmJSFunctionMap(start, size, file, line, column, name);
}
#endif
#ifdef MOZ_VTUNE
if (IsVTuneProfilingActive()) {
unsigned method_id = iJIT_GetNewMethodID();
if (method_id == 0)
return true;
iJIT_Method_Load method;
method.method_id = method_id;
method.method_name = const_cast<char*>(name);
method.method_load_address = (void*)start;
method.method_size = size;
method.line_number_size = 0;
method.line_number_table = nullptr;
method.class_id = 0;
method.class_file_name = nullptr;
method.source_file_name = nullptr;
iJIT_NotifyEvent(iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED, (void*)&method);
}
#endif
}
return true;
}
bool
Module::setProfilingEnabled(JSContext* cx, bool enabled)
{
MOZ_ASSERT(dynamicallyLinked_);
MOZ_ASSERT(!activation());
if (profilingEnabled_ == enabled)
return true;
// 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.
if (enabled) {
if (!funcLabels_.resize(module_->numFuncs)) {
ReportOutOfMemory(cx);
return false;
}
for (const CodeRange& codeRange : module_->codeRanges) {
if (!codeRange.isFunction())
continue;
UniqueChars owner;
const char* funcName = getFuncName(cx, codeRange.funcIndex(), &owner);
if (!funcName)
return false;
UniqueChars label(JS_smprintf("%s (%s:%u)",
funcName,
module_->filename.get(),
codeRange.funcLineOrBytecode()));
if (!label) {
ReportOutOfMemory(cx);
return false;
}
funcLabels_[codeRange.funcIndex()] = Move(label);
}
} else {
funcLabels_.clear();
}
// Patch callsites and returns to execute profiling prologues/epilogues.
{
AutoWritableJitCode awjc(cx->runtime(), code(), codeBytes());
AutoFlushICache afc("Module::setProfilingEnabled");
AutoFlushICache::setRange(uintptr_t(code()), codeBytes());
for (const CallSite& callSite : module_->callSites)
EnableProfilingPrologue(*this, callSite, enabled);
for (const CallThunk& callThunk : module_->callThunks)
EnableProfilingThunk(*this, callThunk, enabled);
for (const CodeRange& codeRange : module_->codeRanges)
EnableProfilingEpilogue(*this, codeRange, enabled);
}
// Update the function-pointer tables to point to profiling prologues.
for (FuncPtrTable& table : funcPtrTables_) {
auto array = reinterpret_cast<void**>(globalData() + table.globalDataOffset);
for (size_t i = 0; i < table.numElems; i++) {
const CodeRange* codeRange = lookupCodeRange(array[i]);
// Don't update entries for the BadIndirectCall exit.
if (codeRange->isErrorExit())
continue;
void* from = code() + codeRange->funcNonProfilingEntry();
void* to = code() + codeRange->funcProfilingEntry();
if (!enabled)
Swap(from, to);
MOZ_ASSERT(array[i] == from);
array[i] = to;
}
}
profilingEnabled_ = enabled;
return true;
}
Module::ImportExit&
Module::importToExit(const Import& import)
{
return *reinterpret_cast<ImportExit*>(globalData() + import.exitGlobalDataOffset());
}
bool
Module::clone(JSContext* cx, const StaticLinkData& link, Module* out) const
{
MOZ_ASSERT(dynamicallyLinked_);
// The out->module_ field was already cloned and initialized when 'out' was
// constructed. This function should clone the rest.
MOZ_ASSERT(out->module_);
out->profilingEnabled_ = profilingEnabled_;
if (!CloneVector(cx, funcLabels_, &out->funcLabels_))
return false;
#ifdef DEBUG
// Put the symbolic links back to -1 so PatchDataWithValueCheck assertions
// in Module::staticallyLink are valid.
for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
void* callee = AddressOf(imm, cx);
const Uint32Vector& offsets = link.symbolicLinks[imm];
for (uint32_t offset : offsets) {
jit::Assembler::PatchDataWithValueCheck(jit::CodeLocationLabel(out->code() + offset),
jit::PatchedImmPtr((void*)-1),
jit::PatchedImmPtr(callee));
}
}
#endif
// If the copied machine code has been specialized to the heap, it must be
// unspecialized in the copy.
if (usesHeap())
out->despecializeFromHeap(heap_);
return true;
}
Module::Module(UniqueModuleData module)
: module_(Move(module)),
staticallyLinked_(false),
interrupt_(nullptr),
outOfBounds_(nullptr),
dynamicallyLinked_(false),
profilingEnabled_(false)
{
*(double*)(globalData() + NaN64GlobalDataOffset) = GenericNaN();
*(float*)(globalData() + NaN32GlobalDataOffset) = GenericNaN();
#ifdef DEBUG
uint32_t lastEnd = 0;
for (const CodeRange& cr : module_->codeRanges) {
MOZ_ASSERT(cr.begin() >= lastEnd);
lastEnd = cr.end();
}
#endif
}
Module::~Module()
{
for (unsigned i = 0; i < imports().length(); i++) {
ImportExit& exit = importToExit(imports()[i]);
if (exit.baselineScript)
exit.baselineScript->removeDependentWasmModule(*this, i);
}
}
/* virtual */ void
Module::trace(JSTracer* trc)
{
for (const Import& import : imports()) {
if (importToExit(import).fun)
TraceEdge(trc, &importToExit(import).fun, "wasm function import");
}
if (heap_)
TraceEdge(trc, &heap_, "wasm buffer");
}
/* virtual */ void
Module::addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code, size_t* data)
{
*code += codeBytes();
*data += mallocSizeOf(this) +
globalBytes() +
mallocSizeOf(module_.get()) +
module_->sizeOfExcludingThis(mallocSizeOf) +
funcPtrTables_.sizeOfExcludingThis(mallocSizeOf) +
SizeOfVectorExcludingThis(funcLabels_, mallocSizeOf);
}
/* virtual */ bool
Module::mutedErrors() const
{
// WebAssembly code is always CORS-same-origin and so errors are never
// muted. For asm.js, muting depends on the ScriptSource containing the
// asm.js so this function is overridden by AsmJSModule.
return false;
}
/* virtual */ const char16_t*
Module::displayURL() const
{
// WebAssembly code does not have `//# sourceURL`.
return nullptr;
}
bool
Module::containsFunctionPC(void* pc) const
{
return pc >= code() && pc < (code() + module_->functionBytes);
}
bool
Module::containsCodePC(void* pc) const
{
return pc >= code() && pc < (code() + codeBytes());
}
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*
Module::lookupCallSite(void* returnAddress) const
{
uint32_t target = ((uint8_t*)returnAddress) - code();
size_t lowerBound = 0;
size_t upperBound = module_->callSites.length();
size_t match;
if (!BinarySearch(CallSiteRetAddrOffset(module_->callSites), lowerBound, upperBound, target, &match))
return nullptr;
return &module_->callSites[match];
}
const CodeRange*
Module::lookupCodeRange(void* pc) const
{
CodeRange::PC target((uint8_t*)pc - code());
size_t lowerBound = 0;
size_t upperBound = module_->codeRanges.length();
size_t match;
if (!BinarySearch(module_->codeRanges, lowerBound, upperBound, target, &match))
return nullptr;
return &module_->codeRanges[match];
}
struct HeapAccessOffset
{
const HeapAccessVector& accesses;
explicit HeapAccessOffset(const HeapAccessVector& accesses) : accesses(accesses) {}
uintptr_t operator[](size_t index) const {
return accesses[index].insnOffset();
}
};
const HeapAccess*
Module::lookupHeapAccess(void* pc) const
{
MOZ_ASSERT(containsFunctionPC(pc));
uint32_t target = ((uint8_t*)pc) - code();
size_t lowerBound = 0;
size_t upperBound = module_->heapAccesses.length();
size_t match;
if (!BinarySearch(HeapAccessOffset(module_->heapAccesses), lowerBound, upperBound, target, &match))
return nullptr;
return &module_->heapAccesses[match];
}
bool
Module::staticallyLink(ExclusiveContext* cx, const StaticLinkData& linkData)
{
MOZ_ASSERT(!dynamicallyLinked_);
MOZ_ASSERT(!staticallyLinked_);
staticallyLinked_ = true;
// Push a JitContext for benefit of IsCompilingAsmJS and delay flushing
// until Module::dynamicallyLink.
JitContext jcx(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread()));
MOZ_ASSERT(IsCompilingAsmJS());
AutoFlushICache afc("Module::staticallyLink", /* inhibit = */ true);
AutoFlushICache::setRange(uintptr_t(code()), codeBytes());
interrupt_ = code() + linkData.pod.interruptOffset;
outOfBounds_ = code() + linkData.pod.outOfBoundsOffset;
for (StaticLinkData::InternalLink link : linkData.internalLinks) {
uint8_t* patchAt = code() + link.patchAtOffset;
void* target = code() + link.targetOffset;
// If the target of an InternalLink is the non-profiling entry of a
// function, then we assume it is for a call that wants to call the
// profiling entry when profiling is enabled. Note that the target may
// be in the middle of a function (e.g., for a switch table) and in
// these cases we should not modify the target.
if (profilingEnabled_) {
if (const CodeRange* cr = lookupCodeRange(target)) {
if (cr->isFunction() && link.targetOffset == cr->funcNonProfilingEntry())
target = code() + cr->funcProfilingEntry();
}
}
if (link.isRawPointerPatch())
*(void**)(patchAt) = target;
else
Assembler::PatchInstructionImmediate(patchAt, PatchedImmPtr(target));
}
for (auto imm : MakeEnumeratedRange(SymbolicAddress::Limit)) {
const Uint32Vector& offsets = linkData.symbolicLinks[imm];
for (size_t i = 0; i < offsets.length(); i++) {
uint8_t* patchAt = code() + offsets[i];
void* target = AddressOf(imm, cx);
Assembler::PatchDataWithValueCheck(CodeLocationLabel(patchAt),
PatchedImmPtr(target),
PatchedImmPtr((void*)-1));
}
}
for (const StaticLinkData::FuncPtrTable& table : linkData.funcPtrTables) {
auto array = reinterpret_cast<void**>(globalData() + table.globalDataOffset);
for (size_t i = 0; i < table.elemOffsets.length(); i++) {
uint8_t* elem = code() + table.elemOffsets[i];
const CodeRange* codeRange = lookupCodeRange(elem);
if (profilingEnabled_ && !codeRange->isErrorExit())
elem = code() + codeRange->funcProfilingEntry();
array[i] = elem;
}
}
// CodeRangeVector, CallSiteVector and the code technically have all the
// necessary info to do all the updates necessary in setProfilingEnabled.
// However, to simplify the finding of function-pointer table sizes and
// global-data offsets, save just that information here.
if (!funcPtrTables_.appendAll(linkData.funcPtrTables)) {
ReportOutOfMemory(cx);
return false;
}
return true;
}
static bool
WasmCall(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedFunction callee(cx, &args.callee().as<JSFunction>());
Module& module = ExportedFunctionToModuleObject(callee)->module();
uint32_t exportIndex = ExportedFunctionToIndex(callee);
return module.callExport(cx, exportIndex, args);
}
static JSFunction*
NewExportedFunction(JSContext* cx, Handle<WasmModuleObject*> moduleObj, const ExportMap& exportMap,
uint32_t exportIndex)
{
Module& module = moduleObj->module();
unsigned numArgs = module.exports()[exportIndex].sig().args().length();
RootedAtom name(cx, module.getFuncAtom(cx, exportMap.exportFuncIndices[exportIndex]));
if (!name)
return nullptr;
JSFunction* fun = NewNativeConstructor(cx, WasmCall, numArgs, name,
gc::AllocKind::FUNCTION_EXTENDED, GenericObject,
JSFunction::ASMJS_CTOR);
if (!fun)
return nullptr;
fun->setExtendedSlot(FunctionExtended::WASM_MODULE_SLOT, ObjectValue(*moduleObj));
fun->setExtendedSlot(FunctionExtended::WASM_EXPORT_INDEX_SLOT, Int32Value(exportIndex));
return fun;
}
static bool
CreateExportObject(JSContext* cx,
Handle<WasmModuleObject*> moduleObj,
Handle<ArrayBufferObjectMaybeShared*> heap,
const ExportMap& exportMap,
const ExportVector& exports,
MutableHandleObject exportObj)
{
MOZ_ASSERT(exportMap.exportFuncIndices.length() == exports.length());
MOZ_ASSERT(exportMap.fieldNames.length() == exportMap.fieldsToExports.length());
for (size_t fieldIndex = 0; fieldIndex < exportMap.fieldNames.length(); fieldIndex++) {
const char* fieldName = exportMap.fieldNames[fieldIndex].get();
if (!*fieldName) {
MOZ_ASSERT(!exportObj);
uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
if (exportIndex == ExportMap::MemoryExport) {
MOZ_ASSERT(heap);
exportObj.set(heap);
} else {
exportObj.set(NewExportedFunction(cx, moduleObj, exportMap, exportIndex));
if (!exportObj)
return false;
}
break;
}
}
Rooted<ValueVector> vals(cx, ValueVector(cx));
for (size_t exportIndex = 0; exportIndex < exports.length(); exportIndex++) {
JSFunction* fun = NewExportedFunction(cx, moduleObj, exportMap, exportIndex);
if (!fun || !vals.append(ObjectValue(*fun)))
return false;
}
if (!exportObj) {
exportObj.set(JS_NewPlainObject(cx));
if (!exportObj)
return false;
}
for (size_t fieldIndex = 0; fieldIndex < exportMap.fieldNames.length(); fieldIndex++) {
const char* fieldName = exportMap.fieldNames[fieldIndex].get();
if (!*fieldName)
continue;
JSAtom* atom = AtomizeUTF8Chars(cx, fieldName, strlen(fieldName));
if (!atom)
return false;
RootedId id(cx, AtomToId(atom));
RootedValue val(cx);
uint32_t exportIndex = exportMap.fieldsToExports[fieldIndex];
if (exportIndex == ExportMap::MemoryExport)
val = ObjectValue(*heap);
else
val = vals[exportIndex];
if (!JS_DefinePropertyById(cx, exportObj, id, val, JSPROP_ENUMERATE))
return false;
}
return true;
}
bool
Module::dynamicallyLink(JSContext* cx,
Handle<WasmModuleObject*> moduleObj,
Handle<ArrayBufferObjectMaybeShared*> heap,
Handle<FunctionVector> importArgs,
const ExportMap& exportMap,
MutableHandleObject exportObj)
{
MOZ_ASSERT(this == &moduleObj->module());
MOZ_ASSERT(staticallyLinked_);
MOZ_ASSERT(!dynamicallyLinked_);
dynamicallyLinked_ = true;
// Push a JitContext for benefit of IsCompilingAsmJS and flush the ICache.
// We've been inhibiting flushing up to this point so flush it all now.
JitContext jcx(CompileRuntime::get(cx->compartment()->runtimeFromAnyThread()));
MOZ_ASSERT(IsCompilingAsmJS());
AutoFlushICache afc("Module::dynamicallyLink");
AutoFlushICache::setRange(uintptr_t(code()), codeBytes());
// Initialize imports with actual imported values.
MOZ_ASSERT(importArgs.length() == imports().length());
for (size_t i = 0; i < imports().length(); i++) {
const Import& import = imports()[i];
ImportExit& exit = importToExit(import);
exit.code = code() + import.interpExitCodeOffset();
exit.fun = importArgs[i];
exit.baselineScript = nullptr;
}
// Specialize code to the actual heap.
if (usesHeap())
specializeToHeap(heap);
// See AllocateCode comment above.
if (!ExecutableAllocator::makeExecutable(code(), codeBytes())) {
ReportOutOfMemory(cx);
return false;
}
if (!sendCodeRangesToProfiler(cx))
return false;
return CreateExportObject(cx, moduleObj, heap, exportMap, exports(), exportObj);
}
SharedMem<uint8_t*>
Module::heap() const
{
MOZ_ASSERT(dynamicallyLinked_);
MOZ_ASSERT(usesHeap());
MOZ_ASSERT(rawHeapPtr());
return hasSharedHeap()
? SharedMem<uint8_t*>::shared(rawHeapPtr())
: SharedMem<uint8_t*>::unshared(rawHeapPtr());
}
size_t
Module::heapLength() const
{
MOZ_ASSERT(dynamicallyLinked_);
MOZ_ASSERT(usesHeap());
return heap_->byteLength();
}
void
Module::deoptimizeImportExit(uint32_t importIndex)
{
MOZ_ASSERT(dynamicallyLinked_);
const Import& import = imports()[importIndex];
ImportExit& exit = importToExit(import);
exit.code = code() + import.interpExitCodeOffset();
exit.baselineScript = nullptr;
}
bool
Module::callExport(JSContext* cx, uint32_t exportIndex, CallArgs args)
{
MOZ_ASSERT(dynamicallyLinked_);
const Export& exp = exports()[exportIndex];
// Enable/disable profiling in the Module to match the current global
// profiling state. Don't do this if the Module is already active on the
// stack since this would leave the Module in a state where profiling is
// enabled but the stack isn't unwindable.
if (profilingEnabled() != cx->runtime()->spsProfiler.enabled() && !activation()) {
if (!setProfilingEnabled(cx, cx->runtime()->spsProfiler.enabled()))
return false;
}
// The calling convention for an external call into wasm is to pass an
// array of 16-byte values where each value contains either a coerced int32
// (in the low word), a double value (in the low dword) or a SIMD vector
// value, with the coercions specified by the wasm signature. The external
// entry point unpacks this array into the system-ABI-specified registers
// and stack memory and then calls into the internal entry point. The return
// value is stored in the first element of the array (which, therefore, must
// have length >= 1).
Vector<Module::EntryArg, 8> coercedArgs(cx);
if (!coercedArgs.resize(Max<size_t>(1, exp.sig().args().length())))
return false;
RootedValue v(cx);
for (unsigned i = 0; i < exp.sig().args().length(); ++i) {
v = i < args.length() ? args[i] : UndefinedValue();
switch (exp.sig().arg(i)) {
case ValType::I32:
if (!ToInt32(cx, v, (int32_t*)&coercedArgs[i]))
return false;
break;
case ValType::I64:
MOZ_CRASH("int64");
case ValType::F32:
if (!RoundFloat32(cx, v, (float*)&coercedArgs[i]))
return false;
break;
case ValType::F64:
if (!ToNumber(cx, v, (double*)&coercedArgs[i]))
return false;
break;
case ValType::I32x4: {
SimdConstant simd;
if (!ToSimdConstant<Int32x4>(cx, v, &simd))
return false;
memcpy(&coercedArgs[i], simd.asInt32x4(), Simd128DataSize);
break;
}
case ValType::F32x4: {
SimdConstant simd;
if (!ToSimdConstant<Float32x4>(cx, v, &simd))
return false;
memcpy(&coercedArgs[i], simd.asFloat32x4(), Simd128DataSize);
break;
}
case ValType::B32x4: {
SimdConstant simd;
if (!ToSimdConstant<Bool32x4>(cx, v, &simd))
return false;
// Bool32x4 uses the same representation as Int32x4.
memcpy(&coercedArgs[i], simd.asInt32x4(), Simd128DataSize);
break;
}
case ValType::Limit:
MOZ_CRASH("Limit");
}
}
{
// Push a WasmActivation to describe the wasm frames we're about to push
// when running this module. Additionally, push a JitActivation so that
// the optimized wasm-to-Ion FFI call path (which we want to be very
// fast) can avoid doing so. The JitActivation is marked as inactive so
// stack iteration will skip over it.
WasmActivation activation(cx, *this);
JitActivation jitActivation(cx, /* active */ false);
// Call the per-exported-function trampoline created by GenerateEntry.
auto entry = JS_DATA_TO_FUNC_PTR(EntryFuncPtr, code() + exp.stubOffset());
if (!CALL_GENERATED_2(entry, coercedArgs.begin(), globalData()))
return false;
}
if (args.isConstructing()) {
// By spec, when a function is called as a constructor and this function
// returns a primary type, which is the case for all wasm exported
// functions, the returned value is discarded and an empty object is
// returned instead.
PlainObject* obj = NewBuiltinClassInstance<PlainObject>(cx);
if (!obj)
return false;
args.rval().set(ObjectValue(*obj));
return true;
}
JSObject* simdObj;
switch (exp.sig().ret()) {
case ExprType::Void:
args.rval().set(UndefinedValue());
break;
case ExprType::I32:
args.rval().set(Int32Value(*(int32_t*)&coercedArgs[0]));
break;
case ExprType::I64:
MOZ_CRASH("int64");
case ExprType::F32:
case ExprType::F64:
args.rval().set(NumberValue(*(double*)&coercedArgs[0]));
break;
case ExprType::I32x4:
simdObj = CreateSimd<Int32x4>(cx, (int32_t*)&coercedArgs[0]);
if (!simdObj)
return false;
args.rval().set(ObjectValue(*simdObj));
break;
case ExprType::F32x4:
simdObj = CreateSimd<Float32x4>(cx, (float*)&coercedArgs[0]);
if (!simdObj)
return false;
args.rval().set(ObjectValue(*simdObj));
break;
case ExprType::B32x4:
simdObj = CreateSimd<Bool32x4>(cx, (int32_t*)&coercedArgs[0]);
if (!simdObj)
return false;
args.rval().set(ObjectValue(*simdObj));
break;
case ExprType::Limit:
MOZ_CRASH("Limit");
}
return true;
}
bool
Module::callImport(JSContext* cx, uint32_t importIndex, unsigned argc, const Value* argv,
MutableHandleValue rval)
{
MOZ_ASSERT(dynamicallyLinked_);
const Import& import = imports()[importIndex];
RootedValue fval(cx, ObjectValue(*importToExit(import).fun));
if (!Invoke(cx, UndefinedValue(), fval, argc, argv, rval))
return false;
ImportExit& exit = importToExit(import);
// The exit may already have become optimized.
void* jitExitCode = code() + import.jitExitCodeOffset();
if (exit.code == jitExitCode)
return true;
// Test if the function is JIT compiled.
if (!exit.fun->hasScript())
return true;
JSScript* script = exit.fun->nonLazyScript();
if (!script->hasBaselineScript()) {
MOZ_ASSERT(!script->hasIonScript());
return true;
}
// Don't enable jit entry when we have a pending ion builder.
// Take the interpreter path which will link it and enable
// the fast path on the next call.
if (script->baselineScript()->hasPendingIonBuilder())
return true;
// Currently we can't rectify arguments. Therefore disable if argc is too low.
if (exit.fun->nargs() > import.sig().args().length())
return true;
// Ensure the argument types are included in the argument TypeSets stored in
// the TypeScript. This is necessary for Ion, because the import exit will
// use the skip-arg-checks entry point.
//
// Note that the TypeScript is never discarded while the script has a
// BaselineScript, so if those checks hold now they must hold at least until
// the BaselineScript is discarded and when that happens the import exit is
// patched back.
if (!TypeScript::ThisTypes(script)->hasType(TypeSet::UndefinedType()))
return true;
for (uint32_t i = 0; i < exit.fun->nargs(); i++) {
TypeSet::Type type = TypeSet::UnknownType();
switch (import.sig().args()[i]) {
case ValType::I32: type = TypeSet::Int32Type(); break;
case ValType::I64: MOZ_CRASH("NYI");
case ValType::F32: type = TypeSet::DoubleType(); break;
case ValType::F64: type = TypeSet::DoubleType(); break;
case ValType::I32x4: MOZ_CRASH("NYI");
case ValType::F32x4: MOZ_CRASH("NYI");
case ValType::B32x4: MOZ_CRASH("NYI");
case ValType::Limit: MOZ_CRASH("Limit");
}
if (!TypeScript::ArgTypes(script, i)->hasType(type))
return true;
}
// Let's optimize it!
if (!script->baselineScript()->addDependentWasmModule(cx, *this, importIndex))
return false;
exit.code = jitExitCode;
exit.baselineScript = script->baselineScript();
return true;
}
const char*
Module::prettyFuncName(uint32_t funcIndex) const
{
return module_->prettyFuncNames[funcIndex].get();
}
const char*
Module::getFuncName(JSContext* cx, uint32_t funcIndex, UniqueChars* owner) const
{
if (!module_->prettyFuncNames.empty())
return prettyFuncName(funcIndex);
char* chars = JS_smprintf("wasm-function[%u]", funcIndex);
if (!chars) {
ReportOutOfMemory(cx);
return nullptr;
}
owner->reset(chars);
return chars;
}
JSAtom*
Module::getFuncAtom(JSContext* cx, uint32_t funcIndex) const
{
UniqueChars owner;
const char* chars = getFuncName(cx, funcIndex, &owner);
if (!chars)
return nullptr;
JSAtom* atom = AtomizeUTF8Chars(cx, chars, strlen(chars));
if (!atom)
return nullptr;
return atom;
}
const char*
Module::profilingLabel(uint32_t funcIndex) const
{
MOZ_ASSERT(dynamicallyLinked_);
MOZ_ASSERT(profilingEnabled_);
return funcLabels_[funcIndex].get();
}
const Class WasmModuleObject::class_ = {
"WasmModuleObject",
JSCLASS_IS_ANONYMOUS | JSCLASS_DELAY_METADATA_CALLBACK |
JSCLASS_HAS_RESERVED_SLOTS(WasmModuleObject::RESERVED_SLOTS),
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
WasmModuleObject::finalize,
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
WasmModuleObject::trace
};
bool
WasmModuleObject::hasModule() const
{
MOZ_ASSERT(is<WasmModuleObject>());
return !getReservedSlot(MODULE_SLOT).isUndefined();
}
/* static */ void
WasmModuleObject::finalize(FreeOp* fop, JSObject* obj)
{
WasmModuleObject& moduleObj = obj->as<WasmModuleObject>();
if (moduleObj.hasModule())
fop->delete_(&moduleObj.module());
}
/* static */ void
WasmModuleObject::trace(JSTracer* trc, JSObject* obj)
{
WasmModuleObject& moduleObj = obj->as<WasmModuleObject>();
if (moduleObj.hasModule())
moduleObj.module().trace(trc);
}
/* static */ WasmModuleObject*
WasmModuleObject::create(ExclusiveContext* cx)
{
AutoSetNewObjectMetadata metadata(cx);
JSObject* obj = NewObjectWithGivenProto(cx, &WasmModuleObject::class_, nullptr);
if (!obj)
return nullptr;
return &obj->as<WasmModuleObject>();
}
bool
WasmModuleObject::init(Module* module)
{
MOZ_ASSERT(is<WasmModuleObject>());
MOZ_ASSERT(!hasModule());
if (!module)
return false;
setReservedSlot(MODULE_SLOT, PrivateValue(module));
return true;
}
Module&
WasmModuleObject::module() const
{
MOZ_ASSERT(is<WasmModuleObject>());
MOZ_ASSERT(hasModule());
return *(Module*)getReservedSlot(MODULE_SLOT).toPrivate();
}
void
WasmModuleObject::addSizeOfMisc(MallocSizeOf mallocSizeOf, size_t* code, size_t* data)
{
if (hasModule())
module().addSizeOfMisc(mallocSizeOf, code, data);
}
bool
wasm::IsExportedFunction(JSFunction* fun)
{
return fun->maybeNative() == WasmCall;
}
WasmModuleObject*
wasm::ExportedFunctionToModuleObject(JSFunction* fun)
{
MOZ_ASSERT(IsExportedFunction(fun));
const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_MODULE_SLOT);
return &v.toObject().as<WasmModuleObject>();
}
uint32_t
wasm::ExportedFunctionToIndex(JSFunction* fun)
{
MOZ_ASSERT(IsExportedFunction(fun));
const Value& v = fun->getExtendedSlot(FunctionExtended::WASM_EXPORT_INDEX_SLOT);
return v.toInt32();
}