gitnexus 1.6.8-rc.9 → 1.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/README.md +3 -3
  2. package/dist/_shared/graph/types.d.ts +31 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/language-detection.d.ts.map +1 -1
  5. package/dist/_shared/language-detection.js +11 -1
  6. package/dist/_shared/language-detection.js.map +1 -1
  7. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  8. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  9. package/dist/_shared/lbug/schema-constants.js +6 -0
  10. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  11. package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
  12. package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
  13. package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
  14. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  15. package/dist/cli/ai-context.d.ts +31 -1
  16. package/dist/cli/ai-context.js +18 -16
  17. package/dist/cli/analyze-config.js +11 -0
  18. package/dist/cli/analyze.d.ts +14 -0
  19. package/dist/cli/analyze.js +98 -0
  20. package/dist/cli/embedding-dims.d.ts +33 -0
  21. package/dist/cli/embedding-dims.js +39 -0
  22. package/dist/cli/eval-server.d.ts +8 -0
  23. package/dist/cli/eval-server.js +242 -2
  24. package/dist/cli/help-i18n.js +19 -0
  25. package/dist/cli/i18n/en.d.ts +15 -1
  26. package/dist/cli/i18n/en.js +15 -1
  27. package/dist/cli/i18n/resources.d.ts +29 -1
  28. package/dist/cli/i18n/zh-CN.d.ts +14 -0
  29. package/dist/cli/i18n/zh-CN.js +15 -1
  30. package/dist/cli/index.js +89 -1
  31. package/dist/cli/mcp.d.ts +6 -1
  32. package/dist/cli/mcp.js +26 -1
  33. package/dist/cli/optional-grammars.d.ts +6 -8
  34. package/dist/cli/optional-grammars.js +8 -11
  35. package/dist/cli/setup.d.ts +3 -1
  36. package/dist/cli/setup.js +67 -17
  37. package/dist/cli/skill-gen.js +2 -1
  38. package/dist/cli/tool.d.ts +19 -1
  39. package/dist/cli/tool.js +96 -3
  40. package/dist/core/embeddings/hf-env.d.ts +0 -66
  41. package/dist/core/embeddings/http-client.d.ts +7 -0
  42. package/dist/core/embeddings/http-client.js +5 -3
  43. package/dist/core/embeddings/server-mapping.d.ts +1 -1
  44. package/dist/core/embeddings/server-mapping.js +7 -3
  45. package/dist/core/graph/import-cycles.d.ts +10 -0
  46. package/dist/core/graph/import-cycles.js +103 -0
  47. package/dist/core/group/cross-impact.d.ts +2 -1
  48. package/dist/core/group/cross-impact.js +5 -1
  49. package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
  50. package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
  51. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  52. package/dist/core/group/extractors/http-route-extractor.js +22 -4
  53. package/dist/core/group/extractors/include-extractor.js +22 -13
  54. package/dist/core/group/sync.d.ts +27 -1
  55. package/dist/core/group/sync.js +0 -0
  56. package/dist/core/incremental/subgraph-extract.js +18 -1
  57. package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
  58. package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
  59. package/dist/core/ingestion/cfg/collect.d.ts +51 -0
  60. package/dist/core/ingestion/cfg/collect.js +77 -0
  61. package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
  62. package/dist/core/ingestion/cfg/control-dependence.js +182 -0
  63. package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
  64. package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
  65. package/dist/core/ingestion/cfg/emit.d.ts +293 -0
  66. package/dist/core/ingestion/cfg/emit.js +646 -0
  67. package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
  68. package/dist/core/ingestion/cfg/post-dominators.js +176 -0
  69. package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
  70. package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
  71. package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
  72. package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
  73. package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
  74. package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
  75. package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
  76. package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
  77. package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
  78. package/dist/core/ingestion/cfg/traversal-result.js +2 -0
  79. package/dist/core/ingestion/cfg/types.d.ts +274 -0
  80. package/dist/core/ingestion/cfg/types.js +13 -0
  81. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
  82. package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
  83. package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
  84. package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
  85. package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
  86. package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
  87. package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
  88. package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
  89. package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
  90. package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
  91. package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
  92. package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
  93. package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
  94. package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
  95. package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
  96. package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
  97. package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
  98. package/dist/core/ingestion/cfg/visitors/go.js +638 -0
  99. package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
  100. package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
  101. package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
  102. package/dist/core/ingestion/cfg/visitors/java.js +812 -0
  103. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
  104. package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
  105. package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
  106. package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
  107. package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
  108. package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
  109. package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
  110. package/dist/core/ingestion/cfg/visitors/php.js +721 -0
  111. package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
  112. package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
  113. package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
  114. package/dist/core/ingestion/cfg/visitors/python.js +558 -0
  115. package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
  116. package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
  117. package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
  118. package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
  119. package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
  120. package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
  121. package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
  122. package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
  123. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
  124. package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
  125. package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
  126. package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
  127. package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
  128. package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
  129. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
  130. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
  131. package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
  132. package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
  133. package/dist/core/ingestion/import-resolvers/utils.js +2 -0
  134. package/dist/core/ingestion/language-provider.d.ts +11 -0
  135. package/dist/core/ingestion/languages/c-cpp.js +6 -1
  136. package/dist/core/ingestion/languages/cobol.js +4 -0
  137. package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
  138. package/dist/core/ingestion/languages/cpp/captures.js +50 -1
  139. package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
  140. package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
  141. package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
  142. package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
  143. package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
  144. package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
  145. package/dist/core/ingestion/languages/cpp/query.js +23 -0
  146. package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
  147. package/dist/core/ingestion/languages/csharp.js +2 -0
  148. package/dist/core/ingestion/languages/dart.js +2 -0
  149. package/dist/core/ingestion/languages/go.js +2 -0
  150. package/dist/core/ingestion/languages/java.js +3 -0
  151. package/dist/core/ingestion/languages/kotlin/query.js +3 -2
  152. package/dist/core/ingestion/languages/kotlin.js +3 -0
  153. package/dist/core/ingestion/languages/php.js +2 -0
  154. package/dist/core/ingestion/languages/python.js +2 -0
  155. package/dist/core/ingestion/languages/ruby.js +2 -0
  156. package/dist/core/ingestion/languages/rust.js +2 -0
  157. package/dist/core/ingestion/languages/swift.js +2 -0
  158. package/dist/core/ingestion/languages/typescript.js +5 -0
  159. package/dist/core/ingestion/languages/vue.js +6 -0
  160. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
  161. package/dist/core/ingestion/method-extractors/generic.js +1 -0
  162. package/dist/core/ingestion/method-types.d.ts +2 -0
  163. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  164. package/dist/core/ingestion/model/symbol-table.js +1 -0
  165. package/dist/core/ingestion/mro-processor.js +109 -0
  166. package/dist/core/ingestion/parsing-processor.js +23 -0
  167. package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
  168. package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
  169. package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
  170. package/dist/core/ingestion/pipeline-phases/index.js +2 -0
  171. package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
  172. package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
  173. package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
  174. package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
  175. package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
  176. package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
  177. package/dist/core/ingestion/pipeline.d.ts +92 -10
  178. package/dist/core/ingestion/pipeline.js +11 -2
  179. package/dist/core/ingestion/scope-extractor.js +12 -2
  180. package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
  181. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
  182. package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
  183. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
  184. package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
  185. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
  186. package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
  187. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
  188. package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
  189. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
  190. package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
  191. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
  192. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
  193. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
  194. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
  195. package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
  196. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
  197. package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
  198. package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
  199. package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
  200. package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
  201. package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
  202. package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
  203. package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
  204. package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
  205. package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
  206. package/dist/core/ingestion/taint/call-summary-model.js +25 -0
  207. package/dist/core/ingestion/taint/emit.d.ts +125 -0
  208. package/dist/core/ingestion/taint/emit.js +208 -0
  209. package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
  210. package/dist/core/ingestion/taint/interproc-emit.js +88 -0
  211. package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
  212. package/dist/core/ingestion/taint/interproc-solver.js +312 -0
  213. package/dist/core/ingestion/taint/match.d.ts +153 -0
  214. package/dist/core/ingestion/taint/match.js +278 -0
  215. package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
  216. package/dist/core/ingestion/taint/path-codec.js +190 -0
  217. package/dist/core/ingestion/taint/propagate.d.ts +216 -0
  218. package/dist/core/ingestion/taint/propagate.js +659 -0
  219. package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
  220. package/dist/core/ingestion/taint/site-safety.js +98 -0
  221. package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
  222. package/dist/core/ingestion/taint/source-sink-config.js +35 -12
  223. package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
  224. package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
  225. package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
  226. package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
  227. package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
  228. package/dist/core/ingestion/taint/summary-harvest.js +537 -0
  229. package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
  230. package/dist/core/ingestion/taint/summary-model.js +86 -0
  231. package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
  232. package/dist/core/ingestion/taint/typescript-model.js +102 -0
  233. package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
  234. package/dist/core/ingestion/utils/env.d.ts +7 -0
  235. package/dist/core/ingestion/utils/env.js +12 -0
  236. package/dist/core/ingestion/utils/method-props.js +1 -0
  237. package/dist/core/ingestion/workers/parse-worker.d.ts +13 -0
  238. package/dist/core/ingestion/workers/parse-worker.js +76 -17
  239. package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
  240. package/dist/core/ingestion/workers/worker-pool.js +15 -2
  241. package/dist/core/lbug/csv-generator.d.ts +51 -3
  242. package/dist/core/lbug/csv-generator.js +369 -255
  243. package/dist/core/lbug/lbug-adapter.d.ts +58 -18
  244. package/dist/core/lbug/lbug-adapter.js +346 -84
  245. package/dist/core/lbug/lbug-config.d.ts +19 -0
  246. package/dist/core/lbug/lbug-config.js +29 -0
  247. package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
  248. package/dist/core/lbug/pdg-emit-sink.js +358 -0
  249. package/dist/core/lbug/pool-adapter.d.ts +35 -0
  250. package/dist/core/lbug/pool-adapter.js +90 -1
  251. package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
  252. package/dist/core/lbug/rel-pair-routing.js +140 -0
  253. package/dist/core/lbug/schema.d.ts +2 -2
  254. package/dist/core/lbug/schema.js +3 -0
  255. package/dist/core/logger.d.ts +0 -5
  256. package/dist/core/run-analyze.d.ts +96 -0
  257. package/dist/core/run-analyze.js +207 -3
  258. package/dist/core/tree-sitter/parser-loader.js +5 -4
  259. package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
  260. package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
  261. package/dist/core/tree-sitter/vendored-grammars.js +57 -0
  262. package/dist/core/wiki/generator.js +3 -1
  263. package/dist/core/wiki/graph-queries.d.ts +6 -0
  264. package/dist/core/wiki/graph-queries.js +9 -1
  265. package/dist/mcp/http-transport.d.ts +134 -0
  266. package/dist/mcp/http-transport.js +503 -0
  267. package/dist/mcp/local/local-backend.d.ts +110 -24
  268. package/dist/mcp/local/local-backend.js +1225 -19
  269. package/dist/mcp/local/pdg-impact.d.ts +384 -0
  270. package/dist/mcp/local/pdg-impact.js +1972 -0
  271. package/dist/mcp/resources.js +13 -1
  272. package/dist/mcp/tools.d.ts +12 -0
  273. package/dist/mcp/tools.js +214 -7
  274. package/dist/server/analyze-launch.d.ts +29 -0
  275. package/dist/server/analyze-launch.js +137 -0
  276. package/dist/server/analyze-upload.d.ts +33 -0
  277. package/dist/server/analyze-upload.js +123 -0
  278. package/dist/server/analyze-worker.d.ts +17 -1
  279. package/dist/server/api.d.ts +17 -0
  280. package/dist/server/api.js +132 -168
  281. package/dist/server/git-clone.d.ts +43 -2
  282. package/dist/server/git-clone.js +211 -16
  283. package/dist/server/mcp-http.d.ts +5 -6
  284. package/dist/server/mcp-http.js +9 -82
  285. package/dist/server/middleware.d.ts +40 -0
  286. package/dist/server/middleware.js +92 -0
  287. package/dist/server/private-ip.d.ts +1 -0
  288. package/dist/server/private-ip.js +15 -0
  289. package/dist/server/upload-ingest.d.ts +56 -0
  290. package/dist/server/upload-ingest.js +276 -0
  291. package/dist/server/upload-paths.d.ts +37 -0
  292. package/dist/server/upload-paths.js +57 -0
  293. package/dist/server/upload-sweep.d.ts +20 -0
  294. package/dist/server/upload-sweep.js +57 -0
  295. package/dist/storage/git.js +5 -8
  296. package/dist/storage/parse-cache.d.ts +22 -1
  297. package/dist/storage/parse-cache.js +41 -10
  298. package/dist/storage/repo-manager.d.ts +121 -10
  299. package/dist/storage/repo-manager.js +43 -5
  300. package/dist/types/pipeline.d.ts +9 -0
  301. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
  302. package/hooks/claude/gitnexus-hook.cjs +110 -13
  303. package/hooks/claude/hook-db-lock-probe.cjs +513 -26
  304. package/package.json +8 -2
  305. package/scripts/assert-publish-grammar-coverage.cjs +31 -0
  306. package/scripts/build-tree-sitter-grammars.cjs +16 -10
  307. package/scripts/cross-platform-tests.ts +1 -0
  308. package/skills/gitnexus-debugging.md +16 -4
  309. package/skills/gitnexus-exploring.md +3 -3
  310. package/skills/gitnexus-guide.md +33 -0
  311. package/skills/gitnexus-pdg-query.md +89 -0
  312. package/skills/gitnexus-refactoring.md +1 -1
  313. package/skills/gitnexus-taint-analysis.md +178 -0
  314. package/vendor/tree-sitter-c/package.json +1 -1
  315. package/vendor/tree-sitter-dart/package.json +1 -1
  316. package/vendor/tree-sitter-kotlin/package.json +1 -1
  317. package/vendor/tree-sitter-proto/package.json +1 -1
  318. package/vendor/tree-sitter-swift/package.json +1 -1
  319. package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
  320. package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
  321. package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
  322. package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
  323. package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
  324. package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
  325. package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
  326. package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
  327. package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
  328. package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
  329. package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
  330. package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
  331. package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
  332. package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
  333. package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
  334. package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
  335. package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
  336. package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
  337. package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
  338. package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
  339. package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
  340. package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
  341. package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
  342. package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
  343. package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
  344. package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
  345. package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
  346. package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
  347. package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
  348. package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
  349. package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
  350. package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
  351. package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
  352. package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
  353. package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
  354. package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
  355. package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
  356. package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
  357. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
  358. package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
  359. package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
  360. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
  361. package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
  362. package/web/assets/index-BvYev1UJ.js +635 -0
  363. package/web/assets/index-_lgn7hs5.css +2 -0
  364. package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
  365. package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
  366. package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
  367. package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
  368. package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
  369. package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
  370. package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
  371. package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
  372. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
  373. package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
  374. package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
  375. package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
  376. package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
  377. package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
  378. package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
  379. package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
  380. package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
  381. package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
  382. package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
  383. package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
  384. package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
  385. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
  386. package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
  387. package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
  388. package/web/index.html +13 -15
  389. package/scripts/materialize-vendor-grammars.cjs +0 -97
  390. package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
  391. package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
  392. package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
  393. package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
  394. package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
  395. package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
  396. package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
  397. package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
  398. package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
  399. package/web/assets/chunk-CilyBKbf.js +0 -1
  400. package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
  401. package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
  402. package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
  403. package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
  404. package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
  405. package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
  406. package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
  407. package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
  408. package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
  409. package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
  410. package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
  411. package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
  412. package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
  413. package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
  414. package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
  415. package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
  416. package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
  417. package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
  418. package/web/assets/index-BKWA-m7o.css +0 -2
  419. package/web/assets/index-CG6q8eTs.js +0 -626
  420. package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
  421. package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
  422. package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
  423. package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
  424. package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
  425. package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
  426. package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
  427. package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
  428. package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
  429. package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
  430. /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
@@ -0,0 +1,785 @@
1
+ import { buildDominanceFrontiers, buildDominators, condenseReachingSets, hasReachableLoop, latticeEquals, reversePostOrder, tarjanScc, unionSets, } from './reaching-defs-graph.js';
2
+ /**
3
+ * Canonical `block:stmt` string key for a program point. Colon-separated to
4
+ * match the codebase's `blockIndex:stmtIndex` id conventions. Shared by the
5
+ * taint propagation engine (dedup/state keys) and the taint emit path
6
+ * (persisted edge-id material) so the two never drift.
7
+ */
8
+ export function pointKey(p) {
9
+ return `${p.blockIndex}:${p.stmtIndex}`;
10
+ }
11
+ /**
12
+ * def-site key: packs (blockIndex, stmtIndex) into one number. The stride is
13
+ * a per-BLOCK statement bound, and `maxFunctionLines` caps LINES, not
14
+ * statements — a minified one-line function coalesces arbitrarily many
15
+ * statements into one block, so an overflow would silently alias
16
+ * (block b, stmt STRIDE+k) with (block b+1, stmt k) and fabricate wrong-block
17
+ * facts. computeReachingDefs therefore range-checks up front and bails to a
18
+ * sound empty `overflow` result instead of ever letting a key alias.
19
+ * 2^21 statements per block × blocks ≤ 2^32 stays inside Number's 2^53.
20
+ */
21
+ const STMT_STRIDE = 1 << 21;
22
+ const defKey = (blockIndex, stmtIndex) => blockIndex * STMT_STRIDE + stmtIndex;
23
+ const EMPTY_LATTICE = new Map();
24
+ /**
25
+ * Compute reaching definitions for one function. See the module doc for the
26
+ * purity/determinism/sharing contract.
27
+ *
28
+ * This is the production entry point. As of #2201 it auto-dispatches via
29
+ * {@link computeInSetsAuto} — the SSA-sparse solver ({@link computeInSetsSparse})
30
+ * for looping functions large enough to amortize construction, the dense
31
+ * GEN/KILL worklist ({@link computeInSetsDense}) everywhere else (and for the
32
+ * throw-edge / unreachable-block functions the SSA path does not model). The two
33
+ * solvers are held byte-identical by the equivalence fuzz (status, bindings,
34
+ * sorted facts, def/use telemetry), so the dispatch is a pure performance
35
+ * heuristic; the dense solver doubles as that differential oracle.
36
+ */
37
+ export function computeReachingDefs(cfg, limits) {
38
+ // #2201: production auto-selects the solver per function (see
39
+ // {@link computeInSetsAuto}) — the SSA solver where it pays off (looping
40
+ // functions large enough to amortize construction, incl. the deep nests the
41
+ // dense ceiling used to truncate), the dense worklist everywhere else (small
42
+ // or loop-free functions, where it is faster). Both are held byte-identical
43
+ // by the equivalence fuzz, so the choice is a pure performance heuristic.
44
+ return solveReachingDefs(cfg, limits, computeInSetsAuto);
45
+ }
46
+ /**
47
+ * Per-file memoized reaching-defs solver (#2227 tri-review, U12). Under `--pdg`
48
+ * the SAME per-function RD fixpoint was solved 3–4× per analyze (RD emit +
49
+ * call-summary harvest + taint + summary harvest). Cache by (cfg identity,
50
+ * limits) so each DISTINCT solve runs once: the RD-emit bucket (passes
51
+ * `maxBlockVisits`) and the harvest/taint bucket (does not) stay byte-identical
52
+ * to their inline solves because the limits are part of the key. Lazy — solves
53
+ * on first request, so the taint zero-match fast path still skips its solve.
54
+ * Create one per FILE and drop it after the file to bound the per-function
55
+ * `facts` arrays (100k+ objects on a huge function) from going whole-repo.
56
+ */
57
+ export function createMemoizedReachingDefs() {
58
+ const cache = new Map();
59
+ return (cfg, limits) => {
60
+ const key = `${limits?.maxFacts ?? ''}|${limits?.maxBlockVisits ?? ''}`;
61
+ let byKey = cache.get(cfg);
62
+ if (byKey === undefined) {
63
+ byKey = new Map();
64
+ cache.set(cfg, byKey);
65
+ }
66
+ const hit = byKey.get(key);
67
+ if (hit !== undefined)
68
+ return hit;
69
+ const result = computeReachingDefs(cfg, limits);
70
+ byKey.set(key, result);
71
+ return result;
72
+ };
73
+ }
74
+ /**
75
+ * Dense GEN/KILL monotone worklist — the original (#2082 M2) reaching-defs
76
+ * solver. As of #2201 it plays two roles: (1) the production dispatcher
77
+ * ({@link computeInSetsAuto}) routes small / loop-free functions, and the
78
+ * throw-edge / unreachable-block functions the SSA path does not model, to this
79
+ * dense solver; (2) it is the differential equivalence ORACLE the fuzz checks
80
+ * the SSA path against. Keep it behavior-frozen — it is the ground truth.
81
+ *
82
+ * @internal exported for the equivalence fuzz harness (direct dense-vs-sparse
83
+ * comparison); the bench drives the production {@link computeReachingDefs}.
84
+ */
85
+ export function computeReachingDefsDense(cfg, limits) {
86
+ return solveReachingDefs(cfg, limits, computeInSetsDense);
87
+ }
88
+ /**
89
+ * SSA-sparse reaching-defs (#2201) — exposed directly so the equivalence fuzz
90
+ * can drive the SSA solver on every eligible CFG (bypassing the production
91
+ * size/loop dispatch heuristic in {@link computeInSetsAuto}) and assert
92
+ * byte-identity against the dense oracle. See {@link computeInSetsSparse} for
93
+ * the algorithm and byte-identical contract.
94
+ *
95
+ * @internal exported only for the equivalence fuzz harness.
96
+ */
97
+ export function computeReachingDefsSparse(cfg, limits) {
98
+ return solveReachingDefs(cfg, limits, computeInSetsSparse);
99
+ }
100
+ /**
101
+ * Shared orchestrator: the no-facts / overflow guards, the harvest, the
102
+ * adjacency build, the swappable IN-set computation, and the statement sweep.
103
+ * Only `computeInSets` differs between the production (sparse) and oracle
104
+ * (dense) paths — everything else is identical, which is what makes the two
105
+ * byte-identical by construction.
106
+ */
107
+ function solveReachingDefs(cfg, limits, computeInSets) {
108
+ if (!cfg.bindings) {
109
+ return { status: 'no-facts', bindings: [], facts: [], defCount: 0, useCount: 0 };
110
+ }
111
+ const blocks = cfg.blocks;
112
+ const n = blocks.length;
113
+ // Key-aliasing guard (see STMT_STRIDE): a block with ≥ STRIDE statements
114
+ // cannot be keyed without aliasing into the next block's def sites, which
115
+ // would fabricate wrong-block facts — strictly worse than producing none.
116
+ // Bail to a sound empty `overflow` result (the emit path warns distinctly).
117
+ for (const b of blocks) {
118
+ if ((b.statements?.length ?? 0) >= STMT_STRIDE) {
119
+ return { status: 'overflow', bindings: cfg.bindings, facts: [], defCount: 0, useCount: 0 };
120
+ }
121
+ }
122
+ const h = harvestStatementFacts(blocks, n);
123
+ const adj = buildAdjacency(cfg, n);
124
+ const solved = computeInSets(cfg, n, h, adj, limits);
125
+ if (!solved.converged) {
126
+ // Did NOT converge within the budget — the in-sets are not at the fixpoint,
127
+ // so any facts would be unsound. Bail to a sound empty `truncated` result
128
+ // (a coverage gap, not an error), carrying the def/use telemetry gathered.
129
+ return {
130
+ status: 'truncated',
131
+ bindings: cfg.bindings,
132
+ facts: [],
133
+ defCount: h.defCount,
134
+ useCount: h.useCount,
135
+ };
136
+ }
137
+ const maxFacts = limits?.maxFacts && limits.maxFacts > 0 ? limits.maxFacts : Infinity;
138
+ const { facts, truncated } = sweepFacts(blocks, solved.reachingAt, h.defLine, maxFacts);
139
+ return {
140
+ status: truncated ? 'truncated' : 'computed',
141
+ bindings: cfg.bindings,
142
+ facts,
143
+ defCount: h.defCount,
144
+ useCount: h.useCount,
145
+ };
146
+ }
147
+ /**
148
+ * Per-block GEN + def/use telemetry. gen[b]: bindingIdx → { set, kills }. A
149
+ * MUST def resets the accumulated set (kill is total); a MAY def (conditionally-
150
+ * evaluated context — see StatementFacts.mayDefs) only ADDS: the binding's
151
+ * incoming defs survive, so the transfer is out[x] = kills ? set : in[x] ∪ set.
152
+ * allDefsGen[b] is what a throw edge delivers to its handler: an exception can
153
+ * fire between any two statements, so every intermediate def may be the live one
154
+ * at the handler — IN∪OUT alone misses defs overwritten later in the same
155
+ * coalesced block.
156
+ */
157
+ function harvestStatementFacts(blocks, n) {
158
+ const gen = new Array(n).fill(null);
159
+ const allDefsGen = new Array(n).fill(null);
160
+ const defLine = new Map(); // defKey → source line
161
+ let defCount = 0;
162
+ let useCount = 0;
163
+ for (const b of blocks) {
164
+ const stmts = b.statements;
165
+ if (!stmts || stmts.length === 0)
166
+ continue;
167
+ let g = null;
168
+ let all = null;
169
+ for (let i = 0; i < stmts.length; i++) {
170
+ const s = stmts[i];
171
+ useCount += s.uses.length;
172
+ const key = defKey(b.index, i);
173
+ const record = (d, kills) => {
174
+ defCount += 1;
175
+ defLine.set(key, s.line);
176
+ if (!g)
177
+ g = new Map();
178
+ const entry = g.get(d);
179
+ if (kills || !entry) {
180
+ g.set(d, { set: new Set([key]), kills: kills || (entry?.kills ?? false) });
181
+ }
182
+ else {
183
+ entry.set.add(key); // may-def accumulates; never clears
184
+ }
185
+ if (!all)
186
+ all = new Map();
187
+ const allSet = all.get(d);
188
+ if (allSet)
189
+ allSet.add(key);
190
+ else
191
+ all.set(d, new Set([key]));
192
+ };
193
+ if (s.mayDefs)
194
+ for (const d of s.mayDefs)
195
+ record(d, false);
196
+ for (const d of s.defs)
197
+ record(d, true);
198
+ }
199
+ gen[b.index] = g;
200
+ allDefsGen[b.index] = all;
201
+ }
202
+ return { gen, allDefsGen, defLine, defCount, useCount };
203
+ }
204
+ /**
205
+ * Throw-aware predecessor/successor adjacency, sorted for deterministic merges.
206
+ * A `throw` edge contributes IN(from) ∪ allDefs(from) to its handler, not OUT:
207
+ * an exception may fire BEFORE the block's defs complete (the seed def in
208
+ * `let x = seed(); try { x = risky(); } catch { sink(x) }` must reach the sink)
209
+ * AND between any two defs of a multi-def coalesced block. Sound over-
210
+ * approximation; monotone, so the fixpoint absorbs it. See mergePreds.
211
+ */
212
+ function buildAdjacency(cfg, n) {
213
+ const preds = Array.from({ length: n }, () => []);
214
+ const succs = Array.from({ length: n }, () => []);
215
+ // Handlers whose IN depends on this block's IN (throw edges) — requeued on
216
+ // IN change, since a genned binding can absorb IN growth without changing
217
+ // OUT, which would otherwise leave the handler stale.
218
+ const throwSuccs = Array.from({ length: n }, () => []);
219
+ for (const e of cfg.edges) {
220
+ // Optional-chained pushes drop out-of-range endpoints defensively — the
221
+ // emit path validates via isEmitSafeCfg, but this pure function also runs
222
+ // on hand-built CFGs.
223
+ succs[e.from]?.push(e.to);
224
+ preds[e.to]?.push({ from: e.from, viaThrow: e.kind === 'throw' });
225
+ if (e.kind === 'throw')
226
+ throwSuccs[e.from]?.push(e.to);
227
+ }
228
+ for (const list of preds) {
229
+ list.sort((a, b) => a.from - b.from || Number(a.viaThrow) - Number(b.viaThrow));
230
+ // duplicate (from, throw+non-throw) pairs both survive — the throw leg
231
+ // adds IN(from); the merge dedups set-wise.
232
+ }
233
+ for (const list of succs)
234
+ list.sort((a, b) => a - b);
235
+ return { preds, succs, throwSuccs };
236
+ }
237
+ /**
238
+ * DENSE IN-set computer — the original monotone GEN/KILL worklist. Iterates in
239
+ * reverse post-order, seeded with every block (unreachable blocks keep ⊥ IN —
240
+ * correct, their defs reach nothing). Convergence: sets grow monotonically
241
+ * within the finite def-site universe ⇒ ≤ loop-depth+1 passes in practice.
242
+ *
243
+ * WTO / loop-aware iteration (Bourdoncle 1993) was evaluated as a fix for the
244
+ * O(blocks²) deep-loop-nest blow-up and REJECTED (#2195): on the dense-loop
245
+ * benchmark a faithful weak-topological-order solver was 104/104 byte-identical
246
+ * but 0% faster — the cost is inherent to dense-set propagation + lattice
247
+ * merges, not visitation order. The asymptotic fix shipped in #2201: the
248
+ * SSA-sparse solver ({@link computeInSetsSparse}). This dense version is retained
249
+ * only as the differential equivalence oracle the fuzz checks SSA against.
250
+ *
251
+ * @internal
252
+ */
253
+ function computeInSetsDense(cfg, n, h, adj, limits) {
254
+ const { gen, allDefsGen } = h;
255
+ const { preds, succs, throwSuccs } = adj;
256
+ const { order } = reversePostOrder(cfg.entryIndex, succs, n);
257
+ const inSets = new Array(n).fill(EMPTY_LATTICE);
258
+ const outSets = new Array(n).fill(EMPTY_LATTICE);
259
+ const inWorklist = new Array(n).fill(true);
260
+ let pending = n;
261
+ const maxBlockVisits = limits?.maxBlockVisits && limits.maxBlockVisits > 0 ? limits.maxBlockVisits : Infinity;
262
+ let blockVisits = 0;
263
+ while (pending > 0) {
264
+ for (const b of order) {
265
+ if (!inWorklist[b])
266
+ continue;
267
+ inWorklist[b] = false;
268
+ pending -= 1;
269
+ if (++blockVisits > maxBlockVisits)
270
+ return { converged: false };
271
+ const p = preds[b];
272
+ const inB = p.length === 0
273
+ ? EMPTY_LATTICE
274
+ : p.length === 1 && !p[0].viaThrow
275
+ ? outSets[p[0].from] // alias — zero allocation on straight-line chains
276
+ : mergePreds(p, inSets, outSets, allDefsGen);
277
+ const inChanged = !latticeEquals(inSets[b], inB);
278
+ inSets[b] = inB;
279
+ const g = gen[b];
280
+ // OUT = overlay(IN): a KILLING gen entry replaces the binding's set; a
281
+ // may-def-only entry unions with the incoming set (never kills). When
282
+ // nothing is genned, OUT aliases IN outright.
283
+ let outB;
284
+ if (!g) {
285
+ outB = inB;
286
+ }
287
+ else {
288
+ outB = new Map(inB); // copies REFERENCES, never set contents
289
+ for (const [bindingIdx, entry] of g) {
290
+ if (entry.kills) {
291
+ outB.set(bindingIdx, entry.set);
292
+ }
293
+ else {
294
+ const incoming = inB.get(bindingIdx);
295
+ outB.set(bindingIdx, incoming ? unionSets(incoming, entry.set) : entry.set);
296
+ }
297
+ }
298
+ }
299
+ const requeue = (s) => {
300
+ if (!inWorklist[s]) {
301
+ inWorklist[s] = true;
302
+ pending += 1;
303
+ }
304
+ };
305
+ if (!latticeEquals(outSets[b], outB)) {
306
+ outSets[b] = outB;
307
+ for (const s of succs[b])
308
+ requeue(s);
309
+ }
310
+ if (inChanged)
311
+ for (const s of throwSuccs[b])
312
+ requeue(s);
313
+ }
314
+ }
315
+ return { converged: true, reachingAt: (blockIndex, binding) => inSets[blockIndex]?.get(binding) };
316
+ }
317
+ /**
318
+ * SPARSE IN-set computer (#2201) — the production solver. Instead of the dense
319
+ * GEN/KILL worklist's per-block lattice fixpoint, it builds pruned SSA for the
320
+ * function (Cooper-Harvey-Kennedy dominators → Cytron dominance frontiers and
321
+ * φ-placement → stack-based renaming) and answers block-entry reaching-def
322
+ * queries by walking the SSA def-use graph. φ-nodes statically capture loop
323
+ * merges, so a use's reaching set is recovered without iterating the loop
324
+ * (depth-independent), and pass-through blocks carry the dominating definition
325
+ * via the rename stack rather than re-materializing a dense lattice at every
326
+ * block — the two effects that make it faster than the dense solver on the
327
+ * deep-nest and dense-bindings pathologies.
328
+ *
329
+ * BYTE-IDENTICAL CONTRACT: it computes the same may-reaching-definition SET at
330
+ * each block entry as {@link computeInSetsDense}. Order does not matter — {@link
331
+ * sweepFacts} sorts each use's reaching keys before the maxFacts cutoff (#2201
332
+ * KTD6) — so only set CONTENTS must match; the equivalence fuzz holds the line.
333
+ *
334
+ * SCOPE (KTD4): the SSA path covers fully-reachable CFGs with kill/may-def
335
+ * transfers, reducible AND irreducible (CHK + Cytron are correct on irreducible
336
+ * graphs). It does NOT model throw edges' IN∪allDefs handler semantics or
337
+ * propagation among unreachable blocks; functions with either are routed to the
338
+ * dense oracle — byte-identical and correct, just not asymptotically faster.
339
+ * These are not the perf pathologies (deep nests / dense-bindings are
340
+ * throw-free and fully reachable), so the win lands where it matters.
341
+ *
342
+ * No fixpoint iteration ⇒ the solve always converges in O(program); the
343
+ * `maxBlockVisits` ceiling that fired on the dense worklist's deep nests never
344
+ * fires here (#2201 acceptance). The bound is honored only on the dense
345
+ * fallback path.
346
+ *
347
+ * @internal
348
+ */
349
+ function computeInSetsSparse(cfg, n, h, adj, limits) {
350
+ const nBindings = cfg.bindings?.length ?? 0;
351
+ if (nBindings === 0)
352
+ return { converged: true, reachingAt: () => undefined };
353
+ const { gen } = h;
354
+ const { preds, succs, throwSuccs } = adj;
355
+ const entry = cfg.entryIndex;
356
+ // Gate to the dense oracle for the shapes the SSA path does not model.
357
+ for (const list of throwSuccs)
358
+ if (list.length)
359
+ return computeInSetsDense(cfg, n, h, adj, limits);
360
+ // Malformed-input guard: an out-of-range binding index (negative or
361
+ // ≥ nBindings — a corrupted/stale durable parsedfile store) would crash the
362
+ // SSA path's nBindings-sized arrays (defBlocks[v]/stacks[u]). The dense solver
363
+ // tolerates any index (its lattice is a Map), so fall back — keeping the two
364
+ // byte-identical AND preserving the graceful per-function degradation the
365
+ // dense path gave (a throw here would escape the unguarded taint/harvest call
366
+ // sites and lose the whole file's taint layer). See hasEmitSafeFacts (emit.ts).
367
+ for (const b of cfg.blocks) {
368
+ const stmts = b.statements;
369
+ if (!stmts)
370
+ continue;
371
+ for (const s of stmts) {
372
+ for (const d of s.defs)
373
+ if (d < 0 || d >= nBindings)
374
+ return computeInSetsDense(cfg, n, h, adj, limits);
375
+ for (const u of s.uses)
376
+ if (u < 0 || u >= nBindings)
377
+ return computeInSetsDense(cfg, n, h, adj, limits);
378
+ if (s.mayDefs)
379
+ for (const d of s.mayDefs)
380
+ if (d < 0 || d >= nBindings)
381
+ return computeInSetsDense(cfg, n, h, adj, limits);
382
+ }
383
+ }
384
+ // Synthetic pre-entry block (#2201): textbook SSA construction assumes the
385
+ // entry has no predecessors. A loop back-edge into the entry — or a self-loop
386
+ // on it — makes the entry a merge that needs a φ, and the dominance-frontier
387
+ // walk degenerates when idom[entry] === entry (it never lands the entry in its
388
+ // own frontier). A virtual start node S → entry (S itself has no preds)
389
+ // restores the invariant: idom[entry] = S, the entry joins {start ⊔
390
+ // back-edges}, and the implicit start operand contributes ⊥ (an empty rename
391
+ // stack). S carries no statements, gen, or uses and is never queried.
392
+ const S = n;
393
+ const nx = n + 1;
394
+ const succsX = new Array(nx);
395
+ for (let b = 0; b < n; b++)
396
+ succsX[b] = succs[b];
397
+ succsX[S] = [entry];
398
+ const dPredsX = new Array(nx);
399
+ for (let b = 0; b < n; b++) {
400
+ // preds[b] is pre-sorted by `from` (buildAdjacency), so duplicate `from`
401
+ // values (a throw + non-throw edge to the same handler, or parallel edges)
402
+ // are ADJACENT — dedup by skipping consecutive equals instead of a per-block
403
+ // Set + spread + sort (#2201 review R9). S = n exceeds every block index, so
404
+ // appending it for the entry keeps the list ascending without a re-sort.
405
+ const list = [];
406
+ let last = -1;
407
+ for (const p of preds[b]) {
408
+ if (p.from !== last) {
409
+ list.push(p.from);
410
+ last = p.from;
411
+ }
412
+ }
413
+ if (b === entry)
414
+ list.push(S);
415
+ dPredsX[b] = list;
416
+ }
417
+ dPredsX[S] = [];
418
+ // ── dominators (Cooper-Harvey-Kennedy; correct on irreducible CFGs) ──
419
+ // RPO rooted at the synthetic entry. `reachX` is the reachability the DFS
420
+ // already computed — reused for the unreachable-block gate below instead of a
421
+ // separate BFS (#2201 review R8). Because S→entry is S's only edge, reachX[b]
422
+ // (b<n) is exactly "reachable from entry", identical to the old BFS gate.
423
+ const { order: rpo, visited: reachX } = reversePostOrder(S, succsX, nx);
424
+ // The SSA path does not model propagation among unreachable blocks (KTD4) —
425
+ // fall back to the dense oracle if any block is unreachable from the entry.
426
+ for (let b = 0; b < n; b++)
427
+ if (!reachX[b])
428
+ return computeInSetsDense(cfg, n, h, adj, limits);
429
+ const idom = buildDominators(rpo, dPredsX, S, nx);
430
+ // ── dominance frontiers (Cytron) ──
431
+ const df = buildDominanceFrontiers(dPredsX, idom, nx);
432
+ // ── per-binding def blocks (must- or may-def ⇒ block transfer touches v) ──
433
+ const defBlocks = Array.from({ length: nBindings }, () => []);
434
+ for (let b = 0; b < n; b++) {
435
+ const g = gen[b];
436
+ if (g)
437
+ for (const v of g.keys())
438
+ defBlocks[v].push(b);
439
+ }
440
+ // ── value-graph nodes: leaves carry def-site keys; internal nodes (φ /
441
+ // may-def union) carry operand node ids. reachingSet(node) = union of all
442
+ // leaf keys reachable through operands (computed once, cycle-safe, below).
443
+ const nodeKeys = [];
444
+ const nodeOps = [];
445
+ const newLeaf = (keys) => (nodeKeys.push(keys),
446
+ nodeOps.push([]),
447
+ nodeKeys.length - 1);
448
+ const newInternal = () => (nodeKeys.push(null), nodeOps.push([]), nodeKeys.length - 1);
449
+ // ── φ-placement: φ for v at the iterated dominance frontier of v's defs ──
450
+ const phiNode = new Array(nx).fill(null);
451
+ for (let v = 0; v < nBindings; v++) {
452
+ const dB = defBlocks[v];
453
+ if (dB.length === 0)
454
+ continue;
455
+ const placed = new Set();
456
+ const inWork = new Set(dB);
457
+ const work = [...dB];
458
+ while (work.length) {
459
+ const x = work.pop();
460
+ for (const y of df[x]) {
461
+ if (placed.has(y))
462
+ continue;
463
+ placed.add(y);
464
+ let m = phiNode[y];
465
+ if (!m)
466
+ phiNode[y] = m = new Map();
467
+ m.set(v, newInternal());
468
+ if (!inWork.has(y)) {
469
+ inWork.add(y);
470
+ work.push(y);
471
+ }
472
+ }
473
+ }
474
+ }
475
+ // ── memory bound (#2201 review R1): cap the value graph, else fall back ──
476
+ // After φ-placement, nodeKeys.length == the φ-node count — the term that grows
477
+ // superlinearly with the input on the deep-loop / dense-binding pathology.
478
+ // Renaming below adds at most ~2 nodes per gen entry (already bounded by the
479
+ // def-site universe the STMT_STRIDE overflow guard caps). If the projected
480
+ // total would exceed the budget, fall back to the dense oracle here — BEFORE
481
+ // paying for renaming + Tarjan SCC on a blown-up graph. Byte-identical (dense
482
+ // is the equivalence oracle) and bounded (dense honors maxBlockVisits). Mirrors
483
+ // the throw-edge / unreachable / OOB-binding gates at the top of this function.
484
+ const nodeBudget = limits?.maxSsaValueGraphNodes && limits.maxSsaValueGraphNodes > 0
485
+ ? limits.maxSsaValueGraphNodes
486
+ : DEFAULT_MAX_SSA_VALUE_GRAPH_NODES;
487
+ let projectedRenameNodes = 0;
488
+ for (let b = 0; b < n; b++)
489
+ projectedRenameNodes += (gen[b]?.size ?? 0) * 2;
490
+ if (nodeKeys.length + projectedRenameNodes > nodeBudget) {
491
+ return computeInSetsDense(cfg, n, h, adj, limits);
492
+ }
493
+ // ── renaming (iterative dominator-tree DFS, per-binding value stacks) ──
494
+ const domChildren = Array.from({ length: nx }, () => []);
495
+ for (let b = 0; b < nx; b++)
496
+ if (b !== S && idom[b] !== -1)
497
+ domChildren[idom[b]].push(b);
498
+ for (const list of domChildren)
499
+ list.sort((a, b) => a - b);
500
+ const stacks = Array.from({ length: nBindings }, () => []);
501
+ const entryValue = new Array(nx).fill(null);
502
+ const enterBlock = (b) => {
503
+ const pushed = [];
504
+ const pm = phiNode[b];
505
+ if (pm)
506
+ for (const [v, node] of pm) {
507
+ stacks[v].push(node);
508
+ pushed.push(v);
509
+ }
510
+ // record block-entry (IN) value for each binding USED here — after φ push,
511
+ // before this block's own gen (the sweep applies intra-block defs itself).
512
+ // The synthetic entry S has no block ⇒ no statements/gen/uses.
513
+ const stmts = cfg.blocks[b]?.statements;
514
+ if (stmts) {
515
+ let ev = null;
516
+ for (const s of stmts)
517
+ for (const u of s.uses) {
518
+ const st = stacks[u];
519
+ if (st.length) {
520
+ if (!ev)
521
+ ev = new Map();
522
+ ev.set(u, st[st.length - 1]);
523
+ }
524
+ }
525
+ entryValue[b] = ev;
526
+ }
527
+ // apply block gen ⇒ OUT values that flow to successors
528
+ const g = gen[b];
529
+ if (g)
530
+ for (const [v, ge] of g) {
531
+ const st = stacks[v];
532
+ let node;
533
+ if (ge.kills) {
534
+ node = newLeaf(ge.set);
535
+ }
536
+ else {
537
+ node = newInternal();
538
+ if (st.length)
539
+ nodeOps[node].push(st[st.length - 1]); // prior reaching (may-def keeps it)
540
+ nodeOps[node].push(newLeaf(ge.set));
541
+ }
542
+ st.push(node);
543
+ pushed.push(v);
544
+ }
545
+ // fill successor φ operands with this block's current OUT for each φ binding
546
+ for (const s of succsX[b]) {
547
+ const sm = phiNode[s];
548
+ if (!sm)
549
+ continue;
550
+ for (const [v, phi] of sm) {
551
+ const st = stacks[v];
552
+ if (st.length)
553
+ nodeOps[phi].push(st[st.length - 1]);
554
+ }
555
+ }
556
+ return pushed;
557
+ };
558
+ const frames = [
559
+ { b: S, ci: 0, pushed: enterBlock(S) },
560
+ ];
561
+ while (frames.length) {
562
+ const f = frames[frames.length - 1];
563
+ const kids = domChildren[f.b];
564
+ if (f.ci < kids.length) {
565
+ const c = kids[f.ci++];
566
+ frames.push({ b: c, ci: 0, pushed: enterBlock(c) });
567
+ }
568
+ else {
569
+ for (const v of f.pushed)
570
+ stacks[v].pop();
571
+ frames.pop();
572
+ }
573
+ }
574
+ // ── reaching sets per node via SCC condensation (cycle-safe union) ──
575
+ // Tarjan condenses the value graph (operand cycles from loop φs collapse to a
576
+ // single SCC); a forward pass over the reverse-topo SCC order unions each
577
+ // SCC's reaching set from its operands' (alias fast path for single-source
578
+ // SCCs — #2201 review R2). Both stages are pure (reaching-defs-graph.ts).
579
+ const { sccOf, sccMembers } = tarjanScc(nodeOps);
580
+ const reachByScc = condenseReachingSets(sccMembers, sccOf, nodeKeys, nodeOps);
581
+ return {
582
+ converged: true,
583
+ reachingAt: (blockIndex, binding) => {
584
+ const node = entryValue[blockIndex]?.get(binding);
585
+ if (node === undefined)
586
+ return undefined;
587
+ const set = reachByScc[sccOf[node]];
588
+ return set.size ? set : undefined;
589
+ },
590
+ };
591
+ }
592
+ /**
593
+ * Minimum block count below which SSA construction (dominators + dominance
594
+ * frontiers + φ-placement + renaming + SCC) does not amortize over the dense
595
+ * worklist's single-pass aliasing. Calibrated empirically (~14-block crossover
596
+ * for loop-heavy functions; 16 leaves headroom); the dense-bindings
597
+ * `rd_scaling_budget` gate in bench/cfg/baselines.json catches a regression if
598
+ * this is mistuned. Paired with a reachable-loop check — loop-free functions
599
+ * always take the cheaper dense path regardless of size.
600
+ */
601
+ const SSA_MIN_BLOCKS = 16;
602
+ /**
603
+ * Default ceiling on the SSA-sparse solver's value-graph node count (#2201
604
+ * review R1). Above this the sparse path falls back to the dense oracle (which
605
+ * bounds its own work via `maxBlockVisits`), trading the deep-loop full-facts
606
+ * win for bounded memory on pathological inputs. Sized FAR above any real or
607
+ * benchmarked function: the suite's densest SSA scenarios (`dense-bindings`,
608
+ * `deep-nest`) build well under 10⁴ nodes, while the pathology this guards
609
+ * (thousands of blocks × hundreds of bindings) builds 10⁶–10⁷. The
610
+ * `dense-bindings` / `deep-nest` `rd_scaling_budget` gates in
611
+ * bench/cfg/baselines.json fail if this is set so low it forces those scenarios
612
+ * onto the dense path. Overridable per-call via
613
+ * {@link ReachingDefsLimits.maxSsaValueGraphNodes}.
614
+ */
615
+ const DEFAULT_MAX_SSA_VALUE_GRAPH_NODES = 1_000_000;
616
+ /**
617
+ * Production solver dispatcher (#2201). The SSA solver beats the dense worklist
618
+ * only when there is enough work to amortize SSA construction — a loop (so the
619
+ * dense fixpoint pays the loop-depth pass multiplier, or truncates at the
620
+ * ceiling) AND a non-trivial block count. Small or loop-free functions, which
621
+ * dense solves in one or two cheap aliasing passes, stay on the dense path.
622
+ * Because the two solvers are byte-identical (held by the equivalence fuzz),
623
+ * this is a pure performance heuristic with no effect on results.
624
+ *
625
+ * @internal
626
+ */
627
+ function computeInSetsAuto(cfg, n, h, adj, limits) {
628
+ if (n >= SSA_MIN_BLOCKS && hasReachableLoop(cfg.entryIndex, adj.succs, n)) {
629
+ return computeInSetsSparse(cfg, n, h, adj, limits);
630
+ }
631
+ return computeInSetsDense(cfg, n, h, adj, limits);
632
+ }
633
+ /**
634
+ * Statement sweep — recover statement-granular def→use facts from the per-block
635
+ * entry reaching lattices, sort them, and apply the maxFacts truncation. SHARED
636
+ * by both solvers, and the maxFacts cutoff is where their (intentionally
637
+ * different) reaching-set INSERTION orders would otherwise leak into the output:
638
+ * the dense worklist seeds keys in RPO fixpoint order, the SSA solver in
639
+ * renaming/SCC order, so a loop-carried use's reaching set is the same SET in a
640
+ * different order. The byte-identity of a TRUNCATED result therefore does NOT
641
+ * come from matching insertion orders — it comes from the KTD6 per-use
642
+ * `useKeys.sort()` BELOW, which canonicalizes each use's keys by defKey before
643
+ * the cutoff. (The full, untruncated fact array is re-sorted at the end, so the
644
+ * pre-sort is a no-op there; its whole purpose is the truncated prefix.) Outer
645
+ * emission order — block index, then statement index, then use order — is shared
646
+ * structurally and needs no canonicalization.
647
+ */
648
+ function sweepFacts(blocks, reachingAt, defLine, maxFacts) {
649
+ const facts = [];
650
+ let truncated = false;
651
+ // Scratch buffer for one use's reaching def-keys, reused across every use to
652
+ // avoid a per-use array allocation (#2201 review R9). Cleared per use; the
653
+ // KTD6 sort below operates on it in place.
654
+ const useKeys = [];
655
+ outer: for (const b of blocks) {
656
+ const stmts = b.statements;
657
+ if (!stmts || stmts.length === 0)
658
+ continue;
659
+ // Sparse intra-block overlay: only the bindings REDEFINED within this block
660
+ // so far. A use's reaching set is the overlay's override if present, else
661
+ // the block-entry reaching set (reachingAt). This never materializes the
662
+ // full block lattice — the dense O(live-vars) per-block copy the sparse
663
+ // solver exists to avoid.
664
+ const overlay = new Map();
665
+ for (let i = 0; i < stmts.length; i++) {
666
+ const s = stmts[i];
667
+ // A use's binding that the SAME statement also defines could be a
668
+ // read-then-write (`x += 1` — sees prior defs) OR a write-then-read
669
+ // (`if ((m = re.exec(s)) && m[1])` — sees the same-statement def).
670
+ // StatementFacts carries no intra-statement order, so emit BOTH: prior
671
+ // defs ∪ the same-statement def. Sound over-approximation — the extra
672
+ // self-fact on compound assignments is harmless; missing the
673
+ // assign-and-test def→use (the most common JS idiom) would be a taint
674
+ // false negative. May-defs join the self-key set the same way.
675
+ // def/mayDef arrays are tiny (1–3 entries), so a membership scan over them
676
+ // is cheaper than the old per-statement `new Set([...defs, ...mayDefs])`
677
+ // (#2201 review R9). `hasSelfDefs` short-circuits pure-use statements.
678
+ const hasSelfDefs = s.defs.length > 0 || (s.mayDefs?.length ?? 0) > 0;
679
+ for (const u of s.uses) {
680
+ const reaching = overlay.get(u) ?? reachingAt(b.index, u);
681
+ const selfKey = hasSelfDefs && (s.defs.includes(u) || (s.mayDefs?.includes(u) ?? false))
682
+ ? defKey(b.index, i)
683
+ : undefined;
684
+ if (!reaching && selfKey === undefined)
685
+ continue;
686
+ // Reuse the scratch buffer instead of spreading a fresh array per use.
687
+ useKeys.length = 0;
688
+ if (reaching)
689
+ for (const k of reaching)
690
+ useKeys.push(k);
691
+ if (selfKey !== undefined && !reaching?.has(selfKey))
692
+ useKeys.push(selfKey);
693
+ // Canonical emission order (#2201 KTD6): sort each use's reaching
694
+ // def-sites by defKey (= def block, then def stmt) BEFORE the maxFacts
695
+ // cutoff. The full (untruncated) fact array is re-sorted identically at
696
+ // the end, so this is a no-op there; its purpose is to make the
697
+ // TRUNCATED subset schedule-independent — the reaching SET's insertion
698
+ // order is fixpoint-evaluation-order-dependent for loop-carried
699
+ // bindings (dense RPO vs sparse change-driven seed different keys
700
+ // first), so a pre-sort cutoff is what keeps the two solvers'
701
+ // truncated results byte-identical.
702
+ useKeys.sort((a, b) => a - b);
703
+ for (const key of useKeys) {
704
+ if (facts.length >= maxFacts) {
705
+ truncated = true;
706
+ break outer;
707
+ }
708
+ const defBlock = Math.floor(key / STMT_STRIDE);
709
+ const defStmt = key % STMT_STRIDE;
710
+ facts.push({
711
+ bindingIdx: u,
712
+ def: { blockIndex: defBlock, stmtIndex: defStmt, line: defLine.get(key) ?? s.line },
713
+ use: { blockIndex: b.index, stmtIndex: i, line: s.line },
714
+ });
715
+ }
716
+ }
717
+ if (s.mayDefs?.length) {
718
+ // Gen WITHOUT kill: the conditional def joins the binding's set.
719
+ const key = defKey(b.index, i);
720
+ for (const d of s.mayDefs) {
721
+ const prior = overlay.get(d) ?? reachingAt(b.index, d);
722
+ overlay.set(d, prior ? unionSets(prior, new Set([key])) : new Set([key]));
723
+ }
724
+ }
725
+ if (s.defs.length > 0) {
726
+ for (const d of s.defs)
727
+ overlay.set(d, new Set([defKey(b.index, i)])); // kill + gen
728
+ }
729
+ }
730
+ }
731
+ facts.sort((a, b) => a.def.blockIndex - b.def.blockIndex ||
732
+ a.def.stmtIndex - b.def.stmtIndex ||
733
+ a.use.blockIndex - b.use.blockIndex ||
734
+ a.use.stmtIndex - b.use.stmtIndex ||
735
+ a.bindingIdx - b.bindingIdx);
736
+ return { facts, truncated };
737
+ }
738
+ /**
739
+ * Union predecessor lattices, sharing sets where possible. A normal edge
740
+ * contributes OUT(from). A THROW edge contributes IN(from) ∪ allDefs(from):
741
+ * an exception may fire before, between, or after any of the block's defs, so
742
+ * the handler can observe the incoming state OR any intermediate def — OUT
743
+ * alone (last-def-wins) misses defs overwritten later in the same block.
744
+ * IN ∪ allDefs ⊇ OUT, so the throw contribution subsumes it.
745
+ */
746
+ function mergePreds(preds, inSets, outSets, allDefsGen) {
747
+ const merged = new Map();
748
+ const mergeOne = (source) => {
749
+ for (const [bindingIdx, set] of source) {
750
+ const existing = merged.get(bindingIdx);
751
+ if (!existing) {
752
+ merged.set(bindingIdx, set); // share the first contributor's set
753
+ }
754
+ else if (existing !== set) {
755
+ // Union only when the references differ. Copy-on-extend: `existing`
756
+ // may be a shared set from another block — never mutate it.
757
+ let target = existing;
758
+ let copied = false;
759
+ for (const key of set) {
760
+ if (!target.has(key)) {
761
+ if (!copied) {
762
+ target = new Set(existing);
763
+ copied = true;
764
+ }
765
+ target.add(key);
766
+ }
767
+ }
768
+ if (copied)
769
+ merged.set(bindingIdx, target);
770
+ }
771
+ }
772
+ };
773
+ for (const p of preds) {
774
+ if (p.viaThrow) {
775
+ mergeOne(inSets[p.from]); // exception may fire pre-defs…
776
+ const all = allDefsGen[p.from];
777
+ if (all)
778
+ mergeOne(all); // …or after ANY of the block's defs
779
+ }
780
+ else {
781
+ mergeOne(outSets[p.from]);
782
+ }
783
+ }
784
+ return merged;
785
+ }