Cache visited nodes and types in shared arrays to reduce object allocation

This commit is contained in:
Anders Hejlsberg 2016-05-04 15:18:57 -07:00
parent 9fa5971af0
commit 3a4b56d6b7
3 changed files with 35 additions and 25 deletions

View file

@ -654,6 +654,7 @@ namespace ts {
}
function setFlowNodeReferenced(flow: FlowNode) {
// On first reference we set the Referenced flag, thereafter we set the Shared flag
flow.flags |= flow.flags & FlowFlags.Referenced ? FlowFlags.Shared : FlowFlags.Referenced;
}

View file

@ -183,6 +183,7 @@ namespace ts {
let flowStackStart = 0;
let flowStackCount = 0;
let visitedFlowCount = 0;
const tupleTypes: Map<TupleType> = {};
const unionTypes: Map<UnionType> = {};
@ -199,6 +200,8 @@ namespace ts {
const flowTypeCaches: Map<Type>[] = [];
const flowStackNodes: FlowNode[] = [];
const flowStackCacheKeys: string[] = [];
const visitedFlowNodes: FlowNode[] = [];
const visitedFlowTypes: Type[] = [];
const potentialThisCollisions: Node[] = [];
const awaitedTypeStack: number[] = [];
@ -7378,17 +7381,12 @@ namespace ts {
return false;
}
function getFlowNodeId(flow: FlowNode): number {
function getFlowTypeCache(flow: FlowNode): Map<Type> {
if (!flow.id) {
flow.id = nextFlowId;
nextFlowId++;
}
return flow.id;
}
function getFlowTypeCache(flow: FlowNode): Map<Type> {
const id = getFlowNodeId(flow);
return flowTypeCaches[id] || (flowTypeCaches[id] = {});
return flowTypeCaches[flow.id] || (flowTypeCaches[flow.id] = {});
}
function typeMaybeAssignableTo(source: Type, target: Type) {
@ -7583,19 +7581,27 @@ namespace ts {
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType: Type) {
let key: string;
let cachedTypes: Type[];
if (!reference.flowNode || declaredType === initialType && !(declaredType.flags & TypeFlags.Narrowable)) {
return declaredType;
}
return getTypeAtFlowNode(reference.flowNode);
const visitedFlowStart = visitedFlowCount;
const result = getTypeAtFlowNode(reference.flowNode);
visitedFlowCount = visitedFlowStart;
return result;
function getTypeAtFlowNode(flow: FlowNode): Type {
while (true) {
let type: Type;
let id = flow.flags & FlowFlags.Shared ? getFlowNodeId(flow) : 0;
if (id && cachedTypes && (type = cachedTypes[id])) {
return type;
if (flow.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
// antecedent of more than one node.
for (let i = visitedFlowStart; i < visitedFlowCount; i++) {
if (visitedFlowNodes[i] === flow) {
return visitedFlowTypes[i];
}
}
}
let type: Type;
if (flow.flags & FlowFlags.Assignment) {
type = getTypeAtFlowAssignment(<FlowAssignment>flow);
if (!type) {
@ -7619,11 +7625,14 @@ namespace ts {
type = declaredType;
}
else {
// At the top of the flow we have the initial type
// At the top of the flow we have the initial type.
type = initialType;
}
if (id) {
(cachedTypes || (cachedTypes = []))[id] = type;
if (flow.flags & FlowFlags.Shared) {
// Record visited node and the associated type in the cache.
visitedFlowNodes[visitedFlowCount] = flow;
visitedFlowTypes[visitedFlowCount] = type;
visitedFlowCount++;
}
return type;
}

View file

@ -1516,15 +1516,15 @@ namespace ts {
}
export const enum FlowFlags {
Unreachable = 1 << 0,
Start = 1 << 1,
BranchLabel = 1 << 2,
LoopLabel = 1 << 3,
Assignment = 1 << 4,
TrueCondition = 1 << 5,
FalseCondition = 1 << 6,
Referenced = 1 << 7,
Shared = 1 << 8,
Unreachable = 1 << 0, // Unreachable code
Start = 1 << 1, // Start of flow graph
BranchLabel = 1 << 2, // Non-looping junction
LoopLabel = 1 << 3, // Looping junction
Assignment = 1 << 4, // Assignment
TrueCondition = 1 << 5, // Condition known to be true
FalseCondition = 1 << 6, // Condition known to be false
Referenced = 1 << 7, // Referenced as antecedent once
Shared = 1 << 8, // Referenced as antecedent more than once
Label = BranchLabel | LoopLabel,
Condition = TrueCondition | FalseCondition
}