Borrow the array of an inline Atomics operation from its local

emitInlineAtomicsOp read its typed array with emitExpr, so every Atomics
call on a local array retained the array before the atomic instruction and
released it after, two out-of-line calls around one instruction. The
array now goes through emitBorrowedInput: an unchanged, unboxed local owns
it across the later operands and the operation, and the instruction and
scr_atomics_op only read it. Fields, globals, captures and bindings an
operand can reassign keep their own reference.
This commit is contained in:
Chris Tate
2026-10-22 20:57:07 -05:00
parent e501b046d5
commit f3bb5d2cb1
2 changed files with 32 additions and 1 deletions
@@ -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);
+29
View File
@@ -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/);
});