了解gcc关于AAPC的行为(在STM32上)

了解gcc关于AAPC的行为(在STM32上),gcc,arm,stm32,Gcc,Arm,Stm32,编辑:我完全知道函数asmCopy可能不是functional,我的问题更多的是关于gcc在寄存器中传递参数的行为 我正在使用STM32CubeIDE开发STM32H7,它的生成器是arm none eabi gcc 优化级别为-Os 我看到以下行为,我无法解释。我拍摄了屏幕截图以获得并行的asm和C代码 我的C代码正在调用3个函数。第一个和第三个参数完全相同 第二个不需要参数。代码如下: static void Reset_Cycle_Counter(void) { volatile

编辑:我完全知道函数asmCopy可能不是functional,我的问题更多的是关于gcc在寄存器中传递参数的行为

我正在使用STM32CubeIDE开发STM32H7,它的生成器是arm none eabi gcc

优化级别为-Os

我看到以下行为,我无法解释。我拍摄了屏幕截图以获得并行的asm和C代码

我的C代码正在调用3个函数。第一个和第三个参数完全相同

第二个不需要参数。代码如下:

static void Reset_Cycle_Counter(void)
{
    volatile unsigned long *DWT_CYCCNT = (unsigned long *)0xE0001004;
    volatile unsigned long *DWT_CONTROL = (uint32_t *)0xE0001000;

    // Reset cycle counter
    *DWT_CONTROL = *DWT_CONTROL & ~0x00000001 ;
    *DWT_CYCCNT = 0;
    *DWT_CONTROL = *DWT_CONTROL | 1 ;
}
第三个函数很特别:我正在尝试编写一些汇编代码(现在很可能是错误的)

在第一次函数调用(对memcpy)之前,r0、r1和r2都加载了正确的值

然后在调用第三个函数之前,正如您在下面看到的,r1和r2中的参数是错误的(qspi_addr应该是0x90000000)。

我对AAPCS(ARM上的过程调用标准)的理解是,在调用子程序之前,寄存器r0到r3应该加载函数的参数(如果有)。子例程不需要保留或恢复这些寄存器。第二个函数修改r1和r2是正常的。因此,我希望编译器在第三次调用之前更新r0、r1和r2

如果我将优化代码更改为-O0,我确实会得到这种预期行为


你觉得怎么样?

如果你试着问编译器如何存档它,一切都会变得容易得多

代码呢

asmCopy:
.L2:
        adds    r2, r2, #-1
        bcs     .L3
        bx      lr
.L3:
        ldrb r12,[r1], #1
        strb r12,[r0], #1
        b       .L2
asmCopy1:
        subs    r0, r0, #1
        add     r2, r2, r1
.L5:
        cmp     r1, r2
        bne     .L6
        bx      lr
.L6:
        ldrb    r3, [r1], #1    @ zero_extendqisi2
        strb    r3, [r0, #1]!
        b       .L5

您不能仅仅打开一个内联汇编块并假设r0和r1仍然包含函数参数。对此没有任何保证。如果需要使用参数,则需要将其作为输入和/或输出操作数正确传递

static void __attribute__((noinline))
myAsmCopy(void* dst, void* src, uint32_t bytes) {
  asm volatile("1: cbz %[bytes], 1f \n"
               "ldrb r12, [%[src]], #1 \n"
               "strb r12, [%[dst]], #1 \n"
               "subs %[bytes], #1 \n"
               "b 1b \n"
               "1: \n"
               : [dst] "+&r"(dst), [src] "+&r"(src), [bytes] "+&r"(bytes)
               :
               : "cc", "memory", "r12");
}
GCC在这里有一些关于内联汇编的详细文档:


很明显,你以前从来没有用过这些东西,我必须强烈反对。如果“C包含步兵枪”,那么内联装配就是将一支6发左轮手枪和5发子弹对准你的头部。

我想我已经找到了答案

在我正在测试的函数中(无论是我实现的糟糕的函数,还是@Vinci中更好的函数),传递给函数的一些参数是全局变量(运行某些测试的伪数据数组)

我的理解是,编译器“修改”函数的原型,以构建一个只使用一个参数的函数。其他参数被视为常量,仅在函数开始时相对加载

因此,我修改了代码以调用完全相同的函数,但使用了本地易失性指针,问题就消失了:我可以看到寄存器r0、r1和r2按照我的预期加载了参数


这有意义吗?

关键是要玩内联汇编,我当然不打算在实际应用中使用它。。。我的问题不是关于我试图编写的asm函数,而是关于AAPCS。“你不能仅仅打开一个内联汇编块,然后假设r0和r1仍然包含函数参数。对此没有任何保证”完全同意。但是在我的例子中,r1在分支指令(函数调用)之前没有包含正确的值,这从编译器的角度来说并不重要,因为你不需要对它们做任何事情。我想我不明白你的意思。我多年来一直在调试ARM组件,我总是看到在r0、r1、r2、r3中传递函数参数。这就是ARM的过程调用标准所说的:“前四个寄存器r0-r3(a1-a4)用于将参数值传递到子例程中,并从函数返回结果值。它们也可以用于保存例程中的中间值(但通常仅在子例程调用之间)。”那么,您为什么说“你没有对它们做任何事情”
subs
已经设置了条件代码,因此
b1b
可以是
bne 1b
,本地标签更改为
ldrb
指令,在循环计数中保存一个ins。我不理解“如果你试图问编译器如何存档它”这句话“。还有,编译器资源管理器中第一个函数的asm有什么问题?还有,如果输入r1时r1不包含*src的值,第二个函数asmCopy1如何工作?因为它是ABI。显然,它更复杂。我看了一下@Vinci代码的反汇编,在myAsmCopy的一开始(就在BL之后),r1和r2不包含函数参数,但是函数的第一个指令是MOV和LDR,用于加载带有硬编码值的寄存器r1和r2(我的代码只是一个示例,参数是固定的)。很简单,没有注释的问题是编译器在使用R0和R1时看不到任何东西,可能会决定复制R2(
字节
)。例如,它可能决定展开循环。特别是,这符合您对无优化案例工作的描述。带注释的内联汇编程序有助于解决这个问题。如果编译器内联所有函数,它可能会决定
src
dst
是无用的。同样的问题也发生在正确的带注释的内联汇编函数上(见下文)。函数没有内联,循环也没有展开。从另一个“答案”来看,“我的理解是编译器“修改”了函数的原型,以构建一个只使用一个参数的函数。”编译器修改某些内容是您没有正确注释的证据。下面的功能可能不正确。另外,静态函数不必遵守任何ABI。如果希望编译器遵守ABI,则需要将此函数设置为全局函数。这不是最好的解决办法;两者都不是易失性的。
Reset\u Cycle\u计数器
clobber r0-r2?您帖子中的图片有问题(最好给我们发短信给我们)
asmCopy:
.L2:
        adds    r2, r2, #-1
        bcs     .L3
        bx      lr
.L3:
        ldrb r12,[r1], #1
        strb r12,[r0], #1
        b       .L2
asmCopy1:
        subs    r0, r0, #1
        add     r2, r2, r1
.L5:
        cmp     r1, r2
        bne     .L6
        bx      lr
.L6:
        ldrb    r3, [r1], #1    @ zero_extendqisi2
        strb    r3, [r0, #1]!
        b       .L5
static void __attribute__((noinline))
myAsmCopy(void* dst, void* src, uint32_t bytes) {
  asm volatile("1: cbz %[bytes], 1f \n"
               "ldrb r12, [%[src]], #1 \n"
               "strb r12, [%[dst]], #1 \n"
               "subs %[bytes], #1 \n"
               "b 1b \n"
               "1: \n"
               : [dst] "+&r"(dst), [src] "+&r"(src), [bytes] "+&r"(bytes)
               :
               : "cc", "memory", "r12");
}