v8/test/mjsunit/wasm/memory64.js
Clemens Backes 18469ec4bf [wasm] Implement bulk memory operations on memory64
This makes the bulk memory operations respect the memory type, i.e.
using i64 values for memory offsets if memory64 is enabled.

The called C functions now expect memory offsets to be passed as
{uintptr_t}, such that we can address any memory on all systems. For
64-bit memories on 32-bit systems, the upper half of the 64-bit value is
checked in compiled code before passing the lower half to the C
function.

Liftoff support turned out to be a bit harder than expected, because we
cannot hold three 64-bit values in registers on ia32 (not enough
registers...). Thus implement that in a follow-up CL.

R=thibaudm@chromium.org

Bug: v8:10949, chromium:1281995
Change-Id: Ie77636145f94cc579d479c4e7c346ba3c682679d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3427206
Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78918}
2022-02-03 10:35:04 +00:00

223 lines
8.2 KiB
JavaScript

// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --experimental-wasm-memory64
d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
// We use standard JavaScript doubles to represent bytes and offsets. They offer
// enough precision (53 bits) for every allowed memory size.
function BasicMemory64Tests(num_pages) {
const num_bytes = num_pages * kPageSize;
print(`Testing ${num_bytes} bytes (${num_pages} pages)`);
let builder = new WasmModuleBuilder();
builder.addMemory64(num_pages, num_pages, true);
builder.addFunction('load', makeSig([kWasmF64], [kWasmI32]))
.addBody([
kExprLocalGet, 0, // local.get 0
kExprI64UConvertF64, // i64.uconvert_sat.f64
kExprI32LoadMem, 0, 0, // i32.load_mem align=1 offset=0
])
.exportFunc();
builder.addFunction('store', makeSig([kWasmF64, kWasmI32], []))
.addBody([
kExprLocalGet, 0, // local.get 0
kExprI64UConvertF64, // i64.uconvert_sat.f64
kExprLocalGet, 1, // local.get 1
kExprI32StoreMem, 0, 0, // i32.store_mem align=1 offset=0
])
.exportFunc();
let module = builder.instantiate();
let memory = module.exports.memory;
let load = module.exports.load;
let store = module.exports.store;
let array = new Int8Array(memory.buffer);
assertEquals(num_bytes, array.length);
assertEquals(0, load(num_bytes - 4));
assertThrows(() => load(num_bytes - 3));
store(num_bytes - 4, 0x12345678);
assertEquals(0x12345678, load(num_bytes - 4));
let kStoreOffset = 27;
store(kStoreOffset, 11);
assertEquals(11, load(kStoreOffset));
// Now check 100 random positions.
for (let i = 0; i < 100; ++i) {
let position = Math.floor(Math.random() * num_bytes);
let expected = 0;
if (position == kStoreOffset) {
expected = 11;
} else if (num_bytes - position <= 4) {
expected = [0x12, 0x34, 0x56, 0x78][num_bytes - position - 1];
}
assertEquals(expected, array[position]);
}
}
(function TestSmallMemory() {
print(arguments.callee.name);
BasicMemory64Tests(4);
})();
(function Test3GBMemory() {
print(arguments.callee.name);
let num_pages = 3 * 1024 * 1024 * 1024 / kPageSize;
// This test can fail if 3GB of memory cannot be allocated.
try {
BasicMemory64Tests(num_pages);
} catch (e) {
assertInstanceof(e, RangeError);
assertMatches(/Out of memory/, e.message);
}
})();
// TODO(clemensb): Allow for memories >4GB and enable this test.
//(function Test5GBMemory() {
// print(arguments.callee.name);
// let num_pages = 5 * 1024 * 1024 * 1024 / kPageSize;
// BasicMemory64Tests(num_pages);
//})();
(function TestGrow64() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
builder.addMemory64(1, 10, false);
builder.addFunction('grow', makeSig([kWasmI64], [kWasmI64]))
.addBody([
kExprLocalGet, 0, // local.get 0
kExprMemoryGrow, 0, // memory.grow 0
])
.exportFunc();
let instance = builder.instantiate();
assertEquals(1n, instance.exports.grow(2n));
assertEquals(3n, instance.exports.grow(1n));
assertEquals(-1n, instance.exports.grow(-1n));
assertEquals(-1n, instance.exports.grow(1n << 31n));
assertEquals(-1n, instance.exports.grow(1n << 32n));
assertEquals(-1n, instance.exports.grow(1n << 33n));
assertEquals(-1n, instance.exports.grow(1n << 63n));
assertEquals(-1n, instance.exports.grow(7n)); // Above the of 10.
assertEquals(4n, instance.exports.grow(6n)); // Just at the maximum of 10.
})();
(function TestBulkMemoryOperations() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
const kMemSizeInPages = 10;
const kMemSize = kMemSizeInPages * kPageSize;
builder.addMemory64(kMemSizeInPages, kMemSizeInPages);
const kSegmentSize = 1024;
// Build a data segment with values [0, kSegmentSize-1].
const segment = Array.from({length: kSegmentSize}, (_, idx) => idx)
builder.addPassiveDataSegment(segment);
builder.exportMemoryAs('memory');
builder.addFunction('fill', makeSig([kWasmI64, kWasmI32, kWasmI64], []))
.addBody([
kExprLocalGet, 0, // local.get 0 (dst)
kExprLocalGet, 1, // local.get 1 (value)
kExprLocalGet, 2, // local.get 2 (size)
kNumericPrefix, kExprMemoryFill, 0 // memory.fill mem=0
])
.exportFunc();
builder.addFunction('copy', makeSig([kWasmI64, kWasmI64, kWasmI64], []))
.addBody([
kExprLocalGet, 0, // local.get 0 (dst)
kExprLocalGet, 1, // local.get 1 (src)
kExprLocalGet, 2, // local.get 2 (size)
kNumericPrefix, kExprMemoryCopy, 0, 0 // memory.copy srcmem=0 dstmem=0
])
.exportFunc();
builder.addFunction('init', makeSig([kWasmI64, kWasmI32, kWasmI32], []))
.addBody([
kExprLocalGet, 0, // local.get 0 (dst)
kExprLocalGet, 1, // local.get 1 (offset)
kExprLocalGet, 2, // local.get 2 (size)
kNumericPrefix, kExprMemoryInit, 0, 0 // memory.init seg=0 mem=0
])
.exportFunc();
let instance = builder.instantiate();
let fill = instance.exports.fill;
let copy = instance.exports.copy;
let init = instance.exports.init;
// {memory(offset,size)} extracts the memory at [offset, offset+size)] into an
// Array.
let memory = (offset, size) => Array.from(new Uint8Array(
instance.exports.memory.buffer.slice(offset, offset + size)));
// Empty init (size=0).
init(0n, 0, 0);
assertEquals([0, 0], memory(0, 2));
// Init memory[5..7] with [10..12].
init(5n, 10, 3);
assertEquals([0, 0, 10, 11, 12, 0, 0], memory(3, 7));
// Init the end of memory ([kMemSize-2, kMemSize-1]) with [20, 21].
init(BigInt(kMemSize-2), 20, 2);
assertEquals([0, 0, 20, 21], memory(kMemSize - 4, 4));
// Writing slightly OOB.
assertTraps(kTrapMemOutOfBounds, () => init(BigInt(kMemSize-2), 20, 3));
// Writing OOB, but the low 32-bit are in-bound.
assertTraps(kTrapMemOutOfBounds, () => init(1n << 32n, 0, 0));
// OOB even though size == 0.
assertTraps(kTrapMemOutOfBounds, () => init(-1n, 0, 0));
// More OOB.
assertTraps(kTrapMemOutOfBounds, () => init(-1n, 0, 1));
assertTraps(kTrapMemOutOfBounds, () => init(1n << 62n, 0, 1));
assertTraps(kTrapMemOutOfBounds, () => init(1n << 63n, 0, 1));
// Empty copy (size=0).
copy(0n, 0n, 0n);
// Copy memory[5..7] (containing [10..12]) to [3..5].
copy(3n, 5n, 3n);
assertEquals([0, 0, 0, 10, 11, 12, 11, 12, 0], memory(0, 9));
// Copy to the end of memory ([kMemSize-2, kMemSize-1]).
copy(BigInt(kMemSize-2), 3n, 2n);
assertEquals([0, 0, 10, 11], memory(kMemSize - 4, 4));
// Writing slightly OOB.
assertTraps(kTrapMemOutOfBounds, () => copy(BigInt(kMemSize-2), 0n, 3n));
// Writing OOB, but the low 32-bit are in-bound.
assertTraps(kTrapMemOutOfBounds, () => copy(1n << 32n, 0n, 1n));
assertTraps(kTrapMemOutOfBounds, () => copy(0n, 0n, 1n << 32n));
// OOB even though size == 0.
assertTraps(kTrapMemOutOfBounds, () => copy(-1n, 0n, 0n));
// More OOB.
assertTraps(kTrapMemOutOfBounds, () => copy(-1n, 0n, 1n));
assertTraps(kTrapMemOutOfBounds, () => copy(1n << 62n, 0n, 1n));
assertTraps(kTrapMemOutOfBounds, () => copy(1n << 63n, 0n, 1n));
// Empty fill (size=0).
fill(0n, 0, 0n);
// Fill memory[15..17] with 3s.
fill(15n, 3, 3n);
assertEquals([0, 3, 3, 3, 0], memory(14, 5));
// Fill the end of memory ([kMemSize-2, kMemSize-1]) with 7s.
fill(BigInt(kMemSize-2), 7, 2n);
assertEquals([0, 0, 7, 7], memory(kMemSize - 4, 4));
// Writing slightly OOB.
assertTraps(kTrapMemOutOfBounds, () => fill(BigInt(kMemSize-2), 0, 3n));
// Writing OOB, but the low 32-bit are in-bound.
assertTraps(kTrapMemOutOfBounds, () => fill(1n << 32n, 0, 1n));
assertTraps(kTrapMemOutOfBounds, () => fill(0n, 0, 1n << 32n));
// OOB even though size == 0.
assertTraps(kTrapMemOutOfBounds, () => fill(-1n, 0, 0n));
// More OOB.
assertTraps(kTrapMemOutOfBounds, () => fill(-1n, 0, 1n));
assertTraps(kTrapMemOutOfBounds, () => fill(1n << 62n, 0, 1n));
assertTraps(kTrapMemOutOfBounds, () => fill(1n << 63n, 0, 1n));
})();