【问题标题】:asm error: "operand type mismatch for `rol'" with multiple input operandsasm 错误:带有多个输入操作数的“‘rol’的操作数类型不匹配”
【发布时间】:2018-11-04 01:57:24
【问题描述】:

环境:Debian 9.5 - gcc 6.3.0

当我尝试使用多个 InputOperands 时,无法让嵌入式汇编函数工作。

我有以下代码工作(基本rol 函数,1 InputOperand,预定义rol 操作数):

#include <stdio.h>
#include <stdlib.h>

void asm_rol(int32_t* p_rolled)
{
    __asm__ volatile
    (
        ".intel_syntax noprefix;"
        "rol %0, 1;"
        :"=a"(*p_rolled)
        :"a"(*p_rolled)
        :"cc"
    );
}

int main(int argc, char** argv)
{
    int32_t test = 0x1;
    asm_rol(&test);

    printf("0x%08x\n", test);
    return 0;
}

这打印出0x00000002,是rol 0x1, 1 的正确结果。

现在我不明白为什么下面的代码无法编译。我认为我对InputOperands 的使用不好:

#include <stdio.h>
#include <stdlib.h>

void asm_rol(int32_t* p_rolled, int16_t i)
{
    __asm__ volatile
    (
        ".intel_syntax noprefix;"
        "rol %0, %1;"
        :"=a"(*p_rolled)
        :"a"(*p_rolled), "b"(i)
        :"cc"
    );
}

int main(int argc, char** argv)
{
    int32_t test = 0x1;
    asm_rol(&test, 1);

    printf("0x%08x\n", test);
    return 0;
}

gcc 返回错误:

resolve.c: Assembler messages:
resolve.c:6: Error: operand type mismatch for `rol'

我用int8_tint32_t 尝试了i,它没有任何改变。

我必须说我是在这种环境下在 C 中嵌入 asm 的新手,我只在 Windows 上使用 Visual Studio 完成了一些基本的内联汇编。

【问题讨论】:

  • 因为唯一允许用于控制移位位数的移位指令的寄存器是 CL
  • void asm_rol(int32_t* p_rolled, int8_t i) { __asm__ volatile ( ".intel_syntax noprefix;" "rol %0, %1;" :"+a"(*p_rolled) :"cI"(i) :"cc" ); } 可能工作
  • 因为 %1 可能意味着 %2 。 %2 表示它映射到第三个约束。约束编号基于 0 (0=1, 1=2, 2=3)
  • @NdFeB:因为该 asm 语句中的班次计数为 %2。使用命名约束,如asm("rol %0, %[count]", blah blah, [count] "cI" (i) ); 并使用"+r" 作为读写输入/输出操作数;没有理由强制编译器使用 EAX/RAX。无论如何,所有这些仍然比纯 C 更糟糕,因为它破坏了持续传播和其他可能的优化。
  • 在调试内联 asm 时,您应该查看编译器生成的 asm 以查看它替换到模板中的内容。例如godbolt.org 很方便:How to remove "noise" from GCC/clang assembly output?。您甚至可以执行nop # %0 %1 %2 之类的操作,以查看编译器为所有操作数选择的内容,无论您是否在模板中引用它们。有关更多指南和示例,另请参阅 stackoverflow.com/tags/inline-assembly/info

标签: c gcc assembly operands


【解决方案1】:

正如 Michael Petch 在 cmets 中所说,

唯一允许用于控制移位位数的移位指令的寄存器是 CL

他还提供了以下解决方案:

void asm_rol(int32_t* p_rolled, int8_t i) {
    __asm__ volatile
    (
        ".intel_syntax noprefix;"
        "rol %0, %1;"
        :"+a"(*p_rolled)
        :"cI"(i) :"cc"
    );
}

c 表示使用 CL 寄存器(假设您将变量 i 的类型更改为 int8_t 而不是 int16_t。Capital-Eye (I) 表示约束也可以是 0 到 32 之间的立即值。

正如 Michael Petch 和 Peter Cordes 在 cmets 中指出的那样,我的代码不起作用,因为我使用 %1 作为 rol 操作数,但正确的变量是 %2。我犯了这个错误是因为我认为只有InputOperands%# 引用。

Michael Petch 和 Peter Cordes 还提供了一些不错的文档:

(...) 机器约束可以在这里找到:gcc.gnu.org/onlinedocs/gcc/… 在 i386 信息下

没有理由使用内联 asm 进行旋转。 Best practices for circular shift (rotate) operations in C++

在调试内联 asm 时,您应该查看编译器生成的 asm 以查看它替换到模板中的内容。例如godbolt.org 很方便:How to remove "noise" from GCC/clang assembly output?。您甚至可以执行 nop # %0 %1 %2 之类的操作,以查看编译器为所有操作数选择的内容,无论您是否在模板中引用它们。有关更多指南和示例,另请参阅 stackoverflow.com/tags/inline-assembly/info

【讨论】:

  • 您努力编写和格式化 cmets,这对我来说很好。来吧,在 2 天内接受你自己的文章。
  • 您不需要将此 asm 语句设为 volatile。如果从未使用过 *p_rolled 的新值,您希望将其优化掉。或者,如果相同的值多次旋转相同的计数,则将其提升出循环。此外,"cc" clobbers 对于 i386 和 x86-64 上的内联 asm 是隐式的。如果您看到没有它的示例,那并没有错;添加它完全是可选的。
  • 你的.intel_syntax noprefix 将破坏它后面的任何编译生成的代码,除非编译器已经在使用-masm=intel 编译时发出英特尔语法代码。如果您希望它在没有-masm=intel 的情况下工作,您需要使用.att_syntax 恢复AT&T 语法。但是如果你-masm=intel 编译,它就会中断。在这种情况下,您可以使用 asm 方言替代品,例如 {%0, %1|%1, %0},使其适用于两者。但通常人们在 GNU C 内联汇编中使用 AT&T 语法。至少 Linux 内核是这样做的。在您自己的项目中,您可以做任何事情。
  • @NdFeB 如果没有输出操作数,那么 volatile 是隐式的。由于所有更改都在输入和输出操作数中捕获,并且没有其他副作用,因此可以将其删除。对额外的 volatile 进行优化可能会在不需要时强制生成代码。
  • 如果你想变得更漂亮,还有asm ("rol %[value], %[rollby]" : [value] "+a" (*p_rolled) : [rollby] "cI"(i) :"cc"); 使用这样的符号名称在这样的 1-liner asm 中可能没有太大区别。但如果事情变得更复杂,不用一直看“哪个又是 %2?”,这很好。如果您最终需要添加或删除参数,那就太好了。不得不重新编号一切是恶心。我也可能将 "+a" 更改为 "+r"。正如 Peter 所说,如果编译器有其他可以更好地工作的寄存器,则无需强制 eax。
猜你喜欢
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-08
相关资源
最近更新 更多