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

imzjd6km  于 2023-06-30  发布在  其他
关注(0)|答案(2)|浏览(124)

问题描述

我试图设计一个C代码,将uint32_t元素的数组A解包为uint32_t元素的数组B,其中A的每个元素解包为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有足够的空间用于解包,并且我们在ARM Cortex-M3上。

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

由于GCC不擅长优化这种解包,我编写了展开的C & inline 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} \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))或仅在最后一个宏("=r" (mem), "=r" (pma))中将它们作为输出操作数传递,或者以某种方式(如何)将它们包含在内联asm clobbers中,但这只会使寄存器处理再次变得糟糕。
只有一个解决方案是禁用函数内联(__attribute__ ((noinline))),但在这种情况下,我失去了GCC的优势,它可以削减适当数量的宏并内联它,如果nmb 8byteBlocks在编译时已知的话。(将代码重写为纯程序集也有同样的缺点。)
有没有可能在内联装配中解决这个问题?

46scxncf

46scxncf1#

我 * 认为 * 你正在寻找+约束修饰符,这意味着“这个操作数既是读的,也是写的”。(请参阅GCC的内联程序集文档的“修改器”部分。)
你还需要告诉GCC这个asm读写内存; easiest way是通过将"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选择暂存寄存器,您会获得更好的寄存器分配器行为,但我不知道如何告诉它按照ldmstm的要求以特定的数字顺序选择暂存寄存器,或者如何告诉它只使用2字节Thumb指令可访问的寄存器。
(It可以用"m"类型的输入和输出操作数精确地指定要读取和写入的内存,但是它很复杂,可能不会有太大的改进。如果您发现这段代码可以工作,但会导致一堆不相关的东西不必要地从内存重新加载到寄存器中,请参阅How can I indicate that the memory pointed to by an inline ASM argument may be used?
(You如果将unpack的函数签名更改为

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

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

6tqwzwtp

6tqwzwtp2#

非常感谢zwol,这正是我正在寻找的,但在GCC内联汇编页面中找不到。它完美地解决了这个问题-现在GCC在不同的寄存器中复制srcdst,并在最后一个UNPACK宏之后正确地使用它们。
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”。
1.在上面的代码中,我删除了nmb 8byteBlocks值范围检查,以使其更清晰。当然,你的最后一句话不仅对所有C程序员来说都是一个很好的观点。

相关问题