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.
- package/README.md +3 -3
- package/dist/_shared/graph/types.d.ts +31 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +11 -1
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +6 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +31 -1
- package/dist/cli/ai-context.js +18 -16
- package/dist/cli/analyze-config.js +11 -0
- package/dist/cli/analyze.d.ts +14 -0
- package/dist/cli/analyze.js +98 -0
- package/dist/cli/embedding-dims.d.ts +33 -0
- package/dist/cli/embedding-dims.js +39 -0
- package/dist/cli/eval-server.d.ts +8 -0
- package/dist/cli/eval-server.js +242 -2
- package/dist/cli/help-i18n.js +19 -0
- package/dist/cli/i18n/en.d.ts +15 -1
- package/dist/cli/i18n/en.js +15 -1
- package/dist/cli/i18n/resources.d.ts +29 -1
- package/dist/cli/i18n/zh-CN.d.ts +14 -0
- package/dist/cli/i18n/zh-CN.js +15 -1
- package/dist/cli/index.js +89 -1
- package/dist/cli/mcp.d.ts +6 -1
- package/dist/cli/mcp.js +26 -1
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/setup.d.ts +3 -1
- package/dist/cli/setup.js +67 -17
- package/dist/cli/skill-gen.js +2 -1
- package/dist/cli/tool.d.ts +19 -1
- package/dist/cli/tool.js +96 -3
- package/dist/core/embeddings/hf-env.d.ts +0 -66
- package/dist/core/embeddings/http-client.d.ts +7 -0
- package/dist/core/embeddings/http-client.js +5 -3
- package/dist/core/embeddings/server-mapping.d.ts +1 -1
- package/dist/core/embeddings/server-mapping.js +7 -3
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/cross-impact.d.ts +2 -1
- package/dist/core/group/cross-impact.js +5 -1
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +22 -4
- package/dist/core/group/extractors/include-extractor.js +22 -13
- package/dist/core/group/sync.d.ts +27 -1
- package/dist/core/group/sync.js +0 -0
- package/dist/core/incremental/subgraph-extract.js +18 -1
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
- package/dist/core/ingestion/cfg/collect.d.ts +51 -0
- package/dist/core/ingestion/cfg/collect.js +77 -0
- package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
- package/dist/core/ingestion/cfg/control-dependence.js +182 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
- package/dist/core/ingestion/cfg/emit.d.ts +293 -0
- package/dist/core/ingestion/cfg/emit.js +646 -0
- package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
- package/dist/core/ingestion/cfg/post-dominators.js +176 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
- package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
- package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
- package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
- package/dist/core/ingestion/cfg/traversal-result.js +2 -0
- package/dist/core/ingestion/cfg/types.d.ts +274 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
- package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
- package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
- package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
- package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/go.js +638 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
- package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
- package/dist/core/ingestion/cfg/visitors/java.js +812 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
- package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
- package/dist/core/ingestion/cfg/visitors/php.js +721 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
- package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
- package/dist/core/ingestion/cfg/visitors/python.js +558 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
- package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
- package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
- package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
- package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
- package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
- package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
- package/dist/core/ingestion/import-resolvers/utils.js +2 -0
- package/dist/core/ingestion/language-provider.d.ts +11 -0
- package/dist/core/ingestion/languages/c-cpp.js +6 -1
- package/dist/core/ingestion/languages/cobol.js +4 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
- package/dist/core/ingestion/languages/cpp/captures.js +50 -1
- package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
- package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
- package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
- package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
- package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
- package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
- package/dist/core/ingestion/languages/csharp.js +2 -0
- package/dist/core/ingestion/languages/dart.js +2 -0
- package/dist/core/ingestion/languages/go.js +2 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/kotlin/query.js +3 -2
- package/dist/core/ingestion/languages/kotlin.js +3 -0
- package/dist/core/ingestion/languages/php.js +2 -0
- package/dist/core/ingestion/languages/python.js +2 -0
- package/dist/core/ingestion/languages/ruby.js +2 -0
- package/dist/core/ingestion/languages/rust.js +2 -0
- package/dist/core/ingestion/languages/swift.js +2 -0
- package/dist/core/ingestion/languages/typescript.js +5 -0
- package/dist/core/ingestion/languages/vue.js +6 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/mro-processor.js +109 -0
- package/dist/core/ingestion/parsing-processor.js +23 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/index.js +2 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
- package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
- package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
- package/dist/core/ingestion/pipeline.d.ts +92 -10
- package/dist/core/ingestion/pipeline.js +11 -2
- package/dist/core/ingestion/scope-extractor.js +12 -2
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
- package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
- package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
- package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
- package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
- package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
- package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
- package/dist/core/ingestion/taint/call-summary-model.js +25 -0
- package/dist/core/ingestion/taint/emit.d.ts +125 -0
- package/dist/core/ingestion/taint/emit.js +208 -0
- package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
- package/dist/core/ingestion/taint/interproc-emit.js +88 -0
- package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
- package/dist/core/ingestion/taint/interproc-solver.js +312 -0
- package/dist/core/ingestion/taint/match.d.ts +153 -0
- package/dist/core/ingestion/taint/match.js +278 -0
- package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
- package/dist/core/ingestion/taint/path-codec.js +190 -0
- package/dist/core/ingestion/taint/propagate.d.ts +216 -0
- package/dist/core/ingestion/taint/propagate.js +659 -0
- package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
- package/dist/core/ingestion/taint/site-safety.js +98 -0
- package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
- package/dist/core/ingestion/taint/source-sink-config.js +35 -12
- package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
- package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
- package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
- package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
- package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
- package/dist/core/ingestion/taint/summary-harvest.js +537 -0
- package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
- package/dist/core/ingestion/taint/summary-model.js +86 -0
- package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
- package/dist/core/ingestion/utils/env.d.ts +7 -0
- package/dist/core/ingestion/utils/env.js +12 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +13 -0
- package/dist/core/ingestion/workers/parse-worker.js +76 -17
- package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
- package/dist/core/ingestion/workers/worker-pool.js +15 -2
- package/dist/core/lbug/csv-generator.d.ts +51 -3
- package/dist/core/lbug/csv-generator.js +369 -255
- package/dist/core/lbug/lbug-adapter.d.ts +58 -18
- package/dist/core/lbug/lbug-adapter.js +346 -84
- package/dist/core/lbug/lbug-config.d.ts +19 -0
- package/dist/core/lbug/lbug-config.js +29 -0
- package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
- package/dist/core/lbug/pdg-emit-sink.js +358 -0
- package/dist/core/lbug/pool-adapter.d.ts +35 -0
- package/dist/core/lbug/pool-adapter.js +90 -1
- package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
- package/dist/core/lbug/rel-pair-routing.js +140 -0
- package/dist/core/lbug/schema.d.ts +2 -2
- package/dist/core/lbug/schema.js +3 -0
- package/dist/core/logger.d.ts +0 -5
- package/dist/core/run-analyze.d.ts +96 -0
- package/dist/core/run-analyze.js +207 -3
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/core/wiki/generator.js +3 -1
- package/dist/core/wiki/graph-queries.d.ts +6 -0
- package/dist/core/wiki/graph-queries.js +9 -1
- package/dist/mcp/http-transport.d.ts +134 -0
- package/dist/mcp/http-transport.js +503 -0
- package/dist/mcp/local/local-backend.d.ts +110 -24
- package/dist/mcp/local/local-backend.js +1225 -19
- package/dist/mcp/local/pdg-impact.d.ts +384 -0
- package/dist/mcp/local/pdg-impact.js +1972 -0
- package/dist/mcp/resources.js +13 -1
- package/dist/mcp/tools.d.ts +12 -0
- package/dist/mcp/tools.js +214 -7
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/api.d.ts +17 -0
- package/dist/server/api.js +132 -168
- package/dist/server/git-clone.d.ts +43 -2
- package/dist/server/git-clone.js +211 -16
- package/dist/server/mcp-http.d.ts +5 -6
- package/dist/server/mcp-http.js +9 -82
- package/dist/server/middleware.d.ts +40 -0
- package/dist/server/middleware.js +92 -0
- package/dist/server/private-ip.d.ts +1 -0
- package/dist/server/private-ip.js +15 -0
- package/dist/server/upload-ingest.d.ts +56 -0
- package/dist/server/upload-ingest.js +276 -0
- package/dist/server/upload-paths.d.ts +37 -0
- package/dist/server/upload-paths.js +57 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/git.js +5 -8
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +41 -10
- package/dist/storage/repo-manager.d.ts +121 -10
- package/dist/storage/repo-manager.js +43 -5
- package/dist/types/pipeline.d.ts +9 -0
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
- package/hooks/claude/gitnexus-hook.cjs +110 -13
- package/hooks/claude/hook-db-lock-probe.cjs +513 -26
- package/package.json +8 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/scripts/cross-platform-tests.ts +1 -0
- package/skills/gitnexus-debugging.md +16 -4
- package/skills/gitnexus-exploring.md +3 -3
- package/skills/gitnexus-guide.md +33 -0
- package/skills/gitnexus-pdg-query.md +89 -0
- package/skills/gitnexus-refactoring.md +1 -1
- package/skills/gitnexus-taint-analysis.md +178 -0
- package/vendor/tree-sitter-c/package.json +1 -1
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-kotlin/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -1
- package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
- package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
- package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
- package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
- package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
- package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
- package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
- package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
- package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
- package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
- package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
- package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
- package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
- package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
- package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
- package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
- package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
- package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
- package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
- package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
- package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
- package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
- package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
- package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
- package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
- package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
- package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
- package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
- package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
- package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
- package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
- package/web/assets/index-BvYev1UJ.js +635 -0
- package/web/assets/index-_lgn7hs5.css +2 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
- package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
- package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
- package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
- package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
- package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
- package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
- package/web/index.html +13 -15
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
- package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
- package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
- package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
- package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
- package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
- package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
- package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
- package/web/assets/chunk-CilyBKbf.js +0 -1
- package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
- package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
- package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
- package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
- package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
- package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
- package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
- package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
- package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
- package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
- package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
- package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
- package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
- package/web/assets/index-BKWA-m7o.css +0 -2
- package/web/assets/index-CG6q8eTs.js +0 -626
- package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
- package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
- package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
- /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
|
+
}
|