ARM程序集:存储上的自动增量寄存器

时间:2012-02-01 19:40:06

标签: assembly arm

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

我制作了一个小程序,它在向量中存储两个数字。完成后,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]实际上是out[i+8],因此前8个打印值是向量中的最后8个,其余的是超出边界的内存位置。

我尝试在代码中使用volatile关键字的不同组合,但只有在使用-O0标志进行编译或者使用volatile向量而不是指针时,行为才会发生变化新的,比如

volatile int out[16];

3 个答案:

答案 0 :(得分:5)

对于商店和加载,您可以这样做:

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

在最后输入的内容,在这种情况下为4,在寄存器用于地址之后但在指令完成之前添加到基址寄存器(ldr示例中的r1和str示例中的r2)非常喜欢

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

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

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

是将两个本地整数分配出[0]和[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>

从ram读取printf的准备工作。

答案 1 :(得分:0)

GCC内联汇编程序要求将所有已修改的寄存器和非易失性变量列为输出或clobbers。在第二个例子中,GCC可能并且确实假设分配给inout的寄存器不会改变。

正确的方法是:

out_temp = out;
asm volatile ("..." : "+r"(in), "+r"(out_temp) :: "memory" );

答案 2 :(得分: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。在这个简单的例子中,它可能并不重要。