diff --git a/packages/compiler/src/backend/llvm/expr-bytes.ts b/packages/compiler/src/backend/llvm/expr-bytes.ts index c16ae856a..41ddf2b9d 100644 --- a/packages/compiler/src/backend/llvm/expr-bytes.ts +++ b/packages/compiler/src/backend/llvm/expr-bytes.ts @@ -1118,7 +1118,9 @@ export function emitInlineAtomicsOp( if (op !== 0 && op !== 1 && op !== 3 && ATOMIC_RMW[op] === undefined) return null; const elem = array.type.elem; const B = host.B; - const receiver = host.emitExpr(array).name; + // The instruction and scr_atomics_op borrow the array: an unchanged local + // owns it across the later operands and the operation. + const receiver = emitBorrowedInput(host, array).name; const idxValue = host.emitExpr(index); const operand = host.emitExpr(value); const swap = host.emitExpr(replacement); diff --git a/tests/harness/atomics-emission.test.ts b/tests/harness/atomics-emission.test.ts index 9ae06d904..2d708e382 100644 --- a/tests/harness/atomics-emission.test.ts +++ b/tests/harness/atomics-emission.test.ts @@ -61,3 +61,32 @@ console.log(work(new Int32Array(buffer), new Uint8Array(buffer), new Int16Array( expect(calls).toHaveLength(9); for (const call of calls) expect(call).toMatch(/\) cold$/); }); + +test("an unchanged local array is borrowed across Atomics operations", async () => { + const llvm = await emittedLlvm( + "borrow", + `function count(buffer: SharedArrayBuffer, n: number): number { + const cells = new Int32Array(buffer); + for (let i = 0; i < n; i++) { + Atomics.add(cells, 0, 1); + Atomics.compareExchange(cells, 1, Atomics.load(cells, 1), i); + } + return Atomics.load(cells, 0); +} +function swap(buffer: SharedArrayBuffer, other: SharedArrayBuffer): number { + let cells = new Int32Array(buffer); + return Atomics.add(cells, ((cells = new Int32Array(other)), 0), 1); +} +const buffer = new SharedArrayBuffer(16); +console.log(count(buffer, 3), swap(buffer, new SharedArrayBuffer(16))); +`, + ); + // The local owns the array across each operation: no reference count + // operations around the atomic instructions. + const counted = body(llvm, "sc_bf_count"); + expect(counted).toMatch(/atomicrmw add ptr/); + expect(counted).not.toMatch(/call ptr @scr_bytes_retain_v/); + // An operand that reassigns the binding releases the old array, so the + // operation keeps its own reference. + expect(body(llvm, "sc_bf_swap")).toMatch(/call ptr @scr_bytes_retain_v/); +});