Хабрахабр

Go 1.11: AVX-512 со вкусом Go

11 значительно обновлён ассемблер под платформу x86. В Go 1.

У программистов появится возможность использовать AVX-512 — новейшие инструкции, доступные в процессорах Intel.

Под катом:

  • Самые значительные обновления в cmd/asm (go tool asm)
  • Как был внедрён новый набор инструкций в Go ассемблер
  • Использование новых инструкций и специальных возможностей EVEX префикса
  • Уровень интеграции в тулчейн (рецепты обхождения текущих ограничений)

Из видимого для программиста:

ниже: rounding, zeroing, ...).
— Сотни новых инструкций (379 новых опкодов + AVX инструкции с EVEX префиксом). — Новые векторные регистры: X16-X31, Y16-Y31 и Z0-Z31
— Добавлены регистры масок: K0-K7
— Особые возможности EVEX префикса (см.

  • Добавлено 110 недостающих legacy инструкций (CL97235).
  • Почти на 25% более быстрое ассемблирование (CL108895). Ускоряет сборку примерно на 1.5%.

11 это не попадёт. Была также проведена предварительная работа по улучшению сообщений об ошибках (CL108515), но в релиз go1.

Кроме самого факта добавления новых расширений, важно то, что в новом ассемблере все VEX и EVEX таблицы сгенерированы автоматически.

Теперь в Go такой x86 ассемблер, в который не нужно добавлять новые инструкции вручную.

Часть ассемблера, ответственная за генерацию машинного кода, находится в стандартном пакете cmd/internal/obj/x86.

Большая часть кода в нём — это транслированные из C исходники x86 ассемблера из plan9.

Таблицы ассемблера концептуально состоят из 3-х измерений: X, Y и Z.
Конкретная инструкция генерируется как encode(X, Y, Z).
Альтернативной ментальной моделью может являться table[X][Y][Z], но она менее близка к деталям реализации.

Затем перебирается список доступных комбинаций операндов (измерение Y) и выбирается соответствующий аргументам инструкции объект ytab. Из пространства опкодов (измерение X) выбирается соответствующий ассемблируемой инструкции объект optab. Последним шагом является выбор схемы кодогенерации: Z-case.

В коде легко найти константы, которые имеют Y и Z префиксы, но ничего с префиксом X там нет.

Забавное примечание

Есть гипотеза, что изначально это были A, B и C префиксы, затем B и C переименовали в Y и Z, а опкоды так и остались с префиксом A.

As, что может быть сокращением от asm (assembler opcode), либо просто означать множественное число A. Что также забавно, тип A-констант — obj.

Ранее инструкции в Go x86 ассемблер добавлялись вручную, по следующей схеме:

  1. Добавление новой константы в aenum.go.
  2. Добавление optab в глобальную таблицу ассемблера x86.
  3. Подбор или добавление нужного ytab списка.
  4. Добавление end2end тестов для новой инструкции.

Если у нас уже есть все нужные A, Y и Z константы, остаётся генерировать сами таблицы encoder'а и тесты.

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

Данная программа генерирует те самые optab и ytab объекты для ассемблера. Для генерации всех инструкций, которые используют VEX и EVEX префиксы, была написана утилита x86avxgen.

Входными данными для программы являются XED datafiles, работать с которыми из Go можно с помощью пакета xeddata.

Преимущество кодогенерации в том, что для реализации новых инструкций из серии AVX будет достаточно перезапустить x86avxgen и добавить тестов.
Генерация тестов также автоматизирована с помощью Intel XED encoder'а (XED — это в первую очередь библиотека).

EVEX имеет большое свободное пространство для опкодов и потенциал для расширений, так что новые инструкции точно появятся.
В ближайшее будущее можно подглядывать с помощью документа ISA-extensions.

Кроме самих таблиц кодогенератора был обновлён парсер.
Теперь для x86 можно использовать списки регистров и суффиксы опкодов.

Списки регистров используются для multi-source инструкций, таких как VP4DPWSSD.
В мануале используется нотация +n:

VP4DPWSSD zmm1{k1}{z}, zmm2+3, m128

В данном случае +3 означает, что второй zmm операнд описывает диапазон регистров из 4 элементов (в мануале эти диапазоны именуются "register block").

Диапазон для Z0+3 в Go ассемблере будет выглядеть следующим образом:

VP4DPWSSD Z25, [Z0-Z3], (AX)

Использование диапазонов типа [Z0-Z1], [Z3-Z0], [AX-DX] является ошибкой
этапа ассемблирования.

Суффиксы используются для активации особых возможностей AVX-512.
Например, возьмём одну из новых форм инструкции VADDPD:

VADDPD zmm1 {k1}{z}, zmm2, zmm3/m512/m64bcst{er}

Сейчас мы разберём, что означает вся эта магия из {k1}, {z}, m64bcst и {er}.

Обратите внимание: порядок операндов полностью обратен Intel синтаксису.
Точно так же, как и в GNU ассемблере (AT&T синтаксис).

// Без суффиксов, "обычный" VADDPD.
VADDPD (AX), Z30, Z10 // {k1} - merging с использованием маски из K аргумента.
VADDPD (AX), Z30, K5, Z10 // Для всех инструкций ниже можно использовать формы с явным K регистром,
// что будет активировать селективный merging или zeroing. // {z} - возможен zeroing-mask (без этого суффикса будет merging-mask).
VADDPD.Z (AX), Z30, Z10 // m64bcst - возможность использовать embedded broadcasting.
// Обозначение "bcst" позаимствовано у Microsoft ассемблера (MASM).
VADDPD.BCST (AX), Z30, Z10 // {er} - доступен embedded rounding. Не совместим с memory операндами.
// Округление всегда включает SAE (см. ниже), поэтому суффикс выглядит как составной.
VADDPD.RU_SAE Z0, Z30, Z10 // Округление к +Inf
VADDPD.RD_SAE Z0, Z30, Z10 // Округление к -Inf
VADDPD.RZ_SAE Z0, Z30, Z10 // Округление к нулю
VADDPD.RN_SAE Z0, Z30, Z10 // Округление в "ближайшую сторону"

Что более интересно, суффикс Z, если инструкция его поддерживает, может использоваться совместно с другими суффиксами:

// SAE - доступен "surpress all exceptions".
// В мануале обозначается меткой {sae}.
VMAXPD.SAE.Z Z3, Z2, Z1

На вопросы вроде "А почему именно так?" может ответить go#22779: AVX512 design.
Рекомендуется также пройтись по приводимой там ссылке на golang-dev.

Порядок операндов идентичен GNU ассемблеру.

Тех, кто застал "странный" порядок операндов в CMP инструкциях, ждёт новость:
для AVX инструкций эти особые правила не действуют (хорошо это или плохо — решайте сами).

Feature

GNU ассемблер

Go ассемблер

Masking

VPORD %ZMM0, %ZMM1, %ZMM2{%K2}
{k} всегда у dst операнда

VPODR Z0, Z1, K2, Z2
{k} всегда перед dst операндом

Broadcasting

VPORD (%RDX){1to16}, %ZMM1, %ZMM2
1toN у аргумента-памяти

VPORD.BCST (DX), Z1, Z2
BCST суффикс

Zeroing

VPORD %ZMM0, %ZMM1, %ZMM2{z}
{z} аргумент у dst операнда

VPORD.Z Z0, Z1, Z2
Z суффикс

Rounding

VSQRTPD {ru-sae}, %ZMM0, %ZMM1
Особый первый аргумент

VSQRTPD.RU_SAE Z0, Z1
Суффикс

SAE

VUCOMISD {sae}, %XMM0, %XMM1
Аналогично rounding

VUCOMISD.SAE X0, X1
Аналогично rounding

Multi-source

V4FMADDPS (%RCX), %ZMM4, %ZMM1
Указывается первый регистр

V4FMADDPS (CX), [Z4-Z7], Z1
Явное указание диапазона

Иными словами, VADDPD X1, X2, X3 будет иметь VEX префикс. Оба ассемблера при сборке инструкций, где возможно применить и VEX, и EVEX схемы, используют VEX.

В случаях, когда есть неоднозначность размерности операнда, в Go ассемблере опкоды получают дополнительные size-суффиксы:

VCVTSS2USIL (AX), DX // VCVTSS2USI (%RAX), %EDX
VCVTSS2USIQ (AX), DX // VCVTSS2USI (%RAX), %RDX

Там, где в Intel синтаксисе можно указать ширину memory операнда, в GNU и Go ассемблерах используются X и Y size-суффиксы:

VCVTTPD2DQX (AX), X0 // VCVTTPD2DQ XMM0, XMMWORD PTR [RAX]
VCVTTPD2DQY (AX), X0 // VCVTTPD2DQ XMM0, YMMWORD PTR [RAX]

Полный список инструкций с size-суффиксами можно найти в документации.

CL113315 добавляет поддержку AVX-512 в go tool asm, в основном затрагивая парсер и кодогенератор obj/x86, но что произойдёт, если вы соберёте .s файл и попробуете исследовать его с помощью go tool objdump?

// Файл avx.s
TEXT avxCheck(SB), 0, $0 VPOR X0, X1, X2 // AVX1 VPOR Y0, Y1, Y2 // AVX2 VPORD.BCST (DX), Z1, K2, Z2 // AVX-512 RET

Вы увидите не то, что ожидаете:

$ go tool asm avx.s
$ go tool objdump avx.o TEXT avxCheck(SB) gofile..$GOROOT/avx.s avx.s:2 0xb7 c5f1ebd0 JMP 0x8b avx.s:3 0xbb c5f5ebd0 JMP 0x8f avx.s:4 0xbf 62 ? avx.s:4 0xc0 f1 ICEBP avx.s:4 0xc1 755a JNE 0x11d avx.s:4 0xc3 eb12 JMP 0xd7 avx.s:5 0xc5 c3 RET

Использовать objdump на объектных файлах Go не получится:

$ objdump -D avx.o objdump: avx.o: File format not recognized

Но его можно использовать на исполняемых файлах.
Если ассемблерный код включён в main пакет, системный objdump справится с задачей.

Более простым способом получить машинный код является передача -S аргумента:

$ go tool asm -S avx.s avxCheck STEXT nosplit size=15 args=0xffffffff80000000 locals=0x0 0x0000 00000 (avx.s:1) TEXT avxCheck(SB), NOSPLIT, $0 0x0000 00000 (avx.s:2) VPOR X0, X1, X2 0x0004 00004 (avx.s:3) VPOR Y0, Y1, Y2 0x0008 00008 (avx.s:4) VPORD.BCST (DX), Z1, K2, Z2 0x000e 00014 (avx.s:5) RET 0x0000 c5 f1 eb d0 c5 f5 eb d0 62 f1 75 5a eb 12 c3 ........b.uZ...
go.info.avxCheck SDWARFINFO size=34 0x0000 02 61 76 78 43 68 65 63 6b 00 00 00 00 00 00 00 .avxCheck....... 0x0010 00 00 00 00 00 00 00 00 00 00 01 9c 00 00 00 00 ................ 0x0020 01 00

Интересующие нас октеты: c5 f1 eb d0 c5 f5 eb d0 62 f1 75 5a eb 12 c3.
Скопируем их и будем делать реверс через системный objdump:

# 1. Перегнать текстовое представление в бинарное с помощью xxd.
# 2. Запустить objdump в binary режиме.
# Примечание: для Intel синтаксиса можно вместо "i386" использовать "i386:intel".
$ echo 'c5 f1 eb d0 c5 f5 eb d0 62 f1 75 5a eb 12 c3' | xxd -r -p > shellcode.bin && objdump -b binary -m i386 -D shellcode.bin Disassembly of section .data: 00000000 <.data>: 0: c5 f1 eb d0 vpor %xmm0,%xmm1,%xmm2 4: c5 f5 eb d0 vpor %ymm0,%ymm1,%ymm2 8: 62 f1 75 5a eb 12 vpord (%edx){1to16},%zmm1,%zmm2{%k2} e: c3 ret

Дизассемблирование с помощью XED

XED также предоставляет несколько полезных утилит, одна из которых позволяет
использовать encoder/decoder через командную строку.

$ echo 'c5 f1 eb d0 c5 f5 eb d0 62 f1 75 5a eb 12 c3' > data.txt && xed -64 -A -ih data.txt && rm data.txt 00 LOGICAL AVX C5F1EBD0 vpor %xmm0, %xmm1, %xmm2
04 LOGICAL AVX2 C5F5EBD0 vpor %ymm0, %ymm1, %ymm2
08 LOGICAL AVX512EVEX 62F1755AEB12 vpordl (%rdx){1to16}, %zmm1, %zmm2{%k2}
0e RET BASE C3 retq

Флаг -A выбирает AT&T синтаксис, -64 выбирает 64-битный режим.

Утилита xed-ex4 показывает детальную информацию об инструкции:

$ xed-ex4 -64 C5 F1 EB D0 PARSING BYTES: c5 f1 eb d0 VPOR VPOR_XMMdq_XMMdq_XMMdq EASZ:3, EOSZ:2, HAS_MODRM:1, LZCNT, MAP:1, MAX_BYTES:4, MOD:3, MODE:2, MODRM_BYTE:208, NOMINAL_OPCODE:235, OUTREG:XMM0, P4, POS_MODRM:3, POS_NOMINAL_OPCODE:2, REG:2, REG0:XMM2, REG1:XMM1, REG2:XMM0, SMODE:2, TZCNT, VEXDEST210:6, VEXDEST3, VEXVALID:1, VEX_PREFIX:1
0 REG0/W/DQ/EXPLICIT/NT_LOOKUP_FN/XMM_R
1 REG1/R/DQ/EXPLICIT/NT_LOOKUP_FN/XMM_N
2 REG2/R/DQ/EXPLICIT/NT_LOOKUP_FN/XMM_B
YDIS: vpor xmm2, xmm1, xmm0
ATT syntax: vpor %xmm0, %xmm1, %xmm2
INTEL syntax: vpor xmm2, xmm1, xmm0

go tool objdump основан на x86.csv, который не содержит многих новых инструкций и имеет неточности.

Сам csv файл создан утилитой x86spec на основе конвертации из Intel мануала (PDF).
Следующим шагом может быть создание x86.csv из таблиц XED'а, что позволит повторно сгенерировать таблицы для decoder'а.

11 им приходилось использовать утилиту asm2plan9s. Одним из крупных пользователей AVX-512 в мире Go является minio.
До 1.

Вот, например, их результаты для sha256:

Processor SIMD Speed (MB/s)
3.0 GHz Intel Xeon Platinum 8124M AVX512 3498
1.2 GHz ARM Cortex-A53 ARM64 638
3.0 GHz Intel Xeon Platinum 8124M AVX2 449
3.1 GHz Intel Core i7 AVX 362
3.1 GHz Intel Core i7 SSE 299

Таким образом вы сможете эксперементировать с новыми возможностями, типа merging/zeroing масок, без риска попасть в полностью новое пространство "особенностей". Для того, чтобы начать знакомиться с новым расширением, вы можете попробовать использовать уже знакомые вам по AVX1 и AVX2 инструкции (без Z регистров).

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

Рекомендую также ознакомиться с golang.org/wiki/AVX-512-support-in-Go-assembler.

Более детально тема эффективного использования AVX-512 будет затронута в отдельной статье.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть