| // Copyright 2024 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-gc |
| |
| d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); |
| |
| (function TestRepeatedArrayFillWithGC() { |
| print(arguments.callee.name); |
| let builder = new WasmModuleBuilder(); |
| |
| let struct = builder.addStruct([makeField(kWasmI64, true)]); |
| let array = builder.addArray(wasmRefNullType(struct), true); |
| |
| let gcFunc = builder.addImport("m", "gc", kSig_v_v); |
| |
| // This needs to be large enough, so that we call into C++ instead of running |
| // a loop in the generated code. (see kArrayNewMinimumSizeForMemSet) |
| let arrayLength = 50; |
| |
| builder.addFunction("create", makeSig([kWasmI64], [wasmRefNullType(array)])) |
| .addLocals(wasmRefNullType(array), 1) |
| .addLocals(wasmRefNullType(struct), 1) |
| .addBody([ |
| // Create the fill value (young generation). |
| kExprLocalGet, 0, |
| kGCPrefix, kExprStructNew, struct, |
| kExprLocalTee, 2, |
| // Create an array. |
| // As the size is larger than kArrayNewMinimumSizeForMemSet (16), this will |
| // call to C++ for initializing the elements. The fill value is passed as a |
| // pointer. There were two issues with that: |
| // 1) For a reference value on pointer-compressed builds, this emitted a |
| // __ ChangeInt32ToInt64() operation causing the value to become |
| // untagged. |
| // 2) This untagged value was then stored in an untagged stack slot. So any |
| // GC would not visit this stack slot. Note that this bug doesn't seem |
| // possible to be triggered as the stack slot is not GVN'ed (so the |
| // second fill initializes a separate stack slot). |
| kExprI32Const, arrayLength, |
| kGCPrefix, kExprArrayNew, array, |
| kExprDrop, |
| // Trigger a gc. |
| kExprCallFunction, gcFunc, |
| // Create another array. |
| // The initialization emitted the same __ ChangeInt32ToInt64() for the fill |
| // value (the struct.new above). Due to GVN the compiler decides to reuse |
| // the instruction from above. However, due to the GC in between this |
| // "int64" now contains an outdated pointer pointing into the previous |
| // half-space for the young generation. (Note that anyways we should pass |
| // the decompressed pointer, zeroing out the upper half doesn't fit to our |
| // calling convention, it just happens to not be an issue as we only use the |
| // value to store it inside the tagged (compressed) slots inside the array.) |
| kExprLocalGet, 2, |
| kExprI32Const, arrayLength, |
| kGCPrefix, kExprArrayNew, array, |
| ]).exportFunc(); |
| |
| builder.addFunction("get", |
| makeSig([wasmRefNullType(array), kWasmI32], [kWasmI64])) |
| .addBody([ |
| kExprLocalGet, 0, |
| kExprLocalGet, 1, |
| kGCPrefix, kExprArrayGet, array, |
| kGCPrefix, kExprStructGet, struct, 0, |
| ]).exportFunc(); |
| |
| let wasm = builder.instantiate({m: {gc}}).exports; |
| let arr = wasm.create(43n); |
| for (let i = 0; i < arrayLength; ++i) { |
| assertEquals(43n, wasm.get(arr, i)); |
| } |
| })(); |