BLKmode parameters are stored in unaligned stack slot when passed via registers.

Renlin Li renlin.li@foss.arm.com
Tue Mar 6 20:03:00 GMT 2018


Hi Richard,

On 06/03/18 16:04, Richard Biener wrote:
> On Tue, Mar 6, 2018 at 4:21 PM, Renlin Li <renlin.li@arm.com> wrote:
>> Hi all,
>>
>> The problem described here probably only affects targets whose ABI allow to
>> pass structured
>> arguments of certain size via registers.
>>
>> If the mode of the parameter type is BLKmode, in the callee, during RTL
>> expanding,
>> a stack slot will be reserved for this parameter, and the incoming value
>> will be copied into
>> the stack slot.
>>
>> However, the stack slot for the parameter will not be aligned if the
>> alignment of parameter type
>> exceeds MAX_SUPPORTED_STACK_ALIGNMENT.
>> Chances are, unaligned memory access might cause run-time errors.
>>
>> For local variable on the stack, the alignment of the data type is honored,
>> although the document states that it is not guaranteed.
>>
>> For example:
>>
>> #include <stdint.h>
>> union U {
>>      uint32_t M0;
>>      uint32_t M1;
>>      uint32_t M2;
>>      uint32_t M3;
>> } __attribute((aligned(16)));
>>
>> void tmp (union U *);
>> void foo (union U P0)
>> {
>>    union U P1 = P0;
>>    tmp (&P1);
>> }
>>
>> The code-gen from armv7-a is like this:
>>
>> foo:
>>      @ args = 0, pretend = 0, frame = 48
>>      @ frame_needed = 0, uses_anonymous_args = 0
>>      str    lr, [sp, #-4]!
>>      sub    sp, sp, #52
>>      mov    ip, sp
>>      stm    ip, {r0, r1, r2, r3}  --> ip is not 128-bit aligned
>>      add    lr, sp, #39
>>      bic    lr, lr, #15
>>      ldm    ip, {r0, r1, r2, r3}
>>      stm    lr, {r0, r1, r2, r3} --> lr is 128-bit aligned
>>      mov    r0, lr
>>      bl    tmp
>>      add    sp, sp, #52
>>      @ sp needed
>>      ldr    pc, [sp], #4
>>
>> There are other obvious missed optimizations in the code-generation above.
>> The stack slot for parameter P0 and local variable P1 could be merged.
>> So that some of the load/store instructions could be removed.
>> I think this is a known missed optimization case.
>>
>> To summaries, there are two issues here:
>> 1, (wrong code) unaligned stack slot allocated for parameters during
>> function expansion.
>> 2, (missed optimization) stack slot for parameter sometimes is not
>> necessary.
>>     In certain scenario, the argument register could directly be used.
>>     Currently, this is only possible when the parameter mode is not BLKmode.
>>
>> For issue 1, we can do similar things as expand_used_vars.
>> Dynamically align the stack slot address for parameters whose alignment
>> exceeds
>> PREDERRED_STACK_BOUNDARY. Other parameters could be store in gap between the
>> aligned address and fp when possible.
>>
>> For issue 2, I checked the behavior of LLVM, it seems the stack slot
>> allocation
>> for parameters are explicitly exposed by the alloca IR instruction at the
>> very beginning.
>> Later, there are optimization/transformation passes like mem2reg, reg2mem,
>> sroa etc. to remove
>> unnecessary alloca instructions.
>>
>> In gcc, the stack allocation for parameters and local variables are done
>> during expand pass, implicitly.
>> And RTL passes are not able to remove the unnecessary stack allocation and
>> load/store operations.
>>
>> For example:
>>
>> uint32_t bar(union U P0)
>> {
>>    return P0.M0;
>> }
>>
>> Currently, the code-gen is different on different targets.
>> There are various backend hooks which make the code-gen sub-optimal.
>> For example, aarch64 target could directly return with w0 while armv7-a
>> target generates unnecessary
>> store and load.
>>
>> However, this optimization should be target independent, unrelated target
>> alignment configuration.
>> Both issue 1&2 could be resolved if gcc has a similar approach. But I assume
>> the change is big.
>>
>> Is there any suggestions for solving issue 1 and improving issue 2 in a
>> generic way?
>> I can create a bugzilla ticket to record the issue.
> 
> What does the ABI say for passing such over-aligned data types?
> 
> For solving 1) you could copy the argument as passed by the ABI
> to a properly aligned stack location in the callee.
> 
> Generally it sounds like either the ABI doesn't specify anything
> or the ABI specifies something that violates user expectation.
> 
> For 2) again, it is the ABI which specifies whether an argument
> is passed via the stack or via registers.  So - what does the ABI say?


The compiler is doing the right thing here to pass argument via registers.
To be specific, there are such clause in the arm PCS:

> B.5 If the argument is an alignment adjusted type its value is passed as a copy of the actual value. The
> copy will have an alignment defined as follows.
> ...
> For a Composite Type, the alignment of the copy will have 4-byte alignment if its natural alignment is
> <= 4 and 8-byte alignment if its natural alignment is >= 8

> C.3 If the argument requires double-word alignment (8-byte), the NCRN is rounded up to the next even
> register number.
> C.4 If the size in words of the argument is not more than r4 minus NCRN, the argument is copied into
> core registers, starting at the NCRN. The NCRN is incremented by the number of registers used.
> Successive registers hold the parts of the argument they would hold if its value were loaded into
> those registers from memory using an LDM instruction. The argument has now been allocated.


This is quite similar for other RISC machines.
Here, the problem here how arguments/parameters are received in the callee.
To store the incoming parameters on the stack, it seems an implementation decision.

Even for the following case without over-alignment, in the callee, it will save r0-r3 into local
stack first, and load M3 from local copy.

struct U {
     uint32_t M0;
     uint32_t M1;
     uint32_t M2;
     uint32_t M3;
};

int x (struct U p)
{
   return p.M3;
}


Regards,
Renlin

> 
> Richard.
> 
>> Regards,
>> Renlin



More information about the Gcc mailing list