Skip to content

Commit bcc3f19

Browse files
committed
rustdoc-search: depth limit T<U> -> U unboxing
Profiler output: https://notriddle.com/rustdoc-html-demo-9/search-unbox-limit/ This is a performance enhancement aimed at a problem I found while using type-driven search on the Rust compiler. It is caused by [`Interner`], a trait with 41 associated types, many of which recurse back to `Self` again. This caused search.js to struggle. It eventually terminates, after about 10 minutes of turning my PC into a space header, but it's doing `41!` unifications and that's too slow. [`Interner`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/trait.Interner.html
1 parent b054da8 commit bcc3f19

File tree

1 file changed

+108
-31
lines changed

1 file changed

+108
-31
lines changed

src/librustdoc/html/static/js/search.js

Lines changed: 108 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// ignore-tidy-filelength
12
/* global addClass, getNakedUrl, getSettingValue */
23
/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
34

@@ -80,6 +81,13 @@ const longItemTypes = [
8081
const TY_GENERIC = itemTypes.indexOf("generic");
8182
const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
8283

84+
// Hard limit on how deep to recurse into generics when doing type-driven search.
85+
// This needs limited, partially because
86+
// a search for `Ty` shouldn't match `WithInfcx<ParamEnvAnd<Vec<ConstTy<Interner<Ty=Ty>>>>>`,
87+
// but mostly because this is the simplest and most principled way to limit the number
88+
// of permutations we need to check.
89+
const UNBOXING_LIMIT = 5;
90+
8391
// In the search display, allows to switch between tabs.
8492
function printTab(nb) {
8593
let iter = 0;
@@ -1383,10 +1391,23 @@ if (parserState.userQuery[parserState.pos] === "[") {
13831391
* @param {Map<number,number>|null} mgensIn
13841392
* - Map functions generics to query generics (never modified).
13851393
* @param {null|Map<number,number> -> bool} solutionCb - Called for each `mgens` solution.
1394+
* @param {number} unboxingDepth
1395+
* - Limit checks that Ty matches Vec<Ty>,
1396+
* but not Vec<ParamEnvAnd<WithInfcx<ConstTy<Interner<Ty=Ty>>>>>
13861397
*
13871398
* @return {boolean} - Returns true if a match, false otherwise.
13881399
*/
1389-
function unifyFunctionTypes(fnTypesIn, queryElems, whereClause, mgensIn, solutionCb) {
1400+
function unifyFunctionTypes(
1401+
fnTypesIn,
1402+
queryElems,
1403+
whereClause,
1404+
mgensIn,
1405+
solutionCb,
1406+
unboxingDepth
1407+
) {
1408+
if (unboxingDepth >= UNBOXING_LIMIT) {
1409+
return false;
1410+
}
13901411
/**
13911412
* @type Map<integer, integer>|null
13921413
*/
@@ -1405,7 +1426,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
14051426
&& queryElems[0].bindings.size === 0) {
14061427
const queryElem = queryElems[0];
14071428
for (const fnType of fnTypesIn) {
1408-
if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
1429+
if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgens)) {
14091430
continue;
14101431
}
14111432
if (fnType.id < 0 && queryElem.id < 0) {
@@ -1424,7 +1445,13 @@ if (parserState.userQuery[parserState.pos] === "[") {
14241445
}
14251446
}
14261447
for (const fnType of fnTypesIn) {
1427-
if (!unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) {
1448+
if (!unifyFunctionTypeIsUnboxCandidate(
1449+
fnType,
1450+
queryElem,
1451+
whereClause,
1452+
mgens,
1453+
unboxingDepth + 1
1454+
)) {
14281455
continue;
14291456
}
14301457
if (fnType.id < 0) {
@@ -1439,7 +1466,8 @@ if (parserState.userQuery[parserState.pos] === "[") {
14391466
queryElems,
14401467
whereClause,
14411468
mgensScratch,
1442-
solutionCb
1469+
solutionCb,
1470+
unboxingDepth + 1
14431471
)) {
14441472
return true;
14451473
}
@@ -1448,7 +1476,8 @@ if (parserState.userQuery[parserState.pos] === "[") {
14481476
queryElems,
14491477
whereClause,
14501478
mgens ? new Map(mgens) : null,
1451-
solutionCb
1479+
solutionCb,
1480+
unboxingDepth + 1
14521481
)) {
14531482
return true;
14541483
}
@@ -1484,7 +1513,7 @@ if (parserState.userQuery[parserState.pos] === "[") {
14841513
let queryElemsTmp = null;
14851514
for (let i = flast; i >= 0; i -= 1) {
14861515
const fnType = fnTypes[i];
1487-
if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) {
1516+
if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgens)) {
14881517
continue;
14891518
}
14901519
let mgensScratch;
@@ -1521,7 +1550,8 @@ if (parserState.userQuery[parserState.pos] === "[") {
15211550
fnType,
15221551
queryElem,
15231552
whereClause,
1524-
mgensScratch
1553+
mgensScratch,
1554+
unboxingDepth
15251555
);
15261556
if (!solution) {
15271557
return false;
@@ -1533,14 +1563,16 @@ if (parserState.userQuery[parserState.pos] === "[") {
15331563
queryElem.generics,
15341564
whereClause,
15351565
simplifiedMgens,
1536-
solutionCb
1566+
solutionCb,
1567+
unboxingDepth
15371568
);
15381569
if (passesUnification) {
15391570
return true;
15401571
}
15411572
}
15421573
return false;
1543-
}
1574+
},
1575+
unboxingDepth
15441576
);
15451577
if (passesUnification) {
15461578
return true;
@@ -1552,7 +1584,13 @@ if (parserState.userQuery[parserState.pos] === "[") {
15521584
}
15531585
for (let i = flast; i >= 0; i -= 1) {
15541586
const fnType = fnTypes[i];
1555-
if (!unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) {
1587+
if (!unifyFunctionTypeIsUnboxCandidate(
1588+
fnType,
1589+
queryElem,
1590+
whereClause,
1591+
mgens,
1592+
unboxingDepth + 1
1593+
)) {
15561594
continue;
15571595
}
15581596
let mgensScratch;
@@ -1576,7 +1614,8 @@ if (parserState.userQuery[parserState.pos] === "[") {
15761614
queryElems,
15771615
whereClause,
15781616
mgensScratch,
1579-
solutionCb
1617+
solutionCb,
1618+
unboxingDepth + 1
15801619
);
15811620
if (passesUnification) {
15821621
return true;
@@ -1595,11 +1634,10 @@ if (parserState.userQuery[parserState.pos] === "[") {
15951634
*
15961635
* @param {FunctionType} fnType
15971636
* @param {QueryElement} queryElem
1598-
* @param {[FunctionSearchType]} whereClause - Trait bounds for generic items.
15991637
* @param {Map<number,number>|null} mgensIn - Map functions generics to query generics.
16001638
* @returns {boolean}
16011639
*/
1602-
function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgensIn) {
1640+
function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, mgensIn) {
16031641
// type filters look like `trait:Read` or `enum:Result`
16041642
if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) {
16051643
return false;
@@ -1694,9 +1732,16 @@ if (parserState.userQuery[parserState.pos] === "[") {
16941732
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
16951733
* @param {Map<number,number>} mgensIn - Map functions generics to query generics.
16961734
* Never modified.
1735+
* @param {number} unboxingDepth
16971736
* @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}}
16981737
*/
1699-
function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) {
1738+
function unifyFunctionTypeCheckBindings(
1739+
fnType,
1740+
queryElem,
1741+
whereClause,
1742+
mgensIn,
1743+
unboxingDepth
1744+
) {
17001745
if (fnType.bindings.size < queryElem.bindings.size) {
17011746
return false;
17021747
}
@@ -1723,7 +1768,8 @@ if (parserState.userQuery[parserState.pos] === "[") {
17231768
// return `false` makes unifyFunctionTypes return the full set of
17241769
// possible solutions
17251770
return false;
1726-
}
1771+
},
1772+
unboxingDepth
17271773
);
17281774
return newSolutions;
17291775
});
@@ -1753,9 +1799,19 @@ if (parserState.userQuery[parserState.pos] === "[") {
17531799
* @param {QueryElement} queryElem
17541800
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
17551801
* @param {Map<number,number>|null} mgens - Map functions generics to query generics.
1802+
* @param {number} unboxingDepth
17561803
* @returns {boolean}
17571804
*/
1758-
function unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens) {
1805+
function unifyFunctionTypeIsUnboxCandidate(
1806+
fnType,
1807+
queryElem,
1808+
whereClause,
1809+
mgens,
1810+
unboxingDepth
1811+
) {
1812+
if (unboxingDepth >= UNBOXING_LIMIT) {
1813+
return false;
1814+
}
17591815
if (fnType.id < 0 && queryElem.id >= 0) {
17601816
if (!whereClause) {
17611817
return false;
@@ -1777,14 +1833,21 @@ if (parserState.userQuery[parserState.pos] === "[") {
17771833
whereClause[(-fnType.id) - 1],
17781834
queryElem,
17791835
whereClause,
1780-
mgensTmp
1836+
mgensTmp,
1837+
unboxingDepth
17811838
);
17821839
} else if (fnType.generics.length > 0 || fnType.bindings.size > 0) {
17831840
const simplifiedGenerics = [
17841841
...fnType.generics,
17851842
...Array.from(fnType.bindings.values()).flat(),
17861843
];
1787-
return checkIfInList(simplifiedGenerics, queryElem, whereClause, mgens);
1844+
return checkIfInList(
1845+
simplifiedGenerics,
1846+
queryElem,
1847+
whereClause,
1848+
mgens,
1849+
unboxingDepth
1850+
);
17881851
}
17891852
return false;
17901853
}
@@ -1796,13 +1859,14 @@ if (parserState.userQuery[parserState.pos] === "[") {
17961859
* @param {Array<FunctionType>} list
17971860
* @param {QueryElement} elem - The element from the parsed query.
17981861
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
1799-
* @param {Map<number,number>|null} mgens - Map functions generics to query generics.
1862+
* @param {Map<number,number>|null} mgens - Map functions generics to query generics.
1863+
* @param {number} unboxingDepth
18001864
*
18011865
* @return {boolean} - Returns true if found, false otherwise.
18021866
*/
1803-
function checkIfInList(list, elem, whereClause, mgens) {
1867+
function checkIfInList(list, elem, whereClause, mgens, unboxingDepth) {
18041868
for (const entry of list) {
1805-
if (checkType(entry, elem, whereClause, mgens)) {
1869+
if (checkType(entry, elem, whereClause, mgens, unboxingDepth)) {
18061870
return true;
18071871
}
18081872
}
@@ -1816,14 +1880,23 @@ if (parserState.userQuery[parserState.pos] === "[") {
18161880
* @param {Row} row
18171881
* @param {QueryElement} elem - The element from the parsed query.
18181882
* @param {[FunctionType]} whereClause - Trait bounds for generic items.
1819-
* @param {Map<number,number>|null} mgens - Map functions generics to query generics.
1883+
* @param {Map<number,number>|null} mgens - Map functions generics to query generics.
18201884
*
18211885
* @return {boolean} - Returns true if the type matches, false otherwise.
18221886
*/
1823-
function checkType(row, elem, whereClause, mgens) {
1887+
function checkType(row, elem, whereClause, mgens, unboxingDepth) {
1888+
if (unboxingDepth >= UNBOXING_LIMIT) {
1889+
return false;
1890+
}
18241891
if (row.bindings.size === 0 && elem.bindings.size === 0) {
1825-
if (elem.id < 0) {
1826-
return row.id < 0 || checkIfInList(row.generics, elem, whereClause, mgens);
1892+
if (elem.id < 0 && mgens === null) {
1893+
return row.id < 0 || checkIfInList(
1894+
row.generics,
1895+
elem,
1896+
whereClause,
1897+
mgens,
1898+
unboxingDepth + 1
1899+
);
18271900
}
18281901
if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 &&
18291902
typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 &&
@@ -1834,11 +1907,12 @@ if (parserState.userQuery[parserState.pos] === "[") {
18341907
row.generics,
18351908
elem,
18361909
whereClause,
1837-
mgens
1910+
mgens,
1911+
unboxingDepth
18381912
);
18391913
}
18401914
}
1841-
return unifyFunctionTypes([row], [elem], whereClause, mgens);
1915+
return unifyFunctionTypes([row], [elem], whereClause, mgens, null, unboxingDepth);
18421916
}
18431917

18441918
/**
@@ -2053,9 +2127,9 @@ if (parserState.userQuery[parserState.pos] === "[") {
20532127
);
20542128
if (tfpDist !== null) {
20552129
const in_args = row.type && row.type.inputs
2056-
&& checkIfInList(row.type.inputs, elem, row.type.where_clause);
2130+
&& checkIfInList(row.type.inputs, elem, row.type.where_clause, null, 0);
20572131
const returned = row.type && row.type.output
2058-
&& checkIfInList(row.type.output, elem, row.type.where_clause);
2132+
&& checkIfInList(row.type.output, elem, row.type.where_clause, null, 0);
20592133
if (in_args) {
20602134
results_in_args.max_dist = Math.max(results_in_args.max_dist || 0, tfpDist);
20612135
const maxDist = results_in_args.size < MAX_RESULTS ?
@@ -2141,9 +2215,12 @@ if (parserState.userQuery[parserState.pos] === "[") {
21412215
row.type.output,
21422216
parsedQuery.returned,
21432217
row.type.where_clause,
2144-
mgens
2218+
mgens,
2219+
null,
2220+
0 // unboxing depth
21452221
);
2146-
}
2222+
},
2223+
0 // unboxing depth
21472224
)) {
21482225
return;
21492226
}

0 commit comments

Comments
 (0)