Skip to content

feat(pto): auto-promote persistent fragment loops to full unroll - #1341

Open
jimmychou0 wants to merge 3 commits into
hw-native-sys:mainfrom
jimmychou0:feat-persistent-unroll-promotion
Open

feat(pto): auto-promote persistent fragment loops to full unroll#1341
jimmychou0 wants to merge 3 commits into
hw-native-sys:mainfrom
jimmychou0:feat-persistent-unroll-promotion

Conversation

@jimmychou0

@jimmychou0 jimmychou0 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Stacked on PR #1339:本分支基于 feat-unroll-enable-hint(#1339 的最新单提交 5acad2f4c,已含其全部 review 修复,包括 Pass B 更名 pto-convert-scf-to-cf-with-loop-hints 与完整 SCF→CF 转换),本 PR 自身只有 1 个提交。#1339 合入 main 后,本 PR 的 diff 会自动收窄为 promotion 部分。

概述

新增 pto-promote-persistent-fragment-loops pass:自动识别访问 persistent SIMT fragment buffer 的循环并提升为强制 full unroll,落实 persistent fragment 方案unroll-enable 与 persistent promotion 设计 中的 promotion 部分。

解决的问题:persistent fragment materialization 要求访问 persistent buffer 的循环被完全展开(否则访问无法归一到稳定的 resident slot)。此前该前置条件依赖调用方手写 {pto.unroll = "full"}——漏标时 materialization 在缺少静态访问集合的情况下失败,且没有清晰诊断。本 pass 把"必须手写"变成"自动识别 + 自动提升 + fail-fast"。

实现

识别(discovery):

  • 以显式 llvm.alloca {pto.persistent} 为入口(不做结构推断),沿 use graph(getelementptr → load/store 及其他 user)收集每个相关 op 的所有外层 scf.for:section 内直接包裹 access 的循环、包裹整个 pto.section.simt 的 kernel 级循环、多层嵌套的所有层级;
  • pto.persistent 属性名从 SIMTPersistentFragmentAnalysis.cpp 的私有常量提升为 PTO.h 共享常量。

提升(promotion):

原始状态 结果
无 hint pto.unroll = "full"
pto.unroll = "enable" 覆盖为 "full"(刻意:保持 enable 会把 loop 留到 metadata 阶段,materialization 失去静态访问集合)
pto.unroll = "full" 保持不变
pto.unroll_factor = N 硬错误

命中的循环附加内部 marker pto.persistent_unroll:marker 让 pto-unroll-loops每一条"丢 hint + remark"的兜底都升级为 persistent 专属硬错误——动态 trip、空 body、非 index 归纳变量、展开失败(这些在 promotion 阶段无法预先检查);marker 随循环展开一同消失,不残留 IR。

Fail-fast(硬错误,绝不静默降级——persistent alloca 不是可安全回退的普通内存):

  • persistent 循环上的 unroll_factor;
  • scf.while 内的 persistent access(该结构无法承载 full-unroll hint);
  • 静态 trip 超过 max-persistent-unroll-trip-count(pass option,默认 128,报错含函数名、trip 值与每个 persistent allocation 的位置)。

诊断在整个函数内收集完毕后统一失败,保证并行 pass 调度下发出的诊断集合确定。

管线位置:prepareVPTOForEmission 内、pto-unroll-loops 正前方(promotion → native unroll → SCCP/canonicalize/CSE → persistent fragment analysis → materialization)。

职责边界:只做识别与标注;slot 分配、keep/resume 生成、SIMT outline、LLVM metadata lowering 仍归各自 pass。

测试与验证

  • lit:promote_persistent_loops.pto(无 hint/enable/full 三种提升、无关循环不动、嵌套全层级、kernel 级循环、iter_args 穿线、promotion+unroll 端到端无残留 attr、被覆盖的 enable 不产生任何 unroll metadata);promote_persistent_loops_invalid.pto(factor / scf.while / guardrail 报错);promote_persistent_dynamic_unroll.pto(三种 unroll 侧硬错误,每个用 -split-input-file 独立 module 以保证诊断可观测)。
  • 144:全量 lit 通过,仅 3 个 element/predicate/vsts offset-index 用例失败——不含本改动时同样失败,属于 main Enable VPTO soft post-update by default #1330(Enable VPTO soft post-update by default)与该验证树基线不一致,与本 PR 无关;全部既有 persistent fragment 测试通过;CANN 模拟器 dsl-st 全套无 FAIL;合规检查 errors=0。

提交列表

  1. bf705b632 feat(pto): auto-promote persistent fragment loops to full unroll

@jimmychou0
jimmychou0 force-pushed the feat-persistent-unroll-promotion branch 3 times, most recently from 8fa28f0 to bf705b6 Compare August 26, 2026 00:43
Persistent fragment materialization requires every loop touching a
persistent buffer to be fully unrolled so each access resolves to a
stable resident slot.  Until now this precondition relied on authors
manually writing {pto.unroll = "full"} on those loops; a missing
annotation made materialization fail without a clear cause.  The new
pto-promote-persistent-fragment-loops pass (wired into
prepareVPTOForEmission immediately before pto-unroll-loops) automates
the discovery and marking, per the persistent-fragment plan:

- Discovery: every llvm.alloca carrying {pto.persistent} is an explicit
  entry point (no structural re-inference).  The pass walks the alloca's
  use graph and collects every enclosing scf.for of every related op -
  loops directly wrapping an access inside a SIMT section, kernel-level
  loops wrapping whole pto.section.simt regions, and every nesting
  layer.
- Promotion: no-hint and "enable" loops are rewritten to "full"
  (overriding enable is deliberate: keeping enable would leave the loop
  to the metadata stage and materialization would lose the static access
  set it depends on); an existing "full" is kept.  Every promoted loop
  is additionally marked {pto.persistent_unroll}.
- Fail-fast (hard errors, never a silent fallback): a fixed
  pto.unroll_factor on a persistent loop; a persistent access nested
  under scf.while; a statically known trip count above the new
  max-persistent-unroll-trip-count option (default 128, error names the
  function, the trip count and every persistent allocation).  Diagnostics
  are collected across the whole function before failing once, so the
  emitted set stays deterministic under the parallel pass manager.
- The marker makes pto-unroll-loops turn each of its drop-with-remark
  fallbacks into a persistent-specific hard error: a dynamic trip count,
  an empty body, a non-index induction variable, or a failed unroll.
  Silently keeping a persistent loop would break materialization
  downstream.  The marker disappears together with the unrolled loop.
- The {pto.persistent} attribute name is promoted into PTO.h and shared
  with SIMTPersistentFragmentAnalysis instead of being file-private.

Tests: promote_persistent_loops.pto covers no-hint/enable/full
promotion, unrelated loops left untouched, nested loops, kernel-level
loops wrapping sections, iter_args threading, a promotion + unroll end to
end run leaving no attributes behind, and (via a third RUN line) that an
overridden enable hint produces no llvm.loop.unroll metadata;
promote_persistent_loops_invalid.pto covers the fixed-factor, scf.while
and guardrail errors; promote_persistent_dynamic_unroll.pto covers the
three unroll-side hard errors, one module per -split-input-file section
so every diagnostic is observable regardless of scheduling.

Docs: the persistent SIMT fragment plan gains an "automatic promotion"
section (discovery rules, enable-override rationale, fail-fast contract,
and the pass's deliberately narrow responsibilities); the loop unroll
hint design doc records it as revision v4.

Validation (144): full lit suite green except the three
element/predicate/vsts offset-index tests that also fail without this
change (they belong to main's hw-native-sys#1330); CANN simulator dsl-st suite passes
with no failures; compliance check reports errors=0.
@jimmychou0
jimmychou0 force-pushed the feat-persistent-unroll-promotion branch from bf705b6 to b1e0cec Compare August 26, 2026 03:14
- discovery walks the pointer flow only (GEP chains; load/store and any
  other consumer are terminal accesses) instead of following every
  result's users, so loops that merely consume a loaded value are no
  longer promoted or hard-errored
- deduplicate the scf.while diagnostic per while op
- keep relatedOps in insertion order via SmallSetVector so diagnostics
  stay stable regardless of the related-op count
- skip statically zero-trip loops instead of promoting them into a
  misleading "no constant trip count" hard error
- PTO.h: move the persistent-attr constants below the unroll contract
  comment they interrupted
- docs: move the v5 revision entry to the top of the revision list and
  sync the pointer-flow wording
- tests: cover load-result consumers and zero-trip loops staying
  untouched, and single emission of the scf.while diagnostic
@jimmychou0
jimmychou0 force-pushed the feat-persistent-unroll-promotion branch from 85b6fc6 to cadfca2 Compare August 26, 2026 04:10
…d dead loops

Address codex review findings:

- validate the loop-unroll hint before promotion overwrites it, so a
  malformed pto.unroll value or attribute type on a persistent loop
  stays a hard error instead of being silently repaired to "full";
  the validation now lives in a shared LoopUnrollUtils.h used by both
  pto-unroll-loops and the promotion pass
- skip the whole enclosing-loop chain when any member is statically
  zero-trip: the access never executes, so promoting a dynamic outer
  loop would only produce a spurious "no constant trip count" error
- compute the static trip count in uint64_t (ceiling division without
  span+step-1) so extreme bounds like lb=INT64_MIN/ub=INT64_MAX cannot
  overflow signed arithmetic; counts beyond int64_t range report as
  non-constant, which keeps them on the fail-fast paths
- add an end-to-end lit test driving an unannotated persistent loop
  through the full ptoas pipeline into fragment materialization
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.

1 participant