重写GCC内联汇编以不需要volatile或内存clobber

时间:2016-02-04 23:13:59

标签: c gcc arm inline-assembly

是否可以重写或改进此功能,以便在其内联汇编中不需要volatile或通用内存clobber?

// do stuff with the input Foo structure and write the result to the 
// output Bar structure.
static inline void MemFrob(const struct Foo* input, struct Bar* output) {
    register const Foo* r0 asm("r0") = input;
    register Bar* r1 asm("r1") = output;

    __asm__ __volatile__(
        "svc #0x0f0000 \n\t"
        : "+r" (r0), "+r" (r1)
        :
        : "r2", "r3", "cc", "memory"
        );
}

对于这种特定情况,目标平台是ARM7系统,代码正在使用GCC 5.3.0进行编译。正在执行的系统调用具有与C函数调用相同的调用约定。经过一些反复试验后,我已经达到了上述“有效”的目的,但我还不确定它是否正确并且总是有效,这取决于优化编译器的奇思妙想。

我希望能够删除“内存”clobber并告诉GCC确切地修改哪个内存,但是GCC Extended Asm文档讨论了如何为特定寄存器分配值,然后讨论内存约束,但不是他们俩都可以合并。截至目前,从上面的示例中删除“memory”clobber可能导致GCC在前面的代码中不使用输出。

我还希望能够在不使用输出的情况下删除volatile。但截至目前,从上面的例子中删除volatile会导致GCC根本不发出汇编。

通过将代码移动到外部编译单元,添加额外的内联汇编以手动将系统调用参数移动到r0 / r1或取消内联是浪费的解决方法,我宁愿避免使用。

1 个答案:

答案 0 :(得分:1)

长话短说:这就是"m"约束的含义。通常,如果您使用volatile__volatile__asm,则是因为代码中存在错误。编译器的主要工作之一是流分析,因此只要您提供足够的信息来进行正确的流分析,一切都将正常工作。

这是一个固定版本:

void MemFrob(const struct Foo* input, struct Bar* output) {
    register const Foo* r0 asm("r0") = input;
    register Bar* r1 asm("r1") = output;
    __asm__ (
        "svc #0x0f0000"
        : "=m"(*r1) // writes data to *output (but does not read)
        : "m"(*r0), // reads data in *input
          "l"(r0), "l"(r1) // This is necessary to ensure correct register
        : "r2", "r3", "cc"
        );
}

您可以在https://gcc.godbolt.org/(建议的-O2编译器选项)上对其进行测试。输出如下:

svc #0x0f0000
bx lr

显然,在内联时,它应该减少到只有一条指令。

不幸的是,在使用内联ARM程序集时,我无法弄清楚如何指定特定的寄存器,除了上面的方法有点笨拙。