tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 5fca0dd3b2149782d64aaacd4e8d4f83fa556341
parent e5f94075691d21bf86086589e65fb7b8e6ac7c0d
Author: Andy Wingo <wingo@igalia.com>
Date:   Mon,  8 Dec 2025 16:02:51 +0000

Bug 1977854 - Add PageSize to wasm::Pages.  r=rhunt,bvisness

No more public constructors for wasm::Pages; instead there are static
methods.  Leads to lots of PageSize::Standard everywhere; with custom
page sizes, these sizes will come from the memory descriptor.

Differential Revision: https://phabricator.services.mozilla.com/D257724

Diffstat:
Mjs/src/builtin/TestingFunctions.cpp | 16++++++++++++----
Mjs/src/vm/ArrayBufferObject.cpp | 92+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mjs/src/vm/ArrayBufferObject.h | 16+++++++++++-----
Mjs/src/vm/SharedArrayObject.cpp | 14++++++++++----
Mjs/src/vm/SharedArrayObject.h | 6+++---
Mjs/src/wasm/AsmJS.cpp | 2+-
Mjs/src/wasm/WasmBCMemory.cpp | 4++--
Mjs/src/wasm/WasmDump.cpp | 8++++++--
Mjs/src/wasm/WasmInstance.cpp | 10++++++----
Mjs/src/wasm/WasmIonCompile.cpp | 5++++-
Mjs/src/wasm/WasmJS.cpp | 24++++++++++++++----------
Mjs/src/wasm/WasmMemory.cpp | 32+++++++++++++++++---------------
Mjs/src/wasm/WasmMemory.h | 86++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mjs/src/wasm/WasmModule.cpp | 6++++--
Mjs/src/wasm/WasmModuleTypes.h | 14+++++++++-----
15 files changed, 217 insertions(+), 118 deletions(-)

diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp @@ -974,14 +974,22 @@ static bool WasmMaxMemoryPages(JSContext* cx, unsigned argc, Value* vp) { if (!ls) { return false; } + wasm::PageSize pageSize = wasm::PageSize::Standard; + if (argc > 1 && args.get(1).isInt32()) { + uint32_t pageSizeBytes = args.get(1).toInt32(); + if (pageSizeBytes != PageSizeInBytes(wasm::PageSize::Standard)) { + JS_ReportErrorASCII(cx, "bad page size"); + return false; + } + } if (StringEqualsLiteral(ls, "i32")) { - args.rval().setInt32( - int32_t(wasm::MaxMemoryPages(wasm::AddressType::I32).value())); + wasm::Pages pages = wasm::MaxMemoryPages(wasm::AddressType::I32, pageSize); + args.rval().setInt32(pages.pageCount()); return true; } if (StringEqualsLiteral(ls, "i64")) { - args.rval().setInt32( - int32_t(wasm::MaxMemoryPages(wasm::AddressType::I64).value())); + wasm::Pages pages = wasm::MaxMemoryPages(wasm::AddressType::I64, pageSize); + args.rval().setNumber(pages.pageCount()); return true; } JS_ReportErrorASCII(cx, "bad address type"); diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp @@ -817,7 +817,8 @@ bool ArrayBufferObject::resizeImpl(JSContext* cx, const CallArgs& args) { return false; } - Pages newPages = Pages::fromByteLengthExact(newByteLength); + Pages newPages = + Pages::fromByteLengthExact(newByteLength, wasm::PageSize::Standard); MOZ_RELEASE_ASSERT(WasmArrayBufferSourceMaxPages(obj).isSome()); Rooted<ArrayBufferObject*> res( cx, @@ -1602,10 +1603,15 @@ void WasmArrayRawBuffer::discard(size_t byteOffset, size_t byteLen) { /* static */ WasmArrayRawBuffer* WasmArrayRawBuffer::AllocateWasm( - AddressType addressType, Pages initialPages, Pages clampedMaxPages, - const Maybe<Pages>& sourceMaxPages, const Maybe<size_t>& mapped) { + AddressType addressType, wasm::PageSize pageSize, Pages initialPages, + Pages clampedMaxPages, const Maybe<Pages>& sourceMaxPages, + const Maybe<size_t>& mapped) { // Prior code has asserted that initial pages is within our implementation // limits (wasm::MaxMemoryPages) and we can assume it is a valid size_t. + MOZ_RELEASE_ASSERT(initialPages.pageSize() == pageSize); + MOZ_RELEASE_ASSERT(clampedMaxPages.pageSize() == pageSize); + MOZ_RELEASE_ASSERT(!sourceMaxPages.isSome() || + (pageSize == sourceMaxPages->pageSize())); MOZ_ASSERT(initialPages.hasByteLength()); size_t numBytes = initialPages.byteLength(); @@ -1637,8 +1643,9 @@ WasmArrayRawBuffer* WasmArrayRawBuffer::AllocateWasm( uint8_t* base = reinterpret_cast<uint8_t*>(data) + gc::SystemPageSize(); uint8_t* header = base - sizeof(WasmArrayRawBuffer); - auto rawBuf = new (header) WasmArrayRawBuffer( - addressType, base, clampedMaxPages, sourceMaxPages, mappedSize, numBytes); + auto rawBuf = new (header) + WasmArrayRawBuffer(addressType, pageSize, base, clampedMaxPages, + sourceMaxPages, mappedSize, numBytes); return rawBuf; } @@ -1667,6 +1674,7 @@ template <typename ObjT, typename RawbufT> static ArrayBufferObjectMaybeShared* CreateSpecificWasmBuffer( JSContext* cx, const wasm::MemoryDesc& memory) { bool useHugeMemory = wasm::IsHugeMemoryEnabled(memory.addressType()); + wasm::PageSize pageSize = memory.pageSize(); Pages initialPages = memory.initialPages(); Maybe<Pages> sourceMaxPages = memory.maximumPages(); Pages clampedMaxPages = wasm::ClampedMaxPages( @@ -1681,9 +1689,9 @@ static ArrayBufferObjectMaybeShared* CreateSpecificWasmBuffer( } #endif - RawbufT* buffer = - RawbufT::AllocateWasm(memory.limits.addressType, initialPages, - clampedMaxPages, sourceMaxPages, mappedSize); + RawbufT* buffer = RawbufT::AllocateWasm( + memory.limits.addressType, memory.pageSize(), initialPages, + clampedMaxPages, sourceMaxPages, mappedSize); if (!buffer) { if (useHugeMemory) { WarnNumberASCII(cx, JSMSG_WASM_HUGE_MEMORY_FAILED); @@ -1698,24 +1706,33 @@ static ArrayBufferObjectMaybeShared* CreateSpecificWasmBuffer( // If we fail, and have a sourceMaxPages, try to reserve the biggest // chunk in the range [initialPages, clampedMaxPages) using log backoff. if (!sourceMaxPages) { - wasm::Log(cx, "new Memory({initial=%" PRIu64 " pages}) failed", - initialPages.value()); + wasm::Log(cx, + "new Memory({initial=%" PRIu64 + " pages, " + "pageSize=%" PRIu32 " bytes}) failed", + initialPages.pageCount(), + wasm::PageSizeInBytes(initialPages.pageSize())); ReportOutOfMemory(cx); return nullptr; } - uint64_t cur = clampedMaxPages.value() / 2; - for (; Pages(cur) > initialPages; cur /= 2) { - buffer = RawbufT::AllocateWasm(memory.limits.addressType, initialPages, - Pages(cur), sourceMaxPages, mappedSize); + uint64_t cur = clampedMaxPages.pageCount() / 2; + for (; cur > initialPages.pageCount(); cur /= 2) { + buffer = RawbufT::AllocateWasm( + memory.limits.addressType, pageSize, initialPages, + Pages::fromPageCount(cur, pageSize), sourceMaxPages, mappedSize); if (buffer) { break; } } if (!buffer) { - wasm::Log(cx, "new Memory({initial=%" PRIu64 " pages}) failed", - initialPages.value()); + wasm::Log(cx, + "new Memory({initial=%" PRIu64 + " pages, " + "pageSize=%" PRIu32 " bytes}) failed", + initialPages.pageCount(), + wasm::PageSizeInBytes(initialPages.pageSize())); ReportOutOfMemory(cx); return nullptr; } @@ -1750,19 +1767,25 @@ static ArrayBufferObjectMaybeShared* CreateSpecificWasmBuffer( if (useHugeMemory) { wasm::Log(cx, "new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64 - " pages}) succeeded", - initialPages.value(), sourceMaxPages->value()); + " pages, pageSize:%" PRIu32 " bytes}) succeeded", + initialPages.pageCount(), sourceMaxPages->pageCount(), + wasm::PageSizeInBytes(initialPages.pageSize())); } else { wasm::Log(cx, "new Memory({initial:%" PRIu64 " pages, maximum:%" PRIu64 - " pages}) succeeded " + " pages, pageSize:%" PRIu32 + " bytes}) succeeded " "with internal maximum of %" PRIu64 " pages", - initialPages.value(), sourceMaxPages->value(), - object->wasmClampedMaxPages().value()); + initialPages.pageCount(), sourceMaxPages->pageCount(), + wasm::PageSizeInBytes(initialPages.pageSize()), + object->wasmClampedMaxPages().pageCount()); } } else { - wasm::Log(cx, "new Memory({initial:%" PRIu64 " pages}) succeeded", - initialPages.value()); + wasm::Log(cx, + "new Memory({initial:%" PRIu64 " pages, pageSize:%" PRIu32 + " bytes}) succeeded", + initialPages.pageCount(), + wasm::PageSizeInBytes(initialPages.pageSize())); } return object; @@ -1770,8 +1793,9 @@ static ArrayBufferObjectMaybeShared* CreateSpecificWasmBuffer( ArrayBufferObjectMaybeShared* js::CreateWasmBuffer( JSContext* cx, const wasm::MemoryDesc& memory) { - MOZ_RELEASE_ASSERT(memory.initialPages() <= - wasm::MaxMemoryPages(memory.addressType())); + MOZ_RELEASE_ASSERT( + memory.initialPages() <= + wasm::MaxMemoryPages(memory.addressType(), memory.pageSize())); MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers); if (memory.isShared()) { @@ -1960,7 +1984,7 @@ Pages ArrayBufferObject::wasmPages() const { return contents().wasmBuffer()->pages(); } MOZ_ASSERT(isPreparedForAsmJS()); - return Pages::fromByteLengthExact(byteLength()); + return Pages::fromByteLengthExact(byteLength(), wasm::PageSize::Standard); } Pages ArrayBufferObject::wasmClampedMaxPages() const { @@ -1968,7 +1992,7 @@ Pages ArrayBufferObject::wasmClampedMaxPages() const { return contents().wasmBuffer()->clampedMaxPages(); } MOZ_ASSERT(isPreparedForAsmJS()); - return Pages::fromByteLengthExact(byteLength()); + return Pages::fromByteLengthExact(byteLength(), wasm::PageSize::Standard); } Maybe<Pages> ArrayBufferObject::wasmSourceMaxPages() const { @@ -1976,7 +2000,8 @@ Maybe<Pages> ArrayBufferObject::wasmSourceMaxPages() const { return contents().wasmBuffer()->sourceMaxPages(); } MOZ_ASSERT(isPreparedForAsmJS()); - return Some<Pages>(Pages::fromByteLengthExact(byteLength())); + return Some<Pages>( + Pages::fromByteLengthExact(byteLength(), wasm::PageSize::Standard)); } size_t js::WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf) { @@ -2042,7 +2067,7 @@ ArrayBufferObject* ArrayBufferObject::wasmGrowToPagesInPlace( if (newPages > oldBuf->wasmClampedMaxPages()) { return nullptr; } - MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t) && + MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t, newPages.pageSize()) && newPages.byteLength() <= ArrayBufferObject::ByteLengthLimit); if (oldBuf->is<ResizableArrayBufferObject>()) { @@ -2121,7 +2146,7 @@ ArrayBufferObject* ArrayBufferObject::wasmMovingGrowToPages( if (newPages > oldBuf->wasmClampedMaxPages()) { return nullptr; } - MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t) && + MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t, newPages.pageSize()) && newPages.byteLength() <= ArrayBufferObject::ByteLengthLimit); // We have checked against the clamped maximum and so we know we can convert @@ -2140,9 +2165,10 @@ ArrayBufferObject* ArrayBufferObject::wasmMovingGrowToPages( Pages clampedMaxPages = wasm::ClampedMaxPages(t, newPages, Nothing(), /* hugeMemory */ false); - WasmArrayRawBuffer* newRawBuf = - WasmArrayRawBuffer::AllocateWasm(oldBuf->wasmAddressType(), newPages, - clampedMaxPages, Nothing(), Nothing()); + wasm::PageSize pageSize = wasm::PageSize::Standard; + WasmArrayRawBuffer* newRawBuf = WasmArrayRawBuffer::AllocateWasm( + oldBuf->wasmAddressType(), pageSize, newPages, clampedMaxPages, Nothing(), + Nothing()); if (!newRawBuf) { return nullptr; } diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h @@ -987,17 +987,19 @@ class MutableWrappedPtrOperations<InnerViewTable, Wrapper> class WasmArrayRawBuffer { wasm::AddressType addressType_; + wasm::PageSize pageSize_; wasm::Pages clampedMaxPages_; mozilla::Maybe<wasm::Pages> sourceMaxPages_; size_t mappedSize_; // See comment on mappedSize(). size_t length_; protected: - WasmArrayRawBuffer(wasm::AddressType addressType, uint8_t* buffer, - wasm::Pages clampedMaxPages, + WasmArrayRawBuffer(wasm::AddressType addressType, wasm::PageSize pageSize, + uint8_t* buffer, wasm::Pages clampedMaxPages, const mozilla::Maybe<wasm::Pages>& sourceMaxPages, size_t mappedSize, size_t length) : addressType_(addressType), + pageSize_(pageSize), clampedMaxPages_(clampedMaxPages), sourceMaxPages_(sourceMaxPages), mappedSize_(mappedSize), @@ -1005,12 +1007,15 @@ class WasmArrayRawBuffer { // Assert that this WasmArrayRawBuffer was allocated in the correct place // relative to its data. MOZ_ASSERT(buffer == dataPointer()); + MOZ_ASSERT(pageSize == clampedMaxPages.pageSize()); + MOZ_ASSERT_IF(sourceMaxPages.isSome(), + (pageSize == sourceMaxPages->pageSize())); } public: static WasmArrayRawBuffer* AllocateWasm( - wasm::AddressType addressType, wasm::Pages initialPages, - wasm::Pages clampedMaxPages, + wasm::AddressType addressType, wasm::PageSize pageSize, + wasm::Pages initialPages, wasm::Pages clampedMaxPages, const mozilla::Maybe<wasm::Pages>& sourceMaxPages, const mozilla::Maybe<size_t>& mappedSize); static void Release(void* mem); @@ -1031,6 +1036,7 @@ class WasmArrayRawBuffer { } wasm::AddressType addressType() const { return addressType_; } + wasm::PageSize pageSize() const { return pageSize_; } uint8_t* basePointer() { return dataPointer() - gc::SystemPageSize(); } @@ -1055,7 +1061,7 @@ class WasmArrayRawBuffer { size_t byteLength() const { return length_; } wasm::Pages pages() const { - return wasm::Pages::fromByteLengthExact(length_); + return wasm::Pages::fromByteLengthExact(length_, wasm::PageSize::Standard); } /* diff --git a/js/src/vm/SharedArrayObject.cpp b/js/src/vm/SharedArrayObject.cpp @@ -96,12 +96,16 @@ SharedArrayRawBuffer* SharedArrayRawBuffer::Allocate(bool isGrowable, } WasmSharedArrayRawBuffer* WasmSharedArrayRawBuffer::AllocateWasm( - wasm::AddressType addressType, Pages initialPages, + wasm::AddressType addressType, wasm::PageSize pageSize, Pages initialPages, wasm::Pages clampedMaxPages, const mozilla::Maybe<wasm::Pages>& sourceMaxPages, const mozilla::Maybe<size_t>& mappedSize) { // Prior code has asserted that initial pages is within our implementation // limits (wasm::MaxMemoryPages()) and we can assume it is a valid size_t. + MOZ_RELEASE_ASSERT(initialPages.pageSize() == pageSize); + MOZ_RELEASE_ASSERT(clampedMaxPages.pageSize() == pageSize); + MOZ_RELEASE_ASSERT(!sourceMaxPages.isSome() || + (pageSize == sourceMaxPages->pageSize())); MOZ_ASSERT(initialPages.hasByteLength()); size_t length = initialPages.byteLength(); @@ -130,7 +134,8 @@ WasmSharedArrayRawBuffer* WasmSharedArrayRawBuffer::AllocateWasm( uint8_t* base = buffer - sizeof(WasmSharedArrayRawBuffer); return new (base) WasmSharedArrayRawBuffer( buffer, length, addressType, clampedMaxPages, - sourceMaxPages.valueOr(Pages(0)), computedMappedSize); + sourceMaxPages.valueOr(Pages::fromPageCount(0, pageSize)), + computedMappedSize); } bool WasmSharedArrayRawBuffer::wasmGrowToPagesInPlace(const Lock&, @@ -142,7 +147,7 @@ bool WasmSharedArrayRawBuffer::wasmGrowToPagesInPlace(const Lock&, if (newPages > clampedMaxPages_) { return false; } - MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t) && + MOZ_ASSERT(newPages <= wasm::MaxMemoryPages(t, newPages.pageSize()) && newPages.byteLength() <= ArrayBufferObject::ByteLengthLimit); // We have checked against the clamped maximum and so we know we can convert @@ -426,7 +431,8 @@ bool SharedArrayBufferObject::growImpl(JSContext* cx, const CallArgs& args) { return false; } - Pages newPages = Pages::fromByteLengthExact(newByteLength); + Pages newPages = + Pages::fromByteLengthExact(newByteLength, wasm::PageSize::Standard); return buffer->rawWasmBufferObject()->wasmGrowToPagesInPlace( *lock, buffer->wasmAddressType(), newPages); } diff --git a/js/src/vm/SharedArrayObject.h b/js/src/vm/SharedArrayObject.h @@ -167,8 +167,8 @@ class WasmSharedArrayRawBuffer : public SharedArrayRawBuffer { }; static WasmSharedArrayRawBuffer* AllocateWasm( - wasm::AddressType addressType, wasm::Pages initialPages, - wasm::Pages clampedMaxPages, + wasm::AddressType addressType, wasm::PageSize pageSize, + wasm::Pages initialPages, wasm::Pages clampedMaxPages, const mozilla::Maybe<wasm::Pages>& sourceMaxPages, const mozilla::Maybe<size_t>& mappedSize); @@ -185,7 +185,7 @@ class WasmSharedArrayRawBuffer : public SharedArrayRawBuffer { wasm::AddressType wasmAddressType() const { return addressType_; } wasm::Pages volatileWasmPages() const { - return wasm::Pages::fromByteLengthExact(length_); + return wasm::Pages::fromByteLengthExact(length_, wasm::PageSize::Standard); } wasm::Pages wasmClampedMaxPages() const { return clampedMaxPages_; } diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp @@ -7428,7 +7428,7 @@ bool js::IsValidAsmJSHeapLength(size_t length) { } // The heap length is limited by what a wasm memory32 can handle. - if (length > MaxMemoryBytes(AddressType::I32)) { + if (length > MaxMemoryBytes(AddressType::I32, wasm::PageSize::Standard)) { return false; } diff --git a/js/src/wasm/WasmBCMemory.cpp b/js/src/wasm/WasmBCMemory.cpp @@ -422,8 +422,8 @@ void BaseCompiler::prepareMemoryAccess(MemoryAccessDesc* access, // bits are in the bound. if (!codeMeta_.memories[access->memoryIndex()] .boundsCheckLimitIsAlways32Bits() && - MaxMemoryBytes( - codeMeta_.memories[access->memoryIndex()].addressType()) >= + MaxMemoryBytes(codeMeta_.memories[access->memoryIndex()].addressType(), + codeMeta_.memories[access->memoryIndex()].pageSize()) >= 0x100000000) { boundsCheck4GBOrLargerAccess(access->memoryIndex(), instance, ptr, &ok); } else { diff --git a/js/src/wasm/WasmDump.cpp b/js/src/wasm/WasmDump.cpp @@ -744,9 +744,13 @@ void wasm::DumpMemoryDesc(const MemoryDesc& memDesc, StructuredPrinter& out, if (memDesc.addressType() == AddressType::I64) { out.printf("i64 "); } - out.printf("%" PRIu64, memDesc.initialPages().value()); + out.printf("%" PRIu64, memDesc.initialPages().pageCount()); if (memDesc.maximumPages().isSome()) { - out.printf(" %" PRIu64, memDesc.maximumPages().value().value()); + out.printf(" %" PRIu64, memDesc.maximumPages().value().pageCount()); + } + if (memDesc.initialPages().pageSize() != PageSize::Standard) { + out.printf("(pagesize %" PRIu32 ")", + static_cast<uint32_t>(memDesc.initialPages().pageSize())); } out.printf(")"); } diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp @@ -614,9 +614,10 @@ 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(MaxMemory32PagesValidation)); + MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory32PagesValidation, + pages.pageSize())); #endif - return uint32_t(pages.value()); + return uint32_t(pages.pageCount()); } /* static */ uint64_t Instance::memorySize_m64(Instance* instance, @@ -630,9 +631,10 @@ static int32_t PerformWake(Instance* instance, PtrT byteOffset, int32_t count, Pages pages = instance->memory(memoryIndex)->volatilePages(); #ifdef JS_64BIT - MOZ_ASSERT(pages <= Pages(MaxMemory64PagesValidation)); + MOZ_ASSERT(pages <= Pages::fromPageCount(MaxMemory64PagesValidation, + pages.pageSize())); #endif - return pages.value(); + return pages.pageCount(); } template <typename PointerT, typename CopyFuncT, typename IndexT> diff --git a/js/src/wasm/WasmIonCompile.cpp b/js/src/wasm/WasmIonCompile.cpp @@ -1579,6 +1579,8 @@ class FunctionCompiler { } MWasmLoadInstance* needBoundsCheck(uint32_t memoryIndex) { + MOZ_RELEASE_ASSERT(codeMeta().memories[memoryIndex].pageSize() == + PageSize::Standard); #ifdef JS_64BIT // For 32-bit base pointers: // @@ -1592,7 +1594,8 @@ class FunctionCompiler { bool mem32LimitIs64Bits = isMem32(memoryIndex) && !codeMeta().memories[memoryIndex].boundsCheckLimitIsAlways32Bits() && - MaxMemoryBytes(codeMeta().memories[memoryIndex].addressType()) >= + MaxMemoryBytes(codeMeta().memories[memoryIndex].addressType(), + codeMeta().memories[memoryIndex].pageSize()) >= 0x100000000; #else // On 32-bit platforms we have no more than 2GB memory and the limit for a diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp @@ -996,7 +996,7 @@ static JSObject* MemoryTypeToObject(JSContext* cx, bool shared, if (maxPages) { RootedId maximumId(cx, NameToId(cx->names().maximum)); RootedValue maximumValue(cx); - if (!CreateAddressValue(cx, maxPages.value().value(), addressType, + if (!CreateAddressValue(cx, maxPages.value().pageCount(), addressType, &maximumValue)) { ReportOutOfMemory(cx); return nullptr; @@ -1009,7 +1009,8 @@ static JSObject* MemoryTypeToObject(JSContext* cx, bool shared, RootedId minimumId(cx, NameToId(cx->names().minimum)); RootedValue minimumValue(cx); - if (!CreateAddressValue(cx, minPages.value(), addressType, &minimumValue)) { + if (!CreateAddressValue(cx, minPages.pageCount(), addressType, + &minimumValue)) { ReportOutOfMemory(cx); return nullptr; } @@ -2177,7 +2178,8 @@ bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) { return false; } - if (Pages(limits.initial) > MaxMemoryPages(limits.addressType)) { + if (Pages::fromPageCount(limits.initial, PageSize::Standard) > + MaxMemoryPages(limits.addressType, PageSize::Standard)) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_MEM_IMP_LIMIT); return false; @@ -2601,7 +2603,8 @@ size_t WasmMemoryObject::boundsCheckLimit() const { MOZ_ASSERT(mappedSize % wasm::StandardPageSizeBytes == 0); MOZ_ASSERT(mappedSize >= wasm::GuardSize); size_t limit = mappedSize - wasm::GuardSize; - MOZ_ASSERT(limit <= MaxMemoryBoundsCheckLimit(addressType())); + MOZ_ASSERT(limit <= MaxMemoryBoundsCheckLimit(addressType(), + wasm::PageSize::Standard)); return limit; } @@ -2632,7 +2635,7 @@ uint64_t WasmMemoryObject::growShared(Handle<WasmMemoryObject*> memory, Pages oldNumPages = rawBuf->volatileWasmPages(); Pages newPages = oldNumPages; - if (!newPages.checkedIncrement(Pages(delta))) { + if (!newPages.checkedIncrement(delta)) { return uint64_t(int64_t(-1)); } @@ -2642,7 +2645,7 @@ uint64_t WasmMemoryObject::growShared(Handle<WasmMemoryObject*> memory, // New buffer objects will be created lazily in all agents (including in // this agent) by bufferGetterImpl, above, so no more work to do here. - return oldNumPages.value(); + return oldNumPages.pageCount(); } /* static */ @@ -2658,13 +2661,14 @@ uint64_t WasmMemoryObject::grow(Handle<WasmMemoryObject*> memory, #if !defined(JS_64BIT) // TODO (large ArrayBuffer): See more information at the definition of // MaxMemoryBytes(). - MOZ_ASSERT(MaxMemoryBytes(memory->addressType()) <= UINT32_MAX, - "Avoid 32-bit overflows"); + MOZ_ASSERT( + MaxMemoryBytes(memory->addressType(), PageSize::Standard) <= UINT32_MAX, + "Avoid 32-bit overflows"); #endif Pages oldNumPages = oldBuf->wasmPages(); Pages newPages = oldNumPages; - if (!newPages.checkedIncrement(Pages(delta))) { + if (!newPages.checkedIncrement(delta)) { return uint64_t(int64_t(-1)); } @@ -2692,7 +2696,7 @@ uint64_t WasmMemoryObject::grow(Handle<WasmMemoryObject*> memory, } } - return oldNumPages.value(); + return oldNumPages.pageCount(); } /* static */ diff --git a/js/src/wasm/WasmMemory.cpp b/js/src/wasm/WasmMemory.cpp @@ -313,36 +313,39 @@ static const size_t MinOffsetGuardLimit = OffsetGuardLimit; static_assert(MaxInlineMemoryCopyLength < MinOffsetGuardLimit, "precondition"); static_assert(MaxInlineMemoryFillLength < MinOffsetGuardLimit, "precondition"); -wasm::Pages wasm::MaxMemoryPages(AddressType t) { +wasm::Pages wasm::MaxMemoryPages(AddressType t, PageSize pageSize) { #ifdef JS_64BIT MOZ_ASSERT_IF(t == AddressType::I64, !IsHugeMemoryEnabled(t)); size_t desired = MaxMemoryPagesValidation(t); - constexpr size_t actual = - ArrayBufferObject::ByteLengthLimit / StandardPageSizeBytes; - return wasm::Pages(std::min(desired, actual)); + size_t actual = + ArrayBufferObject::ByteLengthLimit / PageSizeInBytes(pageSize); + return wasm::Pages::fromPageCount(std::min(desired, actual), pageSize); #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. - static_assert(ArrayBufferObject::ByteLengthLimit >= - INT32_MAX / StandardPageSizeBytes); - return wasm::Pages(INT32_MAX / StandardPageSizeBytes); + MOZ_ASSERT(ArrayBufferObject::ByteLengthLimit >= + INT32_MAX / PageSizeInBytes(pageSize)); + return wasm::Pages::fromPageCount(INT32_MAX / PageSizeInBytes(pageSize), + pageSize); #endif } -size_t wasm::MaxMemoryBoundsCheckLimit(AddressType t) { - return MaxMemoryBytes(t); +size_t wasm::MaxMemoryBoundsCheckLimit(AddressType t, PageSize pageSize) { + return MaxMemoryBytes(t, pageSize); } Pages wasm::ClampedMaxPages(AddressType t, Pages initialPages, const mozilla::Maybe<Pages>& sourceMaxPages, bool useHugeMemory) { - Pages clampedMaxPages; + PageSize pageSize = initialPages.pageSize(); + Pages clampedMaxPages = Pages::forPageSize(pageSize); if (sourceMaxPages.isSome()) { // There is a specified maximum, clamp it to the implementation limit of // maximum pages - clampedMaxPages = std::min(*sourceMaxPages, wasm::MaxMemoryPages(t)); + clampedMaxPages = + std::min(*sourceMaxPages, wasm::MaxMemoryPages(t, pageSize)); #ifndef JS_64BIT static_assert(sizeof(uintptr_t) == 4, "assuming not 64 bit implies 32 bit"); @@ -352,8 +355,7 @@ Pages wasm::ClampedMaxPages(AddressType t, Pages initialPages, // "a lot of memory". Maintain the invariant that initialPages <= // clampedMaxPages. static const uint64_t OneGib = 1 << 30; - static const Pages OneGibPages = - Pages(OneGib / wasm::StandardPageSizeBytes); + const Pages OneGibPages = Pages::fromByteLengthExact(OneGib, pageSize); Pages clampedPages = std::max(OneGibPages, initialPages); clampedMaxPages = std::min(clampedPages, clampedMaxPages); @@ -361,13 +363,13 @@ Pages wasm::ClampedMaxPages(AddressType t, Pages initialPages, } else { // There is not a specified maximum, fill it in with the implementation // limit of maximum pages - clampedMaxPages = wasm::MaxMemoryPages(t); + clampedMaxPages = wasm::MaxMemoryPages(t, pageSize); } // Double-check our invariants MOZ_RELEASE_ASSERT(sourceMaxPages.isNothing() || clampedMaxPages <= *sourceMaxPages); - MOZ_RELEASE_ASSERT(clampedMaxPages <= wasm::MaxMemoryPages(t)); + MOZ_RELEASE_ASSERT(clampedMaxPages <= wasm::MaxMemoryPages(t, pageSize)); MOZ_RELEASE_ASSERT(initialPages <= clampedMaxPages); return clampedMaxPages; diff --git a/js/src/wasm/WasmMemory.h b/js/src/wasm/WasmMemory.h @@ -66,13 +66,16 @@ static_assert(StandardPageSizeBytes == 64 * 1024); static_assert((StandardPageSizeBytes * MaxMemory64PagesValidation) <= (uint64_t(1) << 53) - 1); -// Pages is a typed unit representing a multiple of wasm::StandardPageSizeBytes. +// Pages is a typed unit representing a multiple of the page size, which +// defaults to wasm::StandardPageSizeBytes. The page size can be customized +// only if the custom page sizes proposal is enabled. +// // We generally use pages as the unit of length when representing linear memory // lengths so as to avoid overflow when the specified initial or maximum pages // would overflow the native word size. // -// Modules may specify pages up to 2^48 inclusive and so Pages is 64-bit on all -// platforms. +// Modules may specify pages up to 2^48 (or 2^64 - 1 with tiny pages) inclusive +// and so Pages is 64-bit on all platforms. // // We represent byte lengths using the native word size, as it is assumed that // consumers of this API will only need byte lengths once it is time to @@ -83,74 +86,103 @@ struct Pages { private: // Pages are specified by limit fields, which in general may be up to 2^48, // so we must use uint64_t here. - uint64_t value_; + uint64_t pageCount_; + PageSize pageSize_; + + constexpr Pages(uint64_t pageCount, PageSize pageSize) + : pageCount_(pageCount), pageSize_(pageSize) {} public: - constexpr Pages() : value_(0) {} - constexpr explicit Pages(uint64_t value) : value_(value) {} + static constexpr Pages fromPageCount(uint64_t pageCount, PageSize pageSize) { + return Pages(pageCount, pageSize); + } - // Get the wrapped page value. Only use this if you must, prefer to use or - // add new APIs to Page. - uint64_t value() const { return value_; } + static constexpr Pages forPageSize(PageSize pageSize) { + return Pages(0, pageSize); + } + + static constexpr bool byteLengthIsMultipleOfPageSize(size_t byteLength, + PageSize pageSize) { + return byteLength % PageSizeInBytes(pageSize) == 0; + } // Converts from a byte length to pages, assuming that the length is an // exact multiple of the page size. - static Pages fromByteLengthExact(size_t byteLength) { - MOZ_ASSERT(byteLength % StandardPageSizeBytes == 0); - return Pages(byteLength / StandardPageSizeBytes); + static constexpr Pages fromByteLengthExact(size_t byteLength, + PageSize pageSize) { + MOZ_RELEASE_ASSERT(byteLengthIsMultipleOfPageSize(byteLength, pageSize)); + return Pages(byteLength / PageSizeInBytes(pageSize), pageSize); + } + + Pages& operator=(const Pages& other) { + MOZ_RELEASE_ASSERT(other.pageSize_ == pageSize_); + pageCount_ = other.pageCount_; + return *this; } + // Get the wrapped page count and size. Only use this if you must, prefer to + // use or add new APIs to Page. + uint64_t pageCount() const { return pageCount_; } + PageSize pageSize() const { return pageSize_; } + // Return whether the page length may overflow when converted to a byte // length in the native word size. bool hasByteLength() const { - mozilla::CheckedInt<size_t> length(value_); - length *= StandardPageSizeBytes; + mozilla::CheckedInt<size_t> length(pageCount_); + length *= PageSizeInBytes(pageSize_); return length.isValid(); } // Converts from pages to byte length in the native word size. Users must // check for overflow, or be assured else-how that overflow cannot happen. size_t byteLength() const { - mozilla::CheckedInt<size_t> length(value_); - length *= StandardPageSizeBytes; + mozilla::CheckedInt<size_t> length(pageCount_); + length *= PageSizeInBytes(pageSize_); return length.value(); } // Return the byteLength for a 64-bits memory. uint64_t byteLength64() const { - mozilla::CheckedInt<uint64_t> length(value_); - length *= StandardPageSizeBytes; + mozilla::CheckedInt<uint64_t> length(pageCount_); + length *= PageSizeInBytes(pageSize_); return length.value(); } // Increment this pages by delta and return whether the resulting value // did not overflow. If there is no overflow, then this is set to the // resulting value. - bool checkedIncrement(Pages delta) { - mozilla::CheckedInt<uint64_t> newValue = value_; - newValue += delta.value_; + bool checkedIncrement(uint64_t delta) { + mozilla::CheckedInt<uint64_t> newValue = pageCount_; + newValue += delta; if (!newValue.isValid()) { return false; } - value_ = newValue.value(); + pageCount_ = newValue.value(); return true; } // Implement pass-through comparison operators so that Pages can be compared. - constexpr auto operator<=>(const Pages& other) const = default; + 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. -extern Pages MaxMemoryPages(AddressType t); +extern Pages MaxMemoryPages(AddressType t, PageSize pageSize); // The byte value of MaxMemoryPages(t). -static inline size_t MaxMemoryBytes(AddressType t) { - return MaxMemoryPages(t).byteLength(); +static inline size_t MaxMemoryBytes(AddressType t, PageSize pageSize) { + return MaxMemoryPages(t, pageSize).byteLength(); } // A value representing the largest valid value for boundsCheckLimit. -extern size_t MaxMemoryBoundsCheckLimit(AddressType t); +extern size_t MaxMemoryBoundsCheckLimit(AddressType t, PageSize pageSize); static inline uint64_t MaxMemoryPagesValidation(AddressType addressType) { return addressType == AddressType::I32 ? MaxMemory32PagesValidation diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp @@ -509,7 +509,8 @@ bool Module::instantiateMemories( } if (!CheckLimits(cx, desc.initialPages(), desc.maximumPages(), - /* defaultMax */ MaxMemoryPages(desc.addressType()), + /* defaultMax */ + MaxMemoryPages(desc.addressType(), desc.pageSize()), /* actualLength */ memory->volatilePages(), memory->sourceMaxPages(), codeMeta().isAsmJS(), "Memory")) { @@ -522,7 +523,8 @@ bool Module::instantiateMemories( } else { MOZ_ASSERT(!codeMeta().isAsmJS()); - if (desc.initialPages() > MaxMemoryPages(desc.addressType())) { + if (desc.initialPages() > + MaxMemoryPages(desc.addressType(), desc.pageSize())) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_MEM_IMP_LIMIT); return false; diff --git a/js/src/wasm/WasmModuleTypes.h b/js/src/wasm/WasmModuleTypes.h @@ -834,20 +834,24 @@ struct MemoryDesc { AddressType addressType() const { return limits.addressType; } + PageSize pageSize() const { return PageSize::Standard; } + // The initial length of this memory in pages. - Pages initialPages() const { return Pages(limits.initial); } + Pages initialPages() const { + return Pages::fromPageCount(limits.initial, pageSize()); + } // The maximum length of this memory in pages. mozilla::Maybe<Pages> maximumPages() const { - return limits.maximum.map([](uint64_t x) { return Pages(x); }); + return limits.maximum.map( + [&](uint64_t x) { return Pages::fromPageCount(x, pageSize()); }); } - // The initial length of this memory in bytes. uint64_t initialLength() const { // See static_assert after MemoryDesc for why this is safe for memory32. MOZ_ASSERT_IF(addressType() == AddressType::I64, - limits.initial <= UINT64_MAX / StandardPageSizeBytes); - return limits.initial * StandardPageSizeBytes; + limits.initial <= UINT64_MAX / PageSizeInBytes(pageSize())); + return initialPages().byteLength(); } MemoryDesc() = default;