C++ 跨平台组装((x64 | | x86)和(Microsoft x64 | | SystemV))

C++ 跨平台组装((x64 | | x86)和(Microsoft x64 | | SystemV)),c++,gcc,assembly,cross-platform,abi,C++,Gcc,Assembly,Cross Platform,Abi,我正试图编写一些代码,以便了解更多关于汇编和JIT编译器之类的知识。到目前为止,我已经能够提出一个XOR函数,从理论上讲,它可以在Windows和Linux环境中的x86或x64机器上工作 假设我理解正确,则[RE]AX寄存器用于保存整数返回值,而[RE]DX是用于在函数之间传递整数的可用寄存器之一。我选择不严格遵循ABI,而是使用[RE]AX传递第一个参数,因为它保存MOV指令而不影响结果 有没有更好(更优雅或更高效)的方法来发布跨平台组装,或者我在开发这个程序集时有没有犯过任何错误 #inc

我正试图编写一些代码,以便了解更多关于汇编和JIT编译器之类的知识。到目前为止,我已经能够提出一个XOR函数,从理论上讲,它可以在Windows和Linux环境中的x86或x64机器上工作

假设我理解正确,则
[RE]AX
寄存器用于保存整数返回值,而
[RE]DX
是用于在函数之间传递整数的可用寄存器之一。我选择不严格遵循ABI,而是使用
[RE]AX
传递第一个参数,因为它保存
MOV
指令而不影响结果

有没有更好(更优雅或更高效)的方法来发布跨平台组装,或者我在开发这个程序集时有没有犯过任何错误

#include <cstdint>
#include <iostream>

template<typename TInput>
static auto Xor(TInput const highPart, TInput const lowPart) {
    constexpr bool is16Bit = (std::is_same<TInput, int16_t>::value || std::is_same<TInput, uint16_t>::value);
    constexpr bool is32Bit = (std::is_same<TInput, int32_t>::value || std::is_same<TInput, uint32_t>::value);
    static_assert(is16Bit || is32Bit, "type must be a member of the type family: [u]int{16, 32}_t");

    if constexpr (is16Bit) {
        uint16_t result;

        #if (defined(__linux__) || defined(__unix__) || defined(_WIN32))
            asm volatile ("xorw %%dx, %%ax;" : "=a" (result) : "a" (highPart), "d" (lowPart));
        #else
            #error "Unsupported platform detected."
        #endif

        return result;
    }
    else if constexpr (is32Bit) {
        uint32_t result;

        #if (defined(__linux__) || defined(__unix__) || defined(_WIN32))
            asm volatile ("xorl %%edx, %%eax;" : "=a" (result) : "a" (highPart), "d" (lowPart));
        #else
            #error "Unsupported platform detected."
        #endif

        return result;
    }
}

#define HIGH_PART 4;
#define LOW_PART 8;

int main() {
    int16_t const a = HIGH_PART;
    int16_t const b = LOW_PART;
    int16_t const c = Xor(a, b);

    uint32_t const x = HIGH_PART;
    uint32_t const y = LOW_PART;
    uint32_t const z = Xor(x, y);

    std::cout << c << "\n";
    std::cout << z << "\n";
    getchar();

    return 0;
}

不能让编译器以64位模式在EAX/RAX中传递函数arg。在32位模式下,您可以使用gcc“regparm”调用约定,如
\uuuuu属性(regparm(3)))int my_func(int,int)
以按该顺序传递EAX、ECX、EDX中的参数。(因此,在EAX中具有函数arg的内联asm之前,编译器将需要一个
mov

或者,您可以使用
\uuuuu属性((sysv\u abi))
声明函数,以便始终使用sysv abi,即使在Windows上编译时也是如此。但这只有在所有调用方都由GCC/clang/ICC而不是MSVC编译时才有效。在32位模式下更糟糕;i386 System V调用约定是垃圾:在堆栈上传递所有参数,并且在edx:eax中只返回int64_t,而不是在两个成员的64位结构中返回

调用
sysv_abi
函数也可能是
ms_abi
函数来保存/恢复所有xmm6..15,除非sysv_abi函数调用可以内联并优化。因此,总的来说,如果函数没有大量使用XMM regs并保存/恢复其中的大部分,那么这可能是一个糟糕的计划


使用固定寄存器输入/输出约束通常没有用处,除非您使用的指令是隐式寄存器(如不能使用BMI2
shlx
/
shrx
),则使用
cl
中的移位计数)

让编译器使用
“r”
“+r”
约束进行寄存器分配。(或
“=r”
“0”
匹配约束)这样,无论值在哪里,函数都可以高效内联。对于可以是寄存器或32位立即数的输入,也可以使用“re”。或者甚至
“rem”
,用于也可以是内存的输入。但是,如果您重复使用输入,最好在asm之前让编译器为您加载它

另见

硬编码寄存器分配部分地破坏了使用内联asm而不是编译器必须调用而不是内联的独立asm函数的目的

查看编译器为您的代码生成的asm,查看它生成的周围代码,以及它如何通过选择操作数填充模板


还请注意,
“r”
为16位类型选择16位寄存器,为32位类型选择32位寄存器,因此所有这些类型大小调整基本上都是不必要的。(尽管取决于输入的写入方式,但使用32位异或可能优于16位异或,如果以后有人读取完整的32位或64位寄存器,可能会避免部分寄存器暂停。但是如果输入寄存器是使用16位操作数大小写入的,那么在P6系列CPU上,32位异或将创建部分寄存器暂停。)您可以使用32位大小的
“%k0”
替代为
“xor%0”模板替换填写的大小。请参阅。

int16\u t c=a^b和gcc。我的意思是,我不确定你在问什么:a)在处理算术的原始机器代码中,win/linux之间没有区别,这是在ABI和服务调用中(这里不相关)b)不清楚64b是从哪里开始的,以及如何进行的,到目前为止在Q.c)中没有使用
xorw
根据当前的CPU模式进行不同的编码(16b模式与32b/64b模式),我认为没有一种明智的方法可以同时满足这两种需求……在尝试同时满足32和64目标平台时,会出现类似的问题,那么您的目标是什么?@Ped7g使用寄存器的机器没有区别,但如果我希望生成的程序“与他人友好相处”那么我应该坚持目标平台的ABI,不是吗?这里的目标是最终发出机器代码,而不是使用GCC编译XOR表达式。我的潜在目标列在标题中:x64、x86、Linux和Windows。@Ped7g我正在获得知识和理解,正如第一句话所述;我不想打败现代编译器(然而!),我只是想体验一下它们处理的所有不同的复杂性。另外,我不确定如果是16b,如果是32b……为什么不简单地定义两个具有适当参数类型的函数,让编译器根据使用的类型选择正确的函数……在现代x86世界中,这通常更有意义(从性能角度看)即使使用16b的值,也要执行
xorl
,这取决于它们的源代码以及如何将它们读入寄存器,以及在
xor
之后计算如何继续……但在现代x86中,每个操作编译的整个概念毫无意义,这将带来整个解决方案的解释器级性能。对不起,我退出了从这里,我不明白你的问题……也许其他人会更好地理解你。:)使用与我的本地计算机相同的设置进行了测试,它似乎生成了我要求它生成的内容。只是好奇它是如何工作的,因为你的第一句话让我相信它不应该…@Kittoes0124:我说的是函数args。内联后,你没有使用任何函数args,只是为mov编译时间常数-立即。所以你看到的就是eff
template<typename T>
static auto Xor(T const highPart, T const lowPart) {
    constexpr bool is16Bit = (std::is_same<T, int16_t>::value || std::is_same<T, uint16_t>::value);
    constexpr bool is32Bit = (std::is_same<T, int32_t>::value || std::is_same<T, uint32_t>::value);
    static_assert(is16Bit || is32Bit, "type must be a member of the type family: [u]int{16, 32}_t");

    #if !(defined(__linux__) || defined(__unix__) || defined(_WIN32))
        #error "Unsupported platform detected."
    #endif

    T result;

    if constexpr (is16Bit) {
        asm volatile ("xorw %%dx, %%ax;" : "=a" (result) : "a" (highPart), "d" (lowPart));
    }
    else if constexpr (is32Bit) {
        asm volatile ("xorl %%edx, %%eax;" : "=a" (result) : "a" (highPart), "d" (lowPart));
    }

    return result;
}