Кроссплатформенная сборка ((x64 || x86) && (Microsoft x64 || SystemV)) - PullRequest
0 голосов
/ 25 февраля 2019

Я пытаюсь написать некоторый код, чтобы узнать больше о ассемблере и таких вещах, как JIT-компиляторы.До сих пор мне удавалось придумать функцию XOR, которая теоретически должна работать на машинах x86 или x64 в средах Windows и Linux.

Если предположить, что я все правильно понял, [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, мы можем сделать вещи более общими.

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

1 Ответ

0 голосов
/ 25 февраля 2019

Вы не можете заставить компилятор передавать функцию arg в EAX / RAX в 64-битном режиме.В 32-битном режиме вы можете использовать соглашение о вызовах gcc "regparm", например __attribute__((regparm(3))) int my_func(int,int);, для передачи аргументов в EAX, ECX, EDX в этом порядке.(Таким образом, компилятору потребуется mov перед встроенным asm, который имеет функцию arg в EAX).

Или вы можете объявить свои функции с помощью __attribute__((sysv_abi)), чтобы всегда использовать SysV ABI, даже при компиляции в Windows,Но это работает только в том случае, если все абоненты скомпилированы GCC / clang / ICC, а не MSVC.И это хуже в 32-битном режиме;Соглашение о вызовах i386 System V - это дерьмо: передача всех аргументов в стеке, и только ed64_t возвращается в edx: eax, а не в 64-разрядных структурах с двумя членами.

Вызов функции sysv_abi также может бытьms_abi функция для сохранения / восстановления всего xmm6..15, если только вызов функции sysv_abi не может быть встроен и оптимизирован.Таким образом, в целом это, вероятно, плохой план, если функция еще не интенсивно использовала регистры XMM и сохранила / восстановила большинство из них.


Использование фиксированных ограничений ввода / вывода регистров не являетсяобычно полезно, если вы не используете инструкции с неявными регистрами (например, счетчик сдвигов в cl, если вы не можете использовать BMI2 shlx / shrx).

Пусть компилятор зарегистрируетсяраспределение с использованием ограничений "r" и "+r".(Или "=r" и "0" соответствия ограничениям), чтобы ваша функция могла эффективно работать, независимо от того, где находятся значения.Также используйте "re" для входов, которые могут быть регистровыми или 32-битными.Или даже "rem" для входов, которые также могут быть памятью.Но если вы используете входные данные несколько раз, может быть лучше, чтобы компилятор загрузил их для вас перед ассемблером.

См. Также https://stackoverflow.com/tags/inline-assembly/info

Жесткое кодирование распределения регистров частично нарушаетцель использования встроенного asm вместо автономных функций asm, которые компилятор должен вызывать вместо встраивания.

Посмотрите на сгенерированный компилятором asm для вашего кода, чтобы увидеть, какой окружающий код он сгенерировал, и как он заполнилв шаблоне, выбрав операнды.

Также обратите внимание, что "r" выбирает 16-битные регистры для 16-битных типов и 32-битные регистры для 32-битных типов, так что все эти вещи по размеру типов в основномненужным.(Хотя в зависимости от того, как были записаны ваши входные данные, использование 32-битного xor может быть лучше, чем 16-битного xor, возможно, следует избегать частичных остановок регистров, если что-то позже прочитает полные 32 или 64-битные регистры. Но если ваши входные регистры былизаписанный с размером 16-битного операнда, тогда на процессорах семейства P6 32-битный xor создаст частичную регистрацию.) Вы можете переопределить размер, заполненный для замены шаблона "xor %0", с помощью "%k0" для 32размер бита и т. д. См. x86 Модификаторы операнда в руководстве по GCC .

...