[2/3] cse: Take single register constraints into account

Message ID 20260110161159.1073435-3-stefansf@linux.ibm.com
State New
Headers
Series Take single register constraints into account |

Commit Message

Stefan Schulze Frielinghaus Jan. 10, 2026, 4:11 p.m. UTC
  From: Stefan Schulze Frielinghaus <stefansf@gcc.gnu.org>

This fixes

t.c:17:3: error: 'asm' operand has impossible constraints or there are not enough registers
   17 |   __asm__ ("" : "=f" (a), "={fr2}" (e) : "{fr1}" (d));
      |   ^~~~~~~

on powerpc64le-unknown-linux-gnu for the attached test.  Prior cse1 we have

(insn 2 4 3 2 (set (reg/v:TF 120 [ c ])
        (reg:TF 33 1 [ c ])) "t.c":14:26 614 {*movtf_64bit_dm}
     (nil))
...
(insn 10 9 6 2 (parallel [
            (set (reg:DF 121)
                (asm_operands:DF ("") ("=f") 0 [
                        (reg:TF 124)
                    ]
                     [
                        (asm_input:TF ("{fr1}") t.c:17)
                    ]
                     [] t.c:17))
            (set (reg:DF 123 [ e ])
                (asm_operands:DF ("") ("={fr2}") 1 [
                        (reg:TF 124)
                    ]
                     [
                        (asm_input:TF ("{fr1}") t.c:17)
                    ]
                     [] t.c:17))
            (clobber (reg:SI 98 ca))
        ]) "t.c":17:3 -1
     (nil))
...
(insn 12 11 13 2 (set (reg:TF 33 1)
            (reg/v:TF 120 [ c ])) "t.c":18:12 614 {*movtf_64bit_dm}
     (nil))

During cse1, in insn 12 pseudo 120 is substituted with hard register 33
rendering the resulting insn trivial which is why the insn gets removed
afterwards.  Since hard register 33 has a use after insn 12, the
register is live before and after insn 10.  This leaves us with the
non-trivial problem, during LRA, to also assign hard register 33 to
pseudo 124 which is coming from the constraint of insn 10.  Since hard
registers are not tracked, except for liveness, this cannot be solved by
reloads which is why we end up with an error.

Therefore, treat single register constraints as clobbers of the
respective hard registers.  This includes constraints associated a
single register class as well as hard register constraints.

gcc/ChangeLog:

	* cse.cc (invalidate_from_sets_and_clobbers): Consider any hard
	register referred to by any single register constraint
	potentially being clobbered.

gcc/testsuite/ChangeLog:

	* gcc.target/powerpc/asm-hard-reg-2.c: New test.
---
 Bootstrapped and regtested on aarch64, powerpc64le, s390, x86_64.  Ok
 for mainline?

 gcc/cse.cc                                    | 42 +++++++++++++++++++
 .../gcc.target/powerpc/asm-hard-reg-2.c       |  9 ++++
 2 files changed, 51 insertions(+)
 create mode 100644 gcc/testsuite/gcc.target/powerpc/asm-hard-reg-2.c
  

Comments

Jeffrey Law Jan. 13, 2026, 3:20 p.m. UTC | #1
On 1/10/2026 9:11 AM, Stefan Schulze Frielinghaus wrote:
> From: Stefan Schulze Frielinghaus <stefansf@gcc.gnu.org>
>
> This fixes
>
> t.c:17:3: error: 'asm' operand has impossible constraints or there are not enough registers
>     17 |   __asm__ ("" : "=f" (a), "={fr2}" (e) : "{fr1}" (d));
>        |   ^~~~~~~
>
> on powerpc64le-unknown-linux-gnu for the attached test.  Prior cse1 we have
>
> (insn 2 4 3 2 (set (reg/v:TF 120 [ c ])
>          (reg:TF 33 1 [ c ])) "t.c":14:26 614 {*movtf_64bit_dm}
>       (nil))
> ...
> (insn 10 9 6 2 (parallel [
>              (set (reg:DF 121)
>                  (asm_operands:DF ("") ("=f") 0 [
>                          (reg:TF 124)
>                      ]
>                       [
>                          (asm_input:TF ("{fr1}") t.c:17)
>                      ]
>                       [] t.c:17))
>              (set (reg:DF 123 [ e ])
>                  (asm_operands:DF ("") ("={fr2}") 1 [
>                          (reg:TF 124)
>                      ]
>                       [
>                          (asm_input:TF ("{fr1}") t.c:17)
>                      ]
>                       [] t.c:17))
>              (clobber (reg:SI 98 ca))
>          ]) "t.c":17:3 -1
>       (nil))
> ...
> (insn 12 11 13 2 (set (reg:TF 33 1)
>              (reg/v:TF 120 [ c ])) "t.c":18:12 614 {*movtf_64bit_dm}
>       (nil))
>
> During cse1, in insn 12 pseudo 120 is substituted with hard register 33
> rendering the resulting insn trivial which is why the insn gets removed
> afterwards.  Since hard register 33 has a use after insn 12, the
> register is live before and after insn 10.  This leaves us with the
> non-trivial problem, during LRA, to also assign hard register 33 to
> pseudo 124 which is coming from the constraint of insn 10.  Since hard
> registers are not tracked, except for liveness, this cannot be solved by
> reloads which is why we end up with an error.
>
> Therefore, treat single register constraints as clobbers of the
> respective hard registers.  This includes constraints associated a
> single register class as well as hard register constraints.
>
> gcc/ChangeLog:
>
> 	* cse.cc (invalidate_from_sets_and_clobbers): Consider any hard
> 	register referred to by any single register constraint
> 	potentially being clobbered.
>
> gcc/testsuite/ChangeLog:
>
> 	* gcc.target/powerpc/asm-hard-reg-2.c: New test.
Can we really get at the register classes while in CSE?   That's a bit 
of a surprise.  My recollection is these kinds of cases a handled by the 
likely-spilled hooks.   Look in hash_rtx, where that code is lying around.

Jeff


jeff
  
Stefan Schulze Frielinghaus Jan. 13, 2026, 4:50 p.m. UTC | #2
On Tue, Jan 13, 2026 at 08:20:49AM -0700, Jeffrey Law wrote:
> 
> 
> On 1/10/2026 9:11 AM, Stefan Schulze Frielinghaus wrote:
> > From: Stefan Schulze Frielinghaus <stefansf@gcc.gnu.org>
> > 
> > This fixes
> > 
> > t.c:17:3: error: 'asm' operand has impossible constraints or there are not enough registers
> >     17 |   __asm__ ("" : "=f" (a), "={fr2}" (e) : "{fr1}" (d));
> >        |   ^~~~~~~
> > 
> > on powerpc64le-unknown-linux-gnu for the attached test.  Prior cse1 we have
> > 
> > (insn 2 4 3 2 (set (reg/v:TF 120 [ c ])
> >          (reg:TF 33 1 [ c ])) "t.c":14:26 614 {*movtf_64bit_dm}
> >       (nil))
> > ...
> > (insn 10 9 6 2 (parallel [
> >              (set (reg:DF 121)
> >                  (asm_operands:DF ("") ("=f") 0 [
> >                          (reg:TF 124)
> >                      ]
> >                       [
> >                          (asm_input:TF ("{fr1}") t.c:17)
> >                      ]
> >                       [] t.c:17))
> >              (set (reg:DF 123 [ e ])
> >                  (asm_operands:DF ("") ("={fr2}") 1 [
> >                          (reg:TF 124)
> >                      ]
> >                       [
> >                          (asm_input:TF ("{fr1}") t.c:17)
> >                      ]
> >                       [] t.c:17))
> >              (clobber (reg:SI 98 ca))
> >          ]) "t.c":17:3 -1
> >       (nil))
> > ...
> > (insn 12 11 13 2 (set (reg:TF 33 1)
> >              (reg/v:TF 120 [ c ])) "t.c":18:12 614 {*movtf_64bit_dm}
> >       (nil))
> > 
> > During cse1, in insn 12 pseudo 120 is substituted with hard register 33
> > rendering the resulting insn trivial which is why the insn gets removed
> > afterwards.  Since hard register 33 has a use after insn 12, the
> > register is live before and after insn 10.  This leaves us with the
> > non-trivial problem, during LRA, to also assign hard register 33 to
> > pseudo 124 which is coming from the constraint of insn 10.  Since hard
> > registers are not tracked, except for liveness, this cannot be solved by
> > reloads which is why we end up with an error.
> > 
> > Therefore, treat single register constraints as clobbers of the
> > respective hard registers.  This includes constraints associated a
> > single register class as well as hard register constraints.
> > 
> > gcc/ChangeLog:
> > 
> > 	* cse.cc (invalidate_from_sets_and_clobbers): Consider any hard
> > 	register referred to by any single register constraint
> > 	potentially being clobbered.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* gcc.target/powerpc/asm-hard-reg-2.c: New test.
> Can we really get at the register classes while in CSE?   That's a bit of a
> surprise.  My recollection is these kinds of cases a handled by the
> likely-spilled hooks.   Look in hash_rtx, where that code is lying around.

ira_class_singleton is setup in
setup_prohibited_and_exclude_class_mode_regs() which is called during
ira_init() which itself is called during backend_init_target().  So my
reading is that it should be save to access during CSE.  On the other
hand I'm wondering why this is not in ira_init_once().  Maybe I'm
overlooking something here.  Let me double check.

The likely-spilled hook seems to be precisely what I was looking for.
Thanks for the pointer!

Cheers,
Stefan
  
Stefan Schulze Frielinghaus Jan. 14, 2026, 3:13 p.m. UTC | #3
On Tue, Jan 13, 2026 at 05:50:47PM +0100, Stefan Schulze Frielinghaus wrote:
> On Tue, Jan 13, 2026 at 08:20:49AM -0700, Jeffrey Law wrote:
> > 
> > 
> > On 1/10/2026 9:11 AM, Stefan Schulze Frielinghaus wrote:
> > > From: Stefan Schulze Frielinghaus <stefansf@gcc.gnu.org>
> > > 
> > > This fixes
> > > 
> > > t.c:17:3: error: 'asm' operand has impossible constraints or there are not enough registers
> > >     17 |   __asm__ ("" : "=f" (a), "={fr2}" (e) : "{fr1}" (d));
> > >        |   ^~~~~~~
> > > 
> > > on powerpc64le-unknown-linux-gnu for the attached test.  Prior cse1 we have
> > > 
> > > (insn 2 4 3 2 (set (reg/v:TF 120 [ c ])
> > >          (reg:TF 33 1 [ c ])) "t.c":14:26 614 {*movtf_64bit_dm}
> > >       (nil))
> > > ...
> > > (insn 10 9 6 2 (parallel [
> > >              (set (reg:DF 121)
> > >                  (asm_operands:DF ("") ("=f") 0 [
> > >                          (reg:TF 124)
> > >                      ]
> > >                       [
> > >                          (asm_input:TF ("{fr1}") t.c:17)
> > >                      ]
> > >                       [] t.c:17))
> > >              (set (reg:DF 123 [ e ])
> > >                  (asm_operands:DF ("") ("={fr2}") 1 [
> > >                          (reg:TF 124)
> > >                      ]
> > >                       [
> > >                          (asm_input:TF ("{fr1}") t.c:17)
> > >                      ]
> > >                       [] t.c:17))
> > >              (clobber (reg:SI 98 ca))
> > >          ]) "t.c":17:3 -1
> > >       (nil))
> > > ...
> > > (insn 12 11 13 2 (set (reg:TF 33 1)
> > >              (reg/v:TF 120 [ c ])) "t.c":18:12 614 {*movtf_64bit_dm}
> > >       (nil))
> > > 
> > > During cse1, in insn 12 pseudo 120 is substituted with hard register 33
> > > rendering the resulting insn trivial which is why the insn gets removed
> > > afterwards.  Since hard register 33 has a use after insn 12, the
> > > register is live before and after insn 10.  This leaves us with the
> > > non-trivial problem, during LRA, to also assign hard register 33 to
> > > pseudo 124 which is coming from the constraint of insn 10.  Since hard
> > > registers are not tracked, except for liveness, this cannot be solved by
> > > reloads which is why we end up with an error.
> > > 
> > > Therefore, treat single register constraints as clobbers of the
> > > respective hard registers.  This includes constraints associated a
> > > single register class as well as hard register constraints.
> > > 
> > > gcc/ChangeLog:
> > > 
> > > 	* cse.cc (invalidate_from_sets_and_clobbers): Consider any hard
> > > 	register referred to by any single register constraint
> > > 	potentially being clobbered.
> > > 
> > > gcc/testsuite/ChangeLog:
> > > 
> > > 	* gcc.target/powerpc/asm-hard-reg-2.c: New test.
> > Can we really get at the register classes while in CSE?   That's a bit of a
> > surprise.  My recollection is these kinds of cases a handled by the
> > likely-spilled hooks.   Look in hash_rtx, where that code is lying around.
> 
> ira_class_singleton is setup in
> setup_prohibited_and_exclude_class_mode_regs() which is called during
> ira_init() which itself is called during backend_init_target().  So my
> reading is that it should be save to access during CSE.  On the other
> hand I'm wondering why this is not in ira_init_once().  Maybe I'm
> overlooking something here.  Let me double check.

Hmm even after giving it a second thought I still don't see a reason not
to access ira_class_singleton since it is set during initialize_rtl().
Maybe I'm overlooking something fundamental here?

Anyhow, I added

        else
          {
            enum reg_class cl
              = reg_class_for_constraint (lookup_constraint (p));
            if (cl == NO_REGS)
              continue;
            machine_mode mode = recog_data.operand_mode[nop];
            int regno = ira_class_singleton[cl][mode];
            if (regno >= 0)
              invalidate_reg (gen_rtx_REG (mode, regno));
          }

for the sake of completeness and symmetry of hard register constraints
and constraints associated a single register class.  Maybe the stage4
interrupt should be applied here, too, i.e., I don't want to risk it and
introduce hiccups.  Would the patch be ok if I remove the else clause
and the include statement of ira.h?

Cheers,
Stefan
  
Jeffrey Law Jan. 15, 2026, 4:01 p.m. UTC | #4
On 1/14/2026 8:13 AM, Stefan Schulze Frielinghaus wrote:
>>>> gcc/ChangeLog:
>>>>
>>>> 	* cse.cc (invalidate_from_sets_and_clobbers): Consider any hard
>>>> 	register referred to by any single register constraint
>>>> 	potentially being clobbered.
>>>>
>>>> gcc/testsuite/ChangeLog:
>>>>
>>>> 	* gcc.target/powerpc/asm-hard-reg-2.c: New test.
>>> Can we really get at the register classes while in CSE?   That's a bit of a
>>> surprise.  My recollection is these kinds of cases a handled by the
>>> likely-spilled hooks.   Look in hash_rtx, where that code is lying around.
>> ira_class_singleton is setup in
>> setup_prohibited_and_exclude_class_mode_regs() which is called during
>> ira_init() which itself is called during backend_init_target().  So my
>> reading is that it should be save to access during CSE.  On the other
>> hand I'm wondering why this is not in ira_init_once().  Maybe I'm
>> overlooking something here.  Let me double check.
> Hmm even after giving it a second thought I still don't see a reason not
> to access ira_class_singleton since it is set during initialize_rtl().
> Maybe I'm overlooking something fundamental here?
>
> Anyhow, I added
>
>          else
>            {
>              enum reg_class cl
>                = reg_class_for_constraint (lookup_constraint (p));
>              if (cl == NO_REGS)
>                continue;
>              machine_mode mode = recog_data.operand_mode[nop];
>              int regno = ira_class_singleton[cl][mode];
>              if (regno >= 0)
>                invalidate_reg (gen_rtx_REG (mode, regno));
>            }
>
> for the sake of completeness and symmetry of hard register constraints
> and constraints associated a single register class.  Maybe the stage4
> interrupt should be applied here, too, i.e., I don't want to risk it and
> introduce hiccups.  Would the patch be ok if I remove the else clause
> and the include statement of ira.h?
It was more "I didn't realize we could do that" and "we should tie this 
into the existing mechansisms to avoid CSE in some cases if possible".


The likely-spilled classes machinery can't be used as-is since the 
decision for your case is dependent upon context (ie, the insn itself 
and its constraints).  So perhaps what we want is your code to peek at 
the constraints, but bolted onto the code that's checking for the 
likely-spilled classes?

Jeff
  
Stefan Schulze Frielinghaus Jan. 16, 2026, 2:29 p.m. UTC | #5
On Thu, Jan 15, 2026 at 09:01:41AM -0700, Jeffrey Law wrote:
> 
> 
> On 1/14/2026 8:13 AM, Stefan Schulze Frielinghaus wrote:
> > > > > gcc/ChangeLog:
> > > > > 
> > > > > 	* cse.cc (invalidate_from_sets_and_clobbers): Consider any hard
> > > > > 	register referred to by any single register constraint
> > > > > 	potentially being clobbered.
> > > > > 
> > > > > gcc/testsuite/ChangeLog:
> > > > > 
> > > > > 	* gcc.target/powerpc/asm-hard-reg-2.c: New test.
> > > > Can we really get at the register classes while in CSE?   That's a bit of a
> > > > surprise.  My recollection is these kinds of cases a handled by the
> > > > likely-spilled hooks.   Look in hash_rtx, where that code is lying around.
> > > ira_class_singleton is setup in
> > > setup_prohibited_and_exclude_class_mode_regs() which is called during
> > > ira_init() which itself is called during backend_init_target().  So my
> > > reading is that it should be save to access during CSE.  On the other
> > > hand I'm wondering why this is not in ira_init_once().  Maybe I'm
> > > overlooking something here.  Let me double check.
> > Hmm even after giving it a second thought I still don't see a reason not
> > to access ira_class_singleton since it is set during initialize_rtl().
> > Maybe I'm overlooking something fundamental here?
> > 
> > Anyhow, I added
> > 
> >          else
> >            {
> >              enum reg_class cl
> >                = reg_class_for_constraint (lookup_constraint (p));
> >              if (cl == NO_REGS)
> >                continue;
> >              machine_mode mode = recog_data.operand_mode[nop];
> >              int regno = ira_class_singleton[cl][mode];
> >              if (regno >= 0)
> >                invalidate_reg (gen_rtx_REG (mode, regno));
> >            }
> > 
> > for the sake of completeness and symmetry of hard register constraints
> > and constraints associated a single register class.  Maybe the stage4
> > interrupt should be applied here, too, i.e., I don't want to risk it and
> > introduce hiccups.  Would the patch be ok if I remove the else clause
> > and the include statement of ira.h?
> It was more "I didn't realize we could do that" and "we should tie this into
> the existing mechansisms to avoid CSE in some cases if possible".

Understood.  Thanks for clarification.

> 
> 
> The likely-spilled classes machinery can't be used as-is since the decision
> for your case is dependent upon context (ie, the insn itself and its
> constraints).  So perhaps what we want is your code to peek at the
> constraints, but bolted onto the code that's checking for the likely-spilled
> classes?

My knowledge about cse is very thin and so far I don't see how to
implement this in hash_rtx() or tight this with likely-spilled classes.
For example, having

1: r100=%1
2: r101=...
3: r102=exp_a(r100)
4: r103=exp_b(r101)
5: r104=exp_c(r100)

Assume that insn 3 has no hard register constraint, but insn 4 does
which is referring to hard register %1.  Then I would still like to keep
track that r100 and %1 hold the same value, while analyzing insn 1,
which enables me to substitute r100 by %1 in insn 3.  For insn 4,
however, I would like to invalidate this relation, since operand r101 is
potentially loaded into %1 and would clobber the register.  Explicitly
clobbering/invalidating register %1 while analyzing insn 4 ensures that
r100 is not substituted by %1 in insn 5.

Thus, at first encounter I still want to hash a hard register and at
some later point once I encounter a hard register constraint referring to
the same hard register, I want to invalidate it.  To be conservative
here, I do this irrespective whether an alternative is likely, unlikely,
or whatsoever.  Basically I'm treating a hard register constraint as a
possible load.

In that regard hard register constraints may not be as restrictive as
single register classes, since for the latter even a substitution into
insn 3 wouldn't be done if I read the current implementation correctly.

Cheers,
Stefan
  
Jeffrey Law Jan. 22, 2026, 4:55 p.m. UTC | #6
On 1/16/2026 7:29 AM, Stefan Schulze Frielinghaus wrote:
>>
>> The likely-spilled classes machinery can't be used as-is since the decision
>> for your case is dependent upon context (ie, the insn itself and its
>> constraints).  So perhaps what we want is your code to peek at the
>> constraints, but bolted onto the code that's checking for the likely-spilled
>> classes?
> My knowledge about cse is very thin and so far I don't see how to
> implement this in hash_rtx() or tight this with likely-spilled classes.
> For example, having
>
> 1: r100=%1
> 2: r101=...
> 3: r102=exp_a(r100)
> 4: r103=exp_b(r101)
> 5: r104=exp_c(r100)
>
> Assume that insn 3 has no hard register constraint, but insn 4 does
> which is referring to hard register %1.  Then I would still like to keep
> track that r100 and %1 hold the same value, while analyzing insn 1,
> which enables me to substitute r100 by %1 in insn 3.  For insn 4,
> however, I would like to invalidate this relation, since operand r101 is
> potentially loaded into %1 and would clobber the register.  Explicitly
> clobbering/invalidating register %1 while analyzing insn 4 ensures that
> r100 is not substituted by %1 in insn 5.
>
> Thus, at first encounter I still want to hash a hard register and at
> some later point once I encounter a hard register constraint referring to
> the same hard register, I want to invalidate it.  To be conservative
> here, I do this irrespective whether an alternative is likely, unlikely,
> or whatsoever.  Basically I'm treating a hard register constraint as a
> possible load.
>
> In that regard hard register constraints may not be as restrictive as
> single register classes, since for the latter even a substitution into
> insn 3 wouldn't be done if I read the current implementation correctly.
The basic idea is that by setting "record" to false, the value won't be 
kept in the hash table and it won't be subject to CSE going forward.  
  For your scenario I think we still have a problem; but I don't see a 
great solution.  Essentially at 1, you don't know if %1 is safe to have 
its lifetime extended.   That's the key difference -- more context is 
needed for the single register constraint issues.

ISTM that you have to invalidate the relevant hash table entries at 4.  
Not sure the best way to do that, most of CSE has fallen out of my brain 
through the years.

jeff


>
> Cheers,
> Stefan
  
Stefan Schulze Frielinghaus Jan. 23, 2026, 2:49 p.m. UTC | #7
On Thu, Jan 22, 2026 at 09:55:42AM -0700, Jeffrey Law wrote:
> 
> 
> On 1/16/2026 7:29 AM, Stefan Schulze Frielinghaus wrote:
> > > 
> > > The likely-spilled classes machinery can't be used as-is since the decision
> > > for your case is dependent upon context (ie, the insn itself and its
> > > constraints).  So perhaps what we want is your code to peek at the
> > > constraints, but bolted onto the code that's checking for the likely-spilled
> > > classes?
> > My knowledge about cse is very thin and so far I don't see how to
> > implement this in hash_rtx() or tight this with likely-spilled classes.
> > For example, having
> > 
> > 1: r100=%1
> > 2: r101=...
> > 3: r102=exp_a(r100)
> > 4: r103=exp_b(r101)
> > 5: r104=exp_c(r100)
> > 
> > Assume that insn 3 has no hard register constraint, but insn 4 does
> > which is referring to hard register %1.  Then I would still like to keep
> > track that r100 and %1 hold the same value, while analyzing insn 1,
> > which enables me to substitute r100 by %1 in insn 3.  For insn 4,
> > however, I would like to invalidate this relation, since operand r101 is
> > potentially loaded into %1 and would clobber the register.  Explicitly
> > clobbering/invalidating register %1 while analyzing insn 4 ensures that
> > r100 is not substituted by %1 in insn 5.
> > 
> > Thus, at first encounter I still want to hash a hard register and at
> > some later point once I encounter a hard register constraint referring to
> > the same hard register, I want to invalidate it.  To be conservative
> > here, I do this irrespective whether an alternative is likely, unlikely,
> > or whatsoever.  Basically I'm treating a hard register constraint as a
> > possible load.
> > 
> > In that regard hard register constraints may not be as restrictive as
> > single register classes, since for the latter even a substitution into
> > insn 3 wouldn't be done if I read the current implementation correctly.
> The basic idea is that by setting "record" to false, the value won't be kept
> in the hash table and it won't be subject to CSE going forward.   For your
> scenario I think we still have a problem; but I don't see a great solution. 
> Essentially at 1, you don't know if %1 is safe to have its lifetime
> extended.   That's the key difference -- more context is needed for the
> single register constraint issues.

Yes, there is basically one contract which has to be fulfilled at the
moment: A hard register N must not be live at an insn which constraints
an operand to hard register N via a hard register constraint or a
constraint referring to a single register class.  That would be a bug
coming either from an optimization or if the target emits a sequence of
instructions where we have assignments to hard registers and usages of
hard register constraints referring to the same hard register.

Assume that in the following r100 is constraint to hard register %1 in
insn 1 and 3.  Furthermore, assume that hard register %1 becomes dead
between insn 2 and 3.

1: r101=exp_a(r100)
   ...
2: %r1=...
   ...
3: r102=exp_b(r100)

Then r100 would be assigned %r1 for insn 1 and spilled prior insn 2 and
reloaded just after insn 2 as soon as %1 becomes dead.  Thus, between
insn 2 and 3 the lifetime of %1 may be extended assuming that no other
insn has a single register constraint referring to %1.  The important
part is that %1 becomes dead somewhere before insn 3, i.e., I have to
prevent CSE to extend the lifetime of register %1 across insn 3.

> 
> ISTM that you have to invalidate the relevant hash table entries at 4.  Not
> sure the best way to do that, most of CSE has fallen out of my brain through
> the years.

Isn't this what is happening by this patch during
invalidate_from_sets_and_clobbers() where we have the context in form of
the insn?  I'm basically removing all hash table entries which are
related to a register which is referred by any hard register constraint.
That should prevent CSE to propagate a hard register any further.

Cheers,
Stefan
  
Jeffrey Law Feb. 3, 2026, 11:55 p.m. UTC | #8
On 1/23/2026 7:49 AM, Stefan Schulze Frielinghaus wrote:
> On Thu, Jan 22, 2026 at 09:55:42AM -0700, Jeffrey Law wrote:
>>
>> On 1/16/2026 7:29 AM, Stefan Schulze Frielinghaus wrote:
>>>> The likely-spilled classes machinery can't be used as-is since the decision
>>>> for your case is dependent upon context (ie, the insn itself and its
>>>> constraints).  So perhaps what we want is your code to peek at the
>>>> constraints, but bolted onto the code that's checking for the likely-spilled
>>>> classes?
>>> My knowledge about cse is very thin and so far I don't see how to
>>> implement this in hash_rtx() or tight this with likely-spilled classes.
>>> For example, having
>>>
>>> 1: r100=%1
>>> 2: r101=...
>>> 3: r102=exp_a(r100)
>>> 4: r103=exp_b(r101)
>>> 5: r104=exp_c(r100)
>>>
>>> Assume that insn 3 has no hard register constraint, but insn 4 does
>>> which is referring to hard register %1.  Then I would still like to keep
>>> track that r100 and %1 hold the same value, while analyzing insn 1,
>>> which enables me to substitute r100 by %1 in insn 3.  For insn 4,
>>> however, I would like to invalidate this relation, since operand r101 is
>>> potentially loaded into %1 and would clobber the register.  Explicitly
>>> clobbering/invalidating register %1 while analyzing insn 4 ensures that
>>> r100 is not substituted by %1 in insn 5.
>>>
>>> Thus, at first encounter I still want to hash a hard register and at
>>> some later point once I encounter a hard register constraint referring to
>>> the same hard register, I want to invalidate it.  To be conservative
>>> here, I do this irrespective whether an alternative is likely, unlikely,
>>> or whatsoever.  Basically I'm treating a hard register constraint as a
>>> possible load.
>>>
>>> In that regard hard register constraints may not be as restrictive as
>>> single register classes, since for the latter even a substitution into
>>> insn 3 wouldn't be done if I read the current implementation correctly.
>> The basic idea is that by setting "record" to false, the value won't be kept
>> in the hash table and it won't be subject to CSE going forward.   For your
>> scenario I think we still have a problem; but I don't see a great solution.
>> Essentially at 1, you don't know if %1 is safe to have its lifetime
>> extended.   That's the key difference -- more context is needed for the
>> single register constraint issues.
> Yes, there is basically one contract which has to be fulfilled at the
> moment: A hard register N must not be live at an insn which constraints
> an operand to hard register N via a hard register constraint or a
> constraint referring to a single register class.  That would be a bug
> coming either from an optimization or if the target emits a sequence of
> instructions where we have assignments to hard registers and usages of
> hard register constraints referring to the same hard register.
>
> Assume that in the following r100 is constraint to hard register %1 in
> insn 1 and 3.  Furthermore, assume that hard register %1 becomes dead
> between insn 2 and 3.
>
> 1: r101=exp_a(r100)
>     ...
> 2: %r1=...
>     ...
> 3: r102=exp_b(r100)
>
> Then r100 would be assigned %r1 for insn 1 and spilled prior insn 2 and
> reloaded just after insn 2 as soon as %1 becomes dead.  Thus, between
> insn 2 and 3 the lifetime of %1 may be extended assuming that no other
> insn has a single register constraint referring to %1.  The important
> part is that %1 becomes dead somewhere before insn 3, i.e., I have to
> prevent CSE to extend the lifetime of register %1 across insn 3.
>
>> ISTM that you have to invalidate the relevant hash table entries at 4.  Not
>> sure the best way to do that, most of CSE has fallen out of my brain through
>> the years.
> Isn't this what is happening by this patch during
> invalidate_from_sets_and_clobbers() where we have the context in form of
> the insn?  I'm basically removing all hash table entries which are
> related to a register which is referred by any hard register constraint.
> That should prevent CSE to propagate a hard register any further.
Yea, it just took me a little time to reach the conclusion that this has 
to be handled as an invalidation problem, not as a "avoid putting 
something into the hash table" problem.    Now that I'm on board with 
that key concept, it's obvious to me that your patch is a reasonable 
solution.


OK for the trunk.  Thanks for your patience.

Jeff
>
> Cheers,
> Stefan
  

Patch

diff --git a/gcc/cse.cc b/gcc/cse.cc
index a86fe307819..28175e2732b 100644
--- a/gcc/cse.cc
+++ b/gcc/cse.cc
@@ -23,6 +23,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "backend.h"
 #include "target.h"
 #include "rtl.h"
+#include "stmt.h"
 #include "tree.h"
 #include "cfghooks.h"
 #include "df.h"
@@ -31,6 +32,7 @@  along with GCC; see the file COPYING3.  If not see
 #include "insn-config.h"
 #include "regs.h"
 #include "emit-rtl.h"
+#include "ira.h"
 #include "recog.h"
 #include "cfgrtl.h"
 #include "cfganal.h"
@@ -6217,6 +6219,46 @@  invalidate_from_sets_and_clobbers (rtx_insn *insn)
 	    invalidate (SET_DEST (y), VOIDmode);
 	}
     }
+
+  /* Any single register constraint may introduce a conflict, if the associated
+     hard register is live.  For example:
+
+     r100=%1
+     r101=42
+     r102=exp(r101)
+
+     If the first operand r101 of exp is constrained to hard register %1, then
+     r100 cannot be trivially substituted by %1 in the following since %1 got
+     clobbered.  Such conflicts may stem from single register classes as well
+     as hard register constraints.  Since prior RA we do not know which
+     alternative will be chosen, be conservative and consider any such hard
+     register from any alternative as a potential clobber.  */
+  extract_insn (insn);
+  for (int nop = recog_data.n_operands - 1; nop >= 0; --nop)
+    {
+      int c;
+      const char *p = recog_data.constraints[nop];
+      for (; (c = *p); p += CONSTRAINT_LEN (c, p))
+	if (c == ',')
+	  ;
+	else if (c == '{')
+	  {
+	    int regno = decode_hard_reg_constraint (p);
+	    machine_mode mode = recog_data.operand_mode[nop];
+	    invalidate_reg (gen_rtx_REG (mode, regno));
+	  }
+	else
+	  {
+	    enum reg_class cl
+	      = reg_class_for_constraint (lookup_constraint (p));
+	    if (cl == NO_REGS)
+	      continue;
+	    machine_mode mode = recog_data.operand_mode[nop];
+	    int regno = ira_class_singleton[cl][mode];
+	    if (regno >= 0)
+	      invalidate_reg (gen_rtx_REG (mode, regno));
+	  }
+    }
 }
 
 static rtx cse_process_note (rtx);
diff --git a/gcc/testsuite/gcc.target/powerpc/asm-hard-reg-2.c b/gcc/testsuite/gcc.target/powerpc/asm-hard-reg-2.c
new file mode 100644
index 00000000000..c0aad292acb
--- /dev/null
+++ b/gcc/testsuite/gcc.target/powerpc/asm-hard-reg-2.c
@@ -0,0 +1,9 @@ 
+/* { dg-do compile } */
+
+double a;
+double b (long double c) {
+  long double d = 0;
+  double e;
+  __asm__ ("" : "=f" (a), "={fr2}" (e) : "{fr1}" (d));
+  return c + c;
+}