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

[インデックス 16761] ファイルの概要

このコミットは、Go言語のリンカであるcmd/6l (x86-64アーキテクチャ用) と cmd/8l (x86アーキテクチャ用) におけるアセンブリ命令のエンコーディングに関する最適化を導入しています。具体的には、XCHG(交換)命令において、可能な場合に1バイト形式のオペコードを使用するように変更されています。

コミット

commit aad4720b5193221c000892e73615322698170d68
Author: Russ Cox <rsc@golang.org>
Date:   Fri Jul 12 20:58:38 2013 -0400

    cmd/6l, cmd/8l: use one-byte XCHG forms when possible
    
    Pointed out by khr.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/11145044

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/aad4720b5193221c000892e73615322698170d68

元コミット内容

cmd/6l, cmd/8l: use one-byte XCHG forms when possible

このコミットは、cmd/6l(64ビット版リンカ)とcmd/8l(32ビット版リンカ)において、XCHG命令をエンコードする際に、可能な場合は1バイト形式を使用するように変更します。この変更はkhrによって指摘されたものです。

変更の背景

x86アーキテクチャには、特定のレジスタ(特にアキュムレータレジスタであるAX/EAX/RAX)と他のレジスタ間で値を交換するXCHG命令に、より短い1バイト形式のオペコードが存在します。例えば、XCHG EAX, EAX0x90という1バイトのオペコードでエンコードされ、これは一般的にNOP(No Operation)命令としても知られています。

従来のリンカは、XCHG命令をエンコードする際に、常に汎用的な2バイト以上のオペコード(例: 0x87)を使用していた可能性があります。しかし、アキュムレータレジスタが関与する特定のXCHG操作では、より短い1バイトのオペコードを使用できるため、生成されるバイナリのサイズを削減し、命令フェッチの効率を向上させる可能性があります。

このコミットは、このような最適化の機会を捉え、リンカがより効率的な命令エンコーディングを選択できるようにすることで、Goプログラムのバイナリサイズをわずかに削減し、実行時のパフォーマンスに寄与することを目的としています。khr氏による指摘が、この最適化の実装のきっかけとなりました。

前提知識の解説

x86アセンブリにおけるXCHG命令

XCHG(Exchange)命令は、x86アセンブリ言語において、2つのオペランドの値を交換するために使用される命令です。オペランドは、2つの汎用レジスタ、または1つのレジスタと1つのメモリ位置のいずれかです。

例:

  • XCHG EAX, EBX: EAXレジスタとEBXレジスタの値を交換します。
  • XCHG EAX, [ESI]: EAXレジスタとESIレジスタが指すメモリ位置の値を交換します。

XCHG命令のオペコードと0x90 (NOP)

XCHG命令は、オペランドの組み合わせによって異なるオペコード(機械語のバイト列)を持ちます。特に重要なのは、アキュムレータレジスタ(16ビットのAX、32ビットのEAX、64ビットのRAX)が関与する場合です。

  • XCHG AX, AX (16ビット): この命令は、AXレジスタ自身とAXレジスタの値を交換するため、実質的には何も操作を行いません。この命令のオペコードは**0x90**という1バイトです。
  • XCHG EAX, EAX (32ビット): 同様に、EAXレジスタ自身とEAXレジスタの値を交換し、オペコードは**0x90**です。
  • XCHG RAX, RAX (64ビット): 64ビットモードでも、RAXレジスタ自身との交換はオペコード**0x90**となります。

この0x90というオペコードは、x86アーキテクチャにおいて公式に**NOP(No Operation)命令**として指定されています。これは、CPUが何もしない命令として認識し、単に次の命令に進むことを意味します。NOPは、コードのパディング、アライメント調整、デバッグ時のプレースホルダーなど、様々な目的で利用されます。

Goのリンカ (cmd/6l, cmd/8l)

Go言語のコンパイラツールチェーンは、ソースコードを機械語に変換する際に、アセンブラとリンカを使用します。

  • cmd/6l: x86-64 (AMD64) アーキテクチャ用のリンカです。Goのソースコードから生成されたオブジェクトファイルをリンクし、実行可能なバイナリを生成します。
  • cmd/8l: x86 (IA-32) アーキテクチャ用のリンカです。同様に、32ビット環境向けのバイナリを生成します。

これらのリンカは、Goのコンパイラが生成した中間表現(アセンブリ命令の抽象的な表現)を、最終的な機械語のバイト列に変換する役割を担っています。この変換プロセスにおいて、命令のエンコーディング(どのオペコードを使用するか)を決定します。

optab.cファイル

optab.cファイルは、Goのリンカにおいて、各アセンブリ命令に対応するオペコードやオペランドの形式を定義する「命令テーブル」のような役割を果たします。リンカは、このテーブルを参照して、特定のアセンブリ命令をどのように機械語にエンコードするかを決定します。

このファイル内の定義は、命令の種類(例: AXCHGLXCHGの32ビット版)、オペランドのタイプ(例: レジスタ、メモリ)、そしてそれらに対応するオペコードのバイト列を含んでいます。

技術的詳細

このコミットの核心は、XCHG命令のエンコーディングロジックを改善し、よりコンパクトな1バイト形式のオペコード(0x90)を優先的に使用するようにすることです。

x86アーキテクチャでは、XCHG命令は汎用的な形式として0x87というオペコード(ModR/MバイトやSIBバイトを伴う場合がある)を持ちます。しかし、アキュムレータレジスタ(AX, EAX, RAX)と他の汎用レジスタとの間で交換を行う場合、より短い1バイトのオペコードが存在します。

具体的には、XCHG reg, AX/EAX/RAXまたはXCHG AX/EAX/RAX, regの形式は、0x90にレジスタを示すビットを加えた1バイトのオペコードで表現できます。例えば、XCHG EAX, ECX0x87 C8(2バイト)ですが、XCHG EAX, EBX0x93(1バイト)で表現できます(0x90 + EBXレジスタを示すビット)。

このコミットでは、リンカの命令テーブル(optab.c)に、これらの1バイト形式のXCHG命令の定義を追加しています。これにより、リンカは、アキュムレータレジスタが関与するXCHG命令をエンコードする際に、従来の汎用的な複数バイト形式(0x87)ではなく、より短い1バイト形式(0x90をベースとしたもの)を選択できるようになります。

この最適化は、生成されるGoバイナリのサイズをわずかに削減し、命令キャッシュの効率を向上させることで、プログラムの起動時間や実行速度に微細な改善をもたらす可能性があります。

コアとなるコードの変更箇所

変更は主にsrc/cmd/6l/optab.csrc/cmd/8l/optab.cの2つのファイルで行われています。

src/cmd/6l/optab.c

--- a/src/cmd/6l/optab.c
+++ b/src/cmd/6l/optab.c
@@ -247,8 +247,10 @@ uchar	yrb_mb[] =
 	Yrb,	Ymb,	Zr_m,	1,
 	0
 };
-uchar	yml_ml[] =
+uchar	yxchg[] =
 {
+	Yax,	Yrl,	Z_rp,	1,
+	Yrl,	Yax,	Zrp_,	1,
 	Yrl,	Yml,	Zr_m,	1,
 	Yml,	Yrl,	Zm_r,	1,
 	0
@@ -1174,9 +1176,9 @@ Optab optab[] =
 	{ AWAIT,	ynone,	Px, 0x9b },
 	{ AWORD,	ybyte,	Px, 2 },
 	{ AXCHGB,	yml_mb,	Pb, 0x86,0x86 },
-	{ AXCHGL,	yml_ml,	Px, 0x87,0x87 },
-	{ AXCHGQ,	yml_ml,	Pw, 0x87,0x87 },
-	{ AXCHGW,	yml_ml,	Pe, 0x87,0x87 },
+	{ AXCHGL,	yxchg,	Px, 0x90,0x90,0x87,0x87 },
+	{ AXCHGQ,	yxchg,	Pw, 0x90,0x90,0x87,0x87 },
+	{ AXCHGW,	yxchg,	Pe, 0x90,0x90,0x87,0x87 },
 	{ AXLAT,	ynone,	Px, 0xd7 },
 	{ AXORB,	yxorb,	Pb, 0x34,0x80,(06),0x30,0x32 },
 	{ AXORL,	yxorl,	Px, 0x83,(06),0x35,0x81,(06),0x31,0x33 },

src/cmd/8l/optab.c

--- a/src/cmd/8l/optab.c
+++ b/src/cmd/8l/optab.c
@@ -196,8 +196,10 @@ uchar	yml_mb[] =
 	Ymb,	Yrb,	Zm_r,	1,
 	0
 };
-uchar	yml_ml[] =
+uchar	yxchg[] =
 {
+	Yax,	Yrl,	Z_rp,	1,
+	Yrl,	Yax,	Zrp_,	1,
 	Yrl,	Yml,	Zr_m,	1,
 	Yml,	Yrl,	Zm_r,	1,
 	0
@@ -696,8 +698,8 @@ Optab optab[] =
 	{ AWAIT,	ynone,	Px, 0x9b },
 	{ AWORD,	ybyte,	Px, 2 },
 	{ AXCHGB,	yml_mb,	Pb, 0x86,0x86 },
-	{ AXCHGL,	yml_ml,	Px, 0x87,0x87 },
-	{ AXCHGW,	yml_ml,	Pe, 0x87,0x87 },
+	{ AXCHGL,	yxchg,	Px, 0x90,0x90,0x87,0x87 },
+	{ AXCHGW,	yxchg,	Pe, 0x90,0x90,0x87,0x87 },
 	{ AXLAT,	ynone,	Px, 0xd7 },
 	{ AXORB,	yxorb,	Pb, 0x34,0x80,(06),0x30,0x32 },
 	{ AXORL,	yxorl,	Px, 0x83,(06),0x35,0x81,(06),0x31,0x33 },

コアとなるコードの解説

このコミットの主要な変更点は、XCHG命令のオペランドタイプを定義する部分です。

  1. yml_mlからyxchgへの名称変更と新しいエントリの追加:

    • 以前はyml_mlという名前で定義されていたXCHG命令のオペランドタイプリストが、yxchgという新しい名前に変更されました。
    • そして、このyxchgリストの先頭に以下の2つのエントリが追加されました。
      Yax,	Yrl,	Z_rp,	1,
      Yrl,	Yax,	Zrp_,	1,
      
      • Yax: アキュムレータレジスタ(AX/EAX/RAX)を示します。
      • Yrl: 汎用レジスタ(AL/CL/DL/BLなど、またはAX/CX/DX/BXなど)を示します。
      • Z_rp: オペコードのレジスタ部分にエンコードされる形式を示します。
      • Zrp_: オペコードのレジスタ部分にエンコードされる形式を示します。
      • 1: この組み合わせが1バイトのオペコードで表現可能であることを示唆しています。

    これらの新しいエントリは、XCHG AX, reg(またはEAX, regRAX, reg)およびXCHG reg, AX(またはreg, EAXreg, RAX)のような形式のXCHG命令が、より短い1バイトのオペコードでエンコードできることをリンカに伝えます。

  2. AXCHGL, AXCHGQ, AXCHGWのオペコード定義の変更:

    • AXCHGL (32ビット XCHG)、AXCHGQ (64ビット XCHG)、AXCHGW (16ビット XCHG) の各命令の定義が変更されました。

    • 変更前:

      { AXCHGL,	yml_ml,	Px, 0x87,0x87 },
      { AXCHGQ,	yml_ml,	Pw, 0x87,0x87 },
      { AXCHGW,	yml_ml,	Pe, 0x87,0x87 },
      

      ここでは、yml_mlというオペランドタイプリストと、汎用的なXCHGオペコードである0x87が指定されていました。

    • 変更後:

      { AXCHGL,	yxchg,	Px, 0x90,0x90,0x87,0x87 },
      { AXCHGQ,	yxchg,	Pw, 0x90,0x90,0x87,0x87 },
      { AXCHGW,	yxchg,	Pe, 0x90,0x90,0x87,0x87 },
      

      新しいyxchgオペランドタイプリストが使用され、オペコードの定義が0x90,0x90,0x87,0x87に変更されました。 これは、リンカがXCHG命令をエンコードする際に、まずyxchgリストで定義された1バイト形式(0x90をベースとしたもの)を試み、それが適用できない場合にのみ、従来の汎用的な2バイト形式(0x87)を使用するという優先順位を示しています。

この変更により、Goのリンカは、アキュムレータレジスタが関与するXCHG命令に対して、より効率的でコンパクトな1バイトの機械語命令を生成できるようになり、結果として生成されるバイナリのサイズが最適化されます。

関連リンク

  • Go言語の公式ドキュメント: https://golang.org/doc/
  • Goのツールチェーンに関する情報: https://golang.org/cmd/
  • x86命令セットリファレンス (Intel/AMDの公式ドキュメントを参照することを推奨します。例: Intel® 64 and IA-32 Architectures Software Developer’s Manuals)

参考にした情報源リンク