Repository navigation
Fuse bit-field shifts into one variable shift on ARM - #9575
Open
alexreinking wants to merge 2 commits into
Open
alexreinking wants to merge 2 commits into
alexreinking wants to merge 2 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## alexreinking/arm-repeat-shuffle #9575 +/- ##
==================================================================
Coverage ? 73.13%
==================================================================
Files ? 263
Lines ? 82586
Branches ? 20195
==================================================================
Hits ? 60397
Misses ? 15894
Partials ? 6295 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
alexreinking
commented
Oct 22, 2026
alexreinking
force-pushed
the
alexreinking/arm-field-shift
branch
2 times, most recently
from
October 23, 2026 00:07
4f79e9f to
976577c
Compare
alexreinking
changed the base branch from
main
to
alexreinking/arm-repeat-shuffle
October 23, 2026 00:07
alexreinking
force-pushed
the
alexreinking/arm-field-shift
branch
from
October 23, 2026 00:38
976577c to
6019735
Compare
alexreinking
force-pushed
the
alexreinking/arm-field-shift
branch
from
October 23, 2026 05:16
6019735 to
5055374
Compare
alexreinking
force-pushed
the
alexreinking/arm-field-shift
branch
from
October 23, 2026 14:43
5055374 to
7bd00fa
Compare
alexreinking
force-pushed
the
alexreinking/arm-field-shift
branch
from
October 24, 2026 15:43
7bd00fa to
0416644
Compare
alexreinking
force-pushed
the
alexreinking/arm-field-shift
branch
from
October 25, 2026 21:05
0416644 to
7171817
Compare
alexreinking
force-pushed
the
alexreinking/arm-field-shift
branch
from
October 26, 2026 19:58
7171817 to
c648a08
Compare
alexreinking
force-pushed
the
alexreinking/arm-field-shift
branch
from
October 26, 2026 23:05
c648a08 to
e75116e
Compare
Moving a bit field at a varying position y to a constant position c, ((x >> y) & m) << c (or with % 2^w in place of & m) is lowered as a variable shift right (ushl/sshl by -y), a shift left by the constant c, and a mask. Neon's ushl/sshl (vshl on arm32) shift right for negative amounts, so the two shifts can be one: (x << (c - y)) & (m << c) with c - y as a signed shift amount. This holds for both signedness of x whenever 0 <= y < bits and 0 <= c < bits: every bit of the field lands in the same place either way, and bits shifted off the top are dropped by the shifted mask. CodeGen_ARM now does this rewrite for vector shift_lefts when constant_integer_bounds proves y in range; a constant y is left alone since LLVM already combines constant shifts. It doesn't apply to SVE, where the [us]shl patterns are unavailable. This form comes from decoding bit-plane quantized weights (e.g. ggml's q5_0/q5_1, whose fifth bit of each 4-bit code is packed 8 to a byte): bit (i % 8) of a repeated qh byte moves to bit 4. On an Apple M-series core it removes a shl per 16 lanes from the q5_0 dot product loop, and speeds up a single-threaded q5_0 4096x4096 gemv by ~14%. The rewrite is not a target-independent simplifier rule because the fused form is worse on x86, where variable shifts with signed amounts expand to two shifts and a select. Adds correctness_arm_bit_field_shift, which compares the vectorized and scalar paths across all integer types, mask forms, field widths and destinations (plus shift amounts with unknown bounds, which must not be rewritten), and checks that the aarch64 assembly for the q5 pattern has a ushl and no shl. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add bitwise-and matching, construction, and folding support plus shift and bitwise operator overloads. Canonicalize power-of-two modulo before applying the fusion rule, and cover matcher operators and non-power-of-two divisors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
alexreinking
force-pushed
the
alexreinking/arm-field-shift
branch
from
November 2, 2026 23:50
e75116e to
97d9361
Compare
alexreinking
changed the base branch from
alexreinking/arm-repeat-shuffle
to
alexreinking/const-blob-placement
November 2, 2026 23:50
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Moving a bit field at a varying position
yto a constant positionc,currently lowers on ARM to a variable shift right (
ushlby-y), a shift left by the constantc, and a mask. Neon'sushl/sshl(vshlon arm32) shift right for negative amounts, so the two shifts can be one:This holds for either signedness of
xwhenever0 <= y < bitsand0 <= c < bits: every bit of the field lands in the same place, and bits shifted off the top are dropped by the shifted mask.CodeGen_ARMnow does this rewrite for vectorshift_lefts whenconstant_integer_boundsprovesyin range. A constantyis left alone because LLVM already combines constant shifts. SVE is excluded because the[us]shlpatterns aren't available there.It isn't a target-independent simplifier rule because the fused form is worse on x86, where variable shifts with signed amounts expand to two shifts and a select.
The pattern comes from decoding bit-plane quantized weights, e.g. ggml's q5_0/q5_1, where the fifth bit of each 4-bit code is packed 8 to a byte: bit
i % 8of a repeatedqhbyte moves to bit 4. Per 16 lanes, aarch64 before:and after:
Benchmarks
Apple M-series, q5 kernels written in Halide (from the ggml app on #9561), LLVM 22. Ratio = ggml time / Halide time (higher is better), median of 3 rounds, all with #9574 applied (since superseded by #9606, now on main):
The q5_0 dot product loop drops from 58 to 56 instructions.
Tests
Adds
correctness_arm_bit_field_shift:c, and shift amounts with known bounds and with unknown bounds (which must not be rewritten);arm-64-linuxand checks the assembly has aushland noshl. With the rewrite disabled, this check fails.Validation:
ctest -R "correctness_(arm_bit_field_shift|simd_op_check_arm|bitwise_ops|shift_by_unsigned_negated|intrinsics|arm_dot_product_by_element)$": passedctest -L correctness -R "simd|vector|shift|bit|mod|math|intrin|narrow|widen|mul|div|saturat|round|cast|arm" -E cross_compilation: 101 tests, all passed or skipped (GPU, HVX, PowerPC, RISC-V not built).correctness_cross_compilationfails locally only because Hexagon isn't enabled in this build.Checklist