mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-28 23:31:56 +00:00
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:
parent
85aac3e34e
commit
7d83d7c31e
@ -1467,6 +1467,13 @@ static bool WasmLoadedFromCache(JSContext* cx, unsigned argc, Value* vp) {
|
||||
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) {
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
if (args.length() != 1) {
|
||||
@ -7014,6 +7021,10 @@ gc::ZealModeHelpText),
|
||||
" Returns a boolean indicating whether a given module was deserialized directly from a\n"
|
||||
" 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,
|
||||
"isLazyFunction(fun)",
|
||||
" True if fun is a lazy JSFunction."),
|
||||
|
@ -43,6 +43,7 @@
|
||||
#include "vm/StringType.h" // for NameToId, PropertyName, JSAtom
|
||||
#include "wasm/WasmDebug.h" // for ExprLoc, DebugState
|
||||
#include "wasm/WasmInstance.h" // for Instance
|
||||
#include "wasm/WasmJS.h" // for WasmInstanceObject
|
||||
#include "wasm/WasmTypes.h" // for Bytes
|
||||
|
||||
#include "vm/BytecodeUtil-inl.h" // for BytecodeRangeWithPosition
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "mozilla/MemoryReporting.h" // mozilla::MallocSizeOf
|
||||
#include "mozilla/Range.h" // mozilla::Range
|
||||
#include "mozilla/Span.h" // mozilla::Span
|
||||
#include "mozilla/Unused.h" // mozilla::Unused
|
||||
#include "mozilla/Variant.h" // mozilla::Variant
|
||||
|
||||
#include <stddef.h> // size_t
|
||||
|
@ -421,7 +421,7 @@ static int testWasmFuzz(const uint8_t* buf, size_t size) {
|
||||
if (propObj->is<WasmMemoryObject>()) {
|
||||
Rooted<WasmMemoryObject*> memory(gCx,
|
||||
&propObj->as<WasmMemoryObject>());
|
||||
size_t byteLen = memory->volatileMemoryLength32();
|
||||
size_t byteLen = memory->volatileMemoryLength().getWasmUint32();
|
||||
if (byteLen) {
|
||||
// Read the bounds of the buffer to ensure it is valid.
|
||||
// AddressSanitizer would detect any out-of-bounds here.
|
||||
|
@ -3,6 +3,13 @@ if (!wasmIsSupported())
|
||||
|
||||
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
|
||||
|
||||
function wasmEvalText(str, imports, options) {
|
||||
|
9
js/src/jit-test/tests/asm.js/testLargeHeap.js
Normal file
9
js/src/jit-test/tests/asm.js/testLargeHeap.js
Normal 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);
|
311
js/src/jit-test/tests/wasm/large-memory.js
Normal file
311
js/src/jit-test/tests/wasm/large-memory.js
Normal 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/);
|
||||
}
|
@ -5,9 +5,9 @@
|
||||
//
|
||||
// Tests of limits of memory and table types
|
||||
|
||||
const PageSize = 65536;
|
||||
const PageSize = PageSizeInBytes;
|
||||
const MemoryMaxValid = 65536;
|
||||
const MemoryMaxRuntime = Math.floor(0x7fff_ffff / PageSize);
|
||||
const MemoryMaxRuntime = MaxPagesIn32BitMemory;
|
||||
|
||||
const TableMaxValid = 0xffff_ffff;
|
||||
const TableMaxRuntime = 10_000_000;
|
||||
|
@ -266,7 +266,7 @@ for (var foldOffsets = 0; foldOffsets <= 1; foldOffsets++) {
|
||||
}
|
||||
|
||||
// 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]) {
|
||||
if (align < 2) {
|
||||
testLoadOOB('i32', '8_s', index, offset, align);
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
#include "jsapi-tests/tests.h"
|
||||
|
||||
static const unsigned BufferSize = 20;
|
||||
static const unsigned BufSize = 20;
|
||||
static unsigned FinalizeCalls = 0;
|
||||
static JSFinalizeStatus StatusBuffer[BufferSize];
|
||||
static JSFinalizeStatus StatusBuffer[BufSize];
|
||||
|
||||
BEGIN_TEST(testGCFinalizeCallback) {
|
||||
JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, true);
|
||||
@ -155,13 +155,13 @@ virtual void uninit() override {
|
||||
}
|
||||
|
||||
bool checkSingleGroup() {
|
||||
CHECK(FinalizeCalls < BufferSize);
|
||||
CHECK(FinalizeCalls < BufSize);
|
||||
CHECK(FinalizeCalls == 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool checkMultipleGroups() {
|
||||
CHECK(FinalizeCalls < BufferSize);
|
||||
CHECK(FinalizeCalls < BufSize);
|
||||
CHECK(FinalizeCalls % 3 == 1);
|
||||
CHECK((FinalizeCalls - 1) / 3 > 1);
|
||||
return true;
|
||||
@ -187,7 +187,7 @@ bool checkFinalizeStatus() {
|
||||
|
||||
static void FinalizeCallback(JSFreeOp* fop, JSFinalizeStatus status,
|
||||
void* data) {
|
||||
if (FinalizeCalls < BufferSize) {
|
||||
if (FinalizeCalls < BufSize) {
|
||||
StatusBuffer[FinalizeCalls] = status;
|
||||
}
|
||||
++FinalizeCalls;
|
||||
|
@ -659,13 +659,12 @@ WasmArrayRawBuffer* ArrayBufferObject::BufferContents::wasmBuffer() const {
|
||||
}
|
||||
|
||||
template <typename ObjT, typename RawbufT>
|
||||
static bool CreateSpecificWasmBuffer(
|
||||
JSContext* cx, uint32_t initialSize, const Maybe<uint64_t>& maxSize,
|
||||
wasm::MemoryKind memKind,
|
||||
static bool CreateSpecificWasmBuffer32(
|
||||
JSContext* cx, uint64_t initialSize, const Maybe<uint64_t>& maxSize,
|
||||
MutableHandleArrayBufferObjectMaybeShared maybeSharedObject) {
|
||||
bool useHugeMemory = wasm::IsHugeMemoryEnabled();
|
||||
|
||||
MOZ_RELEASE_ASSERT(memKind == wasm::MemoryKind::Memory32);
|
||||
MOZ_RELEASE_ASSERT(initialSize <= wasm::MaxMemory32Bytes());
|
||||
|
||||
Maybe<uint64_t> clampedMaxSize = maxSize;
|
||||
if (clampedMaxSize) {
|
||||
@ -674,6 +673,11 @@ static bool CreateSpecificWasmBuffer(
|
||||
// clampedMaxSize to a smaller value that satisfies the 32-bit invariants
|
||||
// clampedMaxSize + wasm::PageSize < UINT32_MAX and clampedMaxSize %
|
||||
// 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 &&
|
||||
clampedMaxSize.value() >= (UINT32_MAX - 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));
|
||||
clampedMaxSize = Some(std::min(clamp, *clampedMaxSize));
|
||||
#endif
|
||||
MOZ_RELEASE_ASSERT(initialSize <= clampedMaxSize.value());
|
||||
}
|
||||
|
||||
Maybe<size_t> mappedSize;
|
||||
@ -704,8 +709,8 @@ static bool CreateSpecificWasmBuffer(
|
||||
}
|
||||
#endif
|
||||
|
||||
RawbufT* buffer =
|
||||
RawbufT::Allocate(BufferSize(initialSize), clampedMaxSize, mappedSize);
|
||||
RawbufT* buffer = RawbufT::Allocate(BufferSize(size_t(initialSize)),
|
||||
clampedMaxSize, mappedSize);
|
||||
if (!buffer) {
|
||||
if (useHugeMemory) {
|
||||
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
|
||||
// in the range [initialSize, clampedMaxSize) using log backoff.
|
||||
if (!clampedMaxSize) {
|
||||
wasm::Log(cx, "new Memory({initial=%" PRIu32 " bytes}) failed",
|
||||
wasm::Log(cx, "new Memory({initial=%" PRIu64 " bytes}) failed",
|
||||
initialSize);
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
@ -730,15 +735,15 @@ static bool CreateSpecificWasmBuffer(
|
||||
|
||||
for (; cur > initialSize; cur /= 2) {
|
||||
uint64_t clampedMaxSize = RoundUp(cur, wasm::PageSize);
|
||||
buffer = RawbufT::Allocate(BufferSize(initialSize), Some(clampedMaxSize),
|
||||
mappedSize);
|
||||
buffer = RawbufT::Allocate(BufferSize(size_t(initialSize)),
|
||||
Some(clampedMaxSize), mappedSize);
|
||||
if (buffer) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!buffer) {
|
||||
wasm::Log(cx, "new Memory({initial=%" PRIu32 " bytes}) failed",
|
||||
wasm::Log(cx, "new Memory({initial=%" PRIu64 " bytes}) failed",
|
||||
initialSize);
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
@ -753,7 +758,8 @@ static bool CreateSpecificWasmBuffer(
|
||||
// ObjT::createFromNewRawBuffer assumes ownership of |buffer| even in case
|
||||
// of failure.
|
||||
RootedArrayBufferObjectMaybeShared object(
|
||||
cx, ObjT::createFromNewRawBuffer(cx, buffer, BufferSize(initialSize)));
|
||||
cx, ObjT::createFromNewRawBuffer(cx, buffer,
|
||||
BufferSize(size_t(initialSize))));
|
||||
if (!object) {
|
||||
return false;
|
||||
}
|
||||
@ -778,44 +784,43 @@ static bool CreateSpecificWasmBuffer(
|
||||
if (clampedMaxSize) {
|
||||
if (useHugeMemory) {
|
||||
wasm::Log(cx,
|
||||
"new Memory({initial:%" PRIu32 " bytes, maximum:%" PRIu64
|
||||
"new Memory({initial:%" PRIu64 " bytes, maximum:%" PRIu64
|
||||
" bytes}) succeeded",
|
||||
initialSize, *clampedMaxSize);
|
||||
} else {
|
||||
wasm::Log(cx,
|
||||
"new Memory({initial:%" PRIu32 " bytes, maximum:%" PRIu64
|
||||
"new Memory({initial:%" PRIu64 " bytes, maximum:%" PRIu64
|
||||
" bytes}) succeeded "
|
||||
"with internal maximum of %" PRIu64,
|
||||
initialSize, *clampedMaxSize, object->wasmMaxSize().value());
|
||||
}
|
||||
} else {
|
||||
wasm::Log(cx, "new Memory({initial:%" PRIu32 " bytes}) succeeded",
|
||||
wasm::Log(cx, "new Memory({initial:%" PRIu64 " bytes}) succeeded",
|
||||
initialSize);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool js::CreateWasmBuffer(JSContext* cx, wasm::MemoryKind memKind,
|
||||
const wasm::Limits& memory,
|
||||
MutableHandleArrayBufferObjectMaybeShared buffer) {
|
||||
MOZ_ASSERT(memory.initial % wasm::PageSize == 0);
|
||||
bool js::CreateWasmBuffer32(JSContext* cx, uint64_t initialSize,
|
||||
const Maybe<uint64_t>& maxSize, bool sharedMemory,
|
||||
MutableHandleArrayBufferObjectMaybeShared buffer) {
|
||||
MOZ_ASSERT(initialSize % wasm::PageSize == 0);
|
||||
MOZ_RELEASE_ASSERT(cx->wasm().haveSignalHandlers);
|
||||
MOZ_RELEASE_ASSERT(memory.initial <=
|
||||
ArrayBufferObject::maxBufferByteLength());
|
||||
MOZ_RELEASE_ASSERT(initialSize <= ArrayBufferObject::maxBufferByteLength());
|
||||
|
||||
if (memory.shared == wasm::Shareable::True) {
|
||||
if (sharedMemory) {
|
||||
if (!cx->realm()->creationOptions().getSharedMemoryAndAtomicsEnabled()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WASM_NO_SHMEM_LINK);
|
||||
return false;
|
||||
}
|
||||
return CreateSpecificWasmBuffer<SharedArrayBufferObject,
|
||||
SharedArrayRawBuffer>(
|
||||
cx, uint32_t(memory.initial), memory.maximum, memKind, buffer);
|
||||
return CreateSpecificWasmBuffer32<SharedArrayBufferObject,
|
||||
SharedArrayRawBuffer>(cx, initialSize,
|
||||
maxSize, buffer);
|
||||
}
|
||||
return CreateSpecificWasmBuffer<ArrayBufferObject, WasmArrayRawBuffer>(
|
||||
cx, uint32_t(memory.initial), memory.maximum, memKind, buffer);
|
||||
return CreateSpecificWasmBuffer32<ArrayBufferObject, WasmArrayRawBuffer>(
|
||||
cx, initialSize, maxSize, buffer);
|
||||
}
|
||||
|
||||
bool ArrayBufferObject::prepareForAsmJS() {
|
||||
|
@ -16,10 +16,10 @@
|
||||
#include "gc/ZoneAllocator.h"
|
||||
#include "js/ArrayBuffer.h"
|
||||
#include "js/GCHashTable.h"
|
||||
#include "vm/BufferSize.h"
|
||||
#include "vm/JSObject.h"
|
||||
#include "vm/Runtime.h"
|
||||
#include "vm/SharedMem.h"
|
||||
#include "wasm/WasmTypes.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
@ -109,16 +109,6 @@ mozilla::Maybe<uint64_t> WasmArrayBufferMaxSize(
|
||||
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 {
|
||||
public:
|
||||
inline BufferSize byteLength() const;
|
||||
@ -496,9 +486,13 @@ using RootedArrayBufferObject = Rooted<ArrayBufferObject*>;
|
||||
using HandleArrayBufferObject = Handle<ArrayBufferObject*>;
|
||||
using MutableHandleArrayBufferObject = MutableHandle<ArrayBufferObject*>;
|
||||
|
||||
bool CreateWasmBuffer(JSContext* cx, wasm::MemoryKind memKind,
|
||||
const wasm::Limits& memory,
|
||||
MutableHandleArrayBufferObjectMaybeShared buffer);
|
||||
// Create a buffer for a 32-bit wasm memory. Arguments of the Limits structure
|
||||
// are broken out in order to avoid having this file depending on WasmTypes.h,
|
||||
// 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
|
||||
// and the views that use their storage.
|
||||
|
29
js/src/vm/BufferSize.h
Normal file
29
js/src/vm/BufferSize.h
Normal 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
|
@ -6752,7 +6752,7 @@ static bool CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata,
|
||||
buffer.set(&bufferObj->as<ArrayBufferObject>());
|
||||
|
||||
// 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();
|
||||
|
||||
if (!IsValidAsmJSHeapLength(memoryLength)) {
|
||||
@ -6785,6 +6785,21 @@ static bool CheckBuffer(JSContext* cx, const AsmJSMetadata& metadata,
|
||||
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()) {
|
||||
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.
|
||||
if (length > MaxMemory32Bytes) {
|
||||
if (length > MaxMemory32Bytes()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1020,15 +1020,6 @@ static const unsigned MaxParams = 1000;
|
||||
static const unsigned MaxResults = 1000;
|
||||
static const unsigned MaxStructFields = 1000;
|
||||
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 MaxModuleBytes = 1024 * 1024 * 1024;
|
||||
static const unsigned MaxFunctionBytes = 7654321;
|
||||
|
@ -411,6 +411,8 @@ TypeCode env_elem_typecode(const CraneliftModuleEnvironment* env,
|
||||
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) {
|
||||
// env.maxMemoryLength is in bytes. Convert it to wasm pages.
|
||||
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 & wasm::PageMask) == 0);
|
||||
return (uint32_t)(inBytes >> wasm::PageBits);
|
||||
} else {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
bool env_uses_shared_memory(const CraneliftModuleEnvironment* env) {
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "jit/ExecutableAllocator.h"
|
||||
#include "jit/MacroAssembler.h"
|
||||
#include "wasm/WasmInstance.h"
|
||||
#include "wasm/WasmJS.h"
|
||||
#include "wasm/WasmStubs.h"
|
||||
#include "wasm/WasmValidate.h"
|
||||
|
||||
|
@ -350,7 +350,8 @@ Instance::callImport_general(Instance* instance, int32_t funcImportIndex,
|
||||
// write tests for cross-realm calls.
|
||||
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);
|
||||
return byteLength / wasm::PageSize;
|
||||
}
|
||||
@ -372,7 +373,8 @@ static int32_t PerformWait(Instance* instance, uint32_t byteOffset, T value,
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (byteOffset + sizeof(T) > instance->memory()->volatileMemoryLength32()) {
|
||||
if (byteOffset + sizeof(T) >
|
||||
instance->memory()->volatileMemoryLength().getWasmUint32()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WASM_OUT_OF_BOUNDS);
|
||||
return -1;
|
||||
@ -427,7 +429,8 @@ static int32_t PerformWait(Instance* instance, uint32_t byteOffset, T value,
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (byteOffset >= instance->memory()->volatileMemoryLength32()) {
|
||||
if (byteOffset >=
|
||||
instance->memory()->volatileMemoryLength().getWasmUint32()) {
|
||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WASM_OUT_OF_BOUNDS);
|
||||
return -1;
|
||||
@ -475,7 +478,7 @@ inline int32_t WasmMemoryCopy(T memBase, uint32_t memLen,
|
||||
MOZ_ASSERT(SASigMemCopy.failureMode == FailureMode::FailOnNegI32);
|
||||
|
||||
const WasmArrayRawBuffer* rawBuf = WasmArrayRawBuffer::fromDataPtr(memBase);
|
||||
uint32_t memLen = ByteLength32(rawBuf);
|
||||
uint32_t memLen = rawBuf->byteLength().getWasmUint32();
|
||||
|
||||
return WasmMemoryCopy(memBase, memLen, dstByteOffset, srcByteOffset, len,
|
||||
memmove);
|
||||
@ -492,7 +495,7 @@ inline int32_t WasmMemoryCopy(T memBase, uint32_t memLen,
|
||||
|
||||
const SharedArrayRawBuffer* rawBuf =
|
||||
SharedArrayRawBuffer::fromDataPtr(memBase);
|
||||
uint32_t memLen = VolatileByteLength32(rawBuf);
|
||||
uint32_t memLen = rawBuf->volatileByteLength().getWasmUint32();
|
||||
|
||||
return WasmMemoryCopy<SharedMem<uint8_t*>, RacyMemMove>(
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
@ -555,7 +558,7 @@ inline int32_t WasmMemoryFill(T memBase, uint32_t memLen, uint32_t byteOffset,
|
||||
|
||||
const SharedArrayRawBuffer* rawBuf =
|
||||
SharedArrayRawBuffer::fromDataPtr(memBase);
|
||||
uint32_t memLen = VolatileByteLength32(rawBuf);
|
||||
uint32_t memLen = rawBuf->volatileByteLength().getWasmUint32();
|
||||
|
||||
return WasmMemoryFill(SharedMem<uint8_t*>::shared(memBase), memLen,
|
||||
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();
|
||||
|
||||
WasmMemoryObject* mem = instance->memory();
|
||||
const uint32_t memLen = mem->volatileMemoryLength32();
|
||||
const uint32_t memLen = mem->volatileMemoryLength().getWasmUint32();
|
||||
|
||||
// We are proposing to copy
|
||||
//
|
||||
@ -1210,7 +1213,8 @@ bool Instance::init(JSContext* cx, const JSFunctionVector& funcImports,
|
||||
|
||||
tlsData()->memoryBase =
|
||||
memory_ ? memory_->buffer().dataPointerEither().unwrap() : nullptr;
|
||||
tlsData()->boundsCheckLimit32 = memory_ ? memory_->boundsCheckLimit32() : 0;
|
||||
tlsData()->boundsCheckLimit32 =
|
||||
memory_ ? memory_->boundsCheckLimit().getWasmUint32() : 0;
|
||||
tlsData()->instance = this;
|
||||
tlsData()->realm = realm_;
|
||||
tlsData()->cx = cx;
|
||||
@ -1456,7 +1460,7 @@ bool Instance::memoryAccessInGuardRegion(uint8_t* addr,
|
||||
}
|
||||
|
||||
size_t lastByteOffset = addr - base + (numBytes - 1);
|
||||
return lastByteOffset >= memory()->volatileMemoryLength32() &&
|
||||
return lastByteOffset >= memory()->volatileMemoryLength().getWasmUint32() &&
|
||||
lastByteOffset < memoryMappedSize();
|
||||
}
|
||||
|
||||
@ -1472,7 +1476,7 @@ bool Instance::memoryAccessInBounds(uint8_t* addr, unsigned numBytes) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t length = memory()->volatileMemoryLength32();
|
||||
uint32_t length = memory()->volatileMemoryLength().getWasmUint32();
|
||||
if (addr >= base + length) {
|
||||
return false;
|
||||
}
|
||||
@ -2063,7 +2067,7 @@ void Instance::onMovingGrowMemory() {
|
||||
|
||||
ArrayBufferObject& buffer = memory_->buffer().as<ArrayBufferObject>();
|
||||
tlsData()->memoryBase = buffer.dataPointer();
|
||||
tlsData()->boundsCheckLimit32 = memory_->boundsCheckLimit32();
|
||||
tlsData()->boundsCheckLimit32 = memory_->boundsCheckLimit().getWasmUint32();
|
||||
}
|
||||
|
||||
void Instance::onMovingGrowTable(const Table* theTable) {
|
||||
|
@ -520,39 +520,6 @@ bool wasm::CodeCachingAvailable(JSContext* 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
|
||||
|
||||
@ -2466,7 +2433,7 @@ bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (limits.initial > MaxMemory32Pages) {
|
||||
if (limits.initial > MaxMemory32Pages()) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WASM_MEM_IMP_LIMIT);
|
||||
return false;
|
||||
@ -2475,7 +2442,8 @@ bool WasmMemoryObject::construct(JSContext* cx, unsigned argc, Value* vp) {
|
||||
ConvertMemoryPagesToBytes(&limits);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -2509,10 +2477,10 @@ bool WasmMemoryObject::bufferGetterImpl(JSContext* cx, const CallArgs& args) {
|
||||
RootedArrayBufferObjectMaybeShared buffer(cx, &memoryObj->buffer());
|
||||
|
||||
if (memoryObj->isShared()) {
|
||||
uint32_t memoryLength = memoryObj->volatileMemoryLength32();
|
||||
MOZ_ASSERT(memoryLength >= ByteLength32(buffer));
|
||||
uint32_t memoryLength = memoryObj->volatileMemoryLength().getWasmUint32();
|
||||
MOZ_ASSERT(memoryLength >= buffer->byteLength().getWasmUint32());
|
||||
|
||||
if (memoryLength > ByteLength32(buffer)) {
|
||||
if (memoryLength > buffer->byteLength().getWasmUint32()) {
|
||||
RootedSharedArrayBufferObject newBuffer(
|
||||
cx,
|
||||
SharedArrayBufferObject::New(cx, memoryObj->sharedArrayRawBuffer(),
|
||||
@ -2614,7 +2582,7 @@ bool WasmMemoryObject::typeImpl(JSContext* cx, const CallArgs& args) {
|
||||
}
|
||||
|
||||
uint32_t minimumPages = mozilla::AssertedCast<uint32_t>(
|
||||
memoryObj->volatileMemoryLength32() / wasm::PageSize);
|
||||
memoryObj->volatileMemoryLength().getWasmUint32() / wasm::PageSize);
|
||||
if (!props.append(IdValuePair(NameToId(cx->names().minimum),
|
||||
Int32Value(minimumPages)))) {
|
||||
return false;
|
||||
@ -2640,11 +2608,11 @@ bool WasmMemoryObject::type(JSContext* cx, unsigned argc, Value* vp) {
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t WasmMemoryObject::volatileMemoryLength32() const {
|
||||
BufferSize WasmMemoryObject::volatileMemoryLength() const {
|
||||
if (isShared()) {
|
||||
return VolatileByteLength32(sharedArrayRawBuffer());
|
||||
return sharedArrayRawBuffer()->volatileByteLength();
|
||||
}
|
||||
return ByteLength32(buffer());
|
||||
return buffer().byteLength();
|
||||
}
|
||||
|
||||
bool WasmMemoryObject::isShared() const {
|
||||
@ -2679,8 +2647,10 @@ WasmMemoryObject::InstanceSet* WasmMemoryObject::getOrCreateObservers(
|
||||
|
||||
bool WasmMemoryObject::isHuge() const {
|
||||
#ifdef WASM_SUPPORTS_HUGE_MEMORY
|
||||
static_assert(MaxMemory32Bytes < HugeMappedSize,
|
||||
"Non-huge buffer may be confused as huge");
|
||||
// TODO: Turn this into a static_assert, if we are able to make
|
||||
// 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;
|
||||
#else
|
||||
return false;
|
||||
@ -2691,15 +2661,17 @@ bool WasmMemoryObject::movingGrowable() const {
|
||||
return !isHuge() && !buffer().wasmMaxSize();
|
||||
}
|
||||
|
||||
uint32_t WasmMemoryObject::boundsCheckLimit32() const {
|
||||
BufferSize WasmMemoryObject::boundsCheckLimit() const {
|
||||
if (!buffer().isWasm() || isHuge()) {
|
||||
return ByteLength32(buffer());
|
||||
return buffer().byteLength();
|
||||
}
|
||||
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(wasm::IsValidBoundsCheckImmediate(mappedSize - wasm::GuardSize));
|
||||
return mappedSize - wasm::GuardSize;
|
||||
return BufferSize(mappedSize - wasm::GuardSize);
|
||||
}
|
||||
|
||||
bool WasmMemoryObject::addMovingGrowObserver(JSContext* cx,
|
||||
@ -2725,8 +2697,9 @@ uint32_t WasmMemoryObject::growShared(HandleWasmMemoryObject memory,
|
||||
SharedArrayRawBuffer* rawBuf = memory->sharedArrayRawBuffer();
|
||||
SharedArrayRawBuffer::Lock lock(rawBuf);
|
||||
|
||||
MOZ_ASSERT(VolatileByteLength32(rawBuf) % PageSize == 0);
|
||||
uint32_t oldNumPages = VolatileByteLength32(rawBuf) / PageSize;
|
||||
MOZ_ASSERT(rawBuf->volatileByteLength().getWasmUint32() % PageSize == 0);
|
||||
uint32_t oldNumPages =
|
||||
rawBuf->volatileByteLength().getWasmUint32() / PageSize;
|
||||
|
||||
CheckedInt<uint32_t> newSize = oldNumPages;
|
||||
newSize += delta;
|
||||
@ -2735,6 +2708,12 @@ uint32_t WasmMemoryObject::growShared(HandleWasmMemoryObject memory,
|
||||
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()) {
|
||||
return -1;
|
||||
}
|
||||
@ -2758,15 +2737,16 @@ uint32_t WasmMemoryObject::grow(HandleWasmMemoryObject memory, uint32_t delta,
|
||||
|
||||
RootedArrayBufferObject oldBuf(cx, &memory->buffer().as<ArrayBufferObject>());
|
||||
|
||||
MOZ_ASSERT(ByteLength32(oldBuf) % PageSize == 0);
|
||||
uint32_t oldNumPages = ByteLength32(oldBuf) / PageSize;
|
||||
MOZ_ASSERT(oldBuf->byteLength().getWasmUint32() % PageSize == 0);
|
||||
uint32_t oldNumPages = oldBuf->byteLength().getWasmUint32() / PageSize;
|
||||
|
||||
// FIXME (large ArrayBuffer): This does not allow 65536 pages, which is
|
||||
// technically the max. That may be a webcompat problem. We can fix this
|
||||
// once wasmMovingGrowToSize and wasmGrowToSizeInPlace accept size_t rather
|
||||
// than uint32_t. See the FIXME in WasmConstants.h for additional
|
||||
// information.
|
||||
static_assert(MaxMemory32Pages <= UINT32_MAX / PageSize, "Avoid overflows");
|
||||
// TODO (large ArrayBuffer): This does not allow 65536 pages. See more
|
||||
// information at the definition of MaxMemory32Bytes().
|
||||
//
|
||||
// TODO: Turn this into a static_assert, if we are able to make
|
||||
// MaxMemory32Bytes() constexpr once the dust settles for the 4GB heaps.
|
||||
MOZ_ASSERT(MaxMemory32Pages() <= UINT32_MAX / PageSize,
|
||||
"Avoid 32-bit overflows");
|
||||
|
||||
CheckedInt<uint32_t> newSize = oldNumPages;
|
||||
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
|
||||
// use the correct limit, they don't have enough context.
|
||||
if (newSize.value() > MaxMemory32Pages * PageSize) {
|
||||
if (newSize.value() > MaxMemory32Bytes()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -4880,3 +4860,27 @@ static const ClassSpec WebAssemblyClassSpec = {CreateWebAssemblyObject,
|
||||
const JSClass js::WasmNamespaceObject::class_ = {
|
||||
js_WebAssembly_str, JSCLASS_HAS_CACHED_PROTO(JSProto_WebAssembly),
|
||||
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
|
||||
|
@ -34,7 +34,8 @@
|
||||
#include "js/RootingAPI.h" // MovableCellHasher
|
||||
#include "js/SweepingAPI.h" // JS::WeakCache
|
||||
#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/NativeObject.h" // NativeObject
|
||||
#include "wasm/WasmTypes.h" // MutableHandleWasmInstanceObject, wasm::*
|
||||
@ -56,10 +57,8 @@ class ArrayBufferObjectMaybeShared;
|
||||
class JSStringBuilder;
|
||||
class SharedArrayRawBuffer;
|
||||
class TypedArrayObject;
|
||||
class WasmArrayRawBuffer;
|
||||
class WasmFunctionScope;
|
||||
class WasmInstanceScope;
|
||||
class SharedArrayRawBuffer;
|
||||
|
||||
namespace wasm {
|
||||
|
||||
@ -162,6 +161,12 @@ void ReportSimdAnalysis(const char* data);
|
||||
// options can support try/catch, throw, rethrow, and branch_on_exn (evolving).
|
||||
bool ExceptionsAvailable(JSContext* cx);
|
||||
|
||||
size_t MaxMemory32Pages();
|
||||
|
||||
static inline size_t MaxMemory32Bytes() {
|
||||
return MaxMemory32Pages() * PageSize;
|
||||
}
|
||||
|
||||
// Compiles the given binary wasm module given the ArrayBufferObject
|
||||
// and links the module's imports with the given import object.
|
||||
|
||||
@ -204,17 +209,6 @@ uint32_t ExportedFunctionToFuncIndex(JSFunction* fun);
|
||||
|
||||
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
|
||||
|
||||
// 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
|
||||
// represents shared memory then `buffer().byteLength()` never changes, and
|
||||
// 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
|
||||
// `volatileMemoryLength32()`, instead.
|
||||
// `volatileMemoryLength()`, instead.
|
||||
ArrayBufferObjectMaybeShared& buffer() const;
|
||||
|
||||
// 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
|
||||
// for shared memory, so do not call this from a signal handler.
|
||||
uint32_t volatileMemoryLength32() const;
|
||||
js::BufferSize volatileMemoryLength() const;
|
||||
|
||||
bool isShared() const;
|
||||
bool isHuge() const;
|
||||
bool movingGrowable() const;
|
||||
uint32_t boundsCheckLimit32() const;
|
||||
js::BufferSize boundsCheckLimit() const;
|
||||
|
||||
// If isShared() is true then obtain the underlying buffer object.
|
||||
SharedArrayRawBuffer* sharedArrayRawBuffer() const;
|
||||
|
@ -568,7 +568,7 @@ bool Module::initSegments(JSContext* cx, HandleWasmInstanceObject instanceObj,
|
||||
}
|
||||
|
||||
if (memoryObj) {
|
||||
uint32_t memoryLength = memoryObj->volatileMemoryLength32();
|
||||
uint32_t memoryLength = memoryObj->volatileMemoryLength().getWasmUint32();
|
||||
uint8_t* memoryBase =
|
||||
memoryObj->buffer().dataPointerEither().unwrap(/* memcpy */);
|
||||
|
||||
@ -648,8 +648,8 @@ bool Module::instantiateFunctions(JSContext* cx,
|
||||
|
||||
template <typename T>
|
||||
static bool CheckLimits(JSContext* cx, T declaredMin,
|
||||
const Maybe<T>& declaredMax, T actualLength,
|
||||
const Maybe<T>& actualMax, bool isAsmJS,
|
||||
const Maybe<T>& declaredMax, T defaultMax,
|
||||
T actualLength, const Maybe<T>& actualMax, bool isAsmJS,
|
||||
const char* kind) {
|
||||
if (isAsmJS) {
|
||||
MOZ_ASSERT(actualLength >= declaredMin);
|
||||
@ -659,7 +659,7 @@ static bool CheckLimits(JSContext* cx, T declaredMin,
|
||||
}
|
||||
|
||||
if (actualLength < declaredMin ||
|
||||
actualLength > declaredMax.valueOr(UINT32_MAX)) {
|
||||
actualLength > declaredMax.valueOr(defaultMax)) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WASM_BAD_IMP_SIZE, kind);
|
||||
return false;
|
||||
@ -718,7 +718,9 @@ bool Module::instantiateMemory(JSContext* cx,
|
||||
MOZ_ASSERT_IF(!metadata().isAsmJS(), memory->buffer().isWasm());
|
||||
|
||||
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")) {
|
||||
return false;
|
||||
@ -730,16 +732,15 @@ bool Module::instantiateMemory(JSContext* cx,
|
||||
} else {
|
||||
MOZ_ASSERT(!metadata().isAsmJS());
|
||||
|
||||
if (declaredMin / PageSize > MaxMemory32Pages) {
|
||||
if (declaredMin > MaxMemory32Bytes()) {
|
||||
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
|
||||
JSMSG_WASM_MEM_IMP_LIMIT);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedArrayBufferObjectMaybeShared buffer(cx);
|
||||
Limits l(declaredMin, declaredMax,
|
||||
declaredShared ? Shareable::True : Shareable::False);
|
||||
if (!CreateWasmBuffer(cx, MemoryKind::Memory32, l, &buffer)) {
|
||||
if (!CreateWasmBuffer32(cx, declaredMin, declaredMax, declaredShared,
|
||||
&buffer)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -846,8 +847,10 @@ bool Module::instantiateImportedTable(JSContext* cx, const TableDesc& td,
|
||||
MOZ_ASSERT(!metadata().isAsmJS());
|
||||
|
||||
Table& table = tableObj->table();
|
||||
if (!CheckLimits(cx, td.initialLength, td.maximumLength, table.length(),
|
||||
table.maximum(), metadata().isAsmJS(), "Table")) {
|
||||
if (!CheckLimits(cx, td.initialLength, td.maximumLength,
|
||||
/* declaredMin */ MaxTableLimitField,
|
||||
/* actualLength */ table.length(), table.maximum(),
|
||||
metadata().isAsmJS(), "Table")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -62,8 +62,14 @@ static_assert((MaxMemoryAccessSize & (MaxMemoryAccessSize - 1)) == 0,
|
||||
"MaxMemoryAccessSize is not a power of two");
|
||||
|
||||
#if defined(WASM_SUPPORTS_HUGE_MEMORY)
|
||||
static_assert(HugeMappedSize > MaxMemory32Bytes,
|
||||
"Normal array buffer could be confused with huge memory");
|
||||
// TODO: We want this static_assert back, but it reqires MaxMemory32Bytes to be
|
||||
// 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
|
||||
|
||||
Val::Val(const LitVal& val) {
|
||||
|
@ -4006,8 +4006,8 @@ static const unsigned MaxMemoryAccessSize = LitVal::sizeofLargestValue();
|
||||
|
||||
#ifdef WASM_SUPPORTS_HUGE_MEMORY
|
||||
|
||||
// On WASM_SUPPORTS_HUGE_MEMORY platforms, every asm.js or WebAssembly memory
|
||||
// unconditionally allocates a huge region of virtual memory of size
|
||||
// On WASM_SUPPORTS_HUGE_MEMORY platforms, every asm.js or WebAssembly 32-bit
|
||||
// memory unconditionally allocates a huge region of virtual memory of size
|
||||
// wasm::HugeMappedSize. This allows all memory resizing to work without
|
||||
// reallocation and provides enough guard space for all offsets to be folded
|
||||
// into memory accesses.
|
||||
|
Loading…
Reference in New Issue
Block a user