Go 1.11は、x86プラットフォームのアセンブラを大幅に更新しました。
プログラマはAVX-512を使用できます。これはIntelプロセッサで利用可能な最新の命令です。
カットの下:
-
cmd/asm
最も重要な更新(go tool asm
) - Goアセンブラーで新しい命令セットが導入された方法
- 新しいEVEXプレフィックス指示とアクセシビリティ機能の使用
- ツールチェーン統合レベル(現在の制限を回避するためのレシピ)
新機能
目に見えるものからプログラマーまで:
- AVX-512
-新しいベクトルレジスタ: X16-X31
、 Y16-Y31
、およびZ0-Z31
-マスクレジスタを追加: K0-K7
-EVEXプレフィックスの特別な機能(以下を参照:丸め、ゼロ化、...)。
-数百の新しい命令(379個の新しいオペコード+ EVEXプレフィックス付きAVX {1,2}命令)。
エラーメッセージ( CL108515 )を改善するための予備作業も行われていますが、これはgo1.11リリースには含まれません。
新しい拡張機能を追加するという事実に加えて、新しいアセンブラではすべてのVEXおよびEVEXテーブルが自動的に生成されることが重要です。
Goには、新しい命令を手動で追加する必要のないx86アセンブラがあります。
Goアセンブラーのエンコーダー
マシンコードの生成を担当するアセンブラの部分は、標準パッケージcmd / internal / obj / x86にあります。
その中のコードのほとんどは、C から翻訳されたplan9のx86アセンブラーソースです。
アセンブラーテーブルは、概念的には3つの次元(X、Y、Z)で構成されます。
特定の命令は、 encode(X, Y, Z)
として生成されます。
代替のメンタルモデルはtable[X][Y][Z]
かもしれませんが、実装の詳細にはそれほど近づきません。
アセンブルされた命令に対応optab
オブジェクトは、オペコードスペース(次元X)から選択されます。 次に、使用可能なオペランドの組み合わせのリスト(ディメンションY)を反復処理し、命令の引数に対応するytab
オブジェクトを選択します。 最後のステップは、コード生成スキームを選択することです:Zケース。
YおよびZプレフィックスを持つコード内の定数を見つけるのは簡単ですが、Xプレフィックスを持つものは何もありません。
最初はA、B、Cのプレフィックスでしたが、BとCはYとZに名前が変更され、オペコードはプレフィックスAのままだったという仮説があります。
また、面白いのは、A定数のタイプがobj.As
、これはasm
( obj.As
オペコード)の略であるか、単に複数のA
意味する場合があることですA
以前は、Go x86アセンブラーの命令は次のスキームに従って手動で追加されていました。
- aenum.goに新しい定数を追加します 。
- x86 グローバルアセンブラテーブルに
optab
を追加します 。 - 必要な
ytab
リストの選択または追加。 - 新しい命令にend2endテストを追加します。
必要なすべてのA、Y、Z定数が既にある場合、エンコーダーテーブル自体とテストを生成します。
このプロセスは、命令に関する情報を読み取ることができるソースを持っている場合、それらのエンコーディング、許可されたオペランドのタイプなどが十分に自動化されています。
幸いにも、このようなソースがあります。
x86avxgenおよびIntel XED
VEXおよびEVEXプレフィックスを使用するすべての命令を生成するために、 x86avxgenユーティリティが作成されました。 このプログラムは、 ytab
に対して同じoptab
およびytab
オブジェクトを生成します。
プログラムの入力はXed datafilesで 、Goからxeddataパッケージを使用してアクセスできます。
コード生成の利点は、 AVXシリーズから新しい命令を実装するために、 x86avxgen
を再起動してx86avxgen
を追加するだけです。
テスト生成は、 Intel XEDエンコーダーを使用して自動化されます(XEDは主にライブラリーです)。
EVEXにはオペコード用の空きスペースが多く、拡張の可能性があるため、新しい指示が必ず表示されます。
近い将来、 ISA-extensionsドキュメントを使用して覗くことができます。
構文
コードジェネレーターテーブル自体に加えて、パーサーが更新されました。
x86
レジスタリストとオペコードサフィックスを使用できます。
レジスタリストは、 VP4DPWSSD
などのマルチソース命令に使用されます。
マニュアルでは、 +n
表記を使用しています 。
VP4DPWSSD zmm1{k1}{z}, zmm2+3, m128
この場合、 +3
は、2番目のzmm
オペランドが4要素のレジスタの範囲を記述することを意味します(マニュアルでは、これらの範囲は「レジスタブロック」と呼ばれます)。
GoアセンブラのZ0+3
の範囲は次のようになります。
VP4DPWSSD Z25, [Z0-Z3], (AX)
[Z0-Z1]
、 [Z3-Z0]
、 [AX-DX]
などの範囲を使用するのは誤りです
組み立て段階。
接尾辞は、AVX-512の特別な機能を有効にするために使用されます。
たとえば、 VADDPD
命令の新しい形式の1つを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
「なぜ正確に?」のような質問の場合 #22779 go:AVX512 designと答えることができます。
また、そこに提供されているgolang-devへのリンクに従うことをお勧めします。
GNUアセンブラーとの比較
オペランドの順序は、GNUアセンブラーと同じです。
CMP
命令でオペランドの「奇妙な」順序を見つけた人は、ニュースを待っています。
AVX命令の場合、これらの特別な規則は適用されません(良くも悪くも、自分で決めてください)。
特徴 | GNUアセンブラー | アセンブラーに行く |
---|---|---|
マスキング | VPORD %ZMM0, %ZMM1, %ZMM2{%K2}
{k}
常にdstオペランドにあります | VPODR Z0, Z1, K2, Z2
{k}
常にdstオペランドの前 |
放送放送 | VPORD (%RDX){1to16}, %ZMM1, %ZMM2
メモリ引数の 1toN
| VPORD.BCST (DX), Z1, Z2
BCST
サフィックス |
ゼロ調整 | VPORD %ZMM0, %ZMM1, %ZMM2{z}
dstオペランドへの {z}
引数 | VPORD.Z Z0, Z1, Z2
Z
サフィックス |
丸め | VSQRTPD {ru-sae}, %ZMM0, %ZMM1
特別な第一引数 | VSQRTPD.RU_SAE Z0, Z1
接尾辞 |
さえ | VUCOMISD {sae}, %XMM0, %XMM1
丸めに似ています | VUCOMISD.SAE X0, X1
丸めに似ています |
マルチソース | V4FMADDPS (%RCX), %ZMM4, %ZMM1
最初のレジスタが示されます | V4FMADDPS (CX), [Z4-Z7], Z1
明示的な範囲表示 |
両方のアセンブラーは、VEXスキームとEVEXスキームの両方を使用できる命令をアセンブルするときにVEXを使用します。 つまり、 VADDPD X1, X2, X3
にはVEXプレフィックスが付きます。
オペランドの次元にあいまいさがある場合、Goアセンブラでは、オペコードは追加のサイズサフィックスを受け取ります。
VCVTSS2USIL (AX), DX // VCVTSS2USI (%RAX), %EDX VCVTSS2USIQ (AX), DX // VCVTSS2USI (%RAX), %RDX
Intel構文でメモリオペランドの幅を指定できる場合、GNUおよびGoアセンブラはX
およびY
サイズのサフィックスを使用します。
VCVTTPD2DQX (AX), X0 // VCVTTPD2DQ XMM0, XMMWORD PTR [RAX] VCVTTPD2DQY (AX), X0 // VCVTTPD2DQ XMM0, YMMWORD PTR [RAX]
サイズの接尾辞が付いた命令の完全なリストは、 ドキュメントにあります 。
AVX-512の分解
CL113315は、主にobj/x86
パーサーとコードジェネレーターに影響を与えるAVX-512サポートをgo tool asm
に追加しますが、 .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
Goオブジェクトファイルでobjdump
を使用すると失敗します。
$ objdump -D avx.o objdump: avx.o: File format not recognized
ただし、実行可能ファイルでは使用できます。
アセンブラコードがメインパッケージに含まれている場合、システム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はいくつかの便利なユーティリティも提供します。
コマンドライン経由でエンコーダー/デコーダーを使用します。
$ 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ファイル自体は、Intelマニュアル(PDF)からの変換に基づいてx86specユーティリティによって作成されました。
次のステップは、 x86.csv
テーブルからx86.csv
を作成することです。これにより、デコーダーのテーブルを再生成できます。
AVX-512アプリケーション
Goの世界でAVX-512の主要なユーザーの1人はminioです。
1.11以前は、 asm2plan9sユーティリティを使用する必要がありました 。
たとえば、 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
新しい拡張機能に慣れるために、AVX1およびAVX2( Z
レジスタなし)で既におなじみの命令を使用してみてください。 したがって、マスクのマージ/ゼロ化などの新しい機能を、まったく新しい「機能」の領域に陥るリスクなしに試すことができます。
最も重要なことは、最終的な結論を出す前に測定することです。 同時に、関数自体とアプリケーション全体のパフォーマンスの両方を確認します。
また、 golang.org/wiki/AVX-512-support-in-Go-assemblerをチェックアウトすることをお勧めします。
より詳細には、AVX-512の効果的な使用のトピックについては、別の記事で説明します。