[英]How to mark as clobbered input operands (C register variables) in extended GCC inline assembly?
问题描述
我正在尝试设计将 uint32_t 元素的数组A
解包到 uint32_t 元素的数组B
的 C 代码,其中A
每个元素都被解包为B
两个连续元素,以便B[2*i]
包含A[i]
低 16 位A[i]
和B[2*i + 1]
包含A[i]
右移的高 16 位,即,
B[2*i] = A[i] & 0xFFFFul;
B[2*i+1] = A[i] >> 16u;
请注意,数组对齐为 4,长度可变,但A
始终包含 uint32_t 的 4 的倍数且大小 <= 32, B
有足够的空间用于解包,我们在 ARM Cortex-M3 上。
当前 GCC 内联汇编中的错误解决方案
由于 GCC 不擅长优化这种解包,我编写了展开的 C 和内联汇编,以使其速度优化,代码大小和寄存器使用可接受。 展开的代码如下所示:
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} \n\t" \
"lsrs r3, r2, #16 \n\t" \
"lsrs r5, r4, #16 \n\t" \
"stm %1!, {r2-r5} \n\t" \
: \
: "r" (src), "r" (dst) \
: "r2", "r3", "r4", "r5" \
);
它会一直工作,直到 GCC 的优化器决定内联函数(想要的属性)并在下一个代码中重用寄存器变量src
和dst
。 显然,由于ldm %0!
和stm %1!
指令src
和dst
在离开 switch 语句时包含不同的地址。
如何解决?
我不知道如何通知 GCC 用于src
和dst
寄存器在最后case 1:
中的最后一个 UNPACK 宏之后无效case 1:
我试图将它们作为输出操作数传递给所有或仅最后一个宏( "=r" (mem), "=r" (pma)
)或以某种方式(如何)将它们包含在内联 asm clobbers 中,但它只进行寄存器处理糟糕的代码再次变得更糟。
只有一种解决方案是禁用函数内联( __attribute__ ((noinline))
),但在这种情况下,我失去了 GCC 的优势,如果 nmb8byteBlocks 在编译时已知,它可以减少适当数量的宏并内联它。 (同样的缺点也适用于将代码重写为纯汇编。)
有没有可能如何在内联汇编中解决这个问题?
我认为您正在寻找+
约束修饰符,这意味着“此操作数既可读取又可写入”。 (请参阅 GCC 内联汇编文档的“修饰符”部分。)
你还需要告诉GCC这个asm
读写内存; 最简单的方法是将"memory"
添加到 clobber 列表中。 并且您使用lsrs
破坏“条件代码”,因此还需要"cc"
破坏。 尝试这个:
#define UNPACK(src, dst) \
asm volatile ( \
"ldm %0!, {r2, r4} \n\t" \
"lsrs r3, r2, #16 \n\t" \
"lsrs r5, r4, #16 \n\t" \
"stm %1!, {r2-r5} \n\t" \
: "+r" (src), "+r" (dst) \
: /* no input-only operands */ \
: "r2", "r3", "r4", "r5", "memory", "cc" \
);
(微优化:由于不使用班次中的条件代码,因此最好使用编辑:我被提醒, lsr
而不是lsrs
。这也使几个月后的代码更易于阅读;将来您不会挠头想知道是否有这里实际上需要条件代码的一些原因。lsrs
编码比 Thumb 格式的lsr
更紧凑,即使不需要条件代码,这也足以成为使用它的理由。 )
(我想说,如果您让 GCC 选择临时寄存器,您将获得更好的寄存器分配器行为,但我不知道如何告诉它按照ldm
和stm
要求以特定数字顺序选择临时寄存器,或者如何告诉它只使用 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 在不同的寄存器中制作了src
和dst
的副本,并在最后一个UNPACK
宏之后正确使用它们。 两个评论:
lsrs
是因为它编译为 2 字节 Cortex-M3 本机lsrs
。 如果我使用标志不变lsr
版本,它会编译为 4 字节mov.w r3, r2, lsr #16
-> 16 位 Thumb 2 lsr
默认带有 's'。 如果没有“s”,则必须使用 32 位 Thumb 2(我必须检查它)。 无论如何,在这种情况下,我应该在clobbers中添加“cc”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.