mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-23 06:10:02 +08:00
`a.push(v)` where v may hold undefined at run time (an unchecked array read or a hole-observing for-of binding, as in tsc-ts's filterType loop) built a temporary one-element array, stored v or present undefined into it, and spread it onto the receiver: an allocation, a growth, a copy and two releases per append. Such pushes now evaluate every argument first, then append each present value through the ordinary push (and its inline dense fast path) and each missing one as a present-undefined slot at the current length, which is what Node stores. Pure receivers and arguments are re-read instead of copied into owned temporaries. Call arguments read from arrays of nullable references now take the inline dense lookup like other projections (the value is the element pointer itself, so the path is a few loads), and scr_arr_peek_ref answers dense in-range indices before the general lookup. compareSymbols' `declarations[0]!` arguments no longer leave compiled code.
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
// push() of a value that may be a hole or missing read appends directly to
|
|
// the receiver: present values take the ordinary append, missing ones a
|
|
// present-undefined slot. Holes, lengths, return values, evaluation order,
|
|
// and reference counts must match Node.
|
|
|
|
class Item {
|
|
readonly id: number;
|
|
readonly label: string;
|
|
constructor(id: number, label: string) {
|
|
this.id = id;
|
|
this.label = label;
|
|
}
|
|
}
|
|
|
|
function describe(items: readonly Item[]): string {
|
|
const parts: string[] = [];
|
|
for (let i = 0; i < items.length; i++) {
|
|
parts.push(i in items ? (items[i] === undefined ? "undef" : items[i]!.label) : "hole");
|
|
}
|
|
return `${items.length}:[${parts.join(",")}]`;
|
|
}
|
|
|
|
const a = new Item(1, "a");
|
|
const b = new Item(2, "b");
|
|
const c = new Item(3, "c");
|
|
|
|
// for-of observes holes as undefined; pushing them stores present undefined.
|
|
const sparse: Item[] = [a];
|
|
sparse[3] = b;
|
|
sparse.push(c);
|
|
const copied: Item[] = [];
|
|
let lengths = 0;
|
|
for (const item of sparse) lengths += copied.push(item);
|
|
console.log("for-of copy", describe(copied), lengths, 1 in copied, 2 in copied);
|
|
|
|
// The filterType shape: keep the constituents a predicate accepts.
|
|
function filterItems(items: Item[], keep: (item: Item) => boolean): Item[] {
|
|
const kept: Item[] = [];
|
|
for (const item of items) if (keep(item)) kept.push(item);
|
|
return kept;
|
|
}
|
|
const many: Item[] = [];
|
|
for (let i = 0; i < 40; i++) many.push(new Item(i, "i" + i));
|
|
const evens = filterItems(many, (item) => item.id % 2 === 0);
|
|
console.log("filtered", evens.length, evens[0]!.label, evens[19]!.label);
|
|
|
|
// Unchecked indexed reads past the end or into holes are missing values.
|
|
const source: Item[] = [a, b];
|
|
source[4] = c;
|
|
const reads: Item[] = [];
|
|
for (let i = 0; i < 6; i++) reads.push(source[i]);
|
|
console.log("reads", describe(reads));
|
|
|
|
// Several arguments: all evaluate before the first append, left to right.
|
|
const multi: Item[] = [c];
|
|
const pushed = multi.push(source[2], source[0], source[9], source[4]);
|
|
console.log("multi", pushed, describe(multi));
|
|
|
|
// Arguments that reassign or mutate the receiver: the receiver evaluated
|
|
// first keeps receiving the appends; the length is read after the values.
|
|
let target: Item[] = [a];
|
|
const first = target;
|
|
const second: Item[] = [];
|
|
const ret = target.push(source[3], ((target = second), source[1]));
|
|
console.log("reassigned", ret, describe(first), describe(second), target === second);
|
|
const shrinking: Item[] = [a, b, c];
|
|
const ret2 = shrinking.push(source[5], ((shrinking.length = 1), source[0]));
|
|
console.log("shrunk", ret2, describe(shrinking));
|
|
|
|
// Numbers and strings through the same path.
|
|
const nums: number[] = [1, 2];
|
|
nums[5] = 6;
|
|
const numCopy: number[] = [];
|
|
for (const n of nums) numCopy.push(n);
|
|
console.log("nums", numCopy.length, JSON.stringify(numCopy), 3 in numCopy);
|
|
const words: string[] = ["x"];
|
|
words[2] = "z";
|
|
const wordCopy: string[] = [];
|
|
for (const w of words) wordCopy.push(w);
|
|
console.log("words", wordCopy.length, JSON.stringify(wordCopy), 1 in wordCopy);
|
|
|
|
// Values pushed from a source that is dropped stay alive in the copy.
|
|
function detach(): Item[] {
|
|
const temp: Item[] = [new Item(7, "seven"), new Item(8, "eight")];
|
|
const out: Item[] = [];
|
|
for (const item of temp) out.push(item);
|
|
temp.length = 0;
|
|
return out;
|
|
}
|
|
const detached = detach();
|
|
console.log("detached", describe(detached), detached[1]!.id);
|
|
|
|
// A field receiver and a field value.
|
|
class Holder {
|
|
list: Item[] = [];
|
|
pending: Item[] = [b];
|
|
}
|
|
const holder = new Holder();
|
|
holder.pending[2] = c;
|
|
for (let i = 0; i < 4; i++) holder.list.push(holder.pending[i]);
|
|
console.log("holder", describe(holder.list));
|