summaryrefslogtreecommitdiffstats
path: root/js/src/wasm/WasmBCMemory.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/wasm/WasmBCMemory.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--js/src/wasm/WasmBCMemory.cpp2991
1 files changed, 2991 insertions, 0 deletions
diff --git a/js/src/wasm/WasmBCMemory.cpp b/js/src/wasm/WasmBCMemory.cpp
new file mode 100644
index 0000000000..f4e19d95e8
--- /dev/null
+++ b/js/src/wasm/WasmBCMemory.cpp
@@ -0,0 +1,2991 @@
+/* -*- 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/WasmBCClass.h"
+#include "wasm/WasmBCDefs.h"
+#include "wasm/WasmBCRegDefs.h"
+
+#include "jit/MacroAssembler-inl.h"
+
+#include "wasm/WasmBCClass-inl.h"
+#include "wasm/WasmBCCodegen-inl.h"
+#include "wasm/WasmBCRegDefs-inl.h"
+#include "wasm/WasmBCRegMgmt-inl.h"
+#include "wasm/WasmBCStkMgmt-inl.h"
+
+namespace js {
+namespace wasm {
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Heap access subroutines.
+
+// Bounds check elimination.
+//
+// We perform BCE on two kinds of address expressions: on constant heap pointers
+// that are known to be in the heap or will be handled by the out-of-bounds trap
+// handler; and on local variables that have been checked in dominating code
+// without being updated since.
+//
+// For an access through a constant heap pointer + an offset we can eliminate
+// the bounds check if the sum of the address and offset is below the sum of the
+// minimum memory length and the offset guard length.
+//
+// For an access through a local variable + an offset we can eliminate the
+// bounds check if the local variable has already been checked and has not been
+// updated since, and the offset is less than the guard limit.
+//
+// To track locals for which we can eliminate checks we use a bit vector
+// bceSafe_ that has a bit set for those locals whose bounds have been checked
+// and which have not subsequently been set. Initially this vector is zero.
+//
+// In straight-line code a bit is set when we perform a bounds check on an
+// access via the local and is reset when the variable is updated.
+//
+// In control flow, the bit vector is manipulated as follows. Each ControlItem
+// has a value bceSafeOnEntry, which is the value of bceSafe_ on entry to the
+// item, and a value bceSafeOnExit, which is initially ~0. On a branch (br,
+// brIf, brTable), we always AND the branch target's bceSafeOnExit with the
+// value of bceSafe_ at the branch point. On exiting an item by falling out of
+// it, provided we're not in dead code, we AND the current value of bceSafe_
+// into the item's bceSafeOnExit. Additional processing depends on the item
+// type:
+//
+// - After a block, set bceSafe_ to the block's bceSafeOnExit.
+//
+// - On loop entry, after pushing the ControlItem, set bceSafe_ to zero; the
+// back edges would otherwise require us to iterate to a fixedpoint.
+//
+// - After a loop, the bceSafe_ is left unchanged, because only fallthrough
+// control flow will reach that point and the bceSafe_ value represents the
+// correct state of the fallthrough path.
+//
+// - Set bceSafe_ to the ControlItem's bceSafeOnEntry at both the 'then' branch
+// and the 'else' branch.
+//
+// - After an if-then-else, set bceSafe_ to the if-then-else's bceSafeOnExit.
+//
+// - After an if-then, set bceSafe_ to the if-then's bceSafeOnExit AND'ed with
+// the if-then's bceSafeOnEntry.
+//
+// Finally, when the debugger allows locals to be mutated we must disable BCE
+// for references via a local, by returning immediately from bceCheckLocal if
+// compilerEnv_.debugEnabled() is true.
+
+void BaseCompiler::bceCheckLocal(MemoryAccessDesc* access, AccessCheck* check,
+ uint32_t local) {
+ // We only eliminate bounds checks for memory 0
+ if (access->memoryIndex() != 0) {
+ return;
+ }
+
+ if (local >= sizeof(BCESet) * 8) {
+ return;
+ }
+
+ uint32_t offsetGuardLimit =
+ GetMaxOffsetGuardLimit(moduleEnv_.hugeMemoryEnabled(0));
+
+ if ((bceSafe_ & (BCESet(1) << local)) &&
+ access->offset64() < offsetGuardLimit) {
+ check->omitBoundsCheck = true;
+ }
+
+ // The local becomes safe even if the offset is beyond the guard limit.
+ bceSafe_ |= (BCESet(1) << local);
+}
+
+void BaseCompiler::bceLocalIsUpdated(uint32_t local) {
+ if (local >= sizeof(BCESet) * 8) {
+ return;
+ }
+
+ bceSafe_ &= ~(BCESet(1) << local);
+}
+
+// Alignment check elimination.
+//
+// Alignment checks for atomic operations can be omitted if the pointer is a
+// constant and the pointer + offset is aligned. Alignment checking that can't
+// be omitted can still be simplified by checking only the pointer if the offset
+// is aligned.
+//
+// (In addition, alignment checking of the pointer can be omitted if the pointer
+// has been checked in dominating code, but we don't do that yet.)
+
+template <>
+RegI32 BaseCompiler::popConstMemoryAccess<RegI32>(MemoryAccessDesc* access,
+ AccessCheck* check) {
+ int32_t addrTemp;
+ MOZ_ALWAYS_TRUE(popConst(&addrTemp));
+ uint32_t addr = addrTemp;
+
+ uint32_t offsetGuardLimit = GetMaxOffsetGuardLimit(
+ moduleEnv_.hugeMemoryEnabled(access->memoryIndex()));
+
+ uint64_t ea = uint64_t(addr) + uint64_t(access->offset());
+ uint64_t limit =
+ moduleEnv_.memories[access->memoryIndex()].initialLength32() +
+ offsetGuardLimit;
+
+ check->omitBoundsCheck = ea < limit;
+ check->omitAlignmentCheck = (ea & (access->byteSize() - 1)) == 0;
+
+ // Fold the offset into the pointer if we can, as this is always
+ // beneficial.
+ if (ea <= UINT32_MAX) {
+ addr = uint32_t(ea);
+ access->clearOffset();
+ }
+
+ RegI32 r = needI32();
+ moveImm32(int32_t(addr), r);
+ return r;
+}
+
+#ifdef ENABLE_WASM_MEMORY64
+template <>
+RegI64 BaseCompiler::popConstMemoryAccess<RegI64>(MemoryAccessDesc* access,
+ AccessCheck* check) {
+ int64_t addrTemp;
+ MOZ_ALWAYS_TRUE(popConst(&addrTemp));
+ uint64_t addr = addrTemp;
+
+ uint32_t offsetGuardLimit = GetMaxOffsetGuardLimit(
+ moduleEnv_.hugeMemoryEnabled(access->memoryIndex()));
+
+ uint64_t ea = addr + access->offset64();
+ bool overflow = ea < addr;
+ uint64_t limit =
+ moduleEnv_.memories[access->memoryIndex()].initialLength64() +
+ offsetGuardLimit;
+
+ if (!overflow) {
+ check->omitBoundsCheck = ea < limit;
+ check->omitAlignmentCheck = (ea & (access->byteSize() - 1)) == 0;
+
+ // Fold the offset into the pointer if we can, as this is always
+ // beneficial.
+ addr = uint64_t(ea);
+ access->clearOffset();
+ }
+
+ RegI64 r = needI64();
+ moveImm64(int64_t(addr), r);
+ return r;
+}
+#endif
+
+template <typename RegType>
+RegType BaseCompiler::popMemoryAccess(MemoryAccessDesc* access,
+ AccessCheck* check) {
+ check->onlyPointerAlignment =
+ (access->offset64() & (access->byteSize() - 1)) == 0;
+
+ // If there's a constant it will have the correct type for RegType.
+ if (hasConst()) {
+ return popConstMemoryAccess<RegType>(access, check);
+ }
+
+ // If there's a local it will have the correct type for RegType.
+ uint32_t local;
+ if (peekLocal(&local)) {
+ bceCheckLocal(access, check, local);
+ }
+
+ return pop<RegType>();
+}
+
+#ifdef JS_64BIT
+static inline RegI64 RegPtrToRegIntptr(RegPtr r) {
+ return RegI64(Register64(Register(r)));
+}
+
+# ifndef WASM_HAS_HEAPREG
+static inline RegPtr RegIntptrToRegPtr(RegI64 r) {
+ return RegPtr(Register64(r).reg);
+}
+# endif
+#else
+static inline RegI32 RegPtrToRegIntptr(RegPtr r) { return RegI32(Register(r)); }
+
+# ifndef WASM_HAS_HEAPREG
+static inline RegPtr RegIntptrToRegPtr(RegI32 r) { return RegPtr(Register(r)); }
+# endif
+#endif
+
+void BaseCompiler::pushHeapBase(uint32_t memoryIndex) {
+ RegPtr heapBase = need<RegPtr>();
+
+#ifdef WASM_HAS_HEAPREG
+ if (memoryIndex == 0) {
+ move(RegPtr(HeapReg), heapBase);
+ push(RegPtrToRegIntptr(heapBase));
+ return;
+ }
+#endif
+
+#ifdef RABALDR_PIN_INSTANCE
+ movePtr(RegPtr(InstanceReg), heapBase);
+#else
+ fr.loadInstancePtr(heapBase);
+#endif
+
+ uint32_t offset = instanceOffsetOfMemoryBase(memoryIndex);
+ masm.loadPtr(Address(heapBase, offset), heapBase);
+ push(RegPtrToRegIntptr(heapBase));
+}
+
+void BaseCompiler::branchAddNoOverflow(uint64_t offset, RegI32 ptr, Label* ok) {
+ // The invariant holds because ptr is RegI32 - this is m32.
+ MOZ_ASSERT(offset <= UINT32_MAX);
+ masm.branchAdd32(Assembler::CarryClear, Imm32(uint32_t(offset)), ptr, ok);
+}
+
+#ifdef ENABLE_WASM_MEMORY64
+void BaseCompiler::branchAddNoOverflow(uint64_t offset, RegI64 ptr, Label* ok) {
+# if defined(JS_64BIT)
+ masm.branchAddPtr(Assembler::CarryClear, ImmWord(offset), Register64(ptr).reg,
+ ok);
+# else
+ masm.branchAdd64(Assembler::CarryClear, Imm64(offset), ptr, ok);
+# endif
+}
+#endif
+
+void BaseCompiler::branchTestLowZero(RegI32 ptr, Imm32 mask, Label* ok) {
+ masm.branchTest32(Assembler::Zero, ptr, mask, ok);
+}
+
+#ifdef ENABLE_WASM_MEMORY64
+void BaseCompiler::branchTestLowZero(RegI64 ptr, Imm32 mask, Label* ok) {
+# ifdef JS_64BIT
+ masm.branchTestPtr(Assembler::Zero, Register64(ptr).reg, mask, ok);
+# else
+ masm.branchTestPtr(Assembler::Zero, ptr.low, mask, ok);
+# endif
+}
+#endif
+
+void BaseCompiler::boundsCheck4GBOrLargerAccess(uint32_t memoryIndex,
+ RegPtr instance, RegI32 ptr,
+ Label* ok) {
+#ifdef JS_64BIT
+ // Extend the value to 64 bits, check the 64-bit value against the 64-bit
+ // bound, then chop back to 32 bits. On most platform the extending and
+ // chopping are no-ops. It's important that the value we end up with has
+ // flowed through the Spectre mask
+
+ // Note, ptr and ptr64 are the same register.
+ RegI64 ptr64 = fromI32(ptr);
+
+ // In principle there may be non-zero bits in the upper bits of the
+ // register; clear them.
+# ifdef RABALDR_ZERO_EXTENDS
+ masm.debugAssertCanonicalInt32(ptr);
+# else
+ masm.move32To64ZeroExtend(ptr, ptr64);
+# endif
+
+ boundsCheck4GBOrLargerAccess(memoryIndex, instance, ptr64, ok);
+
+ // Restore the value to the canonical form for a 32-bit value in a
+ // 64-bit register and/or the appropriate form for further use in the
+ // indexing instruction.
+# ifdef RABALDR_ZERO_EXTENDS
+ // The canonical value is zero-extended; we already have that.
+# else
+ masm.move64To32(ptr64, ptr);
+# endif
+#else
+ // No support needed, we have max 2GB heap on 32-bit
+ MOZ_CRASH("No 32-bit support");
+#endif
+}
+
+void BaseCompiler::boundsCheckBelow4GBAccess(uint32_t memoryIndex,
+ RegPtr instance, RegI32 ptr,
+ Label* ok) {
+ // If the memory's max size is known to be smaller than 64K pages exactly,
+ // we can use a 32-bit check and avoid extension and wrapping.
+ masm.wasmBoundsCheck32(
+ Assembler::Below, ptr,
+ Address(instance, instanceOffsetOfBoundsCheckLimit(memoryIndex)), ok);
+}
+
+void BaseCompiler::boundsCheck4GBOrLargerAccess(uint32_t memoryIndex,
+ RegPtr instance, RegI64 ptr,
+ Label* ok) {
+ // Any Spectre mitigation will appear to update the ptr64 register.
+ masm.wasmBoundsCheck64(
+ Assembler::Below, ptr,
+ Address(instance, instanceOffsetOfBoundsCheckLimit(memoryIndex)), ok);
+}
+
+void BaseCompiler::boundsCheckBelow4GBAccess(uint32_t memoryIndex,
+ RegPtr instance, RegI64 ptr,
+ Label* ok) {
+ // The bounds check limit is valid to 64 bits, so there's no sense in doing
+ // anything complicated here. There may be optimization paths here in the
+ // future and they may differ on 32-bit and 64-bit.
+ boundsCheck4GBOrLargerAccess(memoryIndex, instance, ptr, ok);
+}
+
+// Make sure the ptr could be used as an index register.
+static inline void ToValidIndex(MacroAssembler& masm, RegI32 ptr) {
+#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \
+ defined(JS_CODEGEN_RISCV64)
+ // When ptr is used as an index, it will be added to a 64-bit register.
+ // So we should explicitly promote ptr to 64-bit. Since now ptr holds a
+ // unsigned 32-bit value, we zero-extend it to 64-bit here.
+ masm.move32To64ZeroExtend(ptr, Register64(ptr));
+#endif
+}
+
+#if defined(ENABLE_WASM_MEMORY64)
+static inline void ToValidIndex(MacroAssembler& masm, RegI64 ptr) {}
+#endif
+
+// RegIndexType is RegI32 for Memory32 and RegI64 for Memory64.
+template <typename RegIndexType>
+void BaseCompiler::prepareMemoryAccess(MemoryAccessDesc* access,
+ AccessCheck* check, RegPtr instance,
+ RegIndexType ptr) {
+ uint32_t offsetGuardLimit = GetMaxOffsetGuardLimit(
+ moduleEnv_.hugeMemoryEnabled(access->memoryIndex()));
+
+ // Fold offset if necessary for further computations.
+ if (access->offset64() >= offsetGuardLimit ||
+ access->offset64() > UINT32_MAX ||
+ (access->isAtomic() && !check->omitAlignmentCheck &&
+ !check->onlyPointerAlignment)) {
+ Label ok;
+ branchAddNoOverflow(access->offset64(), ptr, &ok);
+ masm.wasmTrap(Trap::OutOfBounds, bytecodeOffset());
+ masm.bind(&ok);
+ access->clearOffset();
+ check->onlyPointerAlignment = true;
+ }
+
+ // Alignment check if required.
+
+ if (access->isAtomic() && !check->omitAlignmentCheck) {
+ MOZ_ASSERT(check->onlyPointerAlignment);
+ // We only care about the low pointer bits here.
+ Label ok;
+ branchTestLowZero(ptr, Imm32(access->byteSize() - 1), &ok);
+ masm.wasmTrap(Trap::UnalignedAccess, bytecodeOffset());
+ masm.bind(&ok);
+ }
+
+ // Ensure no instance if we don't need it.
+
+ if (moduleEnv_.hugeMemoryEnabled(access->memoryIndex()) &&
+ access->memoryIndex() == 0) {
+ // We have HeapReg and no bounds checking and need load neither
+ // memoryBase nor boundsCheckLimit from instance.
+ MOZ_ASSERT_IF(check->omitBoundsCheck, instance.isInvalid());
+ }
+#ifdef WASM_HAS_HEAPREG
+ // We have HeapReg and don't need to load the memoryBase from instance.
+ MOZ_ASSERT_IF(check->omitBoundsCheck && access->memoryIndex() == 0,
+ instance.isInvalid());
+#endif
+
+ // Bounds check if required.
+
+ if (!moduleEnv_.hugeMemoryEnabled(access->memoryIndex()) &&
+ !check->omitBoundsCheck) {
+ Label ok;
+#ifdef JS_64BIT
+ // The checking depends on how many bits are in the pointer and how many
+ // bits are in the bound.
+ static_assert(0x100000000 % PageSize == 0);
+ if (!moduleEnv_.memories[access->memoryIndex()]
+ .boundsCheckLimitIs32Bits() &&
+ MaxMemoryPages(
+ moduleEnv_.memories[access->memoryIndex()].indexType()) >=
+ Pages(0x100000000 / PageSize)) {
+ boundsCheck4GBOrLargerAccess(access->memoryIndex(), instance, ptr, &ok);
+ } else {
+ boundsCheckBelow4GBAccess(access->memoryIndex(), instance, ptr, &ok);
+ }
+#else
+ boundsCheckBelow4GBAccess(access->memoryIndex(), instance, ptr, &ok);
+#endif
+ masm.wasmTrap(Trap::OutOfBounds, bytecodeOffset());
+ masm.bind(&ok);
+ }
+
+ ToValidIndex(masm, ptr);
+}
+
+template <typename RegIndexType>
+void BaseCompiler::computeEffectiveAddress(MemoryAccessDesc* access) {
+ if (access->offset()) {
+ Label ok;
+ RegIndexType ptr = pop<RegIndexType>();
+ branchAddNoOverflow(access->offset64(), ptr, &ok);
+ masm.wasmTrap(Trap::OutOfBounds, bytecodeOffset());
+ masm.bind(&ok);
+ access->clearOffset();
+ push(ptr);
+ }
+}
+
+RegPtr BaseCompiler::maybeLoadMemoryBaseForAccess(
+ RegPtr instance, const MemoryAccessDesc* access) {
+#ifdef JS_CODEGEN_X86
+ // x86 adds the memory base to the wasm pointer directly using an addressing
+ // mode and doesn't require the memory base to be loaded to a register.
+ return RegPtr();
+#endif
+
+#ifdef WASM_HAS_HEAPREG
+ if (access->memoryIndex() == 0) {
+ return RegPtr(HeapReg);
+ }
+#endif
+ RegPtr memoryBase = needPtr();
+ uint32_t offset = instanceOffsetOfMemoryBase(access->memoryIndex());
+ masm.loadPtr(Address(instance, offset), memoryBase);
+ return memoryBase;
+}
+
+bool BaseCompiler::needInstanceForAccess(const MemoryAccessDesc* access,
+ const AccessCheck& check) {
+#ifndef WASM_HAS_HEAPREG
+ // Platform requires instance for memory base.
+ return true;
+#else
+ if (access->memoryIndex() != 0) {
+ // Need instance to load the memory base
+ return true;
+ }
+ return !moduleEnv_.hugeMemoryEnabled(access->memoryIndex()) &&
+ !check.omitBoundsCheck;
+#endif
+}
+
+RegPtr BaseCompiler::maybeLoadInstanceForAccess(const MemoryAccessDesc* access,
+ const AccessCheck& check) {
+ if (needInstanceForAccess(access, check)) {
+#ifdef RABALDR_PIN_INSTANCE
+ // NOTE, returning InstanceReg here depends for correctness on *ALL*
+ // clients not attempting to free this register and not push it on the value
+ // stack.
+ //
+ // We have assertions in place to guard against that, so the risk of the
+ // leaky abstraction is acceptable. performRegisterLeakCheck() will ensure
+ // that after every bytecode, the union of available registers from the
+ // regalloc and used registers from the stack equals the set of allocatable
+ // registers at startup. Thus if the instance is freed incorrectly it will
+ // end up in that union via the regalloc, and if it is pushed incorrectly it
+ // will end up in the union via the stack.
+ return RegPtr(InstanceReg);
+#else
+ RegPtr instance = need<RegPtr>();
+ fr.loadInstancePtr(instance);
+ return instance;
+#endif
+ }
+ return RegPtr::Invalid();
+}
+
+RegPtr BaseCompiler::maybeLoadInstanceForAccess(const MemoryAccessDesc* access,
+ const AccessCheck& check,
+ RegPtr specific) {
+ if (needInstanceForAccess(access, check)) {
+#ifdef RABALDR_PIN_INSTANCE
+ movePtr(RegPtr(InstanceReg), specific);
+#else
+ fr.loadInstancePtr(specific);
+#endif
+ return specific;
+ }
+ return RegPtr::Invalid();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Load and store.
+
+void BaseCompiler::executeLoad(MemoryAccessDesc* access, AccessCheck* check,
+ RegPtr instance, RegPtr memoryBase, RegI32 ptr,
+ AnyReg dest, RegI32 temp) {
+ // Emit the load. At this point, 64-bit offsets will have been resolved.
+#if defined(JS_CODEGEN_X64)
+ MOZ_ASSERT(temp.isInvalid());
+ Operand srcAddr(memoryBase, ptr, TimesOne, access->offset());
+
+ if (dest.tag == AnyReg::I64) {
+ masm.wasmLoadI64(*access, srcAddr, dest.i64());
+ } else {
+ masm.wasmLoad(*access, srcAddr, dest.any());
+ }
+#elif defined(JS_CODEGEN_X86)
+ MOZ_ASSERT(memoryBase.isInvalid() && temp.isInvalid());
+ masm.addPtr(
+ Address(instance, instanceOffsetOfMemoryBase(access->memoryIndex())),
+ ptr);
+ Operand srcAddr(ptr, access->offset());
+
+ if (dest.tag == AnyReg::I64) {
+ MOZ_ASSERT(dest.i64() == specific_.abiReturnRegI64);
+ masm.wasmLoadI64(*access, srcAddr, dest.i64());
+ } else {
+ // For 8 bit loads, this will generate movsbl or movzbl, so
+ // there's no constraint on what the output register may be.
+ masm.wasmLoad(*access, srcAddr, dest.any());
+ }
+#elif defined(JS_CODEGEN_MIPS64)
+ if (IsUnaligned(*access)) {
+ switch (dest.tag) {
+ case AnyReg::I64:
+ masm.wasmUnalignedLoadI64(*access, memoryBase, ptr, ptr, dest.i64(),
+ temp);
+ break;
+ case AnyReg::F32:
+ masm.wasmUnalignedLoadFP(*access, memoryBase, ptr, ptr, dest.f32(),
+ temp);
+ break;
+ case AnyReg::F64:
+ masm.wasmUnalignedLoadFP(*access, memoryBase, ptr, ptr, dest.f64(),
+ temp);
+ break;
+ case AnyReg::I32:
+ masm.wasmUnalignedLoad(*access, memoryBase, ptr, ptr, dest.i32(), temp);
+ break;
+ default:
+ MOZ_CRASH("Unexpected type");
+ }
+ } else {
+ if (dest.tag == AnyReg::I64) {
+ masm.wasmLoadI64(*access, memoryBase, ptr, ptr, dest.i64());
+ } else {
+ masm.wasmLoad(*access, memoryBase, ptr, ptr, dest.any());
+ }
+ }
+#elif defined(JS_CODEGEN_ARM)
+ MOZ_ASSERT(temp.isInvalid());
+ if (dest.tag == AnyReg::I64) {
+ masm.wasmLoadI64(*access, memoryBase, ptr, ptr, dest.i64());
+ } else {
+ masm.wasmLoad(*access, memoryBase, ptr, ptr, dest.any());
+ }
+#elif defined(JS_CODEGEN_ARM64)
+ MOZ_ASSERT(temp.isInvalid());
+ if (dest.tag == AnyReg::I64) {
+ masm.wasmLoadI64(*access, memoryBase, ptr, dest.i64());
+ } else {
+ masm.wasmLoad(*access, memoryBase, ptr, dest.any());
+ }
+#elif defined(JS_CODEGEN_LOONG64)
+ MOZ_ASSERT(temp.isInvalid());
+ if (dest.tag == AnyReg::I64) {
+ masm.wasmLoadI64(*access, memoryBase, ptr, ptr, dest.i64());
+ } else {
+ masm.wasmLoad(*access, memoryBase, ptr, ptr, dest.any());
+ }
+#elif defined(JS_CODEGEN_RISCV64)
+ MOZ_ASSERT(temp.isInvalid());
+ if (dest.tag == AnyReg::I64) {
+ masm.wasmLoadI64(*access, memoryBase, ptr, ptr, dest.i64());
+ } else {
+ masm.wasmLoad(*access, memoryBase, ptr, ptr, dest.any());
+ }
+#else
+ MOZ_CRASH("BaseCompiler platform hook: load");
+#endif
+}
+
+// ptr and dest may be the same iff dest is I32.
+// This may destroy ptr even if ptr and dest are not the same.
+void BaseCompiler::load(MemoryAccessDesc* access, AccessCheck* check,
+ RegPtr instance, RegPtr memoryBase, RegI32 ptr,
+ AnyReg dest, RegI32 temp) {
+ prepareMemoryAccess(access, check, instance, ptr);
+ executeLoad(access, check, instance, memoryBase, ptr, dest, temp);
+}
+
+#ifdef ENABLE_WASM_MEMORY64
+void BaseCompiler::load(MemoryAccessDesc* access, AccessCheck* check,
+ RegPtr instance, RegPtr memoryBase, RegI64 ptr,
+ AnyReg dest, RegI64 temp) {
+ prepareMemoryAccess(access, check, instance, ptr);
+
+# if !defined(JS_64BIT)
+ // On 32-bit systems we have a maximum 2GB heap and bounds checking has
+ // been applied to ensure that the 64-bit pointer is valid.
+ return executeLoad(access, check, instance, memoryBase, RegI32(ptr.low), dest,
+ maybeFromI64(temp));
+# elif defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64)
+ // On x64 and arm64 the 32-bit code simply assumes that the high bits of the
+ // 64-bit pointer register are zero and performs a 64-bit add. Thus the code
+ // generated is the same for the 64-bit and the 32-bit case.
+ return executeLoad(access, check, instance, memoryBase, RegI32(ptr.reg), dest,
+ maybeFromI64(temp));
+# elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64)
+ // On mips64 and loongarch64, the 'prepareMemoryAccess' function will make
+ // sure that ptr holds a valid 64-bit index value. Thus the code generated in
+ // 'executeLoad' is the same for the 64-bit and the 32-bit case.
+ return executeLoad(access, check, instance, memoryBase, RegI32(ptr.reg), dest,
+ maybeFromI64(temp));
+# elif defined(JS_CODEGEN_RISCV64)
+ // RISCV the 'prepareMemoryAccess' function will make
+ // sure that ptr holds a valid 64-bit index value. Thus the code generated in
+ // 'executeLoad' is the same for the 64-bit and the 32-bit case.
+ return executeLoad(access, check, instance, memoryBase, RegI32(ptr.reg), dest,
+ maybeFromI64(temp));
+# else
+ MOZ_CRASH("Missing platform hook");
+# endif
+}
+#endif
+
+void BaseCompiler::executeStore(MemoryAccessDesc* access, AccessCheck* check,
+ RegPtr instance, RegPtr memoryBase, RegI32 ptr,
+ AnyReg src, RegI32 temp) {
+ // Emit the store. At this point, 64-bit offsets will have been resolved.
+#if defined(JS_CODEGEN_X64)
+ MOZ_ASSERT(temp.isInvalid());
+ Operand dstAddr(memoryBase, ptr, TimesOne, access->offset());
+
+ masm.wasmStore(*access, src.any(), dstAddr);
+#elif defined(JS_CODEGEN_X86)
+ MOZ_ASSERT(memoryBase.isInvalid() && temp.isInvalid());
+ masm.addPtr(
+ Address(instance, instanceOffsetOfMemoryBase(access->memoryIndex())),
+ ptr);
+ Operand dstAddr(ptr, access->offset());
+
+ if (access->type() == Scalar::Int64) {
+ masm.wasmStoreI64(*access, src.i64(), dstAddr);
+ } else {
+ AnyRegister value;
+ ScratchI8 scratch(*this);
+ if (src.tag == AnyReg::I64) {
+ if (access->byteSize() == 1 && !ra.isSingleByteI32(src.i64().low)) {
+ masm.mov(src.i64().low, scratch);
+ value = AnyRegister(scratch);
+ } else {
+ value = AnyRegister(src.i64().low);
+ }
+ } else if (access->byteSize() == 1 && !ra.isSingleByteI32(src.i32())) {
+ masm.mov(src.i32(), scratch);
+ value = AnyRegister(scratch);
+ } else {
+ value = src.any();
+ }
+
+ masm.wasmStore(*access, value, dstAddr);
+ }
+#elif defined(JS_CODEGEN_ARM)
+ MOZ_ASSERT(temp.isInvalid());
+ if (access->type() == Scalar::Int64) {
+ masm.wasmStoreI64(*access, src.i64(), memoryBase, ptr, ptr);
+ } else if (src.tag == AnyReg::I64) {
+ masm.wasmStore(*access, AnyRegister(src.i64().low), memoryBase, ptr, ptr);
+ } else {
+ masm.wasmStore(*access, src.any(), memoryBase, ptr, ptr);
+ }
+#elif defined(JS_CODEGEN_MIPS64)
+ if (IsUnaligned(*access)) {
+ switch (src.tag) {
+ case AnyReg::I64:
+ masm.wasmUnalignedStoreI64(*access, src.i64(), memoryBase, ptr, ptr,
+ temp);
+ break;
+ case AnyReg::F32:
+ masm.wasmUnalignedStoreFP(*access, src.f32(), memoryBase, ptr, ptr,
+ temp);
+ break;
+ case AnyReg::F64:
+ masm.wasmUnalignedStoreFP(*access, src.f64(), memoryBase, ptr, ptr,
+ temp);
+ break;
+ case AnyReg::I32:
+ masm.wasmUnalignedStore(*access, src.i32(), memoryBase, ptr, ptr, temp);
+ break;
+ default:
+ MOZ_CRASH("Unexpected type");
+ }
+ } else {
+ if (src.tag == AnyReg::I64) {
+ masm.wasmStoreI64(*access, src.i64(), memoryBase, ptr, ptr);
+ } else {
+ masm.wasmStore(*access, src.any(), memoryBase, ptr, ptr);
+ }
+ }
+#elif defined(JS_CODEGEN_ARM64)
+ MOZ_ASSERT(temp.isInvalid());
+ if (access->type() == Scalar::Int64) {
+ masm.wasmStoreI64(*access, src.i64(), memoryBase, ptr);
+ } else {
+ masm.wasmStore(*access, src.any(), memoryBase, ptr);
+ }
+#elif defined(JS_CODEGEN_LOONG64)
+ MOZ_ASSERT(temp.isInvalid());
+ if (access->type() == Scalar::Int64) {
+ masm.wasmStoreI64(*access, src.i64(), memoryBase, ptr, ptr);
+ } else {
+ masm.wasmStore(*access, src.any(), memoryBase, ptr, ptr);
+ }
+#elif defined(JS_CODEGEN_RISCV64)
+ MOZ_ASSERT(temp.isInvalid());
+ if (access->type() == Scalar::Int64) {
+ masm.wasmStoreI64(*access, src.i64(), memoryBase, ptr, ptr);
+ } else {
+ masm.wasmStore(*access, src.any(), memoryBase, ptr, ptr);
+ }
+#else
+ MOZ_CRASH("BaseCompiler platform hook: store");
+#endif
+}
+
+// ptr and src must not be the same register.
+// This may destroy ptr and src.
+void BaseCompiler::store(MemoryAccessDesc* access, AccessCheck* check,
+ RegPtr instance, RegPtr memoryBase, RegI32 ptr,
+ AnyReg src, RegI32 temp) {
+ prepareMemoryAccess(access, check, instance, ptr);
+ executeStore(access, check, instance, memoryBase, ptr, src, temp);
+}
+
+#ifdef ENABLE_WASM_MEMORY64
+void BaseCompiler::store(MemoryAccessDesc* access, AccessCheck* check,
+ RegPtr instance, RegPtr memoryBase, RegI64 ptr,
+ AnyReg src, RegI64 temp) {
+ prepareMemoryAccess(access, check, instance, ptr);
+ // See comments in load()
+# if !defined(JS_64BIT)
+ return executeStore(access, check, instance, memoryBase, RegI32(ptr.low), src,
+ maybeFromI64(temp));
+# elif defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64) || \
+ defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \
+ defined(JS_CODEGEN_RISCV64)
+ return executeStore(access, check, instance, memoryBase, RegI32(ptr.reg), src,
+ maybeFromI64(temp));
+# else
+ MOZ_CRASH("Missing platform hook");
+# endif
+}
+#endif
+
+template <typename RegType>
+void BaseCompiler::doLoadCommon(MemoryAccessDesc* access, AccessCheck check,
+ ValType type) {
+ RegPtr instance;
+ RegPtr memoryBase;
+ RegType temp;
+#if defined(JS_CODEGEN_MIPS64)
+ temp = need<RegType>();
+#endif
+
+ switch (type.kind()) {
+ case ValType::I32: {
+ RegType rp = popMemoryAccess<RegType>(access, &check);
+ RegI32 rv = needI32();
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ load(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ push(rv);
+ free(rp);
+ break;
+ }
+ case ValType::I64: {
+ RegI64 rv;
+ RegType rp;
+#ifdef JS_CODEGEN_X86
+ rv = specific_.abiReturnRegI64;
+ needI64(rv);
+ rp = popMemoryAccess<RegType>(access, &check);
+#else
+ rp = popMemoryAccess<RegType>(access, &check);
+ rv = needI64();
+#endif
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ load(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ push(rv);
+ free(rp);
+ break;
+ }
+ case ValType::F32: {
+ RegType rp = popMemoryAccess<RegType>(access, &check);
+ RegF32 rv = needF32();
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ load(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ push(rv);
+ free(rp);
+ break;
+ }
+ case ValType::F64: {
+ RegType rp = popMemoryAccess<RegType>(access, &check);
+ RegF64 rv = needF64();
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ load(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ push(rv);
+ free(rp);
+ break;
+ }
+#ifdef ENABLE_WASM_SIMD
+ case ValType::V128: {
+ RegType rp = popMemoryAccess<RegType>(access, &check);
+ RegV128 rv = needV128();
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ load(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ push(rv);
+ free(rp);
+ break;
+ }
+#endif
+ default:
+ MOZ_CRASH("load type");
+ break;
+ }
+
+#ifndef RABALDR_PIN_INSTANCE
+ maybeFree(instance);
+#endif
+#ifdef WASM_HAS_HEAPREG
+ if (memoryBase != HeapReg) {
+ maybeFree(memoryBase);
+ }
+#else
+ maybeFree(memoryBase);
+#endif
+ maybeFree(temp);
+}
+
+void BaseCompiler::loadCommon(MemoryAccessDesc* access, AccessCheck check,
+ ValType type) {
+ if (isMem32(access->memoryIndex())) {
+ doLoadCommon<RegI32>(access, check, type);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ doLoadCommon<RegI64>(access, check, type);
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+}
+
+template <typename RegType>
+void BaseCompiler::doStoreCommon(MemoryAccessDesc* access, AccessCheck check,
+ ValType resultType) {
+ RegPtr instance;
+ RegPtr memoryBase;
+ RegType temp;
+#if defined(JS_CODEGEN_MIPS64)
+ temp = need<RegType>();
+#endif
+
+ switch (resultType.kind()) {
+ case ValType::I32: {
+ RegI32 rv = popI32();
+ RegType rp = popMemoryAccess<RegType>(access, &check);
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ store(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ free(rp);
+ free(rv);
+ break;
+ }
+ case ValType::I64: {
+ RegI64 rv = popI64();
+ RegType rp = popMemoryAccess<RegType>(access, &check);
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ store(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ free(rp);
+ free(rv);
+ break;
+ }
+ case ValType::F32: {
+ RegF32 rv = popF32();
+ RegType rp = popMemoryAccess<RegType>(access, &check);
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ store(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ free(rp);
+ free(rv);
+ break;
+ }
+ case ValType::F64: {
+ RegF64 rv = popF64();
+ RegType rp = popMemoryAccess<RegType>(access, &check);
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ store(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ free(rp);
+ free(rv);
+ break;
+ }
+#ifdef ENABLE_WASM_SIMD
+ case ValType::V128: {
+ RegV128 rv = popV128();
+ RegType rp = popMemoryAccess<RegType>(access, &check);
+ instance = maybeLoadInstanceForAccess(access, check);
+ memoryBase = maybeLoadMemoryBaseForAccess(instance, access);
+ store(access, &check, instance, memoryBase, rp, AnyReg(rv), temp);
+ free(rp);
+ free(rv);
+ break;
+ }
+#endif
+ default:
+ MOZ_CRASH("store type");
+ break;
+ }
+
+#ifndef RABALDR_PIN_INSTANCE
+ maybeFree(instance);
+#endif
+#ifdef WASM_HAS_HEAPREG
+ if (memoryBase != HeapReg) {
+ maybeFree(memoryBase);
+ }
+#else
+ maybeFree(memoryBase);
+#endif
+ maybeFree(temp);
+}
+
+void BaseCompiler::storeCommon(MemoryAccessDesc* access, AccessCheck check,
+ ValType type) {
+ if (isMem32(access->memoryIndex())) {
+ doStoreCommon<RegI32>(access, check, type);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ doStoreCommon<RegI64>(access, check, type);
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+}
+
+// Convert something that may contain a heap index into a Register that can be
+// used in an access.
+
+static inline Register ToRegister(RegI32 r) { return Register(r); }
+#ifdef ENABLE_WASM_MEMORY64
+# ifdef JS_PUNBOX64
+static inline Register ToRegister(RegI64 r) { return r.reg; }
+# else
+static inline Register ToRegister(RegI64 r) { return r.low; }
+# endif
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Atomic operations.
+//
+// The atomic operations have very diverse per-platform needs for register
+// allocation and temps. To handle that, the implementations are structured as
+// a per-operation framework method that calls into platform-specific helpers
+// (usually called PopAndAllocate, Perform, and Deallocate) in a per-operation
+// namespace. This structure results in a little duplication and boilerplate
+// but is otherwise clean and flexible and keeps code and supporting definitions
+// entirely co-located.
+
+// Some consumers depend on the returned Address not incorporating instance, as
+// instance may be the scratch register.
+//
+// RegIndexType is RegI32 for Memory32 and RegI64 for Memory64.
+template <typename RegIndexType>
+Address BaseCompiler::prepareAtomicMemoryAccess(MemoryAccessDesc* access,
+ AccessCheck* check,
+ RegPtr instance,
+ RegIndexType ptr) {
+ MOZ_ASSERT(needInstanceForAccess(access, *check) == instance.isValid());
+ prepareMemoryAccess(access, check, instance, ptr);
+
+#ifdef WASM_HAS_HEAPREG
+ if (access->memoryIndex() == 0) {
+ masm.addPtr(HeapReg, ToRegister(ptr));
+ } else {
+ masm.addPtr(
+ Address(instance, instanceOffsetOfMemoryBase(access->memoryIndex())),
+ ToRegister(ptr));
+ }
+#else
+ masm.addPtr(
+ Address(instance, instanceOffsetOfMemoryBase(access->memoryIndex())),
+ ToRegister(ptr));
+#endif
+
+ // At this point, 64-bit offsets will have been resolved.
+ return Address(ToRegister(ptr), access->offset());
+}
+
+#ifndef WASM_HAS_HEAPREG
+# ifdef JS_CODEGEN_X86
+using ScratchAtomicNoHeapReg = ScratchEBX;
+# else
+# error "Unimplemented porting interface"
+# endif
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Atomic load and store.
+
+namespace atomic_load64 {
+
+#ifdef JS_CODEGEN_ARM
+
+static void Allocate(BaseCompiler* bc, RegI64* rd, RegI64*) {
+ *rd = bc->needI64Pair();
+}
+
+static void Deallocate(BaseCompiler* bc, RegI64) {}
+
+#elif defined JS_CODEGEN_X86
+
+static void Allocate(BaseCompiler* bc, RegI64* rd, RegI64* temp) {
+ // The result is in edx:eax, and we need ecx:ebx as a temp. But ebx will also
+ // be used as a scratch, so don't manage that here.
+ bc->needI32(bc->specific_.ecx);
+ *temp = bc->specific_.ecx_ebx;
+ bc->needI64(bc->specific_.edx_eax);
+ *rd = bc->specific_.edx_eax;
+}
+
+static void Deallocate(BaseCompiler* bc, RegI64 temp) {
+ // See comment above.
+ MOZ_ASSERT(temp.high == js::jit::ecx);
+ bc->freeI32(bc->specific_.ecx);
+}
+
+#elif defined(__wasi__) || (defined(JS_CODEGEN_NONE) && !defined(JS_64BIT))
+
+static void Allocate(BaseCompiler*, RegI64*, RegI64*) {}
+static void Deallocate(BaseCompiler*, RegI64) {}
+
+#endif
+
+} // namespace atomic_load64
+
+#if !defined(JS_64BIT)
+template <typename RegIndexType>
+void BaseCompiler::atomicLoad64(MemoryAccessDesc* access) {
+ RegI64 rd, temp;
+ atomic_load64::Allocate(this, &rd, &temp);
+
+ AccessCheck check;
+ RegIndexType rp = popMemoryAccess<RegIndexType>(access, &check);
+
+# ifdef WASM_HAS_HEAPREG
+ RegPtr instance = maybeLoadInstanceForAccess(access, check);
+ auto memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ masm.wasmAtomicLoad64(*access, memaddr, temp, rd);
+# ifndef RABALDR_PIN_INSTANCE
+ maybeFree(instance);
+# endif
+# else
+ ScratchAtomicNoHeapReg scratch(*this);
+ RegPtr instance =
+ maybeLoadInstanceForAccess(access, check, RegIntptrToRegPtr(scratch));
+ auto memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ masm.wasmAtomicLoad64(*access, memaddr, temp, rd);
+ MOZ_ASSERT(instance == scratch);
+# endif
+
+ free(rp);
+ atomic_load64::Deallocate(this, temp);
+ pushI64(rd);
+}
+#endif
+
+void BaseCompiler::atomicLoad(MemoryAccessDesc* access, ValType type) {
+ Scalar::Type viewType = access->type();
+ if (Scalar::byteSize(viewType) <= sizeof(void*)) {
+ loadCommon(access, AccessCheck(), type);
+ return;
+ }
+
+ MOZ_ASSERT(type == ValType::I64 && Scalar::byteSize(viewType) == 8);
+
+#if !defined(JS_64BIT)
+ if (isMem32(access->memoryIndex())) {
+ atomicLoad64<RegI32>(access);
+ } else {
+# ifdef ENABLE_WASM_MEMORY64
+ atomicLoad64<RegI64>(access);
+# else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+# endif
+ }
+#else
+ MOZ_CRASH("Should not happen");
+#endif
+}
+
+void BaseCompiler::atomicStore(MemoryAccessDesc* access, ValType type) {
+ Scalar::Type viewType = access->type();
+
+ if (Scalar::byteSize(viewType) <= sizeof(void*)) {
+ storeCommon(access, AccessCheck(), type);
+ return;
+ }
+
+ MOZ_ASSERT(type == ValType::I64 && Scalar::byteSize(viewType) == 8);
+
+#if !defined(JS_64BIT)
+ if (isMem32(access->memoryIndex())) {
+ atomicXchg64<RegI32>(access, WantResult(false));
+ } else {
+# ifdef ENABLE_WASM_MEMORY64
+ atomicXchg64<RegI64>(access, WantResult(false));
+# else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+# endif
+ }
+#else
+ MOZ_CRASH("Should not happen");
+#endif
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Atomic RMW op= operations.
+
+void BaseCompiler::atomicRMW(MemoryAccessDesc* access, ValType type,
+ AtomicOp op) {
+ Scalar::Type viewType = access->type();
+ if (Scalar::byteSize(viewType) <= 4) {
+ if (isMem32(access->memoryIndex())) {
+ atomicRMW32<RegI32>(access, type, op);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ atomicRMW32<RegI64>(access, type, op);
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+ } else {
+ MOZ_ASSERT(type == ValType::I64 && Scalar::byteSize(viewType) == 8);
+ if (isMem32(access->memoryIndex())) {
+ atomicRMW64<RegI32>(access, type, op);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ atomicRMW64<RegI64>(access, type, op);
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+ }
+}
+
+namespace atomic_rmw32 {
+
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
+
+struct Temps {
+ // On x86 we use the ScratchI32 for the temp, otherwise we'd run out of
+ // registers for 64-bit operations.
+# if defined(JS_CODEGEN_X64)
+ RegI32 t0;
+# endif
+};
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, AtomicOp op, RegI32* rd,
+ RegI32* rv, Temps* temps) {
+ bc->needI32(bc->specific_.eax);
+ if (op == AtomicFetchAddOp || op == AtomicFetchSubOp) {
+ // We use xadd, so source and destination are the same. Using
+ // eax here is overconstraining, but for byte operations on x86
+ // we do need something with a byte register.
+ if (type == ValType::I64) {
+ *rv = bc->popI64ToSpecificI32(bc->specific_.eax);
+ } else {
+ *rv = bc->popI32ToSpecific(bc->specific_.eax);
+ }
+ *rd = *rv;
+ } else {
+ // We use a cmpxchg loop. The output must be eax; the input
+ // must be in a separate register since it may be used several
+ // times.
+ if (type == ValType::I64) {
+ *rv = bc->popI64ToI32();
+ } else {
+ *rv = bc->popI32();
+ }
+ *rd = bc->specific_.eax;
+# ifdef JS_CODEGEN_X64
+ temps->t0 = bc->needI32();
+# endif
+ }
+}
+
+template <typename T>
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access, T srcAddr,
+ AtomicOp op, RegI32 rv, RegI32 rd, const Temps& temps) {
+# ifdef JS_CODEGEN_X64
+ RegI32 temp = temps.t0;
+# else
+ RegI32 temp;
+ ScratchI32 scratch(*bc);
+ if (op != AtomicFetchAddOp && op != AtomicFetchSubOp) {
+ temp = scratch;
+ }
+# endif
+ bc->masm.wasmAtomicFetchOp(access, op, rv, srcAddr, temp, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rv, const Temps& temps) {
+ if (rv != bc->specific_.eax) {
+ bc->freeI32(rv);
+ }
+# ifdef JS_CODEGEN_X64
+ bc->maybeFree(temps.t0);
+# endif
+}
+
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
+
+struct Temps {
+ RegI32 t0;
+};
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, AtomicOp op, RegI32* rd,
+ RegI32* rv, Temps* temps) {
+ *rv = type == ValType::I64 ? bc->popI64ToI32() : bc->popI32();
+ temps->t0 = bc->needI32();
+ *rd = bc->needI32();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, AtomicOp op, RegI32 rv, RegI32 rd,
+ const Temps& temps) {
+ bc->masm.wasmAtomicFetchOp(access, op, rv, srcAddr, temps.t0, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rv, const Temps& temps) {
+ bc->freeI32(rv);
+ bc->freeI32(temps.t0);
+}
+
+#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64)
+
+struct Temps {
+ RegI32 t0, t1, t2;
+};
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, AtomicOp op, RegI32* rd,
+ RegI32* rv, Temps* temps) {
+ *rv = type == ValType::I64 ? bc->popI64ToI32() : bc->popI32();
+ if (Scalar::byteSize(viewType) < 4) {
+ temps->t0 = bc->needI32();
+ temps->t1 = bc->needI32();
+ temps->t2 = bc->needI32();
+ }
+ *rd = bc->needI32();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, AtomicOp op, RegI32 rv, RegI32 rd,
+ const Temps& temps) {
+ bc->masm.wasmAtomicFetchOp(access, op, rv, srcAddr, temps.t0, temps.t1,
+ temps.t2, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rv, const Temps& temps) {
+ bc->freeI32(rv);
+ bc->maybeFree(temps.t0);
+ bc->maybeFree(temps.t1);
+ bc->maybeFree(temps.t2);
+}
+
+#elif defined(JS_CODEGEN_RISCV64)
+
+struct Temps {
+ RegI32 t0, t1, t2;
+};
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, AtomicOp op, RegI32* rd,
+ RegI32* rv, Temps* temps) {
+ *rv = type == ValType::I64 ? bc->popI64ToI32() : bc->popI32();
+ if (Scalar::byteSize(viewType) < 4) {
+ temps->t0 = bc->needI32();
+ temps->t1 = bc->needI32();
+ temps->t2 = bc->needI32();
+ }
+ *rd = bc->needI32();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, AtomicOp op, RegI32 rv, RegI32 rd,
+ const Temps& temps) {
+ bc->masm.wasmAtomicFetchOp(access, op, rv, srcAddr, temps.t0, temps.t1,
+ temps.t2, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rv, const Temps& temps) {
+ bc->freeI32(rv);
+ bc->maybeFree(temps.t0);
+ bc->maybeFree(temps.t1);
+ bc->maybeFree(temps.t2);
+}
+
+#elif defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32)
+
+using Temps = Nothing;
+
+static void PopAndAllocate(BaseCompiler*, ValType, Scalar::Type, AtomicOp,
+ RegI32*, RegI32*, Temps*) {}
+
+static void Perform(BaseCompiler*, const MemoryAccessDesc&, Address, AtomicOp,
+ RegI32, RegI32, const Temps&) {}
+
+static void Deallocate(BaseCompiler*, RegI32, const Temps&) {}
+
+#endif
+
+} // namespace atomic_rmw32
+
+template <typename RegIndexType>
+void BaseCompiler::atomicRMW32(MemoryAccessDesc* access, ValType type,
+ AtomicOp op) {
+ Scalar::Type viewType = access->type();
+ RegI32 rd, rv;
+ atomic_rmw32::Temps temps;
+ atomic_rmw32::PopAndAllocate(this, type, viewType, op, &rd, &rv, &temps);
+
+ AccessCheck check;
+ RegIndexType rp = popMemoryAccess<RegIndexType>(access, &check);
+ RegPtr instance = maybeLoadInstanceForAccess(access, check);
+
+ auto memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ atomic_rmw32::Perform(this, *access, memaddr, op, rv, rd, temps);
+
+#ifndef RABALDR_PIN_INSTANCE
+ maybeFree(instance);
+#endif
+ atomic_rmw32::Deallocate(this, rv, temps);
+ free(rp);
+
+ if (type == ValType::I64) {
+ pushU32AsI64(rd);
+ } else {
+ pushI32(rd);
+ }
+}
+
+namespace atomic_rmw64 {
+
+#if defined(JS_CODEGEN_X64)
+
+static void PopAndAllocate(BaseCompiler* bc, AtomicOp op, RegI64* rd,
+ RegI64* rv, RegI64* temp) {
+ if (op == AtomicFetchAddOp || op == AtomicFetchSubOp) {
+ // We use xaddq, so input and output must be the same register.
+ *rv = bc->popI64();
+ *rd = *rv;
+ } else {
+ // We use a cmpxchgq loop, so the output must be rax and we need a temp.
+ bc->needI64(bc->specific_.rax);
+ *rd = bc->specific_.rax;
+ *rv = bc->popI64();
+ *temp = bc->needI64();
+ }
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, AtomicOp op, RegI64 rv, RegI64 temp,
+ RegI64 rd) {
+ bc->masm.wasmAtomicFetchOp64(access, op, rv, srcAddr, temp, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, AtomicOp op, RegI64 rv, RegI64 temp) {
+ bc->maybeFree(temp);
+ if (op != AtomicFetchAddOp && op != AtomicFetchSubOp) {
+ bc->freeI64(rv);
+ }
+}
+
+#elif defined(JS_CODEGEN_X86)
+
+// Register allocation is tricky, see comments at atomic_xchg64 below.
+//
+// - Initially rv=ecx:edx and eax is reserved, rd=unallocated.
+// - Then rp is popped into esi+edi because those are the only available.
+// - The Setup operation makes rd=edx:eax.
+// - Deallocation then frees only the ecx part of rv.
+//
+// The temp is unused here.
+
+static void PopAndAllocate(BaseCompiler* bc, AtomicOp op, RegI64* rd,
+ RegI64* rv, RegI64*) {
+ bc->needI32(bc->specific_.eax);
+ bc->needI32(bc->specific_.ecx);
+ bc->needI32(bc->specific_.edx);
+ *rv = RegI64(Register64(bc->specific_.ecx, bc->specific_.edx));
+ bc->popI64ToSpecific(*rv);
+}
+
+static void Setup(BaseCompiler* bc, RegI64* rd) { *rd = bc->specific_.edx_eax; }
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, AtomicOp op, RegI64 rv, RegI64, RegI64 rd,
+ const ScratchAtomicNoHeapReg& scratch) {
+ MOZ_ASSERT(rv.high == bc->specific_.ecx);
+ MOZ_ASSERT(Register(scratch) == js::jit::ebx);
+
+ bc->fr.pushGPR(rv.high);
+ bc->fr.pushGPR(rv.low);
+ Address value(StackPointer, 0);
+
+ bc->masm.wasmAtomicFetchOp64(access, op, value, srcAddr,
+ bc->specific_.ecx_ebx, rd);
+
+ bc->fr.popBytes(8);
+}
+
+static void Deallocate(BaseCompiler* bc, AtomicOp, RegI64, RegI64) {
+ bc->freeI32(bc->specific_.ecx);
+}
+
+#elif defined(JS_CODEGEN_ARM)
+
+static void PopAndAllocate(BaseCompiler* bc, AtomicOp op, RegI64* rd,
+ RegI64* rv, RegI64* temp) {
+ // We use a ldrex/strexd loop so the temp and the output must be
+ // odd/even pairs.
+ *rv = bc->popI64();
+ *temp = bc->needI64Pair();
+ *rd = bc->needI64Pair();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, AtomicOp op, RegI64 rv, RegI64 temp,
+ RegI64 rd) {
+ bc->masm.wasmAtomicFetchOp64(access, op, rv, srcAddr, temp, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, AtomicOp op, RegI64 rv, RegI64 temp) {
+ bc->freeI64(rv);
+ bc->freeI64(temp);
+}
+
+#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \
+ defined(JS_CODEGEN_LOONG64)
+
+static void PopAndAllocate(BaseCompiler* bc, AtomicOp op, RegI64* rd,
+ RegI64* rv, RegI64* temp) {
+ *rv = bc->popI64();
+ *temp = bc->needI64();
+ *rd = bc->needI64();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, AtomicOp op, RegI64 rv, RegI64 temp,
+ RegI64 rd) {
+ bc->masm.wasmAtomicFetchOp64(access, op, rv, srcAddr, temp, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, AtomicOp op, RegI64 rv, RegI64 temp) {
+ bc->freeI64(rv);
+ bc->freeI64(temp);
+}
+#elif defined(JS_CODEGEN_RISCV64)
+
+static void PopAndAllocate(BaseCompiler* bc, AtomicOp op, RegI64* rd,
+ RegI64* rv, RegI64* temp) {
+ *rv = bc->popI64();
+ *temp = bc->needI64();
+ *rd = bc->needI64();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, AtomicOp op, RegI64 rv, RegI64 temp,
+ RegI64 rd) {
+ bc->masm.wasmAtomicFetchOp64(access, op, rv, srcAddr, temp, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, AtomicOp op, RegI64 rv, RegI64 temp) {
+ bc->freeI64(rv);
+ bc->freeI64(temp);
+}
+
+#elif defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32)
+
+static void PopAndAllocate(BaseCompiler*, AtomicOp, RegI64*, RegI64*, RegI64*) {
+}
+
+static void Perform(BaseCompiler*, const MemoryAccessDesc&, Address,
+ AtomicOp op, RegI64, RegI64, RegI64) {}
+
+static void Deallocate(BaseCompiler*, AtomicOp, RegI64, RegI64) {}
+
+#endif
+
+} // namespace atomic_rmw64
+
+template <typename RegIndexType>
+void BaseCompiler::atomicRMW64(MemoryAccessDesc* access, ValType type,
+ AtomicOp op) {
+ RegI64 rd, rv, temp;
+ atomic_rmw64::PopAndAllocate(this, op, &rd, &rv, &temp);
+
+ AccessCheck check;
+ RegIndexType rp = popMemoryAccess<RegIndexType>(access, &check);
+
+#if defined(WASM_HAS_HEAPREG)
+ RegPtr instance = maybeLoadInstanceForAccess(access, check);
+ auto memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ atomic_rmw64::Perform(this, *access, memaddr, op, rv, temp, rd);
+# ifndef RABALDR_PIN_INSTANCE
+ maybeFree(instance);
+# endif
+#else
+ ScratchAtomicNoHeapReg scratch(*this);
+ RegPtr instance =
+ maybeLoadInstanceForAccess(access, check, RegIntptrToRegPtr(scratch));
+ auto memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ atomic_rmw64::Setup(this, &rd);
+ atomic_rmw64::Perform(this, *access, memaddr, op, rv, temp, rd, scratch);
+ MOZ_ASSERT(instance == scratch);
+#endif
+
+ free(rp);
+ atomic_rmw64::Deallocate(this, op, rv, temp);
+
+ pushI64(rd);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Atomic exchange (also used for atomic store in some cases).
+
+void BaseCompiler::atomicXchg(MemoryAccessDesc* access, ValType type) {
+ Scalar::Type viewType = access->type();
+ if (Scalar::byteSize(viewType) <= 4) {
+ if (isMem32(access->memoryIndex())) {
+ atomicXchg32<RegI32>(access, type);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ atomicXchg32<RegI64>(access, type);
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+ } else {
+ MOZ_ASSERT(type == ValType::I64 && Scalar::byteSize(viewType) == 8);
+ if (isMem32(access->memoryIndex())) {
+ atomicXchg64<RegI32>(access, WantResult(true));
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ atomicXchg64<RegI64>(access, WantResult(true));
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+ }
+}
+
+namespace atomic_xchg32 {
+
+#if defined(JS_CODEGEN_X64)
+
+using Temps = Nothing;
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, RegI32* rd, RegI32* rv,
+ Temps*) {
+ // The xchg instruction reuses rv as rd.
+ *rv = (type == ValType::I64) ? bc->popI64ToI32() : bc->popI32();
+ *rd = *rv;
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI32 rv, RegI32 rd, const Temps&) {
+ bc->masm.wasmAtomicExchange(access, srcAddr, rv, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32, const Temps&) {}
+
+#elif defined(JS_CODEGEN_X86)
+
+using Temps = Nothing;
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, RegI32* rd, RegI32* rv,
+ Temps*) {
+ // The xchg instruction reuses rv as rd.
+ *rv = (type == ValType::I64) ? bc->popI64ToI32() : bc->popI32();
+ *rd = *rv;
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI32 rv, RegI32 rd, const Temps&) {
+ if (access.type() == Scalar::Uint8 && !bc->ra.isSingleByteI32(rd)) {
+ ScratchI8 scratch(*bc);
+ // The output register must have a byte persona.
+ bc->masm.wasmAtomicExchange(access, srcAddr, rv, scratch);
+ bc->masm.movl(scratch, rd);
+ } else {
+ bc->masm.wasmAtomicExchange(access, srcAddr, rv, rd);
+ }
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32, const Temps&) {}
+
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
+
+using Temps = Nothing;
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, RegI32* rd, RegI32* rv,
+ Temps*) {
+ *rv = (type == ValType::I64) ? bc->popI64ToI32() : bc->popI32();
+ *rd = bc->needI32();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI32 rv, RegI32 rd, const Temps&) {
+ bc->masm.wasmAtomicExchange(access, srcAddr, rv, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rv, const Temps&) {
+ bc->freeI32(rv);
+}
+
+#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64)
+
+struct Temps {
+ RegI32 t0, t1, t2;
+};
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, RegI32* rd, RegI32* rv,
+ Temps* temps) {
+ *rv = (type == ValType::I64) ? bc->popI64ToI32() : bc->popI32();
+ if (Scalar::byteSize(viewType) < 4) {
+ temps->t0 = bc->needI32();
+ temps->t1 = bc->needI32();
+ temps->t2 = bc->needI32();
+ }
+ *rd = bc->needI32();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI32 rv, RegI32 rd, const Temps& temps) {
+ bc->masm.wasmAtomicExchange(access, srcAddr, rv, temps.t0, temps.t1, temps.t2,
+ rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rv, const Temps& temps) {
+ bc->freeI32(rv);
+ bc->maybeFree(temps.t0);
+ bc->maybeFree(temps.t1);
+ bc->maybeFree(temps.t2);
+}
+
+#elif defined(JS_CODEGEN_RISCV64)
+
+struct Temps {
+ RegI32 t0, t1, t2;
+};
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, RegI32* rd, RegI32* rv,
+ Temps* temps) {
+ *rv = (type == ValType::I64) ? bc->popI64ToI32() : bc->popI32();
+ if (Scalar::byteSize(viewType) < 4) {
+ temps->t0 = bc->needI32();
+ temps->t1 = bc->needI32();
+ temps->t2 = bc->needI32();
+ }
+ *rd = bc->needI32();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI32 rv, RegI32 rd, const Temps& temps) {
+ bc->masm.wasmAtomicExchange(access, srcAddr, rv, temps.t0, temps.t1, temps.t2,
+ rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rv, const Temps& temps) {
+ bc->freeI32(rv);
+ bc->maybeFree(temps.t0);
+ bc->maybeFree(temps.t1);
+ bc->maybeFree(temps.t2);
+}
+
+#elif defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32)
+
+using Temps = Nothing;
+
+static void PopAndAllocate(BaseCompiler*, ValType, Scalar::Type, RegI32*,
+ RegI32*, Temps*) {}
+static void Perform(BaseCompiler*, const MemoryAccessDesc&, Address, RegI32,
+ RegI32, const Temps&) {}
+static void Deallocate(BaseCompiler*, RegI32, const Temps&) {}
+
+#endif
+
+} // namespace atomic_xchg32
+
+template <typename RegIndexType>
+void BaseCompiler::atomicXchg32(MemoryAccessDesc* access, ValType type) {
+ Scalar::Type viewType = access->type();
+
+ RegI32 rd, rv;
+ atomic_xchg32::Temps temps;
+ atomic_xchg32::PopAndAllocate(this, type, viewType, &rd, &rv, &temps);
+
+ AccessCheck check;
+
+ RegIndexType rp = popMemoryAccess<RegIndexType>(access, &check);
+ RegPtr instance = maybeLoadInstanceForAccess(access, check);
+
+ auto memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ atomic_xchg32::Perform(this, *access, memaddr, rv, rd, temps);
+
+#ifndef RABALDR_PIN_INSTANCE
+ maybeFree(instance);
+#endif
+ free(rp);
+ atomic_xchg32::Deallocate(this, rv, temps);
+
+ if (type == ValType::I64) {
+ pushU32AsI64(rd);
+ } else {
+ pushI32(rd);
+ }
+}
+
+namespace atomic_xchg64 {
+
+#if defined(JS_CODEGEN_X64)
+
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rd, RegI64* rv) {
+ *rv = bc->popI64();
+ *rd = *rv;
+}
+
+static void Deallocate(BaseCompiler* bc, RegI64 rd, RegI64) {
+ bc->maybeFree(rd);
+}
+
+#elif defined(JS_CODEGEN_X86)
+
+// Register allocation is tricky in several ways.
+//
+// - For a 64-bit access on memory64 we need six registers for rd, rv, and rp,
+// but have only five (as the temp ebx is needed too), so we target all
+// registers explicitly to make sure there's space.
+//
+// - We'll be using cmpxchg8b, and when we do the operation, rv must be in
+// ecx:ebx, and rd must be edx:eax. We can't use ebx for rv initially because
+// we need ebx for a scratch also, so use a separate temp and move the value
+// to ebx just before the operation.
+//
+// In sum:
+//
+// - Initially rv=ecx:edx and eax is reserved, rd=unallocated.
+// - Then rp is popped into esi+edi because those are the only available.
+// - The Setup operation makes rv=ecx:ebx and rd=edx:eax and moves edx->ebx.
+// - Deallocation then frees only the ecx part of rv.
+
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rd, RegI64* rv) {
+ bc->needI32(bc->specific_.ecx);
+ bc->needI32(bc->specific_.edx);
+ bc->needI32(bc->specific_.eax);
+ *rv = RegI64(Register64(bc->specific_.ecx, bc->specific_.edx));
+ bc->popI64ToSpecific(*rv);
+}
+
+static void Setup(BaseCompiler* bc, RegI64* rv, RegI64* rd,
+ const ScratchAtomicNoHeapReg& scratch) {
+ MOZ_ASSERT(rv->high == bc->specific_.ecx);
+ MOZ_ASSERT(Register(scratch) == js::jit::ebx);
+ bc->masm.move32(rv->low, scratch);
+ *rv = bc->specific_.ecx_ebx;
+ *rd = bc->specific_.edx_eax;
+}
+
+static void Deallocate(BaseCompiler* bc, RegI64 rd, RegI64 rv) {
+ MOZ_ASSERT(rd == bc->specific_.edx_eax || rd == RegI64::Invalid());
+ bc->maybeFree(rd);
+ bc->freeI32(bc->specific_.ecx);
+}
+
+#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \
+ defined(JS_CODEGEN_LOONG64)
+
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rd, RegI64* rv) {
+ *rv = bc->popI64();
+ *rd = bc->needI64();
+}
+
+static void Deallocate(BaseCompiler* bc, RegI64 rd, RegI64 rv) {
+ bc->freeI64(rv);
+ bc->maybeFree(rd);
+}
+
+#elif defined(JS_CODEGEN_ARM)
+
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rd, RegI64* rv) {
+ // Both rv and rd must be odd/even pairs.
+ *rv = bc->popI64ToSpecific(bc->needI64Pair());
+ *rd = bc->needI64Pair();
+}
+
+static void Deallocate(BaseCompiler* bc, RegI64 rd, RegI64 rv) {
+ bc->freeI64(rv);
+ bc->maybeFree(rd);
+}
+
+#elif defined(JS_CODEGEN_RISCV64)
+
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rd, RegI64* rv) {
+ *rv = bc->popI64();
+ *rd = bc->needI64();
+}
+
+static void Deallocate(BaseCompiler* bc, RegI64 rd, RegI64 rv) {
+ bc->freeI64(rv);
+ bc->maybeFree(rd);
+}
+#elif defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32)
+
+static void PopAndAllocate(BaseCompiler*, RegI64*, RegI64*) {}
+static void Deallocate(BaseCompiler*, RegI64, RegI64) {}
+
+#endif
+
+} // namespace atomic_xchg64
+
+template <typename RegIndexType>
+void BaseCompiler::atomicXchg64(MemoryAccessDesc* access,
+ WantResult wantResult) {
+ RegI64 rd, rv;
+ atomic_xchg64::PopAndAllocate(this, &rd, &rv);
+
+ AccessCheck check;
+ RegIndexType rp = popMemoryAccess<RegIndexType>(access, &check);
+
+#ifdef WASM_HAS_HEAPREG
+ RegPtr instance = maybeLoadInstanceForAccess(access, check);
+ auto memaddr =
+ prepareAtomicMemoryAccess<RegIndexType>(access, &check, instance, rp);
+ masm.wasmAtomicExchange64(*access, memaddr, rv, rd);
+# ifndef RABALDR_PIN_INSTANCE
+ maybeFree(instance);
+# endif
+#else
+ ScratchAtomicNoHeapReg scratch(*this);
+ RegPtr instance =
+ maybeLoadInstanceForAccess(access, check, RegIntptrToRegPtr(scratch));
+ Address memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ atomic_xchg64::Setup(this, &rv, &rd, scratch);
+ masm.wasmAtomicExchange64(*access, memaddr, rv, rd);
+ MOZ_ASSERT(instance == scratch);
+#endif
+
+ free(rp);
+ if (wantResult) {
+ pushI64(rd);
+ rd = RegI64::Invalid();
+ }
+ atomic_xchg64::Deallocate(this, rd, rv);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Atomic compare-exchange.
+
+void BaseCompiler::atomicCmpXchg(MemoryAccessDesc* access, ValType type) {
+ Scalar::Type viewType = access->type();
+ if (Scalar::byteSize(viewType) <= 4) {
+ if (isMem32(access->memoryIndex())) {
+ atomicCmpXchg32<RegI32>(access, type);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ atomicCmpXchg32<RegI64>(access, type);
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+ } else {
+ MOZ_ASSERT(type == ValType::I64 && Scalar::byteSize(viewType) == 8);
+ if (isMem32(access->memoryIndex())) {
+ atomicCmpXchg64<RegI32>(access, type);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ atomicCmpXchg64<RegI64>(access, type);
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+ }
+}
+
+namespace atomic_cmpxchg32 {
+
+#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)
+
+using Temps = Nothing;
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, RegI32* rexpect, RegI32* rnew,
+ RegI32* rd, Temps*) {
+ // For cmpxchg, the expected value and the result are both in eax.
+ bc->needI32(bc->specific_.eax);
+ if (type == ValType::I64) {
+ *rnew = bc->popI64ToI32();
+ *rexpect = bc->popI64ToSpecificI32(bc->specific_.eax);
+ } else {
+ *rnew = bc->popI32();
+ *rexpect = bc->popI32ToSpecific(bc->specific_.eax);
+ }
+ *rd = *rexpect;
+}
+
+template <typename T>
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access, T srcAddr,
+ RegI32 rexpect, RegI32 rnew, RegI32 rd, const Temps&) {
+# if defined(JS_CODEGEN_X86)
+ ScratchI8 scratch(*bc);
+ if (access.type() == Scalar::Uint8) {
+ MOZ_ASSERT(rd == bc->specific_.eax);
+ if (!bc->ra.isSingleByteI32(rnew)) {
+ // The replacement value must have a byte persona.
+ bc->masm.movl(rnew, scratch);
+ rnew = scratch;
+ }
+ }
+# endif
+ bc->masm.wasmCompareExchange(access, srcAddr, rexpect, rnew, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32, RegI32 rnew, const Temps&) {
+ bc->freeI32(rnew);
+}
+
+#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
+
+using Temps = Nothing;
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, RegI32* rexpect, RegI32* rnew,
+ RegI32* rd, Temps*) {
+ if (type == ValType::I64) {
+ *rnew = bc->popI64ToI32();
+ *rexpect = bc->popI64ToI32();
+ } else {
+ *rnew = bc->popI32();
+ *rexpect = bc->popI32();
+ }
+ *rd = bc->needI32();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI32 rexpect, RegI32 rnew, RegI32 rd,
+ const Temps&) {
+ bc->masm.wasmCompareExchange(access, srcAddr, rexpect, rnew, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rexpect, RegI32 rnew,
+ const Temps&) {
+ bc->freeI32(rnew);
+ bc->freeI32(rexpect);
+}
+
+#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64)
+
+struct Temps {
+ RegI32 t0, t1, t2;
+};
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, RegI32* rexpect, RegI32* rnew,
+ RegI32* rd, Temps* temps) {
+ if (type == ValType::I64) {
+ *rnew = bc->popI64ToI32();
+ *rexpect = bc->popI64ToI32();
+ } else {
+ *rnew = bc->popI32();
+ *rexpect = bc->popI32();
+ }
+ if (Scalar::byteSize(viewType) < 4) {
+ temps->t0 = bc->needI32();
+ temps->t1 = bc->needI32();
+ temps->t2 = bc->needI32();
+ }
+ *rd = bc->needI32();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI32 rexpect, RegI32 rnew, RegI32 rd,
+ const Temps& temps) {
+ bc->masm.wasmCompareExchange(access, srcAddr, rexpect, rnew, temps.t0,
+ temps.t1, temps.t2, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rexpect, RegI32 rnew,
+ const Temps& temps) {
+ bc->freeI32(rnew);
+ bc->freeI32(rexpect);
+ bc->maybeFree(temps.t0);
+ bc->maybeFree(temps.t1);
+ bc->maybeFree(temps.t2);
+}
+
+#elif defined(JS_CODEGEN_RISCV64)
+
+struct Temps {
+ RegI32 t0, t1, t2;
+};
+
+static void PopAndAllocate(BaseCompiler* bc, ValType type,
+ Scalar::Type viewType, RegI32* rexpect, RegI32* rnew,
+ RegI32* rd, Temps* temps) {
+ if (type == ValType::I64) {
+ *rnew = bc->popI64ToI32();
+ *rexpect = bc->popI64ToI32();
+ } else {
+ *rnew = bc->popI32();
+ *rexpect = bc->popI32();
+ }
+ if (Scalar::byteSize(viewType) < 4) {
+ temps->t0 = bc->needI32();
+ temps->t1 = bc->needI32();
+ temps->t2 = bc->needI32();
+ }
+ *rd = bc->needI32();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI32 rexpect, RegI32 rnew, RegI32 rd,
+ const Temps& temps) {
+ bc->masm.wasmCompareExchange(access, srcAddr, rexpect, rnew, temps.t0,
+ temps.t1, temps.t2, rd);
+}
+
+static void Deallocate(BaseCompiler* bc, RegI32 rexpect, RegI32 rnew,
+ const Temps& temps) {
+ bc->freeI32(rnew);
+ bc->freeI32(rexpect);
+ bc->maybeFree(temps.t0);
+ bc->maybeFree(temps.t1);
+ bc->maybeFree(temps.t2);
+}
+
+#elif defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32)
+
+using Temps = Nothing;
+
+static void PopAndAllocate(BaseCompiler*, ValType, Scalar::Type, RegI32*,
+ RegI32*, RegI32*, Temps*) {}
+
+static void Perform(BaseCompiler*, const MemoryAccessDesc&, Address, RegI32,
+ RegI32, RegI32, const Temps& temps) {}
+
+static void Deallocate(BaseCompiler*, RegI32, RegI32, const Temps&) {}
+
+#endif
+
+} // namespace atomic_cmpxchg32
+
+template <typename RegIndexType>
+void BaseCompiler::atomicCmpXchg32(MemoryAccessDesc* access, ValType type) {
+ Scalar::Type viewType = access->type();
+ RegI32 rexpect, rnew, rd;
+ atomic_cmpxchg32::Temps temps;
+ atomic_cmpxchg32::PopAndAllocate(this, type, viewType, &rexpect, &rnew, &rd,
+ &temps);
+
+ AccessCheck check;
+ RegIndexType rp = popMemoryAccess<RegIndexType>(access, &check);
+ RegPtr instance = maybeLoadInstanceForAccess(access, check);
+
+ auto memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ atomic_cmpxchg32::Perform(this, *access, memaddr, rexpect, rnew, rd, temps);
+
+#ifndef RABALDR_PIN_INSTANCE
+ maybeFree(instance);
+#endif
+ free(rp);
+ atomic_cmpxchg32::Deallocate(this, rexpect, rnew, temps);
+
+ if (type == ValType::I64) {
+ pushU32AsI64(rd);
+ } else {
+ pushI32(rd);
+ }
+}
+
+namespace atomic_cmpxchg64 {
+
+// The templates are needed for x86 code generation, which needs complicated
+// register allocation for memory64.
+
+template <typename RegIndexType>
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew,
+ RegI64* rd);
+
+template <typename RegIndexType>
+static void Deallocate(BaseCompiler* bc, RegI64 rexpect, RegI64 rnew);
+
+#if defined(JS_CODEGEN_X64)
+
+template <typename RegIndexType>
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew,
+ RegI64* rd) {
+ // For cmpxchg, the expected value and the result are both in rax.
+ bc->needI64(bc->specific_.rax);
+ *rnew = bc->popI64();
+ *rexpect = bc->popI64ToSpecific(bc->specific_.rax);
+ *rd = *rexpect;
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI64 rexpect, RegI64 rnew, RegI64 rd) {
+ bc->masm.wasmCompareExchange64(access, srcAddr, rexpect, rnew, rd);
+}
+
+template <typename RegIndexType>
+static void Deallocate(BaseCompiler* bc, RegI64 rexpect, RegI64 rnew) {
+ bc->freeI64(rnew);
+}
+
+#elif defined(JS_CODEGEN_X86)
+
+template <typename RegIndexType>
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI64 rexpect, RegI64 rnew, RegI64 rd,
+ ScratchAtomicNoHeapReg& scratch);
+
+// Memory32: For cmpxchg8b, the expected value and the result are both in
+// edx:eax, and the replacement value is in ecx:ebx. But we can't allocate ebx
+// initially because we need it later for a scratch, so instead we allocate a
+// temp to hold the low word of 'new'.
+
+template <>
+void PopAndAllocate<RegI32>(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew,
+ RegI64* rd) {
+ bc->needI64(bc->specific_.edx_eax);
+ bc->needI32(bc->specific_.ecx);
+ RegI32 tmp = bc->needI32();
+ *rnew = bc->popI64ToSpecific(RegI64(Register64(bc->specific_.ecx, tmp)));
+ *rexpect = bc->popI64ToSpecific(bc->specific_.edx_eax);
+ *rd = *rexpect;
+}
+
+template <>
+void Perform<RegI32>(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI64 rexpect, RegI64 rnew, RegI64 rd,
+ ScratchAtomicNoHeapReg& scratch) {
+ MOZ_ASSERT(Register(scratch) == js::jit::ebx);
+ MOZ_ASSERT(rnew.high == bc->specific_.ecx);
+ bc->masm.move32(rnew.low, ebx);
+ bc->masm.wasmCompareExchange64(access, srcAddr, rexpect,
+ bc->specific_.ecx_ebx, rd);
+}
+
+template <>
+void Deallocate<RegI32>(BaseCompiler* bc, RegI64 rexpect, RegI64 rnew) {
+ bc->freeI64(rnew);
+}
+
+// Memory64: Register allocation is particularly hairy here. With memory64, we
+// have up to seven live values: i64 expected-value, i64 new-value, i64 pointer,
+// and instance. The instance can use the scratch but there's no avoiding that
+// we'll run out of registers.
+//
+// Unlike for the rmw ops, we can't use edx as the rnew.low since it's used
+// for the rexpect.high. And we can't push anything onto the stack while we're
+// popping the memory address because the memory address may be on the stack.
+
+# ifdef ENABLE_WASM_MEMORY64
+template <>
+void PopAndAllocate<RegI64>(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew,
+ RegI64* rd) {
+ // We reserve these (and ebx). The 64-bit pointer will end up in esi+edi.
+ bc->needI32(bc->specific_.eax);
+ bc->needI32(bc->specific_.ecx);
+ bc->needI32(bc->specific_.edx);
+
+ // Pop the 'new' value and stash it in the instance scratch area. Do not
+ // initialize *rnew to anything.
+ RegI64 tmp(Register64(bc->specific_.ecx, bc->specific_.edx));
+ bc->popI64ToSpecific(tmp);
+ {
+ ScratchPtr instanceScratch(*bc);
+ bc->stashI64(instanceScratch, tmp);
+ }
+
+ *rexpect = bc->popI64ToSpecific(bc->specific_.edx_eax);
+ *rd = *rexpect;
+}
+
+template <>
+void Perform<RegI64>(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI64 rexpect, RegI64 rnew, RegI64 rd,
+ ScratchAtomicNoHeapReg& scratch) {
+ MOZ_ASSERT(rnew.isInvalid());
+ rnew = bc->specific_.ecx_ebx;
+
+ bc->unstashI64(RegPtr(Register(bc->specific_.ecx)), rnew);
+ bc->masm.wasmCompareExchange64(access, srcAddr, rexpect, rnew, rd);
+}
+
+template <>
+void Deallocate<RegI64>(BaseCompiler* bc, RegI64 rexpect, RegI64 rnew) {
+ // edx:ebx have been pushed as the result, and the pointer was freed
+ // separately in the caller, so just free ecx.
+ bc->free(bc->specific_.ecx);
+}
+# endif
+
+#elif defined(JS_CODEGEN_ARM)
+
+template <typename RegIndexType>
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew,
+ RegI64* rd) {
+ // The replacement value and the result must both be odd/even pairs.
+ *rnew = bc->popI64Pair();
+ *rexpect = bc->popI64();
+ *rd = bc->needI64Pair();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI64 rexpect, RegI64 rnew, RegI64 rd) {
+ bc->masm.wasmCompareExchange64(access, srcAddr, rexpect, rnew, rd);
+}
+
+template <typename RegIndexType>
+static void Deallocate(BaseCompiler* bc, RegI64 rexpect, RegI64 rnew) {
+ bc->freeI64(rexpect);
+ bc->freeI64(rnew);
+}
+
+#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \
+ defined(JS_CODEGEN_LOONG64)
+
+template <typename RegIndexType>
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew,
+ RegI64* rd) {
+ *rnew = bc->popI64();
+ *rexpect = bc->popI64();
+ *rd = bc->needI64();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI64 rexpect, RegI64 rnew, RegI64 rd) {
+ bc->masm.wasmCompareExchange64(access, srcAddr, rexpect, rnew, rd);
+}
+
+template <typename RegIndexType>
+static void Deallocate(BaseCompiler* bc, RegI64 rexpect, RegI64 rnew) {
+ bc->freeI64(rexpect);
+ bc->freeI64(rnew);
+}
+
+#elif defined(JS_CODEGEN_RISCV64)
+
+template <typename RegIndexType>
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew,
+ RegI64* rd) {
+ *rnew = bc->popI64();
+ *rexpect = bc->popI64();
+ *rd = bc->needI64();
+}
+
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI64 rexpect, RegI64 rnew, RegI64 rd) {
+ bc->masm.wasmCompareExchange64(access, srcAddr, rexpect, rnew, rd);
+}
+
+template <typename RegIndexType>
+static void Deallocate(BaseCompiler* bc, RegI64 rexpect, RegI64 rnew) {
+ bc->freeI64(rexpect);
+ bc->freeI64(rnew);
+}
+
+#elif defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32)
+
+template <typename RegIndexType>
+static void PopAndAllocate(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew,
+ RegI64* rd) {}
+static void Perform(BaseCompiler* bc, const MemoryAccessDesc& access,
+ Address srcAddr, RegI64 rexpect, RegI64 rnew, RegI64 rd) {}
+template <typename RegIndexType>
+static void Deallocate(BaseCompiler* bc, RegI64 rexpect, RegI64 rnew) {}
+
+#endif
+
+} // namespace atomic_cmpxchg64
+
+template <typename RegIndexType>
+void BaseCompiler::atomicCmpXchg64(MemoryAccessDesc* access, ValType type) {
+ RegI64 rexpect, rnew, rd;
+ atomic_cmpxchg64::PopAndAllocate<RegIndexType>(this, &rexpect, &rnew, &rd);
+
+ AccessCheck check;
+ RegIndexType rp = popMemoryAccess<RegIndexType>(access, &check);
+
+#ifdef WASM_HAS_HEAPREG
+ RegPtr instance = maybeLoadInstanceForAccess(access, check);
+ auto memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ atomic_cmpxchg64::Perform(this, *access, memaddr, rexpect, rnew, rd);
+# ifndef RABALDR_PIN_INSTANCE
+ maybeFree(instance);
+# endif
+#else
+ ScratchAtomicNoHeapReg scratch(*this);
+ RegPtr instance =
+ maybeLoadInstanceForAccess(access, check, RegIntptrToRegPtr(scratch));
+ Address memaddr = prepareAtomicMemoryAccess(access, &check, instance, rp);
+ atomic_cmpxchg64::Perform<RegIndexType>(this, *access, memaddr, rexpect, rnew,
+ rd, scratch);
+ MOZ_ASSERT(instance == scratch);
+#endif
+
+ free(rp);
+ atomic_cmpxchg64::Deallocate<RegIndexType>(this, rexpect, rnew);
+
+ pushI64(rd);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Synchronization.
+
+bool BaseCompiler::atomicWait(ValType type, MemoryAccessDesc* access) {
+ switch (type.kind()) {
+ case ValType::I32: {
+ RegI64 timeout = popI64();
+ RegI32 val = popI32();
+
+ if (isMem32(access->memoryIndex())) {
+ computeEffectiveAddress<RegI32>(access);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ computeEffectiveAddress<RegI64>(access);
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+
+ pushI32(val);
+ pushI64(timeout);
+ pushI32(access->memoryIndex());
+
+ if (!emitInstanceCall(isMem32(access->memoryIndex()) ? SASigWaitI32M32
+ : SASigWaitI32M64)) {
+ return false;
+ }
+ break;
+ }
+ case ValType::I64: {
+ RegI64 timeout = popI64();
+ RegI64 val = popI64();
+
+ if (isMem32(access->memoryIndex())) {
+ computeEffectiveAddress<RegI32>(access);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+# ifdef JS_CODEGEN_X86
+ {
+ ScratchPtr scratch(*this);
+ stashI64(scratch, val);
+ freeI64(val);
+ }
+# endif
+ computeEffectiveAddress<RegI64>(access);
+# ifdef JS_CODEGEN_X86
+ {
+ ScratchPtr scratch(*this);
+ val = needI64();
+ unstashI64(scratch, val);
+ }
+# endif
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+
+ pushI64(val);
+ pushI64(timeout);
+ pushI32(access->memoryIndex());
+
+ if (!emitInstanceCall(isMem32(access->memoryIndex()) ? SASigWaitI64M32
+ : SASigWaitI64M64)) {
+ return false;
+ }
+ break;
+ }
+ default:
+ MOZ_CRASH();
+ }
+
+ return true;
+}
+
+bool BaseCompiler::atomicWake(MemoryAccessDesc* access) {
+ RegI32 count = popI32();
+
+ if (isMem32(access->memoryIndex())) {
+ computeEffectiveAddress<RegI32>(access);
+ RegI32 byteOffset = popI32();
+ pushI32(byteOffset);
+ } else {
+#ifdef ENABLE_WASM_MEMORY64
+ computeEffectiveAddress<RegI64>(access);
+ RegI64 byteOffset = popI64();
+ pushI64(byteOffset);
+#else
+ MOZ_CRASH("Memory64 not enabled / supported on this platform");
+#endif
+ }
+
+ pushI32(count);
+ pushI32(access->memoryIndex());
+ return emitInstanceCall(isMem32(access->memoryIndex()) ? SASigWakeM32
+ : SASigWakeM64);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Bulk memory.
+
+void BaseCompiler::memCopyInlineM32() {
+ MOZ_ASSERT(MaxInlineMemoryCopyLength != 0);
+
+ // This function assumes a memory index of zero
+ uint32_t memoryIndex = 0;
+ int32_t signedLength;
+ MOZ_ALWAYS_TRUE(popConst(&signedLength));
+ uint32_t length = signedLength;
+ MOZ_ASSERT(length != 0 && length <= MaxInlineMemoryCopyLength);
+
+ RegI32 src = popI32();
+ RegI32 dest = popI32();
+
+ // Compute the number of copies of each width we will need to do
+ size_t remainder = length;
+#ifdef ENABLE_WASM_SIMD
+ size_t numCopies16 = 0;
+ if (MacroAssembler::SupportsFastUnalignedFPAccesses()) {
+ numCopies16 = remainder / sizeof(V128);
+ remainder %= sizeof(V128);
+ }
+#endif
+#ifdef JS_64BIT
+ size_t numCopies8 = remainder / sizeof(uint64_t);
+ remainder %= sizeof(uint64_t);
+#endif
+ size_t numCopies4 = remainder / sizeof(uint32_t);
+ remainder %= sizeof(uint32_t);
+ size_t numCopies2 = remainder / sizeof(uint16_t);
+ remainder %= sizeof(uint16_t);
+ size_t numCopies1 = remainder;
+
+ // Load all source bytes onto the value stack from low to high using the
+ // widest transfer width we can for the system. We will trap without writing
+ // anything if any source byte is out-of-bounds.
+ bool omitBoundsCheck = false;
+ size_t offset = 0;
+
+#ifdef ENABLE_WASM_SIMD
+ for (uint32_t i = 0; i < numCopies16; i++) {
+ RegI32 temp = needI32();
+ moveI32(src, temp);
+ pushI32(temp);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Simd128, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ loadCommon(&access, check, ValType::V128);
+
+ offset += sizeof(V128);
+ omitBoundsCheck = true;
+ }
+#endif
+
+#ifdef JS_64BIT
+ for (uint32_t i = 0; i < numCopies8; i++) {
+ RegI32 temp = needI32();
+ moveI32(src, temp);
+ pushI32(temp);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Int64, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ loadCommon(&access, check, ValType::I64);
+
+ offset += sizeof(uint64_t);
+ omitBoundsCheck = true;
+ }
+#endif
+
+ for (uint32_t i = 0; i < numCopies4; i++) {
+ RegI32 temp = needI32();
+ moveI32(src, temp);
+ pushI32(temp);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Uint32, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ loadCommon(&access, check, ValType::I32);
+
+ offset += sizeof(uint32_t);
+ omitBoundsCheck = true;
+ }
+
+ if (numCopies2) {
+ RegI32 temp = needI32();
+ moveI32(src, temp);
+ pushI32(temp);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Uint16, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ loadCommon(&access, check, ValType::I32);
+
+ offset += sizeof(uint16_t);
+ omitBoundsCheck = true;
+ }
+
+ if (numCopies1) {
+ RegI32 temp = needI32();
+ moveI32(src, temp);
+ pushI32(temp);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Uint8, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ loadCommon(&access, check, ValType::I32);
+ }
+
+ // Store all source bytes from the value stack to the destination from
+ // high to low. We will trap without writing anything on the first store
+ // if any dest byte is out-of-bounds.
+ offset = length;
+ omitBoundsCheck = false;
+
+ if (numCopies1) {
+ offset -= sizeof(uint8_t);
+
+ RegI32 value = popI32();
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushI32(value);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Uint8, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ storeCommon(&access, check, ValType::I32);
+
+ omitBoundsCheck = true;
+ }
+
+ if (numCopies2) {
+ offset -= sizeof(uint16_t);
+
+ RegI32 value = popI32();
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushI32(value);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Uint16, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ storeCommon(&access, check, ValType::I32);
+
+ omitBoundsCheck = true;
+ }
+
+ for (uint32_t i = 0; i < numCopies4; i++) {
+ offset -= sizeof(uint32_t);
+
+ RegI32 value = popI32();
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushI32(value);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Uint32, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ storeCommon(&access, check, ValType::I32);
+
+ omitBoundsCheck = true;
+ }
+
+#ifdef JS_64BIT
+ for (uint32_t i = 0; i < numCopies8; i++) {
+ offset -= sizeof(uint64_t);
+
+ RegI64 value = popI64();
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushI64(value);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Int64, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ storeCommon(&access, check, ValType::I64);
+
+ omitBoundsCheck = true;
+ }
+#endif
+
+#ifdef ENABLE_WASM_SIMD
+ for (uint32_t i = 0; i < numCopies16; i++) {
+ offset -= sizeof(V128);
+
+ RegV128 value = popV128();
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushV128(value);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Simd128, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ storeCommon(&access, check, ValType::V128);
+
+ omitBoundsCheck = true;
+ }
+#endif
+
+ freeI32(dest);
+ freeI32(src);
+}
+
+void BaseCompiler::memFillInlineM32() {
+ MOZ_ASSERT(MaxInlineMemoryFillLength != 0);
+
+ // This function assumes a memory index of zero
+ uint32_t memoryIndex = 0;
+ int32_t signedLength;
+ int32_t signedValue;
+ MOZ_ALWAYS_TRUE(popConst(&signedLength));
+ MOZ_ALWAYS_TRUE(popConst(&signedValue));
+ uint32_t length = uint32_t(signedLength);
+ uint32_t value = uint32_t(signedValue);
+ MOZ_ASSERT(length != 0 && length <= MaxInlineMemoryFillLength);
+
+ RegI32 dest = popI32();
+
+ // Compute the number of copies of each width we will need to do
+ size_t remainder = length;
+#ifdef ENABLE_WASM_SIMD
+ size_t numCopies16 = 0;
+ if (MacroAssembler::SupportsFastUnalignedFPAccesses()) {
+ numCopies16 = remainder / sizeof(V128);
+ remainder %= sizeof(V128);
+ }
+#endif
+#ifdef JS_64BIT
+ size_t numCopies8 = remainder / sizeof(uint64_t);
+ remainder %= sizeof(uint64_t);
+#endif
+ size_t numCopies4 = remainder / sizeof(uint32_t);
+ remainder %= sizeof(uint32_t);
+ size_t numCopies2 = remainder / sizeof(uint16_t);
+ remainder %= sizeof(uint16_t);
+ size_t numCopies1 = remainder;
+
+ MOZ_ASSERT(numCopies2 <= 1 && numCopies1 <= 1);
+
+ // Generate splatted definitions for wider fills as needed
+#ifdef ENABLE_WASM_SIMD
+ V128 val16(value);
+#endif
+#ifdef JS_64BIT
+ uint64_t val8 = SplatByteToUInt<uint64_t>(value, 8);
+#endif
+ uint32_t val4 = SplatByteToUInt<uint32_t>(value, 4);
+ uint32_t val2 = SplatByteToUInt<uint32_t>(value, 2);
+ uint32_t val1 = value;
+
+ // Store the fill value to the destination from high to low. We will trap
+ // without writing anything on the first store if any dest byte is
+ // out-of-bounds.
+ size_t offset = length;
+ bool omitBoundsCheck = false;
+
+ if (numCopies1) {
+ offset -= sizeof(uint8_t);
+
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushI32(val1);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Uint8, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ storeCommon(&access, check, ValType::I32);
+
+ omitBoundsCheck = true;
+ }
+
+ if (numCopies2) {
+ offset -= sizeof(uint16_t);
+
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushI32(val2);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Uint16, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ storeCommon(&access, check, ValType::I32);
+
+ omitBoundsCheck = true;
+ }
+
+ for (uint32_t i = 0; i < numCopies4; i++) {
+ offset -= sizeof(uint32_t);
+
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushI32(val4);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Uint32, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ storeCommon(&access, check, ValType::I32);
+
+ omitBoundsCheck = true;
+ }
+
+#ifdef JS_64BIT
+ for (uint32_t i = 0; i < numCopies8; i++) {
+ offset -= sizeof(uint64_t);
+
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushI64(val8);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Int64, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ storeCommon(&access, check, ValType::I64);
+
+ omitBoundsCheck = true;
+ }
+#endif
+
+#ifdef ENABLE_WASM_SIMD
+ for (uint32_t i = 0; i < numCopies16; i++) {
+ offset -= sizeof(V128);
+
+ RegI32 temp = needI32();
+ moveI32(dest, temp);
+ pushI32(temp);
+ pushV128(val16);
+
+ MemoryAccessDesc access(memoryIndex, Scalar::Simd128, 1, offset,
+ bytecodeOffset(), hugeMemoryEnabled(memoryIndex));
+ AccessCheck check;
+ check.omitBoundsCheck = omitBoundsCheck;
+ storeCommon(&access, check, ValType::V128);
+
+ omitBoundsCheck = true;
+ }
+#endif
+
+ freeI32(dest);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// SIMD and Relaxed SIMD.
+
+#ifdef ENABLE_WASM_SIMD
+void BaseCompiler::loadSplat(MemoryAccessDesc* access) {
+ // We can implement loadSplat mostly as load + splat because the push of the
+ // result onto the value stack in loadCommon normally will not generate any
+ // code, it will leave the value in a register which we will consume.
+
+ // We use uint types when we can on the general assumption that unsigned loads
+ // might be smaller/faster on some platforms, because no sign extension needs
+ // to be done after the sub-register load.
+ RegV128 rd = needV128();
+ switch (access->type()) {
+ case Scalar::Uint8: {
+ loadCommon(access, AccessCheck(), ValType::I32);
+ RegI32 rs = popI32();
+ masm.splatX16(rs, rd);
+ free(rs);
+ break;
+ }
+ case Scalar::Uint16: {
+ loadCommon(access, AccessCheck(), ValType::I32);
+ RegI32 rs = popI32();
+ masm.splatX8(rs, rd);
+ free(rs);
+ break;
+ }
+ case Scalar::Uint32: {
+ loadCommon(access, AccessCheck(), ValType::I32);
+ RegI32 rs = popI32();
+ masm.splatX4(rs, rd);
+ free(rs);
+ break;
+ }
+ case Scalar::Int64: {
+ loadCommon(access, AccessCheck(), ValType::I64);
+ RegI64 rs = popI64();
+ masm.splatX2(rs, rd);
+ free(rs);
+ break;
+ }
+ default:
+ MOZ_CRASH();
+ }
+ pushV128(rd);
+}
+
+void BaseCompiler::loadZero(MemoryAccessDesc* access) {
+ access->setZeroExtendSimd128Load();
+ loadCommon(access, AccessCheck(), ValType::V128);
+}
+
+void BaseCompiler::loadExtend(MemoryAccessDesc* access, Scalar::Type viewType) {
+ loadCommon(access, AccessCheck(), ValType::I64);
+
+ RegI64 rs = popI64();
+ RegV128 rd = needV128();
+ masm.moveGPR64ToDouble(rs, rd);
+ switch (viewType) {
+ case Scalar::Int8:
+ masm.widenLowInt8x16(rd, rd);
+ break;
+ case Scalar::Uint8:
+ masm.unsignedWidenLowInt8x16(rd, rd);
+ break;
+ case Scalar::Int16:
+ masm.widenLowInt16x8(rd, rd);
+ break;
+ case Scalar::Uint16:
+ masm.unsignedWidenLowInt16x8(rd, rd);
+ break;
+ case Scalar::Int32:
+ masm.widenLowInt32x4(rd, rd);
+ break;
+ case Scalar::Uint32:
+ masm.unsignedWidenLowInt32x4(rd, rd);
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ freeI64(rs);
+ pushV128(rd);
+}
+
+void BaseCompiler::loadLane(MemoryAccessDesc* access, uint32_t laneIndex) {
+ ValType type = access->type() == Scalar::Int64 ? ValType::I64 : ValType::I32;
+
+ RegV128 rsd = popV128();
+ loadCommon(access, AccessCheck(), type);
+
+ if (type == ValType::I32) {
+ RegI32 rs = popI32();
+ switch (access->type()) {
+ case Scalar::Uint8:
+ masm.replaceLaneInt8x16(laneIndex, rs, rsd);
+ break;
+ case Scalar::Uint16:
+ masm.replaceLaneInt16x8(laneIndex, rs, rsd);
+ break;
+ case Scalar::Int32:
+ masm.replaceLaneInt32x4(laneIndex, rs, rsd);
+ break;
+ default:
+ MOZ_CRASH("unsupported access type");
+ }
+ freeI32(rs);
+ } else {
+ MOZ_ASSERT(type == ValType::I64);
+ RegI64 rs = popI64();
+ masm.replaceLaneInt64x2(laneIndex, rs, rsd);
+ freeI64(rs);
+ }
+
+ pushV128(rsd);
+}
+
+void BaseCompiler::storeLane(MemoryAccessDesc* access, uint32_t laneIndex) {
+ ValType type = access->type() == Scalar::Int64 ? ValType::I64 : ValType::I32;
+
+ RegV128 rs = popV128();
+ if (type == ValType::I32) {
+ RegI32 tmp = needI32();
+ switch (access->type()) {
+ case Scalar::Uint8:
+ masm.extractLaneInt8x16(laneIndex, rs, tmp);
+ break;
+ case Scalar::Uint16:
+ masm.extractLaneInt16x8(laneIndex, rs, tmp);
+ break;
+ case Scalar::Int32:
+ masm.extractLaneInt32x4(laneIndex, rs, tmp);
+ break;
+ default:
+ MOZ_CRASH("unsupported laneSize");
+ }
+ pushI32(tmp);
+ } else {
+ MOZ_ASSERT(type == ValType::I64);
+ RegI64 tmp = needI64();
+ masm.extractLaneInt64x2(laneIndex, rs, tmp);
+ pushI64(tmp);
+ }
+ freeV128(rs);
+
+ storeCommon(access, AccessCheck(), type);
+}
+#endif // ENABLE_WASM_SIMD
+
+} // namespace wasm
+} // namespace js