blob: 2984b1a6b6d23370e8713f92f2794300886680f9 [file] [log] [blame]
// Copyright 2016 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: --expose-wasm --stress-compaction
load("test/mjsunit/wasm/wasm-module-builder.js");
function genMemoryGrowBuilder() {
var builder = new WasmModuleBuilder();
builder.addFunction("grow_memory", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprMemoryGrow, kMemoryZero])
.exportFunc();
builder.addFunction("load", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32LoadMem, 0, 0])
.exportFunc();
builder.addFunction("store", kSig_i_ii)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32StoreMem, 0, 0,
kExprLocalGet, 1])
.exportFunc();
builder.addFunction("load16", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32LoadMem16U, 0, 0])
.exportFunc();
builder.addFunction("store16", kSig_i_ii)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32StoreMem16, 0, 0,
kExprLocalGet, 1])
.exportFunc();
builder.addFunction("load8", kSig_i_i)
.addBody([kExprLocalGet, 0, kExprI32LoadMem8U, 0, 0])
.exportFunc();
builder.addFunction("store8", kSig_i_ii)
.addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32StoreMem8, 0, 0,
kExprLocalGet, 1])
.exportFunc();
return builder;
}
// V8 internal memory size limit.
var kV8MaxPages = 65536;
function testMemoryGrowReadWriteBase(size, load_fn, store_fn) {
// size is the number of bytes for load and stores.
var builder = genMemoryGrowBuilder();
builder.addMemory(1, undefined, false);
var module = builder.instantiate();
var offset;
var load = module.exports[load_fn];
var store = module.exports[store_fn];
function peek() { return load(offset); }
function poke(value) { return store(offset, value); }
function growMem(pages) { return module.exports.grow_memory(pages); }
// Instead of checking every n-th offset, check the first 5.
for(offset = 0; offset <= (4*size); offset+=size) {
poke(20);
assertEquals(20, peek());
}
for (offset = kPageSize - (size - 1); offset < kPageSize + size; offset++) {
assertTraps(kTrapMemOutOfBounds, poke);
assertTraps(kTrapMemOutOfBounds, peek);
}
assertEquals(1, growMem(3));
for (let n = 1; n <= 3; n++) {
for (offset = n * kPageSize - 5 * size; offset <= n * kPageSize + 4 * size;
offset += size) {
// Check the 5 offsets to the before and after the n-th page.
// page n-1 page n page n+1
// +---- ... ------------+---------- ... +------ ...
// | | | ... | | | | | | | | | | | | ... | | | | ...
// <+> ^ ^
// | first offset last offset
// +-> size bytes
poke(20);
assertEquals(20, peek());
}
}
// Check the last 5 valid offsets of the last page.
for (offset = 4*kPageSize-size-(4*size); offset <= 4*kPageSize -size; offset+=size) {
poke(20);
assertEquals(20, peek());
}
for (offset = 4*kPageSize - (size-1); offset < 4*kPageSize + size; offset++) {
assertTraps(kTrapMemOutOfBounds, poke);
assertTraps(kTrapMemOutOfBounds, peek);
}
assertEquals(4, growMem(15));
for (offset = 4*kPageSize - (size-1); offset <= 4*kPageSize + size; offset+=size) {
poke(20);
assertEquals(20, peek());
}
for (offset = 19*kPageSize - 10; offset <= 19*kPageSize - size; offset+=size) {
poke(20);
assertEquals(20, peek());
}
for (offset = 19*kPageSize - (size-1); offset < 19*kPageSize + 5; offset++) {
assertTraps(kTrapMemOutOfBounds, poke);
assertTraps(kTrapMemOutOfBounds, peek);
}
}
(function testMemoryGrowReadWrite32() {
testMemoryGrowReadWriteBase(4, "load", "store");
})();
(function testMemoryGrowReadWrite16() {
testMemoryGrowReadWriteBase(2, "load16", "store16");
})();
(function testMemoryGrowReadWrite8() {
testMemoryGrowReadWriteBase(1, "load8", "store8");
})();
function testMemoryGrowZeroInitialSize() {
var builder = genMemoryGrowBuilder();
builder.addMemory(0, undefined, false);
var module = builder.instantiate();
var offset;
function peek() { return module.exports.load(offset); }
function poke(value) { return module.exports.store(offset, value); }
function growMem(pages) { return module.exports.grow_memory(pages); }
assertTraps(kTrapMemOutOfBounds, peek);
assertTraps(kTrapMemOutOfBounds, poke);
assertEquals(0, growMem(1));
// Check first 5 offsets.
for(offset = 0; offset <= 5; offset++) {
poke(20);
assertEquals(20, peek());
}
// Check last 5 offsets.
for(offset = kPageSize - 5*4; offset <= kPageSize - 4; offset++) {
poke(20);
assertEquals(20, peek());
}
for(offset = kPageSize - 3; offset <= kPageSize + 5; offset++) {
assertTraps(kTrapMemOutOfBounds, peek);
}
offset = 3*kPageSize;
for (var i = 1; i < 4; i++) {
assertTraps(kTrapMemOutOfBounds, poke);
assertEquals(i, growMem(1));
}
poke(20);
assertEquals(20, peek());
}
testMemoryGrowZeroInitialSize();
function testMemoryGrowZeroInitialSizeBase(size, load_fn, store_fn) {
var builder = genMemoryGrowBuilder();
builder.addMemory(0, undefined, false);
var module = builder.instantiate();
var offset;
var load = module.exports[load_fn];
var store = module.exports[store_fn];
function peek() { return load(offset); }
function poke(value) { return store(offset, value); }
function growMem(pages) { return module.exports.grow_memory(pages); }
assertTraps(kTrapMemOutOfBounds, peek);
assertTraps(kTrapMemOutOfBounds, poke);
assertEquals(0, growMem(1));
// Instead of checking every offset, check the first 5.
for(offset = 0; offset <= 4; offset++) {
poke(20);
assertEquals(20, peek());
}
// Check the last 5 valid ones.
for(offset = kPageSize - (size * 4); offset <= kPageSize - size; offset++) {
poke(20);
assertEquals(20, peek());
}
for(offset = kPageSize - (size - 1); offset <= kPageSize + 5; offset++) {
assertTraps(kTrapMemOutOfBounds, peek);
}
}
(function testMemoryGrowZeroInitialSize32() {
testMemoryGrowZeroInitialSizeBase(4, "load", "store");
})();
(function testMemoryGrowZeroInitialSize16() {
testMemoryGrowZeroInitialSizeBase(2, "load16", "store16");
})();
(function testMemoryGrowZeroInitialSize8() {
testMemoryGrowZeroInitialSizeBase(1, "load8", "store8");
})();
function testMemoryGrowTrapMaxPagesZeroInitialMemory() {
var builder = genMemoryGrowBuilder();
builder.addMemory(0, undefined, false);
var module = builder.instantiate();
function growMem(pages) { return module.exports.grow_memory(pages); }
assertEquals(-1, growMem(kV8MaxPages + 1));
}
testMemoryGrowTrapMaxPagesZeroInitialMemory();
function testMemoryGrowTrapMaxPages() {
var builder = genMemoryGrowBuilder();
builder.addMemory(1, 1, false);
var module = builder.instantiate();
function growMem(pages) { return module.exports.grow_memory(pages); }
assertEquals(-1, growMem(kV8MaxPages));
}
testMemoryGrowTrapMaxPages();
function testMemoryGrowTrapsWithNonSmiInput() {
var builder = genMemoryGrowBuilder();
builder.addMemory(0, undefined, false);
var module = builder.instantiate();
function growMem(pages) { return module.exports.grow_memory(pages); }
// The parameter of grow_memory is unsigned. Therefore -1 stands for
// UINT32_MIN, which cannot be represented as SMI.
assertEquals(-1, growMem(-1));
};
testMemoryGrowTrapsWithNonSmiInput();
function testMemoryGrowCurrentMemory() {
var builder = genMemoryGrowBuilder();
builder.addMemory(1, undefined, false);
builder.addFunction("memory_size", kSig_i_v)
.addBody([kExprMemorySize, kMemoryZero])
.exportFunc();
var module = builder.instantiate();
function growMem(pages) { return module.exports.grow_memory(pages); }
function MemSize() { return module.exports.memory_size(); }
assertEquals(1, MemSize());
assertEquals(1, growMem(1));
assertEquals(2, MemSize());
}
testMemoryGrowCurrentMemory();
function testMemoryGrowPreservesDataMemOpBase(size, load_fn, store_fn) {
var builder = genMemoryGrowBuilder();
builder.addMemory(1, undefined, false);
var module = builder.instantiate();
var offset;
var load = module.exports[load_fn];
var store = module.exports[store_fn];
function peek() { return load(offset); }
function poke(value) { return store(offset, value); }
function growMem(pages) { return module.exports.grow_memory(pages); }
// Maximum unsigned integer of size bits.
const max = Math.pow(2, (size * 8)) - 1;
// Check the first 5 offsets.
for(offset = 0; offset <= (4*size); offset+=size) {
poke(offset % max);
assertEquals(offset % max, peek());
}
// Check the last 5 valid offsets.
for(offset = kPageSize - 5*size; offset <= (kPageSize - size); offset+=size) {
poke(offset % max);
assertEquals(offset % max, peek());
}
assertEquals(1, growMem(3));
// Check the first 5 offsets are preserved by growMem.
for(offset = 0; offset <= (4*size); offset+=size) {
assertEquals(offset % max, peek());
}
// Check the last 5 valid offsets are preserved by growMem.
for(offset = kPageSize - 5*size; offset <= (kPageSize - size); offset+=size) {
assertEquals(offset % max, peek());
}
}
(function testMemoryGrowPreservesDataMemOp32() {
testMemoryGrowPreservesDataMemOpBase(4, "load", "store");
})();
(function testMemoryGrowPreservesDataMemOp16() {
testMemoryGrowPreservesDataMemOpBase(2, "load16", "store16");
})();
(function testMemoryGrowPreservesDataMemOp8() {
testMemoryGrowPreservesDataMemOpBase(1, "load8", "store8");
})();
function testMemoryGrowOutOfBoundsOffset() {
var builder = genMemoryGrowBuilder();
builder.addMemory(1, undefined, false);
var module = builder.instantiate();
var offset, val;
function peek() { return module.exports.load(offset); }
function poke(value) { return module.exports.store(offset, value); }
function growMem(pages) { return module.exports.grow_memory(pages); }
offset = 3*kPageSize + 4;
assertTraps(kTrapMemOutOfBounds, poke);
assertEquals(1, growMem(1));
assertTraps(kTrapMemOutOfBounds, poke);
assertEquals(2, growMem(1));
assertTraps(kTrapMemOutOfBounds, poke);
assertEquals(3, growMem(1));
for (offset = 3*kPageSize; offset <= 3*kPageSize + 4; offset++) {
poke(0xaced);
assertEquals(0xaced, peek());
}
for (offset = 4*kPageSize-8; offset <= 4*kPageSize - 4; offset++) {
poke(0xaced);
assertEquals(0xaced, peek());
}
for (offset = 4*kPageSize - 3; offset <= 4*kPageSize + 4; offset++) {
assertTraps(kTrapMemOutOfBounds, poke);
}
}
testMemoryGrowOutOfBoundsOffset();
function testMemoryGrowOutOfBoundsOffset2() {
var builder = new WasmModuleBuilder();
builder.addMemory(16, 128, false);
builder.addFunction("main", kSig_v_v)
.addBody([
kExprI32Const, 20,
kExprI32Const, 29,
kExprMemoryGrow, kMemoryZero,
kExprI32StoreMem, 0, 0xFF, 0xFF, 0xFF, 0x3a
])
.exportAs("main");
var module = builder.instantiate();
assertTraps(kTrapMemOutOfBounds, module.exports.main);
}
testMemoryGrowOutOfBoundsOffset2();
function testMemoryGrowDeclaredMaxTraps() {
var builder = genMemoryGrowBuilder();
builder.addMemory(1, 16, false);
var module = builder.instantiate();
function growMem(pages) { return module.exports.grow_memory(pages); }
assertEquals(1, growMem(5));
assertEquals(6, growMem(5));
assertEquals(-1, growMem(6));
}
testMemoryGrowDeclaredMaxTraps();
(function testMemoryGrowInternalMaxTraps() {
// This test checks that grow_memory does not grow past the internally
// defined maximum memory size.
var builder = genMemoryGrowBuilder();
builder.addMemory(1, kSpecMaxPages, false);
var module = builder.instantiate();
function growMem(pages) { return module.exports.grow_memory(pages); }
assertEquals(1, growMem(20));
assertEquals(-1, growMem(kV8MaxPages - 20));
})();
(function testMemoryGrow4Gb() {
var builder = genMemoryGrowBuilder();
builder.addMemory(1, undefined, false);
var module = builder.instantiate();
var offset, val;
function peek() { return module.exports.load(offset); }
function poke(value) { return module.exports.store(offset, value); }
function growMem(pages) { return module.exports.grow_memory(pages); }
// Check first 5 offsets.
for (offset = 0; offset <= 4 * 4; offset += 4) {
poke(100000 - offset);
assertEquals(100000 - offset, peek());
}
// Check last 5 offsets.
for (offset = (kPageSize - 5 * 4); offset <= (kPageSize - 4); offset += 4) {
poke(100000 - offset);
assertEquals(100000 - offset, peek());
}
let result = growMem(kV8MaxPages - 1);
if (result == 1) {
// Check first 5 offsets.
for (offset = 0; offset <= 4 * 4; offset += 4) {
assertEquals(100000 - offset, peek());
}
// Check last 5 offsets.
for (offset = (kPageSize - 5 * 4); offset <= (kPageSize - 4); offset += 4) {
assertEquals(100000 - offset, peek());
}
// Bounds check for large mem size.
let kMemSize = (kV8MaxPages * kPageSize);
let kLastValidOffset = kMemSize - 4; // Accommodate a 4-byte read/write.
// Check first 5 offsets of last page.
for (offset = kMemSize - kPageSize; offset <= kMemSize - kPageSize + 4 * 4;
offset += 4) {
poke(0xaced);
assertEquals(0xaced, peek());
}
for (offset = kLastValidOffset - 5 * 4; offset <= kLastValidOffset;
offset += 4) {
poke(0xaced);
assertEquals(0xaced, peek());
}
for (offset = kLastValidOffset + 1; offset < kMemSize; offset++) {
assertTraps(kTrapMemOutOfBounds, poke);
}
} else {
// Allocating big chunks of memory can fail on gc_stress, especially on 32
// bit platforms. When grow_memory fails, expected result is -1.
assertEquals(-1, result);
}
})();