簡體   English   中英

跨平台組裝((x64 || x86)&&(Microsoft x64 || SystemV))

[英]Cross-platform assembly ((x64 || x86) && (Microsoft x64 || SystemV))

我試圖編寫一些代碼,以了解有關匯編和諸如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;
}

以下是如何改進的示例; 通過“提升” result變量, if defined(...)constexpr檢查之上進行了constexpr if defined(...)檢查,我們可以使事情更通用。

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;
}

您不能使編譯器以64位模式在EAX / RAX中傳遞函數arg。 在32位模式下,可以使用gcc“ regparm”調用約定,例如__attribute__((regparm(3))) int my_func(int,int); 以該順序在EAX,ECX,EDX中傳遞args。 (因此,編譯器將需要mov聯asm上運行mov ,而該內聯asm在EAX中具有arg函數)。

或者,您可以使用__attribute__((sysv_abi))聲明函數,以始終使用SysV ABI,即使在Windows上進行編譯時也是如此。 但這僅在所有調用者均由GCC / clang / ICC而不是MSVC編譯的情況下有效。 而且在32位模式下更糟。 i386 System V的調用約定是廢話:在堆棧上傳遞所有arg,並且在edx:eax中僅返回int64_t,而不是2成員64位結構。

調用sysv_abi函數可能還會使用ms_abi函數來保存/恢復所有xmm6..15,除非sysv_abi函數調用可以內聯並進行優化。 因此,總的來說,如果該函數尚未大量使用XMM規則並保存/恢復大多數XMM規則,則可能是個不好的計划。


除非使用帶隱式寄存器的指令(例如,如果不能使用BMI2 shlx / shrx ,如cl的移位計數),則使用固定的寄存器輸入/輸出約束通常不會有用。

讓編譯器通過使用"r""+r"約束來注冊分配。 (或"=r""0"匹配約束),因此無論值在哪里,您的函數都可以高效地內聯。 對於可注冊或32位立即數的輸入,也請使用"re" 甚至"rem"也可以用作內存輸入。 但是,如果您反復使用輸入,最好讓編譯器在asm之前為您加載它。

另請參閱https://stackoverflow.com/tags/inline-assembly/info

對寄存器分配進行硬編碼部分地克服了使用嵌入式匯編程序而不是編譯器必須調用而不是內聯的獨立匯編程序功能的目的。

查看您的代碼的編譯器生成的asm,以查看其生成了哪些周圍的代碼,以及如何通過選擇操作數將其填充在模板中。

另請注意, "r"為16位類型選擇16位寄存器,為32位類型選擇32位寄存器,因此基本上不需要所有這些類型大小調整工作。 (盡管取決於輸入的編寫方式,但使用32位xor可能比16位xor更好,如果以后再讀取完整的32位或64位寄存器,則可以避免部分寄存器停頓。但是,如果您使用16位操作數大小,然后在P6系列CPU上使用32位異或會創建部分寄存器停頓。)您可以用"%k0"替代"xor %0"模板替換的填充大小。 32位大小等。請參見GCC手冊中的x86操作數修飾符

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM