mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-30 07:14:39 +08:00
Borrow typed-array locals read from fields and parameters (#1481)
This commit is contained in:
@@ -5385,8 +5385,8 @@ export class LlEmitter {
|
||||
}
|
||||
|
||||
/** A reference held as one plain pointer that emitReadReceiver can read
|
||||
* without owning: class instances, records, arrays, strings and
|
||||
* nullable-pointer unions. Tagged union boxes, closures and dynamic
|
||||
* without owning: class instances, records, arrays, strings, typed arrays
|
||||
* and nullable-pointer unions. Tagged union boxes, closures and dynamic
|
||||
* values keep ordinary ownership. */
|
||||
plainReference(type: IrType): boolean {
|
||||
switch (type.kind) {
|
||||
@@ -5394,6 +5394,7 @@ export class LlEmitter {
|
||||
case "record":
|
||||
case "array":
|
||||
case "string":
|
||||
case "bytes":
|
||||
return true;
|
||||
case "union":
|
||||
return this.nullableUnions.has(type.unionId);
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { emitLlvmModule } from "../src/backend/llvm/emitter.js";
|
||||
import {
|
||||
F64,
|
||||
VOID,
|
||||
bytesOf,
|
||||
type IrExpr,
|
||||
type IrFunction,
|
||||
type IrModule,
|
||||
type IrStmt,
|
||||
} from "../src/ir/ir.js";
|
||||
import { validateModule } from "../src/ir/validate.js";
|
||||
|
||||
const loc = { file: "bytes-field-borrow.ts", start: 0, end: 0 };
|
||||
const units = bytesOf("u16");
|
||||
const scanner = { kind: "object" as const, className: "Scanner" };
|
||||
const self = (): IrExpr => ({ kind: "varRef", localId: "this", type: scanner, loc });
|
||||
const chars = (): IrExpr => ({
|
||||
kind: "fieldGet",
|
||||
obj: self(),
|
||||
className: "Scanner",
|
||||
field: "chars",
|
||||
type: units,
|
||||
loc,
|
||||
});
|
||||
const local = (): IrExpr => ({ kind: "varRef", localId: "c", type: units, loc });
|
||||
const read = (index: number): IrExpr => ({
|
||||
kind: "bytesIntrinsic",
|
||||
method: "get",
|
||||
receiver: local(),
|
||||
args: [{ kind: "numLit", value: index, type: F64, loc }],
|
||||
type: F64,
|
||||
loc,
|
||||
});
|
||||
|
||||
/** `const c = this.chars; ...between; return c[0] + c[1]`. */
|
||||
function method(name: string, between: IrStmt[]): IrFunction {
|
||||
return {
|
||||
name,
|
||||
params: [{ localId: "this", name: "this", type: scanner }],
|
||||
returnType: F64,
|
||||
locals: [
|
||||
{ id: "this", name: "this", type: scanner, mutable: false },
|
||||
{ id: "c", name: "c", type: units, mutable: false },
|
||||
],
|
||||
body: [
|
||||
{ kind: "varDecl", localId: "c", init: chars(), loc },
|
||||
...between,
|
||||
{
|
||||
kind: "return",
|
||||
value: { kind: "bin", op: "+", left: read(0), right: read(1), type: F64 },
|
||||
loc,
|
||||
},
|
||||
],
|
||||
loc,
|
||||
} as IrFunction;
|
||||
}
|
||||
|
||||
test("a typed-array local read from a field the rest of its block never writes borrows it", () => {
|
||||
const replace: IrStmt = {
|
||||
kind: "fieldSet",
|
||||
obj: self(),
|
||||
className: "Scanner",
|
||||
field: "chars",
|
||||
value: {
|
||||
kind: "bytesNew",
|
||||
source: { kind: "numLit", value: 4, type: F64, loc },
|
||||
type: units,
|
||||
loc,
|
||||
},
|
||||
loc,
|
||||
};
|
||||
const mod: IrModule = {
|
||||
irVersion: 16,
|
||||
sourceFile: loc.file,
|
||||
entry: "main",
|
||||
functions: [
|
||||
{ name: "main", params: [], returnType: VOID, locals: [], body: [], loc },
|
||||
method("%Scanner.peek", []),
|
||||
method("%Scanner.swap", [replace]),
|
||||
],
|
||||
classes: [{ name: "Scanner", fields: [{ name: "chars", type: units }], methods: [], loc }],
|
||||
};
|
||||
expect(validateModule(mod)).toEqual([]);
|
||||
const llvm = emitLlvmModule(mod, { pointerBits: 64 });
|
||||
const body = (name: string): string =>
|
||||
new RegExp(`^define [^\\n]*@sc_bf__x25_Scanner_${name}\\b[^]*?^}`, "m").exec(llvm)?.[0] ?? "";
|
||||
expect(body("peek")).not.toBe("");
|
||||
// The field keeps the array alive while the borrowed local reads it.
|
||||
expect(body("peek")).not.toContain("bytes_retain");
|
||||
expect(body("peek")).not.toContain("bytes_release");
|
||||
// Replacing the field could free the array the local holds: it keeps a reference.
|
||||
expect(body("swap")).toContain("call ptr @scr_bytes_retain_v(");
|
||||
});
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"order": [
|
||||
"<repo>/tests/corpus/5891-typed-array-field-borrows.ts"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
// Typed-array locals read from fields and parameters: reads while nothing
|
||||
// replaces the field, the field replaced directly or by a callee while the
|
||||
// local is in use, rebound parameters, views and detached-length reads.
|
||||
|
||||
class Scanner {
|
||||
chars: Uint16Array;
|
||||
pos = 0;
|
||||
constructor(text: string) {
|
||||
this.chars = Scanner.units(text);
|
||||
}
|
||||
static units(text: string): Uint16Array {
|
||||
const out = new Uint16Array(text.length + 2);
|
||||
for (let i = 0; i < text.length; i++) out[i] = text.charCodeAt(i);
|
||||
return out;
|
||||
}
|
||||
sum(): number {
|
||||
const chars = this.chars;
|
||||
let total = 0;
|
||||
for (let i = 0; i < chars.length; i++) total += chars[i]!;
|
||||
return total;
|
||||
}
|
||||
replaceThenRead(): number {
|
||||
const chars = this.chars;
|
||||
this.chars = Scanner.units("zz");
|
||||
return chars[0]! * 1000 + this.chars[0]!;
|
||||
}
|
||||
reset(text: string): void {
|
||||
this.chars = Scanner.units(text);
|
||||
}
|
||||
readAcrossReset(): string {
|
||||
const chars = this.chars;
|
||||
const before = chars[0]!;
|
||||
this.reset("q");
|
||||
return before + ":" + chars[1] + ":" + this.chars[0] + ":" + chars.length;
|
||||
}
|
||||
}
|
||||
|
||||
function firstOf(units: Uint16Array, fallback: Uint16Array): number {
|
||||
if (units.length === 0) units = fallback;
|
||||
let n = 0;
|
||||
while (n < units.length && units[n] !== 0) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
function walk(a: Uint8Array): number {
|
||||
let total = 0;
|
||||
while (a.length > 1) {
|
||||
total += a[0]!;
|
||||
a = a.subarray(1);
|
||||
}
|
||||
return total + a.length;
|
||||
}
|
||||
|
||||
const s = new Scanner("hello");
|
||||
console.log(s.sum(), s.replaceThenRead(), s.sum());
|
||||
const t = new Scanner("abc");
|
||||
console.log(t.readAcrossReset(), t.sum());
|
||||
console.log(firstOf(new Uint16Array(0), Scanner.units("xyz")), firstOf(Scanner.units("ab"), new Uint16Array(1)));
|
||||
console.log(walk(new Uint8Array([1, 2, 3, 4, 5])), walk(new Uint8Array(0)));
|
||||
|
||||
const holders: Scanner[] = [];
|
||||
for (let i = 0; i < 50; i++) holders.push(new Scanner("item" + i));
|
||||
let acc = 0;
|
||||
for (const h of holders) {
|
||||
acc += h.sum();
|
||||
acc += h.replaceThenRead();
|
||||
}
|
||||
console.log(acc);
|
||||
Reference in New Issue
Block a user