// Copyright 2019 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. d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); function assertIsWasmSharedMemory(memory) { assertTrue(memory instanceof Object, "Memory is not an object"); assertTrue(memory instanceof WebAssembly.Memory, "Object is not WebAssembly.Memory" ); assertTrue(memory.buffer instanceof SharedArrayBuffer, "Memory.buffer is not a SharedArrayBuffer"); assertTrue(Object.isFrozen(memory.buffer), "Memory.buffer not frozen"); } function assertTrue(value, msg) { if (!value) { postMessage("Error: " + msg); throw new Error("Exit"); // To stop testing. } } let workerHelpers = assertTrue.toString() + assertIsWasmSharedMemory.toString(); (function TestGrowSharedMemoryWithoutPostMessage() { print(arguments.callee.name); let memory = new WebAssembly.Memory({initial: 1, maximum: 5, shared: true}); assertEquals(memory.buffer.byteLength, kPageSize); assertEquals(1, memory.grow(1)); assertEquals(memory.buffer.byteLength, 2 * kPageSize); })(); (function TestPostMessageWithGrow() { print(arguments.callee.name); function workerCode(workerHelpers) { eval(workerHelpers); onmessage = function(obj) { assertIsWasmSharedMemory(obj.memory); assertTrue(1 === obj.memory.grow(1)); assertTrue(obj.memory.buffer.byteLength === obj.expected_size); assertIsWasmSharedMemory(obj.memory); postMessage("OK"); } } let worker = new Worker(workerCode, {type: 'function', arguments: [workerHelpers]}); let memory = new WebAssembly.Memory({initial: 1, maximum: 5, shared: true}); let obj = {memory: memory, expected_size: 2 * kPageSize}; assertEquals(obj.memory.buffer.byteLength, kPageSize); worker.postMessage(obj); assertEquals("OK", worker.getMessage()); assertEquals(obj.memory.buffer.byteLength, 2 * kPageSize); worker.terminate(); })(); // PostMessage from two different workers, and assert that the grow // operations are performed on the same memory object. (function TestWorkersWithGrowEarlyWorkerTerminate() { print(arguments.callee.name); function workerCode(workerHelpers) { eval(workerHelpers); onmessage = function(obj) { assertIsWasmSharedMemory(obj.memory); obj.memory.grow(1); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === obj.expected_size); postMessage("OK"); }; } let workers = [new Worker(workerCode, {type: 'function', arguments: [workerHelpers]}), new Worker(workerCode, {type: 'function', arguments: [workerHelpers]})]; let memory = new WebAssembly.Memory({initial: 1, maximum: 5, shared: true}); let expected_pages = 1; for (let worker of workers) { assertEquals(memory.buffer.byteLength, expected_pages++ * kPageSize); let obj = {memory: memory, expected_size: expected_pages * kPageSize}; worker.postMessage(obj); assertEquals("OK", worker.getMessage()); assertEquals(memory.buffer.byteLength, expected_pages * kPageSize); worker.terminate(); } assertEquals(memory.buffer.byteLength, expected_pages * kPageSize); })(); // PostMessage of Multiple memories and grow (function TestGrowSharedWithMultipleMemories() { print(arguments.callee.name); function workerCode(workerHelpers) { eval(workerHelpers); onmessage = function(obj) { let expected_size = 0; let kPageSize = 0x10000; for (let memory of obj.memories) { assertIsWasmSharedMemory(memory); assertTrue(expected_size === memory.grow(2)); expected_size+=2; assertIsWasmSharedMemory(memory); assertTrue(memory.buffer.byteLength === expected_size * kPageSize); } postMessage("OK"); }; } let worker = new Worker(workerCode, {type: 'function', arguments: [workerHelpers]}); let memories = [new WebAssembly.Memory({initial: 0, maximum: 2, shared: true}), new WebAssembly.Memory({initial: 2, maximum: 10, shared: true}), new WebAssembly.Memory({initial: 4, maximum: 12, shared: true})]; let obj = {memories: memories}; worker.postMessage(obj); assertEquals("OK", worker.getMessage()); assertEquals(2 * kPageSize, memories[0].buffer.byteLength); assertEquals(4 * kPageSize, memories[1].buffer.byteLength); assertEquals(6 * kPageSize, memories[2].buffer.byteLength); worker.terminate(); })(); // SharedMemory Object shared between different instances (function TestPostMessageJSAndWasmInterop() { print(arguments.callee.name); function workerCode(workerHelpers) { eval(workerHelpers); onmessage = function(obj) { let kPageSize = 0x10000; assertIsWasmSharedMemory(obj.memory); let instance = new WebAssembly.Instance( obj.module, {m: {memory: obj.memory}}); assertTrue(5 === obj.memory.grow(10)); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 15 * kPageSize); assertTrue(15 === instance.exports.grow(5)); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 20 * kPageSize); postMessage("OK"); } } let worker = new Worker(workerCode, {type: 'function', arguments: [workerHelpers]}); let memory = new WebAssembly.Memory({initial: 5, maximum: 50, shared: true}); var builder = new WasmModuleBuilder(); builder.addImportedMemory("m", "memory", 5, 100, "shared"); builder.addFunction("grow", kSig_i_i) .addBody([kExprLocalGet, 0, kExprMemoryGrow, kMemoryZero]) .exportFunc(); var module = new WebAssembly.Module(builder.toBuffer()); let obj = {memory: memory, module: module}; assertEquals(obj.memory.buffer.byteLength, 5 * kPageSize); worker.postMessage(obj); assertEquals("OK", worker.getMessage()); worker.terminate(); assertEquals(obj.memory.buffer.byteLength, 20 * kPageSize); })(); (function TestConsecutiveJSAndWasmSharedGrow() { print(arguments.callee.name); function workerCode(workerHelpers) { eval(workerHelpers); onmessage = function(obj) { let kPageSize = 0x10000; assertIsWasmSharedMemory(obj.memory); let instance = new WebAssembly.Instance( obj.module, {m: {memory: obj.memory}}); assertTrue(5 === obj.memory.grow(10)); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 15 * kPageSize); assertTrue(15 === instance.exports.grow(5)); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 20 * kPageSize); postMessage("OK"); } } let worker = new Worker(workerCode, {type: 'function', arguments: [workerHelpers]}); let memory = new WebAssembly.Memory({initial: 5, maximum: 50, shared: true}); var builder = new WasmModuleBuilder(); builder.addImportedMemory("m", "memory", 5, 100, "shared"); builder.addFunction("grow", kSig_i_i) .addBody([kExprLocalGet, 0, kExprMemoryGrow, kMemoryZero]) .exportFunc(); var module = new WebAssembly.Module(builder.toBuffer()); let obj = {memory: memory, module: module}; assertEquals(obj.memory.buffer.byteLength, 5 * kPageSize); worker.postMessage(obj); assertEquals("OK", worker.getMessage()); assertEquals(obj.memory.buffer.byteLength, 20 * kPageSize); })(); (function TestConsecutiveWasmSharedGrow() { print(arguments.callee.name); function workerCode(workerHelpers) { eval(workerHelpers); onmessage = function(obj) { let kPageSize = 0x10000; assertIsWasmSharedMemory(obj.memory); let instance = new WebAssembly.Instance( obj.module, {m: {memory: obj.memory}}); assertTrue(5 === obj.memory.grow(10)); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 15 * kPageSize); assertTrue(17 === instance.exports.grow_twice(2)); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 19 * kPageSize); postMessage("OK"); } } let worker = new Worker(workerCode, {type: 'function', arguments: [workerHelpers]}); let memory = new WebAssembly.Memory({initial: 5, maximum: 50, shared: true}); var builder = new WasmModuleBuilder(); builder.addImportedMemory("m", "memory", 5, 100, "shared"); builder.addFunction("grow_twice", kSig_i_i) .addBody([kExprLocalGet, 0, kExprMemoryGrow, kMemoryZero, kExprDrop, kExprLocalGet, 0, kExprMemoryGrow, kMemoryZero]) .exportFunc(); var module = new WebAssembly.Module(builder.toBuffer()); let obj = {memory: memory, module: module}; assertEquals(obj.memory.buffer.byteLength, 5 * kPageSize); worker.postMessage(obj); assertEquals("OK", worker.getMessage()); assertEquals(obj.memory.buffer.byteLength, 19 * kPageSize); let instance = new WebAssembly.Instance(module, {m: {memory: memory}}); assertEquals(21, instance.exports.grow_twice(2)); assertEquals(obj.memory.buffer.byteLength, 23 * kPageSize); })(); (function TestConsecutiveSharedGrowAndMemorySize() { print(arguments.callee.name); function workerCode(workerHelpers) { eval(workerHelpers); onmessage = function(obj) { let kPageSize = 0x10000; assertIsWasmSharedMemory(obj.memory); let instance = new WebAssembly.Instance( obj.module, {m: {memory: obj.memory}}); assertTrue(5 === obj.memory.grow(10)); assertTrue(15 === instance.exports.memory_size()); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 15 * kPageSize); assertTrue(19 === instance.exports.grow_and_size(2)); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 19 * kPageSize); postMessage("OK"); } } let worker = new Worker(workerCode, {type: 'function', arguments: [workerHelpers]}); let memory = new WebAssembly.Memory({initial: 5, maximum: 50, shared: true}); var builder = new WasmModuleBuilder(); builder.addImportedMemory("m", "memory", 5, 100, "shared"); builder.addFunction("grow_and_size", kSig_i_i) .addBody([kExprLocalGet, 0, kExprMemoryGrow, kMemoryZero, kExprDrop, kExprLocalGet, 0, kExprMemoryGrow, kMemoryZero, kExprDrop, kExprMemorySize, kMemoryZero]) .exportFunc(); builder.addFunction("memory_size", kSig_i_v) .addBody([kExprMemorySize, kMemoryZero]) .exportFunc(); var module = new WebAssembly.Module(builder.toBuffer()); let obj = {memory: memory, module: module}; assertEquals(obj.memory.buffer.byteLength, 5 * kPageSize); worker.postMessage(obj); assertEquals("OK", worker.getMessage()); assertEquals(memory.buffer.byteLength, 19 * kPageSize); let instance = new WebAssembly.Instance(module, {m: {memory: memory}}); assertEquals(23, instance.exports.grow_and_size(2)); assertEquals(obj.memory.buffer.byteLength, 23 * kPageSize); assertEquals(23, memory.grow(2)); assertEquals(25, instance.exports.memory_size()); })(); // Only spot checking here because currently the underlying buffer doesn't move. // In the case that the underlying buffer does move, more comprehensive memory // integrity checking and bounds checks testing are needed. (function TestSpotCheckMemoryWithSharedGrow() { print(arguments.callee.name); function workerCode(workerHelpers) { eval(workerHelpers); onmessage = function(obj) { let kPageSize = 0x10000; assertIsWasmSharedMemory(obj.memory); let instance = new WebAssembly.Instance( obj.module, {m: {memory: obj.memory}}); assertTrue(5 === obj.memory.grow(10)); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 15 * kPageSize); // Store again, and verify that the previous stores are still reflected. instance.exports.atomic_store(15 * kPageSize - 4, 0xACED); assertTrue(0xACED === instance.exports.atomic_load(0)); assertTrue(0xACED === instance.exports.atomic_load(5 * kPageSize - 4)); assertTrue(0xACED === instance.exports.atomic_load(15 * kPageSize - 4)); assertTrue(15 === instance.exports.grow(2)); assertIsWasmSharedMemory(obj.memory); assertTrue(obj.memory.buffer.byteLength === 17 * kPageSize); // Validate previous writes. instance.exports.atomic_store(17 * kPageSize - 4, 0xACED); assertTrue(0xACED === instance.exports.atomic_load(0)); assertTrue(0xACED === instance.exports.atomic_load(5 * kPageSize - 4)); assertTrue(0xACED === instance.exports.atomic_load(15 * kPageSize - 4)); assertTrue(0xACED === instance.exports.atomic_load(17 * kPageSize - 4)); postMessage("OK"); } } let worker = new Worker(workerCode, {type: 'function', arguments: [workerHelpers]}); let memory = new WebAssembly.Memory({initial: 5, maximum: 50, shared: true}); var builder = new WasmModuleBuilder(); builder.addImportedMemory("m", "memory", 5, 100, "shared"); builder.addFunction("grow", kSig_i_i) .addBody([kExprLocalGet, 0, kExprMemoryGrow, kMemoryZero]) .exportFunc(); builder.addFunction("atomic_load", kSig_i_i) .addBody([kExprLocalGet, 0, kAtomicPrefix, kExprI32AtomicLoad, 2, 0]) .exportFunc(); builder.addFunction("atomic_store", kSig_v_ii) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kAtomicPrefix, kExprI32AtomicStore, 2, 0]) .exportFunc(); var module = new WebAssembly.Module(builder.toBuffer()); let instance = new WebAssembly.Instance(module, {m: {memory: memory}}); // Store at first and last accessible 32 bit offset. instance.exports.atomic_store(0, 0xACED); instance.exports.atomic_store(5 * kPageSize - 4, 0xACED); // Verify that these were stored. assertEquals(0xACED, instance.exports.atomic_load(0)); assertEquals(0xACED, instance.exports.atomic_load(5 * kPageSize - 4)); // Verify bounds. // If an underlying platform uses traps for a bounds check, // kTrapUnalignedAccess will be thrown before kTrapMemOutOfBounds. // Otherwise, kTrapMemOutOfBounds will be first. assertTrapsOneOf([kTrapMemOutOfBounds, kTrapUnalignedAccess], () => instance.exports.atomic_load(5 * kPageSize - 3)); let obj = {memory: memory, module: module}; assertEquals(obj.memory.buffer.byteLength, 5 * kPageSize); // PostMessage worker.postMessage(obj); assertEquals("OK", worker.getMessage()); assertEquals(memory.buffer.byteLength, 17 * kPageSize); assertEquals(17, instance.exports.grow(2)); assertEquals(obj.memory.buffer.byteLength, 19 * kPageSize); // Validate previous writes, and check bounds. assertTrue(0xACED === instance.exports.atomic_load(0)); assertTrue(0xACED === instance.exports.atomic_load(5 * kPageSize - 4)); assertTrue(0xACED === instance.exports.atomic_load(15 * kPageSize - 4)); assertTrue(0xACED === instance.exports.atomic_load(17 * kPageSize - 4)); assertTrapsOneOf([kTrapMemOutOfBounds, kTrapUnalignedAccess], () => instance.exports.atomic_load(19 * kPageSize - 3)); assertEquals(19, memory.grow(6)); assertEquals(obj.memory.buffer.byteLength, 25 * kPageSize); assertTrapsOneOf([kTrapMemOutOfBounds, kTrapUnalignedAccess], () => instance.exports.atomic_load(25 * kPageSize - 3)); })(); (function TestMemoryBufferTypeAfterGrow() { const memory = new WebAssembly.Memory({ "initial": 1, "maximum": 2, "shared": true }); assertInstanceof(memory.buffer, SharedArrayBuffer); assertEquals(memory.grow(1), 1); assertInstanceof(memory.buffer, SharedArrayBuffer); })(); (function TestSharedMemoryGrowByZero() { const memory = new WebAssembly.Memory({ "initial": 1, "maximum": 2, "shared": true }); assertEquals(memory.grow(0), 1); })(); // Tests that a function receives the update of a shared memory's size if a // loop's stack guard gets invoked. This is not strictly required by spec, but // we implement it as an optimization. (function TestStackGuardUpdatesMemorySize() { print(arguments.callee.name); let initial_size = 1; let final_size = 2; let memory = new WebAssembly.Memory({initial: 1, maximum: 5, shared: true}); let sync_index = 64; let sync_value = 42; let builder = new WasmModuleBuilder(); builder.addImportedMemory("mod", "mem", 1, 5, true); // int x; // while (true) { // memory[sync_index] = sync_value; // x = memory_size(); // if (x != 1) break; // } // return x; builder.addFunction("main", kSig_i_v) .addLocals(kWasmI32, 1) .addBody([ kExprLoop, kWasmVoid, ...wasmI32Const(sync_index), ...wasmI32Const(sync_value), kAtomicPrefix, kExprI32AtomicStore, 0, 0, kExprMemorySize, 0, kExprLocalTee, 0, kExprI32Const, initial_size, kExprI32Eq, kExprBrIf, 0, kExprEnd, kExprLocalGet, 0]) .exportFunc(); builder.addFunction("setter", kSig_v_ii) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kAtomicPrefix, kExprI32AtomicStore, 0, 0]) .exportFunc(); builder.addFunction("getter", kSig_i_i) .addBody([kExprLocalGet, 0, kAtomicPrefix, kExprI32AtomicLoad, 0, 0]) .exportFunc(); let module = new WebAssembly.Module(builder.toBuffer()); function workerCode() { onmessage = function(obj) { let instance = new WebAssembly.Instance( obj.module, {mod: {mem: obj.memory}}); let res = instance.exports.main(); postMessage(res); } } let worker = new Worker(workerCode, {type: 'function', arguments: []}); worker.postMessage({module: module, memory: memory}); let instance = new WebAssembly.Instance(module, {mod: {mem: memory}}); // Make sure the worker thread has entered the loop. while (instance.exports.getter(sync_index) != sync_value) {} memory.grow(final_size - initial_size); assertEquals(final_size, worker.getMessage()); })();