Bug 1676441 - Allow up to 65534 pages + test cases. r=rhunt

The purpose here is to allow wasm memories to use up to 65534 pages of
memory on 64-bit systems, if the underlying ArrayBuffer /
SharedArrayBuffer supports it.  (The remaining two pages remain out of
bounds until we can make the bounds checking limit 64 bits wide, and
some other things.)

As the max value of the heap limit field is 65536, this should unlock
memories above 2GB for all users who do not insist on actually
allocating all 4GB.

The changes here are:

* Parameterize everything properly: Get rid of hardcoded UINT32_MAX
  and INT32_MAX and similar where that is possible.  Some remain;
  comments have been added.
* Add guards for asm.js to remain at heap limit INT32_MAX forever
* Add guard for grow() methods to enforce the limit separately from
  arraybuffer code
* Add test cases and test infrastructure
* Clean up some transitional things that are no longer required, now
  that the underlying arraybuffer code is stable

Some parameters are turned into function calls now, but may become
constants again later.

Differential Revision: https://phabricator.services.mozilla.com/D96602
This commit is contained in:
Lars T Hansen 2021-03-16 06:48:25 +00:00
parent 85aac3e34e
commit 7d83d7c31e
23 changed files with 553 additions and 166 deletions

View File

@ -1467,6 +1467,13 @@ static bool WasmLoadedFromCache(JSContext* cx, unsigned argc, Value* vp) {
return WasmReturnFlag(cx, argc, vp, Flag::Deserialized); return WasmReturnFlag(cx, argc, vp, Flag::Deserialized);
} }
static bool LargeArrayBufferEnabled(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(ArrayBufferObject::maxBufferByteLength() >
ArrayBufferObject::MaxByteLengthForSmallBuffer);
return true;
}
static bool IsLazyFunction(JSContext* cx, unsigned argc, Value* vp) { static bool IsLazyFunction(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp); CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() != 1) { if (args.length() != 1) {
@ -7014,6 +7021,10 @@ gc::ZealModeHelpText),
" Returns a boolean indicating whether a given module was deserialized directly from a\n" " Returns a boolean indicating whether a given module was deserialized directly from a\n"
" cache (as opposed to compiled from bytecode)."), " cache (as opposed to compiled from bytecode)."),
JS_FN_HELP("largeArrayBufferEnabled", LargeArrayBufferEnabled, 0, 0,
"largeArrayBufferEnabled()",
" Returns true if array buffers larger than 2GB can be allocated."),
JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0, JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0,
"isLazyFunction(fun)", "isLazyFunction(fun)",
" True if fun is a lazy JSFunction."), " True if fun is a lazy JSFunction."),

View File

@ -43,6 +43,7 @@
#include "vm/StringType.h" // for NameToId, PropertyName, JSAtom #include "vm/StringType.h" // for NameToId, PropertyName, JSAtom
#include "wasm/WasmDebug.h" // for ExprLoc, DebugState #include "wasm/WasmDebug.h" // for ExprLoc, DebugState
#include "wasm/WasmInstance.h" // for Instance #include "wasm/WasmInstance.h" // for Instance
#include "wasm/WasmJS.h" // for WasmInstanceObject
#include "wasm/WasmTypes.h" // for Bytes #include "wasm/WasmTypes.h" // for Bytes
#include "vm/BytecodeUtil-inl.h" // for BytecodeRangeWithPosition #include "vm/BytecodeUtil-inl.h" // for BytecodeRangeWithPosition

View File

@ -12,6 +12,7 @@
#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf #include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf
#include "mozilla/Range.h" // mozilla::Range #include "mozilla/Range.h" // mozilla::Range
#include "mozilla/Span.h" // mozilla::Span #include "mozilla/Span.h" // mozilla::Span
#include "mozilla/Unused.h" // mozilla::Unused
#include "mozilla/Variant.h" // mozilla::Variant #include "mozilla/Variant.h" // mozilla::Variant
#include <stddef.h> // size_t #include <stddef.h> // size_t

View File

@ -421,7 +421,7 @@ static int testWasmFuzz(const uint8_t* buf, size_t size) {
if (propObj->is<WasmMemoryObject>()) { if (propObj->is<WasmMemoryObject>()) {
Rooted<WasmMemoryObject*> memory(gCx, Rooted<WasmMemoryObject*> memory(gCx,
&propObj->as<WasmMemoryObject>()); &propObj->as<WasmMemoryObject>());
size_t byteLen = memory->volatileMemoryLength32(); size_t byteLen = memory->volatileMemoryLength().getWasmUint32();
if (byteLen) { if (byteLen) {
// Read the bounds of the buffer to ensure it is valid. // Read the bounds of the buffer to ensure it is valid.
// AddressSanitizer would detect any out-of-bounds here. // AddressSanitizer would detect any out-of-bounds here.

View File

@ -3,6 +3,13 @@ if (!wasmIsSupported())
load(libdir + "asserts.js"); load(libdir + "asserts.js");
// 65534 allocated pages is our current upper limit for reasons having to do
// with avoiding arithmetic overflow. Eventually this will become 65536.
var PageSizeInBytes = 65536;
var MaxBytesIn32BitMemory = largeArrayBufferEnabled() ? 65534*PageSizeInBytes : 0x7FFF_FFFF;
var MaxPagesIn32BitMemory = Math.floor(MaxBytesIn32BitMemory / PageSizeInBytes);
// "options" is an extension to facilitate the SIMD wormhole // "options" is an extension to facilitate the SIMD wormhole
function wasmEvalText(str, imports, options) { function wasmEvalText(str, imports, options) {

View File

@ -0,0 +1,9 @@
// |jit-test| skip-if: !largeArrayBufferEnabled(); --large-arraybuffers
load(libdir + "asm.js");
// This is a validly structured length but it is too large for our asm.js
// implementation: for the sake of simplicity, we draw the line at 7fff_ffff (ie
// really at 7f00_0000 given constraints on the format of the address).
var buf = new ArrayBuffer(0x8000_0000);
assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[' + BUF_MIN + '] = -1 } return f'), this, null, buf);

View File

@ -0,0 +1,311 @@
// |jit-test| skip-if: !largeArrayBufferEnabled(); --large-arraybuffers
var pagesz = PageSizeInBytes;
var pages_limit = MaxPagesIn32BitMemory;
// 40000 is well above 2GB but not anything that might run into boundary
// conditions near 4GB.
var pages_vanilla = 40000;
for ( let [pages,maxpages] of [[pages_vanilla, pages_vanilla+100],
[pages_limit, pages_limit]] ) {
assertEq(pages == maxpages || maxpages - pages >= 3, true)
let ins = wasmEvalText(`
(module
(memory (export "mem") ${pages} ${maxpages})
(data (i32.const ${(pages-5)*pagesz}) "yabbadabbado")
(data $flintstone passive "yabbadabbado")
(func (export "get_constaddr") (result i32)
(i32.load (i32.const ${pages*pagesz-4})))
(func (export "get_varaddr") (param $p i32) (result i32)
(i32.load (local.get $p)))
(func (export "set_constaddr") (param $v i32)
(i32.store (i32.const ${pages*pagesz-8}) (local.get $v)))
(func (export "set_varaddr") (param $p i32) (param $v i32)
(i32.store (local.get $p) (local.get $v)))
(func (export "get_constaddr_large_offset") (result i32)
(i32.load offset=${(pages-100)*pagesz-4} (i32.const ${pagesz*100})))
(func (export "get_varaddr_large_offset") (param $p i32) (result i32)
(i32.load offset=${(pages-100)*pagesz-4} (local.get $p)))
(func (export "get_constaddr_small_offset") (result i32)
(i32.load offset=${pagesz-4} (i32.const ${(pages-1)*pagesz})))
(func (export "get_varaddr_small_offset") (param $p i32) (result i32)
(i32.load offset=${pagesz-4} (local.get $p)))
(func (export "set_constaddr_large_offset") (param $v i32)
(i32.store offset=${(pages-100)*pagesz-16} (i32.const ${pagesz*100}) (local.get $v)))
(func (export "set_varaddr_large_offset") (param $p i32) (param $v i32)
(i32.store offset=${(pages-100)*pagesz-20} (local.get $p) (local.get $v)))
(func (export "set_constaddr_small_offset") (param $v i32)
(i32.store offset=${pagesz-24} (i32.const ${(pages-1)*pagesz}) (local.get $v)))
(func (export "set_varaddr_small_offset") (param $p i32) (param $v i32)
(i32.store offset=${pagesz-28} (local.get $p) (local.get $v)))
(func (export "copy") (param $dest i32) (param $src i32)
(memory.copy (local.get $dest) (local.get $src) (i32.const 12)))
(func (export "init") (param $dest i32)
(memory.init $flintstone (local.get $dest) (i32.const 0) (i32.const 12)))
(func (export "fill") (param $dest i32) (param $val i32) (param $len i32)
(memory.fill (local.get 0) (local.get 1) (local.get 2)))
(func (export "grow1") (result i32)
(memory.grow (i32.const 1)))
)`);
let buf = new Int32Array(ins.exports.mem.buffer);
let checkFlintstoneAt = function (addr) {
assertEq(buf[addr/4], 0x62626179) // "yabb", little-endian
assertEq(buf[addr/4+1], 0x62616461) // "adab", ditto
assertEq(buf[addr/4+2], 0x6f646162) // "bado"
}
buf[pages*pagesz/4-1] = 0xdeadbeef;
assertEq(ins.exports.get_constaddr(), 0xdeadbeef|0);
assertEq(ins.exports.get_varaddr(pages*pagesz-4), 0xdeadbeef|0);
assertEq(ins.exports.get_constaddr_large_offset(), 0xdeadbeef|0);
assertEq(ins.exports.get_varaddr_large_offset(pagesz*100), 0xdeadbeef|0);
assertEq(ins.exports.get_constaddr_small_offset(), 0xdeadbeef|0);
assertEq(ins.exports.get_varaddr_small_offset((pages-1)*pagesz), 0xdeadbeef|0);
ins.exports.set_constaddr(0xcafebab0);
assertEq(buf[pages*pagesz/4-2], 0xcafebab0|0);
ins.exports.set_varaddr(pages*pagesz-12, 0xcafebab1);
assertEq(buf[pages*pagesz/4-3], 0xcafebab1|0);
ins.exports.set_constaddr_large_offset(0xcafebab2);
assertEq(buf[pages*pagesz/4-4], 0xcafebab2|0);
ins.exports.set_varaddr_large_offset(pagesz*100, 0xcafebab3);
assertEq(buf[pages*pagesz/4-5], 0xcafebab3|0);
ins.exports.set_constaddr_small_offset(0xcafebab4);
assertEq(buf[pages*pagesz/4-6], 0xcafebab4|0);
ins.exports.set_varaddr_small_offset((pages-1)*pagesz, 0xcafebab5);
assertEq(buf[pages*pagesz/4-7], 0xcafebab5|0);
assertErrorMessage(() => ins.exports.get_varaddr(pages*pagesz),
WebAssembly.RuntimeError,
/index out of bounds/);
assertErrorMessage(() => ins.exports.get_varaddr_large_offset(pagesz*100+4),
WebAssembly.RuntimeError,
/index out of bounds/);
assertErrorMessage(() => ins.exports.get_varaddr_small_offset((pages-1)*pagesz+4),
WebAssembly.RuntimeError,
/index out of bounds/);
ins.exports.set_varaddr(pages*pagesz-4, 0); // Should work
assertErrorMessage(() => ins.exports.set_varaddr(pages*pagesz, 0),
WebAssembly.RuntimeError,
/index out of bounds/);
ins.exports.set_varaddr_large_offset(pagesz*100+16, 0); // Should work
assertErrorMessage(() => ins.exports.set_varaddr_large_offset(pagesz*100+20, 0),
WebAssembly.RuntimeError,
/index out of bounds/);
ins.exports.set_varaddr_small_offset((pages-1)*pagesz+24); // Should work
assertErrorMessage(() => ins.exports.set_varaddr_small_offset((pages-1)*pagesz+28, 0),
WebAssembly.RuntimeError,
/index out of bounds/);
// Active init
checkFlintstoneAt((pages-5)*pagesz);
// memory.init
ins.exports.init((pages-6)*pagesz);
checkFlintstoneAt((pages-6)*pagesz);
ins.exports.init(pages*pagesz-12); // Should work
// Dest goes OOB
assertErrorMessage(() => ins.exports.init(pages*pagesz-6),
WebAssembly.RuntimeError,
/index out of bounds/);
// memory.copy
// Dest and src are in bounds
ins.exports.copy((pages-10)*pagesz, (pages-5)*pagesz);
checkFlintstoneAt((pages-10)*pagesz);
// Dest goes OOB
assertErrorMessage(() => ins.exports.copy((pages)*pagesz-6, (pages-5)*pagesz, 12),
WebAssembly.RuntimeError,
/index out of bounds/);
ins.exports.copy((pages)*pagesz-12, (pages-1)*pagesz); // Should work
// Src goes OOB
assertErrorMessage(() => ins.exports.copy((pages)*pagesz-12, (pages)*pagesz-6),
WebAssembly.RuntimeError,
/index out of bounds/);
// memory.fill
let lastpg = (pages-1)*pagesz;
ins.exports.fill(lastpg, 0x37, pagesz);
for ( let i=0; i < pagesz/4; i++ )
assertEq(buf[lastpg/4+i], 0x37373737);
assertErrorMessage(() => ins.exports.fill(lastpg, 0x42, pagesz+1),
WebAssembly.RuntimeError,
/index out of bounds/);
if (pages < maxpages) {
assertEq(ins.exports.grow1(), pages);
assertEq(ins.exports.grow1(), pages+1);
assertEq(ins.exports.grow1(), pages+2);
assertEq(ins.exports.get_varaddr((pages+2)*pagesz), 0);
let i = 0;
while (ins.exports.grow1() != -1) {
i++;
}
assertEq(i, maxpages-pages-3);
}
}
// Very large offsets should be allowed (but may of course be OOB). Observe
// that offset=0xffffffff is accepted by the validator even though this access
// could never succeed.
{
let ins = wasmEvalText(`
(module
(memory (export "mem") 1)
(func (export "get1") (param $p i32) (result i32)
(i32.load offset=0x80000000 (local.get $p)))
(func (export "get2") (param $p i32) (result i32)
(i32.load offset=0xfffffffc (local.get $p)))
(func (export "get3") (param $p i32) (result i32)
(i32.load offset=0xffffffff (local.get $p))))
`);
assertErrorMessage(() => ins.exports.get1(0),
WebAssembly.RuntimeError,
/index out of bounds/);
assertErrorMessage(() => ins.exports.get2(0),
WebAssembly.RuntimeError,
/index out of bounds/);
assertErrorMessage(() => ins.exports.get3(0),
WebAssembly.RuntimeError,
/index out of bounds/);
}
{
let ins = wasmEvalText(`
(module
(memory (export "mem") 32768)
(func (export "get1") (param $p i32) (result i32)
(i32.load8_s offset=0x7fffffff (local.get $p))))
`);
assertEq(ins.exports.get1(0), 0);
assertErrorMessage(() => ins.exports.get1(1),
WebAssembly.RuntimeError,
/index out of bounds/);
assertErrorMessage(() => ins.exports.get1(-1),
WebAssembly.RuntimeError,
/index out of bounds/);
}
{
let ins = wasmEvalText(`
(module
(memory (export "mem") 32769)
(func (export "get1") (param $p i32) (result i32)
(i32.load8_s offset=0x80000000 (local.get $p))))
`);
assertEq(ins.exports.get1(0), 0);
assertEq(ins.exports.get1(65535), 0);
assertErrorMessage(() => ins.exports.get1(65536),
WebAssembly.RuntimeError,
/index out of bounds/);
}
{
let ins = wasmEvalText(`
(module
(memory (export "mem") ${pages_limit})
(func (export "get1") (param $p i32) (result i32)
(i32.load8_s offset=${pages_limit*pagesz-1} (local.get $p))))
`);
assertEq(ins.exports.get1(0), 0);
assertErrorMessage(() => ins.exports.get1(1),
WebAssembly.RuntimeError,
/index out of bounds/);
assertErrorMessage(() => ins.exports.get1(-1),
WebAssembly.RuntimeError,
/index out of bounds/);
}
// Fail to grow at the declared max
// Import and export
{
let ins = wasmEvalText(`
(module
(memory (export "mem") ${pages_limit} ${pages_limit})
(func (export "set_it") (param $addr i32) (param $val i32)
(i32.store (local.get $addr) (local.get $val)))
(func (export "grow1") (result i32)
(memory.grow (i32.const 1)))
)`);
assertEq(ins.exports.grow1(), -1); // Because initial == max
let ins2 = wasmEvalText(`
(module
(import "" "mem" (memory 1))
(func (export "get_it") (param $addr i32) (result i32)
(i32.load (local.get $addr)))
)`,
{"":ins.exports})
ins.exports.set_it((pages_limit*pagesz)-4, 0xbaadf00d);
assertEq(ins2.exports.get_it((pages_limit*pagesz)-4), 0xbaadf00d|0)
}
// Fail to grow at the max heap size even if there is headroom
// in the declared max
if (pages_limit < 65536) {
let ins = wasmEvalText(`
(module
(memory (export "mem") ${pages_limit} ${pages_limit+2})
(func (export "grow1") (result i32)
(memory.grow (i32.const 1)))
)`);
assertEq(ins.exports.grow1(), -1); // Because current is at the max heap size
}
// Fail to instantiate when the minimum is larger than the max heap size
{
assertErrorMessage(() => wasmEvalText(`
(module (memory (export "mem") ${pages_limit+1} ${pages_limit+1}))
`),
WebAssembly.RuntimeError,
/too many memory pages/);
}

View File

@ -5,9 +5,9 @@
// //
// Tests of limits of memory and table types // Tests of limits of memory and table types
const PageSize = 65536; const PageSize = PageSizeInBytes;
const MemoryMaxValid = 65536; const MemoryMaxValid = 65536;
const MemoryMaxRuntime = Math.floor(0x7fff_ffff / PageSize); const MemoryMaxRuntime = MaxPagesIn32BitMemory;
const TableMaxValid = 0xffff_ffff; const TableMaxValid = 0xffff_ffff;
const TableMaxRuntime = 10_000_000; const TableMaxRuntime = 10_000_000;

View File

@ -266,7 +266,7 @@ for (var foldOffsets = 0; foldOffsets <= 1; foldOffsets++) {
} }
// Ensure wrapping doesn't apply. // Ensure wrapping doesn't apply.
offset = 0x7fffffff; // maximum allowed offset that doesn't always throw. offset = 0x7fffffff;
for (let index of [0, 1, 2, 3, 0x7fffffff, 0x80000000, 0x80000001]) { for (let index of [0, 1, 2, 3, 0x7fffffff, 0x80000000, 0x80000001]) {
if (align < 2) { if (align < 2) {
testLoadOOB('i32', '8_s', index, offset, align); testLoadOOB('i32', '8_s', index, offset, align);

View File

@ -4,9 +4,9 @@
#include "jsapi-tests/tests.h" #include "jsapi-tests/tests.h"
static const unsigned BufferSize = 20; static const unsigned BufSize = 20;
static unsigned FinalizeCalls = 0; static unsigned FinalizeCalls = 0;
static JSFinalizeStatus StatusBuffer[BufferSize]; static JSFinalizeStatus StatusBuffer[BufSize];
BEGIN_TEST(testGCFinalizeCallback) { BEGIN_TEST(testGCFinalizeCallback) {
JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, true); JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
@ -155,13 +155,13 @@ virtual void uninit() override {
} }
bool checkSingleGroup() { bool checkSingleGroup() {
CHECK(FinalizeCalls < BufferSize); CHECK(FinalizeCalls < BufSize);
CHECK(FinalizeCalls == 4); CHECK(FinalizeCalls == 4);
return true; return true;
} }
bool checkMultipleGroups() { bool checkMultipleGroups() {
CHECK(FinalizeCalls < BufferSize); CHECK(FinalizeCalls < BufSize);
CHECK(FinalizeCalls % 3 == 1); CHECK(FinalizeCalls % 3 == 1);
CHECK((FinalizeCalls - 1) / 3 > 1); CHECK((FinalizeCalls - 1) / 3 > 1);
return true; return true;
@ -187,7 +187,7 @@ bool checkFinalizeStatus() {
static void FinalizeCallback(JSFreeOp* fop, JSFinalizeStatus status, static void FinalizeCallback(JSFreeOp* fop, JSFinalizeStatus status,
void* data) { void* data) {
if (FinalizeCalls < BufferSize) { if (FinalizeCalls < BufSize) {
StatusBuffer[FinalizeCalls] = status; StatusBuffer[FinalizeCalls] = status;
} }
++FinalizeCalls; ++FinalizeCalls;

View File

@ -659,13 +659,12 @@ WasmArrayRawBuffer* ArrayBufferObject::BufferContents::wasmBuffer() const {
} }
template <typename ObjT, typename RawbufT> template <typename ObjT, typename RawbufT>
static bool CreateSpecificWasmBuffer( static bool CreateSpecificWasmBuffer32(
JSContext* cx, uint32_t initialSize, const Maybe<uint64_t>& maxSize, JSContext* cx, uint64_t initialSize, const Maybe<uint64_t>& maxSize,
wasm::MemoryKind memKind,
MutableHandleArrayBufferObjectMaybeShared maybeSharedObject) { MutableHandleArrayBufferObjectMaybeShared maybeSharedObject) {
bool useHugeMemory = wasm::IsHugeMemoryEnabled(); bool useHugeMemory = wasm::IsHugeMemoryEnabled();
MOZ_RELEASE_ASSERT(memKind == wasm::MemoryKind::Memory32); MOZ_RELEASE_ASSERT(initialSize <= wasm::MaxMemory32Bytes());
Maybe<uint64_t> clampedMaxSize = maxSize; Maybe<uint64_t> clampedMaxSize = maxSize;
if (clampedMaxSize) { if (clampedMaxSize) {
@ -674,6 +673,11 @@ static bool CreateSpecificWasmBuffer(
// clampedMaxSize to a smaller value that satisfies the 32-bit invariants // clampedMaxSize to a smaller value that satisfies the 32-bit invariants
// clampedMaxSize + wasm::PageSize < UINT32_MAX and clampedMaxSize % // clampedMaxSize + wasm::PageSize < UINT32_MAX and clampedMaxSize %
// wasm::PageSize == 0 // wasm::PageSize == 0
//
// Note MaxMemory32LimitField*PageSize == UINT32_MAX+1 == 4GB, as you would
// expect for a 32-bit memory.
//
// Note that this must correspond with MaxMemory32Bytes().
if (!useHugeMemory && if (!useHugeMemory &&
clampedMaxSize.value() >= (UINT32_MAX - wasm::PageSize)) { clampedMaxSize.value() >= (UINT32_MAX - wasm::PageSize)) {
uint64_t clamp = (wasm::MaxMemory32LimitField - 2) * wasm::PageSize; uint64_t clamp = (wasm::MaxMemory32LimitField - 2) * wasm::PageSize;
@ -694,6 +698,7 @@ static bool CreateSpecificWasmBuffer(
uint64_t clamp = std::max(OneGiB, uint64_t(initialSize)); uint64_t clamp = std::max(OneGiB, uint64_t(initialSize));
clampedMaxSize = Some(std::min(clamp, *clampedMaxSize)); clampedMaxSize = Some(std::min(clamp, *clampedMaxSize));
#endif #endif
MOZ_RELEASE_ASSERT(initialSize <= clampedMaxSize.value());
} }
Maybe<size_t> mappedSize; Maybe<size_t> mappedSize;
@ -704,8 +709,8 @@ static bool CreateSpecificWasmBuffer(
} }
#endif #endif
RawbufT* buffer = RawbufT* buffer = RawbufT::Allocate(BufferSize(size_t(initialSize)),
RawbufT::Allocate(BufferSize(initialSize), clampedMaxSize, mappedSize); clampedMaxSize, mappedSize);
if (!buffer) { if (!buffer) {
if (useHugeMemory) { if (useHugeMemory) {
WarnNumberASCII(cx, JSMSG_WASM_HUGE_MEMORY_FAILED); WarnNumberASCII(cx, JSMSG_WASM_HUGE_MEMORY_FAILED);
@ -720,7 +725,7 @@ static bool CreateSpecificWasmBuffer(
// If we fail, and have a clampedMaxSize, try to reserve the biggest chunk // If we fail, and have a clampedMaxSize, try to reserve the biggest chunk
// in the range [initialSize, clampedMaxSize) using log backoff. // in the range [initialSize, clampedMaxSize) using log backoff.
if (!clampedMaxSize) { if (!clampedMaxSize) {
wasm::Log(cx, "new Memory({initial=%" PRIu32 " bytes}) failed", wasm::Log(cx, "new Memory({initial=%" PRIu64 " bytes}) failed",
initialSize); initialSize);
ReportOutOfMemory(cx); ReportOutOfMemory(cx);
return false; return false;
@ -730,15 +735,15 @@ static bool CreateSpecificWasmBuffer(
for (; cur > initialSize; cur /= 2) { for (; cur > initialSize; cur /= 2) {
uint64_t clampedMaxSize = RoundUp(cur, wasm::PageSize); uint64_t clampedMaxSize = RoundUp(cur, wasm::PageSize);
buffer = RawbufT::Allocate(BufferSize(initialSize), Some(clampedMaxSize), buffer = RawbufT::Allocate(BufferSize(size_t(initialSize)),
mappedSize); Some(clampedMaxSize), mappedSize);
if (buffer) { if (buffer) {
break; break;
} }
} }
if (!buffer) { if (!buffer) {
wasm::Log(cx, "new Memory({initial=%" PRIu32 " bytes}) failed", wasm::Log(cx, "new Memory({initial=%" PRIu64 " bytes}) failed",
initialSize); initialSize);
ReportOutOfMemory(cx); ReportOutOfMemory(cx);
return false; return false;
@ -753,7 +758,8 @@ static bool CreateSpecificWasmBuffer(
// ObjT::createFromNewRawBuffer assumes ownership of |buffer| even in case // ObjT::createFromNewRawBuffer assumes ownership of |buffer| even in case
// of failure. // of failure.
RootedArrayBufferObjectMaybeShared object( RootedArrayBufferObjectMaybeShared object(
cx, ObjT::createFromNewRawBuffer(cx, buffer, BufferSize(initialSize))); cx, ObjT::createFromNewRawBuffer(cx, buffer,
BufferSize(size_t(initialSize))));
if (!object) { if (!object) {
return false; return false;
} }
@ -778,44 +784,43 @@ static bool CreateSpecificWasmBuffer(
if (clampedMaxSize) { if (clampedMaxSize) {
if (useHugeMemory) { if (useHugeMemory) {
wasm::Log(cx, wasm::Log(cx,
"new Memory({initial:%" PRIu32 " bytes, maximum:%" PRIu64 "new Memory({initial:%" PRIu64 " bytes, maximum:%" PRIu64
" bytes}) succeeded", " bytes}) succeeded",
initialSize, *clampedMaxSize); initialSize, *clampedMaxSize);
} else { } else {
wasm::Log(cx, wasm::Log(cx,
"new Memory({initial:%" PRIu32 " bytes, maximum:%" PRIu64 "new Memory({initial:%" PRIu64 " bytes, maximum:%" PRIu64
" bytes}) succeeded " " bytes}) succeeded "
"with internal maximum of %" PRIu64, "with internal maximum of %" PRIu64,
initialSize, *clampedMaxSize, object->wasmMaxSize().value()); initialSize, *clampedMaxSize, object->wasmMaxSize().value());
} }
} else { } else {
wasm::Log(cx, "new Memory({initial:%" PRIu32 " bytes}) succeeded", wasm::Log(cx, "new Memory({initial:%" PRIu64 " bytes}) succeeded",
initialSize); initialSize);
} }
return true; return true;
} }
bool js::CreateWasmBuffer(JSContext* cx, wasm::MemoryKind memKind, bool js::CreateWasmBuffer32(JSContext* cx, uint64_t initialSize,
const wasm::Limits& memory, const Maybe<uint64_t>& maxSize, bool sharedMemory,
MutableHandleArrayBufferObjectMaybeShared buffer) { MutableHandleArrayBufferObjectMaybeShared buffer) {
MOZ_ASSERT(memory.initial % wasm::PageSize == 0); MOZ_ASSERT(initialSize % wasm::PageSize == 0);
MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers); MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers);
MOZ_RELEASE_ASSERT(memory.initial <= MOZ_RELEASE_ASSERT(initialSize <= ArrayBufferObject::maxBufferByteLength());
ArrayBufferObject::maxBufferByteLength());
if (memory.shared == wasm::Shareable::True) { if (sharedMemory) {
if (!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) { if (!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_NO_SHMEM_LINK); JSMSG_WASM_NO_SHMEM_LINK);
return false; return false;
} }
return CreateSpecificWasmBuffer<SharedArrayBufferObject, return CreateSpecificWasmBuffer32<SharedArrayBufferObject,
SharedArrayRawBuffer>( SharedArrayRawBuffer>(cx, initialSize,
cx, uint32_t(memory.initial), memory.maximum, memKind, buffer); maxSize, buffer);
} }
return CreateSpecificWasmBuffer<ArrayBufferObject, WasmArrayRawBuffer>( return CreateSpecificWasmBuffer32<ArrayBufferObject, WasmArrayRawBuffer>(
cx, uint32_t(memory.initial), memory.maximum, memKind, buffer); cx, initialSize, maxSize, buffer);
} }
bool ArrayBufferObject::prepareForAsmJS() { bool ArrayBufferObject::prepareForAsmJS() {

View File

@ -16,10 +16,10 @@
#include "gc/ZoneAllocator.h" #include "gc/ZoneAllocator.h"
#include "js/ArrayBuffer.h" #include "js/ArrayBuffer.h"
#include "js/GCHashTable.h" #include "js/GCHashTable.h"
#include "vm/BufferSize.h"
#include "vm/JSObject.h" #include "vm/JSObject.h"
#include "vm/Runtime.h" #include "vm/Runtime.h"
#include "vm/SharedMem.h" #include "vm/SharedMem.h"
#include "wasm/WasmTypes.h"
namespace js { namespace js {
@ -109,16 +109,6 @@ mozilla::Maybe<uint64_t> WasmArrayBufferMaxSize(
const ArrayBufferObjectMaybeShared* buf); const ArrayBufferObjectMaybeShared* buf);
size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf); size_t WasmArrayBufferMappedSize(const ArrayBufferObjectMaybeShared* buf);
// Class wrapping an ArrayBuffer or ArrayBufferView byte offset or length.
class BufferSize {
size_t size_ = 0;
public:
explicit BufferSize(size_t size) : size_(size) {}
size_t get() const { return size_; }
};
class ArrayBufferObjectMaybeShared : public NativeObject { class ArrayBufferObjectMaybeShared : public NativeObject {
public: public:
inline BufferSize byteLength() const; inline BufferSize byteLength() const;
@ -496,9 +486,13 @@ using RootedArrayBufferObject = Rooted<ArrayBufferObject*>;
using HandleArrayBufferObject = Handle<ArrayBufferObject*>; using HandleArrayBufferObject = Handle<ArrayBufferObject*>;
using MutableHandleArrayBufferObject = MutableHandle<ArrayBufferObject*>; using MutableHandleArrayBufferObject = MutableHandle<ArrayBufferObject*>;
bool CreateWasmBuffer(JSContext* cx, wasm::MemoryKind memKind, // Create a buffer for a 32-bit wasm memory. Arguments of the Limits structure
const wasm::Limits& memory, // are broken out in order to avoid having this file depending on WasmTypes.h,
MutableHandleArrayBufferObjectMaybeShared buffer); // as that creates a circularity through WasmJS.h.
bool CreateWasmBuffer32(JSContext* cx, uint64_t initialSize,
const mozilla::Maybe<uint64_t>& maxSize,
bool sharedMemory,
MutableHandleArrayBufferObjectMaybeShared buffer);
// Per-compartment table that manages the relationship between array buffers // Per-compartment table that manages the relationship between array buffers
// and the views that use their storage. // and the views that use their storage.

29
js/src/vm/BufferSize.h Normal file
View File

@ -0,0 +1,29 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef vm_BufferSize_h
#define vm_BufferSize_h
namespace js {
// Class wrapping an ArrayBuffer or ArrayBufferView byte offset or length.
class BufferSize {
size_t size_ = 0;
public:
explicit BufferSize(size_t size) : size_(size) {}
size_t get() const { return size_; }
uint32_t getWasmUint32() const {
// Exact test on max heap size for the time being: 64K-2 pages
MOZ_ASSERT(size_ <= UINT32_MAX - 65536 * 2 + 1);
return size_;
}
};
} // namespace js
#endif // vm_BufferSize_h

View File

@ -6752,7 +6752,7 @@ static bool CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata,
buffer.set(&bufferObj->as<ArrayBufferObject>()); buffer.set(&bufferObj->as<ArrayBufferObject>());
// Do not assume the buffer's length fits within the wasm heap limit, so do // Do not assume the buffer's length fits within the wasm heap limit, so do
// not call ByteLength32(). // not call getWasmUint32()
size_t memoryLength = buffer->byteLength().get(); size_t memoryLength = buffer->byteLength().get();
if (!IsValidAsmJSHeapLength(memoryLength)) { if (!IsValidAsmJSHeapLength(memoryLength)) {
@ -6785,6 +6785,21 @@ static bool CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata,
return LinkFail(cx, msg.get()); return LinkFail(cx, msg.get());
} }
// ArrayBuffer lengths in SpiderMonkey used to be restricted to <= INT32_MAX,
// but that has since been relaxed for the benefit of wasm. We keep the old
// limit for asm.js so as to avoid having to worry about whether the asm.js
// implementation is safe for larger heaps.
if (memoryLength >= INT32_MAX) {
UniqueChars msg(
JS_smprintf("ArrayBuffer byteLength 0x%" PRIx64
" is too large for asm.js (implementation limit).",
uint64_t(memoryLength)));
if (!msg) {
return false;
}
return LinkFail(cx, msg.get());
}
if (!buffer->prepareForAsmJS()) { if (!buffer->prepareForAsmJS()) {
return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use"); return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
} }
@ -7279,7 +7294,7 @@ bool js::IsValidAsmJSHeapLength(size_t length) {
} }
// The heap length is limited by what wasm can handle. // The heap length is limited by what wasm can handle.
if (length > MaxMemory32Bytes) { if (length > MaxMemory32Bytes()) {
return false; return false;
} }

View File

@ -1020,15 +1020,6 @@ static const unsigned MaxParams = 1000;
static const unsigned MaxResults = 1000; static const unsigned MaxResults = 1000;
static const unsigned MaxStructFields = 1000; static const unsigned MaxStructFields = 1000;
static const unsigned MaxMemory32LimitField = 65536; static const unsigned MaxMemory32LimitField = 65536;
#ifdef JS_64BIT
// FIXME (large ArrayBuffer): This should be upped to UINT32_MAX / PageSize
// initially, then to (size_t(UINT32_MAX) + 1) / PageSize subsequently, see the
// companion FIXME in WasmMemoryObject::grow() for additional information.
static const unsigned MaxMemory32Pages = INT32_MAX / PageSize;
#else
static const unsigned MaxMemory32Pages = INT32_MAX / PageSize;
#endif
static const size_t MaxMemory32Bytes = size_t(MaxMemory32Pages) * PageSize;
static const unsigned MaxStringBytes = 100000; static const unsigned MaxStringBytes = 100000;
static const unsigned MaxModuleBytes = 1024 * 1024 * 1024; static const unsigned MaxModuleBytes = 1024 * 1024 * 1024;
static const unsigned MaxFunctionBytes = 7654321; static const unsigned MaxFunctionBytes = 7654321;

View File

@ -411,6 +411,8 @@ TypeCode env_elem_typecode(const CraneliftModuleEnvironment* env,
return env->env->elemSegments[index]->elemType.packed().typeCode(); return env->env->elemSegments[index]->elemType.packed().typeCode();
} }
// Returns a number of pages in the range [0..65536], or UINT32_MAX to signal
// that no maximum has been set.
uint32_t env_max_memory(const CraneliftModuleEnvironment* env) { uint32_t env_max_memory(const CraneliftModuleEnvironment* env) {
// env.maxMemoryLength is in bytes. Convert it to wasm pages. // env.maxMemoryLength is in bytes. Convert it to wasm pages.
if (env->env->maxMemoryLength.isSome()) { if (env->env->maxMemoryLength.isSome()) {
@ -421,9 +423,8 @@ uint32_t env_max_memory(const CraneliftModuleEnvironment* env) {
MOZ_RELEASE_ASSERT(inBytes <= (((uint64_t)1) << 32)); MOZ_RELEASE_ASSERT(inBytes <= (((uint64_t)1) << 32));
MOZ_RELEASE_ASSERT((inBytes & wasm::PageMask) == 0); MOZ_RELEASE_ASSERT((inBytes & wasm::PageMask) == 0);
return (uint32_t)(inBytes >> wasm::PageBits); return (uint32_t)(inBytes >> wasm::PageBits);
} else {
return UINT32_MAX;
} }
return UINT32_MAX;
} }
bool env_uses_shared_memory(const CraneliftModuleEnvironment* env) { bool env_uses_shared_memory(const CraneliftModuleEnvironment* env) {

View File

@ -26,6 +26,7 @@
#include "jit/ExecutableAllocator.h" #include "jit/ExecutableAllocator.h"
#include "jit/MacroAssembler.h" #include "jit/MacroAssembler.h"
#include "wasm/WasmInstance.h" #include "wasm/WasmInstance.h"
#include "wasm/WasmJS.h"
#include "wasm/WasmStubs.h" #include "wasm/WasmStubs.h"
#include "wasm/WasmValidate.h" #include "wasm/WasmValidate.h"

View File

@ -350,7 +350,8 @@ Instance::callImport_general(Instance* instance, int32_t funcImportIndex,
// write tests for cross-realm calls. // write tests for cross-realm calls.
MOZ_ASSERT(TlsContext.get()->realm() == instance->realm()); MOZ_ASSERT(TlsContext.get()->realm() == instance->realm());
uint32_t byteLength = instance->memory()->volatileMemoryLength32(); uint32_t byteLength =
instance->memory()->volatileMemoryLength().getWasmUint32();
MOZ_ASSERT(byteLength % wasm::PageSize == 0); MOZ_ASSERT(byteLength % wasm::PageSize == 0);
return byteLength / wasm::PageSize; return byteLength / wasm::PageSize;
} }
@ -372,7 +373,8 @@ static int32_t PerformWait(Instance* instance, uint32_t byteOffset, T value,
return -1; return -1;
} }
if (byteOffset + sizeof(T) > instance->memory()->volatileMemoryLength32()) { if (byteOffset + sizeof(T) >
instance->memory()->volatileMemoryLength().getWasmUint32()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_OUT_OF_BOUNDS); JSMSG_WASM_OUT_OF_BOUNDS);
return -1; return -1;
@ -427,7 +429,8 @@ static int32_t PerformWait(Instance* instance, uint32_t byteOffset, T value,
return -1; return -1;
} }
if (byteOffset >= instance->memory()->volatileMemoryLength32()) { if (byteOffset >=
instance->memory()->volatileMemoryLength().getWasmUint32()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_OUT_OF_BOUNDS); JSMSG_WASM_OUT_OF_BOUNDS);
return -1; return -1;
@ -475,7 +478,7 @@ inline int32_t WasmMemoryCopy(T memBase, uint32_t memLen,
MOZ_ASSERT(SASigMemCopy.failureMode == FailureMode::FailOnNegI32); MOZ_ASSERT(SASigMemCopy.failureMode == FailureMode::FailOnNegI32);
const WasmArrayRawBuffer* rawBuf = WasmArrayRawBuffer::fromDataPtr(memBase); const WasmArrayRawBuffer* rawBuf = WasmArrayRawBuffer::fromDataPtr(memBase);
uint32_t memLen = ByteLength32(rawBuf); uint32_t memLen = rawBuf->byteLength().getWasmUint32();
return WasmMemoryCopy(memBase, memLen, dstByteOffset, srcByteOffset, len, return WasmMemoryCopy(memBase, memLen, dstByteOffset, srcByteOffset, len,
memmove); memmove);
@ -492,7 +495,7 @@ inline int32_t WasmMemoryCopy(T memBase, uint32_t memLen,
const SharedArrayRawBuffer* rawBuf = const SharedArrayRawBuffer* rawBuf =
SharedArrayRawBuffer::fromDataPtr(memBase); SharedArrayRawBuffer::fromDataPtr(memBase);
uint32_t memLen = VolatileByteLength32(rawBuf); uint32_t memLen = rawBuf->volatileByteLength().getWasmUint32();
return WasmMemoryCopy<SharedMem<uint8_t*>, RacyMemMove>( return WasmMemoryCopy<SharedMem<uint8_t*>, RacyMemMove>(
SharedMem<uint8_t*>::shared(memBase), memLen, dstByteOffset, SharedMem<uint8_t*>::shared(memBase), memLen, dstByteOffset,
@ -542,7 +545,7 @@ inline int32_t WasmMemoryFill(T memBase, uint32_t memLen, uint32_t byteOffset,
MOZ_ASSERT(SASigMemFill.failureMode == FailureMode::FailOnNegI32); MOZ_ASSERT(SASigMemFill.failureMode == FailureMode::FailOnNegI32);
const WasmArrayRawBuffer* rawBuf = WasmArrayRawBuffer::fromDataPtr(memBase); const WasmArrayRawBuffer* rawBuf = WasmArrayRawBuffer::fromDataPtr(memBase);
uint32_t memLen = ByteLength32(rawBuf); uint32_t memLen = rawBuf->byteLength().getWasmUint32();
return WasmMemoryFill(memBase, memLen, byteOffset, value, len, memset); return WasmMemoryFill(memBase, memLen, byteOffset, value, len, memset);
} }
@ -555,7 +558,7 @@ inline int32_t WasmMemoryFill(T memBase, uint32_t memLen, uint32_t byteOffset,
const SharedArrayRawBuffer* rawBuf = const SharedArrayRawBuffer* rawBuf =
SharedArrayRawBuffer::fromDataPtr(memBase); SharedArrayRawBuffer::fromDataPtr(memBase);
uint32_t memLen = VolatileByteLength32(rawBuf); uint32_t memLen = rawBuf->volatileByteLength().getWasmUint32();
return WasmMemoryFill(SharedMem<uint8_t*>::shared(memBase), memLen, return WasmMemoryFill(SharedMem<uint8_t*>::shared(memBase), memLen,
byteOffset, value, len, byteOffset, value, len,
@ -586,7 +589,7 @@ inline int32_t WasmMemoryFill(T memBase, uint32_t memLen, uint32_t byteOffset,
const uint32_t segLen = seg.bytes.length(); const uint32_t segLen = seg.bytes.length();
WasmMemoryObject* mem = instance->memory(); WasmMemoryObject* mem = instance->memory();
const uint32_t memLen = mem->volatileMemoryLength32(); const uint32_t memLen = mem->volatileMemoryLength().getWasmUint32();
// We are proposing to copy // We are proposing to copy
// //
@ -1210,7 +1213,8 @@ bool Instance::init(JSContext* cx, const JSFunctionVector& funcImports,
tlsData()->memoryBase = tlsData()->memoryBase =
memory_ ? memory_->buffer().dataPointerEither().unwrap() : nullptr; memory_ ? memory_->buffer().dataPointerEither().unwrap() : nullptr;
tlsData()->boundsCheckLimit32 = memory_ ? memory_->boundsCheckLimit32() : 0; tlsData()->boundsCheckLimit32 =
memory_ ? memory_->boundsCheckLimit().getWasmUint32() : 0;
tlsData()->instance = this; tlsData()->instance = this;
tlsData()->realm = realm_; tlsData()->realm = realm_;
tlsData()->cx = cx; tlsData()->cx = cx;
@ -1456,7 +1460,7 @@ bool Instance::memoryAccessInGuardRegion(uint8_t* addr,
} }
size_t lastByteOffset = addr - base + (numBytes - 1); size_t lastByteOffset = addr - base + (numBytes - 1);
return lastByteOffset >= memory()->volatileMemoryLength32() && return lastByteOffset >= memory()->volatileMemoryLength().getWasmUint32() &&
lastByteOffset < memoryMappedSize(); lastByteOffset < memoryMappedSize();
} }
@ -1472,7 +1476,7 @@ bool Instance::memoryAccessInBounds(uint8_t* addr, unsigned numBytes) const {
return false; return false;
} }
uint32_t length = memory()->volatileMemoryLength32(); uint32_t length = memory()->volatileMemoryLength().getWasmUint32();
if (addr >= base + length) { if (addr >= base + length) {
return false; return false;
} }
@ -2063,7 +2067,7 @@ void Instance::onMovingGrowMemory() {
ArrayBufferObject& buffer = memory_->buffer().as<ArrayBufferObject>(); ArrayBufferObject& buffer = memory_->buffer().as<ArrayBufferObject>();
tlsData()->memoryBase = buffer.dataPointer(); tlsData()->memoryBase = buffer.dataPointer();
tlsData()->boundsCheckLimit32 = memory_->boundsCheckLimit32(); tlsData()->boundsCheckLimit32 = memory_->boundsCheckLimit().getWasmUint32();
} }
void Instance::onMovingGrowTable(const Table* theTable) { void Instance::onMovingGrowTable(const Table* theTable) {

View File

@ -520,39 +520,6 @@ bool wasm::CodeCachingAvailable(JSContext* cx) {
return StreamingCompilationAvailable(cx) && IonAvailable(cx); return StreamingCompilationAvailable(cx) && IonAvailable(cx);
} }
// As the return values from the underlying buffer accessors will become size_t
// before long, they are captured as size_t here.
uint32_t wasm::ByteLength32(Handle<ArrayBufferObjectMaybeShared*> buffer) {
size_t len = buffer->byteLength().get();
MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize);
return uint32_t(len);
}
uint32_t wasm::ByteLength32(const ArrayBufferObjectMaybeShared& buffer) {
size_t len = buffer.byteLength().get();
MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize);
return uint32_t(len);
}
uint32_t wasm::ByteLength32(const WasmArrayRawBuffer* buffer) {
size_t len = buffer->byteLength().get();
MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize);
return uint32_t(len);
}
uint32_t wasm::ByteLength32(const ArrayBufferObject& buffer) {
size_t len = buffer.byteLength().get();
MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize);
return uint32_t(len);
}
uint32_t wasm::VolatileByteLength32(const SharedArrayRawBuffer* buffer) {
size_t len = buffer->volatileByteLength().get();
MOZ_ASSERT(len <= size_t(MaxMemory32Pages) * PageSize);
return uint32_t(len);
}
// ============================================================================ // ============================================================================
// Imports // Imports
@ -2466,7 +2433,7 @@ bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) {
return false; return false;
} }
if (limits.initial > MaxMemory32Pages) { if (limits.initial > MaxMemory32Pages()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MEM_IMP_LIMIT); JSMSG_WASM_MEM_IMP_LIMIT);
return false; return false;
@ -2475,7 +2442,8 @@ bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) {
ConvertMemoryPagesToBytes(&limits); ConvertMemoryPagesToBytes(&limits);
RootedArrayBufferObjectMaybeShared buffer(cx); RootedArrayBufferObjectMaybeShared buffer(cx);
if (!CreateWasmBuffer(cx, MemoryKind::Memory32, limits, &buffer)) { if (!CreateWasmBuffer32(cx, limits.initial, limits.maximum,
limits.shared == wasm::Shareable::True, &buffer)) {
return false; return false;
} }
@ -2509,10 +2477,10 @@ bool WasmMemoryObject::bufferGetterImpl(JSContext* cx, const CallArgs& args) {
RootedArrayBufferObjectMaybeShared buffer(cx, &memoryObj->buffer()); RootedArrayBufferObjectMaybeShared buffer(cx, &memoryObj->buffer());
if (memoryObj->isShared()) { if (memoryObj->isShared()) {
uint32_t memoryLength = memoryObj->volatileMemoryLength32(); uint32_t memoryLength = memoryObj->volatileMemoryLength().getWasmUint32();
MOZ_ASSERT(memoryLength >= ByteLength32(buffer)); MOZ_ASSERT(memoryLength >= buffer->byteLength().getWasmUint32());
if (memoryLength > ByteLength32(buffer)) { if (memoryLength > buffer->byteLength().getWasmUint32()) {
RootedSharedArrayBufferObject newBuffer( RootedSharedArrayBufferObject newBuffer(
cx, cx,
SharedArrayBufferObject::New(cx, memoryObj->sharedArrayRawBuffer(), SharedArrayBufferObject::New(cx, memoryObj->sharedArrayRawBuffer(),
@ -2614,7 +2582,7 @@ bool WasmMemoryObject::typeImpl(JSContext* cx, const CallArgs& args) {
} }
uint32_t minimumPages = mozilla::AssertedCast<uint32_t>( uint32_t minimumPages = mozilla::AssertedCast<uint32_t>(
memoryObj->volatileMemoryLength32() / wasm::PageSize); memoryObj->volatileMemoryLength().getWasmUint32() / wasm::PageSize);
if (!props.append(IdValuePair(NameToId(cx->names().minimum), if (!props.append(IdValuePair(NameToId(cx->names().minimum),
Int32Value(minimumPages)))) { Int32Value(minimumPages)))) {
return false; return false;
@ -2640,11 +2608,11 @@ bool WasmMemoryObject::type(JSContext* cx, unsigned argc, Value* vp) {
} }
#endif #endif
uint32_t WasmMemoryObject::volatileMemoryLength32() const { BufferSize WasmMemoryObject::volatileMemoryLength() const {
if (isShared()) { if (isShared()) {
return VolatileByteLength32(sharedArrayRawBuffer()); return sharedArrayRawBuffer()->volatileByteLength();
} }
return ByteLength32(buffer()); return buffer().byteLength();
} }
bool WasmMemoryObject::isShared() const { bool WasmMemoryObject::isShared() const {
@ -2679,8 +2647,10 @@ WasmMemoryObject::InstanceSet* WasmMemoryObject::getOrCreateObservers(
bool WasmMemoryObject::isHuge() const { bool WasmMemoryObject::isHuge() const {
#ifdef WASM_SUPPORTS_HUGE_MEMORY #ifdef WASM_SUPPORTS_HUGE_MEMORY
static_assert(MaxMemory32Bytes < HugeMappedSize, // TODO: Turn this into a static_assert, if we are able to make
"Non-huge buffer may be confused as huge"); // MaxMemory32Bytes() constexpr once the dust settles for the 4GB heaps.
MOZ_ASSERT(MaxMemory32Bytes() < HugeMappedSize,
"Non-huge buffer may be confused as huge");
return buffer().wasmMappedSize() >= HugeMappedSize; return buffer().wasmMappedSize() >= HugeMappedSize;
#else #else
return false; return false;
@ -2691,15 +2661,17 @@ bool WasmMemoryObject::movingGrowable() const {
return !isHuge() && !buffer().wasmMaxSize(); return !isHuge() && !buffer().wasmMaxSize();
} }
uint32_t WasmMemoryObject::boundsCheckLimit32() const { BufferSize WasmMemoryObject::boundsCheckLimit() const {
if (!buffer().isWasm() || isHuge()) { if (!buffer().isWasm() || isHuge()) {
return ByteLength32(buffer()); return buffer().byteLength();
} }
size_t mappedSize = buffer().wasmMappedSize(); size_t mappedSize = buffer().wasmMappedSize();
MOZ_ASSERT(mappedSize <= UINT32_MAX); // See clamping performed in CreateSpecificWasmBuffer()
MOZ_ASSERT(mappedSize < UINT32_MAX);
MOZ_ASSERT(mappedSize % wasm::PageSize == 0);
MOZ_ASSERT(mappedSize >= wasm::GuardSize); MOZ_ASSERT(mappedSize >= wasm::GuardSize);
MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize - wasm::GuardSize)); MOZ_ASSERT(wasm::IsValidBoundsCheckImmediate(mappedSize - wasm::GuardSize));
return mappedSize - wasm::GuardSize; return BufferSize(mappedSize - wasm::GuardSize);
} }
bool WasmMemoryObject::addMovingGrowObserver(JSContext* cx, bool WasmMemoryObject::addMovingGrowObserver(JSContext* cx,
@ -2725,8 +2697,9 @@ uint32_t WasmMemoryObject::growShared(HandleWasmMemoryObject memory,
SharedArrayRawBuffer* rawBuf = memory->sharedArrayRawBuffer(); SharedArrayRawBuffer* rawBuf = memory->sharedArrayRawBuffer();
SharedArrayRawBuffer::Lock lock(rawBuf); SharedArrayRawBuffer::Lock lock(rawBuf);
MOZ_ASSERT(VolatileByteLength32(rawBuf) % PageSize == 0); MOZ_ASSERT(rawBuf->volatileByteLength().getWasmUint32() % PageSize == 0);
uint32_t oldNumPages = VolatileByteLength32(rawBuf) / PageSize; uint32_t oldNumPages =
rawBuf->volatileByteLength().getWasmUint32() / PageSize;
CheckedInt<uint32_t> newSize = oldNumPages; CheckedInt<uint32_t> newSize = oldNumPages;
newSize += delta; newSize += delta;
@ -2735,6 +2708,12 @@ uint32_t WasmMemoryObject::growShared(HandleWasmMemoryObject memory,
return -1; return -1;
} }
// Always check against the max here, do not rely on the buffer resizers to
// use the correct limit, they don't have enough context.
if (newSize.value() > MaxMemory32Bytes()) {
return -1;
}
if (newSize.value() > rawBuf->maxSize()) { if (newSize.value() > rawBuf->maxSize()) {
return -1; return -1;
} }
@ -2758,15 +2737,16 @@ uint32_t WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta,
RootedArrayBufferObject oldBuf(cx, &memory->buffer().as<ArrayBufferObject>()); RootedArrayBufferObject oldBuf(cx, &memory->buffer().as<ArrayBufferObject>());
MOZ_ASSERT(ByteLength32(oldBuf) % PageSize == 0); MOZ_ASSERT(oldBuf->byteLength().getWasmUint32() % PageSize == 0);
uint32_t oldNumPages = ByteLength32(oldBuf) / PageSize; uint32_t oldNumPages = oldBuf->byteLength().getWasmUint32() / PageSize;
// FIXME (large ArrayBuffer): This does not allow 65536 pages, which is // TODO (large ArrayBuffer): This does not allow 65536 pages. See more
// technically the max. That may be a webcompat problem. We can fix this // information at the definition of MaxMemory32Bytes().
// once wasmMovingGrowToSize and wasmGrowToSizeInPlace accept size_t rather //
// than uint32_t. See the FIXME in WasmConstants.h for additional // TODO: Turn this into a static_assert, if we are able to make
// information. // MaxMemory32Bytes() constexpr once the dust settles for the 4GB heaps.
static_assert(MaxMemory32Pages <= UINT32_MAX / PageSize, "Avoid overflows"); MOZ_ASSERT(MaxMemory32Pages() <= UINT32_MAX / PageSize,
"Avoid 32-bit overflows");
CheckedInt<uint32_t> newSize = oldNumPages; CheckedInt<uint32_t> newSize = oldNumPages;
newSize += delta; newSize += delta;
@ -2777,7 +2757,7 @@ uint32_t WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta,
// Always check against the max here, do not rely on the buffer resizers to // Always check against the max here, do not rely on the buffer resizers to
// use the correct limit, they don't have enough context. // use the correct limit, they don't have enough context.
if (newSize.value() > MaxMemory32Pages * PageSize) { if (newSize.value() > MaxMemory32Bytes()) {
return -1; return -1;
} }
@ -4880,3 +4860,27 @@ static const ClassSpec WebAssemblyClassSpec = {CreateWebAssemblyObject,
const JSClass js::WasmNamespaceObject::class_ = { const JSClass js::WasmNamespaceObject::class_ = {
js_WebAssembly_str, JSCLASS_HAS_CACHED_PROTO(JSProto_WebAssembly), js_WebAssembly_str, JSCLASS_HAS_CACHED_PROTO(JSProto_WebAssembly),
JS_NULL_CLASS_OPS, &WebAssemblyClassSpec}; JS_NULL_CLASS_OPS, &WebAssemblyClassSpec};
// Sundry
#ifdef JS_64BIT
// TODO (large ArrayBuffer):
//
// This should be upped to (size_t(UINT32_MAX) + 1) / PageSize, see the
// companion TODO in WasmMemoryObject::grow() for additional information.
// Doing so is hard.
//
// The "-2" here accounts for the !huge-memory case in CreateSpecificWasmBuffer,
// which is guarding against an overflow. Also see
// WasmMemoryObject::boundsCheckLimit() for related assertions.
size_t wasm::MaxMemory32Pages() {
size_t desired = MaxMemory32LimitField - 2;
size_t actual = ArrayBufferObject::maxBufferByteLength() / PageSize;
return std::min(desired, actual);
}
#else
size_t wasm::MaxMemory32Pages() {
MOZ_ASSERT(ArrayBufferObject::maxBufferByteLength() >= INT32_MAX / PageSize);
return INT32_MAX / PageSize;
}
#endif

View File

@ -34,7 +34,8 @@
#include "js/RootingAPI.h" // MovableCellHasher #include "js/RootingAPI.h" // MovableCellHasher
#include "js/SweepingAPI.h" // JS::WeakCache #include "js/SweepingAPI.h" // JS::WeakCache
#include "js/TypeDecls.h" // HandleValue, HandleObject, MutableHandleObject, MutableHandleFunction #include "js/TypeDecls.h" // HandleValue, HandleObject, MutableHandleObject, MutableHandleFunction
#include "js/Vector.h" // JS::Vector #include "js/Vector.h" // JS::Vector
#include "vm/BufferSize.h"
#include "vm/JSFunction.h" // JSFunction #include "vm/JSFunction.h" // JSFunction
#include "vm/NativeObject.h" // NativeObject #include "vm/NativeObject.h" // NativeObject
#include "wasm/WasmTypes.h" // MutableHandleWasmInstanceObject, wasm::* #include "wasm/WasmTypes.h" // MutableHandleWasmInstanceObject, wasm::*
@ -56,10 +57,8 @@ class ArrayBufferObjectMaybeShared;
class JSStringBuilder; class JSStringBuilder;
class SharedArrayRawBuffer; class SharedArrayRawBuffer;
class TypedArrayObject; class TypedArrayObject;
class WasmArrayRawBuffer;
class WasmFunctionScope; class WasmFunctionScope;
class WasmInstanceScope; class WasmInstanceScope;
class SharedArrayRawBuffer;
namespace wasm { namespace wasm {
@ -162,6 +161,12 @@ void ReportSimdAnalysis(const char* data);
// options can support try/catch, throw, rethrow, and branch_on_exn (evolving). // options can support try/catch, throw, rethrow, and branch_on_exn (evolving).
bool ExceptionsAvailable(JSContext* cx); bool ExceptionsAvailable(JSContext* cx);
size_t MaxMemory32Pages();
static inline size_t MaxMemory32Bytes() {
return MaxMemory32Pages() * PageSize;
}
// Compiles the given binary wasm module given the ArrayBufferObject // Compiles the given binary wasm module given the ArrayBufferObject
// and links the module's imports with the given import object. // and links the module's imports with the given import object.
@ -204,17 +209,6 @@ uint32_t ExportedFunctionToFuncIndex(JSFunction* fun);
bool IsSharedWasmMemoryObject(JSObject* obj); bool IsSharedWasmMemoryObject(JSObject* obj);
// Abstractions that clarify that we are working on a 32-bit memory and check
// that the buffer length does not exceed that's memory's fixed limits.
//
// Once the larger ArrayBuffers are stable these may become accessors on the
// objects themselves: wasmByteLength32() etc.
uint32_t ByteLength32(Handle<ArrayBufferObjectMaybeShared*> buffer);
uint32_t ByteLength32(const ArrayBufferObjectMaybeShared& buffer);
uint32_t ByteLength32(const WasmArrayRawBuffer* buffer);
uint32_t ByteLength32(const ArrayBufferObject& buffer);
uint32_t VolatileByteLength32(const SharedArrayRawBuffer* buffer);
} // namespace wasm } // namespace wasm
// The class of WebAssembly.Module. Each WasmModuleObject owns a // The class of WebAssembly.Module. Each WasmModuleObject owns a
@ -407,21 +401,21 @@ class WasmMemoryObject : public NativeObject {
// `buffer()` returns the current buffer object always. If the buffer // `buffer()` returns the current buffer object always. If the buffer
// represents shared memory then `buffer().byteLength()` never changes, and // represents shared memory then `buffer().byteLength()` never changes, and
// in particular it may be a smaller value than that returned from // in particular it may be a smaller value than that returned from
// `volatileMemoryLength32()` below. // `volatileMemoryLength()` below.
// //
// Generally, you do not want to call `buffer().byteLength()`, but to call // Generally, you do not want to call `buffer().byteLength()`, but to call
// `volatileMemoryLength32()`, instead. // `volatileMemoryLength()`, instead.
ArrayBufferObjectMaybeShared& buffer() const; ArrayBufferObjectMaybeShared& buffer() const;
// The current length of the memory. In the case of shared memory, the // The current length of the memory. In the case of shared memory, the
// length can change at any time. Also note that this will acquire a lock // length can change at any time. Also note that this will acquire a lock
// for shared memory, so do not call this from a signal handler. // for shared memory, so do not call this from a signal handler.
uint32_t volatileMemoryLength32() const; js::BufferSize volatileMemoryLength() const;
bool isShared() const; bool isShared() const;
bool isHuge() const; bool isHuge() const;
bool movingGrowable() const; bool movingGrowable() const;
uint32_t boundsCheckLimit32() const; js::BufferSize boundsCheckLimit() const;
// If isShared() is true then obtain the underlying buffer object. // If isShared() is true then obtain the underlying buffer object.
SharedArrayRawBuffer* sharedArrayRawBuffer() const; SharedArrayRawBuffer* sharedArrayRawBuffer() const;

View File

@ -568,7 +568,7 @@ bool Module::initSegments(JSContext* cx, HandleWasmInstanceObject instanceObj,
} }
if (memoryObj) { if (memoryObj) {
uint32_t memoryLength = memoryObj->volatileMemoryLength32(); uint32_t memoryLength = memoryObj->volatileMemoryLength().getWasmUint32();
uint8_t* memoryBase = uint8_t* memoryBase =
memoryObj->buffer().dataPointerEither().unwrap(/* memcpy */); memoryObj->buffer().dataPointerEither().unwrap(/* memcpy */);
@ -648,8 +648,8 @@ bool Module::instantiateFunctions(JSContext* cx,
template <typename T> template <typename T>
static bool CheckLimits(JSContext* cx, T declaredMin, static bool CheckLimits(JSContext* cx, T declaredMin,
const Maybe<T>& declaredMax, T actualLength, const Maybe<T>& declaredMax, T defaultMax,
const Maybe<T>& actualMax, bool isAsmJS, T actualLength, const Maybe<T>& actualMax, bool isAsmJS,
const char* kind) { const char* kind) {
if (isAsmJS) { if (isAsmJS) {
MOZ_ASSERT(actualLength >= declaredMin); MOZ_ASSERT(actualLength >= declaredMin);
@ -659,7 +659,7 @@ static bool CheckLimits(JSContext* cx, T declaredMin,
} }
if (actualLength < declaredMin || if (actualLength < declaredMin ||
actualLength > declaredMax.valueOr(UINT32_MAX)) { actualLength > declaredMax.valueOr(defaultMax)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_IMP_SIZE, kind); JSMSG_WASM_BAD_IMP_SIZE, kind);
return false; return false;
@ -718,7 +718,9 @@ bool Module::instantiateMemory(JSContext* cx,
MOZ_ASSERT_IF(!metadata().isAsmJS(), memory->buffer().isWasm()); MOZ_ASSERT_IF(!metadata().isAsmJS(), memory->buffer().isWasm());
if (!CheckLimits(cx, declaredMin, declaredMax, if (!CheckLimits(cx, declaredMin, declaredMax,
uint64_t(memory->volatileMemoryLength32()), /* defaultMax */ uint64_t(MaxMemory32Bytes()),
/* actualLength */
uint64_t(memory->volatileMemoryLength().getWasmUint32()),
memory->buffer().wasmMaxSize(), metadata().isAsmJS(), memory->buffer().wasmMaxSize(), metadata().isAsmJS(),
"Memory")) { "Memory")) {
return false; return false;
@ -730,16 +732,15 @@ bool Module::instantiateMemory(JSContext* cx,
} else { } else {
MOZ_ASSERT(!metadata().isAsmJS()); MOZ_ASSERT(!metadata().isAsmJS());
if (declaredMin / PageSize > MaxMemory32Pages) { if (declaredMin > MaxMemory32Bytes()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MEM_IMP_LIMIT); JSMSG_WASM_MEM_IMP_LIMIT);
return false; return false;
} }
RootedArrayBufferObjectMaybeShared buffer(cx); RootedArrayBufferObjectMaybeShared buffer(cx);
Limits l(declaredMin, declaredMax, if (!CreateWasmBuffer32(cx, declaredMin, declaredMax, declaredShared,
declaredShared ? Shareable::True : Shareable::False); &buffer)) {
if (!CreateWasmBuffer(cx, MemoryKind::Memory32, l, &buffer)) {
return false; return false;
} }
@ -846,8 +847,10 @@ bool Module::instantiateImportedTable(JSContext* cx, const TableDesc& td,
MOZ_ASSERT(!metadata().isAsmJS()); MOZ_ASSERT(!metadata().isAsmJS());
Table& table = tableObj->table(); Table& table = tableObj->table();
if (!CheckLimits(cx, td.initialLength, td.maximumLength, table.length(), if (!CheckLimits(cx, td.initialLength, td.maximumLength,
table.maximum(), metadata().isAsmJS(), "Table")) { /* declaredMin */ MaxTableLimitField,
/* actualLength */ table.length(), table.maximum(),
metadata().isAsmJS(), "Table")) {
return false; return false;
} }

View File

@ -62,8 +62,14 @@ static_assert((MaxMemoryAccessSize & (MaxMemoryAccessSize - 1)) == 0,
"MaxMemoryAccessSize is not a power of two"); "MaxMemoryAccessSize is not a power of two");
#if defined(WASM_SUPPORTS_HUGE_MEMORY) #if defined(WASM_SUPPORTS_HUGE_MEMORY)
static_assert(HugeMappedSize > MaxMemory32Bytes, // TODO: We want this static_assert back, but it reqires MaxMemory32Bytes to be
"Normal array buffer could be confused with huge memory"); // a constant or constexpr function, not a regular function as now.
//
// The assert is also present in WasmMemoryObject::isHuge and
// WasmMemoryObject::grow, so it's OK to comment out here for now.
// static_assert(MaxMemory32Bytes < HugeMappedSize(),
// "Normal array buffer could be confused with huge memory");
#endif #endif
Val::Val(const LitVal& val) { Val::Val(const LitVal& val) {

View File

@ -4006,8 +4006,8 @@ static const unsigned MaxMemoryAccessSize = LitVal::sizeofLargestValue();
#ifdef WASM_SUPPORTS_HUGE_MEMORY #ifdef WASM_SUPPORTS_HUGE_MEMORY
// On WASM_SUPPORTS_HUGE_MEMORY platforms, every asm.js or WebAssembly memory // On WASM_SUPPORTS_HUGE_MEMORY platforms, every asm.js or WebAssembly 32-bit
// unconditionally allocates a huge region of virtual memory of size // memory unconditionally allocates a huge region of virtual memory of size
// wasm::HugeMappedSize. This allows all memory resizing to work without // wasm::HugeMappedSize. This allows all memory resizing to work without
// reallocation and provides enough guard space for all offsets to be folded // reallocation and provides enough guard space for all offsets to be folded
// into memory accesses. // into memory accesses.