[インデックス 13963] ファイルの概要
このコミットは、Go言語のツールチェイン、特にcmd/6a
(アセンブラ) と cmd/6l
(リンカ) に、IntelのAES-NI命令セットとPSHUFD
命令のサポートを追加するものです。これは、将来的にcrypto/aes
パッケージにおけるAES暗号化処理のパフォーマンスを向上させるための準備作業として行われました。
コミット
commit e039c405c8d587632fee6c18ddc1f2b8fe171ffd
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu Sep 27 01:53:08 2012 +0800
cmd/6a, cmd/6l: add support for AES-NI instrutions and PSHUFD
This CL adds support for the these 7 new instructions to 6a/6l in
preparation of the upcoming CL for AES-NI accelerated crypto/aes:
AESENC, AESENCLAST, AESDEC, AESDECLAST, AESIMC, AESKEYGENASSIST,
and PSHUFD.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5970055
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/e039c405c8d587632fee6c18ddc1f2b8fe171ffd
元コミット内容
このコミットは、Goのアセンブラ(cmd/6a
)とリンカ(cmd/6l
)に、以下の7つの新しい命令のサポートを追加します。
AESENC
AESENCLAST
AESDEC
AESDECLAST
AESIMC
AESKEYGENASSIST
PSHUFD
これらの命令は、crypto/aes
パッケージでAES暗号化を高速化するための準備として導入されました。
変更の背景
Go言語の標準ライブラリには、暗号化機能を提供するcrypto
パッケージが含まれています。その中でもcrypto/aes
は、Advanced Encryption Standard (AES) アルゴリズムを実装しています。しかし、ソフトウェアのみでAESを実装すると、特に大量のデータを処理する場合にパフォーマンスのボトルネックとなることがあります。
近年のIntelプロセッサには、AES暗号化・復号化処理をハードウェアレベルで高速化するための専用命令セットである「AES-NI (Advanced Encryption Standard New Instructions)」が搭載されています。これらの命令を利用することで、ソフトウェア実装に比べて格段に高いパフォーマンスと、サイドチャネル攻撃に対する耐性を得ることができます。
このコミットは、GoのツールチェインがこれらのAES-NI命令を認識し、アセンブリコードとして生成・リンクできるようにするための基盤を構築するものです。これにより、後続のコミットでcrypto/aes
パッケージがこれらのハードウェア命令を利用できるようになり、Goアプリケーションにおける暗号化処理の性能が大幅に向上することが期待されます。
また、PSHUFD
命令も追加されています。これは、SIMD (Single Instruction, Multiple Data) 命令の一部であり、データのシャッフル(並べ替え)を行う際に利用されます。暗号化処理、特にAESのようなブロック暗号では、データのビット操作や並べ替えが頻繁に行われるため、PSHUFD
のような命令のサポートもパフォーマンス向上に寄与します。
前提知識の解説
1. Go言語のツールチェイン (cmd/6a
, cmd/6l
)
cmd/6a
(アセンブラ): Go言語のソースコード(特にgo:asm
ディレクティブで指定されたアセンブリコード)を、ターゲットアーキテクチャ(この場合はx86-64、Goの命名規則では6
がx86-64を指す)の機械語に変換するツールです。アセンブリ命令の構文解析と、それに対応するオペコードの生成を担当します。cmd/6l
(リンカ): アセンブラによって生成されたオブジェクトファイルや、コンパイラによって生成されたオブジェクトファイルを結合し、実行可能なバイナリを生成するツールです。命令のオペコードだけでなく、その命令がどのようなオペランド(引数)を取るか、メモリ配置はどうなるかといった情報も扱います。
2. AES-NI (Advanced Encryption Standard New Instructions)
Intelが開発したx86命令セットの拡張で、AES暗号化アルゴリズムの主要な計算ステップをハードウェアで実行するための命令群です。これにより、ソフトウェア実装に比べて大幅な高速化と、タイミング攻撃などのサイドチャネル攻撃に対する耐性が向上します。主な命令には以下のようなものがあります。
AESENC
: AESの1ラウンドの暗号化を実行します。AESENCLAST
: AESの最終ラウンドの暗号化を実行します。AESDEC
: AESの1ラウンドの復号化を実行します。AESDECLAST
: AESの最終ラウンドの復号化を実行します。AESIMC
: AESの逆MixColumns変換を実行します。AESKEYGENASSIST
: AESのラウンドキー生成を支援します。
3. PSHUFD (Packed Shuffle Doublewords)
SSE2 (Streaming SIMD Extensions 2) 命令セットの一部で、128ビットXMMレジスタ内の4つの32ビット整数(ダブルワード)を、即値オペランドで指定されたパターンに従ってシャッフル(並べ替え)する命令です。SIMD演算において、データの並べ替えは頻繁に必要となる操作であり、この命令は効率的なデータ操作を可能にします。
4. オペコードと命令の定義
アセンブラやリンカは、各アセンブリ命令に対応する数値コード(オペコード)と、その命令がどのようなオペランド(レジスタ、メモリ、即値など)を取るか、そしてそれらがどのようにエンコードされるかを知っている必要があります。
lex.c
: アセンブラの字句解析器の一部で、アセンブリ命令の文字列を内部的なトークン(命令の種類を示す定数)にマッピングします。6.out.h
: Goのリンカが使用するヘッダファイルで、アセンブリ命令に対応する内部的な定数(例:AAESENC
)が定義されています。optab.c
: オペレーションテーブル(Operation Table)を定義するファイルで、各アセンブリ命令のオペコード、オペランドの型、エンコーディング情報などが記述されています。リンカが機械語を生成する際にこのテーブルを参照します。span.c
: リンカのコード生成の一部で、命令のオペランドの処理や、命令のバイト数を計算するロジックが含まれています。
技術的詳細
このコミットは、Goのツールチェインが新しい命令を正しく解釈し、機械語に変換できるようにするための複数の変更を含んでいます。
-
src/cmd/6a/lex.c
の変更:- アセンブラが新しい命令名(
AESENC
,AESENCLAST
,AESDEC
,AESDECLAST
,AESIMC
,AESKEYGENASSIST
,PSHUFD
)を認識できるように、字句解析器のテーブルにこれらの命令を追加しています。 LTYPE3
とLTYPEX
は、これらの命令が取るオペランドの型を示す内部的な定数です。LTYPE3
は特定のオペランドパターン(例:Yxm, Yxr, Zlitm_r
)を示し、LTYPEX
は別のパターン(例:Yxm, Yxr, Zibm_r
)を示します。これらはoptab.c
で定義されるオペランドのエンコーディングルールと関連しています。
- アセンブラが新しい命令名(
-
src/cmd/6l/6.out.h
の変更:- リンカが使用する命令の内部定数として、
AAESENC
からAPSHUFD
までの新しい定数がenum as
に追加されています。これにより、リンカはこれらの命令を識別できるようになります。
- リンカが使用する命令の内部定数として、
-
src/cmd/6l/optab.c
の変更:- このファイルは、各アセンブリ命令のオペコードとオペランドのエンコーディングルールを定義する
Optab
構造体の配列を含んでいます。 - 新しい命令(
AAESENC
など)に対応するOptab
エントリが追加されています。これらのエントリは、命令のオペランドの型(yaes
,yaes2
)、プレフィックス(Pq
,Pf3
,Pf2
,Pm
)、実際のオペコードバイト(例:0x38, 0xdc
)、および追加のエンコーディング情報(例:(0)
)を指定します。 yaes
とyaes2
という新しいオペランド型定義が追加されています。これらは、AES-NI命令が通常取るオペランドの組み合わせ(例: 128ビットXMMレジスタとメモリ/XMMレジスタ)を抽象化しています。- 既存の命令(
AIMUL3Q
,APEXTRW
,APINSRW
,APSHUFHW
,APSHUFL
,APSHUFLW
,APSHUFW
,ASHUFPD
,ASHUFPS
)のOptab
エントリも修正されています。具体的には、これらの命令のエンコーディング情報に(00)
が追加されています。これは、特定の命令のエンコーディングにおいて、追加のバイト(REXプレフィックスなど)が必要となる場合に、その情報をリンカに伝えるためのものです。Zibm_r
の定義が1
から2
に変更されている箇所も、オペランドのエンコーディングに関する調整です。
- このファイルは、各アセンブリ命令のオペコードとオペランドのエンコーディングルールを定義する
-
src/cmd/6l/span.c
の変更:Zibm_r
というオペランド型を処理するロジックが変更されています。以前は*andptr++ = op;
で単一のオペコードバイトを処理していましたが、while ((op = o->op[z++]) != 0) *andptr++ = op;
に変更され、o->op
配列から複数のオペコードバイトを読み込むようになりました。これは、一部の命令(特に新しいAES-NI命令や既存のSIMD命令)が、単一のオペコードバイトだけでなく、複数のバイトで構成されるオペコードやプレフィックスを持つ場合に、それらを正しく処理できるようにするための汎用的な改善です。
これらの変更により、Goのアセンブラとリンカは、AES-NI命令とPSHUFD命令を含むアセンブリコードを正しくコンパイルし、実行可能なバイナリに含めることができるようになります。
コアとなるコードの変更箇所
src/cmd/6a/lex.c
--- a/src/cmd/6a/lex.c
+++ b/src/cmd/6a/lex.c
@@ -1008,6 +1008,13 @@ struct
"PREFETCHT2", LTYPE2, APREFETCHT2,
"PREFETCHNTA", LTYPE2, APREFETCHNTA,
"UNDEF", LTYPE0, AUNDEF,
+ "AESENC", LTYPE3, AAESENC,
+ "AESENCLAST", LTYPE3, AAESENCLAST,
+ "AESDEC", LTYPE3, AAESDEC,
+ "AESDECLAST", LTYPE3, AAESDECLAST,
+ "AESIMC", LTYPE3, AAESIMC,
+ "AESKEYGENASSIST", LTYPEX, AAESKEYGENASSIST,
+ "PSHUFD", LTYPEX, APSHUFD,
0
};
src/cmd/6l/6.out.h
--- a/src/cmd/6l/6.out.h
+++ b/src/cmd/6l/6.out.h
@@ -748,6 +748,15 @@ enum as
AUNDEF,
+ AAESENC,
+ AAESENCLAST,
+ AAESDEC,
+ AAESDECLAST,
+ AAESIMC,
+ AAESKEYGENASSIST,
+
+ APSHUFD,
+
ALAST
};
src/cmd/6l/optab.c
--- a/src/cmd/6l/optab.c
+++ b/src/cmd/6l/optab.c
@@ -269,7 +269,7 @@ uchar yimul[] =
};
uchar yimul3[] =
{
- Yml, Yrl, Zibm_r, 1,
+ Yml, Yrl, Zibm_r, 2,
0
};
uchar ybyte[] =
@@ -518,17 +518,17 @@ uchar ymrxr[] =
};
uchar ymshuf[] =
{
- Ymm, Ymr, Zibm_r, 1,
+ Ymm, Ymr, Zibm_r, 2,
0
};
uchar yxshuf[] =
{
- Yxm, Yxr, Zibm_r, 1,
+ Yxm, Yxr, Zibm_r, 2,
0
};
uchar yextrw[] =
{
- Yxr, Yrl, Zibm_r, 1,
+ Yxr, Yrl, Zibm_r, 2,
0
};
uchar ypsdq[] =
@@ -551,6 +551,16 @@ uchar yprefetch[] =
Ym, Ynone, Zm_o, 2,
0,
};
+uchar yaes[] =
+{
+ Yxm, Yxr, Zlitm_r, 2,
+ 0
+};
+uchar yaes2[] =
+{
+ Yxm, Yxr, Zibm_r, 2,
+ 0
+};
/*
* You are doasm, holding in your hand a Prog* with p->as set to, say, ACRC32,
@@ -791,7 +801,7 @@ Optab optab[] =
{ AIMULL, yimul, Px, 0xf7,(05),0x6b,0x69,Pm,0xaf },
{ AIMULQ, yimul, Pw, 0xf7,(05),0x6b,0x69,Pm,0xaf },
{ AIMULW, yimul, Pe, 0xf7,(05),0x6b,0x69,Pm,0xaf },
- { AIMUL3Q, yimul3, Pw, 0x6b },
+ { AIMUL3Q, yimul3, Pw, 0x6b,(00) },
{ AINB, yin, Pb, 0xe4,0xec },
{ AINCB, yincb, Pb, 0xfe,(00) },
{ AINCL, yincl, Px, 0xff,(00) },
@@ -950,7 +960,7 @@ Optab optab[] =
{ APCMPGTB, ymm, Py, 0x64,Pe,0x64 },
{ APCMPGTL, ymm, Py, 0x66,Pe,0x66 },
{ APCMPGTW, ymm, Py, 0x65,Pe,0x65 },
- { APEXTRW, yextrw, Pq, 0xc5 },
+ { APEXTRW, yextrw, Pq, 0xc5,(00) },
{ APF2IL, ymfp, Px, 0x1d },
{ APF2IW, ymfp, Px, 0x1c },
{ API2FL, ymfp, Px, 0x0d },
@@ -971,7 +981,7 @@ Optab optab[] =
{ APFRSQRT, ymfp, Px, 0x97 },
{ APFSUB, ymfp, Px, 0x9a },
{ APFSUBR, ymfp, Px, 0xaa },
- { APINSRW, yextrw, Pq, 0xc4 },
+ { APINSRW, yextrw, Pq, 0xc4,(00) },
{ APMADDWL, ymm, Py, 0xf5,Pe,0xf5 },
{ APMAXSW, yxm, Pe, 0xee },
{ APMAXUB, yxm, Pe, 0xde },
@@ -993,10 +1003,10 @@ Optab optab[] =
{ APOPW, ypopl, Pe, 0x58,0x8f,(00) },
{ APOR, ymm, Py, 0xeb,Pe,0xeb },
{ APSADBW, yxm, Pq, 0xf6 },
- { APSHUFHW, yxshuf, Pf3, 0x70 },
- { APSHUFL, yxshuf, Pq, 0x70 },
- { APSHUFLW, yxshuf, Pf2, 0x70 },
- { APSHUFW, ymshuf, Pm, 0x70 },
+ { APSHUFHW, yxshuf, Pf3, 0x70,(00) },
+ { APSHUFL, yxshuf, Pq, 0x70,(00) },
+ { APSHUFLW, yxshuf, Pf2, 0x70,(00) },
+ { APSHUFW, ymshuf, Pm, 0x70,(00) },
{ APSLLO, ypsdq, Pq, 0x73,(07) },
{ APSLLL, yps, Py, 0xf2, 0x72,(06), Pe,0xf2, Pe,0x72,(06) },
{ APSLLQ, yps, Py, 0xf3, 0x73,(06), Pe,0xf3, Pe,0x73,(06) },
@@ -1101,8 +1111,8 @@ Optab optab[] =
{ ASHRL, yshl, Px, 0xd1,(05),0xc1,(05),0xd3,(05),0xd3,(05) },
{ ASHRQ, yshl, Pw, 0xd1,(05),0xc1,(05),0xd3,(05),0xd3,(05) },
{ ASHRW, yshl, Pe, 0xd1,(05),0xc1,(05),0xd3,(05),0xd3,(05) },
- { ASHUFPD, yxshuf, Pq, 0xc6 },
- { ASHUFPS, yxshuf, Pm, 0xc6 },
+ { ASHUFPD, yxshuf, Pq, 0xc6,(00) },
+ { ASHUFPS, yxshuf, Pm, 0xc6,(00) },
{ ASQRTPD, yxm, Pe, 0x51 },
{ ASQRTPS, yxm, Pm, 0x51 },
{ ASQRTSD, yxm, Pf2, 0x51 },
@@ -1296,6 +1306,15 @@ Optab optab[] =
{ AUNDEF, ynone, Px, 0x0f, 0x0b },
+ { AAESENC, yaes, Pq, 0x38,0xdc,(0) },
+ { AAESENCLAST, yaes, Pq, 0x38,0xdd,(0) },
+ { AAESDEC, yaes, Pq, 0x38,0xde,(0) },
+ { AAESDECLAST, yaes, Pq, 0x38,0xdf,(0) },
+ { AAESIMC, yaes, Pq, 0x38,0xdb,(0) },
+ { AAESKEYGENASSIST, yaes2, Pq, 0x3a,0xdf,(0) },
+
+ { APSHUFD, yaes2, Pq, 0x70,(0) },
+
{ AEND },
0
};
src/cmd/6l/span.c
--- a/src/cmd/6l/span.c
+++ b/src/cmd/6l/span.c
@@ -1247,7 +1247,8 @@ found:
break;
case Zibm_r:
- *andptr++ = op;
+ while ((op = o->op[z++]) != 0)
+ *andptr++ = op;
asmand(&p->from, &p->to);
*andptr++ = p->to.offset;
break;
コアとなるコードの解説
src/cmd/6a/lex.c
の変更
このファイルでは、アセンブラが認識する命令のリストに、新しいAES-NI命令とPSHUFD
命令が追加されています。
"AESENC"
,LTYPE3
,AAESENC
:AESENC
命令を定義し、そのオペランドタイプをLTYPE3
、内部的な命令定数をAAESENC
と関連付けています。他のAES命令も同様です。"AESKEYGENASSIST"
,LTYPEX
,AAESKEYGENASSIST
:AESKEYGENASSIST
命令はLTYPEX
という異なるオペランドタイプを使用します。"PSHUFD"
,LTYPEX
,APSHUFD
:PSHUFD
命令もLTYPEX
オペランドタイプを使用します。
LTYPE3
とLTYPEX
は、命令が取るオペランドの数や種類、エンコーディング方法をリンカに伝えるための抽象化された型です。
src/cmd/6l/6.out.h
の変更
このヘッダファイルは、Goのリンカがアセンブリ命令を識別するために使用する列挙型enum as
に、新しい命令に対応する定数を追加しています。
AAESENC
,AAESENCLAST
,AAESDEC
,AAESDECLAST
,AAESIMC
,AAESKEYGENASSIST
,APSHUFD
: これらの定数は、アセンブラが生成した中間表現で、対応する命令を示すために使用されます。リンカはこれらの定数を見て、optab.c
で定義された情報に基づいて機械語を生成します。
src/cmd/6l/optab.c
の変更
このファイルは、Goのリンカの心臓部の一つであり、各アセンブリ命令を実際の機械語バイトに変換するためのルールを定義しています。
-
新しいオペランド型
yaes
とyaes2
の追加:uchar yaes[] = { Yxm, Yxr, Zlitm_r, 2, 0 }; uchar yaes2[] = { Yxm, Yxr, Zibm_r, 2, 0 };
これらは、AES-NI命令や
PSHUFD
命令が通常取るオペランドのパターンを定義しています。Yxm
: XMMレジスタまたはメモリオペランド。Yxr
: XMMレジスタオペランド。Zlitm_r
/Zibm_r
: オペランドのエンコーディングに関する情報。Zlitm_r
はリテラル(即値)を含むメモリ/レジスタオペランド、Zibm_r
は即値を含むバイトオペランドを示唆します。2
: オペランドのバイト数またはエンコーディングの複雑さを示す値。
-
新しい命令の
Optab
エントリの追加:{ AAESENC, yaes, Pq, 0x38,0xdc,(0) }, { AAESENCLAST, yaes, Pq, 0x38,0xdd,(0) }, // ... (他のAES命令) { AAESKEYGENASSIST, yaes2, Pq, 0x3a,0xdf,(0) }, { APSHUFD, yaes2, Pq, 0x70,(0) },
これらの行は、各命令(例:
AAESENC
)が、どのオペランド型(yaes
)、どのプレフィックス(Pq
)、どのオペコードバイト(0x38, 0xdc
)、そして追加のエンコーディング情報((0)
)を持つかをリンカに伝えます。Pq
: 64ビットオペランドサイズプレフィックス(REX.W)や、VEXプレフィックスの一部など、特定の命令に必要なプレフィックスを示します。0x38, 0xdc
: これらは実際の機械語オペコードのバイト列です。AES-NI命令は通常、2バイト以上のオペコードを持ちます。
-
既存命令の
Optab
エントリの修正:AIMUL3Q
,APEXTRW
,APINSRW
,APSHUFHW
などの既存のSIMD命令のエントリに(00)
が追加されています。これは、これらの命令のエンコーディングが、特定のプレフィックスや追加のバイトを必要とする場合に、リンカがそれを正しく処理できるようにするための調整です。また、yimul3
,ymshuf
,yxshuf
,yextrw
などのオペランド型定義で、Zibm_r
の後の数値が1
から2
に変更されています。これは、オペランドのエンコーディングに関する詳細な調整であり、リンカがより正確な機械語を生成するために必要です。
src/cmd/6l/span.c
の変更
このファイルでは、リンカが命令のバイト数を計算し、最終的なバイナリに配置する際のロジックが変更されています。
case Zibm_r:
のブロックが変更されています。
以前は- *andptr++ = op; + while ((op = o->op[z++]) != 0) + *andptr++ = op;
op
という単一のバイトをandptr
に追加していましたが、変更後はo->op
配列から0
(終端マーカー)に遭遇するまで複数のバイトを読み込むようになりました。これは、optab.c
で定義された命令のオペコードが、単一のバイトではなく、複数のバイト(例:0x38, 0xdc
)で構成される場合に、それらすべてを正しく処理できるようにするための汎用的な改善です。これにより、リンカはより複雑な命令エンコーディングに対応できるようになります。
これらの変更が連携することで、GoのツールチェインはAES-NI命令とPSHUFD命令を完全にサポートし、Goプログラム内でこれらのハードウェアアクセラレーション機能を活用するための道を開きます。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Goのコードレビューシステム (Gerrit): https://go.googlesource.com/go/+/refs/heads/master/CONTRIBUTING.md
- このコミットのGerritレビューページ: https://golang.org/cl/5970055
参考にした情報源リンク
- Intel® Advanced Encryption Standard New Instructions (AES-NI): https://www.intel.com/content/www/us/en/developer/articles/technical/intel-advanced-encryption-standard-aes-ni-instructions.html
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html (特にVol. 2A/2B/2C: Instruction Set Reference, A-Z)
- Go Assembly Language (Goのアセンブリ言語に関する公式ドキュメント): https://go.dev/doc/asm
- Goの
crypto/aes
パッケージ: https://pkg.go.dev/crypto/aes - SIMD (Single Instruction, Multiple Data) について: https://ja.wikipedia.org/wiki/SIMD
- Goのツールチェインの内部構造に関する一般的な情報源 (例: "Go internals" などのキーワードで検索すると、関連するブログ記事やプレゼンテーションが見つかることがあります)
- Goの
cmd/6a
とcmd/6l
のソースコード (Goリポジトリ内)I have provided the detailed explanation as requested.