Skip to content

Commit dd829c4

Browse files
committed
Use lazy speculatable map for relationship caches to reduce eager copy overhead
1 parent e92f50b commit dd829c4

File tree

1 file changed

+87
-6
lines changed

1 file changed

+87
-6
lines changed

src/compiler/checker.ts

+87-6
Original file line numberDiff line numberDiff line change
@@ -1695,6 +1695,87 @@ export class NodeLinks extends SpeculatableLinks {
16951695
declare fakeScopeForSignatureDeclaration?: "params" | "typeParams";
16961696
}
16971697

1698+
/**
1699+
* SpeculatableMap is like a Map, except it lazily follows the speculation state of the host
1700+
*
1701+
* Also, it doesn't support `.delete` or `.clear` - not because it couldn't, buit just because
1702+
* the use for this (relationship caches), doesn't use either feature of `Map`s.
1703+
*/
1704+
class SpeculatableMap<K, V> implements Map<K, V> {
1705+
private innerMap = new Map<K, [epoch: number, value: V][]>();
1706+
constructor(public host: SpeculationHost) {}
1707+
1708+
private getCurrentStateFromList(key: K, valueList: [epoch: number, value: V][]): [exists: boolean, value: V] {
1709+
const discarded = this.host.getDiscardedSpeculativeEpochs();
1710+
let unwrappedValue: V | undefined;
1711+
let exists = false;
1712+
for (let i = valueList.length - 1; i >= 0; i--) {
1713+
const [epoch, value] = valueList[i];
1714+
if (discarded.has(epoch)) {
1715+
valueList.splice(i, 1);
1716+
if (valueList.length === 0) {
1717+
this.innerMap.delete(key);
1718+
break;
1719+
}
1720+
continue;
1721+
}
1722+
unwrappedValue = value;
1723+
exists = true;
1724+
break;
1725+
}
1726+
return [exists, unwrappedValue!];
1727+
}
1728+
1729+
get(key: K): V | undefined {
1730+
const valueList = this.innerMap.get(key);
1731+
if (!valueList || !valueList.length) return undefined;
1732+
const [ exists, unwrappedValue ] = this.getCurrentStateFromList(key, valueList);
1733+
if (!exists) return undefined;
1734+
return unwrappedValue;
1735+
}
1736+
has(key: K): boolean {
1737+
const valueList = this.innerMap.get(key);
1738+
if (!valueList || !valueList.length) return false;
1739+
const [ exists, _unwrappedValue ] = this.getCurrentStateFromList(key, valueList);
1740+
return exists;
1741+
}
1742+
set(key: K, value: V): this {
1743+
const valueList = this.innerMap.get(key) || this.innerMap.set(key, []).get(key)!;
1744+
valueList.push([ this.host.getCurrentSpeculativeEpoch(), value ]);
1745+
return this;
1746+
}
1747+
/**
1748+
* Approximate - does not include lazy removals
1749+
*/
1750+
get size(): number {
1751+
return this.innerMap.size;
1752+
}
1753+
1754+
// These methods are unimplemented mostly because they're thus-far unused.
1755+
declare clear: never;
1756+
declare delete: never;
1757+
declare entries: never;
1758+
declare values: never;
1759+
declare [globalThis.Symbol.iterator]: never;
1760+
1761+
// Implemented just to provide utlity when debugging
1762+
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
1763+
return this.innerMap.forEach((valueList, k, _inner) => {
1764+
const [ exists, unwrappedValue ] = this.getCurrentStateFromList(k, valueList);
1765+
if (exists) {
1766+
return callbackfn.call(thisArg, unwrappedValue, k, this);
1767+
}
1768+
});
1769+
}
1770+
1771+
// Likewise, provided for debuggability
1772+
keys(): IterableIterator<K> {
1773+
return this.innerMap.keys();
1774+
}
1775+
1776+
[globalThis.Symbol.toStringTag] = SpeculatableMap.name;
1777+
}
1778+
16981779
/** @internal */
16991780
export function getNodeId(node: Node): number {
17001781
if (!node.id) {
@@ -2586,12 +2667,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
25862667

25872668
// Relationship caches need to be speculative so we can un-upgrade "ErrorAndReported" results back down, since we're rewinding the error
25882669
// Otherwise, we think we've emitted lots of elaboration that we just swallow up in discarded speculative contexts
2589-
var subtypeRelation = registerSpeculativeCache(new Map<string, RelationComparisonResult>());
2590-
var strictSubtypeRelation = registerSpeculativeCache(new Map<string, RelationComparisonResult>());
2591-
var assignableRelation = registerSpeculativeCache(new Map<string, RelationComparisonResult>());
2592-
var comparableRelation = registerSpeculativeCache(new Map<string, RelationComparisonResult>());
2593-
var identityRelation = registerSpeculativeCache(new Map<string, RelationComparisonResult>());
2594-
var enumRelation = registerSpeculativeCache(new Map<string, RelationComparisonResult>());
2670+
var subtypeRelation = new SpeculatableMap<string, RelationComparisonResult>(speculationHost);
2671+
var strictSubtypeRelation = new SpeculatableMap<string, RelationComparisonResult>(speculationHost);
2672+
var assignableRelation = new SpeculatableMap<string, RelationComparisonResult>(speculationHost);
2673+
var comparableRelation = new SpeculatableMap<string, RelationComparisonResult>(speculationHost);
2674+
var identityRelation = new SpeculatableMap<string, RelationComparisonResult>(speculationHost);
2675+
var enumRelation = new SpeculatableMap<string, RelationComparisonResult>(speculationHost);
25952676

25962677
var builtinGlobals = createSymbolTable();
25972678
builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol);

0 commit comments

Comments
 (0)