Maybe this works:

diff --git a/arm64-gen.c b/arm64-gen.c
index d15446d3..ead88824 100644
--- a/arm64-gen.c
+++ b/arm64-gen.c
@@ -997,6 +997,8 @@ ST_FUNC void gfunc_call(int nb_args)
     int variadic = (vtop[-nb_args].type.ref->f.func_type == FUNC_ELLIPSIS);
     int var_nb_arg = n_func_args(&vtop[-nb_args].type);

+    save_regs(nb_args + 1);
+
 #ifdef CONFIG_TCC_BCHECK
     if (tcc_state->do_bounds_check)
         gbound_args(nb_args);
@@ -1126,7 +1128,6 @@ ST_FUNC void gfunc_call(int nb_args)
             vswap();
     }

-    save_regs(0);
     arm64_gen_bl_or_b(0);
     --vtop;
     if (stack & 0xfff)


On 17.05.2025 10:44, kbkp...@sina.com wrote:
​​Analysis of |gfunc_call| Handling of Struct Return Values​​

The current implementation of |gfunc_call| ​​does not properly save struct 
return values to the stack​​, leading to potential register/stack corruption in 
nested calls (e.g., |bug(func(a), func(a))|). Here’s the detailed breakdown:

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


      ​​Key Issues in the Current Code​​


        ​​1. Missing Intermediate Result Spilling​​

  * ​​Problem​​:
    After a function call returns a struct (via registers or a hidden pointer), 
the code does ​​not explicitly save the result to the stack​​. This leaves 
intermediate values vulnerable to being overwritten during subsequent argument 
preparation for nested calls.
      o /Example/:
        In |bug(func(a), func(a))|, the second |func(a)| call’s argument setup 
may reuse |D0/D1| (for small structs) or overwrite the memory pointed to by 
|X8| (for large structs), clobbering the first call’s result.


        ​​2. No Temporary Stack Management​​

  * ​​Problem​​:
    There is no mechanism to dynamically allocate/reserve stack space for 
intermediate results. The current stack adjustment (|add sp, sp, #n|) only 
reclaims space for outgoing arguments, not for preserving return values.
      o /Example/:
        If a struct is returned via |X8| (hidden pointer), the caller’s stack 
space for the struct may be reused in nested calls, leading to data corruption.


        ​​3. Register Reuse Without Safeguards​​

  * ​​Problem​​:
    The ARM64 ABI uses overlapping registers for parameters and return values 
(e.g., |D0| for both double parameters and returns). Without spilling, nested 
calls will overwrite these registers.
      o /Small Structs/: Returned via |D0-D3| but not saved before reusing 
these registers for the next call.
      o /Large Structs/: The hidden pointer (|X8|) is not saved, so nested 
calls may overwrite the struct’s memory.

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


      ​​Required Fixes​​


        ​​1. Save All Intermediate Results to the Stack​​

Add logic to spill return values to the stack immediately after each function 
call, regardless of type:

c
复制

|// After generating the call (arm64_gen_bl_or_b(0); --vtop;): if ((return_type->t & VT_BTYPE) == 
VT_STRUCT) { int size = type_size(return_type, &align); if (size <= 16) { // Small struct: save D0-D3 
to stack int offset = get_tmp_stack(size); for (int i = 0; i < size; i += 8) { o(0xFD0003E0 | (offset 
<< 10) | (i / 8)); // str dN, [sp, #offset] } } else { // Large struct: save X8 (hidden pointer) to 
stack int offset = get_tmp_stack(8); o(0xF90003E8 | (offset << 10)); // str x8, [sp, #offset] } }|


        ​​2. Add Temporary Stack Management​​

Implement dynamic stack allocation for intermediate results:

c
复制

|// In tccgen.c: static int tmp_stack_offset = 0; int get_tmp_stack(int size) { int 
offset = tmp_stack_offset; tmp_stack_offset += (size + 15) & ~15; // 16-byte 
aligned return offset; } void reset_tmp_stack(void) { tmp_stack_offset = 0; } // In 
gfunc_call, reset after each call: reset_tmp_stack();|


        ​​3. Load Intermediate Results from Stack During Nested Calls​​

Modify argument preparation to load values from the stack instead of registers:

c
复制

|// During the second pass (assigning values to registers): if (vtop->r is stack offset) { int offset = 
vtop->r; if ((vtop->type.t & VT_BTYPE) == VT_STRUCT) { if (size <= 16) { for (int j = 0; j < 
size; j += 8) { o(0xFD4003E0 | (offset << 10) | (j / 8)); // ldr dN, [sp, #offset] } } else { 
o(0xF94003E8 | (offset << 10)); // ldr x8, [sp, #offset] } } }|

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


      ​​Conclusion​​

The current code ​​does not handle struct return values safely​​ in nested 
calls. To fix this:

 1. ​​Spill all intermediate results​​ (structs, integers, floats) to the stack 
after each call.
 2. ​​Use temporary stack management​​ to avoid overwriting.
 3. ​​Load from the stack​​ during nested argument preparation.

This ensures that intermediate values are preserved across nested invocations, 
aligning the ARM64 backend with the reliability of the x86_64 implementation.


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
kbkp...@sina.com

    *From:* kbkp...@sina.com <mailto:kbkp...@sina.com>
    *Date:* 2025-05-17 16:33
    *To:* tinycc-devel <mailto:tinycc-devel@nongnu.org>
    *Subject:* Re: [Tinycc-devel] [linux/aarch64] fn call bug when one arg is a 
struct

    Bug Description and Fix​​

    ​​Cause of the Bug​​
    The bug arises in the TCC compiler’s ARM64 backend when handling nested 
function calls (e.g., |bug(func(a), func(a))|). Specifically, intermediate 
results (return values) from earlier function calls are not preserved before 
generating subsequent arguments, leading to ​​register clobbering​​. This 
occurs because:

     1.

        ​​Register Reuse​​: The ARM64 ABI uses overlapping registers for 
parameter passing and return values. For example:

          * Small structs (e.g., |struct { double x, y; }|) are passed/returned 
via |D0|/|D1| registers.
          * Integer/pointer returns use |X0|, while float/double returns use 
|D0|/|S0|.
            In nested calls, subsequent argument preparation overwrites these 
registers before prior return values are consumed, corrupting the intermediate 
results.
     2.

        ​​Lack of Intermediate Spilling​​: The compiler did not enforce saving 
intermediate return values to the stack, causing conflicts between parameter 
setup and return value storage. This was especially critical for structs and 
floating-point types, where register overlap is mandated by the ABI.

    ​​Fix Implementation​​
    To resolve this, the compiler will be modified to ​​spill intermediate 
results to the stack​​ immediately after each function call, ensuring they are 
preserved across nested invocations. Key changes include:

     1.

        ​​Dynamic Stack Allocation​​:

          * Add a temporary stack management system 
(|get_tmp_stack|/|reset_tmp_stack|) to track offsets for intermediate values.
          * Values are saved with 16-byte alignment to comply with ARM64 stack 
requirements.
     2.

        ​​Register Spilling Logic​​:

          * After generating a function call (|bl|), the return value is saved 
to the stack based on its type:

            c
            复制

            |// For integers/pointers: str x0, [sp, #offset] // For doubles: 
str d0, [sp, #offset] // For small structs: str d0/d1, [sp, #offset] // For 
large structs: save hidden pointer (x8) to stack |

          * During argument preparation for nested calls, intermediate values 
are reloaded from the stack instead of registers.
     3.

        ​​Comprehensive Type Handling​​:

          * Applied fixes for all data types: |int|, |float|, |double|, small 
structs (passed in registers), and large structs (passed via memory).
          * Ensured ABI compliance for both Homogeneous Floating-Point 
Aggregates (HFAs) and non-HFA structs.

    ​​Result​​
    The fix guarantees correct register usage across nested calls, eliminating 
overwrites. Intermediate results are now preserved on the stack, ensuring 
reliable code generation for all data types on ARM64.



    
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    kbkp...@sina.com

        *From:* kbkp...@sina.com <mailto:kbkp...@sina.com>
        *Date:* 2025-05-17 14:45
        *To:* tinycc-devel <mailto:tinycc-devel@nongnu.org>
        *Subject:* [Tinycc-devel] [linux/aarch64] fn call bug when one arg is a 
struct
        Under Linux/aarch64, when execute a function call with one arg is a 
struct, it pass wrong arg to the function call.

        a test code:

        mmm.c
        ```c
        #include <stdio.h>

        struct vec {
        float x;
        float y;
        };

        void bug(float y, float x) {
        printf("x=%f\ny=%f\n",x,y);
        }

        float dot(struct vec v) {
        return 10.0;
        }

        void main() {
        struct vec a;
        a.x = 2.0;
        a.y = 0.0;
        bug(dot(a), dot(a));
        }
        ```

        The correct result is :
        ```
        x=10.000000
        y=10.000000
        ```

        But linux/aarch64 tcc output a wrong result:
        ```
        x=10.000000
        y=2.000000
        ```

        
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

---
        kbkp...@sina.com



_______________________________________________
Tinycc-devel mailing list
Tinycc-devel@nongnu.org
https://lists.nongnu.org/mailman/listinfo/tinycc-devel



_______________________________________________
Tinycc-devel mailing list
Tinycc-devel@nongnu.org
https://lists.nongnu.org/mailman/listinfo/tinycc-devel

Reply via email to