简体   繁体   中英

How to determine the correct way to pass the struct parameters in arm64 assembly?

Suppose I have this function that has many struct parameters (example from Raylib ):

void DrawTexturePro(Texture texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint) {
    // do something with these
}

full test.c :

// Texture, tex data stored in GPU memory (VRAM)
typedef struct Texture {
    unsigned int id;        // OpenGL texture id
    int width;              // Texture base width
    int height;             // Texture base height
    int mipmaps;            // Mipmap levels, 1 by default
    int format;             // Data format (PixelFormat type)
} Texture;
// Rectangle, 4 components
typedef struct Rectangle {
    float x;                // Rectangle top-left corner position x
    float y;                // Rectangle top-left corner position y
    float width;            // Rectangle width
    float height;           // Rectangle height
} Rectangle;
// Vector2, 2 components
typedef struct Vector2 {
    float x;                // Vector x component
    float y;                // Vector y component
} Vector2;
// Color, 4 components, R8G8B8A8 (32bit)
typedef struct Color {
    unsigned char r;        // Color red value
    unsigned char g;        // Color green value
    unsigned char b;        // Color blue value
    unsigned char a;        // Color alpha value
} Color;

void DrawTexturePro(Texture texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint) {
    // do something with these
}

int main(int argc, char** argv) {

    Texture tex = {0, 1, 2, 3, 4};
    Rectangle rec = { 0.0f, 0.1f, 0.2f, 0.3f};
    Vector2 vec = { 0.4f, 0.5f};
    Color color = {'a', 'b', 'c', 'd'};
    DrawTexturePro(tex, rec, rec, vec, 0.6f, color);
    return 0;
}

when I try to disassemble this code, it is very interesting to see that:

_DrawTexturePro:                        ; @DrawTexturePro
    .cfi_startproc
; %bb.0:
    sub sp, sp, #64
    .cfi_def_cfa_offset 64
    ldr w10, [sp, #64]
    ldr w9, [sp, #68]
    ldr w8, [sp, #72]
    str s0, [sp, #48]
    str s1, [sp, #52]
    str s2, [sp, #56]
    str s3, [sp, #60]
    str s4, [sp, #32]
    str s5, [sp, #36]
    str s6, [sp, #40]
    str s7, [sp, #44]
    str w10, [sp, #24]
    str w9, [sp, #28]
    str x1, [sp, #8]
    ldr w9, [sp, #8]
    str w9, [sp, #20]
    str w8, [sp, #4]
    add sp, sp, #64
    ret
    .cfi_endproc

full disassembly:

    .section    __TEXT,__text,regular,pure_instructions
    .build_version macos, 13, 0 sdk_version 13, 1
    .globl  _DrawTexturePro                 ; -- Begin function DrawTexturePro
    .p2align    2
_DrawTexturePro:                        ; @DrawTexturePro
    .cfi_startproc
; %bb.0:
    sub sp, sp, #64
    .cfi_def_cfa_offset 64
    ldr w10, [sp, #64]
    ldr w9, [sp, #68]
    ldr w8, [sp, #72]
    str s0, [sp, #48]
    str s1, [sp, #52]
    str s2, [sp, #56]
    str s3, [sp, #60]
    str s4, [sp, #32]
    str s5, [sp, #36]
    str s6, [sp, #40]
    str s7, [sp, #44]
    str w10, [sp, #24]
    str w9, [sp, #28]
    str x1, [sp, #8]
    ldr w9, [sp, #8]
    str w9, [sp, #20]
    str w8, [sp, #4]
    add sp, sp, #64
    ret
    .cfi_endproc
                                        ; -- End function
    .globl  _main                           ; -- Begin function main
    .p2align    2
_main:                                  ; @main
    .cfi_startproc
; %bb.0:
    sub sp, sp, #144
    stp x29, x30, [sp, #128]            ; 16-byte Folded Spill
    add x29, sp, #128
    .cfi_def_cfa w29, 16
    .cfi_offset w30, -8
    .cfi_offset w29, -16
    mov w8, #0
    str w8, [sp, #20]                   ; 4-byte Folded Spill
    stur    wzr, [x29, #-4]
    stur    w0, [x29, #-8]
    stur    x1, [x29, #-16]
    adrp    x8, l___const.main.tex@PAGE
    add x8, x8, l___const.main.tex@PAGEOFF
    ldr q0, [x8]
    stur    q0, [x29, #-48]
    ldr w8, [x8, #16]
    stur    w8, [x29, #-32]
    adrp    x8, l___const.main.rec@PAGE
    add x8, x8, l___const.main.rec@PAGEOFF
    ldr q0, [x8]
    str q0, [sp, #64]
    adrp    x8, l___const.main.vec@PAGE
    add x8, x8, l___const.main.vec@PAGEOFF
    ldr x8, [x8]
    str x8, [sp, #56]
    adrp    x8, l___const.main.color@PAGE
    add x8, x8, l___const.main.color@PAGEOFF
    ldr w8, [x8]
    str w8, [sp, #52]
    ldur    q0, [x29, #-48]
    add x0, sp, #32
    str q0, [sp, #32]
    ldur    w8, [x29, #-32]
    str w8, [sp, #48]
    ldr s0, [sp, #64]
    ldr s1, [sp, #68]
    ldr s2, [sp, #72]
    ldr s3, [sp, #76]
    ldr s4, [sp, #64]
    ldr s5, [sp, #68]
    ldr s6, [sp, #72]
    ldr s7, [sp, #76]
    ldr w10, [sp, #56]
    ldr w9, [sp, #60]
    ldr w8, [sp, #52]
    str w8, [sp, #24]
    ldr x1, [sp, #24]
    mov x8, sp
    str w10, [x8]
    str w9, [x8, #4]
    mov w9, #39322
    movk    w9, #16153, lsl #16
    fmov    s16, w9
    str s16, [x8, #8]
    bl  _DrawTexturePro
    ldr w0, [sp, #20]                   ; 4-byte Folded Reload
    ldp x29, x30, [sp, #128]            ; 16-byte Folded Reload
    add sp, sp, #144
    ret
    .cfi_endproc
                                        ; -- End function
    .section    __TEXT,__const
    .p2align    2                               ; @__const.main.tex
l___const.main.tex:
    .long   0                               ; 0x0
    .long   1                               ; 0x1
    .long   2                               ; 0x2
    .long   3                               ; 0x3
    .long   4                               ; 0x4

    .section    __TEXT,__literal16,16byte_literals
    .p2align    2                               ; @__const.main.rec
l___const.main.rec:
    .long   0x00000000                      ; float 0
    .long   0x3dcccccd                      ; float 0.100000001
    .long   0x3e4ccccd                      ; float 0.200000003
    .long   0x3e99999a                      ; float 0.300000012

    .section    __TEXT,__literal8,8byte_literals
    .p2align    2                               ; @__const.main.vec
l___const.main.vec:
    .long   0x3ecccccd                      ; float 0.400000006
    .long   0x3f000000                      ; float 0.5

    .section    __TEXT,__literal4,4byte_literals
l___const.main.color:                   ; @__const.main.color
    .byte   97                              ; 0x61
    .byte   98                              ; 0x62
    .byte   99                              ; 0x63
    .byte   100                             ; 0x64

Looks like the params are passed so that parts of the structs are in multiple registers . According to AArch64 parameter passing rules if the Composite Type (in this case the struct?) is larger than 16 bytes, then rules B.4 dictates that it will be copied to allocated memory and passed as an address.

If the argument type is a Composite Type that is larger than 16 bytes, then the argument is copied to memory allocated by the caller and the argument is replaced by a pointer to the copy.

However , Rectangle is a struct comprised of 4 float values, making its size at least 32 bytes. Why then is its members passed to s0-s3 and s4-s7 (line 71 to 79 of my disassembly) instead of being just a single address passed in a register (and btw if that is the case, which register set will this address be used in, the regular or the floating point registers?)

Rectangle 's size is only 16 bytes since each IEEE-754 float is 4 bytes. The compiler is correct. (thanks @Siguza)

My question is two fold:

  1. If I look at the C function declaration, how do I tell which way the compiler would like me to pass parameters to it using AArch64 assembly? (for example, passing an address of the struct on one register vs passing the truct's values onto multiple registers)
  2. did the AArch64 procedure call standard address it somehow and I'm just not seeing it, or is this really not defined?

EDIT: Question clarified following @httpdigest's pointer in the comment.

EDIT2: Question error fixed following @Siguza's comment.

Thanks to @httpdigest and @Siguza I think the answer to my question is as follow:

The parameter passing rules for aarch64 can be found here . Darwin's rules that diverge from the standard can be found here .

To determine the correct way of passing the parameter when you are looking at a function:

  1. Know the size of your param. Check if the param is larger than 16 bytes. If it is, then allocate memory, copy it to said memory and pass a pointer to the first available general register.
  2. If the param is smaller than 16 bytes and is not a composite type, depending on which machine type it is (reference here ) pass it to either the general purpose registers (x0-x7 for example) or the floating point registers (q0-q7 for example).
  3. If the param is a struct smaller than 16 bytes, then each of its elements will be loaded onto the registers one by one (for example, line 70-73 in my disassembly shows how Rectangle is passed as four floats on s0 to s3).

Thank you very much everyone. This is making more sense to me now.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM