// Copyright 2023 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-gc d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); (function TestArrayFillImmutable() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let array = builder.addArray(kWasmI32, false); // Parameters: array, starting index, value, length. builder.addFunction( "array_fill", makeSig([wasmRefNullType(array), kWasmI32, kWasmI32, kWasmI32], [])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 3, kGCPrefix, kExprArrayFill, array]) .exportFunc(); assertThrows(() => builder.instantiate(), WebAssembly.CompileError, /immediate array type #0 is immutable/); })(); (function TestArrayFill() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let struct = builder.addStruct([makeField(kWasmI32, true)]); let array = builder.addArray(wasmRefNullType(struct), true); let array16 = builder.addArray(kWasmI16, true); builder.addFunction( "make_array", makeSig([kWasmI32], [wasmRefType(array)])) .addBody([kExprLocalGet, 0, kGCPrefix, kExprArrayNewDefault, array]) .exportFunc(); builder.addFunction( "array_get", makeSig([wasmRefNullType(array), kWasmI32], [kWasmI32])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kGCPrefix, kExprArrayGet, array, kGCPrefix, kExprStructGet, struct, 0]) .exportFunc(); // Parameters: array, starting index, value in struct, length. builder.addFunction( "array_fill", makeSig([wasmRefNullType(array), kWasmI32, kWasmI32, kWasmI32], [])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kGCPrefix, kExprStructNew, struct, kExprLocalGet, 3, kGCPrefix, kExprArrayFill, array]) .exportFunc(); builder.addFunction("array_fill_i16", kSig_i_v).exportFunc() .addLocals(wasmRefType(array16), 1) .addBody([ kExprI32Const, 4, // Array length for allocation. kGCPrefix, kExprArrayNewDefault, array16, kExprLocalTee, 0, // Array (for fill). kExprI32Const, 0, // Offset (for fill). kExprI32Const, 42, // Value (for fill). kExprI32Const, 4, // Length (for fill). kGCPrefix, kExprArrayFill, array16, kExprLocalGet, 0, // Array (for get). kExprI32Const, 0, // Index (for get). kGCPrefix, kExprArrayGetS, array16, ]); let wasm = builder.instantiate().exports; assertTraps(kTrapNullDereference, () => wasm.array_fill(null, 10, 20, 30)); let array_obj = wasm.make_array(8); assertTraps(kTrapArrayOutOfBounds, () => wasm.array_fill(array_obj, 5, 42, 4)); // Out-of-bounds array.fill traps even if length is 0, if the index is greater // than array.len. assertTraps(kTrapArrayOutOfBounds, () => wasm.array_fill(array_obj, 10, 42, 0)); // Overflow of (index + length) traps. assertTraps(kTrapArrayOutOfBounds, () => wasm.array_fill(array_obj, 5, 42, -1)); wasm.array_fill(array_obj, 2, 42, 3); assertTraps(kTrapNullDereference, () => wasm.array_get(array_obj, 0)); assertTraps(kTrapNullDereference, () => wasm.array_get(array_obj, 1)); assertEquals(42, wasm.array_get(array_obj, 2)); assertEquals(42, wasm.array_get(array_obj, 3)); assertEquals(42, wasm.array_get(array_obj, 4)); assertTraps(kTrapNullDereference, () => wasm.array_get(array_obj, 5)); assertTraps(kTrapNullDereference, () => wasm.array_get(array_obj, 6)); assertTraps(kTrapNullDereference, () => wasm.array_get(array_obj, 7)); // Index = array.len and length = 0 works, it just does nothing. wasm.array_fill(array_obj, 8, 42, 0) wasm.array_fill(array_obj, 4, 54, 2); assertTraps(kTrapNullDereference, () => wasm.array_get(array_obj, 0)); assertTraps(kTrapNullDereference, () => wasm.array_get(array_obj, 1)); assertEquals(42, wasm.array_get(array_obj, 2)); assertEquals(42, wasm.array_get(array_obj, 3)); assertEquals(54, wasm.array_get(array_obj, 4)); assertEquals(54, wasm.array_get(array_obj, 5)); assertTraps(kTrapNullDereference, () => wasm.array_get(array_obj, 6)); assertTraps(kTrapNullDereference, () => wasm.array_get(array_obj, 7)); assertEquals(42, wasm.array_fill_i16()); })(); (function TestArrayNewNonNullable() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let struct = builder.addStruct([makeField(kWasmI32, true)]); let array = builder.addArray(wasmRefType(struct), true); builder.addFunction( "make_array", makeSig([wasmRefType(struct), kWasmI32], [wasmRefType(array)])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kGCPrefix, kExprArrayNew, array]) .exportFunc(); builder.addFunction( "array_get", makeSig([wasmRefNullType(array), kWasmI32], [wasmRefType(struct)])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kGCPrefix, kExprArrayGet, array]) .exportFunc(); builder.addFunction( "make_struct", makeSig([], [wasmRefType(struct)])) .addBody([kGCPrefix, kExprStructNewDefault, struct]) .exportFunc(); let wasm = builder.instantiate().exports; let length = 50; // Enough to go through initialization builtin. let struct_obj = wasm.make_struct(); let array_obj = wasm.make_array(struct_obj, length); for (let i = 0; i < length; i++) { assertEquals(struct_obj, wasm.array_get(array_obj, i)); } })(); (function TestArrayInitData() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let array = builder.addArray(kWasmI16, true); builder.addMemory(10, 10, false); let passive = builder.addPassiveDataSegment([0, 1, 2, 3, 4, 5]); let active = builder.addDataSegment(0, [6, 7, 8, 9]); builder.addFunction( "make_array", makeSig([kWasmI32], [wasmRefType(array)])) .addBody([kExprLocalGet, 0, kGCPrefix, kExprArrayNewDefault, array]) .exportFunc(); builder.addFunction( "init_passive", makeSig([wasmRefNullType(array), kWasmI32, kWasmI32, kWasmI32], [])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 3, kGCPrefix, kExprArrayInitData, array, passive]) .exportFunc(); builder.addFunction( "init_active", makeSig([wasmRefNullType(array), kWasmI32, kWasmI32, kWasmI32], [])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 3, kGCPrefix, kExprArrayInitData, array, active]) .exportFunc(); builder.addFunction( "array_get", makeSig([wasmRefNullType(array), kWasmI32], [kWasmI32])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kGCPrefix, kExprArrayGetS, array]) .exportFunc(); builder.addFunction("data_drop", kSig_v_v) .addBody([kNumericPrefix, kExprDataDrop, passive]) .exportFunc(); let wasm = builder.instantiate().exports; let array_length = 10; assertTraps(kTrapNullDereference, () => wasm.init_passive(null, 0, 0, 0)); let array_obj = wasm.make_array(array_length); for (i = 0; i < array_length; i++) { assertEquals(0, wasm.array_get(array_obj, i)); } // Does nothing. wasm.init_active(array_obj, 0, 0, 0); // Active segments count as dropped. assertTraps(kTrapDataSegmentOutOfBounds, () => wasm.init_active(array_obj, 0, 0, 1)); // Loading 0 bytes from the end of data segment does nothing. wasm.init_passive(array_obj, 0, 6, 0); // Loading 0 bytes to the end of the array does nothing. wasm.init_passive(array_obj, array_length, 0, 0); // Loading 0 bytes beyond the end of data segment traps. assertTraps(kTrapDataSegmentOutOfBounds, () => wasm.init_passive(array_obj, 0, 7, 0)); // Loading 0 bytes beyond the end of array traps. assertTraps(kTrapArrayOutOfBounds, () => wasm.init_passive(array_obj, array_length + 1, 1, 1)); // Out-of-bounds data segment. assertTraps(kTrapDataSegmentOutOfBounds, () => wasm.init_passive(array_obj, 1, 2, 3)); // Out-of-bounds array. assertTraps(kTrapArrayOutOfBounds, () => wasm.init_passive(array_obj, array_length - 1, 0, 2)); // Out-of-bounds array with out-of-smi-range length. assertTraps(kTrapArrayOutOfBounds, () => wasm.init_passive(array_obj, 0, 0, 0x80000000)); // Load [[1, 2], [3, 4]] at index 5 of array. wasm.init_passive(array_obj, 5, 1, 2); assertEquals(0, wasm.array_get(array_obj, 0)); assertEquals(0, wasm.array_get(array_obj, 1)); assertEquals(0, wasm.array_get(array_obj, 2)); assertEquals(0, wasm.array_get(array_obj, 3)); assertEquals(0, wasm.array_get(array_obj, 4)); // Bytes will be loaded in little-endian order. assertEquals(0x0201, wasm.array_get(array_obj, 5)); assertEquals(0x0403, wasm.array_get(array_obj, 6)); assertEquals(0, wasm.array_get(array_obj, 7)); assertEquals(0, wasm.array_get(array_obj, 8)); assertEquals(0, wasm.array_get(array_obj, 9)); // Now drop the segment. wasm.data_drop(); assertTraps(kTrapDataSegmentOutOfBounds, () => wasm.init_passive(array_obj, 5, 1, 2)); })(); (function TestArrayInitDataLengthOverflow() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let array = builder.addArray(kWasmI64, true); let passive = builder.addPassiveDataSegment([0, 1, 2, 3, 4, 5]); builder.addFunction( "make_array", makeSig([kWasmI32], [wasmRefType(array)])) .addBody([kExprLocalGet, 0, kGCPrefix, kExprArrayNewDefault, array]) .exportFunc(); builder.addFunction( "init_passive", makeSig([wasmRefNullType(array), kWasmI32, kWasmI32, kWasmI32], [])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 3, kGCPrefix, kExprArrayInitData, array, passive]) .exportFunc(); let wasm = builder.instantiate().exports; let array_length = 10; let array_obj = wasm.make_array(array_length); // Out-of-bounds array: length is within Smi range, but computation of length // in bytes causes 32-bit overflow. assertTraps(kTrapArrayOutOfBounds, () => wasm.init_passive(array_obj, array_length - 1, 0, 0x20000000)); })(); (function TestArrayInitDataImmutable() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let array = builder.addArray(kWasmI16, false); let passive = builder.addPassiveDataSegment([0, 1, 2, 3, 4, 5]); builder.addFunction( "init_passive", makeSig([wasmRefNullType(array), kWasmI32, kWasmI32, kWasmI32], [])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 3, kGCPrefix, kExprArrayInitData, array, passive]) .exportFunc(); assertThrows(() => builder.instantiate(), WebAssembly.CompileError, /array.init_data can only be used with mutable arrays/); })(); (function TestArrayInitElem() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let sig = builder.addType(kSig_i_i); let array = builder.addArray(wasmRefNullType(sig), true); let table = builder.addTable(wasmRefNullType(sig), 10, 10); let elem1 = builder.addFunction("succ", kSig_i_i) .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Add]); let elem2 = builder.addFunction("pred", kSig_i_i) .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]); let passive = builder.addPassiveElementSegment( [[kExprRefNull, sig], [kExprRefFunc, elem1.index], [kExprRefFunc, elem2.index], [kExprRefFunc, elem1.index]], wasmRefNullType(sig)); let active = builder.addActiveElementSegment( table, [kExprI32Const, 0], [[kExprRefFunc, elem1.index]], wasmRefNullType(sig)); builder.addFunction( "make_array", makeSig([kWasmI32], [wasmRefType(array)])) .addBody([kExprLocalGet, 0, kGCPrefix, kExprArrayNewDefault, array]) .exportFunc(); builder.addFunction( "init_passive", makeSig([wasmRefNullType(array), kWasmI32, kWasmI32, kWasmI32], [])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 3, kGCPrefix, kExprArrayInitElem, array, passive]) .exportFunc(); builder.addFunction( "init_active", makeSig([wasmRefNullType(array), kWasmI32, kWasmI32, kWasmI32], [])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 3, kGCPrefix, kExprArrayInitElem, array, active]) .exportFunc(); builder.addFunction( "array_get", makeSig([wasmRefNullType(array), kWasmI32], [wasmRefNullType(sig)])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kGCPrefix, kExprArrayGet, array]) .exportFunc(); builder.addFunction("elem_drop", kSig_v_v) .addBody([kNumericPrefix, kExprElemDrop, passive]) .exportFunc(); let wasm = builder.instantiate().exports; let array_length = 10; assertTraps(kTrapNullDereference, () => wasm.init_passive(null, 0, 0, 0)); let array_obj = wasm.make_array(array_length); for (i = 0; i < array_length; i++) { assertEquals(null, wasm.array_get(array_obj, i)); } // Does nothing. wasm.init_active(array_obj, 0, 0, 0); // Active segments count as dropped. assertTraps(kTrapElementSegmentOutOfBounds, () => wasm.init_active(array_obj, 0, 0, 1)); // Loading 0 elements from the end of element segment does nothing. wasm.init_passive(array_obj, 0, 4, 0); // Loading 0 elements to the end of the array does nothing. wasm.init_passive(array_obj, array_length, 0, 0); // Loading 0 elements beyond the end of data segment traps. assertTraps(kTrapElementSegmentOutOfBounds, () => wasm.init_passive(array_obj, 0, 5, 0)); // Loading 0 elements beyond the end of array traps. assertTraps(kTrapArrayOutOfBounds, () => wasm.init_passive(array_obj, array_length + 1, 1, 1)); // Out-of-bounds element segment. assertTraps(kTrapElementSegmentOutOfBounds, () => wasm.init_passive(array_obj, 1, 2, 3)); // Out-of-bounds array. assertTraps(kTrapArrayOutOfBounds, () => wasm.init_passive(array_obj, array_length - 1, 0, 2)); // Load the last three elements of the element segment at index 5. wasm.init_passive(array_obj, 5, 1, 3); assertEquals(null, wasm.array_get(array_obj, 0)); assertEquals(null, wasm.array_get(array_obj, 1)); assertEquals(null, wasm.array_get(array_obj, 2)); assertEquals(null, wasm.array_get(array_obj, 3)); assertEquals(null, wasm.array_get(array_obj, 4)); assertEquals(11, wasm.array_get(array_obj, 5)(10)); assertEquals(9, wasm.array_get(array_obj, 6)(10)); assertEquals(11, wasm.array_get(array_obj, 7)(10)); assertEquals(null, wasm.array_get(array_obj, 8)); assertEquals(null, wasm.array_get(array_obj, 9)); // Now drop the segment. wasm.elem_drop(); assertTraps(kTrapElementSegmentOutOfBounds, () => wasm.init_passive(array_obj, 5, 1, 3)); })(); (function TestArrayInitElemImmutable() { print(arguments.callee.name); let builder = new WasmModuleBuilder(); let array = builder.addArray(kWasmFuncRef, false); let elem1 = builder.addFunction("succ", kSig_i_i) .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Add]); let elem2 = builder.addFunction("pred", kSig_i_i) .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]); let passive = builder.addPassiveDataSegment([elem1.index, elem2.index, elem1.index, elem2.index]); builder.addFunction( "init_passive", makeSig([wasmRefNullType(array), kWasmI32, kWasmI32, kWasmI32], [])) .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprLocalGet, 2, kExprLocalGet, 3, kGCPrefix, kExprArrayInitElem, array, passive]) .exportFunc(); assertThrows(() => builder.instantiate(), WebAssembly.CompileError, /array.init_elem can only be used with mutable arrays/); })();