Ограничение ввода GNU C inline asm для регистров маски AVX512 (k1k7)?

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 вместо регистра общего назначения?


person tert    schedule 02.05.2019    source источник
comment
Как насчет Yk? Из здесь.   -  person David Wohlferd    schedule 02.05.2019
comment
ошибка исправлена ​​добавлением {z}.   -  person tert    schedule 03.05.2019


Ответы (2)


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


Нам нужно покопаться в источниках gcc _ 4_, чтобы найти его:

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

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


Предпочитаю встроенные функции вроде _mm512_maskz_add_epi32

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

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

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


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

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

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


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

// works with -march=skylake-avx512 or -march=knl
// or just -mavx512f but don't do that.
// also needed: -masm=intel
#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),
            [mask]  "Yk" ((__mmask16)0xAAAA)
         // no clobbers needed, unlike your question which I fixed with an edit
       );
    return sum;
}

Обратите внимание, что все { и } экранированы с помощью % (https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Special-format-strings), поэтому они не анализируются как диалектные альтернативы {AT&T | Intel-syntax}.

Он компилируется с помощью gcc уже в 4.9, но на самом деле этого не происходит, потому что он не понимает -march=skylake-avx512 и даже не имеет настроек для Skylake или KNL. Для достижения наилучших результатов используйте более свежий GCC, который знает о вашем процессоре.

<сильный> Godbolt компилятор исследователь :

# gcc8.3 -O3 -march=skylake-avx512 or -march=knl  (and -masm=intel)
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, если вы когда-либо использовали его вне встроенного asm.
  • gcc5 поддерживает -mavx512f -mavx512bw, но не знает о Skylake.
  • gcc4.9 не поддерживает -mavx512bw.

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

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

person Peter Cordes    schedule 02.05.2019
comment
Просто чтобы оставить комментарий для всех, кого это интересует. Вот наивный пример ограничения Yk и соответствующих эффектов. godbolt.org/z/B8VLPz - person tert; 02.05.2019
comment
@tert: в вашей функции была ошибка, которую я только что заметил: вы объединяете маску (не нулевую маску) в sum, но она не инициализирована, и вы использовали для нее "=v" только для вывода. Вместо этого я обновил свой ответ на нулевую маску. И укажите некоторые преимущества встроенных функций, например, разрешение компилятору использовать широковещательную загрузку в качестве операнда памяти, что, вероятно, очень трудно позволить компилятору выбирать с помощью встроенного asm. - person Peter Cordes; 02.05.2019
comment
Это правильно. {z} необходимо. Спасибо, что указали на это. - person tert; 03.05.2019
comment
Есть ли ограничение, указывающее, что целочисленный операнд НЕ может быть регистром k? У меня проблема с компилятором Clang, который помещает регистр k во встроенную инструкцию сборки, для которой требуется регистр g.p. Я не знаю, ошибка ли это в Clang или мне нужно указать тип регистра. - person A Fog; 19.06.2019

Хотя это недокументировано, посмотрите здесь видим:

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

Отредактируйте свой Godbolt так:

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

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

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

person David Wohlferd    schedule 02.05.2019
comment
Спасибо за ответ и предложение. Я полностью разочарован в этом. Но когда дело доходит до SIMD, это либо внутренняя функция, либо встроенный asm. Оба могли причинить боль. Понимание одного немного больше может помочь избежать другого. ; ) - person tert; 02.05.2019
comment
@tert: Мой совет: поймите asm-дизайн, а затем напишите встроенные функции C, которые могут компилироваться в asm, который вы хотите. Иногда вам нужно искать правильные имена для встроенных функций, если вы помните только более короткую и простую мнемонику asm, но если вы хотите взглянуть на любую серьезную настройку производительности, вы знаете, что asm - это то, что действительно важно, и прочее вроде agner.org/optimize или uops.info содержит таблицы инструкций, в которых используется мнемоника asm. А средство поиска встроенных компонентов Intel доступно для поиска по мнемонике. - person Peter Cordes; 02.05.2019
comment
@tert: Но для регистров масок AVX512 еще рано, поэтому компиляторы почти всегда копируют их в целочисленные регистры и обратно, даже если сработало бы один kshift или kor. Но Intel наконец-то добавила встроенные функции масок, которые действительно компилируются, чтобы маскировать инструкции, такие как kadd, поэтому, если вам нужно помочь компилятору улучшить asm, вы можете их использовать. Поддерживается в текущих версиях всех основных компиляторов. Отсутствуют встроенные функции AVX-512 для масок? - person Peter Cordes; 02.05.2019