Встроенные трансляции со встроенными функциями и сборкой

В разделе 2.5.3 "Трансляции" Справочника по программированию наборов инструкций Intel по архитектуре мы узнаем, чем AVX512 (и Knights Corner) имеет

бит-поле для кодирования передачи данных для некоторых команд load-op, то есть инструкций, которые загружать данные из памяти и выполнять некоторые вычислительные или операции перемещения данных.

Например, используя синтаксис сборки Intel, мы можем транслировать скаляр по адресу, хранящемуся в rax, а затем умножая на 16 поплавков в zmm2 и записываем результат в zmm1, как этот

vmulps zmm1, zmm2, [rax] {1to16}

Однако нет никаких встроенных функций, которые могут это сделать. Поэтому, с встроенными функциями, компилятор должен иметь возможность сбросить

__m512 bb = _mm512_set1_ps(b);
__m512 ab = _mm512_mul_ps(a,bb);

для одной команды

vmulps zmm1, zmm2, [rax] {1to16}

но я не заметил, что GCC делает это. Я нашел сообщение об ошибке GCC об этом.

Я наблюдал что-то подобное с FMA с GCC. например GCC 4.9 не скроет _mm256_add_ps(_mm256_mul_ps(areg0,breg0) с одной инструкцией fma с -Ofast. Однако GCC 5.1 теперь сворачивает его на одну fma. По крайней мере, есть внутренности, чтобы сделать это с помощью FMA, например. _mm256_fmadd_ps. Но нет, например, _mm512_mulbroad_ps(vector,scalar) внутренне.

GCC может исправить это в какой-то момент, но до тех пор сборка является единственным решением.

Итак, мой вопрос заключается в том, как это сделать с встроенной сборкой в ​​GCC?

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

"vmulps        (%%rax)%{1to16}, %%zmm1, %%zmm2\n\t"

Я действительно ищу функцию, подобную этой

static inline __m512 mul_broad(__m512 a, float b) {
    return a*b;
}

если if b находится в точке памяти в rax, он производит

vmulps        (%rax){1to16}, %zmm0, %zmm0
ret

и если b находится в xmm1, он производит

vbroadcastss    %xmm1, %zmm1
vmulps          %zmm1, %zmm0, %zmm0
ret

GCC уже выполнит регистр vbroadcastss -from-register с intrinsics, но если b находится в памяти, скомпилирует его с vbroadcastss из памяти.

__m512 mul_broad(__m512 a, float b) {       
    __m512 bb = _mm512_set1_ps(b);
    __m512 ab = _mm512_mul_ps(a,bb);
    return ab;
}

clang будет использовать операнд широковещательной памяти, если b находится в памяти.

Ответ 1

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

У меня нет версии GCC, которая поддерживает регистры ZMM, поэтому в следующем примере используются регистры XMM и несколько несуществующих инструкций, чтобы продемонстрировать, как вы можете добиться того, что вы ищете.

typedef __attribute__((vector_size(16))) float v4sf;

v4sf
foo(v4sf a, float b) {
    v4sf ret;
    asm(".ifndef isxmm\n\t"
        ".altmacro\n\t"
        ".macro ifxmm operand, rnum\n\t"
        ".ifc \"\\operand\",\"%%xmm\\rnum\"\n\t"
        ".set isxmm, 1\n\t"
        ".endif\n\t"
        ".endm\n\t"
        ".endif\n\t"
        ".set isxmm, 0\n\t"
        ".set regnum, 0\n\t"
        ".rept 8\n\t"
        "ifxmm <%2>, %%regnum\n\t"
        ".set regnum, regnum + 1\n\t"
        ".endr\n\t"
        ".if isxmm\n\t"
        "alt-1 %1, %2, %0\n\t"
        ".else\n\t"
        "alt-2 %1, %2, %0\n\t"
        ".endif\n\t"
        : "=x,x" (ret)
        : "x,x" (a), "x,m" (b));
    return ret;
}


v4sf
bar(v4sf a, v4sf b) {
    return foo(a, b[0]);
}

Этот пример должен быть скомпилирован с помощью gcc -m32 -msse -O3 и должен генерировать два сообщения об ошибках ассемблера, похожие на следующие:

t103.c: Assembler messages:
t103.c:24: Error: no such instruction: `alt-2 %xmm0,4(%esp),%xmm0'
t103.c:22: Error: no such instruction: `alt-1 %xmm0,%xmm1,%xmm0'

Основная идея здесь - ассемблер проверяет, является ли второй операнд (%2) регистром XMM или чем-то еще, предположительно, местом памяти. Поскольку ассемблер GNU не поддерживает много операций в строках, второй операнд сравнивается со всеми возможными регистрами XMM по одному в цикле .rept. Макрос isxmm используется для вставки %xmm и номера регистра вместе.

Для вашей конкретной проблемы вам, вероятно, потребуется переписать ее примерно так:

__m512
mul_broad(__m512 a, float b) {
    __m512 ret;
    __m512 dummy;
    asm(".ifndef isxmm\n\t"
        ".altmacro\n\t"
        ".macro ifxmm operand, rnum\n\t"
        ".ifc \"\\operand\",\"%%zmm\\rnum\"\n\t"
        ".set isxmm, 1\n\t"
        ".endif\n\t"
        ".endm\n\t"
        ".endif\n\t"
        ".set isxmm, 0\n\t"
        ".set regnum, 0\n\t"
        ".rept 32\n\t"
        "ifxmm <%[b]>, %%regnum\n\t"
        ".set regnum, regnum + 1\n\t"
        ".endr\n\t"
        ".if isxmm\n\t"
        "vbroadcastss %x[b], %[b]\n\t"
        "vmulps %[a], %[b], %[ret]\n\t"
        ".else\n\t"
        "vmulps %[b] %{1to16%}, %[a], %[ret]\n\t"
        "# dummy = %[dummy]\n\t"
        ".endif\n\t"
        : [ret] "=x,x" (ret), [dummy] "=xm,x" (dummy)
        : [a] "x,xm" (a), [b] "m,[dummy]" (b));
    return ret;
}