On 8/14/2026 12:39 AM, Richard Biener wrote:
On Thu, 13 Aug 2026, Jeffrey Law wrote:
On 8/13/2026 1:36 AM, Artemiy Volkov wrote:
This patch adds a simplification rule for expressions of the form
((X >> C1) & C2) << C3, which extract some bits from X at position C1,
perform an "and" with a mask which is normally just 2^N - 1, then shift
the result left by C3. The transformation is limited to cases where X
is unsigned, has its precision equal to its width and where C1 and C3
are smaller than the precision of X (the last condition could probably
be just assumed but I wasn't sure so decided to play it safe).
When all of the above conditions hold, the expression is folded into
either: (a) (X >> (C1 - C3)) & (C2 << C3) when C1 >= C3, or (b)
(X << (C3 - C1)) & (C2 << C3) when C1 < C3. Additional care is required
to preserve the leading zeros formed by the X >> C1 operation in the
original expression; to handle this, we clear the leading bits of the
mask operand as a preliminary step.
The corner case where C1 is one less the precision of X is handled
elsewhere (and is folded to just (X >> C1) << C3 as long as the LSB of
C2 isn't 0.)
On aarch64, this results in:
lsr x0, x0, 16
and w0, w0, 130816
being emitted instead of:
lsr x1, x0, 32
lsr x0, x0, 24
ubfiz w1, w1, 16, 1
ubfiz w0, w0, 8, 8
orr w0, w1, w0
for the expression "((x >> 32) & 1) << 16) | (x >> 24) & 0xff) << 8)".
A couple of new testcases added, with some focus on the case where X is
shifted too far to the right as described above.
Survives bootstrap and regtest on aarch64-linux-gnu and x86_64-linux-gnu.
gcc/ChangeLog:
* match.pd: New rule to fold ((X >> C1) & C2) << C3.
gcc/testsuite/ChangeLog:
* gcc.dg/tree-ssa/match-bit-extract-shift.c: New test.
Interesting you should start looking at this. I just pointed Daniel at a
closely related problem.
In particular should we recognize the (x >> C) & 2^n-1 as a BIT_FIELD_REF.
Doing so for the single bit case would help pr32648 on targets that have
single bit extraction/manipulation like RISC-V.
I vaguely recall concerns that we didn't want to recognize or canonicalize to
BIT_FIELD_REF in the past, but the details escape me. Might as well get that
discussion started since if we target BIT_FIELD_REF it's going to mean this
patch would need further adjustment. I seem to think it was Richi or Andrea
that held this position, but far from 100% certain on that.
I think we want to avoid multiple ways to express the same thing as
that makes writing general simplifiers harder. ISTR we had patches
to lower BIT_FIELD_REFs to shifts and masking to be able to better
combine with other operations. This was for lowering of bitfield
component-refs to accessing representatives plus then extracting
the accessed bits.
ACK. I suspect if we were to steer towards shifts/masks that we'd get
meaningfully worse code, especially on risc-like targets. It's hard for
combine to put all those pieces back together.
The main pattern we played with was this which just recognizes a right
shift followed by a mask with 0x1. This probably could be handled in
gimple->RTL expansion reasonably.
+/* If we mask a right shift with 0x1, then we really just have a
single bit
+ bitfield extraction. We could probably any mask 2^n-1 here as a
general
+ bitfield extraction. */
+(simplify
+ (bit_and (convert? (rshift SSA_NAME@0 INTEGER_CST@1)) integer_onep
+ (if (INTEGRAL_TYPE_P (type)
+ && tree_int_cst_sgn (@1) >= 0
+ && wi::ltu_p (wi::to_wide (@1), TYPE_PRECISION (TREE_TYPE (@0)))
+ && (fold_before_rtl_expansion_p ()
+ || !flag_tree_vrp
+ || optimize_debug)
+ && !BYTES_BIG_ENDIAN)
+ (convert (BIT_FIELD_REF:boolean_type_node @0
+ { build_one_cst (bitsizetype); }
+ (convert:bitsizetype @1)))))
And, no I have no idea why the !flag_tree_vrp is in there... These never
got to the point of being cleaned up for submission.
There's a secondary class of transformations where we can use a
BIT_FIELD_REF to elide an outer AND with a constant and convert inner
shifts to BIT_FIELD_REFs. For example:
+/* In this case the masking is applied to the result of a logical
+ operation. We can use bitfield extractions and elide the final
+ mask to a single bit. */
+(for logical (bit_and bit_ior bit_xor)
+ (simplify
+ (bit_and (logical (convert? (rshift SSA_NAME@0 INTEGER_CST@1))
+ (convert? (rshift SSA_NAME@2 INTEGER_CST@3)))
+ integer_onep)
+ (if (INTEGRAL_TYPE_P (type)
+ && tree_int_cst_sgn (@1) >= 0
+ && tree_int_cst_sgn (@3) >= 0
+ && wi::ltu_p (wi::to_wide (@1), TYPE_PRECISION (TREE_TYPE (@0)))
+ && wi::ltu_p (wi::to_wide (@3), TYPE_PRECISION (TREE_TYPE (@2)))
+ && (fold_before_rtl_expansion_p ()
+ || !flag_tree_vrp
+ || optimize_debug)
+ && !BYTES_BIG_ENDIAN)
+ (logical (convert (BIT_FIELD_REF:boolean_type_node @0
+ { build_one_cst (bitsizetype); }
+ (convert:bitsizetype @1)))
+ (convert (BIT_FIELD_REF:boolean_type_node @2
+ { build_one_cst (bitsizetype); }
+ (convert:bitsizetype @3)))))))
That's meaningfully more difficult to capture during expansion and a
hell of a lot harder to capture during combine.
Jeff