Skip to content

Commit

Permalink
Cache control flow results across invocations (#31003)
Browse files Browse the repository at this point in the history
* Modify flow loop cache key to include all inputs

* Add test case, cache similarly to loop cache, reuse loop cache key (now corrected)

* Use simpler singleton key and type cache for FlowAssignment nodes
  • Loading branch information
weswigham committed May 10, 2019
1 parent 39e9a2b commit d8f2702
Show file tree
Hide file tree
Showing 6 changed files with 28,169 additions and 8 deletions.
43 changes: 35 additions & 8 deletions src/compiler/checker.ts
Expand Up @@ -559,6 +559,8 @@ namespace ts {
const symbolLinks: SymbolLinks[] = [];
const nodeLinks: NodeLinks[] = [];
const flowLoopCaches: Map<Type>[] = [];
const flowAssignmentKeys: string[] = [];
const flowAssignmentTypes: FlowType[] = [];
const flowLoopNodes: FlowNode[] = [];
const flowLoopKeys: string[] = [];
const flowLoopTypes: Type[][] = [];
Expand Down Expand Up @@ -15666,21 +15668,21 @@ namespace ts {
// The result is undefined if the reference isn't a dotted name. We prefix nodes
// occurring in an apparent type position with '@' because the control flow type
// of such nodes may be based on the apparent type instead of the declared type.
function getFlowCacheKey(node: Node): string | undefined {
function getFlowCacheKey(node: Node, declaredType: Type, initialType: Type, flowContainer: Node | undefined): string | undefined {
switch (node.kind) {
case SyntaxKind.Identifier:
const symbol = getResolvedSymbol(<Identifier>node);
return symbol !== unknownSymbol ? (isConstraintPosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
return symbol !== unknownSymbol ? `${flowContainer ? getNodeId(flowContainer) : "-1"}|${getTypeId(declaredType)}|${getTypeId(initialType)}|${isConstraintPosition(node) ? "@" : ""}${getSymbolId(symbol)}` : undefined;
case SyntaxKind.ThisKeyword:
return "0";
case SyntaxKind.NonNullExpression:
case SyntaxKind.ParenthesizedExpression:
return getFlowCacheKey((<NonNullExpression | ParenthesizedExpression>node).expression);
return getFlowCacheKey((<NonNullExpression | ParenthesizedExpression>node).expression, declaredType, initialType, flowContainer);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
const propName = getAccessedPropertyName(<AccessExpression>node);
if (propName !== undefined) {
const key = getFlowCacheKey((<AccessExpression>node).expression);
const key = getFlowCacheKey((<AccessExpression>node).expression, declaredType, initialType, flowContainer);
return key && key + "." + propName;
}
}
Expand Down Expand Up @@ -16345,6 +16347,7 @@ namespace ts {

function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType = declaredType, flowContainer?: Node, couldBeUninitialized?: boolean) {
let key: string | undefined;
let keySet = false;
let flowDepth = 0;
if (flowAnalysisDisabled) {
return errorType;
Expand All @@ -16365,6 +16368,14 @@ namespace ts {
}
return resultType;

function getOrSetCacheKey() {
if (keySet) {
return key;
}
keySet = true;
return key = getFlowCacheKey(reference, declaredType, initialType, flowContainer);
}

function getTypeAtFlowNode(flow: FlowNode): FlowType {
if (flowDepth === 2000) {
// We have made 2000 recursive invocations. To avoid overflowing the call stack we report an error
Expand All @@ -16376,6 +16387,15 @@ 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
Expand Down Expand Up @@ -16406,6 +16426,15 @@ namespace ts {
flow = (<FlowAssignment>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(<FlowCondition>flow);
Expand Down Expand Up @@ -16618,12 +16647,10 @@ namespace ts {
// this flow loop junction, return the cached type.
const id = getFlowNodeId(flow);
const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap<Type>());
const key = getOrSetCacheKey();
if (!key) {
key = getFlowCacheKey(reference);
// No cache key is generated when binding patterns are in unnarrowable situations
if (!key) {
return declaredType;
}
return declaredType;
}
const cached = cache.get(key);
if (cached) {
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Expand Up @@ -2546,6 +2546,8 @@ namespace ts {
Shared = 1 << 10, // Referenced as antecedent more than once
PreFinally = 1 << 11, // Injected edge that links pre-finally label and pre-try flow
AfterFinally = 1 << 12, // Injected edge that links post-finally flow with the rest of the graph
/** @internal */
Cached = 1 << 13, // Indicates that at least one cross-call cache entry exists for this node, even if not a loop participant
Label = BranchLabel | LoopLabel,
Condition = TrueCondition | FalseCondition
}
Expand Down

0 comments on commit d8f2702

Please sign in to comment.