commit a548794c3bff22010945cd0a4f80fc05c9d689ba
parent 338cdae51c4db122cb9a9fcacce7f13efdd98940
Author: Asumu Takikawa <asumu@igalia.com>
Date: Mon, 8 Dec 2025 16:02:52 +0000
Bug 1977854 - Wasm custom page sizes part 2: decoding of page sizes. r=rhunt,bvisness
Differential Revision: https://phabricator.services.mozilla.com/D257728
Diffstat:
12 files changed, 189 insertions(+), 25 deletions(-)
diff --git a/js/src/jit-test/tests/wasm/custom-page-sizes/decoding.js b/js/src/jit-test/tests/wasm/custom-page-sizes/decoding.js
@@ -0,0 +1,114 @@
+// |jit-test| skip-if: !wasmCustomPageSizesEnabled()
+
+load(libdir + "wasm-binary.js");
+
+wasmValidateText(`(module
+ (memory 0 1 (pagesize 1))
+ )
+`);
+
+wasmValidateText(`(module
+ (memory 0 1 (pagesize 65536))
+ )
+`);
+
+// Missing page size.
+assertErrorMessage(() => wasmEvalBinary(moduleWithSections([ {name:memoryId, body:[0x01, 0x08, 0x00]} ])), WebAssembly.CompileError, /failed to decode custom page size/);
+
+// Too many bytes after page size.
+assertErrorMessage(() => wasmEvalBinary(moduleWithSections([ {name:memoryId, body:[0x01, 0x08, 0x00, 0x10, 0x01]} ])), WebAssembly.CompileError, /byte size mismatch in memory section/);
+
+// Tables shouldn't have page sizes.
+assertErrorMessage(() => wasmEvalBinary(moduleWithSections([ {name:tableId, body:[0x01, FuncRefCode, 0x08, 0x00, 0x10]} ])), WebAssembly.CompileError, /unexpected bits set in flags: 8/);
+
+function checkInvalidPageSize(pageSize) {
+ assertErrorMessage(() => wasmValidateText(`(module
+ (memory 0 1 (pagesize ${pageSize}))
+ )`),
+ WebAssembly.CompileError, /bad custom page size/);
+}
+
+checkInvalidPageSize(8);
+checkInvalidPageSize(128);
+checkInvalidPageSize(131072);
+
+wasmValidateText(`(module
+ (memory 0 65536 (pagesize 1))
+ )
+`);
+
+wasmValidateText(`(module
+ (memory i32 0 65536 (pagesize 65536))
+ )
+`);
+
+wasmValidateText(`(module
+ (memory i32 0 4_294_967_295 (pagesize 1))
+ )
+`);
+
+wasmValidateText(`(module
+ (memory i64 0 4_294_967_296 (pagesize 1))
+ )
+`);
+
+wasmValidateText(`(module
+ (memory i64 0 9_007_199_254_740_991 (pagesize 1))
+ )
+`);
+
+function maxPageCount(pageSize) {
+ if (pageSize === 1)
+ return 0xffffffff;
+ return 1 << (32 - Math.log2(pageSize));
+}
+
+function maxPageCount64(pageSize) {
+ if (pageSize === 1)
+ return BigInt(Number.MAX_SAFE_INTEGER);
+ return (1n << (53n - BigInt(Math.log2(pageSize)))) - 1n;
+}
+
+function checkPageCount(pageSize, pageCount) {
+ function check() {
+ return wasmValidateText(`(module
+ (memory 0 ${pageCount} (pagesize ${pageSize}))
+ )`);
+ }
+ if (pageCount <= maxPageCount(pageSize))
+ check();
+ else if (pageCount > 0xffffffff)
+ // Unrepresentable in the text format as it's out of bounds
+ assertErrorMessage(check, SyntaxError,
+ /invalid u32 number/);
+ else
+ assertErrorMessage(check, WebAssembly.CompileError,
+ /maximum memory size too big/);
+}
+
+function checkPageCount64(pageSize, pageCount) {
+ function check() {
+ return wasmValidateText(`(module
+ (memory i64 0 ${pageCount} (pagesize ${pageSize}))
+ )`);
+ }
+ if (pageCount <= maxPageCount64(pageSize))
+ check();
+ else {
+ assertErrorMessage(check, WebAssembly.CompileError,
+ /maximum memory size too big/);
+ }
+}
+
+for (pageSize of [1, 65536])
+ for (pageCount of [0, 1, 42, maxPageCount(pageSize)-1,
+ maxPageCount(pageSize),
+ maxPageCount(pageSize)+1,
+ 0xffffffff])
+ checkPageCount(pageSize, pageCount);
+
+for (pageSize of [1, 65536])
+ for (pageCount of [0n, 1n, 42n, maxPageCount64(pageSize)-1n,
+ maxPageCount64(pageSize),
+ maxPageCount64(pageSize)+1n])
+ checkPageCount64(pageSize, pageCount);
diff --git a/js/src/jit-test/tests/wasm/custom-page-sizes/directives.txt b/js/src/jit-test/tests/wasm/custom-page-sizes/directives.txt
@@ -0,0 +1 @@
+|jit-test| test-also=-P wasm_custom_page_sizes; test-also=--wasm-compiler=baseline -P wasm_custom_page_sizes; test-also=--wasm-compiler=optimizing -P wasm_custom_page_sizes; test-also=--disable-wasm-huge-memory -P wasm_custom_page_sizes; include:wasm.js
diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp
@@ -554,8 +554,8 @@ bool ArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx,
Pages sourceMaxPages = buffer->wasmSourceMaxPages().value();
uint64_t sourceMaxBytes = sourceMaxPages.byteLength64();
- MOZ_ASSERT(sourceMaxBytes <=
- wasm::StandardPageSizeBytes * wasm::MaxMemory64PagesValidation);
+ MOZ_ASSERT(sourceMaxBytes <= wasm::StandardPageSizeBytes *
+ wasm::MaxMemory64StandardPagesValidation);
args.rval().setNumber(double(sourceMaxBytes));
return true;
@@ -1797,6 +1797,7 @@ ArrayBufferObjectMaybeShared* js::CreateWasmBuffer(
memory.initialPages() <=
wasm::MaxMemoryPages(memory.addressType(), memory.pageSize()));
MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers);
+ MOZ_ASSERT(memory.pageSize() == wasm::PageSize::Standard);
if (memory.isShared()) {
if (!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
diff --git a/js/src/vm/SharedArrayObject.cpp b/js/src/vm/SharedArrayObject.cpp
@@ -345,8 +345,8 @@ bool SharedArrayBufferObject::maxByteLengthGetterImpl(JSContext* cx,
Pages sourceMaxPages = buffer->rawWasmBufferObject()->wasmSourceMaxPages();
uint64_t sourceMaxBytes = sourceMaxPages.byteLength64();
- MOZ_ASSERT(sourceMaxBytes <=
- wasm::StandardPageSizeBytes * wasm::MaxMemory64PagesValidation);
+ MOZ_ASSERT(sourceMaxBytes <= wasm::StandardPageSizeBytes *
+ wasm::MaxMemory64StandardPagesValidation);
args.rval().setNumber(double(sourceMaxBytes));
return true;
diff --git a/js/src/wasm/WasmBCMemory.cpp b/js/src/wasm/WasmBCMemory.cpp
@@ -147,7 +147,7 @@ RegI32 BaseCompiler::popConstMemoryAccess<RegI32>(MemoryAccessDesc* access,
// Validation ensures that the offset is in 32-bit range, and the calculation
// of the limit cannot overflow due to our choice of HugeOffsetGuardLimit.
#ifdef WASM_SUPPORTS_HUGE_MEMORY
- static_assert(MaxMemory32PagesValidation * StandardPageSizeBytes <=
+ static_assert(MaxMemory32StandardPagesValidation * StandardPageSizeBytes <=
UINT64_MAX - HugeOffsetGuardLimit);
#endif
uint64_t ea = uint64_t(addr) + uint64_t(access->offset32());
diff --git a/js/src/wasm/WasmConstants.h b/js/src/wasm/WasmConstants.h
@@ -225,12 +225,21 @@ enum class LimitsFlags {
HasMaximum = 0x1,
IsShared = 0x2,
IsI64 = 0x4,
+#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
+ HasCustomPageSize = 0x8,
+#endif
};
enum class LimitsMask {
Table = uint8_t(LimitsFlags::HasMaximum) | uint8_t(LimitsFlags::IsI64),
+#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
+ Memory = uint8_t(LimitsFlags::HasMaximum) | uint8_t(LimitsFlags::IsShared) |
+ uint8_t(LimitsFlags::IsI64) |
+ uint8_t(LimitsFlags::HasCustomPageSize),
+#else
Memory = uint8_t(LimitsFlags::HasMaximum) | uint8_t(LimitsFlags::IsShared) |
uint8_t(LimitsFlags::IsI64),
+#endif
};
enum class DataSegmentKind {
@@ -1130,10 +1139,14 @@ enum class FieldFlags { Mutable = 0x01, AllowedMask = 0x01 };
enum class FieldWideningOp { None, Signed, Unsigned };
-// The WebAssembly spec hard-codes the virtual page size to be 64KiB and
-// requires the size of linear memory to always be a multiple of 64KiB.
-
-enum class PageSize { Standard = 16 };
+// The WebAssembly custom page sizes proposal allows for a virtual page size of
+// either 64KiB, or 1 byte. We call these Standard and Tiny, respectively.
+enum class PageSize {
+#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
+ Tiny = 0,
+#endif
+ Standard = 16
+};
// These limits are agreed upon with other engines for consistency.
@@ -1158,8 +1171,13 @@ static const unsigned MaxLocals = 50000;
static const unsigned MaxParams = 1000;
static const unsigned MaxResults = 1000;
static const unsigned MaxStructFields = 10000;
-static const uint64_t MaxMemory32PagesValidation = uint64_t(1) << 16;
-static const uint64_t MaxMemory64PagesValidation = (uint64_t(1) << 37) - 1;
+#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
+static const uint64_t MaxMemory32TinyPagesValidation = UINT32_MAX;
+static const uint64_t MaxMemory64TinyPagesValidation = (uint64_t(1) << 53) - 1;
+#endif
+static const uint64_t MaxMemory32StandardPagesValidation = uint64_t(1) << 16;
+static const uint64_t MaxMemory64StandardPagesValidation =
+ (uint64_t(1) << 37) - 1;
static const unsigned MaxModuleBytes = 1024 * 1024 * 1024;
static const unsigned MaxFunctionBytes = 7654321;
static const unsigned MaxArrayNewFixedElements = 10000;
diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp
@@ -614,7 +614,7 @@ static int32_t PerformWake(Instance* instance, PtrT byteOffset, int32_t count,
Pages pages = instance->memory(memoryIndex)->volatilePages();
#ifdef JS_64BIT
// Ensure that the memory size is no more than 4GiB.
- MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory32PagesValidation,
+ MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory32StandardPagesValidation,
pages.pageSize()));
#endif
return uint32_t(pages.pageCount());
@@ -631,7 +631,7 @@ static int32_t PerformWake(Instance* instance, PtrT byteOffset, int32_t count,
Pages pages = instance->memory(memoryIndex)->volatilePages();
#ifdef JS_64BIT
- MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory64PagesValidation,
+ MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory64StandardPagesValidation,
pages.pageSize()));
#endif
return pages.pageCount();
diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp
@@ -2177,8 +2177,9 @@ bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) {
RootedObject obj(cx, &args[0].toObject());
Limits limits;
if (!GetLimits(cx, obj, LimitsKind::Memory, &limits) ||
- !CheckLimits(cx, MaxMemoryPagesValidation(limits.addressType),
- LimitsKind::Memory, &limits)) {
+ !CheckLimits(
+ cx, MaxMemoryPagesValidation(limits.addressType, limits.pageSize),
+ LimitsKind::Memory, &limits)) {
return false;
}
diff --git a/js/src/wasm/WasmMemory.cpp b/js/src/wasm/WasmMemory.cpp
@@ -316,7 +316,7 @@ static_assert(MaxInlineMemoryFillLength < MinOffsetGuardLimit, "precondition");
wasm::Pages wasm::MaxMemoryPages(AddressType t, PageSize pageSize) {
#ifdef JS_64BIT
MOZ_ASSERT_IF(t == AddressType::I64, !IsHugeMemoryEnabled(t));
- size_t desired = MaxMemoryPagesValidation(t);
+ size_t desired = MaxMemoryPagesValidation(t, pageSize);
size_t actual =
ArrayBufferObject::ByteLengthLimit / PageSizeInBytes(pageSize);
return wasm::Pages::fromPageCount(std::min(desired, actual), pageSize);
diff --git a/js/src/wasm/WasmMemory.h b/js/src/wasm/WasmMemory.h
@@ -63,7 +63,7 @@ static_assert(StandardPageSizeBytes == 64 * 1024);
// By spec, see
// https://github.com/WebAssembly/spec/issues/1895#issuecomment-2895078022
-static_assert((StandardPageSizeBytes * MaxMemory64PagesValidation) <=
+static_assert((StandardPageSizeBytes * MaxMemory64StandardPagesValidation) <=
(uint64_t(1) << 53) - 1);
// Pages is a typed unit representing a multiple of the page size, which
@@ -166,11 +166,11 @@ struct Pages {
constexpr auto operator<=>(const Pages& other) const {
MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_);
return pageCount_ <=> other.pageCount_;
- };
+ }
constexpr auto operator==(const Pages& other) const {
MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_);
return pageCount_ == other.pageCount_;
- };
+ }
};
// The largest number of pages the application can request.
@@ -184,9 +184,18 @@ static inline size_t MaxMemoryBytes(AddressType t, PageSize pageSize) {
// A value representing the largest valid value for boundsCheckLimit.
extern size_t MaxMemoryBoundsCheckLimit(AddressType t, PageSize pageSize);
-static inline uint64_t MaxMemoryPagesValidation(AddressType addressType) {
- return addressType == AddressType::I32 ? MaxMemory32PagesValidation
- : MaxMemory64PagesValidation;
+static inline uint64_t MaxMemoryPagesValidation(AddressType addressType,
+ PageSize pageSize) {
+#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
+ if (pageSize == PageSize::Tiny) {
+ return addressType == AddressType::I32 ? MaxMemory32TinyPagesValidation
+ : MaxMemory64TinyPagesValidation;
+ }
+#endif
+
+ MOZ_ASSERT(pageSize == PageSize::Standard);
+ return addressType == AddressType::I32 ? MaxMemory32StandardPagesValidation
+ : MaxMemory64StandardPagesValidation;
}
static inline uint64_t MaxTableElemsValidation(AddressType addressType) {
diff --git a/js/src/wasm/WasmModuleTypes.h b/js/src/wasm/WasmModuleTypes.h
@@ -855,7 +855,8 @@ struct MemoryDesc {
// See static_assert after MemoryDesc for why this is safe for memory32.
MOZ_ASSERT_IF(addressType() == AddressType::I64,
limits.initial <= UINT64_MAX / PageSizeInBytes(pageSize()));
- return initialPages().byteLength();
+ return addressType() == AddressType::I64 ? initialPages().byteLength64()
+ : initialPages().byteLength();
}
MemoryDesc() = default;
@@ -869,7 +870,11 @@ using MemoryDescVector = Vector<MemoryDesc, 1, SystemAllocPolicy>;
// We never need to worry about overflow with a Memory32 field when
// using a uint64_t.
-static_assert(MaxMemory32PagesValidation <= UINT64_MAX / StandardPageSizeBytes);
+static_assert(MaxMemory32StandardPagesValidation <=
+ UINT64_MAX / StandardPageSizeBytes);
+#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
+static_assert(MaxMemory32TinyPagesValidation <= UINT64_MAX);
+#endif
struct TableDesc {
Limits limits;
diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp
@@ -2857,6 +2857,20 @@ static bool DecodeLimits(Decoder& d, LimitsKind kind, Limits* limits) {
if (kind == LimitsKind::Memory) {
limits->pageSize = PageSize::Standard;
+#ifdef ENABLE_WASM_CUSTOM_PAGE_SIZES
+ if (flags & uint8_t(LimitsFlags::HasCustomPageSize)) {
+ uint32_t customPageSize;
+ if (!d.readVarU32(&customPageSize)) {
+ return d.fail("failed to decode custom page size");
+ }
+
+ if (customPageSize == static_cast<uint32_t>(PageSize::Tiny)) {
+ limits->pageSize = PageSize::Tiny;
+ } else if (customPageSize != static_cast<uint32_t>(PageSize::Standard)) {
+ return d.fail("bad custom page size");
+ }
+ }
+#endif
}
return true;
@@ -2962,7 +2976,8 @@ static bool DecodeMemoryTypeAndLimits(Decoder& d, CodeMetadata* codeMeta,
return false;
}
- uint64_t maxField = MaxMemoryPagesValidation(limits.addressType);
+ uint64_t maxField =
+ MaxMemoryPagesValidation(limits.addressType, limits.pageSize);
if (limits.initial > maxField) {
return d.fail("initial memory size too big");