Hi!
On Tue, Dec 23, 2025 at 01:02:08AM +0530, Kishan Parmar wrote:
> On 17/12/25 2:18 pm, Andrew Pinski wrote:
> > Note you have the same issue with the following C example before this
> > patch (just after you get with the original form too):
> > ```
> > unsigned foo0 (unsigned x, unsigned y)
> > {
> > unsigned x1 = x >> 31;
> > unsigned yy = y & ~1;
> > return x1 | yy;
> > }
> > ```
> >
> > That was something I was trying to point out.
> > And why having the check for INTEGER_CST is just exchanging one issue
> > for another. It does not solve the new issue here.
> >
> > Wait, I think we are losing/not using the SUBREG_PROMOTED_UNSIGNED_P
> > fact here which is causing the issues really.
> > We lost it when we did the original 2->7 combine (for foo0 above):
> > ```
> > Trying 2 -> 7:
> > 2: r121:DI=r128:DI
> > REG_DEAD r128:DI
> > REG_EQUIV [ap:DI+0x30]
> > 7: r124:SI=r121:DI#4 0>>0x1f
> > REG_DEAD r121:DI
> > Failed to match this instruction:
> > (set (subreg:DI (reg:SI 124 [ x1_2 ]) 0)
> > (zero_extract:DI (reg:DI 128 [ x+-4 ])
> > (const_int 32 [0x20])
> > (const_int 1 [0x1])))
> > Successfully matched this instruction:
> > (set (subreg:DI (reg:SI 124 [ x1_2 ]) 0)
> > (and:DI (lshiftrt:DI (reg:DI 128 [ x+-4 ])
> > (const_int 31 [0x1f]))
> > (const_int 4294967295 [0xffffffff])))
> > ```
> >
> > insn 7 was:
> > ```
> > (insn 7 4 9 2 (set (reg:SI 124 [ x1_2 ])
> > (lshiftrt:SI (subreg/s/v:SI (reg/v:DI 121 [ x+-4 ]) 4)
> > (const_int 31 [0x1f]))) "/app/example.cpp":30:12 292 {lshrsi3}
> > (expr_list:REG_DEAD (reg/v:DI 121 [ x+-4 ])
> > (nil)))
> > ```
> > insn 2 was just move.
> > Note /s/v on the subreg there.
> > If we didn't lose the SUBREG_PROMOTED_UNSIGNED_P, then things would just
> > work.
> > So you need to figure out how to get
> >
> > So again the INTEGER_CST is just working around an issue down the line
> > and not really a good idea; in the case of ppc, the losing of the
> > SUBREG_PROMOTED_UNSIGNED_P.
> >
> > Thanks,
> > Andrew Pinski
> Hello Andrew and Segher,
>
> I went through combine pass traces, and the loss of the
> SUBREG_PROMOTED_UNSIGNED_P flag is during simplification.
Documentation says this is printed as /u, which makes sense. But that
doesn't seem to happen? It also says it is printed as /v, which *does*
happen. The documentation needs fixing.
Either way, this stuff is meant to be an optimisation only (it is set
for subregs where the extension in the larger reg is already known!)
> The issue specifically happens in simplify_shift_const.
> It attempts to commute the shift with the subreg to "widen" the operation to
> the native mode (DI),
> but in doing so, it destroys the metadata.
>
> Input (SImode): (lshiftrt:SI (subreg/s/v:SI (reg:DI 128 [ x ]) 0) (const_int
> 31))
> Output (DImode Widened): (subreg:SI (lshiftrt:DI (reg:DI 128 [ x ])
> (const_int 31)) 0)
lshiftrt:SI leaves the top 32 bits of the resulting thing (seen as
DImode) completely undefined. As pretty much all operations on SImode
values do.
> Let’s say, somehow we manage to keep SUBREG_PROMOTED_UNSIGNED_P after
> simplify_shift_const(modify to preserve /s/v flags),
> and let us assume we have expression
> (set (reg:SI 124 [ x1_2 ])
> (subreg:SI/v/s (lshiftrt:DI (reg:DI 128 [ x ])
> (const_int 31 [0x1f])) 0))
That says that the SImode value of that shift is stored zero-extended in
the DImode register. Which is redundant information of course, that's
just how RTL works.
> But in later simplification, when gcc tries to simplify set.. It tries to
> create compund expression from set via make_compound_operation.
> it typically SUBREG_REG(x) (the inner lshiftrt:DI) and passes that to helper
> functions.
> Functions like gen_lowpart, force_to_mode, or gen_lowpart_or_truncate then
> re-wrap the result in a fresh subreg (truncation), losing the flags again.
>
> Furthermore, even if we preserve the flag, combine tends to generate a
> ZERO_EXTRACT pattern:
> (set (subreg/s/v:DI (reg:SI 124) 0)
> (zero_extract:DI (reg:DI 128) (const_int 32) (const_int 1)))
Only temporarily. For targets that do not support zero_extract (like
rs6000) all this is converted back to simpler, more useful, more
serviceable, more canonical representation.
> The above result we need to convert again back to
> (set (reg:SI 124 [ x1_2 ])
> (subreg:SI/s/v (zero_extract:DI (reg:DI 128 [ x ])
> (const_int 32 [0x20])
> (const_int 1 [0x1])) 0))
>
> Finally, change_zero_ext would need to detect this specific pattern and
> simplify it back to the optimal shift form:
> (set (reg:SI 124) (lshiftrt:SI (subreg/s/v:SI (reg:DI 128 [ x ]) 0)
> (const_int 31 [0x1f])))
>
> Preserving the SUBREG_PROMOTED_UNSIGNED_P property is difficult because it
> requires
> updating the resulting SUBREG after every invocation of gen_lowpart,
> force_to_mode, or gen_lowpart_or_truncate.
But it can be 100% reliably deduced from the lshifrt (by a constant
non-zero amount).
> So, My question here is should we go with preserve and restore the flag
> throughout the pipeline,
> or should we just avoid widening subregs with SUBREG_PROMOTED_UNSIGNED_P?
You should not depend on optional and redundant flags being set *ever*?
Segher