Skip to content

fix(vpto): hoist loop-invariant guarded address chains out of scf.if - #1347

Merged
Zhendong404 merged 1 commit into
hw-native-sys:mainfrom
jimmychou0:zjm/issue-fix-1328
Aug 27, 2026
Merged

fix(vpto): hoist loop-invariant guarded address chains out of scf.if#1347
Zhendong404 merged 1 commit into
hw-native-sys:mainfrom
jimmychou0:zjm/issue-fix-1328

Conversation

@jimmychou0

@jimmychou0 jimmychou0 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Closes #1328

Root cause

The VPTO emission pipeline already runs the generic MLIR LoopInvariantCodeMotion pass, but MLIR LICM only enqueues the top-level operations of the scf.for body. For the guarded access pattern in the issue:

scf.for %iv = ... {
  %in_bounds = ...              // depends on the IV
  scf.if %in_bounds {
    %base = ...                 // loop-invariant base address chain
    %dynamic = ... %iv ...      // IV-dependent offset
    pto.store ...
  }
}

the invariant base-address chain inside the guard is never exposed to LICM, and the surrounding scf.if cannot be hoisted either (IV-dependent condition plus side-effecting memory ops). This is an optimization gap of the generic LICM across guarded regions — not a missing pass or a missing Pure attribute.

Change

Add a narrow vpto-guarded-licm pass (func::FuncOp scope, A5 VPTO only), wired between vpto-soft-postupdate and the generic LICM:

  • Processes scf.for loops innermost-first so invariants can climb out through nested guards and nested loops.
  • Collects pure, speculatable scalar/address ops (integer, index, pto.ptr results) inside scf.if regions and builds their dependency closure; only ops whose operands are defined outside the loop or by already-hoisted ops are moved.
  • Moves the hoisted set in topological order directly before the loop (no cloning).
  • Conservatively leaves in place: IV/iter_args-dependent expressions, side-effecting ops (load/store/call), div/rem with unknown divisors, vector/mask/tile computations, and ops inside nested scf.for regions.

For the issue's repro, the w64/slot/range_base-style base chain moves in front of the loop while the IV-dependent dynamic offset stays inside the guard.

Tests

  • test/lit/vpto/vpto_guarded_licm.pto: focused pass tests — basic, nested scf.if, nested loops (innermost-first climbing), IV-dependent negative, non-speculatable divsi negative.
  • test/lit/vpto/vpto_guarded_licm_pipeline.pto: end-to-end pto.section.simt -> outlined helper -> VPTO pipeline regression checking the base chain is hoisted and the dynamic offset stays guarded.

Validation

  • Built with LLVM 19.1.7 (-Werror) on the validation host; full test/lit suite: 1830 passed, 0 failed (1 unsupported environment skip).
  • New tests pass under llvm-lit.

Review fixes

  • Allow-list for hoistable ops: candidate selection no longer relies on the
    generic isPure() alone. A deterministic explicit allow-list (arith integer /
    cast / compare / select / constant ops plus pto.addptr / pto.castptr /
    pto.ptrtoint / pto.inttoptr) now gates hoisting, so referentially opaque
    "pure" ops such as pto.get_clock32/64, pto.get_vms4_sr and
    pto.vote_/pto.shuffle_/pto.redux_* stay inside the guard.
  • Complete div/rem family excluded: arith.floordivsi / ceildivsi /
    ceildivui are now explicitly excluded alongside divsi/divui/divf/remsi/remui/
    remf, so zero-divisor or INT_MIN / -1 UB cannot be hoisted in front of the
    guard.
  • Code style: all control statements use braces (G.FMT.11-CPP); changed-code
    compliance check reports 0 errors / 0 warnings.
  • New negative regression tests for the SIMT referentially-opaque ops and
    arith.floordivsi.

@jimmychou0
jimmychou0 force-pushed the zjm/issue-fix-1328 branch 3 times, most recently from 4c2cc10 to 32a2e3b Compare August 25, 2026 12:26
@jimmychou0
jimmychou0 marked this pull request as ready for review August 26, 2026 00:39
Generic MLIR LICM only inspects the top-level operations of an scf.for body,
so a loop-invariant base-address chain computed inside an scf.if guard whose
condition depends on the induction variable is never hoisted, and the whole
guard cannot be hoisted either (IV-dependent condition plus side-effecting
memory ops).

Add the vpto-guarded-licm pass (A5 VPTO only) which extracts pure,
speculatable scalar/address subexpressions of guarded regions and moves them
in dependency (topological) order in front of the enclosing scf.for, leaving
IV-dependent arithmetic, side-effecting operations, div/rem and
vector/tile computations in place.  Loops are processed innermost-first so
invariant chains climb out through nested guards and nested loops.  The pass
is wired between vpto-soft-postupdate and the generic LICM.

Tests: focused vpto-guarded-licm cases (basic, nested if, nested loops,
IV-dependent and non-speculatable negatives) plus an end-to-end
pto.section.simt -> outlined helper -> VPTO pipeline regression.
@Zhendong404
Zhendong404 merged commit 614298e into hw-native-sys:main Aug 27, 2026
12 checks passed
@reedhecre

Copy link
Copy Markdown

A3 板测完成(有跳过)

  • 触发方式:merged
  • 源码提交:614298ed2b6f
  • 结果汇总:OK 323 / FAIL 0 / SKIP 29
  • 日志:/home/zhongxuan/ptoas-board-monitor/runtime/logs/20260826_194106_merged_pr1347.log
  • 结果 TSV:/home/zhongxuan/ptoas-board-monitor/runtime/logs/20260826_194106_merged_pr1347.tsv

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] [Performance] VPTO 未将受条件保护的 SIMT 循环不变量地址计算外提

3 participants