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

How to mark as clobbered input operands (C register variables) in extended GCC inline assembly?

问题描述

我正在尝试将 uint32_t 元素的数组 A 设计为 uint32_t 元素的数组 B,其中 [=13= 的每个元素] 被解包为 B 的两个连续元素,因此 B[2*i] 包含 A[i] 的低 16 位并且 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 有足够的 space 用于解包,我们在 ARM Cortex-M3 上。

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

由于 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 的优化器决定内联函数(需要 属性)并在下一个代码中重用寄存器变量 srcdst。显然,由于 ldm %0!stm %1! 指令,srcdst 在离开 switch 语句时包含不同的地址。

如何解决?

我不知道如何通知 GCC srcdst 使用的寄存器在最后一个 case 1:.

中的最后一个 UNPACK 宏之后无效

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

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

有没有可能如何在内联汇编中解决这个问题?

认为 您正在寻找 + 约束修饰符,这意味着“此操作数既可读又可写”。 (请参阅 GCC inline-assembly 文档的“Modifiers”部分。)

你还需要告诉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" \
    );

(Micro-optimization: 由于您不使用班次的条件代码,因此最好使用 lsr 而不是 lsrs。它也使得几个月后代码更容易阅读;未来你不会挠头想知道是否有某种原因为什么这里实际上需要条件代码。 编辑:我被提醒 lsrs 有比 Thumb 格式的 lsr 更紧凑的编码,这足以成为使用它的理由,即使不需要条件代码。)

(我想说的是,如果让 GCC 选择临时寄存器,您会获得更好的寄存器分配器行为,但我不知道如何告诉它根据需要以特定数字顺序选择临时寄存器通过 ldmstm,或者如何告诉它只使用 2 字节 Thumb 指令可访问的寄存器。)

(用"m"类型的输入输出操作数可以准确指定读写什么内存,但是很复杂,可能不会有太大改善。如果你发现这段代码有效但导致一堆无关的东西不必要地从内存重新加载到寄存器中,请参考 )

(如果将 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 macro.Two 评论之后正确使用它们:

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