Mark typed-array header loads invariant (#2016)

This commit is contained in:
Chris Tate
2026-10-23 12:21:01 -05:00
committed by GitHub
3 changed files with 15 additions and 6 deletions
@@ -7,9 +7,15 @@ import type { LlvmEmitterContext } from "./expr-context.js";
* buffer. Neither overlaps the other, a native field, array storage or a
* module global's slot, so element stores leave header loads and global
* receiver slots loop-invariant. Untagged accesses and calls still alias
* every category. */
* every category.
*
* Header loads are also `!invariant.load`: no store or call between the
* construction of a view and its release changes them. Without it, a data
* pointer load ahead of a bounds branch is sunk into the in-bounds block,
* and LICM keeps a conditionally executed load inside the loop. */
export function bytesHeaderAlias(host: LlvmEmitterContext): string {
return host.memoryAliasAttachment("bytes:header");
const tag = host.memoryAliasAttachment("bytes:header");
return tag === "" ? "" : `${tag}, !invariant.load !{}`;
}
export function bytesElementAlias(host: LlvmEmitterContext): string {
@@ -249,6 +249,11 @@ test("emitted loops version the body and tag typed-array memory", () => {
const elements = /store double [^\n]*!tbaa !(\d+)/.exec(body)![1];
const header = /load ptr, ptr [^\n]*!tbaa !(\d+)/.exec(body)![1];
expect(elements).not.toBe(header);
// Header loads are invariant for the view's lifetime; element accesses
// are not.
expect(body).toMatch(/load ptr, ptr [^\n]*, !tbaa !\d+, !invariant\.load !\{\}$/m);
expect(body).toMatch(/load i64, ptr [^\n]*, !tbaa !\d+, !invariant\.load !\{\}$/m);
expect(body).not.toMatch(/(load double|store double)[^\n]*invariant\.load/);
});
test("loops to a typed array's length version the other typed arrays they index", () => {
@@ -64,6 +64,7 @@ import {
emitPairedLoopSafety,
} from "./counted-loops.js";
import { emitBytesLoopVersionGuard, type BytesLoopVersion } from "./bytes-loop-versions.js";
import { bytesHeaderAlias } from "./bytes-alias.js";
import { mangleLocal } from "../mangle.js";
import { LlvmUnsupportedError } from "./unsupported.js";
import type { LlValue } from "./expr-context.js";
@@ -1229,10 +1230,7 @@ export function emitStmtBody(emitter: LlEmitter, s: IrStmt): void {
}
B.line(`${inBounds} = icmp ult i32 ${narrow}, ${len}`);
} else {
emitter.markMemoryPointer(lenPtr, "bytes:header");
B.line(
`${len} = load ${emitter.sizeType}, ptr ${lenPtr}${emitter.fieldAliasAttachment(lenPtr)}`,
);
B.line(`${len} = load ${emitter.sizeType}, ptr ${lenPtr}${bytesHeaderAlias(emitter)}`);
B.line(`${inBounds} = icmp ult ${emitter.sizeType} ${index}, ${len}`);
}
B.condBr(inBounds, lb, exit);