如何在扩展的GCC内联程序集中标记为被破坏的输入操作数(C寄存器变量)?



>问题描述

我正在尝试设计 C 代码解压缩数组Auint32_t 元素到数组Buint32_t元素,其中A的每个元素都解压缩为两个连续的B元素,以便B[2*i]包含低 16 位A[i]B[2*i + 1]包含高 16 位右移A[i], 即,

B[2*i] = A[i] & 0xFFFFul;
B[2*i+1] = A[i] >> 16u;

请注意,数组与 4 对齐,长度可变,但A始终包含 4 的倍数uint32_t,大小为 <= 32,B有足够的空间进行解包,我们在 ARM Cortex-M3 上。

GCC 内联 asm 中当前错误的解决方案

由于GCC在优化这种解包方面并不好,我编写了展开的C和内联asm,以使其速度优化,具有可接受的代码大小和寄存器使用情况。展开的代码如下所示:

static void unpack(uint32_t * src, uint32_t * dst, uint8_t nmb8byteBlocks)
{
switch(nmb8byteBlocks) {
case 8:
UNPACK(src, dst)
case 7:
UNPACK(src, dst)
...
case 1:
UNPACK(src, dst)
default:;
}
}

哪里

#define UNPACK(src, dst) 
asm volatile ( 
"ldm     %0!, {r2, r4} nt" 
"lsrs    r3, r2, #16 nt" 
"lsrs    r5, r4, #16 nt" 
"stm     %1!, {r2-r5} nt" 
: 
: "r" (src), "r" (dst) 
: "r2", "r3", "r4", "r5" 
);

它一直工作到GCC的优化器决定内联函数(want属性)并在下一个代码中重用srcdst寄存器变量。显然,由于ldm %0!stm %1!指令,srcdst在离开 switch 语句时包含不同的地址。

如何解决?

我不知道如何通知 GCC 用于srcdst的寄存器在上case 1:的最后一个 UNPACK 宏之后无效。

我试图将它们作为输出操作数传递到所有或仅最后一个宏 ("=r" (mem), "=r" (pma)) 中,或者以某种方式(如何)将它们包含在内联 asm clobbers 中,但它只会使寄存器处理变得更糟错误代码再次

。只有一个解决方案是禁用函数内联(__attribute__ ((noinline))),但在这种情况下,我失去了GCC的优势,如果nmb8byteBlocks在编译时已知,GCC可以削减适当数量的宏并将其内联。(将代码重写为纯汇编代码也有同样的缺点。

是否有可能在内联装配中解决这个问题?

我认为您正在寻找+约束修饰符,这意味着"此操作数既是可读的又是写入的"。(请参阅 GCC 内联程序集文档的"修饰符"部分。

您还需要告诉 GCC 这个asm读取和写入内存;最简单的方法是将"memory"添加到 clobber 列表中。 并且你用lsrs破坏"条件代码",所以"cc"克洛伯也是必要的。 试试这个:

#define UNPACK(src, dst) 
asm volatile ( 
"ldm     %0!, {r2, r4} nt" 
"lsrs    r3, r2, #16 nt" 
"lsrs    r5, r4, #16 nt" 
"stm     %1!, {r2-r5} nt" 
: "+r" (src), "+r" (dst) 
: /* no input-only operands */ 
: "r2", "r3", "r4", "r5", "memory", "cc" 
);

(微优化:由于您不使用班次的条件代码,因此最好使用lsr而不是lsrs。 它还使代码在几个月后更容易阅读;将来,您不会挠头想知道这里实际上需要条件代码是否有某种原因。编辑:有人提醒我,lsrs的编码比拇指格式的lsr更紧凑,即使不需要条件代码,这也足以成为使用它的理由。

(我想说的是,如果你让GCC选择暂存器寄存器,你会得到更好的寄存器分配器行为,但我不知道如何告诉它按照ldmstm的要求以特定的数字顺序挑选暂存器,或者如何告诉它只使用2字节Thumb指令可访问的寄存器。

(可以使用"m"类型的输入和输出操作数准确指定读取和写入的内存,但这很复杂,可能不会有太大改进。 如果您发现此代码有效,但导致一堆不相关的东西不必要地从内存重新加载到寄存器中,请参阅如何指示可以使用内联 ASM 参数指向的内存?

unpack(如果将其函数签名更改为

static void unpack(const uint32_t *restrict src,
uint32_t *restrict dst,
unsigned int nmb8byteBlocks)

我还会尝试添加if (nmb8byteBlocks > 8) __builtin_trap();作为函数的第一行。

非常感谢 zwol,这正是我一直在寻找的,但在 GCC 内联程序集页面中找不到它。它完美地解决了这个问题——现在 GCC 在不同的寄存器中复制了srcdst,并在最后一个UNPACK宏之后正确使用它们。两点评论:

  1. 我使用lsrs因为它编译为 2 字节 Cortex-M3 本机lsrs。如果我使用不接触lsr版本的标志,它会编译为 4 字节mov.w r3, r2, lsr #16-> 16 位 Thumb 2lsr默认带有"s"。如果没有"s",则必须使用 32 位 Thumb 2(我必须检查它)。无论如何,在这种情况下,我应该在 clobbers 中添加"cc"。
  2. 在上面的代码中,我删除了 nmb8byteBlocks 值范围检查以使其清晰。但是,当然,你的最后一句话不仅对所有C程序员来说都是一个好点。

相关内容

最新更新