Add linearity and accumulator tracking in flow analysis#485
Add linearity and accumulator tracking in flow analysis#485ychenfo wants to merge 11 commits intohkust-taco:hkmc2from
Conversation
ychenfo
commented
May 6, 2026
- Add affinity and accumulator tracking in flow analysis
- Refactor flow analysis strategy types
- Update flow analysis related config
- Pretty print deforest result
… strategy types; update flow analysis config
There was a problem hiding this comment.
Pull request overview
This PR extends the compiler’s flow analysis to track (1) non-affinity/linearity and (2) accumulator-ness, and wires those signals into deforestation and dead-parameter-elimination behavior and logging. It also refactors the flow-analysis strategy model (introducing ConcreteId, strategy origins, and marker strategies) and updates test expectations to match the new debug/pretty-printed output.
Changes:
- Introduce
FlowAnalysisConfigand refactorDeforest/DeadParamElimconfigs to share flow-analysis settings (including new tracking/logging toggles). - Extend
FlowAnalysiswith capture tracking, non-affine tracking, and accumulator tracking; propagate markers through constraints/solver; add scoped trace logging for symbol dumps. - Update deforest + DPE tests (and add a new fusibility test suite) to match the new fusing/non-affine/accumulator reporting format.
Reviewed changes
Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| hkmc2DiffTests/src/test/scala/hkmc2/MLsDiffMaker.scala | Adds flag parsing/validation and maps flags into the new flow-analysis config shape for :deforest and :deadParamElim. |
| hkmc2/shared/src/main/scala/hkmc2/Config.scala | Introduces FlowAnalysisConfig; refactors Deforest/DeadParamElim to wrap it; updates config directive parsing. |
| hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala | Switches deforest trace logger creation to FlowAnalysis.mkTraceLogger. |
| hkmc2/shared/src/main/scala/hkmc2/codegen/flowAnalysis/FlowAnalysis.scala | Major refactor: new strategy/origin model, capture tracking, branch-aware affinity counting, accumulator/non-affine propagation, and scoped trace output. |
| hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Deforest.scala | Wires in new flow-analysis inputs (effective tracking toggles) and changes fusion debug output formatting/printing. |
| hkmc2/shared/src/main/scala/hkmc2/codegen/deforest/Rewrite.scala | Updates IDs from CtorDtorId to ConcreteId and adjusts lookups accordingly. |
| hkmc2/shared/src/main/scala/hkmc2/codegen/DeadParamElim.scala | Reworks concrete IDs, updates flow-analysis invocation to include tracking toggles, and adapts solver logic to new marker strategies. |
| hkmc2/shared/src/test/mlscript/codegen/ConfigDirective.mls | Extends config-directive tests for new deforest logging behavior (logNonAffine). |
| hkmc2/shared/src/test/mlscript/dead-param-elim/basic.mls | Adds coverage for :deadParamElim flag parsing/behavior (default, debug, conflicts, off, unknown). |
| hkmc2/shared/src/test/mlscript/dead-param-elim/dead-ref.mls | Removes a stray blank line in test input. |
| hkmc2/shared/src/test/mlscript/deforest/fusibility.mls | New deforest tests covering non-affinity and accumulator logging scenarios. |
| hkmc2/shared/src/test/mlscript/deforest/floatoutSafety.mls | Removes older float-out safety test file (replaced by newer fusibility coverage). |
| hkmc2/shared/src/test/mlscript/deforest/basic.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/append.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/clashes.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/cyclic.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/determinism.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/imperative.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/listComprehension.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/module.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/multiArgLists.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/nestedMatch.mls | Updates expected deforest debug output formatting and some rendered values. |
| hkmc2/shared/src/test/mlscript/deforest/recursive.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/relaxedPrograms.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/selectionsInNestedMatch.mls | Updates expected deforest debug output formatting. |
| hkmc2/shared/src/test/mlscript/deforest/todos.mls | Updates expected deforest debug output formatting and rendered values. |
| hkmc2/shared/src/test/mlscript/deforest/zipunzip.mls | Updates expected deforest debug output formatting. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| val scope = Scope.empty(Scope.Cfg.default) | ||
| given Scope = scope | ||
| given ShowCfg = ShowCfg.internal | ||
| given SymbolPrinter = new SymbolPrinter(scope) |
There was a problem hiding this comment.
You shouldn't create your own printer; you should use the one provided here
ie, just make pp accept it implicitly.
This way, the symbols will be printed consistently with things like :sir and :lot.
There was a problem hiding this comment.
Updated this to thread the SymbolPrinter (needed by the code printer) through lowering / flow analysis into DeforestFusionSolver, so deforest debug printing no longer creates its own SymbolPrinter and in diff tests we now should use the same dbgPrinter: SymbolPrinter provided by MlsDiffMaker.
For MLsCompiler.scala, I moved the SymbolPrinter and trace logger setup into compileModule because constructing the SymbolPrinter needs a Scope, and Scope.empty requires an Elaborator.State.
| cc.constrain(NoProd, preAnalyzer.res.primitiveStratVar.asConsStrat) | ||
| processBlock(preAnalyzer.pgrm.main)(using cc, NoCons) | ||
|
|
||
| // emit NonAffine for non-affine syms that don't belong to any scc: |
There was a problem hiding this comment.
Can you explain the reasoning behind "don't belong to any scc" => "emit NonAffine"?
There was a problem hiding this comment.
Ah, this was a stale wip note to myself and the wording may be misleading, it's removed now. By this comment I only meant that the correct NonAffine constraints should be added to the correct collector, and the logic is not "doesn’t belong to an scc, therefore emit NonAffine". The symbol is already classified as non-affine by pre-analysis, and this code here is only about placing the non-affine constraints: in mono mode there are no per-scc collectors, so the global collector gets the relevant constraints; in poly mode, scc-owned symbols are handled in their scc strat scheme, and the global collector handles the remaining symbols.
There was a problem hiding this comment.
Thanks for the explanation. Could you document that explanation in the code, at an appropriate and if provide visible place?
| //│ let t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14, tmp15; | ||
| //│ return x(t, a, arg$AA$0$, arg$AA$0$1, arg$AA$0$2, tmp11, tmp12, tmp13, tmp14, tmp15, t, tmp11, tmp12, tmp13, tmp14, tmp15) | ||
| //│ }; | ||
| //│ define test_20$test⁰ as fun test_20$test¹(x) { |
There was a problem hiding this comment.
Do you know why some of these definitions are no longer here?
There was a problem hiding this comment.
The gone definitions are polymorphic instantiations of functions that used to contain deforestation rewritings. In this example since p is not used affinely, some deforestation matches are gone and then we no longer create that much polymorphic instantiations.
| given tl: TraceLogger = constraintSolver.tl | ||
| given Raise = preAnalyzer.raise | ||
|
|
||
| private given Scope = Scope.empty(Scope.Cfg.default) |
There was a problem hiding this comment.
Because the printer below needs a Scope: the codegen.Printer.print method takes a using Scope parameter.
There was a problem hiding this comment.
Ah, but this scope is used to show the top-level definitions. I think it's important that it correspond to the MLsDiffMaker.scala, so you should thread it through.