feat(vpto): VPTOSoftPostUpdate Step 2: accumulator analysis + stride analysis unification#981
feat(vpto): VPTOSoftPostUpdate Step 2: accumulator analysis + stride analysis unification#981yexiaosu wants to merge 8 commits into
Conversation
Review findings1. Pass is a silent no-op on inferred vecscopes (blocking)
Every lit test and Design doc 5.1 is self-contradictory on this: it places the pass before Fix — two options. Either move the pass after 2.
|
Implement iter_arg-based accumulator analysis as a priority path before delta analysis. This handles patterns where base or offset is an iter_arg with explicit yield increment (arith.addi or pto.addptr), including non-constant, loop-varying strides that delta analysis cannot handle. Also fix computeInitialPtr to materialize base at loop entry for iter_arg base pointers.
Major refactoring of VPTOSoftPostUpdate based on review: - Replace two-path (accumulator/delta) mutual exclusion with unified getStride that independently analyzes each operand: accumulator (iter_arg) first, delta (IV/affine) fallback. - Remove tryAccumulatorAnalysis; merge logic into processForOp. - Recursive decomposeLinear for yield expressions: walk addi/subi/ muli/index_cast/addptr chains instead of single-level matching. - getIterArgIncrement look-through: trace through addi/subi/addptr with loop-invariant offset and index_cast to find underlying iter_arg. - Distinguish "not iter_arg" (nullopt, try delta) from "iter_arg but decomposition failed" (Value(), give up) in getIterArgIncrement. - Return zero constant for unchanged iter_arg (yield=blockArg) instead of treating it as failure. - Require both operands to be successfully analyzed (!deltaBase || !deltaOffset → skip) to prevent treating unknown delta as zero.
Analysis (decomposeLinear, getIterArgIncrement, computeDelta) is now pure and returns a symbolic StrideExpr, cached per Value. Materialization runs once at a single insertion point, after all legality checks pass. Fixes three defects: a stride whose increment is defined after the candidate op produced invalid IR or crashed; shared subexpressions were re-decomposed exponentially, taking 58s for a 28-op loop body; and scaling a non-constant i16 repeat_stride by an index-typed weight built an invalid arith.muli.
Two defects in the loop rewrite, both dating from the initial pass. Grouping: rewrites were grouped by (base, stride), but whether two ops can share an iter_arg depends on them walking the same address sequence, and the start address init_ptr is derived from base *and* strideOperand. Two ops on the same base with the same stride but different offsets -- %ub[%iv] and %ub[%iv + 64] -- were merged, and only the first one's init_ptr was kept, so the second silently started 64 elements early. strideOperand is now part of the key. Keying on the original operands rather than on init_ptr keeps the Value identity comparison meaningful, since computeInitialPtr may materialize a separate pto.addptr per candidate; this can split groups that could have been merged, but never merges groups that must stay apart. Traversal order: processVecScope reversed the walk result to get "inner to outer", but Operation::walk already defaults to post-order, so the reverse produced outer-to-inner. Rewriting a loop erases it, which also destroys every loop nested inside it, leaving the already-collected inner ForOp handles dangling -- a nested scf.for with a candidate in the outer loop aborted the compiler. Dropping the reverse restores the intended order.
negative-non-additive-yield was vacuous: FileCheck matches substrings, so
`-> !pto.vreg<64xf32>` also matched the transformed form, and the CHECK-NOT
region was bounded by the following `CHECK: scf.yield`, never reaching the
yield's use of updated_base. Anchor on end-of-line with {{$}} and move
CHECK-NOT after the yield check. It now fails against transformed IR, where
previously it passed.
accum-multilevel-yield never reached the subtraction path in decomposeLinear:
the canonicalizer folded subi(addi(%off, 64), 4) into addi(%off, %c60) before
the pass ran. Subtract the induction variable instead so the chain cannot be
constant-folded, confirmed with --mlir-print-ir-before.
All 26 soft_postupdate lit tests pass.
The pass only walks pto.vecscope regions, but it was scheduled before PTOInferVPTOVecScope -- the only pass that creates them. Kernels that do not already carry a hand-written vecscope therefore reached the pass with nothing to walk, and the rewrite was silently skipped. Every lit test wrote pto.vecscope by hand, so the suite stayed green. Removing the vecscope from soft_postupdate_delta-iv-vlds.pto reproduces it: the vlds keeps its non-post form even though a vecscope appears in the output, added by the later inference pass. Moving the pass after the inference fixes it. The inference splices whole ops into the scope, so scf.for bodies are not restructured and the "directly in loop body" check is unaffected. Adds soft_postupdate_inferred-vecscope.pto, whose input has no hand-written vecscope; it fails on the previous ordering.
vsstb's repeat_stride counts 32-byte blocks while pto.addptr takes an element offset; the pass bridged them with a constant 32, which is only correct when sizeof(T) == 1. Derive the scale from the pointer element type via an explicit per-op stride unit. Rescaling applies only to delta(base), so Element-class ops (E == W) are unchanged and a non-constant repeat_stride increment is now supported.
materializeAtLoopEntry cloned a def-chain in front of the loop without checking the ops are pure, and always returned getResult(0), which is the wrong value for a multi-result op. hoistBefore already handled both correctly. Nothing checked that the computed stride fits the i16 repeat_stride operand. arith.constant truncates silently, so a stride of 100000 became -31072 and the store walked wrong addresses with no diagnostic. constantsFitType now checks this before any IR is created, so the candidate is rejected instead. Design doc 4.2.5 gains the range check, and check 3 now records that the weight != 1 path requires the whole total_delta to be constant -- stricter than necessary, but relaxing it needs structural divisibility on StrideExpr.
209aa49 to
adad5b5
Compare
Implement the accumulator analysis path for VPTOSoftPostUpdate (design doc step 2), and refactor the overall stride computation architecture.
Changes
Tests
10 new lit tests covering: non-constant stride, base via addptr, both iter_args, cross-iter_arg stride, vsstb with iter_arg repeat_stride, multi-level yield chain, look-through offset, mixed accumulator+delta, unchanged iter_arg, non-additive yield (negative).