6

是否可以使用 STR 自动增加寄存器的基地址[Rn]!?我浏览了文档,但未能找到明确的答案,主要是因为 LDR 和 STR 都提供了命令语法 - 理论上它应该适用于两者,但我找不到 auto 的任何示例- 增加商店(加载正常)。

我做了一个小程序,将两个数字存储在一个向量中。完成后,out应该是的内容,{1, 2}但存储会覆盖第一个字节,就好像自动增量不起作用一样。

#include <stdio.h>

int main()
{
        int out[]={0, 0};
        asm volatile (
        "mov    r0, #1          \n\t"
        "str    r0, [%0]!       \n\t"
        "add    r0, r0, #1      \n\t"
        "str    r0, [%0]        \n\t"
        :: "r"(out)
        : "r0" );
        printf("%d %d\n", out[0], out[1]);
        return 0;
}

编辑:虽然答案对于常规加载和存储是正确的,但我发现优化器会弄乱 vldm/vstm 等向量指令的自动增量。例如下面的程序

#include <stdio.h>

int main()
{
        volatile int *in = new int[16];
        volatile int *out = new int[16];

        for (int i=0;i<16;i++) in[i] = i;

        asm volatile (
        "vldm   %0!, {d0-d3}            \n\t"
        "vldm   %0,  {d4-d7}            \n\t"
        "vstm   %1!, {d0-d3}            \n\t"
        "vstm   %1,  {d4-d7}            \n\t"
        :: "r"(in), "r"(out)
        : "memory" );

        for (int i=0;i<16;i++) printf("%d\n", out[i]);
        return 0;
}

编译

g++ -O2 -march=armv7-a -mfpu=neon main.cpp -o main

将在最后 8 个变量的输出上产生乱码,因为优化器会保留递增的变量并将其用于 printf。换句话说,out[i]is 实际上out[i+8],所以前 8 个打印值是向量中的最后 8 个值,其余的是超出范围的内存位置。

我已经尝试在volatile整个代码中使用不同的关键字组合,但是只有当我使用-O0标志编译或者我使用 volatile 向量而不是指针和 new 时,行为才会改变,比如

volatile int out[16];
4

3 回答 3

6

对于存储和加载,您可以这样做:

ldr r0,[r1],#4
str r0,[r2],#4

无论您放在最后,在这种情况下为 4,都会在寄存器用于地址之后但在指令完成之前添加到基址寄存器(在 ldr 示例中为 r1,在 str 示例中为 r2),这非常像

unsigned int a,*b,*c;
...
a = *b++;
*c++ = a;

编辑,您需要查看反汇编以了解发生了什么,如果有的话。我正在使用最新的代码源,或者现在只是来自导师图形工具链的 sourcery lite。

arm-none-linux-gnueabi-gcc (Sourcery CodeBench Lite 2011.09-70) 4.6.1

#include <stdio.h>
int main ()
{
        int out[]={0, 0};
        asm volatile (
        "mov    r0, #1          \n\t"
        "str    r0, [%0], #4       \n\t"
        "add    r0, r0, #1      \n\t"
        "str    r0, [%0]        \n\t"
        :: "r"(out)
        : "r0" );
        printf("%d %d\n", out[0], out[1]);
        return 0;
}


arm-none-linux-gnueabi-gcc str.c -O2  -o str.elf

arm-none-linux-gnueabi-objdump -D str.elf > str.list


00008380 <main>:
    8380:   e92d4010    push    {r4, lr}
    8384:   e3a04000    mov r4, #0
    8388:   e24dd008    sub sp, sp, #8
    838c:   e58d4000    str r4, [sp]
    8390:   e58d4004    str r4, [sp, #4]
    8394:   e1a0300d    mov r3, sp
    8398:   e3a00001    mov r0, #1
    839c:   e4830004    str r0, [r3], #4
    83a0:   e2800001    add r0, r0, #1
    83a4:   e5830000    str r0, [r3]
    83a8:   e59f0014    ldr r0, [pc, #20]   ; 83c4 <main+0x44>
    83ac:   e1a01004    mov r1, r4
    83b0:   e1a02004    mov r2, r4
    83b4:   ebffffe5    bl  8350 <_init+0x20>
    83b8:   e1a00004    mov r0, r4
    83bc:   e28dd008    add sp, sp, #8
    83c0:   e8bd8010    pop {r4, pc}
    83c4:   0000854c    andeq   r8, r0, ip, asr #10

所以

sub sp, sp, #8

就是分配两个局部int out[0]和out[1]

mov r4,#0
str r4,[sp]
str r4,[sp,#4]

是因为它们被初始化为零,然后是内联汇编

8398:   e3a00001    mov r0, #1
839c:   e4830004    str r0, [r3], #4
83a0:   e2800001    add r0, r0, #1
83a4:   e5830000    str r0, [r3]

然后是printf:

83a8:   e59f0014    ldr r0, [pc, #20]   ; 83c4 <main+0x44>
83ac:   e1a01004    mov r1, r4
83b0:   e1a02004    mov r2, r4
83b4:   ebffffe5    bl  8350 <_init+0x20>

现在很清楚为什么它不起作用。你没有被宣布为易变的。您没有理由让代码返回 ram 以获取 printf 的 out[0] 和 out[1] 的值,编译器知道 r4 包含 out[0] 和 out[1] 的值,那里这个函数中的代码太少了,它不必逐出 r4 并重用它,因此它使用 r4 作为 printf。

如果您将其更改为易失性

    volatile int out[]={0, 0};

然后你应该得到想要的结果:

83a8:   e59f0014    ldr r0, [pc, #20]   ; 83c4 <main+0x44>
83ac:   e59d1000    ldr r1, [sp]
83b0:   e59d2004    ldr r2, [sp, #4]
83b4:   ebffffe5    bl  8350 <_init+0x20>

printf 的准备工作从 ram 中读取。

于 2012-02-01T20:20:56.250 回答
0

GCC 内联汇编器要求将所有修改过的寄存器和非易失性变量列为输出或破坏者。in在第二个示例中,GCC 可能并且确实假设分配给的寄存器out不会改变。

正确的方法是:

out_temp = out;
asm volatile ("..." : "+r"(in), "+r"(out_temp) :: "memory" );
于 2014-07-26T08:10:55.520 回答
0

我在寻找类似问题的答案时发现了这个问题:如何绑定输入/输出寄存器。内联汇编器常量的 GCC 文档说+输入寄存器列表中的前缀表示输入/输出寄存器。

在示例中,在我看来,您更愿意保留变量的原始值out。不过,如果您想使用!指令的后增量 ( ) 变体,我认为您应该将参数声明为读/写。以下适用于我的 Raspberry Pi 2:

#include <stdio.h>

int main()
{
  int* in = new int(16);
  volatile int* out = new int(16);

  for (int i=0; i<16; i++) in[i]=i;

  asm volatile(
    "vldm %0!, {d0-d3}\n\t"
    "vldm %0, {d4-d7}\n\t"
    "vstm %1!, {d0-d3}\n\t"
    "vstm %1, {d4-d7}\n\t"
    :"+r"(in), "+r"(out) :: "memory");

  for (int i=0; i<16; i++) printf("%d\n", out[i-8]);
  return 0;
}

这样,编译器就清楚了代码的语义:inout指针都将被改变(增加 8 个元素)。

免责声明:我不知道 ARM ABI 是否允许函数随意破坏 NEON 寄存器 d0 到 d7。在这个简单的例子中,它可能无关紧要。

于 2017-01-02T14:03:59.873 回答