mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-01 07:33:12 +08:00
Read the receiver first when an element write's index can rebind it
An element write of a `T | undefined` binding holds a computed index in a temporary ahead of its two stores, which then read the receiver after the index. When the index can rebind the receiver (`a[(a = b, 0)] = u`), the write now keeps reading the receiver first, so it stores into the array the receiver held before the index ran. The key conversion of a union index (a sequence that declares a temporary and may read the constant "undefined" key) no longer counts as running other code, so such an index is still held.
This commit is contained in:
@@ -260,6 +260,9 @@ const PURE_KINDS = new Set<IrExpr["kind"]>([
|
||||
"unionIsTag",
|
||||
"unionNarrow",
|
||||
"unionWrap",
|
||||
// Its statements are checked on their own (declarations and assignments
|
||||
// are recorded writes).
|
||||
"seqExpr",
|
||||
]);
|
||||
|
||||
/** The locals evaluating `value` may assign, and whether it may run other
|
||||
@@ -273,7 +276,11 @@ function valueEffects(lowerer: Lowerer, value: IrExpr): { writes: Set<string>; o
|
||||
everyExpr(value, {
|
||||
expr: (e) => {
|
||||
if (e.kind === "assignExpr" || e.kind === "incDec") writes.add(e.localId);
|
||||
else if (e.kind === "call" ? !readers.has(e.callee) : !PURE_KINDS.has(e.kind)) opaque = true;
|
||||
else if (e.kind === "libCall") {
|
||||
// The "undefined" key of an undefined index is a constant.
|
||||
if (e.fn !== "array.undefinedKey") opaque = true;
|
||||
} else if (e.kind === "call" ? !readers.has(e.callee) : !PURE_KINDS.has(e.kind))
|
||||
opaque = true;
|
||||
return true;
|
||||
},
|
||||
stmt: (s) => {
|
||||
@@ -314,6 +321,16 @@ function readsMutableLocal(lowerer: Lowerer, e: IrExpr): boolean {
|
||||
});
|
||||
}
|
||||
|
||||
/** Whether `receiver` still reads the same value after `index` runs: the
|
||||
* index assigns none of the bindings it reads, and runs no code that could
|
||||
* assign them through a closure. */
|
||||
export function unaffectedByIndex(lowerer: Lowerer, receiver: IrExpr, index: IrExpr): boolean {
|
||||
const effects = valueEffects(lowerer, index);
|
||||
return (
|
||||
unaffectedBy(receiver, effects) && !(effects.opaque && readsMutableLocal(lowerer, receiver))
|
||||
);
|
||||
}
|
||||
|
||||
/** A number, string or boolean element type. Arrays of it hold a present
|
||||
* undefined in the element's state, so `(T | undefined)[]` stores T
|
||||
* elements (the type mapper) and a T[] seen as one stays the same array. */
|
||||
|
||||
@@ -66,6 +66,7 @@ import {
|
||||
lowerSafeIndexRead,
|
||||
primitiveElement,
|
||||
unionArrayValueRead,
|
||||
unaffectedByIndex,
|
||||
} from "../array-values.js";
|
||||
import { lowerOptionalStringIndex, lowerUnionStringIndex } from "../string-index.js";
|
||||
import { InternalCompilerError } from "../../../errors.js";
|
||||
@@ -2479,7 +2480,9 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression):
|
||||
// A binding's `T | undefined` value is stored by two writes that both
|
||||
// read the index (arrayValueStore). A computed index, such as the key
|
||||
// conversion of a union index that declares a temporary, is held once
|
||||
// ahead of them, after the receiver and before the value as in source order.
|
||||
// ahead of them. The stores then read the receiver after the index, so
|
||||
// an index that can rebind the receiver (`a[(a = b, 0)] = u`) keeps the
|
||||
// stores as they are, reading the receiver first.
|
||||
if (
|
||||
stored.kind === "varRef" &&
|
||||
stored.type.kind === "union" &&
|
||||
@@ -2487,7 +2490,8 @@ export function lowerElementWrite(lowerer: Lowerer, expr: ts.BinaryExpression):
|
||||
!typeEquals(stored.type, arr.type.elem) &&
|
||||
arr.kind === "varRef" &&
|
||||
index.kind !== "varRef" &&
|
||||
index.kind !== "numLit"
|
||||
index.kind !== "numLit" &&
|
||||
unaffectedByIndex(lowerer, arr, index)
|
||||
) {
|
||||
const loc = locOf(expr);
|
||||
const held = lowerer.declareHiddenLocal("%arrayIndex", index.type);
|
||||
|
||||
@@ -179,3 +179,17 @@ function nested(grid: number[][]): number {
|
||||
return s;
|
||||
}
|
||||
console.log(nested([[1, 2], [3]]));
|
||||
|
||||
// The index rebinds the receiver: the store goes to the array the receiver
|
||||
// held before the index ran, as for any element assignment.
|
||||
let target: number[] = [1, 2, 3];
|
||||
const original = target;
|
||||
const other: number[] = [7, 8, 9];
|
||||
const keyed: number[] = [1];
|
||||
const missing = sparse[1];
|
||||
const kept = sparse[0];
|
||||
target[(target = other, keyed[0])] = missing;
|
||||
console.log(original, target === other, 1 in original, other);
|
||||
target = original;
|
||||
target[(target = other, keyed[0])] = kept;
|
||||
console.log(original, other);
|
||||
|
||||
Reference in New Issue
Block a user