Skip to content

rocjitsu: use SIMD words and set-bit iteration for register sets - #12584

Merged
kuhar merged 7 commits into
developfrom
users/bjacob/register-set-word-iteration
Oct 5, 2026
Merged

kuhar merged 7 commits into
developfrom
users/bjacob/register-set-word-iteration

Conversation

@bjacob

@bjacob bjacob commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Register-set iteration scans all 106 SGPR, 1024 VGPR, and 1024 accumulator positions even for sparse instruction def/use sets. Visit occupied lanes and set bits while retaining SIMD words for bulk set operations.

Technical Details

Make the implementation header-only as RegisterSetT<RegisterSetWordType>, selected by the enum values StandardUint64 and Avx2M256, with a WordOps specialization for each. RegisterSet aliases the AVX2 specialization when available and the scalar specialization otherwise. Both implementations can be instantiated in an AVX2 build, so the scalar fallback is compiled and tested routinely.

Use explicit word helpers for membership, masks, counting, equality, and set algebra. Share the partial-word prologue, full-word loop, and partial-word epilogue between range updates and queries. Preserve clipping, single-bit fast paths, register capacities, ordinary/special separation, and the existing RegisterSet API.

Iterate occupied lanes and set bits in ascending order, with consecutive-index batches for full lanes. Reload membership after callbacks to preserve forward additions and removals, keeping edit recovery on a cold path. Replace gnu::flatten with a small inline callback helper, a Clang call-site hint, and explicit inlining on the hot materialization/intersection callbacks. There is no dependency on private standard-library layouts or per-set dynamic allocation.

Issue Tracking

None.

Test Plan

Exercise both word implementations in AVX2 builds and the scalar fallback without AVX2. Run the affected analysis and probe tests, sanitizers, and compiler checks. Compare the latest PR with the pre-PR baseline using independently checked microbenchmarks and matching corpus translations; keep builds and tests separate from timing.

Test Result

  • Rebuilt and passed 438 register-set, liveness, probe, waitcheck state/target, and waitcheck stream tests.
  • All 25 typed register-set cases pass for both scalar and AVX2 implementations in AVX2 builds with GCC and Clang (50 cases per compiler). All 25 scalar cases also pass without AVX2 under GCC UBSan.
  • Temporary scalar-only and AVX2-only fault injections confirmed that the corresponding typed suites detect defects in each implementation.
  • Clang compiled the affected production translation units and the full liveness test translation unit with warnings treated as errors.
  • All PR files pass the repository-pinned clang-format 18.1.4 hook; the pushed commit's pre-commit CI check also passes.
  • All 80,520 fresh baseline/PR corpus translations produce dispatchable output with matching output hashes, byte counts, and diagnostic counts.

Performance

Without PR: 03d97e532a6. Latest PR: 5efecd1b74c.

Standalone iteration

Five randomized paired trials per case and implementation; 12 callbacks at three synthetic densities. GCC 15 and Clang 21, -O3 -DNDEBUG -march=x86-64-v3, no LTO, Ryzen 9 7950X3D pinned to CPU 4. Each sample traverses 4,096 sets for 20 sweeps. All 720 baseline/latest-PR checksums match independently derived expectations.

Compiler Geometric-mean speedup vs without PR (36 cases)
GCC 15 14.61×
Clang 21 8.10×

All cases have equal weight. These are callback-enumeration microbenchmarks, not whole-application or ConSan startup speedups. Each visited index remains observable through an opaque scalar compiler barrier. Projection callbacks also exercise updates; formatting includes allocation and string work.

The latest-PR benchmark applies the same explicit always_inline annotations as production to its materialization and intersection callbacks. Algorithms and inputs are unchanged; the without-PR version retains the original annotations.

B0-to-A0 translation

Fresh measurements of the same 20,130-object CI package corpus. GCC 15.2, -O2 -g -DNDEBUG -march=x86-64-v3, Ryzen 9 7950X3D, CPU 4. Two fresh processes per implementation, in baseline/PR/PR/baseline order. Builds and tests did not overlap timing, and the saved baseline binary was verified against its recorded SHA-256 hash.

Timer scope: translator construction, translate(), and destruction for already-loaded and parsed objects. File I/O, parsing, output hashing, and process startup are excluded. This is host translation time, not GPU execution or ConSan startup; no translation-result cache is used. The one CI-declared RCCL memory-limit case remains excluded.

Group Objects Without PR Latest PR Speedup
All 20,130 188.931 s 177.274 s 1.066×
AOTriton 18,990 56.084 s 49.410 s 1.135×
KPACK libraries 991 128.484 s 123.696 s 1.039×
Other packaged objects 149 4.362 s 4.167 s 1.047×

Values are means of summed per-object wall times. Two runs per implementation are not confidence intervals. Output byte counts, SHA-256 hashes, and diagnostic counts match for every input in both trials.

Submission Checklist

@therock-pr-bot

therock-pr-bot Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

❌ PR Check — Action Required

Check Status Details
📝 PR Description ❌ Fail Error: PR description must reference a JIRA ID, ISSUE ID, or a GitHub closing keyword.
Expected: include a JIRA ID / ISSUE ID line (separator : or -, or omitted; value may be a JIRA key, a number with/without #, or a link), OR a closing keyword + issue reference. Accepted examples:
• JIRA ID : TESTAUTO-6039
• JIRA ID - #330
• JIRA ID #330
• JIRA ID (on separate line)
ROCM-25757
• ISSUE ID : TESTUTO-3334
• ISSUE ID #3334
• ISSUE ID - TESTAUTO-3433
• ISSUE ID (on separate line)
AIRUNTIME-2352
• ISSUE ID : https://github.com/<org_name>/<repo_name>/issues/1234
• Closes #10
• Fixes octo-org/octo-repo#100
• Resolves: #123
• #123
• https://github.com/<org_name>/<repo_name>/issues/123
Current: no valid JIRA/ISSUE/closing-keyword reference found
⛔ Forbidden Files ✅ Pass —
🧪 Unit Test ✅ Pass —
🔎 pre-commit ✅ Pass —
🚫 Draft PR 🔜 To Be Enabled —
🚩 Feature Flag 🔜 To Be Enabled —
📊 Code Coverage 🔜 To Be Enabled —

⚠️ 1 policy check(s) failed. Please address the issues above before this PR can be Reviewed.

🚫 Please fix the failed policies

  • ❌ PR Description

The Not ready to Review label was added to this PR. Once all policies pass, the label is removed automatically.

📖 Need help? See the Policy FAQ for details on every check and how to fix failures.

🙋 Wish to Override Policy?

@therock-pr-bot

Copy link
Copy Markdown

🚫 Please fix the failed policies before requesting reviews.

The following policy checks failed:

  • ❌ PR Description

The Not ready to Review label has been added to this PR.
Once all policies pass, the label will be removed automatically.

@kuhar kuhar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one thought

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Oct 1, 2026
@bjacob
bjacob force-pushed the users/bjacob/register-set-word-iteration branch from 20669f7 to 8f3612b Compare October 1, 2026 15:20
@bjacob
bjacob requested a review from kuhar October 1, 2026 15:30
@bjacob
bjacob force-pushed the users/bjacob/register-set-word-iteration branch from 8f3612b to 37ec9ee Compare October 2, 2026 19:04
@bjacob bjacob changed the title rocjitsu: iterate register sets by nonempty words and set bits rocjitsu: use SIMD words and set-bit iteration for register sets Oct 2, 2026
@bjacob

bjacob commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor Author

@kuhar , thanks for another good comment: the B0-A0 translation workloads showed a minor regression , and pinning that down was very informative: in some cases, the old std::bitset code allowed the compiler to generate AVX2 instructions, and we had lost that. This highlighted that my PR was only halfway done. It needed to continue fully ditching std::bitset in favor of raw words, and then making the words be 256-bit on AVX2, and using a few explicit AVX2 intrinsics. Much more iteration ensued to make sure that the resulting code is neat while maximizing performance, and now we have this (Zen4 CPU):

GCC 15

Density Callback Baseline (ms) This PR (ms) Speedup
Geometric mean 9.71×
sparse materialize 61.658 2.281 27.03×
sparse scalar_projection 73.503 2.076 35.41×
sparse intersection_first 69.458 1.511 45.96×
sparse intersection_last 71.163 5.389 13.20×
sparse intersection_miss 72.036 5.394 13.35×
sparse track_sgpr_pairs 72.291 0.679 106.45×
sparse vector_projection 75.057 5.654 13.27×
sparse max_vgpr 90.825 0.631 143.83×
sparse max_sgpr 74.281 0.615 120.77×
sparse has_sgpr 74.093 0.592 125.24×
sparse spill_cache_misses 67.191 1.659 40.50×
sparse format_registers 70.692 5.935 11.91×
denser materialize 284.040 21.183 13.41×
denser scalar_projection 73.978 6.222 11.89×
denser intersection_first 278.767 6.135 45.44×
denser intersection_last 458.271 242.853 1.89×
denser intersection_miss 455.849 241.651 1.89×
denser track_sgpr_pairs 72.368 4.662 15.52×
denser vector_projection 256.957 222.426 1.16×
denser max_vgpr 87.441 4.969 17.60×
denser max_sgpr 74.478 4.739 15.72×
denser has_sgpr 74.403 4.699 15.83×
denser spill_cache_misses 116.142 62.534 1.86×
denser format_registers 286.608 215.571 1.33×
full materialize 1380.966 87.093 15.86×
full scalar_projection 76.753 21.389 3.59×
full intersection_first 1314.336 21.168 62.09×
full intersection_last 2403.014 1465.957 1.64×
full intersection_miss 2377.847 1443.430 1.65×
full track_sgpr_pairs 71.306 16.194 4.40×
full vector_projection 1169.207 1217.227 0.96×
full max_vgpr 71.775 14.002 5.13×
full max_sgpr 70.830 13.857 5.11×
full has_sgpr 71.066 14.182 5.01×
full spill_cache_misses 365.358 362.210 1.01×
full format_registers 1417.925 1258.698 1.13×

Clang 21

Density Callback Baseline (ms) This PR (ms) Speedup
Geometric mean 6.56×
sparse materialize 77.660 2.162 35.92×
sparse scalar_projection 82.593 2.079 39.73×
sparse intersection_first 76.307 1.386 55.06×
sparse intersection_last 77.837 2.686 28.98×
sparse intersection_miss 78.144 2.705 28.89×
sparse track_sgpr_pairs 72.960 0.730 99.93×
sparse vector_projection 78.142 3.438 22.73×
sparse max_vgpr 80.917 0.688 117.64×
sparse max_sgpr 76.973 0.674 114.18×
sparse has_sgpr 77.286 0.653 118.36×
sparse spill_cache_misses 79.219 1.896 41.79×
sparse format_registers 83.411 7.296 11.43×
denser materialize 84.338 19.965 4.22×
denser scalar_projection 81.343 9.113 8.93×
denser intersection_first 76.535 14.911 5.13×
denser intersection_last 144.824 87.588 1.65×
denser intersection_miss 137.387 88.201 1.56×
denser track_sgpr_pairs 73.149 7.963 9.19×
denser vector_projection 151.079 84.544 1.79×
denser max_vgpr 79.543 7.851 10.13×
denser max_sgpr 76.276 7.897 9.66×
denser has_sgpr 76.257 7.775 9.81×
denser spill_cache_misses 118.230 56.991 2.07×
denser format_registers 408.254 186.972 2.18×
full materialize 91.639 86.194 1.06×
full scalar_projection 72.672 40.131 1.81×
full intersection_first 71.027 52.971 1.34×
full intersection_last 472.674 510.898 0.93×
full intersection_miss 403.897 496.510 0.81×
full track_sgpr_pairs 71.946 35.808 2.01×
full vector_projection 469.180 463.347 1.01×
full max_vgpr 67.979 33.844 2.01×
full max_sgpr 68.186 33.838 2.02×
full has_sgpr 68.422 33.504 2.04×
full spill_cache_misses 320.348 320.934 1.00×
full format_registers 2067.817 1056.902 1.96×

@kuhar kuhar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one note to consider

bjacob added 6 commits October 5, 2026 09:45
RegisterSet iteration scanned every possible register position, even for
sparse instruction def/use sets. Splitting std::bitset into 64-bit chunks
improved iteration but also lost GCC -O2 AVX2 vectorization in bulk set
operations used by B0-to-A0 translation.

Store ordinary register classes in raw 256-bit SIMD words on AVX2 hosts,
with a uint64_t fallback elsewhere. Implement masks, membership, counting,
equality, and set algebra through word helpers without depending on
standard-library representation or scalar byte order.

Enumerate occupied SIMD lanes and set bits in ascending register order.
Use consecutive-index batches for full lanes. Reload membership after
callbacks and recover strictly beyond the emitted index when it changes,
preserving forward additions and removals. Keep recovery on a cold path
and unroll the full-lane loop for both GCC and Clang. Keep register class
constant and inline available callback callees to improve alias analysis.

Share range traversal between updates and queries, with partial-word
prologue/epilogue and a full-word loop. Preserve clipping, single-bit fast
paths, capacities, special-register separation, and the public API.

Add and extend ten register-set regression tests covering bit masks,
range boundaries, every byte pattern, partial class tails, set algebra,
and callback edits within and across full lanes against independent
bitset models. Keep benchmark artifacts outside the repository.

Validation: full GCC release build; 542 focused tests and 107 analysis
suite tests pass. The ten register-set tests also pass with GCC/Clang
AVX2 and scalar builds, GCC AVX2 without BMI, and GCC ASan/UBSan.
Five-trial standalone GCC/Clang benchmarks have 1,080 independently
validated checksums. Across 36 equally weighted cases, the final iterator
is 2.00x faster than the published PR on GCC and 1.63x on Clang (9.71x and
6.56x versus pre-PR). These are not whole-application startup estimates.

Two runs per version over 20,130 CI corpus objects produced identical
translation outputs, sizes, and diagnostics (120,780 translations).
Mean B0-to-A0 translation time is 199.16s, versus 202.71s for the
published PR and 205.06s before the PR, with matched GCC -O2 flags.
@bjacob
bjacob force-pushed the users/bjacob/register-set-word-iteration branch from 5efecd1 to 6bc7120 Compare October 5, 2026 13:46
bjacob added a commit that referenced this pull request Oct 5, 2026
Use the final PR #12584 implementation (6bc7120): header-only
RegisterSetT with explicit StandardUint64 and Avx2M256 word selections,
shared range operations, and mutation-aware set-bit iteration. Keep the
public RegisterSet alias and all existing capacity and special-class rules.

Compile and test both word implementations in AVX2 builds through the
typed register-set suite. Use targeted callback inlining instead of
recursive flattening, and mark WordArrayOps final as requested in review.

Preserve the sanitizers branch's additional analysis tests and consumers.
bjacob added a commit that referenced this pull request Oct 5, 2026
Reconcile the refreshed PR #12584 register-set implementation and sanitizer
resource accounting with develop's compute-queue and VM access changes.
Regenerate ISA sources using the shared packed-VGPR resolver, retain the
allocating realpath ABI and OSS7 support, and integrate legacy extended
SDMA counts.

Update tests for explicit register allocations and shader-array geometry,
CDNA4 Wave64 execution, and independently backed scratch pages.

Validation: full GCC build and CTest run (43,264 registered cases); fix all
three failures and pass 471 affected tests on the rebuilt executable.
Also pass 2,286 Python tests, GCC/Clang register-set typed tests, scalar
UBSan tests, and repository-pinned pre-commit checks on the resolutions.
@kuhar
kuhar merged commit 2b72c13 into develop Oct 5, 2026
58 of 59 checks passed
@kuhar
kuhar deleted the users/bjacob/register-set-word-iteration branch October 5, 2026 17:44
@systems-assistant

Copy link
Copy Markdown
Contributor

TheRock Submodule Bump Activity

Newest first

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants