mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-11-08 08:44:37 +08:00
Skip repeated probes in lowering
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
import { BOOL, type IrExpr, type IrModule, type IrStmt } from "../../ir/ir.js";
|
||||
import type { IrLibFn } from "../../ir/builtin-signatures.js";
|
||||
import { mapExprChildren, mapStmtChildren } from "../../ir/traverse.js";
|
||||
import { moduleRuntimeFeatures } from "../../ir/runtime-features.js";
|
||||
import { moduleUsesAsyncContext } from "../../ir/runtime-features.js";
|
||||
|
||||
/** The argument that is the completion callback: its index; "last" for
|
||||
* calls whose callback is the final argument when one is passed; or
|
||||
@@ -148,7 +148,7 @@ function bindCall(e: IrExpr): IrExpr {
|
||||
/** Binds the completion callbacks of `mod` when its scheduling carries
|
||||
* AsyncLocalStorage contexts. */
|
||||
export function bindCompletionCallbacks(mod: IrModule): void {
|
||||
if (!moduleRuntimeFeatures(mod).asyncContext) return;
|
||||
if (!moduleUsesAsyncContext(mod)) return;
|
||||
const rewriteExpr = (e: IrExpr): IrExpr => bindCall(mapExprChildren(e, rewriteExpr, rewriteStmt));
|
||||
const rewriteStmt = (s: IrStmt): IrStmt => mapStmtChildren(s, rewriteExpr, rewriteStmt);
|
||||
for (const fn of mod.functions) fn.body = fn.body.map(rewriteStmt);
|
||||
|
||||
@@ -2006,6 +2006,9 @@ function byName(fns: readonly IrFunction[]): IrFunction[] {
|
||||
);
|
||||
}
|
||||
|
||||
/** The program module a createRequire binding requires. */
|
||||
type CreateRequireTarget = NonNullable<ReturnType<typeof createRequireProgramModuleOf>>;
|
||||
|
||||
export class Lowerer {
|
||||
readonly frontendServices: FrontendServices | undefined;
|
||||
readonly checker: ts.TypeChecker;
|
||||
@@ -3478,7 +3481,7 @@ export class Lowerer {
|
||||
* tag. */
|
||||
readonly unions = new UnionRegistry();
|
||||
/** Context-free successful type mappings. See TypeMapperCtx.typeMemo. */
|
||||
readonly typeMemo = new Map<string, IrType>();
|
||||
readonly typeMemo = new Map<number, IrType>();
|
||||
readonly ambient = ambientDtsPath();
|
||||
readonly overridesAmbient = overridesDtsPath();
|
||||
readonly fallbackAmbient = fallbackDtsPath();
|
||||
@@ -4942,6 +4945,55 @@ export class Lowerer {
|
||||
return createRequireProgramModuleOf(this, decl.initializer)?.dep ?? null;
|
||||
}
|
||||
|
||||
private readonly createRequireBindingTargets = new Map<ts.Symbol, CreateRequireTarget | null>();
|
||||
private readonly createRequireElementTargets = new Map<
|
||||
ts.Symbol,
|
||||
{ element: ts.BindingElement; target: CreateRequireTarget } | null
|
||||
>();
|
||||
|
||||
/** The program module a `const x = createRequire(...)("./m")` binding
|
||||
* requires, by the binding's symbol; null for every other binding. Every
|
||||
* reference resolves it (resolveValueSymbol), so it is cached. */
|
||||
private createRequireBindingTarget(binding: ts.Symbol): CreateRequireTarget | null {
|
||||
const cached = this.createRequireBindingTargets.get(binding);
|
||||
if (cached !== undefined) return cached;
|
||||
const decl = binding.declarations.find(ts.isVariableDeclaration);
|
||||
const target =
|
||||
decl && ts.isIdentifier(decl.name) && decl.initializer
|
||||
? createRequireProgramModuleOf(this, decl.initializer)
|
||||
: null;
|
||||
this.createRequireBindingTargets.set(binding, target);
|
||||
return target;
|
||||
}
|
||||
|
||||
/** The binding element and required program module of a
|
||||
* `const { m } = createRequire(...)("./m")` binding, by the binding's
|
||||
* symbol; null for every other binding. Cached like
|
||||
* createRequireBindingTarget. */
|
||||
private createRequireElementTarget(
|
||||
binding: ts.Symbol,
|
||||
): { element: ts.BindingElement; target: CreateRequireTarget } | null {
|
||||
const cached = this.createRequireElementTargets.get(binding);
|
||||
if (cached !== undefined) return cached;
|
||||
let found: { element: ts.BindingElement; target: CreateRequireTarget } | null = null;
|
||||
const element = binding.declarations.find(ts.isBindingElement);
|
||||
if (
|
||||
element &&
|
||||
element.name !== undefined &&
|
||||
ts.isIdentifier(element.name) &&
|
||||
ts.isObjectBindingPattern(element.parent)
|
||||
) {
|
||||
const declaration = element.parent.parent;
|
||||
const target =
|
||||
ts.isVariableDeclaration(declaration) && declaration.initializer
|
||||
? createRequireProgramModuleOf(this, declaration.initializer)
|
||||
: null;
|
||||
if (target !== null) found = { element, target };
|
||||
}
|
||||
this.createRequireElementTargets.set(binding, found);
|
||||
return found;
|
||||
}
|
||||
|
||||
/** The represented single value behind a createRequire binding, or null
|
||||
* when the target is a namespace/table module. Collection has already
|
||||
* registered executable exports before entry-module uses lower, so the
|
||||
@@ -4949,9 +5001,7 @@ export class Lowerer {
|
||||
* static storage or callable/class metadata. */
|
||||
private createRequireWholeExportSymbolOf(ident: ts.Identifier): ts.Symbol | null {
|
||||
const binding = this.checker.getSymbolAtLocation(ident);
|
||||
const decl = binding ? binding.declarations.find(ts.isVariableDeclaration) : undefined;
|
||||
if (!decl || !ts.isIdentifier(decl.name) || !decl.initializer) return null;
|
||||
const target = createRequireProgramModuleOf(this, decl.initializer);
|
||||
const target = binding ? this.createRequireBindingTarget(binding) : null;
|
||||
if (target === null) return null;
|
||||
const moduleSym = this.checker.getSymbolAtLocation(target.dep);
|
||||
let symbol = moduleSym?.getExports().get("export=" as ts.__String);
|
||||
@@ -4996,18 +5046,9 @@ export class Lowerer {
|
||||
* target module's real export symbol. */
|
||||
private createRequireDestructuredExportSymbolOf(ident: ts.Identifier): ts.Symbol | null {
|
||||
const binding = this.checker.getSymbolAtLocation(ident);
|
||||
const element = binding ? binding.declarations.find(ts.isBindingElement) : undefined;
|
||||
if (
|
||||
!element ||
|
||||
element.name === undefined ||
|
||||
!ts.isIdentifier(element.name) ||
|
||||
!ts.isObjectBindingPattern(element.parent)
|
||||
)
|
||||
return null;
|
||||
const declaration = element.parent.parent;
|
||||
if (!ts.isVariableDeclaration(declaration) || !declaration.initializer) return null;
|
||||
const target = createRequireProgramModuleOf(this, declaration.initializer);
|
||||
if (target === null) return null;
|
||||
const found = binding ? this.createRequireElementTarget(binding) : null;
|
||||
if (found === null) return null;
|
||||
const { element, target } = found;
|
||||
const property = element.propertyName;
|
||||
const name =
|
||||
property && (ts.isIdentifier(property) || ts.isStringLiteralLike(property))
|
||||
@@ -15102,6 +15143,18 @@ export class Lowerer {
|
||||
* member access that IS a module in its own right (`fs.promises` — the
|
||||
* same object as node:fs/promises, Node's rule). Null otherwise. */
|
||||
builtinNamespaceModuleOf(expr: ts.Expression): string | null {
|
||||
// A function declaration is never a module namespace. Answering that
|
||||
// from the symbol skips the type mapping below (every direct call asks).
|
||||
if (ts.isIdentifier(expr)) {
|
||||
const symbol = this.checker.getSymbolAtLocation(expr);
|
||||
if (
|
||||
symbol !== undefined &&
|
||||
(symbol.flags & ts.SymbolFlags.Alias) === 0 &&
|
||||
symbol.declarations.length > 0 &&
|
||||
symbol.declarations.every(ts.isFunctionDeclaration)
|
||||
)
|
||||
return null;
|
||||
}
|
||||
// Default and CommonJS process bindings alias the global object, whose
|
||||
// methods and properties keep their existing process lowerings.
|
||||
if (stdlibGlobalNameOf(this, expr) === "process") return null;
|
||||
|
||||
@@ -1349,7 +1349,7 @@ export interface TypeMapperCtx {
|
||||
* member graph each time is quadratic work. Generic/mixin contexts opt out
|
||||
* through canMemoizeType because the same checker type can map differently
|
||||
* under different instantiation bindings. */
|
||||
typeMemo?: Map<string, IrType>;
|
||||
typeMemo?: Map<number, IrType>;
|
||||
canMemoizeType?: () => boolean;
|
||||
/** True for files the Lowerer actually compiles (its module order). A
|
||||
* class type can reach the entry through the TYPE world alone — a jsdoc
|
||||
@@ -1862,8 +1862,8 @@ export function mapType(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
const memoizable = typeId !== undefined && (ctx.canMemoizeType?.() ?? true);
|
||||
// An empty literal's type maps to the empty record as an unreduced union's
|
||||
// arm (emptyLiteralArm) and to the checked-dynamic value elsewhere: the two
|
||||
// answers keep separate memo entries.
|
||||
const memoKey = memoizable ? `${ctx.emptyLiteralArm ? 1 : 0}:${typeId}` : null;
|
||||
// answers keep separate memo entries. Type ids are non-negative integers.
|
||||
const memoKey = memoizable ? typeId * 2 + (ctx.emptyLiteralArm ? 1 : 0) : null;
|
||||
if (memoKey !== null) {
|
||||
const memo = ctx.typeMemo?.get(memoKey);
|
||||
if (memo !== undefined) return memo;
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
RUNTIME_EVENT_TARGET_CLASS,
|
||||
RUNTIME_STREAM_CLASSES,
|
||||
} from "./ir.js";
|
||||
import { everyModuleNode } from "./traverse.js";
|
||||
import { everyModuleNode, everyStmtList } from "./traverse.js";
|
||||
import { eachNodeOfKinds, type IrNodeIndex } from "./node-index.js";
|
||||
import { JsIcSites, hasJsIcSite } from "./js-ic.js";
|
||||
|
||||
@@ -1383,6 +1383,17 @@ function scanRuntimeFeatures(
|
||||
* switch that pulls scr_regex.c + the vendored libregexp into the binary
|
||||
* (native-toolchain.ts); regex-free programs keep the historical command line. The
|
||||
* typed traversal visits type slots as well as executable nodes. */
|
||||
/** True when the module enters AsyncLocalStorage stores (RuntimeFeatures
|
||||
* asyncContext): a walk for those libCalls alone, without the other
|
||||
* features' rules. */
|
||||
export function moduleUsesAsyncContext(mod: IrModule): boolean {
|
||||
const visitor = {
|
||||
expr: (e: IrExpr): boolean => e.kind !== "libCall" || !ASYNC_CONTEXT_LIB_FNS.has(e.fn),
|
||||
stmt: (): boolean => true,
|
||||
};
|
||||
return !mod.functions.every((fn) => everyStmtList(fn.body, visitor));
|
||||
}
|
||||
|
||||
export function moduleUsesRegex(mod: IrModule): boolean {
|
||||
return scanRuntimeFeatures(mod, "regex").regex;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user