Skip to content

Run lone array callbacks as loops with their bodies inlined - #1997

Open
ctate wants to merge 3 commits into
mainfrom
ctate/run-lone-array-3e2401
Open

ctate wants to merge 3 commits into
mainfrom
ctate/run-lone-array-3e2401

Conversation

@ctate

@ctate ctate commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator
  • A single map, filter, flatMap, forEach, reduce, some or every call whose callback is a closure literal with unchanging captures now runs as a loop with the callback body copied in, its captures passed by value, and its result allocated once.
  • ast-interp runs 6.5% faster in bench:runtime --layouts=4 (0.34x → 0.32x of Node); the other changed workloads are neutral, and most changed executables get smaller.
  • Int32[] contracts check the values such a map or filter stores, so xs.map((x) => (x * 2) | 0) can initialize an Int32[].

@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
scriptc Ready Ready Preview, v0 Oct 24, 2026 11:51am UTC

Comment on lines +940 to +949
const inherited = new Set((fn.captures ?? []).map((c) => c.localId));
const written = this.written.get(fn.name);
return (arg) => {
const target = this.closureTarget(arg);
if (!target || !written || arg.kind !== "closure") return null;
if (!this.receivers.ignores(target)) return null;
for (const id of arg.captures) {
const local = locals.get(id);
if (!local || local.tdz || inherited.has(id) || written.has(id)) return null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a local class method changes a binding captured by an array callback, the lone-call optimizer treats that binding as stable and replaces its live reads with a stale snapshot.

Fix on Vercel

Comment on lines +943 to +945
const target = this.closureTarget(arg);
if (!target || !written || arg.kind !== "closure") return null;
if (!this.receivers.ignores(target)) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inlining a callback with an Int32 parameter removes its incoming call from contract analysis, so non-int32 array elements are silently accepted for that parameter.

Fix on Vercel

...(frame === undefined ? {} : { frame }),
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new lone-call path inlines callbacks with switch-local records or foldable unions, but cleanup removes their locals without rewriting their switch-case declarations, causing SC9001 compilation failures.

Fix on Vercel

Comment on lines +1282 to +1286
case "while":
case "doWhile":
case "for":
case "block":
return st.labels ? { ...st, labels: st.labels.map(label) } : st;

@vercel vercel Bot Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A lone array callback containing a labeled switch and a break to that label fails compilation because the break is renamed but the switch label is not.

Fix on Vercel

This branch was successfully deployed

1 active deployment
Preview — f23447dc Deployed Oct 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant