From 7b77b497810273c2a58f812d5082fab05333893e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 19 Sep 2019 08:26:52 -0700 Subject: [PATCH 1/3] Enable caching for control flow paths that precede loops --- src/compiler/checker.ts | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 463d00c13e267..19ffdf46ae0f4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17309,24 +17309,31 @@ namespace ts { const antecedentTypes: Type[] = []; let subtypeReduction = false; let firstAntecedentType: FlowType | undefined; - flowLoopNodes[flowLoopCount] = flow; - flowLoopKeys[flowLoopCount] = key; - flowLoopTypes[flowLoopCount] = antecedentTypes; for (const antecedent of flow.antecedents!) { - flowLoopCount++; - const flowType = getTypeAtFlowNode(antecedent); - flowLoopCount--; + let flowType; if (!firstAntecedentType) { - firstAntecedentType = flowType; + // The first antecedent of a loop junction is always the non-looping control + // flow path that leads to the top. + flowType = firstAntecedentType = getTypeAtFlowNode(antecedent); } - const type = getTypeFromFlowType(flowType); - // If we see a value appear in the cache it is a sign that control flow analysis - // was restarted and completed by checkExpressionCached. We can simply pick up - // the resulting type and bail out. - const cached = cache.get(key); - if (cached) { - return cached; + else { + // All but the first antecedent are the looping control flow paths that lead + // back to the loop junction. We track these on the flow loop stack. + flowLoopNodes[flowLoopCount] = flow; + flowLoopKeys[flowLoopCount] = key; + flowLoopTypes[flowLoopCount] = antecedentTypes; + flowLoopCount++; + flowType = getTypeAtFlowNode(antecedent); + flowLoopCount--; + // If we see a value appear in the cache it is a sign that control flow analysis + // was restarted and completed by checkExpressionCached. We can simply pick up + // the resulting type and bail out. + const cached = cache.get(key); + if (cached) { + return cached; + } } + const type = getTypeFromFlowType(flowType); pushIfUnique(antecedentTypes, type); // If an antecedent type is not a subset of the declared type, we need to perform // subtype reduction. This happens when a "foreign" type is injected into the control From d4ad0d53dcb9b5fc529f6455074fad1230522f66 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 20 Sep 2019 06:47:42 -0700 Subject: [PATCH 2/3] Cheaper caching scheme --- src/compiler/checker.ts | 54 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 19ffdf46ae0f4..b1d7a00bd485d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -819,6 +819,7 @@ namespace ts { let flowLoopCount = 0; let sharedFlowCount = 0; let flowAnalysisDisabled = false; + let flowInvocationCount = 0; const emptyStringType = getLiteralType(""); const zeroType = getLiteralType(0); @@ -834,8 +835,7 @@ namespace ts { const symbolLinks: SymbolLinks[] = []; const nodeLinks: NodeLinks[] = []; const flowLoopCaches: Map[] = []; - const flowAssignmentKeys: string[] = []; - const flowAssignmentTypes: FlowType[] = []; + const flowAssignmentTypes: Type[] = []; const flowLoopNodes: FlowNode[] = []; const flowLoopKeys: string[] = []; const flowLoopTypes: Type[][] = []; @@ -16698,12 +16698,6 @@ namespace ts { getInitialTypeOfBindingElement(node); } - function getInitialOrAssignedType(node: VariableDeclaration | BindingElement | Expression, reference: Node) { - return getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? - getInitialType(node) : - getAssignedType(node), reference); - } - function isEmptyArrayAssignment(node: VariableDeclaration | BindingElement | Expression) { return node.kind === SyntaxKind.VariableDeclaration && (node).initializer && isEmptyArrayLiteral((node).initializer!) || @@ -16990,6 +16984,7 @@ namespace ts { if (!reference.flowNode || !couldBeUninitialized && !(declaredType.flags & TypeFlags.Narrowable)) { return declaredType; } + flowInvocationCount++; const sharedFlowStart = sharedFlowCount; const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(reference.flowNode)); sharedFlowCount = sharedFlowStart; @@ -17022,15 +17017,6 @@ namespace ts { flowDepth++; while (true) { const flags = flow.flags; - if (flags & FlowFlags.Cached) { - const key = getOrSetCacheKey(); - if (key) { - const id = getFlowNodeId(flow); - if (flowAssignmentKeys[id] === key) { - return flowAssignmentTypes[id]; - } - } - } if (flags & FlowFlags.Shared) { // We cache results of flow type resolution for shared nodes that were previously visited in // the same getFlowTypeOfReference invocation. A node is considered shared when it is the @@ -17061,15 +17047,6 @@ namespace ts { flow = (flow).antecedent; continue; } - else if (flowLoopCount === flowLoopStart) { // Only cache assignments when not within loop analysis - const key = getOrSetCacheKey(); - if (key && !isIncomplete(type)) { - flow.flags |= FlowFlags.Cached; - const id = getFlowNodeId(flow); - flowAssignmentKeys[id] = key; - flowAssignmentTypes[id] = type; - } - } } else if (flags & FlowFlags.Condition) { type = getTypeAtFlowCondition(flow); @@ -17122,6 +17099,27 @@ namespace ts { } } + function getInitialOrAssignedType(flow: FlowAssignment, reference: Node) { + const node = flow.node; + if (flow.flags & FlowFlags.Cached) { + const cached = flowAssignmentTypes[getNodeId(node)]; + if (cached) { + return cached; + } + } + const startInvocationCount = flowInvocationCount; + const type = getConstraintForLocation(node.kind === SyntaxKind.VariableDeclaration || node.kind === SyntaxKind.BindingElement ? + getInitialType(node) : + getAssignedType(node), reference); + // We cache the assigned type when getFlowTypeOfReference was recursively invoked in the + // resolution of the assigned type and we're not within loop analysis. + if (flowInvocationCount !== startInvocationCount && flowLoopCount === flowLoopStart) { + flow.flags |= FlowFlags.Cached; + flowAssignmentTypes[getNodeId(node)] = type; + } + return type; + } + function getTypeAtFlowAssignment(flow: FlowAssignment) { const node = flow.node; // Assignments only narrow the computed type if the declared type is a union type. Thus, we @@ -17135,11 +17133,11 @@ namespace ts { if (isEmptyArrayAssignment(node)) { return getEvolvingArrayType(neverType); } - const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(node, reference)); + const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(flow, reference)); return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; } if (declaredType.flags & TypeFlags.Union) { - return getAssignmentReducedType(declaredType, getInitialOrAssignedType(node, reference)); + return getAssignmentReducedType(declaredType, getInitialOrAssignedType(flow, reference)); } return declaredType; } From 007b4b1e31623aa8a108219262819d07ba63202a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 20 Sep 2019 10:24:02 -0700 Subject: [PATCH 3/3] Minor simplification --- src/compiler/checker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b1d7a00bd485d..28e20febdcdf8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17099,7 +17099,7 @@ namespace ts { } } - function getInitialOrAssignedType(flow: FlowAssignment, reference: Node) { + function getInitialOrAssignedType(flow: FlowAssignment) { const node = flow.node; if (flow.flags & FlowFlags.Cached) { const cached = flowAssignmentTypes[getNodeId(node)]; @@ -17133,11 +17133,11 @@ namespace ts { if (isEmptyArrayAssignment(node)) { return getEvolvingArrayType(neverType); } - const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(flow, reference)); + const assignedType = getBaseTypeOfLiteralType(getInitialOrAssignedType(flow)); return isTypeAssignableTo(assignedType, declaredType) ? assignedType : anyArrayType; } if (declaredType.flags & TypeFlags.Union) { - return getAssignmentReducedType(declaredType, getInitialOrAssignedType(flow, reference)); + return getAssignmentReducedType(declaredType, getInitialOrAssignedType(flow)); } return declaredType; }