perf(packages/codegen): reduce megamorphic property accesses for BinaryExpressions (#27459)

Perf optimization. Continuation of #27458.

The branch of `printExpression` for `BinaryExpression` (common path) reads the `type` of the left-hand node in order to check if it's a `PrivateInExpression`. This is a costly megamorphic access.

In the common case where it's _not_ a `PrivateInExpression`, it then calls `printBinaryish` which then accesses `node.left.type` again. This incurs the megamorphic load cost twice.

Instead, pass the extracted value of `node.left.type` into `printBinaryish` so it can avoid the 2nd lookup.
This commit is contained in:
overlookmotel committed 2026-10-08 17:07:15 +00:00
1 parent 675ec5e879
commit 3d2f93f87c
2 files changed
+14 -7

No files matched your search

+5 -3
View File
@@ -1,6 +1,6 @@
// Binary/logical expressions (port of `binary_expr_visitor.rs`).
import { typeAssertIs } from "../asserts.ts";
import { debugAssert, typeAssertIs } from "../asserts.ts";
import { CAT_CLOSE_BRACKET, CAT_OTHER } from "./categories.ts";
import { write } from "./write.ts";
import { printPrivateInExpression, printExpression } from "./expression.ts";
@@ -17,7 +17,6 @@ import type {
LogicalExpression,
LogicalOperator,
ParenthesizedExpression,
PrivateIdentifier,
PrivateInExpression,
} from "../../../../npm/oxc-types/types.d.ts";
@@ -49,12 +48,14 @@ interface BinaryVisitor {
* @param state - Printer state
* @param precedence - Precedence of the position this expression sits in, deciding parenthesisation
* @param ctx - Context flags, carrying whether `in` is forbidden and calls are
* @param leftType - `node.left.type`, which the caller has already read
*/
export function printBinaryish(
node: BinaryExpression | LogicalExpression,
state: State,
precedence: number,
ctx: number,
leftType: string,
): void {
// The pending outer levels are threaded through `parent` rather than a separate stack array
let v: BinaryVisitor | null = {
@@ -70,8 +71,9 @@ export function printBinaryish(
// An operand can be any expression, so reading its `type` is a megamorphic load.
// Each left operand's `type` is read once, and carried down the left spine to the next level.
// The root's is read by the caller, for its private-in check, and passed in as `leftType`.
let { left } = node;
let leftType: (Expression | PrivateIdentifier)["type"] = left.type;
debugAssert(leftType === left.type, "`leftType` must be the `type` of `node.left`");
// At the top of each iteration, `left` is `v.e.left`, and `leftType` is its `type`
for (;;) {
+9 -4
View File
@@ -110,17 +110,22 @@ export function printExpression(
case "Literal":
printLiteral(node, state, precedence, ctx);
break;
case "BinaryExpression":
if (node.left.type === "PrivateIdentifier") {
case "BinaryExpression": {
// `node.left` can be any expression, so reading its `type` is a megamorphic load.
// Read it once here, for both the private-in check and `printBinaryish`.
const leftType = node.left.type;
if (leftType === "PrivateIdentifier") {
typeAssertIs<ESTree.PrivateInExpression>(node);
printPrivateInExpression(node, state, precedence);
} else {
typeAssertIs<ESTree.BinaryExpression>(node);
printBinaryish(node, state, precedence, ctx);
printBinaryish(node, state, precedence, ctx, leftType);
}
break;
}
case "LogicalExpression":
printBinaryish(node, state, precedence, ctx);
// The left operand of a logical expression can't be a private name, so no private-in check
printBinaryish(node, state, precedence, ctx, node.left.type);
break;
case "ObjectExpression":
printObjectExpression(node, state);