LLVM 报告:不支持的内联 ASM:类型'void *'的输入与类型'int'匹配的输出



我有以下内联汇编代码:

int get_year(int a, int *b, char * c)
{
    int ret, t1, t2;
    asm (
        "addl %3, %[a]                  nt"
        "movl %[a], %[t1]               nt"
        "movl $58, %%edx                nt"
        "movb %%dl, 0x04(%1)            nt"
        : [t1] "=r" (t1), "=&D" (t2)
        : [a] "r" (a), "rm" (*b), "1" (c)
        : "edx", "memory"
    );
    ret = t1;
    return ret;
}

当我通过 llvm 编译它时,错误转储:

error: unsupported inline asm: input with type 'char *' matching output with type 'int'
                : [a] "r" (a), "rm" (*b), "1" (c)
                                               ^

但是,linux 内核中的 memcpy 函数具有与内联汇编使用相同的格式:

void *memcpy(void *dest, const void *src, size_t n)
{
    int d0, d1, d2;
    asm volatile(
        "rep ; movslnt"
        "movl %4,%%ecxnt"
        "rep ; movsbnt"
        : "=&c" (d0), "=&D" (d1), "=&S" (d2)
        : "0" (n >> 2), "g" (n & 3), "1" (dest), "2" (src)
        : "memory");
    return dest;
}

这工作正常,没有任何编译错误。

首先,如果你想在学习asm时动手,GNU C内联asm是使用asm最的方法之一。 您不仅必须编写正确的 asm,还必须花费大量时间使用深奥的语法来通知编译器您的代码对输入和输出操作数的确切需求,否则您将遇到不好的时间。 在 ASM 中编写整个函数要容易得多。 它们不能内联,但无论如何,这是一个学习练习。 普通函数 ABI 比 C 和具有约束的内联 ASM 之间的边界简单得多。 请参阅 x86 维基...


除了编译错误之外,你还有一个错误:你%[a],即使你告诉 gcc 这是一个仅输入的操作数。

我认为这仍然是一个"正在进行的工作",因为你可以用更好的代码得到相同的结果。 (例如,使用 %edx 作为暂存注册是完全没有必要的。 当然,在一般情况下,如果将其内联到代码中,其中a可能是编译时常量,或者已知与其他内容相关,那么仅用 C 语言执行此操作会获得更好的代码(除非您花费大量时间为各种情况制作内联 asm 变体(。

int get_year(int a, int *b, char * c)
{
    int ret, t1, t2;
    asm (
        "addl %[bval], %[a] nt"
        "movb $58, 4 + %[cval]nt"  // c is an "offsetable" memory operand
        : [t1] "=&r" (t1), [cval] "=o" (*c)
        : [a] "0" (a), [bval] "erm" (*b)
        : // no longer clobbers memory, because we use an output memory operand.
    );
    ret = t1;  // silly redundancy here, could have just used a as an input/output operand and returned it, since you apparently want the value
    return ret;
}

现在编译和组装(使用 godbolt 的"二进制"选项实际组装(。 4 + (%rdx)会产生警告,但会组装以4(%rdx)。 IDK 如何以在已经存在偏移量时不会出错的方式写入偏移量。 (例如,如果操作数是*(c+4),所以生成的asm是4 + 4(%rdx)的,则省略+是行不通的。

这仍然使用匹配输出操作数技巧,但我改为使用内存或一般约束来允许编译器时间常量最终执行addl $constant, %edi

这允许编译器在内联时尽可能灵活。 例如,如果调用方运行get_year(10, &arr[10], &some_struct.char_member),它可以使用它想要的任何寻址模式进行加载和存储,而不必在单个寄存器中生成c。 因此,例如,内联输出最终可能会被movb $58, 4+16(%rbp, %rbx),而不是强制它使用 4(%reg) .

如果我仅在生成 64 位代码时使用 clang 编译您的代码,我可以重现该问题。以 32 位代码为目标时,没有错误。正如Michael Petch所说,这表明问题在于两个操作数的大小不同。

目前还不完全清楚最好的解决方案是什么,因为您的 asm 声明没有多大意义。它相当于:

int get_year(int a, int *b, char *c) {
    a += *b;
    c[4] = 58;
    return a;
}        
使用

汇编语句来执行使用上面的 C 代码可以更清晰、更高效地完成的操作没有任何好处。因此,最好的解决方案是用等效的 C 代码完全替换您的代码。

如果您只是在玩内联程序集,那么等效的内联程序集将是:

int get_year2(int a, int *b, char * c)
{
        asm("addl %[b], %[a]"
            : [a] "+r" (a)
            : [b] "m" (*b)
            : "cc");
        asm("movb $58, %[c4]"
            : [c4] "=rm" (c[4]));
        return a;
}

我使用了两个asm语句,因为这两个部分是不相关的。将它们分开提供了更多的优化机会。例如,如果调用此函数但不使用返回值,编译器可以消除第一个 asm 语句,因为它的结果未使用且没有副作用。

我没有使用匹配约束(给您带来问题的"1"约束(,而是使用"+"约束修饰符将操作数标记为输入和输出。我发现这效果更好。[b]操作数的约束确实应该"rm"但不幸的是,clang 不能很好地处理 rm 约束。

您可能注意到,我只使用了两个汇编语句,而您的示例使用了四个。MOVL 指令不是必需的,如有必要,编译器可以处理将结果移动到返回值寄存器。最后两个汇编语句可以折叠成一个语句,该语句将常量直接移动到内存中,而不会破坏寄存器。说到这里,你的asm语句破坏了EFLAGS,条件代码,所以"cc"应该列出一个混乱的,但正如Peter Cordes指出的那样,x86目标没有必要,但编译器假设它们无论如何都是。

相关内容

  • 没有找到相关文章

最新更新