GCC с встроенной сборкой и быстрой сборкой дополнительного кода для операнда памяти - PullRequest
2 голосов
/ 11 июля 2019

Я ввожу адрес индекса в таблицу в расширенной операции встроенной сборки, но GCC выдает дополнительную инструкцию lea, когда в ней нет необходимости, даже при использовании -Ofast -fomit-frame-pointer или -Os -f....GCC использует RIP-относительные адреса.

Я создавал функцию для преобразования двух последовательных битов в маску XMM, состоящую из двух частей (1 маска четырех слов на бит).Для этого я использую _mm_cvtepi8_epi64 (внутренне vpmovsxbq) с операндом памяти из 8-байтовой таблицы с битами в качестве индекса.

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

Я могу напрямую встроить операцию памяти в шаблон ASM, но это всегда будет вызывать относительную RIP-адресацию (и я не люблю навязываться в обходные пути).

typedef uint64_t xmm2q __attribute__ ((vector_size (16)));

// Used for converting 2 consecutive bits (as index) into a 2-elem XMM mask (pmovsxbq)
static const uint16_t MASK_TABLE[4] = { 0x0000, 0x0080, 0x8000, 0x8080 };

xmm2q mask2b(uint64_t mask) {
    assert(mask < 4);
    #ifdef USE_ASM
        xmm2q result;
        asm("vpmovsxbq %1, %0" : "=x" (result) : "m" (MASK_TABLE[mask]));
        return result;
    #else
        // bad cast (UB?), but input should be `uint16_t*` anyways
        return (xmm2q) _mm_cvtepi8_epi64(*((__m128i*) &MASK_TABLE[mask]));
    #endif
}

Выходная сборка с -SUSE_ASM и без):

__Z6mask2by:                            ## @_Z6mask2by
        .cfi_startproc
## %bb.0:
        leaq    __ZL10MASK_TABLE(%rip), %rax
        vpmovsxbq       (%rax,%rdi,2), %xmm0
        retq
        .cfi_endproc

Что я ожидал (я удалил все лишние вещи):

__Z6mask2by:
        vpmovsxbq __ZL10MASK_TABLE(%rip,%rdi,2), %xmm0
        retq

1 Ответ

1 голос
/ 11 июля 2019

Единственный относительный к RIP режим адресации - RIP + rel32. RIP + reg недоступен.

(В машинном коде 32-битный код имел 2 избыточных способа кодирования [disp32]. X86-64 использует более короткую (без SIB) форму как относительную RIP, более длинную форму SIB как [sign_extended_disp32]).


Если вы компилируете для Linux с -fno-pie -no-pie, GCC сможет получить доступ к статическим данным с 32-битным абсолютным адресом, поэтому он может использовать режим, такой как __ZL10MASK_TABLE(,%rdi,2). Это невозможно для MacOS, где базовый адрес всегда выше 2 ^ 32; 32-разрядная абсолютная адресация полностью не поддерживается в x86-64 MacOS.

В исполняемом файле PIE (или PIC-коде в целом, например, в библиотеке) вам требуется REA-относительный LEA для настройки индексации статического массива. Или любой другой случай, когда статический адрес не умещается в 32 бита и / или не является постоянной времени соединения.


Intrinsics

Да, встроенные функции делают очень неудобным выражение нагрузки pmovzx/sx из узкого источника, поскольку версии встроенных указателей-источников отсутствуют.

*((__m128i*) &MASK_TABLE[mask] небезопасно: если вы отключите оптимизацию, вы вполне можете получить movdqa 16-байтовую загрузку, но адрес будет смещен. Это безопасно только тогда, когда компилятор складывает загрузку в операнд памяти для pmovzxbq, который имеет 2-байтовый операнд памяти, поэтому не требует выравнивания.

На самом деле текущий GCC действительно компилирует ваш код с movdqa 16-байтовой загрузкой, как movdqa xmm0, XMMWORD PTR [rax+rdi*2] перед reg-reg pmovzx. Это явно пропущенная оптимизация. :( clang / LLVM (который MacOS устанавливает как gcc) действительно складывает нагрузку в pmovzx.

Безопасный способ - _mm_cvtepi8_epi64( _mm_cvtsi32_si128(MASK_TABLE[mask]) ) или что-то еще, и затем надеется, что компилятор оптимизирует нулевое расширение от 2 до 4 байтов и сгибает movd в нагрузку, когда вы включаете оптимизацию. Или, может быть, попробуйте _mm_loadu_si32 для 32-битной загрузки, даже если вы действительно хотите 16. Но в прошлый раз, когда я попытался, компиляторы засосали сложение встроенной 64-битной нагрузки в операнд памяти, например, для pmovzxbw. GCC и clang все еще терпят неудачу в этом, но ICC19 преуспевает. https://godbolt.org/z/IdgoKV

Я уже писал об этом раньше:


Ваше целое число -> векторная стратегия

Ваш выбор pmovsx кажется странным. Вам не нужно расширение знака, поэтому я бы выбрал pmovzx (_mm_cvt_epu8_epi64). На самом деле это не более эффективно на любых процессорах.

Здесь справочная таблица работает только с небольшим количеством статических данных. Если бы ваш диапазон маски был больше, вы, возможно, захотите посмотреть на есть ли обратная инструкция к инструкции movemask в intel avx2? для альтернативных стратегий, таких как широковещательная + AND + (сдвиг или сравнение).

Если вы делаете это часто, лучше всего использовать целую строку кэша из 4x 16-байтовых векторных констант, поэтому вам не нужна инструкция pmovzx, просто индексируйте в выровненную таблицу xmm2 или __m128i векторы, которые могут быть источником памяти для любой другой инструкции SSE. Используйте alignas(64), чтобы получить все константы в одной строке кэша.

Вы также можете рассмотреть (встроенные для) pdep + movd xmm0, eax + pmovzxbq reg-reg, если вы ориентируетесь на процессоры Intel с BMI2. (pdep медленно на AMD, однако).

...