Skip to content

Commit 9af313d

Browse files
authored
Use shared binary trampoline in checker (microsoft#43035)
1 parent 15fae38 commit 9af313d

File tree

2 files changed

+165
-87
lines changed

2 files changed

+165
-87
lines changed

src/compiler/checker.ts

Lines changed: 123 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ namespace ts {
357357
const keyofStringsOnly = !!compilerOptions.keyofStringsOnly;
358358
const freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : ObjectFlags.FreshLiteral;
359359

360+
const checkBinaryExpression = createCheckBinaryExpression();
360361
const emitResolver = createResolver();
361362
const nodeBuilder = createNodeBuilder();
362363

@@ -31144,92 +31145,142 @@ namespace ts {
3114431145
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
3114531146
}
3114631147

31147-
const enum CheckBinaryExpressionState {
31148-
MaybeCheckLeft,
31149-
CheckRight,
31150-
FinishCheck
31151-
}
31148+
function createCheckBinaryExpression() {
31149+
interface WorkArea {
31150+
readonly checkMode: CheckMode | undefined;
31151+
skip: boolean;
31152+
stackIndex: number;
31153+
/**
31154+
* Holds the types from the left-side of an expression from [0..stackIndex].
31155+
* Holds the type of the result at stackIndex+1. This allows us to reuse existing stack entries
31156+
* and avoid storing an extra property on the object (i.e., `lastResult`).
31157+
*/
31158+
typeStack: (Type | undefined)[];
31159+
}
3115231160

31153-
function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) {
31154-
const workStacks: {
31155-
expr: BinaryExpression[],
31156-
state: CheckBinaryExpressionState[],
31157-
leftType: (Type | undefined)[]
31158-
} = {
31159-
expr: [node],
31160-
state: [CheckBinaryExpressionState.MaybeCheckLeft],
31161-
leftType: [undefined]
31161+
const trampoline = createBinaryExpressionTrampoline(onEnter, onLeft, onOperator, onRight, onExit, foldState);
31162+
31163+
return (node: BinaryExpression, checkMode: CheckMode | undefined) => {
31164+
const result = trampoline(node, checkMode);
31165+
Debug.assertIsDefined(result);
31166+
return result;
3116231167
};
31163-
let stackIndex = 0;
31164-
let lastResult: Type | undefined;
31165-
while (stackIndex >= 0) {
31166-
node = workStacks.expr[stackIndex];
31167-
switch (workStacks.state[stackIndex]) {
31168-
case CheckBinaryExpressionState.MaybeCheckLeft: {
31169-
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
31170-
finishInvocation(checkExpression(node.right, checkMode));
31171-
break;
31172-
}
31173-
checkGrammarNullishCoalesceWithLogicalExpression(node);
31174-
const operator = node.operatorToken.kind;
31175-
if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
31176-
finishInvocation(checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
31177-
break;
31178-
}
31179-
advanceState(CheckBinaryExpressionState.CheckRight);
31180-
maybeCheckExpression(node.left);
31181-
break;
31182-
}
31183-
case CheckBinaryExpressionState.CheckRight: {
31184-
const leftType = lastResult!;
31185-
workStacks.leftType[stackIndex] = leftType;
31186-
const operator = node.operatorToken.kind;
31187-
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
31188-
if (operator === SyntaxKind.AmpersandAmpersandToken) {
31189-
const parent = walkUpParenthesizedExpressions(node.parent);
31190-
checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
31191-
}
31192-
checkTruthinessOfType(leftType, node.left);
31168+
31169+
function onEnter(node: BinaryExpression, state: WorkArea | undefined, checkMode: CheckMode | undefined) {
31170+
if (state) {
31171+
state.stackIndex++;
31172+
state.skip = false;
31173+
setLeftType(state, /*type*/ undefined);
31174+
setLastResult(state, /*type*/ undefined);
31175+
}
31176+
else {
31177+
state = {
31178+
checkMode,
31179+
skip: false,
31180+
stackIndex: 0,
31181+
typeStack: [undefined, undefined],
31182+
};
31183+
}
31184+
31185+
if (isInJSFile(node) && getAssignedExpandoInitializer(node)) {
31186+
state.skip = true;
31187+
setLastResult(state, checkExpression(node.right, checkMode));
31188+
return state;
31189+
}
31190+
31191+
checkGrammarNullishCoalesceWithLogicalExpression(node);
31192+
31193+
const operator = node.operatorToken.kind;
31194+
if (operator === SyntaxKind.EqualsToken && (node.left.kind === SyntaxKind.ObjectLiteralExpression || node.left.kind === SyntaxKind.ArrayLiteralExpression)) {
31195+
state.skip = true;
31196+
setLastResult(state, checkDestructuringAssignment(node.left, checkExpression(node.right, checkMode), checkMode, node.right.kind === SyntaxKind.ThisKeyword));
31197+
return state;
31198+
}
31199+
31200+
return state;
31201+
}
31202+
31203+
function onLeft(left: Expression, state: WorkArea, _node: BinaryExpression) {
31204+
if (!state.skip) {
31205+
return maybeCheckExpression(state, left);
31206+
}
31207+
}
31208+
31209+
function onOperator(operatorToken: BinaryOperatorToken, state: WorkArea, node: BinaryExpression) {
31210+
if (!state.skip) {
31211+
const leftType = getLastResult(state);
31212+
Debug.assertIsDefined(leftType);
31213+
setLeftType(state, leftType);
31214+
setLastResult(state, /*type*/ undefined);
31215+
const operator = operatorToken.kind;
31216+
if (operator === SyntaxKind.AmpersandAmpersandToken || operator === SyntaxKind.BarBarToken || operator === SyntaxKind.QuestionQuestionToken) {
31217+
if (operator === SyntaxKind.AmpersandAmpersandToken) {
31218+
const parent = walkUpParenthesizedExpressions(node.parent);
31219+
checkTestingKnownTruthyCallableOrAwaitableType(node.left, leftType, isIfStatement(parent) ? parent.thenStatement : undefined);
3119331220
}
31194-
advanceState(CheckBinaryExpressionState.FinishCheck);
31195-
maybeCheckExpression(node.right);
31196-
break;
31197-
}
31198-
case CheckBinaryExpressionState.FinishCheck: {
31199-
const leftType = workStacks.leftType[stackIndex]!;
31200-
const rightType = lastResult!;
31201-
finishInvocation(checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node));
31202-
break;
31221+
checkTruthinessOfType(leftType, node.left);
3120331222
}
31204-
default: return Debug.fail(`Invalid state ${workStacks.state[stackIndex]} for checkBinaryExpression`);
3120531223
}
3120631224
}
3120731225

31208-
return lastResult!;
31226+
function onRight(right: Expression, state: WorkArea, _node: BinaryExpression) {
31227+
if (!state.skip) {
31228+
return maybeCheckExpression(state, right);
31229+
}
31230+
}
31231+
31232+
function onExit(node: BinaryExpression, state: WorkArea): Type | undefined {
31233+
let result: Type | undefined;
31234+
if (state.skip) {
31235+
result = getLastResult(state);
31236+
}
31237+
else {
31238+
const leftType = getLeftType(state);
31239+
Debug.assertIsDefined(leftType);
31240+
31241+
const rightType = getLastResult(state);
31242+
Debug.assertIsDefined(rightType);
3120931243

31210-
function finishInvocation(result: Type) {
31211-
lastResult = result;
31212-
stackIndex--;
31244+
result = checkBinaryLikeExpressionWorker(node.left, node.operatorToken, node.right, leftType, rightType, node);
31245+
}
31246+
31247+
state.skip = false;
31248+
setLeftType(state, /*type*/ undefined);
31249+
setLastResult(state, /*type*/ undefined);
31250+
state.stackIndex--;
31251+
return result;
3121331252
}
3121431253

31215-
/**
31216-
* Note that `advanceState` sets the _current_ head state, and that `maybeCheckExpression` potentially pushes on a new
31217-
* head state; so `advanceState` must be called before any `maybeCheckExpression` during a state's execution.
31218-
*/
31219-
function advanceState(nextState: CheckBinaryExpressionState) {
31220-
workStacks.state[stackIndex] = nextState;
31254+
function foldState(state: WorkArea, result: Type | undefined, _side: "left" | "right") {
31255+
setLastResult(state, result);
31256+
return state;
3122131257
}
3122231258

31223-
function maybeCheckExpression(node: Expression) {
31259+
function maybeCheckExpression(state: WorkArea, node: Expression): BinaryExpression | undefined {
3122431260
if (isBinaryExpression(node)) {
31225-
stackIndex++;
31226-
workStacks.expr[stackIndex] = node;
31227-
workStacks.state[stackIndex] = CheckBinaryExpressionState.MaybeCheckLeft;
31228-
workStacks.leftType[stackIndex] = undefined;
31229-
}
31230-
else {
31231-
lastResult = checkExpression(node, checkMode);
31261+
return node;
3123231262
}
31263+
setLastResult(state, checkExpression(node, state.checkMode));
31264+
}
31265+
31266+
function getLeftType(state: WorkArea) {
31267+
return state.typeStack[state.stackIndex];
31268+
}
31269+
31270+
function setLeftType(state: WorkArea, type: Type | undefined) {
31271+
state.typeStack[state.stackIndex] = type;
31272+
}
31273+
31274+
function getLastResult(state: WorkArea) {
31275+
return state.typeStack[state.stackIndex + 1];
31276+
}
31277+
31278+
function setLastResult(state: WorkArea, type: Type | undefined) {
31279+
// To reduce overhead, reuse the next stack entry to store the
31280+
// last result. This avoids the overhead of an additional property
31281+
// on `WorkArea` and reuses empty stack entries as we walk back up
31282+
// the stack.
31283+
state.typeStack[state.stackIndex + 1] = type;
3123331284
}
3123431285
}
3123531286

0 commit comments

Comments
 (0)