Hi Kito,

On 2025/09/11 16:43, Kito Cheng wrote:
> Hi Tsukasa:
> 
> LGTM! Thanks a lot for spotting this hidden bug and sending the patch!
> Looks like LLVM has a similar issue too - seems we’ll need a fix there
> as well :)
> 
> Also, IIRC you have binutils commit access, so you should be able to
> push this directly. Let me know if you can’t push to trunk.

Committed, thanks!

Tsukasa


> On Tue, Sep 9, 2025 at 9:56 AM Tsukasa OI <[email protected]> 
> wrote:
>>
>> From: Tsukasa OI <[email protected]>
>>
>> In general, tail call optimization requires that the callee's saved
>> registers are a superset of the caller's.
>>
>> The Standard Vector Calling Convention Variant (assembler: .variant_cc)
>> requires that a function with this calling convention preserves vector
>> registers v1-v7 and v24-v31 across calls (i.e. callee-saved).  However,
>> the same set of registers are (function-local) temporary registers
>> (i.e. caller-saved) on the normal (non-vector) calling convention.
>>
>> Even if a function with this calling convention variant calls another
>> function with a non-vector calling convention, those vector registers
>> are correctly clobbered -- except when the sibling (tail) call
>> optimization occurs as it violates the general rule mentioned above.
>>
>> If this happens, following function body:
>>
>> 1.  Save v1-v7 and v24-v31 for clobbering
>> 2.  Call another function with a non-vector calling convention
>>     (which may destroy v1-v7 and/or v24-v31)
>> 3.  Restore v1-v7 and v24-v31
>> 4.  Return.
>>
>> may be incorrectly optimized into the following sequence:
>>
>> 1.  Save v1-v7 and v24-v31 for clobbering
>> 2.  Restore v1-v7 and v24-v31 (?!)
>> 3.  Jump to another function with a non-vector calling convention
>>     (which may destroy v1-v7 and/or v24-v31).
>>
>> This commit suppresses cross CC sibling call optimization from
>> the vector calling convention variant.
>>
>> gcc/ChangeLog:
>>
>>         * config/riscv/riscv.cc (riscv_function_ok_for_sibcall):
>>         Suppress cross calling convention sibcall optimization from
>>         the vector calling convention variant.
>>
>> gcc/testsuite/ChangeLog:
>>
>>         * gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall.c: New test.
>>         * 
>> gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-1.c: Ditto.
>>         * 
>> gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-2.c: Ditto.
>> ---
>>  gcc/config/riscv/riscv.cc                     |  6 +++
>>  .../abi-call-variant_cc-sibcall-indirect-1.c  | 12 +++++
>>  .../abi-call-variant_cc-sibcall-indirect-2.c  | 12 +++++
>>  .../rvv/base/abi-call-variant_cc-sibcall.c    | 54 +++++++++++++++++++
>>  4 files changed, 84 insertions(+)
>>  create mode 100644 
>> gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-1.c
>>  create mode 100644 
>> gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-2.c
>>  create mode 100644 
>> gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall.c
>>
>> diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
>> index bfd43fba1013..0dcc3029c6bf 100644
>> --- a/gcc/config/riscv/riscv.cc
>> +++ b/gcc/config/riscv/riscv.cc
>> @@ -11997,6 +11997,12 @@ riscv_function_ok_for_sibcall (tree decl 
>> ATTRIBUTE_UNUSED,
>>    if (cfun->machine->interrupt_handler_p)
>>      return false;
>>
>> +  /* Don't use sibcall if a non-vector CC function is being called
>> +     from a vector CC function.  */
>> +  if ((riscv_cc) crtl->abi->id () == RISCV_CC_V
>> +      && (riscv_cc) expr_callee_abi (exp).id () != RISCV_CC_V)
>> +    return false;
>> +
>>    /* Don't use sibcalls in the large model, because a sibcall instruction
>>       expanding and a epilogue expanding both use RISCV_PROLOGUE_TEMP
>>       register.  */
>> diff --git 
>> a/gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-1.c
>>  
>> b/gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-1.c
>> new file mode 100644
>> index 000000000000..54ff106716f8
>> --- /dev/null
>> +++ 
>> b/gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-1.c
>> @@ -0,0 +1,12 @@
>> +/* { dg-do compile } */
>> +/* { dg-options "-march=rv64gcv -mabi=lp64d -O2" } */
>> +
>> +void __attribute__((riscv_vector_cc))
>> +f_try_sibcall_v2v_indirect (void __attribute__((riscv_vector_cc))
>> +                           (*func) (void))
>> +{
>> +  func ();
>> +}
>> +
>> +/* { dg-final { scan-assembler-times 
>> "\\.variant_cc\tf_try_sibcall_v2v_indirect\n" 1 } } */
>> +/* { dg-final { scan-assembler-times "\tjr\ta0\n" 1 } } */
>> diff --git 
>> a/gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-2.c
>>  
>> b/gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-2.c
>> new file mode 100644
>> index 000000000000..121ac0f57e0a
>> --- /dev/null
>> +++ 
>> b/gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall-indirect-2.c
>> @@ -0,0 +1,12 @@
>> +/* { dg-do compile } */
>> +/* { dg-options "-march=rv64gcv -mabi=lp64d -O2" } */
>> +
>> +void __attribute__((riscv_vector_cc))
>> +f_try_sibcall_v2n_indirect (void (*func) (void))
>> +{
>> +  func ();
>> +}
>> +
>> +/* { dg-final { scan-assembler-times 
>> "\\.variant_cc\tf_try_sibcall_v2n_indirect\n" 1 } } */
>> +/* { dg-final { scan-assembler-times "\tjalr\ta0\n" 1 } } */
>> +/* { dg-final { scan-assembler-times "\tjr\tra\n" 1 } } */
>> diff --git 
>> a/gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall.c 
>> b/gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall.c
>> new file mode 100644
>> index 000000000000..ccfc38777f49
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.target/riscv/rvv/base/abi-call-variant_cc-sibcall.c
>> @@ -0,0 +1,54 @@
>> +/* { dg-do compile } */
>> +/* { dg-options "-march=rv64gcv -mabi=lp64d -O2" } */
>> +
>> +void f_ext_n2n (void);
>> +void f_ext_v2n (void);
>> +void __attribute__((riscv_vector_cc)) f_ext_n2v (void);
>> +void __attribute__((riscv_vector_cc)) f_ext_v2v (void);
>> +
>> +/* { dg-final { scan-assembler-times "\\.variant_cc\tf_ext_n2n\n" 0 } } */
>> +/* { dg-final { scan-assembler-times "\\.variant_cc\tf_ext_v2n\n" 0 } } */
>> +/* { dg-final { scan-assembler-times "\\.variant_cc\tf_ext_n2v\n" 1 } } */
>> +/* { dg-final { scan-assembler-times "\\.variant_cc\tf_ext_v2v\n" 1 } } */
>> +
>> +void
>> +f_try_sibcall_n2n (void)
>> +{
>> +  f_ext_n2n ();
>> +}
>> +
>> +/* { dg-final { scan-assembler-times "\\.variant_cc\tf_try_sibcall_n2n\n" 0 
>> } } */
>> +/* { dg-final { scan-assembler-times "\ttail\tf_ext_n2n\n" 1 } } */
>> +/* { dg-final { scan-assembler-times "\tcall\tf_ext_n2n\n" 0 } } */
>> +
>> +void
>> +f_try_sibcall_n2v (void)
>> +{
>> +  f_ext_n2v ();
>> +}
>> +
>> +/* { dg-final { scan-assembler-times "\\.variant_cc\tf_try_sibcall_n2v\n" 0 
>> } } */
>> +/* { dg-final { scan-assembler-times "\ttail\tf_ext_n2v\n" 1 } } */
>> +/* { dg-final { scan-assembler-times "\tcall\tf_ext_n2v\n" 0 } } */
>> +
>> +void __attribute__((riscv_vector_cc))
>> +f_try_sibcall_v2n (void)
>> +{
>> +  /* Vector to normal: sibling call optimization shall be
>> +     suppressed to preserve caller's registers: v1-v7 and v24-v31.  */
>> +  f_ext_v2n ();
>> +}
>> +
>> +/* { dg-final { scan-assembler-times "\\.variant_cc\tf_try_sibcall_v2n\n" 1 
>> } } */
>> +/* { dg-final { scan-assembler-times "\ttail\tf_ext_v2n\n" 0 } } */
>> +/* { dg-final { scan-assembler-times "\tcall\tf_ext_v2n\n" 1 } } */
>> +
>> +void __attribute__((riscv_vector_cc))
>> +f_try_sibcall_v2v (void)
>> +{
>> +  f_ext_v2v ();
>> +}
>> +
>> +/* { dg-final { scan-assembler-times "\\.variant_cc\tf_try_sibcall_v2v\n" 1 
>> } } */
>> +/* { dg-final { scan-assembler-times "\ttail\tf_ext_v2v\n" 1 } } */
>> +/* { dg-final { scan-assembler-times "\tcall\tf_ext_v2v\n" 0 } } */
>> --
>> 2.43.0
>>
> 

Reply via email to