GNU C встроенное ограничение ввода asm для регистров маски AVX512 (k1 ... k7)? - PullRequest
6 голосов
/ 02 мая 2019

AVX512 представил функцию opmask для своих арифметических команд.Простой пример: godbolt.org .

#include <immintrin.h>
__m512i add(__m512i a, __m512i b) {
    __m512i sum;
    asm(
        "mov ebx, 0xAAAAAAAA;                                   \n\t"
        "kmovw k1, ebx;                                         \n\t"
        "vpaddd %[SUM] %{k1%}%{z%}, %[A], %[B];  # conditional add   "
        :   [SUM]   "=v"(sum)
        :   [A]     "v" (a),
            [B]     "v" (b)
        : "ebx", "k1"  // clobbers
       );
    return sum;
}

-march=skylake-avx512 -masm=intel -O3

 mov ebx,0xaaaaaaaa
 kmovw k1,ebx
 vpaddd zmm0{k1}{z},zmm0,zmm1

Проблема в том, что нужно указать k1.

Существует ли ограничение ввода, например "r" для целых чисел, за исключением того, что вместо регистра общего назначения выбирается регистр k?

Ответы [ 2 ]

6 голосов
/ 02 мая 2019

__mmask16 буквально является typedef для unsigned short (и других типов маски для других простых целочисленных типов), поэтому нам просто нужно ограничение для передачи его в регистр k.


Нам нужно найти источники gcc config/i386/constraints.md, чтобы найти его:

Ограничение для любого регистра маски - "k". Или используйте "Yk" для k1..k7 (который может использоваться в качестве предиката, в отличие от k0). Вы бы использовали операнд "=k" в качестве пункта назначения для сравнения в маске, например.

Очевидно, что вы можете использовать "=Yk"(tmp) с __mmask16 tmp, чтобы компилятор выполнил для вас выделение регистров, вместо того, чтобы просто объявлять клобберы на тех "k" регистрах, которые вы решили использовать.


Прежде всего, https://gcc.gnu.org/wiki/DontUseInlineAsm, если вы можете избежать этого. Понимание asm великолепно, но используйте его, чтобы прочитать выходные данные компилятора и / или выяснить, что было бы оптимальным, а затем написать встроенные функции, которые можно скомпилировать так, как вы хотите. Информация о настройке производительности, такая как https://agner.org/optimize/ и https://uops.info/, перечисляет вещи по мнемосхемам asm, и они короче / проще для запоминания, чем встроенные, но вы можете выполнить поиск по мнемоникам, чтобы найти встроенные в https://software.intel.com/sites/landingpage/IntrinsicsGuide/

Intrinsics также позволит компилятору сворачивать загрузки в операнды источника памяти для других инструкций; с AVX512 они могут даже транслироваться! Ваш встроенный ассм заставляет компилятор использовать отдельную инструкцию загрузки. Даже вход "vm" не позволит компилятору выбрать широковещательную загрузку в качестве источника памяти , потому что он не будет знать ширину широковещательного элемента инструкции, с которой вы его использовали.

Используйте _mm512_mask_add_epi32 или _mm512_maskz_add_epi32, особенно если вы уже используете __m512i типов из <immintrin.h>.


Кроме того, в вашей ассемблере есть ошибка: вы используете {k1} маскирование слиянием, а не {k1}{z} маскирование нуля , но вы использовали неинициализированный __m512i sum; с выходом "=v" ограничение как место слияния! Как автономная функция, она сливается с a, потому что соглашение о вызовах имеет ZMM0 = первый вход = регистр возвращаемого значения. Но когда вы включаете другие функции, вы определенно не можете предполагать, что sum выберет тот же регистр, что и a. Лучше всего использовать операнд для чтения / записи для "+v"(a), а использовать его в качестве пункта назначения и первого источника.

Маскирование слиянием имеет смысл только с операндом "+v" для чтения / записи. (Или в операторе asm с несколькими инструкциями, когда вы уже написали вывод один раз и хотите объединить другой результат в это.)

Intrinsics помешает вам совершить эту ошибку; версия с маскированием слиянием имеет дополнительный вход для цели слияния. (Операнд назначения asm).


Пример использования "Yk"

// works with -march=skylake-avx512 or -march=knl
// or just -mavx512f but don't do that.
#include <immintrin.h>
__m512i add_zmask(__m512i a, __m512i b) {
    __m512i sum;
    asm(
        "vpaddd %[SUM] %{%[mask]%}%{z%}, %[A], %[B];  # conditional add   "
        :   [SUM]   "=v"(sum)
        :   [A]     "v" (a),
            [B]     "v" (b),
         // no clobbers needed, unlike your question which I fixed with an edit
       );
    return sum;
}

компилируется с gcc еще в 4.9, но на самом деле этого не происходит, потому что он не понимает -march=skylake-avx512 или даже не имеет настроек для настройки Skylake или KNL.

Проводник компилятора Godbolt :

# gcc8.3 -O3 -march=skylake-avx512 or -march=knl
add(long long __vector, long long __vector):
        mov     eax, -21846
        kmovw   k1, eax         # compiler-generated
       # inline asm starts
        vpaddd zmm0 {k1}{z}, zmm0, zmm1;  # conditional add   
       # inline asm ends
        ret

-mavx512bw (подразумевается -march=skylake-avx512, но не knl) требуется для "Yk" для работы с int. Если вы компилируете с -march=knl, целочисленные литералы нуждаются в приведении к __mmask16 или __mask8, потому что unsigned int = __mask32 недоступно для масок.

[mask] "Yk" (0xAAAA) требует AVX512BW, даже если константа помещается в 16 бит, просто потому, что голые целочисленные литералы всегда имеют тип int. (vpaddd zmm имеет 16 элементов на вектор, поэтому я сократил вашу константу до 16-битной.) С AVX512BW вы можете передавать более широкие константы или опускать приведение для узких.

  • gcc6 и новее поддерживают -march=skylake-avx512. Используйте это, чтобы настроить и включить все настройки. Предпочтительно gcc8 или хотя бы gcc7. Новые компиляторы генерируют менее громоздкий код с новыми расширениями ISA, такими как AVX512, если вы когда-либо используете его вне встроенного ассемблера.
  • gcc5 поддерживает -mavx512f -mavx512bw, но не знает о Skylake.
  • gcc4.9 не поддерживает -mavx512bw.

"Yk", к сожалению, еще не задокументировано в https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html.

Я знал, где искать в источнике GCC благодаря ответу Росса на В встроенном ассемблере GNU C, каковы модификаторы для xmm / ymm / zmm для одного операнда?

6 голосов
/ 02 мая 2019

Пока он недокументирован, глядя здесь мы видим:

(define_register_constraint "Yk" "TARGET_AVX512F? MASK_REGS: NO_REGS" "@internal Любой регистр маски, который можно использоватькак предикат, то есть k1-k7. ")

Редактирование вашего годового рычага на это:

asm(
"vpaddd %[SUM] %{%[k]}, %[A], %[B]" 
: [SUM] "=v"(sum) 
: [A] "v" (a), [B] "v" (b), [k] "Yk" (0xaaaaaaaa) );

, кажется, производит правильный вывод.

Это сказало,Я обычно пытаюсь отговорить людей от использования встроенного ассемблера (и недокументированных функций).Вы можете использовать _mm512_mask_add_epi32?

...