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

[インデックス 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) に、新しい命令であるIMUL3QSHLDLのサポートを追加することです。

コミットメッセージでは、特にIMUL命令の3オペランド形式について言及されています。Intelの公式ドキュメントでは、この3オペランド形式は既存のIMUL命令のバリアントとして扱われますが、Goのリンカ6lが既存のIMULとこの3オペランド形式を区別するためには、大規模な変更が必要であることが判明しました。そのため、Goのツールチェイン内では、この3オペランド形式をIMUL3という独立した命令として扱うことにした、と説明されています。IMUL3QQは、64ビットオペランド(Quadword)を意味します。

SHLDL命令についても言及がありますが、提供された差分(diff)にはIMUL3Qに関する変更のみが含まれており、SHLDLに関するコード変更はこのコミットでは確認できません。これは、コミットメッセージがより広範な変更の意図を示しつつも、実際のコミットではIMUL3Qの導入に焦点を当てているか、あるいはSHLDLのサポートが別のコミットで行われたか、または単にコミットメッセージの記述が不完全である可能性を示唆しています。本解説では、差分に明示されているIMUL3Qの変更に焦点を当てて詳細を説明します。

変更の背景

Go言語は、独自のツールチェイン(アセンブラ、リンカなど)を使用しており、特にアセンブリ言語の記述にはPlan 9スタイルのアセンブラ構文を採用しています。x86-64アーキテクチャには、IMUL(Integer Multiply)命令の複数の形式が存在します。

一般的なIMUL命令には、以下のような形式があります。

  1. 1オペランド形式: IMUL src - AX (またはEAX/RAX) レジスタの内容とsrcを乗算し、結果をDX:AX (またはEDX:EAX/RDX:RAX) に格納します。
  2. 2オペランド形式: IMUL dest, src - destsrcを乗算し、結果をdestに格納します。
  3. 3オペランド形式: IMUL dest, src, imm - srcと即値immを乗算し、結果をdestに格納します。この形式は、乗算結果を特定のレジスタに直接格納できるため、コードの簡潔さや効率性において有用です。

このコミットが行われた2012年当時、Goのリンカ6lは、IntelがIMULのバリアントと見なす3オペランド形式を、既存のIMUL命令と区別して適切に処理することが困難でした。既存のリンカの構造を大幅に変更することなくこの命令をサポートするためには、3オペランド形式のIMULIMUL3という新しい独立した命令として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): destsrcの値を乗算し、結果をdestに格納します。例えば、IMUL RAX, RBXRAX = RAX * RBXとなります。
  • 3オペランド形式 (IMUL dest, src, imm): srcと即値immを乗算し、結果をdestに格納します。例えば、IMUL RAX, RBX, 10RAX = RBX * 10となります。この形式は、乗算結果を任意のレジスタに格納できるため、柔軟性が高いです。

GoアセンブラのPlan 9スタイル構文

Goのアセンブラは、IntelやAT&T構文とは異なるPlan 9スタイルの構文を採用しています。主な特徴は以下の通りです。

  • レジスタ名: AX, BXなどではなく、AX, BXのように大文字で表記されます。
  • オペランドの順序: 多くの命令で、ソースオペランドが先に、デスティネーションオペランドが後に来ます(例: MOV $10, AXAX = 10)。ただし、IMULのような一部の命令では、Intel構文と同様にデスティネーションが先に来ることもあります。
  • 命令名のプレフィックス: アーキテクチャを示すプレフィックス(例: MOVL for 32-bit move, MOVQ for 64-bit move)が付きます。このコミットのIMUL3QQも同様に64ビットオペランドを示します。

レキサー (Lexer) とオペコードテーブルの役割

  • レキサー (Lexer): アセンブラの最初の段階で、入力されたアセンブリコードの文字列を、意味のある最小単位(トークン)に分解する役割を担います。例えば、IMUL3Q R1, R2, $10という行は、IMUL3QR1,R2,$10といったトークンに分解されます。レキサーは、新しい命令名(この場合はIMUL3Q)を認識できるように更新される必要があります。
  • オペコードテーブル (Opcode Table): リンカやアセンブラが、特定のアセンブリ命令(例: IMUL3Q)を対応する機械語のバイト列(オペコード)に変換するために使用するルックアップテーブルです。このテーブルには、命令の種類、オペランドの型、命令のバイト数、および命令のエンコーディングに関する情報が含まれています。新しい命令を追加する場合、このテーブルにその命令のエントリを追加する必要があります。

技術的詳細

このコミットは、GoのAMD64ツールチェインにIMUL3Q命令のサポートを統合するために、以下の3つのファイルに具体的な変更を加えています。

  1. src/cmd/6a/lex.c (アセンブラのレキサー):

    • このファイルは、アセンブリコードを解析し、命令やオペランドを識別するための語彙規則を定義しています。
    • IMUL3Qという新しい命令名をレキサーが認識できるように、struct instの配列にエントリが追加されました。
    • LTYPEXは、この命令が特定のオペランドタイプを持つことを示すフラグです。
    • AIMUL3Qは、この命令に対応する内部的なオペコード定数です。
  2. src/cmd/6l/6.out.h (リンカのヘッダファイル):

    • このヘッダファイルは、リンカが使用する様々な定数、特にオペコードの列挙型(enum)を定義しています。
    • 新しい命令IMUL3Qに対応する内部オペコード定数AIMUL3Qenum asに追加されました。これにより、リンカがこの命令を識別し、処理できるようになります。
  3. 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 asAIMUL3Qという新しい定数を追加するものです。

  • 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 },

この変更は、リンカのメインオペコードテーブルoptabIMUL3Q命令のエントリを追加するものです。

  • 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プログラム内でこの特定の乗算命令を効率的に利用できるようになります。

関連リンク

参考にした情報源リンク