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

时间:2015-12-24 03:36:12

标签: assembly inline-assembly lldb

我有下面的内联汇编代码:

int get_year(int a, int *b, char * c)
{
    int ret, t1, t2;

    asm (
        "addl %3, %[a]                  \n\t"
        "movl %[a], %[t1]               \n\t"
        "movl $58, %%edx                \n\t"
        "movb %%dl, 0x04(%1)            \n\t"
        : [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 ; movsl\n\t"
        "movl %4,%%ecx\n\t"
        "rep ; movsb\n\t"
        : "=&c" (d0), "=&D" (d1), "=&S" (d2)
        : "0" (n >> 2), "g" (n & 3), "1" (dest), "2" (src)
        : "memory");

    return dest;
}

这样可以正常工作而不会出现任何编译错误。

2 个答案:

答案 0 :(得分:4)

首先,如果您正试图学习asm,那么GNU C inline asm是使用asm的最难方式之一。你不仅需要编写正确的asm,你必须花费大量的时间使用深奥的语法来告诉编译器完全你的代码输入和输出操作数需要什么,否则你会有一个坏的时间。在ASM中编写整个函数很多更容易。它们不能被内联,但无论如何它都是一种学习练习。正常函数ABI比C和具有约束的内联ASM之间的边界简单更多。请参阅 wiki ...

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

我认为这仍然是一个正在进行中的工作",因为你可以用更好的代码获得相同的结果。 (例如,使用%edx作为临时注册表是完全没必要的。)当然,在一般情况下,将其内联到代码中a可能是编译时常量,或者已知与...相关另外,你可以用C语言做更好的代码(除非你花了很多时间为各种情况制作inline-asm变种。)

int get_year(int a, int *b, char * c)
{
    int ret, t1, t2;

    asm (
        "addl %[bval], %[a] \n\t"
        "movb $58, 4 + %[cval]\n\t"  // 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;
}

现在compiles and assembles(使用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)

答案 1 :(得分:2)

如果我只在生成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",但unfortunately clang doesn't handle rm constraints well

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