perf(packages/codegen): reduce megamorphic type reads in binCheckAndPrepare (#27460)

Perf optimization. Continuation of #27458.

Ensure that costly megamorphic reads of the `type` of `Expression`s happens the minimum of times.

Pass `left` (with any `ParenthesizedExpression` wrappers stripped off) and `left.type` into `binCheckAndPrepare`, so in the paths for `??` and `**`, the unwrapping and megamorphic `.type` accesses don't need to be repeated again. `**` is rare, but `??` is quite heavily used in some codebases.

Passing these 2 vars as params doesn't cost anything, as `binCheckAndPrepare` gets inlined anyway.
This commit is contained in:
overlookmotel committed 2026-10-08 17:07:16 +00:00
1 parent 3d2f93f87c
commit 961f318571
2 files changed
+34 -15

No files matched your search

+31 -15
View File
@@ -5,11 +5,10 @@ import { CAT_CLOSE_BRACKET, CAT_OTHER } from "./categories.ts";
import { write } from "./write.ts";
import { printPrivateInExpression, printExpression } from "./expression.ts";
import { BIN_PRECEDENCE, CTX_FORBID_IN, PADDED_BIN_OPERATORS } from "./operators.ts";
import { withoutParens } from "./parens.ts";
import { PREC_CALL, PREC_EXPONENTIATION, PREC_LOWEST, PREC_PREFIX } from "./precedence.ts";
import type { State } from "../state.ts";
import type { LiteralExtras } from "./types.ts";
import type { Literal, LiteralExtras } from "./types.ts";
import type {
BinaryExpression,
BinaryOperator,
@@ -77,13 +76,13 @@ export function printBinaryish(
// At the top of each iteration, `left` is `v.e.left`, and `leftType` is its `type`
for (;;) {
binCheckAndPrepare(v, state);
while (leftType === "ParenthesizedExpression") {
left = (left as ParenthesizedExpression).expression;
leftType = left.type;
}
binCheckAndPrepare(v, state, left, leftType);
let nextLeft;
if (leftType === "BinaryExpression") {
nextLeft = (left as BinaryExpression | PrivateInExpression).left;
@@ -137,8 +136,19 @@ export function printBinaryish(
*
* `**` is right associative, so its left operand binds tighter, and the rest are the other way round.
* `??` may not sit unparenthesized beside `&&` or `||`, which is why either operand can be forced up to `PREC_PREFIX`.
*
* @param v - The level being prepared. Its `operator` holds the parent level's operator on entry,
* and is replaced with this level's own.
* @param state - Printer state
* @param left - `v.e.left`, with any parens unwrapped
* @param leftType - `left.type`
*/
function binCheckAndPrepare(v: BinaryVisitor, state: State): void {
function binCheckAndPrepare(
v: BinaryVisitor,
state: State,
left: Expression,
leftType: string,
): void {
const { e } = v;
const eOperator = e.operator;
const ePrecedence = BIN_PRECEDENCE[eOperator];
@@ -169,27 +179,33 @@ function binCheckAndPrepare(v: BinaryVisitor, state: State): void {
v.rightPrecedence = ePrecedence;
}
// The operands can be any expression, so reading their `type` is a megamorphic load.
// The left operand arrives from `printBinaryish` already unwrapped, with its `type` already read.
// The right operand's `type` is read only once, while unwrapping parens.
if (eOperator === "??") {
// Nullish coalescing cannot mix with && / || unparenthesized
const left = withoutParens(e.left);
if (left.type === "LogicalExpression" && left.operator !== "??") {
if (leftType === "LogicalExpression" && (left as LogicalExpression).operator !== "??") {
v.leftPrecedence = PREC_PREFIX;
}
const right = withoutParens(e.right);
if (right.type === "LogicalExpression" && right.operator !== "??") {
let { right } = e;
let rightType = right.type;
while (rightType === "ParenthesizedExpression") {
right = (right as ParenthesizedExpression).expression;
rightType = right.type;
}
if (rightType === "LogicalExpression" && (right as LogicalExpression).operator !== "??") {
v.rightPrecedence = PREC_PREFIX;
}
} else if (eOperator === "**") {
// The base of `**` must be an `UpdateExpression`.
// Unary/await bases and negative-printing literals must be parenthesized.
const left = withoutParens(e.left);
if (
left.type === "UnaryExpression"
|| left.type === "AwaitExpression"
|| (TS && left.type === "TSTypeAssertion")
|| (left.type === "Literal"
&& (typeof left.value === "number" || (left as LiteralExtras).bigint != null))
leftType === "UnaryExpression"
|| leftType === "AwaitExpression"
|| (TS && leftType === "TSTypeAssertion")
|| (leftType === "Literal"
&& (typeof (left as Literal).value === "number" || (left as LiteralExtras).bigint != null))
) {
v.leftPrecedence = PREC_CALL;
}
+3
View File
@@ -18,6 +18,9 @@ export interface UnknownNode {
type: "<unknown node>";
}
/** An `Expression` which is a literal. */
export type Literal = Extract<ESTree.Expression, { type: "Literal" }>;
/**
* Properties which only some literals carry. `printLiteral` reads them on any literal to tell
* regexes and bigints apart from the rest.