97cdf35f4a
The table.drop bytecode "drops" the backing storage for an element initializer. In the V8 implementation, this is a nop, other than updating a per-instance boolean array so that two drops of the same segment or a drop of an active segment will trap. This is implemented with inline code in TurboFan in order to be symmetric to memory.drop, but could as easily be a runtime call to be supported in Liftoff. R=mstarzinger@chromium.org CC=binji@chromium.org BUG=v8:7747 Change-Id: Ic017398eaa764dd3a9ff19523453ff7142c9abf6 Reviewed-on: https://chromium-review.googlesource.com/c/1408996 Reviewed-by: Ben Smith <binji@chromium.org> Commit-Queue: Ben Titzer <titzer@chromium.org> Cr-Commit-Position: refs/heads/master@{#58817}
396 lines
12 KiB
JavaScript
396 lines
12 KiB
JavaScript
// Copyright 2018 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-bulk-memory
|
|
|
|
load("test/mjsunit/wasm/wasm-constants.js");
|
|
load("test/mjsunit/wasm/wasm-module-builder.js");
|
|
|
|
(function TestPassiveDataSegment() {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.addMemory(1, 1, false);
|
|
builder.addPassiveDataSegment([0, 1, 2]);
|
|
builder.addPassiveDataSegment([3, 4]);
|
|
|
|
// Should not throw.
|
|
builder.instantiate();
|
|
})();
|
|
|
|
(function TestPassiveElementSegment() {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.addFunction('f', kSig_v_v).addBody([]);
|
|
builder.setTableBounds(1, 1);
|
|
builder.addPassiveElementSegment([0, 0, 0]);
|
|
builder.addPassiveElementSegment([0, 0]);
|
|
|
|
// Should not throw.
|
|
builder.instantiate();
|
|
})();
|
|
|
|
function assertBufferContents(buf, expected) {
|
|
for (let i = 0; i < expected.length; ++i) {
|
|
assertEquals(expected[i], buf[i]);
|
|
}
|
|
for (let i = expected.length; i < buf.length; ++i) {
|
|
assertEquals(0, buf[i]);
|
|
}
|
|
}
|
|
|
|
function getMemoryInit(mem, segment_data) {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.addImportedMemory("", "mem", 0);
|
|
builder.addPassiveDataSegment(segment_data);
|
|
builder.addFunction('init', kSig_v_iii)
|
|
.addBody([
|
|
kExprGetLocal, 0, // Dest.
|
|
kExprGetLocal, 1, // Source.
|
|
kExprGetLocal, 2, // Size in bytes.
|
|
kNumericPrefix, kExprMemoryInit,
|
|
0, // Memory index.
|
|
0, // Data segment index.
|
|
])
|
|
.exportAs('init');
|
|
return builder.instantiate({'': {mem}}).exports.init;
|
|
}
|
|
|
|
(function TestMemoryInit() {
|
|
const mem = new WebAssembly.Memory({initial: 1});
|
|
const memoryInit = getMemoryInit(mem, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
|
|
const u8a = new Uint8Array(mem.buffer);
|
|
|
|
// All zeroes.
|
|
assertBufferContents(u8a, []);
|
|
|
|
// Copy all bytes from data segment 0, to memory at [10, 20).
|
|
memoryInit(10, 0, 10);
|
|
assertBufferContents(u8a, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
|
|
// Copy bytes in range [5, 10) from data segment 0, to memory at [0, 5).
|
|
memoryInit(0, 5, 5);
|
|
assertBufferContents(u8a, [5, 6, 7, 8, 9, 0, 0, 0, 0, 0,
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
|
|
})();
|
|
|
|
(function TestMemoryInitOutOfBounds() {
|
|
const mem = new WebAssembly.Memory({initial: 1});
|
|
// Create a data segment that has a length of kPageSize.
|
|
const memoryInit = getMemoryInit(mem, new Array(kPageSize));
|
|
|
|
// OK, copy the full data segment to memory.
|
|
memoryInit(0, 0, kPageSize);
|
|
|
|
// Source range must not be out of bounds.
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryInit(0, 1, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryInit(0, 1000, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryInit(0, kPageSize, 1));
|
|
|
|
// Destination range must not be out of bounds.
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryInit(1, 0, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryInit(1000, 0, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryInit(kPageSize, 0, 1));
|
|
|
|
// Make sure bounds aren't checked with 32-bit wrapping.
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryInit(1, 1, -1));
|
|
|
|
mem.grow(1);
|
|
|
|
// Works properly after grow.
|
|
memoryInit(kPageSize, 0, 1000);
|
|
|
|
// Traps at new boundary.
|
|
assertTraps(
|
|
kTrapMemOutOfBounds, () => memoryInit(kPageSize + 1, 0, kPageSize));
|
|
})();
|
|
|
|
(function TestMemoryInitOnActiveSegment() {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.addMemory(1);
|
|
builder.addPassiveDataSegment([1, 2, 3]);
|
|
builder.addDataSegment(0, [4, 5, 6]);
|
|
builder.addFunction('init', kSig_v_v)
|
|
.addBody([
|
|
kExprI32Const, 0, // Dest.
|
|
kExprI32Const, 0, // Source.
|
|
kExprI32Const, 0, // Size in bytes.
|
|
kNumericPrefix, kExprMemoryInit,
|
|
0, // Memory index.
|
|
1, // Data segment index.
|
|
])
|
|
.exportAs('init');
|
|
|
|
// Instantiation succeeds, because using memory.init with an active segment
|
|
// is a trap, not a validation error.
|
|
const instance = builder.instantiate();
|
|
|
|
assertTraps(kTrapDataSegmentDropped, () => instance.exports.init());
|
|
})();
|
|
|
|
(function TestMemoryInitOnDroppedSegment() {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.addMemory(1);
|
|
builder.addPassiveDataSegment([1, 2, 3]);
|
|
builder.addFunction('init', kSig_v_v)
|
|
.addBody([
|
|
kExprI32Const, 0, // Dest.
|
|
kExprI32Const, 0, // Source.
|
|
kExprI32Const, 0, // Size in bytes.
|
|
kNumericPrefix, kExprMemoryInit,
|
|
0, // Memory index.
|
|
0, // Data segment index.
|
|
])
|
|
.exportAs('init');
|
|
builder.addFunction('drop', kSig_v_v)
|
|
.addBody([
|
|
kNumericPrefix, kExprMemoryDrop,
|
|
0, // Data segment index.
|
|
])
|
|
.exportAs('drop');
|
|
|
|
// Instantiation succeeds, because using memory.init with an active segment
|
|
// is a trap, not a validation error.
|
|
const instance = builder.instantiate();
|
|
|
|
// OK, segment hasn't been dropped.
|
|
instance.exports.init();
|
|
|
|
instance.exports.drop();
|
|
|
|
// After segment has been dropped, memory.init and memory.drop fail.
|
|
assertTraps(kTrapDataSegmentDropped, () => instance.exports.init());
|
|
assertTraps(kTrapDataSegmentDropped, () => instance.exports.drop());
|
|
})();
|
|
|
|
(function TestMemoryDropOnActiveSegment() {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.addMemory(1);
|
|
builder.addPassiveDataSegment([1, 2, 3]);
|
|
builder.addDataSegment(0, [4, 5, 6]);
|
|
builder.addFunction('drop', kSig_v_v)
|
|
.addBody([
|
|
kNumericPrefix, kExprMemoryDrop,
|
|
1, // Data segment index.
|
|
])
|
|
.exportAs('drop');
|
|
|
|
const instance = builder.instantiate();
|
|
assertTraps(kTrapDataSegmentDropped, () => instance.exports.drop());
|
|
})();
|
|
|
|
function getMemoryCopy(mem) {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.addImportedMemory("", "mem", 0);
|
|
builder.addFunction("copy", kSig_v_iii).addBody([
|
|
kExprGetLocal, 0, // Dest.
|
|
kExprGetLocal, 1, // Source.
|
|
kExprGetLocal, 2, // Size in bytes.
|
|
kNumericPrefix, kExprMemoryCopy, 0,
|
|
]).exportAs("copy");
|
|
return builder.instantiate({'': {mem}}).exports.copy;
|
|
}
|
|
|
|
(function TestMemoryCopy() {
|
|
const mem = new WebAssembly.Memory({initial: 1});
|
|
const memoryCopy = getMemoryCopy(mem);
|
|
|
|
const u8a = new Uint8Array(mem.buffer);
|
|
u8a.set([0, 11, 22, 33, 44, 55, 66, 77]);
|
|
|
|
memoryCopy(10, 1, 8);
|
|
|
|
assertBufferContents(u8a, [0, 11, 22, 33, 44, 55, 66, 77, 0, 0,
|
|
11, 22, 33, 44, 55, 66, 77]);
|
|
|
|
// Copy 0 bytes does nothing.
|
|
memoryCopy(10, 1, 0);
|
|
assertBufferContents(u8a, [0, 11, 22, 33, 44, 55, 66, 77, 0, 0,
|
|
11, 22, 33, 44, 55, 66, 77]);
|
|
})();
|
|
|
|
(function TestMemoryCopyOverlapping() {
|
|
const mem = new WebAssembly.Memory({initial: 1});
|
|
const memoryCopy = getMemoryCopy(mem);
|
|
|
|
const u8a = new Uint8Array(mem.buffer);
|
|
u8a.set([10, 20, 30]);
|
|
|
|
// Copy from [0, 3] -> [2, 5]. The copy must not overwrite 30 before copying
|
|
// it (i.e. cannot copy forward in this case).
|
|
memoryCopy(2, 0, 3);
|
|
assertBufferContents(u8a, [10, 20, 10, 20, 30]);
|
|
|
|
// Copy from [2, 5] -> [0, 3]. The copy must not write the first 10 (i.e.
|
|
// cannot copy backward in this case).
|
|
memoryCopy(0, 2, 3);
|
|
assertBufferContents(u8a, [10, 20, 30, 20, 30]);
|
|
})();
|
|
|
|
(function TestMemoryCopyOutOfBounds() {
|
|
const mem = new WebAssembly.Memory({initial: 1});
|
|
const memoryCopy = getMemoryCopy(mem);
|
|
|
|
memoryCopy(0, 0, kPageSize);
|
|
|
|
// Source range must not be out of bounds.
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryCopy(0, 1, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryCopy(0, 1000, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryCopy(0, kPageSize, 1));
|
|
|
|
// Destination range must not be out of bounds.
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryCopy(1, 0, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryCopy(1000, 0, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryCopy(kPageSize, 0, 1));
|
|
|
|
// Make sure bounds aren't checked with 32-bit wrapping.
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryCopy(1, 1, -1));
|
|
|
|
mem.grow(1);
|
|
|
|
// Works properly after grow.
|
|
memoryCopy(0, kPageSize, 1000);
|
|
|
|
// Traps at new boundary.
|
|
assertTraps(
|
|
kTrapMemOutOfBounds, () => memoryCopy(0, kPageSize + 1, kPageSize));
|
|
})();
|
|
|
|
function getMemoryFill(mem) {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.addImportedMemory("", "mem", 0);
|
|
builder.addFunction("fill", kSig_v_iii).addBody([
|
|
kExprGetLocal, 0, // Dest.
|
|
kExprGetLocal, 1, // Byte value.
|
|
kExprGetLocal, 2, // Size.
|
|
kNumericPrefix, kExprMemoryFill, 0,
|
|
]).exportAs("fill");
|
|
return builder.instantiate({'': {mem}}).exports.fill;
|
|
}
|
|
|
|
(function TestMemoryFill() {
|
|
const mem = new WebAssembly.Memory({initial: 1});
|
|
const memoryFill = getMemoryFill(mem);
|
|
|
|
const u8a = new Uint8Array(mem.buffer);
|
|
|
|
memoryFill(1, 33, 5);
|
|
assertBufferContents(u8a, [0, 33, 33, 33, 33, 33]);
|
|
|
|
memoryFill(4, 66, 4);
|
|
assertBufferContents(u8a, [0, 33, 33, 33, 66, 66, 66, 66]);
|
|
|
|
// Fill 0 bytes does nothing.
|
|
memoryFill(4, 66, 0);
|
|
assertBufferContents(u8a, [0, 33, 33, 33, 66, 66, 66, 66]);
|
|
})();
|
|
|
|
(function TestMemoryFillValueWrapsToByte() {
|
|
const mem = new WebAssembly.Memory({initial: 1});
|
|
const memoryFill = getMemoryFill(mem);
|
|
|
|
const u8a = new Uint8Array(mem.buffer);
|
|
|
|
memoryFill(0, 1000, 3);
|
|
const expected = 1000 & 255;
|
|
assertBufferContents(u8a, [expected, expected, expected]);
|
|
})();
|
|
|
|
(function TestMemoryFillOutOfBounds() {
|
|
const mem = new WebAssembly.Memory({initial: 1});
|
|
const memoryFill = getMemoryFill(mem);
|
|
const v = 123;
|
|
|
|
memoryFill(0, 0, kPageSize);
|
|
|
|
// Destination range must not be out of bounds.
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryFill(1, v, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryFill(1000, v, kPageSize));
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryFill(kPageSize, v, 1));
|
|
|
|
// Make sure bounds aren't checked with 32-bit wrapping.
|
|
assertTraps(kTrapMemOutOfBounds, () => memoryFill(1, v, -1));
|
|
|
|
mem.grow(1);
|
|
|
|
// Works properly after grow.
|
|
memoryFill(kPageSize, v, 1000);
|
|
|
|
// Traps at new boundary.
|
|
assertTraps(
|
|
kTrapMemOutOfBounds, () => memoryFill(kPageSize + 1, v, kPageSize));
|
|
})();
|
|
|
|
(function TestTableInit0() {
|
|
let builder = new WasmModuleBuilder();
|
|
let sig_v_iii = builder.addType(kSig_v_iii);
|
|
|
|
builder.setTableBounds(5, 5);
|
|
builder.addElementSegment(0, false, []);
|
|
builder.addElementSegment(0, false, []);
|
|
|
|
builder.addFunction("init0", sig_v_iii)
|
|
.addBody([
|
|
kExprGetLocal, 0,
|
|
kExprGetLocal, 1,
|
|
kExprGetLocal, 2,
|
|
kNumericPrefix, kExprTableInit, kTableZero, kSegmentZero])
|
|
.exportAs("init0");
|
|
|
|
let instance = builder.instantiate();
|
|
let init = instance.exports.init0;
|
|
// TODO(titzer): we only check that a function containing TableInit can be compiled.
|
|
// init(1, 2, 3);
|
|
})();
|
|
|
|
(function TestTableDropActive() {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.setTableBounds(5, 5);
|
|
builder.addElementSegment(0, false, [0, 0, 0]);
|
|
builder.addFunction('drop', kSig_v_v)
|
|
.addBody([
|
|
kNumericPrefix, kExprTableDrop,
|
|
0, // Element segment index.
|
|
])
|
|
.exportAs('drop');
|
|
|
|
const instance = builder.instantiate();
|
|
assertTraps(kTrapElemSegmentDropped, () => instance.exports.drop());
|
|
})();
|
|
|
|
(function TestTableDropTwice() {
|
|
const builder = new WasmModuleBuilder();
|
|
builder.setTableBounds(5, 5);
|
|
builder.addPassiveElementSegment([0, 0, 0]);
|
|
builder.addFunction('drop', kSig_v_v)
|
|
.addBody([
|
|
kNumericPrefix, kExprTableDrop,
|
|
0, // Element segment index.
|
|
])
|
|
.exportAs('drop');
|
|
|
|
const instance = builder.instantiate();
|
|
instance.exports.drop();
|
|
assertTraps(kTrapElemSegmentDropped, () => instance.exports.drop());
|
|
})();
|
|
|
|
(function TestTableCopy0() {
|
|
let builder = new WasmModuleBuilder();
|
|
let sig_v_iii = builder.addType(kSig_v_iii);
|
|
|
|
builder.setTableBounds(5, 5);
|
|
|
|
builder.addFunction("copy", sig_v_iii)
|
|
.addBody([
|
|
kExprGetLocal, 0,
|
|
kExprGetLocal, 1,
|
|
kExprGetLocal, 2,
|
|
kNumericPrefix, kExprTableCopy, kTableZero])
|
|
.exportAs("copy");
|
|
|
|
let instance = builder.instantiate();
|
|
let copy = instance.exports.copy;
|
|
// TODO(titzer): we only check that a function containing TableCopy can be compiled.
|
|
// copy(1, 2, 3);
|
|
})();
|