Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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のツールチェインが新しい命令を正しく解釈し、機械語に変換できるようにするための複数の変更を含んでいます。

  1. src/cmd/6a/lex.c の変更:

    • アセンブラが新しい命令名(AESENC, AESENCLAST, AESDEC, AESDECLAST, AESIMC, AESKEYGENASSIST, PSHUFD)を認識できるように、字句解析器のテーブルにこれらの命令を追加しています。
    • LTYPE3LTYPEXは、これらの命令が取るオペランドの型を示す内部的な定数です。LTYPE3は特定のオペランドパターン(例: Yxm, Yxr, Zlitm_r)を示し、LTYPEXは別のパターン(例: Yxm, Yxr, Zibm_r)を示します。これらはoptab.cで定義されるオペランドのエンコーディングルールと関連しています。
  2. src/cmd/6l/6.out.h の変更:

    • リンカが使用する命令の内部定数として、AAESENCからAPSHUFDまでの新しい定数がenum asに追加されています。これにより、リンカはこれらの命令を識別できるようになります。
  3. src/cmd/6l/optab.c の変更:

    • このファイルは、各アセンブリ命令のオペコードとオペランドのエンコーディングルールを定義するOptab構造体の配列を含んでいます。
    • 新しい命令(AAESENCなど)に対応するOptabエントリが追加されています。これらのエントリは、命令のオペランドの型(yaes, yaes2)、プレフィックス(Pq, Pf3, Pf2, Pm)、実際のオペコードバイト(例: 0x38, 0xdc)、および追加のエンコーディング情報(例: (0))を指定します。
    • yaesyaes2という新しいオペランド型定義が追加されています。これらは、AES-NI命令が通常取るオペランドの組み合わせ(例: 128ビットXMMレジスタとメモリ/XMMレジスタ)を抽象化しています。
    • 既存の命令(AIMUL3Q, APEXTRW, APINSRW, APSHUFHW, APSHUFL, APSHUFLW, APSHUFW, ASHUFPD, ASHUFPS)のOptabエントリも修正されています。具体的には、これらの命令のエンコーディング情報に(00)が追加されています。これは、特定の命令のエンコーディングにおいて、追加のバイト(REXプレフィックスなど)が必要となる場合に、その情報をリンカに伝えるためのものです。Zibm_rの定義が1から2に変更されている箇所も、オペランドのエンコーディングに関する調整です。
  4. 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オペランドタイプを使用します。

LTYPE3LTYPEXは、命令が取るオペランドの数や種類、エンコーディング方法をリンカに伝えるための抽象化された型です。

src/cmd/6l/6.out.h の変更

このヘッダファイルは、Goのリンカがアセンブリ命令を識別するために使用する列挙型enum asに、新しい命令に対応する定数を追加しています。

  • AAESENC, AAESENCLAST, AAESDEC, AAESDECLAST, AAESIMC, AAESKEYGENASSIST, APSHUFD: これらの定数は、アセンブラが生成した中間表現で、対応する命令を示すために使用されます。リンカはこれらの定数を見て、optab.cで定義された情報に基づいて機械語を生成します。

src/cmd/6l/optab.c の変更

このファイルは、Goのリンカの心臓部の一つであり、各アセンブリ命令を実際の機械語バイトに変換するためのルールを定義しています。

  1. 新しいオペランド型 yaesyaes2 の追加:

    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: オペランドのバイト数またはエンコーディングの複雑さを示す値。
  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バイト以上のオペコードを持ちます。
  3. 既存命令の 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プログラム内でこれらのハードウェアアクセラレーション機能を活用するための道を開きます。

関連リンク

参考にした情報源リンク