On 3/31/26 16:22, Vineet Gupta wrote:
> 
> On 3/31/26 3:42 PM, David Faust wrote:
>>>> So then the only pattern that needs to change here would be
>>>> the register-register 'mov' insn itself. But the pattern is
>>>> matching a move from MM to MM, i.e. the source and dest must
>>>> already be the same mode. So are you sure we would need to emit
>>>> w reg in this case?
>>> Right, the modes not matching is not the issue, it emitting rN reg when
>>> both operands are SImode is the problem which I describe in changelog above.
>> Hm...
>>
>>> this can be a correctness issue where mov needs to zero out the upper bits.
>> I guess I am missing something but honestly I am confused by this part.
>> Why would mov need to zero out the upper bits?
>>
>> I mean, this case is matching only say a (set (reg:SI rD) (reg:SI rS)).
>> i.e. the source register already has an SImode value.  We aren't counting
>> on the state of the bits outside that SImode value.  Moving either the
>> low 32-bits or all 64, doesn't make a difference to that SImode value.
> 
> I think I'm starting to see where you are coming from.
> 
> 
>> Explicitly zeroing the upper 32 bits, while it may be achieved by using
>> a _BPF_ mov32 insn, I don't think is an exact match to the semantics of
>> a simple reg-to-reg move.
> 
> Seems fair.
> 
>> I understood from the testcase/pr that you are saying the issue is with
>> function return/argument passing and extension.  But I don't think this
>> simple case matching on set reg:MM reg:MM is the proper way to
>> address that.> 
> Right and that's not the intent, this is not claiming to fix the ABI, 
> that's a totally different change, this is just the precursor, at least 
> in my mind's eye, but I'm willing to concede that view-point.
> 
> So let me step back, all of this originated from ABI change work and 
> roughly based on HJ's v1 (not v2), I was starting to see the light. 
> There was a grand total of 1 additional selftest failure which was 
> attach_probe. The manually reduced test (attached here) showed the 
> failing case (is essentially beefier version of the test in this patch, 
> but same in spirit)

Pasting here the attached test for reference:

> /* Extracted from BPF selftests attach_probe: verifier failure.
>    Return value of first call is reused as argument of second.
>    It either needs to be explicitly truncated to s32 or w regs need
>    to be used.  */
> /* { dg-do compile } */
> /* { dg-options "-O2 -mcpu=v4" } */
> 
> extern int bpf_copy_from_user_str(int dst__sz);
> 
> _Bool verify_sleepable_user_copy_str(void)
> {
>  int ret;
>  char data_short_pad[4];
> 
>  ret = bpf_copy_from_user_str(sizeof(data_short_pad));
>  if (ret != 4)
>   return false;
> 
>  /* ret above is guaranteed to be 4, same as sizeof.  */
>  ret = bpf_copy_from_user_str(sizeof(data_short_pad));
>  if (ret != 4)
>   return false;
> 
>   return true;
> }
> 
> /* Don't generate 64-bit mov for arg, do 32-bit zero extended mov.  */
> /* { dg-final { scan-assembler-not {r1 = r0} } } */
> /* { dg-final { scan-assembler-not {r1 = \(s32\) r0} } } */
> /* { dg-final { scan-assembler-times {w1 = w0} 1 } } */

I'm confused by the comment at the top.

First:

>    Return value of first call is reused as argument of second.

Where? The only calls I see are identical:
  ret = bpf_copy_from_user_str (sizeof (data_short_pad));

The return value is not reused as an argument.

Secondly:

>    It either needs to be explicitly truncated to s32 or w regs need
>    to be used.  */
Why?  Assuming the value were actually being reused, we would be
taking an int return value and passing it as an int argument to a
function taking an int. There is no conversion needed.

> 
> So with the ABI hooks and everything in place, what we were seeing was 
> following RTL stream

In the RTL below it's clear that the values are all being treated
in SImode:

> 
> (call_insn 7 6 8 2 (set (reg:SI 0 %r0)

So the call return value is SImode.

>          (call (mem:DI (symbol_ref:DI ("bpf_copy_from_user_str") [flags 
> 0x41]  <function_decl 0x7f10c1547300 bpf_copy_from_user_str>) [0 
> bpf_copy_from_user_str S8 A64])
>              (const_int 0 [0])))
>       (expr_list:REG_CALL_DECL (symbol_ref:DI ("bpf_copy_from_user_str") 
> [flags 0x41]  <function_decl 0x7f10c1547300 bpf_copy_from_user_str>)
>          (nil))
>      (expr_list:SI (use (reg:SI 1 %r1))

 (use (reg:SI 1 %r1)) : the arg is passed SI mode

>          (nil)))
> 
> (insn 8 7 9 2 (set (reg/v:SI 19 [ ret ])
>          (reg:SI 0 %r0))

Return value copied to pseudo r19 in SImode to be used for
the comparison.

>       (nil))
> 
> (jump_insn 9 8 10 2 (set (pc)
>        (if_then_else (ne (reg/v:SI 19 [ ret ])
>                  (const_int 4 [0x4]))
>              (label_ref:DI 33)
>              (pc))) {*branch_on_si}
>   -> 33)

> > (insn 11 10 12 4 (set (reg:SI 1 %r1)
>          (const_int 4 [0x4]))
>       (nil))
> 
> cse1 equivalence logic deduces that reg 19 = 4 = SI, guaranteed by 
> control flow, so we end up with

Note that r19 is SImode a priori, to match the mode of the value from %r0.
It is not SI mode as a result of cse1 determining that it holds 4.

> 
> (insn 11 10 12 3 (set (reg:SI 1 %r1)
>         (reg/v:SI 19 [ ret ])) {*movsi}
>       (expr_list:REG_EQUAL (const_int 4 [0x4])
>          (nil)))

> And this carries all the way to the end, which if generates rN is would 
> be problematic and was the reason for this change.

And again, what precisely is problematic about generating rN = rM
for this set?
If %r1 is subsequently used as the int argument to a function call,
then it is valid as is.
If it would be passed as a 'unsigned long' or something else requiring
a conversion then we would rather have e.g. a zero_extend:DI to do that,
not a simple set.


> 
>>>> Maybe I am wrong but to me it seems the behavior in the test
>>>> below (and in the pr) must be more related to extendhisi.
>>> Yes and No :-)
>>>
>>> You are right that for the test below, it starts of with extend, sidi2
>>> to be precise, which gets expanded to 2 shifts (Its on my todo to avoid
>>> doing this for cpuv4 for better combine outcomes, but anyways that's for
>>> later).
>>>
>>> (insn 8 7 9 2 (set (reg:DI 19 [ _1 ])
>>>           (ashift:DI (subreg:DI (reg:SI 21) 0)
>>>               (const_int 32 [0x20])))
>>> (insn 9 8 10 2 (set (reg:DI 19 [ _1 ])
>>>           (ashiftrt:DI (reg:DI 19 [ _1 ])
>>>               (const_int 32 [0x20])))
>>> (insn 10 9 11 2 (set (reg:SI 1 %r1)
>>>           (subreg/s/u:SI (reg:DI 19 [ _1 ]) 0))
>>>
>>> Combine is able to see thru all this to generate insn 14
>> OK, the value is sign extended to DImode, and then truncated
>> back to SImode.  The original value in SImode is unchanged...
>>
>>> scanning new insn with uid = 14.
>>> ...
>>> allowing combination of insns 8 and 9
>>> deferring deletion of insn with uid = 8.
>>> allowing combination of insns 9 and 10
>>> deferring deletion of insn with uid = 9.
>>> rescanning insn with uid = 10.
>>>
>>> (insn 14 5 6 2 (set (reg:SI 23)
>>>           (reg:SI 0 %r0))  {*movsi}
>> ... so this result from combine is valid; the net effect
>> is simply to move the SImode value from one reg to another,
>> keeping SImode.
>>
>> I'm not sure exactly what is supposed to happen in the test.
>> The arg is passed as int, i.e. SImode. The origin of that value
>> is another int, also SImode. Why do the upper bits of the reg need
>> to be explicitly zeroed?
> 
> You may be right that test case is more ABI leaning (and this pinskia 
> said something on PR), but even with the ABI change, this adjustment 
> will still be needed IMO.
> 
> Thx,
> -Vineet

Reply via email to