@@ -50,6 +50,44 @@ These are global memory reads (~400 cycle latency) and must be computed once.
5050 < |"Expr" -> newExpr , "Definitions" -> Table [{names [[i ]], uniqueCalls [[i ]]}, {i , 1 , Length [uniqueCalls ]}], "Count" -> Length [uniqueCalls ]|>
5151 ];
5252
53+ (**********************************************************************************
54+ Pass 1b: Composite-Denominator Hoisting
55+ Extract every distinct negative-integer power of a composite (Plus/Times) base
56+ -- the regulated-propagator denominators 1/(M^2 + q*(...)^2) etc. These are NOT
57+ interpolator leaves, so without global hoisting they fall to the per-sub-kernel
58+ CSE (Pass 3) and are recomputed once per chunk when the term-sum is split.
59+ Hoisting them to shared "_den" defs (like interpolators) computes each once.
60+
61+ Bases are sorted by LeafCount ascending so a denominator nested inside another
62+ gets a lower index; each def body then substitutes the already-assigned inner
63+ "_denK" (rules without its own), making the def list topologically ordered.
64+ Runs AFTER interpolator hoisting, so bases already carry "_interp" placeholders.
65+ **********************************************************************************)
66+
67+ hoistDivisions [expr_ ] :=
68+ Module [{cands , uniqueDens , names , rules , newExpr , defs },
69+ If [Not @ TrueQ [$codeHoistDivisions ],
70+ Return [< |"Expr" -> expr , "Definitions" -> {}, "Count" -> 0 |> ]
71+ ];
72+ (* composite reciprocals: Power[base, n<0] with base a Plus or Times (a shared,
73+ non-trivial denominator), not a bare symbol/number/placeholder *)
74+ cands = Cases [expr ,
75+ Power [b_ , n_ Integer ] /; n < 0 && (Head [b ] === Plus || Head [b ] === Times ),
76+ Infinity ];
77+ (* inner (smaller) denominators first -> def list is dependency-ordered *)
78+ uniqueDens = SortBy [DeleteDuplicates [cands ], LeafCount ];
79+ If [Length [uniqueDens ] === 0 ,
80+ Return [< |"Expr" -> expr , "Definitions" -> {}, "Count" -> 0 |> ]
81+ ];
82+ names = Table ["_den" <> ToString [i ], {i , 1 , Length [uniqueDens ]}];
83+ rules = Thread [uniqueDens -> names ];
84+ newExpr = expr //. rules ;
85+ (* def body for _denI: replace NESTED denominators (all rules except its own,
86+ which would otherwise collapse the whole body to its own name) *)
87+ defs = Table [{names [[i ]], uniqueDens [[i ]] //. Drop [rules , {i }]}, {i , 1 , Length [uniqueDens ]}];
88+ < |"Expr" -> newExpr , "Definitions" -> defs , "Count" -> Length [uniqueDens ]|>
89+ ];
90+
5391(**********************************************************************************
5492 Power Basis Normalization
5593 After CSE, rewrite Power[base, m] in terms of an already-hoisted Power[base, n]
@@ -407,11 +445,35 @@ Partitions interps into shared (referenced by 2+ kernels) and local (1 kernel).
407445 interpNames = # [[1 ]]& /@ interpDefs ;
408446 interpsByName = Association @ Table [interpDefs [[i , 1 ]] -> interpDefs [[i ]], {i , Length [interpDefs ]}];
409447 Module [
410- {chunkRefs , useCounts , sharedNames , sharedDefs }
448+ {chunkRefs , useCounts , sharedNames , sharedDefs , defsByName }
411449 ,
412- (* Find which interp names each chunk references *)
413- chunkRefs = Map [Intersection [interpNames , DeleteDuplicates @ Cases [# , _ String , Infinity ]]& , chunks ];
414- (* Count how many chunks reference each interp *)
450+ (* name -> definition, for transitive reference expansion *)
451+ defsByName = Association @ Table [interpDefs [[i , 1 ]] -> interpDefs [[i ]], {i , Length [interpDefs ]}];
452+ (* Find which def names each chunk references -- TRANSITIVELY: a chunk that
453+ references _den5 also "uses" every _interp/_den that _den5's body reads,
454+ so a denominator shared across chunks (and its inputs) is correctly counted
455+ as shared rather than dropped or recomputed per chunk. *)
456+ chunkRefs =
457+ Map [
458+ Function [{chunk },
459+ Module [{refs , prevLen = - 1 },
460+ (* level {0, Infinity}: a chunk (or def body) that IS a bare
461+ placeholder string -- e.g. a term that is a single hoisted
462+ reciprocal _denK with no other factors -- sits at level 0 and
463+ would be missed by the default Infinity (= {1, Infinity}). *)
464+ refs = Intersection [interpNames , DeleteDuplicates @ Cases [chunk , _ String , {0 , Infinity }]];
465+ While [Length [refs ] =!= prevLen ,
466+ prevLen = Length [refs ];
467+ refs = DeleteDuplicates @ Join [refs ,
468+ Intersection [interpNames ,
469+ Flatten @ Map [Cases [defsByName [# ][[2 ]], _ String , {0 , Infinity }]& , refs ]]];
470+ ];
471+ refs
472+ ]
473+ ],
474+ chunks
475+ ];
476+ (* Count how many chunks reference each interp/den *)
415477 useCounts = Counts [Flatten [chunkRefs ]];
416478 sharedNames = Keys @ Select [useCounts , # > 1 & ];
417479 sharedDefs = Select [interpDefs , MemberQ [sharedNames , # [[1 ]]]& ];
@@ -583,7 +645,7 @@ Partitions interps into shared (referenced by 2+ kernels) and local (1 kernel).
583645**********************************************************************************)
584646
585647optimizeExpression [equation_ ] :=
586- Module [{expr , interpResult , interpCount , splitResult , sharedDefs , subKernels , result , allDefs },
648+ Module [{expr , interpResult , interpCount , divResult , divCount , globalDefs , splitResult , sharedDefs , subKernels , result , allDefs },
587649 FunKitDebug [1 , "Starting optimization pipeline (optimize = " , $codeOptimize , ")" ];
588650 (* If optimization is disabled, return raw expression with no passes *)
589651 If [! TrueQ [$codeOptimize ],
@@ -596,8 +658,15 @@ Partitions interps into shared (referenced by 2+ kernels) and local (1 kernel).
596658 expr = interpResult ["Expr" ];
597659 interpCount = interpResult ["Count" ];
598660 FunKitDebug [2 , "Hoisted " , interpCount , " interpolator calls" ];
661+ (* Pass 1b: Composite-denominator hoisting — share reciprocals across chunks *)
662+ divResult = cgTimed [$ProfileCgHoist , hoistDivisions [expr ]];
663+ expr = divResult ["Expr" ];
664+ divCount = divResult ["Count" ];
665+ FunKitDebug [2 , "Hoisted " , divCount , " composite denominators" ];
666+ (* global defs: interps FIRST, then denominators (which reference interps) *)
667+ globalDefs = Join [interpResult ["Definitions" ], divResult ["Definitions" ]];
599668 (* Pass 2: Early split decision *)
600- splitResult = cgTimed [$ProfileCgSplit , earlySplit [interpResult [ "Definitions" ] , expr ]];
669+ splitResult = cgTimed [$ProfileCgSplit , earlySplit [globalDefs , expr ]];
601670 FunKitDebug [2 , "Early split: " , splitResult ["Split" ]];
602671 (* === PER-KERNEL PASSES === *)
603672 If [TrueQ [splitResult ["Split" ]],
@@ -623,8 +692,8 @@ Partitions interps into shared (referenced by 2+ kernels) and local (1 kernel).
623692 < |"UseSubKernels" -> True , "SharedDefinitions" -> sharedDefs , "SubKernels" -> subKernels |>
624693 ,
625694 (* Single-kernel path: optimize the whole expression *)
626- result = optimizeSubKernel [expr , interpCount ];
627- allDefs = Join [interpResult [ "Definitions" ] , result ["Definitions" ]];
695+ result = optimizeSubKernel [expr , interpCount + divCount ];
696+ allDefs = Join [globalDefs , result ["Definitions" ]];
628697 FunKitDebug [2 , "Single-kernel optimization complete: " , Length [allDefs ], " total defs" ];
629698 (* Try splitting for registers if expression is large *)
630699 splitResult = cgTimed [$ProfileCgSplit , splitIntoSubKernels [allDefs , result ["Expr" ]]];
0 commit comments