From ffd5abc581e0643733bfda98c6b8b8f6d03ec78d Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 22 Oct 2026 14:33:38 -0700 Subject: [PATCH 1/2] Calculate transitive JS library deps after `symbolHandler` in `--symbols-only` mode. NFC Previously, `getTransitiveDeps` was called inside `addFromLibrary` while `symbolHandler` was still iterating through `symbolsNeeded` and mutating `__deps` (such as adding `setTempRet0` for i64-returning functions without `WASM_BIGINT`). Compute `symbolDeps` in the `if (symbolsOnly)` block after `symbolHandler` completes so that all direct `__deps` are populated before traversing the dependency graph. Split out from #27995. --- src/jsifier.mjs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/jsifier.mjs b/src/jsifier.mjs index 83fa44163cf00..3fdee57fa5f12 100644 --- a/src/jsifier.mjs +++ b/src/jsifier.mjs @@ -415,7 +415,6 @@ const PROXY_SYNC_ASYNC = 2; export async function runJSify(outputFile, symbolsOnly) { const libraryItems = []; const libraryAliases = []; - const symbolDeps = {}; const asyncFuncs = []; let postSets = []; @@ -629,13 +628,6 @@ function(${args}) { } if (symbolsOnly) { - if (LibraryManager.library.hasOwnProperty(symbol)) { - // Resolve aliases before looking up deps - var transitiveDeps = getTransitiveDeps(symbol); - symbolDeps[symbol] = transitiveDeps.filter( - (d) => !isJsOnlySymbol(d) && !(d in LibraryManager.library), - ); - } return; } @@ -1003,6 +995,15 @@ var proxiedFunctionTable = [ timer.stop('symbolHandler'); if (symbolsOnly) { + const symbolDeps = {}; + for (const sym of Object.keys(LibraryManager.library)) { + if (!isDecorator(sym)) { + const transitiveDeps = getTransitiveDeps(sym); + symbolDeps[sym] = transitiveDeps.filter( + (d) => !isJsOnlySymbol(d) && !(d in LibraryManager.library), + ); + } + } writeOutput( JSON.stringify({ deps: symbolDeps, From 85466163e7e844d3c7da491886d068a44366279c Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Thu, 22 Oct 2026 15:42:41 -0700 Subject: [PATCH 2/2] Memoize `getTransitiveDeps` in `src/jsifier.mjs`. NFC Cache the computed transitive dependency list for each symbol in `transitiveDepsCache` and reuse cached results when traversing dependencies in subsequent `getTransitiveDeps` calls. Only completed top-level traversals are inserted into the cache so that cyclic dependencies (such as `$FS` <-> `$MEMFS`) are handled safely without caching partial results. In `tools/compiler.mjs --symbols-only`, this reduces graph node visits in `getTransitiveDeps` by ~9x (from 39,349 to 4,381), cutting the `symbolDeps` calculation time by ~41% (from ~16.0ms to ~9.5ms) and overall `--symbols-only` execution time by ~4.6% (from ~152ms to ~145ms). See: #28052 --- src/jsifier.mjs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/jsifier.mjs b/src/jsifier.mjs index 3fdee57fa5f12..70f1b1a46cad6 100644 --- a/src/jsifier.mjs +++ b/src/jsifier.mjs @@ -127,27 +127,40 @@ function isDefined(symName) { return false; } +const transitiveDepsCache = new Map(); + function getTransitiveDeps(symbol) { - // TODO(sbc): Use some kind of cache to avoid quadratic behaviour here. + let result = transitiveDepsCache.get(symbol); + if (result) { + return result; + } const transitiveDeps = new Set(); const seen = new Set(); const toVisit = [symbol]; while (toVisit.length) { const sym = toVisit.pop(); if (!seen.has(sym)) { - let directDeps = LibraryManager.library[sym + '__deps'] ?? []; - directDeps = directDeps.filter((d) => typeof d === 'string'); - for (const dep of directDeps) { + const cached = transitiveDepsCache.get(sym); + let deps = cached; + if (!deps) { + deps = LibraryManager.library[sym + '__deps'] ?? []; + deps = deps.filter((d) => typeof d === 'string'); + } + for (const dep of deps) { if (!transitiveDeps.has(dep)) { debugLog(`adding dependency ${symbol} -> ${dep}`); + transitiveDeps.add(dep); + if (!cached) { + toVisit.push(dep); + } } - transitiveDeps.add(dep); - toVisit.push(dep); } seen.add(sym); } } - return Array.from(transitiveDeps); + result = Array.from(transitiveDeps); + transitiveDepsCache.set(symbol, result); + return result; } function shouldPreprocess(fileName) { @@ -413,6 +426,7 @@ const PROXY_SYNC = 1; const PROXY_SYNC_ASYNC = 2; export async function runJSify(outputFile, symbolsOnly) { + transitiveDepsCache.clear(); const libraryItems = []; const libraryAliases = []; const asyncFuncs = [];