commit 64d90c3f9a4ab0b1d96b0994609fff7dd39fc28d
parent 756cbe40786a688f8aa04257e9163da44eb29ca3
Author: Ben Visness <bvisness@mozilla.com>
Date: Mon, 1 Dec 2025 21:21:44 +0000
Bug 2002621: Isolate ARM immediate requirement to asm.js r=rhunt
We never actually use the boundsCheckLimit in an immediate, ever. We
can isolate the boundsCheckLimit ARM immediate requirement to our asm.js
code to simplify implementation and make custom page sizes easier to
implement.
Differential Revision: https://phabricator.services.mozilla.com/D274172
Diffstat:
9 files changed, 64 insertions(+), 113 deletions(-)
diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp
@@ -122,12 +122,45 @@ static const size_t MinHeapLength = PageSize;
// See IsValidAsmJSHeapLength().
static const uint64_t MaxHeapLength = 0x7f000000;
+// Because ARM has a fixed-width instruction encoding, ARM can only express a
+// limited subset of immediates (in a single instruction).
+static const uint64_t HighestValidARMImmediate = 0xff000000;
+
+// Heap length on ARM should fit in an ARM immediate. We approximate the set
+// of valid ARM immediates with the predicate:
+// 2^n for n in [16, 24)
+// or
+// 2^24 * n for n >= 1.
+static bool IsValidARMImmediate(uint32_t i) {
+ bool valid = (IsPowerOfTwo(i) || (i & 0x00ffffff) == 0);
+
+ MOZ_ASSERT_IF(valid, i % PageSize == 0);
+
+ return valid;
+}
+
+static uint64_t RoundUpToNextValidARMImmediate(uint64_t i) {
+ MOZ_ASSERT(i <= HighestValidARMImmediate);
+ static_assert(HighestValidARMImmediate == 0xff000000,
+ "algorithm relies on specific constant");
+
+ if (i <= 16 * 1024 * 1024) {
+ i = i ? mozilla::RoundUpPow2(i) : 0;
+ } else {
+ i = (i + 0x00ffffff) & ~0x00ffffff;
+ }
+
+ MOZ_ASSERT(IsValidARMImmediate(i));
+
+ return i;
+}
+
static uint64_t RoundUpToNextValidAsmJSHeapLength(uint64_t length) {
if (length <= MinHeapLength) {
return MinHeapLength;
}
- return wasm::RoundUpToNextValidARMImmediate(length);
+ return RoundUpToNextValidARMImmediate(length);
}
static uint64_t DivideRoundingUp(uint64_t a, uint64_t b) {
@@ -7397,5 +7430,6 @@ bool js::IsValidAsmJSHeapLength(size_t length) {
return false;
}
- return wasm::IsValidARMImmediate(length);
+ // asm.js specifies that the heap size must fit in an ARM immediate.
+ return IsValidARMImmediate(length);
}
diff --git a/js/src/wasm/WasmBCMemory.cpp b/js/src/wasm/WasmBCMemory.cpp
@@ -420,11 +420,11 @@ void BaseCompiler::prepareMemoryAccess(MemoryAccessDesc* access,
#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 (!codeMeta_.memories[access->memoryIndex()].boundsCheckLimitIs32Bits() &&
- MaxMemoryPages(
+ if (!codeMeta_.memories[access->memoryIndex()]
+ .boundsCheckLimitIsAlways32Bits() &&
+ MaxMemoryBytes(
codeMeta_.memories[access->memoryIndex()].addressType()) >=
- Pages(0x100000000 / PageSize)) {
+ 0x100000000) {
boundsCheck4GBOrLargerAccess(access->memoryIndex(), instance, ptr, &ok);
} else {
boundsCheckBelow4GBAccess(access->memoryIndex(), instance, ptr, &ok);
diff --git a/js/src/wasm/WasmInstance.h b/js/src/wasm/WasmInstance.h
@@ -90,9 +90,11 @@ class alignas(16) Instance {
// always in sync with the MemoryInstanceData for memory 0.
uint8_t* memory0Base_;
- // Bounds check limit in bytes (or zero if there is no memory) for memory 0
- // This is 64-bits on 64-bit systems so as to allow for heap lengths up to and
- // beyond 4GB, and 32-bits on 32-bit systems, where memories are limited to
+ // Bounds check limit in bytes for memory 0. If there is no memory 0, this
+ // value will be zero.
+ //
+ // This is 64 bits on 64-bit systems so as to allow for heap lengths up to and
+ // beyond 4GB, and 32 bits on 32-bit systems, where memories are limited to
// 2GB.
//
// See "Linear memory addresses and bounds checking" in WasmMemory.cpp.
diff --git a/js/src/wasm/WasmInstanceData.h b/js/src/wasm/WasmInstanceData.h
@@ -150,9 +150,9 @@ struct MemoryInstanceData {
// Pointer to the base of the memory.
uint8_t* base;
- // Bounds check limit in bytes (or zero if there is no memory). This is
- // 64-bits on 64-bit systems so as to allow for heap lengths up to and beyond
- // 4GB, and 32-bits on 32-bit systems, where heaps are limited to 2GB.
+ // Bounds check limit in bytes. This is 64 bits on 64-bit systems so as to
+ // allow for heap lengths up to and beyond 4GB, and 32 bits on 32-bit systems,
+ // where heaps are limited to 2GB.
//
// See "Linear memory addresses and bounds checking" in WasmMemory.cpp.
uintptr_t boundsCheckLimit;
diff --git a/js/src/wasm/WasmIonCompile.cpp b/js/src/wasm/WasmIonCompile.cpp
@@ -1589,12 +1589,11 @@ class FunctionCompiler {
//
// 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.
- static_assert(0x100000000 % PageSize == 0);
bool mem32LimitIs64Bits =
isMem32(memoryIndex) &&
- !codeMeta().memories[memoryIndex].boundsCheckLimitIs32Bits() &&
- MaxMemoryPages(codeMeta().memories[memoryIndex].addressType()) >=
- Pages(0x100000000 / PageSize);
+ !codeMeta().memories[memoryIndex].boundsCheckLimitIsAlways32Bits() &&
+ MaxMemoryBytes(codeMeta().memories[memoryIndex].addressType()) >=
+ 0x100000000;
#else
// On 32-bit platforms we have no more than 2GB memory and the limit for a
// 32-bit base pointer is never a 64-bit value.
diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp
@@ -2599,7 +2599,6 @@ size_t WasmMemoryObject::boundsCheckLimit() const {
#endif
MOZ_ASSERT(mappedSize % wasm::PageSize == 0);
MOZ_ASSERT(mappedSize >= wasm::GuardSize);
- MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize - wasm::GuardSize));
size_t limit = mappedSize - wasm::GuardSize;
MOZ_ASSERT(limit <= MaxMemoryBoundsCheckLimit(addressType()));
return limit;
diff --git a/js/src/wasm/WasmMemory.cpp b/js/src/wasm/WasmMemory.cpp
@@ -312,69 +312,23 @@ static const size_t MinOffsetGuardLimit = OffsetGuardLimit;
static_assert(MaxInlineMemoryCopyLength < MinOffsetGuardLimit, "precondition");
static_assert(MaxInlineMemoryFillLength < MinOffsetGuardLimit, "precondition");
-#ifdef JS_64BIT
wasm::Pages wasm::MaxMemoryPages(AddressType t) {
+#ifdef JS_64BIT
MOZ_ASSERT_IF(t == AddressType::I64, !IsHugeMemoryEnabled(t));
size_t desired = MaxMemoryPagesValidation(t);
constexpr size_t actual = ArrayBufferObject::ByteLengthLimit / PageSize;
return wasm::Pages(std::min(desired, actual));
-}
-
-size_t wasm::MaxMemoryBoundsCheckLimit(AddressType t) {
- return MaxMemoryPages(t).byteLength();
-}
-
#else
-// On 32-bit systems, the heap limit must be representable in the nonnegative
-// range of an int32_t, which means the maximum heap size as observed by wasm
-// code is one wasm page less than 2GB.
-wasm::Pages wasm::MaxMemoryPages(AddressType t) {
+ // On 32-bit systems, the heap limit must be representable in the nonnegative
+ // range of an int32_t, which means the maximum heap size as observed by wasm
+ // code is one wasm page less than 2GB.
static_assert(ArrayBufferObject::ByteLengthLimit >= INT32_MAX / PageSize);
return wasm::Pages(INT32_MAX / PageSize);
-}
-
-// The max bounds check limit can be larger than the MaxMemoryPages because it
-// is really MaxMemoryPages rounded up to the next valid bounds check immediate,
-// see ComputeMappedSize().
-size_t wasm::MaxMemoryBoundsCheckLimit(AddressType t) {
- size_t boundsCheckLimit = size_t(INT32_MAX) + 1;
- MOZ_ASSERT(IsValidBoundsCheckImmediate(boundsCheckLimit));
- return boundsCheckLimit;
-}
#endif
-
-// Because ARM has a fixed-width instruction encoding, ARM can only express a
-// limited subset of immediates (in a single instruction).
-
-static const uint64_t HighestValidARMImmediate = 0xff000000;
-
-// Heap length on ARM should fit in an ARM immediate. We approximate the set
-// of valid ARM immediates with the predicate:
-// 2^n for n in [16, 24)
-// or
-// 2^24 * n for n >= 1.
-bool wasm::IsValidARMImmediate(uint32_t i) {
- bool valid = (IsPowerOfTwo(i) || (i & 0x00ffffff) == 0);
-
- MOZ_ASSERT_IF(valid, i % PageSize == 0);
-
- return valid;
}
-uint64_t wasm::RoundUpToNextValidARMImmediate(uint64_t i) {
- MOZ_ASSERT(i <= HighestValidARMImmediate);
- static_assert(HighestValidARMImmediate == 0xff000000,
- "algorithm relies on specific constant");
-
- if (i <= 16 * 1024 * 1024) {
- i = i ? mozilla::RoundUpPow2(i) : 0;
- } else {
- i = (i + 0x00ffffff) & ~0x00ffffff;
- }
-
- MOZ_ASSERT(IsValidARMImmediate(i));
-
- return i;
+size_t wasm::MaxMemoryBoundsCheckLimit(AddressType t) {
+ return MaxMemoryBytes(t);
}
Pages wasm::ClampedMaxPages(AddressType t, Pages initialPages,
@@ -396,8 +350,6 @@ Pages wasm::ClampedMaxPages(AddressType t, Pages initialPages,
// clampedMaxPages.
static const uint64_t OneGib = 1 << 30;
static const Pages OneGibPages = Pages(OneGib >> wasm::PageBits);
- static_assert(HighestValidARMImmediate > OneGib,
- "computing mapped size on ARM requires clamped max size");
Pages clampedPages = std::max(OneGibPages, initialPages);
clampedMaxPages = std::min(clampedPages, clampedMaxPages);
@@ -422,31 +374,9 @@ size_t wasm::ComputeMappedSize(wasm::Pages clampedMaxPages) {
// implementation limits.
size_t maxSize = clampedMaxPages.byteLength();
- // It is the bounds-check limit, not the mapped size, that gets baked into
- // code. Thus round up the maxSize to the next valid immediate value
- // *before* adding in the guard page.
- //
- // Also see "Wasm Linear Memory Structure" in vm/ArrayBufferObject.cpp.
- uint64_t boundsCheckLimit = RoundUpToNextValidBoundsCheckImmediate(maxSize);
- MOZ_ASSERT(IsValidBoundsCheckImmediate(boundsCheckLimit));
-
- MOZ_ASSERT(boundsCheckLimit % gc::SystemPageSize() == 0);
+ MOZ_ASSERT(maxSize % gc::SystemPageSize() == 0);
MOZ_ASSERT(GuardSize % gc::SystemPageSize() == 0);
- return boundsCheckLimit + GuardSize;
-}
-
-bool wasm::IsValidBoundsCheckImmediate(uint32_t i) {
-#ifdef JS_CODEGEN_ARM
- return IsValidARMImmediate(i);
-#else
- return true;
-#endif
-}
+ maxSize += GuardSize;
-uint64_t wasm::RoundUpToNextValidBoundsCheckImmediate(uint64_t i) {
-#ifdef JS_CODEGEN_ARM
- return RoundUpToNextValidARMImmediate(i);
-#else
- return i;
-#endif
+ return maxSize;
}
diff --git a/js/src/wasm/WasmMemory.h b/js/src/wasm/WasmMemory.h
@@ -136,10 +136,7 @@ static inline size_t MaxMemoryBytes(AddressType t) {
return MaxMemoryPages(t).byteLength();
}
-// A value at least as large as MaxMemoryBytes(t) representing the largest valid
-// bounds check limit on the system. (It can be larger than MaxMemoryBytes()
-// because bounds check limits are rounded up to fit formal requirements on some
-// platforms. Also see ComputeMappedSize().)
+// A value representing the largest valid value for boundsCheckLimit.
extern size_t MaxMemoryBoundsCheckLimit(AddressType t);
static inline uint64_t MaxMemoryPagesValidation(AddressType addressType) {
@@ -166,19 +163,10 @@ extern size_t ComputeMappedSize(Pages clampedMaxPages);
extern uint64_t GetMaxOffsetGuardLimit(bool hugeMemory);
-// Return whether the given immediate satisfies the constraints of the platform.
-extern bool IsValidBoundsCheckImmediate(uint32_t i);
-
-// Return whether the given immediate is valid on arm.
-extern bool IsValidARMImmediate(uint32_t i);
-
// Return the next higher valid immediate that satisfies the constraints of the
// platform.
extern uint64_t RoundUpToNextValidBoundsCheckImmediate(uint64_t i);
-// Return the next higher valid immediate for arm.
-extern uint64_t RoundUpToNextValidARMImmediate(uint64_t i);
-
#ifdef WASM_SUPPORTS_HUGE_MEMORY
// On WASM_SUPPORTS_HUGE_MEMORY platforms, every asm.js or WebAssembly 32-bit
// memory unconditionally allocates a huge region of virtual memory of size
diff --git a/js/src/wasm/WasmModuleTypes.h b/js/src/wasm/WasmModuleTypes.h
@@ -825,10 +825,9 @@ struct MemoryDesc {
// Whether a backing store for this memory may move when grown.
bool canMovingGrow() const { return limits.maximum.isNothing(); }
- // Whether the bounds check limit (see the doc comment in
- // ArrayBufferObject.cpp regarding linear memory structure) can ever be
- // larger than 32-bits.
- bool boundsCheckLimitIs32Bits() const {
+ // Whether the boundsCheckLimit will always fit within 32 bits. See the SMDOC
+ // for "WASM Linear Memory structure".
+ bool boundsCheckLimitIsAlways32Bits() const {
return limits.maximum.isSome() &&
limits.maximum.value() < (0x100000000 / PageSize);
}