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

В разделе 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;
}

где если b в памяти указывают на в rax это производит

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

и если b в xmm1 это производит

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

GCC уже сделает vbroadcastss -из регистрационного случая с внутренностями, но если 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;
}
Другие вопросы по тегам