当前位置: 首页 > 知识库问答 >
问题:

MSVC内联ASM到GCC

水昊阳
2023-03-14

我正在尝试同时处理MSVC和GCC编译器,同时更新这个代码库以在GCC上工作。但我不确定GCCs内联ASM是如何工作的。现在我并不擅长将ASM转换为C,否则我就会使用C而不是ASM。

SLONG Div16(signed long a, signed long b)
{
    signed long v;
#ifdef __GNUC__ // GCC doesnt work.
__asm() {
#else // MSVC
__asm {
#endif
        mov edx, a
        mov ebx, b          
        mov eax, edx           
        shl eax, 16          
        sar edx, 16            
        idiv ebx              
        mov v, eax              
    }
    return v;
}

signed long ROR13(signed long val)
{
    _asm{ 
        ror val, 13
    }
}

我假设ROR13的工作方式类似于(val<<13)(val>>(32-13)),但代码不会产生相同的输出。

什么是将这个内联ASM翻译成GCC的正确方法,或者这个代码的C翻译是什么?

共有1个答案

容修贤
2023-03-14

GCC使用与MSVC完全不同的语法进行内联组装,因此维护这两种表单需要相当多的工作。这也不是个特别好的主意。内联装配有很多问题。人们经常使用它,因为他们认为它会使他们的代码运行得更快,但它通常有相反的效果。除非您是汇编语言和编译器代码生成策略的专家,否则最好让编译器的优化器生成代码。

但是,当您尝试这样做时,您必须在这里稍微小心一点:带符号的右移位是在C中实现定义的,所以如果您关心可移植性,您需要将值转换为等效的无符号类型:

#include <limits.h>   // for CHAR_BIT

signed long ROR13(signed long val)
{
    return ((unsigned long)val >> 13) |
           ((unsigned long)val << ((sizeof(val) * CHAR_BIT) - 13));
}

(另请参见C++中循环移位(旋转)操作的最佳实践)。

signed long Div16(signed long a, signed long b)
{
    return ((long long)a << 16) / b;
}

在可以进行本机64位除法的64位架构上,(假设long仍然是像Windows上一样的32位类型)这将转换为:

movsxd  rax, a   # sign-extend from 32 to 64, if long wasn't already 64-bit
shl     rax, 16
cqo              # sign-extend rax into rdx:rax
movsxd  rcx, b
idiv    rcx      # or  idiv b  if the inputs were already 64-bit
ret

不幸的是,在32位x86上,代码并没有那么好。编译器会向内部库函数发出一个调用,该函数提供扩展的64位除法,因为编译器无法证明使用单个64B/32B=>32Bidiv指令不会出错。(如果商数不适合eax,则会引发#de异常,而不是截断)

换句话说,转换:

int32_t Divide(int64_t a, int32_t b)
{
    return (a / b);
}
mov   eax, a_low
mov   edx, a_high
idiv  b                 # will fault if a/b is outside [-2^32, 2^32-1]
ret

所以,你会变得丑陋。看起来是这样的:

signed long Div16(signed long a, signed long b)
{
#ifdef __GNUC__     // A GNU-style compiler (e.g., GCC, Clang, etc.)
    signed long quotient;
    signed long remainder;  // (unused, but necessary to signal clobbering)
    __asm__("idivl  %[divisor]"
           :          "=a"  (quotient),
                      "=d"  (remainder)
           :           "0"  ((unsigned long)a << 16),
                       "1"  (a >> 16),
             [divisor] "rm" (b)
           : 
           );
    return quotient;
#elif _MSC_VER      // A Microsoft-style compiler (i.e., MSVC)
    __asm
    {
        mov  eax, DWORD PTR [a]
        mov  edx, eax
        shl  eax, 16
        sar  edx, 16
        idiv DWORD PTR [b]
        // leave result in EAX, where it will be returned
    }
#else
    #error "Unsupported compiler"
#endif
}

这将在Microsoft和GNU风格的编译器上产生所需的输出。

嗯,多半是。由于某种原因,当您使用rm约束时,编译器可以自由地选择是将除数作为内存操作数处理还是将其加载到寄存器中,Clang生成的目标代码比仅使用r(强制将其加载到寄存器中)更差。这不会影响GCC或ICC。如果您关心Clang的输出质量,您可能只想使用r,因为这将在所有编译器上提供同样好的目标代码。

(注意:GCC在输出中使用sal助记符,而不是shl助记符。这是相同的指令--差异只对右移位有影响--而且所有正常的汇编程序员都使用shl。我不知道为什么GCC发出sal,但您可以将其精神转换为shl。)

 类似资料:
  • 谢谢你的帮助。

  • 我正在尝试通过与分支内联的am调用c中的外部函数。我正在编译为arm m0指令集,但它返回错误表达式。 代码是: 回报是: 我们需要做什么?

  • 在尝试让一些旧代码重新工作时(https://github.com/chaos4ever/chaos/blob/master/libraries/system/system_calls.h#l387,FWIW),我发现的一些语义似乎在最近的10-15年中发生了非常微妙但仍然危险的变化...:p 该代码在的旧版本(如2.95)中可以很好地工作。总之,这里是代码: 上面代码的问题是(在我的例子中为4.

  • 有没有人要对我说或评论? 跟随生成的程序集 多亏了弄臣的解决之道:

  • 我的理解是,当编写gcc样式的内联asm时,您必须非常具体和准确地了解所有的输入和输出参数(和clobbers),这样编译器就会确切地知道如何为代码分配寄存器,以及它可以对那些寄存器的值和asm代码可能读取和/或修改的任何内存假设什么。编译器使用这些信息尽可能地优化周围的代码(如果它认为内联asm对任何东西都没有影响,甚至完全删除它)。对此不够具体可能会导致不正确的行为,因为编译器是根据您的不正确

  • 尝试使用gcc:https://github.com/wolf9466/cpuminer-multi/blob/master/cryptonight_aesni.c编译此源文件时遇到此错误 “CRYPTONIGT_AESNI.c:162:4:错误:操作数约束不一致”