[インデックス 12168] ファイルの概要
このコミットは、Go言語のAMD64 (x86-64) アセンブラ (6a
) とリンカ (6l
) に、3オペランド形式のIMUL
命令(整数乗算命令)であるIMUL3Q
と、SHLDL
命令(Shift Left Double Length)のサポートを追加するものです。特にIMUL3Q
は、Intelの定義するIMUL
のバリアントとは異なる形で、Goのツールチェイン内で明確に区別するために導入されました。
コミット
commit 36d370700957f4ec8db2d36288eb30d2552d8181
Author: Adam Langley <agl@golang.org>
Date: Thu Feb 23 10:51:04 2012 -0500
6a/6l: add IMUL3Q and SHLDL
Although Intel considers the three-argument form of IMUL to be a
variant of IMUL, I couldn't make 6l able to differentiate it without
huge changes, so I called it IMUL3.
R=rsc
CC=golang-dev
https://golang.org/cl/5686055
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/36d370700957f4ec8db2d36288eb30d2552d8181
元コミット内容
このコミットの目的は、Go言語のAMD64アーキテクチャ向けアセンブラ (6a
) とリンカ (6l
) に、新しい命令であるIMUL3Q
とSHLDL
のサポートを追加することです。
コミットメッセージでは、特にIMUL
命令の3オペランド形式について言及されています。Intelの公式ドキュメントでは、この3オペランド形式は既存のIMUL
命令のバリアントとして扱われますが、Goのリンカ6l
が既存のIMUL
とこの3オペランド形式を区別するためには、大規模な変更が必要であることが判明しました。そのため、Goのツールチェイン内では、この3オペランド形式をIMUL3
という独立した命令として扱うことにした、と説明されています。IMUL3Q
のQ
は、64ビットオペランド(Quadword)を意味します。
SHLDL
命令についても言及がありますが、提供された差分(diff)にはIMUL3Q
に関する変更のみが含まれており、SHLDL
に関するコード変更はこのコミットでは確認できません。これは、コミットメッセージがより広範な変更の意図を示しつつも、実際のコミットではIMUL3Q
の導入に焦点を当てているか、あるいはSHLDL
のサポートが別のコミットで行われたか、または単にコミットメッセージの記述が不完全である可能性を示唆しています。本解説では、差分に明示されているIMUL3Q
の変更に焦点を当てて詳細を説明します。
変更の背景
Go言語は、独自のツールチェイン(アセンブラ、リンカなど)を使用しており、特にアセンブリ言語の記述にはPlan 9スタイルのアセンブラ構文を採用しています。x86-64アーキテクチャには、IMUL
(Integer Multiply)命令の複数の形式が存在します。
一般的なIMUL
命令には、以下のような形式があります。
- 1オペランド形式:
IMUL src
-AX
(またはEAX
/RAX
) レジスタの内容とsrc
を乗算し、結果をDX:AX
(またはEDX:EAX
/RDX:RAX
) に格納します。 - 2オペランド形式:
IMUL dest, src
-dest
とsrc
を乗算し、結果をdest
に格納します。 - 3オペランド形式:
IMUL dest, src, imm
-src
と即値imm
を乗算し、結果をdest
に格納します。この形式は、乗算結果を特定のレジスタに直接格納できるため、コードの簡潔さや効率性において有用です。
このコミットが行われた2012年当時、Goのリンカ6l
は、IntelがIMUL
のバリアントと見なす3オペランド形式を、既存のIMUL
命令と区別して適切に処理することが困難でした。既存のリンカの構造を大幅に変更することなくこの命令をサポートするためには、3オペランド形式のIMUL
をIMUL3
という新しい独立した命令としてGoのアセンブラとリンカに認識させる必要がありました。これにより、Goプログラム内でこの特定の乗算命令を効率的に利用できるようになります。
前提知識の解説
Go言語のツールチェイン (6a
, 6l
)
Go言語は、C言語などとは異なり、独自のコンパイラ、アセンブラ、リンカを含むツールチェインを持っています。
6a
(Assembler): AMD64 (x86-64) アーキテクチャ向けのアセンブラです。Goのソースコードから生成されたアセンブリコード(または手書きのアセンブリコード)をオブジェクトファイルに変換します。Goのアセンブラは、Plan 9オペレーティングシステムのアセンブラに由来する独特の構文(Plan 9アセンブラ構文)を使用します。6l
(Linker): AMD64 (x86-64) アーキテクチャ向けのリンカです。6a
によって生成されたオブジェクトファイルや、Goコンパイラによって生成されたオブジェクトファイルを結合し、実行可能なバイナリを生成します。リンカは、命令のオペコード(機械語表現)を決定し、アドレスを解決する重要な役割を担います。
x86/x86-64アセンブリ言語のIMUL
命令
IMUL
は"Integer Multiply"の略で、符号付き整数乗算を行うx86/x86-64アセンブリ命令です。前述の通り、オペランドの数によって異なる形式があります。
- 2オペランド形式 (
IMUL dest, src
):dest
とsrc
の値を乗算し、結果をdest
に格納します。例えば、IMUL RAX, RBX
はRAX = RAX * RBX
となります。 - 3オペランド形式 (
IMUL dest, src, imm
):src
と即値imm
を乗算し、結果をdest
に格納します。例えば、IMUL RAX, RBX, 10
はRAX = RBX * 10
となります。この形式は、乗算結果を任意のレジスタに格納できるため、柔軟性が高いです。
GoアセンブラのPlan 9スタイル構文
Goのアセンブラは、IntelやAT&T構文とは異なるPlan 9スタイルの構文を採用しています。主な特徴は以下の通りです。
- レジスタ名:
AX
,BX
などではなく、AX
,BX
のように大文字で表記されます。 - オペランドの順序: 多くの命令で、ソースオペランドが先に、デスティネーションオペランドが後に来ます(例:
MOV $10, AX
はAX = 10
)。ただし、IMUL
のような一部の命令では、Intel構文と同様にデスティネーションが先に来ることもあります。 - 命令名のプレフィックス: アーキテクチャを示すプレフィックス(例:
MOVL
for 32-bit move,MOVQ
for 64-bit move)が付きます。このコミットのIMUL3Q
のQ
も同様に64ビットオペランドを示します。
レキサー (Lexer) とオペコードテーブルの役割
- レキサー (Lexer): アセンブラの最初の段階で、入力されたアセンブリコードの文字列を、意味のある最小単位(トークン)に分解する役割を担います。例えば、
IMUL3Q R1, R2, $10
という行は、IMUL3Q
、R1
、,
、R2
、,
、$10
といったトークンに分解されます。レキサーは、新しい命令名(この場合はIMUL3Q
)を認識できるように更新される必要があります。 - オペコードテーブル (Opcode Table): リンカやアセンブラが、特定のアセンブリ命令(例:
IMUL3Q
)を対応する機械語のバイト列(オペコード)に変換するために使用するルックアップテーブルです。このテーブルには、命令の種類、オペランドの型、命令のバイト数、および命令のエンコーディングに関する情報が含まれています。新しい命令を追加する場合、このテーブルにその命令のエントリを追加する必要があります。
技術的詳細
このコミットは、GoのAMD64ツールチェインにIMUL3Q
命令のサポートを統合するために、以下の3つのファイルに具体的な変更を加えています。
-
src/cmd/6a/lex.c
(アセンブラのレキサー):- このファイルは、アセンブリコードを解析し、命令やオペランドを識別するための語彙規則を定義しています。
IMUL3Q
という新しい命令名をレキサーが認識できるように、struct inst
の配列にエントリが追加されました。LTYPEX
は、この命令が特定のオペランドタイプを持つことを示すフラグです。AIMUL3Q
は、この命令に対応する内部的なオペコード定数です。
-
src/cmd/6l/6.out.h
(リンカのヘッダファイル):- このヘッダファイルは、リンカが使用する様々な定数、特にオペコードの列挙型(enum)を定義しています。
- 新しい命令
IMUL3Q
に対応する内部オペコード定数AIMUL3Q
がenum as
に追加されました。これにより、リンカがこの命令を識別し、処理できるようになります。
-
src/cmd/6l/optab.c
(リンカのオペコードテーブル):- このファイルは、各アセンブリ命令がどのように機械語に変換されるかを定義するオペコードテーブル(
optab
配列)を含んでいます。 yimul3
という新しいuchar
配列が定義されました。これはIMUL3Q
命令のオペランドの型とエンコーディングに関する情報を含みます。Yml
: メモリまたはレジスタオペランド。Yrl
: レジスタオペランド。Zibm_r
: 即値、ベースレジスタ、インデックスレジスタ、スケール、オフセットを含むメモリオペランド。これは3オペランドIMUL
の典型的なオペランド構成(dest, src, imm
)を反映しています。1
: オペランドの数(この場合は即値が1つ)。
- メインの
optab
配列に、AIMUL3Q
に対応するエントリが追加されました。AIMUL3Q
:6.out.h
で定義された内部オペコード定数。yimul3
: 上記で定義されたオペランド情報。Pw
: 64ビットオペランドを示すプレフィックス。0x6b
:IMUL
命令の3オペランド形式のオペコードの一部(IMUL r/m32, imm8
またはIMUL r/m64, imm8
)。
- このファイルは、各アセンブリ命令がどのように機械語に変換されるかを定義するオペコードテーブル(
これらの変更により、GoのアセンブラはIMUL3Q
という命令を認識し、リンカはそれを適切な機械語に変換できるようになります。特に、IntelのIMUL
命令の3オペランド形式が、Goのツールチェイン内でIMUL3Q
として明示的に扱われることで、既存のリンカの複雑な変更を回避しつつ、この機能が提供されることになります。
コアとなるコードの変更箇所
diff --git a/src/cmd/6a/lex.c b/src/cmd/6a/lex.c
index 1a8e5ad619..e013bec2a7 100644
--- a/src/cmd/6a/lex.c
+++ b/src/cmd/6a/lex.c
@@ -396,6 +396,7 @@ struct
"IMULB", LTYPEI, AIMULB,
"IMULL", LTYPEI, AIMULL,
"IMULQ", LTYPEI, AIMULQ,
+ "IMUL3Q", LTYPEX, AIMUL3Q,
"IMULW", LTYPEI, AIMULW,
"INB", LTYPE0, AINB,
"INL", LTYPE0, AINL,
diff --git a/src/cmd/6l/6.out.h b/src/cmd/6l/6.out.h
index 559cdc758b..8499159543 100644
--- a/src/cmd/6l/6.out.h
+++ b/src/cmd/6l/6.out.h
@@ -735,6 +735,7 @@ enum as
AMODE,
ACRC32B,
ACRC32Q,
+ AIMUL3Q,
ALAST
};
diff --git a/src/cmd/6l/optab.c b/src/cmd/6l/optab.c
index 2308e0dfea..5746ded19c 100644
--- a/src/cmd/6l/optab.c
+++ b/src/cmd/6l/optab.c
@@ -267,6 +267,11 @@ uchar yimul[] =
Yml, Yrl, Zm_r, 2,
0
};
+uchar yimul3[] =
+{
+ Yml, Yrl, Zibm_r, 1,
+ 0
+};
uchar ybyte[] =
{
Yi64, Ynone, Zbyte, 1,
@@ -772,6 +777,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 },
{ AINB, yin, Pb, 0xe4,0xec },
{ AINCB, yincb, Pb, 0xfe,(00) },
{ AINCL, yincl, Px, 0xff,(00) },
コアとなるコードの解説
src/cmd/6a/lex.c
の変更
"IMULQ", LTYPEI, AIMULQ,
+ "IMUL3Q", LTYPEX, AIMUL3Q,
"IMULW", LTYPEI, AIMULW,
この変更は、Goアセンブラのレキサーに新しい命令IMUL3Q
を認識させるためのものです。
"IMUL3Q"
: アセンブリコード内で使用される命令の文字列リテラルです。LTYPEX
: この命令が特定のオペランドタイプを持つことを示すフラグです。IMUL3Q
は3つのオペランド(デスティネーションレジスタ、ソースレジスタ/メモリ、即値)を取るため、特別な型指定が必要です。AIMUL3Q
: この命令に対応する内部的なオペコード定数です。レキサーがIMUL3Q
という文字列を読み取ると、これをAIMUL3Q
という内部表現に変換し、後続の処理(パース、コード生成)に渡します。
src/cmd/6l/6.out.h
の変更
ACRC32Q,
+ AIMUL3Q,
ALAST
};
この変更は、リンカが使用するオペコードの列挙型enum as
にAIMUL3Q
という新しい定数を追加するものです。
AIMUL3Q
:IMUL3Q
命令を一意に識別するための整数定数です。アセンブラが生成したオブジェクトファイル内でこの定数が使用され、リンカはこれを見て適切な機械語を生成します。
src/cmd/6l/optab.c
の変更
+uchar yimul3[] =
+{
+ Yml, Yrl, Zibm_r, 1,
+ 0
+};
このコードは、IMUL3Q
命令のオペランドの型とエンコーディングに関する情報を定義する新しい配列yimul3
です。
Yml
: 最初のオペランドがメモリまたはレジスタであることを示します。GoのPlan 9アセンブラ構文では、IMUL3Q
のデスティネーションオペランドはレジスタです。Yrl
: 2番目のオペランドがレジスタであることを示します。これはIMUL3Q
のソースオペランドです。Zibm_r
: 3番目のオペランドが即値(Immediate)、ベースレジスタ、インデックスレジスタ、スケール、オフセットを含むメモリオペランドの形式であることを示します。IMUL3Q
では、これは即値オペランドを指します。1
: この命令が1つの即値オペランドを持つことを示します。0
: 配列の終端マーカーです。
{ AIMULQ, yimul, Pw, 0xf7,(05),0x6b,0x69,Pm,0xaf },
{ AIMULW, yimul, Pe, 0xf7,(05),0x6b,0x69,Pm,0xaf },
+ { AIMUL3Q, yimul3, Pw, 0x6b },
{ AINB, yin, Pb, 0xe4,0xec },
この変更は、リンカのメインオペコードテーブルoptab
にIMUL3Q
命令のエントリを追加するものです。
AIMUL3Q
:6.out.h
で定義された内部オペコード定数です。yimul3
: 上記で定義された、IMUL3Q
のオペランド情報を含む配列への参照です。Pw
: 64ビットオペランド(Quadword)を示すプレフィックスバイトです。これは、命令が64ビットデータを操作することを示します。0x6b
:IMUL
命令の3オペランド形式のオペコードの一部です。具体的には、IMUL r64, r/m64, imm8
(8ビット即値)またはIMUL r64, r/m64, imm32
(32ビット即値)の命令エンコーディングに関連するバイトです。このバイトは、命令のオペランドサイズや形式を決定する上で重要です。
これらの変更により、GoのツールチェインはIMUL3Q
という新しいアセンブリ命令を完全にサポートし、Goプログラム内でこの特定の乗算命令を効率的に利用できるようになります。
関連リンク
- Go Gerrit Change-List: https://golang.org/cl/5686055
参考にした情報源リンク
- Intel 64 and IA-32 Architectures Software Developer's Manuals (IMUL命令の詳細): https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
- Go Assembly Language (Plan 9 Assembly): https://go.dev/doc/asm
- The Go Programming Language Specification (Goのツールチェインに関する情報): https://go.dev/ref/spec
- Plan 9 from Bell Labs (Plan 9アセンブラの背景): https://9p.io/plan9/