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

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

このコミットは、Go言語のコンパイラツールチェーンの一部である cmd/6c (64-bit x86アーキテクチャ向けCコンパイラ) と cmd/8c (32-bit x86アーキテクチャ向けCコンパイラ) におけるアセンブリリスト出力の書式設定に関するバグ修正とコードクリーンアップを目的としています。具体的には、Prog (プログラム命令) の出力時に FmtLong フラグが誤って適用され、アセンブリリストに不要な "!!" (感嘆符) が多数表示される問題を解決します。また、関連する list.c ファイル間のコードの類似性を高め、差分を容易に識別できるように整理しています。

コミット

commit 60826e0be6e62ae76f5771b22894bd3dc8bade10
Author: Anthony Martin <ality@pbrane.org>
Date:   Fri Jan 18 19:00:38 2013 -0800

    cmd/6c, cmd/8c: fix print format for Prog
    
    The FmtLong flag should only be used with the %D verb
    when printing an ATEXT Prog. It was erroneously used
    for every Prog except ADATA. This caused a preponderance
    of exclamation points, "!!", in the assembly listings.
    
    I also cleaned up the code so that the list.c files look
    very similar. Now the real differences are easily spotted
    with a simple diff.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7128045

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

https://github.com/golang/go/commit/60826e0be6e62ae76f5771b22894bd3dc8bade10

元コミット内容

cmd/6c, cmd/8c: fix print format for Prog

The FmtLong flag should only be used with the %D verb
when printing an ATEXT Prog. It was erroneously used
for every Prog except ADATA. This caused a preponderance
of exclamation points, "!!", in the assembly listings.

I also cleaned up the code so that the list.c files look
very similar. Now the real differences are easily spotted
with a simple diff.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7128045

変更の背景

この変更の主な背景は、Go言語のコンパイラが生成するアセンブリリストの可読性の問題にありました。具体的には、cmd/6c および cmd/8c コンパイラがアセンブリコードを整形して出力する際に、FmtLong という書式フラグが不適切に適用されることで、本来表示されるべきではない "!!" という文字列が多数混入していました。

この "!!" は、通常、コンパイラがアドレスやオフセットの表現に問題がある、あるいは予期しない値に遭遇した場合にデバッグ目的で出力されるマーカーです。しかし、このバグでは、ATEXT (関数コードセクション) の Prog (命令) を %D (オペランドの書式指定子) で出力する際にのみ FmtLong フラグを使用すべきであるにもかかわらず、ADATA (データセクション) 以外のすべての Prog に対して誤ってこのフラグが適用されていました。結果として、正しくフォーマットされるべきアセンブリ命令のオペランドにも "!!" が付加され、アセンブリリストが非常に読みにくい状態になっていました。

このコミットは、この書式設定の誤りを修正し、アセンブリリストから不要な "!!" を排除することで、開発者が生成されたアセンブリコードをより容易にデバッグ・解析できるようにすることを目的としています。また、src/cmd/6c/list.csrc/cmd/8c/list.c のコード構造を統一することで、将来的なメンテナンスや両ファイル間の差分比較を容易にするという副次的な目的も持っています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のコンパイラツールチェーンとアセンブリに関する基本的な知識が必要です。

  • Go言語のコンパイラツールチェーン: Go言語は、go build コマンドを通じてソースコードをコンパイルし、実行可能なバイナリを生成します。このプロセスには、様々なツールが関与しており、cmd/6ccmd/8c はその一部です。

    • cmd/6c: Go言語のソースコードをx86-64 (AMD64) アーキテクチャ向けのアセンブリコードに変換するCコンパイラです。Goのコンパイラは、歴史的にC言語で書かれた部分が多く、その名残として c が付いています。
    • cmd/8c: Go言語のソースコードをx86 (IA-32) アーキテクチャ向けのアセンブリコードに変換するCコンパイラです。 これらのコンパイラは、Goのソースコードを中間表現に変換し、最終的にターゲットアーキテクチャのアセンブリコードを生成します。
  • アセンブリリスト (Assembly Listing): コンパイラが生成したアセンブリコードを、元のソースコードの行番号やコメントなどと共に整形して表示したものです。デバッグやパフォーマンスチューニングの際に、Goのコードがどのように機械語に変換されるかを理解するために使用されます。

  • Prog (プログラム命令): Goコンパイラの内部表現において、単一のアセンブリ命令を表す構造体です。各 Prog は、命令の種類 (例: MOV, ADD), オペランド (例: レジスタ、メモリ位置、定数), およびその他のメタデータを含みます。

  • ATEXTADATA:

    • ATEXT: アセンブリコードにおいて、実行可能な命令 (関数本体など) が配置されるセクションを指します。
    • ADATA: アセンブリコードにおいて、初期化されたデータ (グローバル変数など) が配置されるセクションを指します。
  • FmtLong フラグ: Goコンパイラの内部で、特定の値を「長い形式」でフォーマットすべきかどうかを示すフラグです。このコンテキストでは、特に64ビットのオフセット値を2つの32ビット値として表示する際に使用されることを意図していました。

  • %D verb (書式指定子): C言語の printf 関数やGoコンパイラの内部書式設定ルーチンで使用される書式指定子の一つです。この文脈では、Prog 構造体内のオペランド (アドレスや定数など) を整形して出力するために使用されます。

  • list.c ファイル: cmd/6ccmd/8c の両方に存在するファイルで、アセンブリリストの生成と書式設定に関するロジックが含まれています。Pconv (Prog変換) や Dconv (Data/Operand変換) といった関数が定義されており、これらがアセンブリ命令やオペランドの表示形式を決定します。

技術的詳細

このコミットの技術的な核心は、src/cmd/6c/list.csrc/cmd/8c/list.c 内の Pconv および Dconv 関数の変更にあります。これらの関数は、Goコンパイラがアセンブリリストを生成する際に、Prog 構造体やその中のアドレス (Adr 構造体) を文字列に変換する役割を担っています。

Pconv 関数の変更

Pconv 関数は、Prog 構造体全体を整形して出力します。変更前は、%lD という書式指定子を使用していました。これは、Dconv 関数が FmtLong フラグを考慮してオペランドを整形することを期待していました。しかし、この FmtLong フラグの適用ロジックに問題がありました。

// 変更前 (src/cmd/6c/list.c)
default:
    sprint(str, "(%L)\t%A\t%D,%lD",
        p->lineno, p->as, &p->from, &p->to);
    break;

// 変更後 (src/cmd/6c/list.c)
default:
    sprint(str, "(%L)\t%A\t%D,%D",
        p->lineno, p->as, &p->from, &p->to);
    break;

この変更により、p->to オペランドの書式設定において、明示的に FmtLong フラグを強制する %lD ではなく、通常の %D を使用するようになりました。これにより、Dconv 関数が FmtLong フラグを適切に処理するようになります。

Dconv 関数の変更

Dconv 関数は、Adr (アドレス) 構造体を整形して出力します。この関数内で FmtLong フラグの処理ロジックが修正されました。

変更前は、FmtLong フラグが設定されている場合、D_CONST (定数) 以外のアドレスに対しては無条件に "!!" を付加していました。そして、D_CONST の場合は $lld-lld 形式で出力していました。このロジックが、ATEXTProg の場合にのみ FmtLong を使用すべきという意図と合致していませんでした。

// 変更前 (src/cmd/6c/list.c の Dconv 関数の一部)
if(fp->flags & FmtLong) {
    if(i != D_CONST) {
        // ATEXT dst is not constant
        sprint(str, "!!%D", a);
        goto brk;
    }
    sprint(str, "$%lld-%lld", a->offset&0xffffffffLL,
        (a->offset>>32)&0xffffffffLL);
    goto brk;
}

// 変更後 (src/cmd/6c/list.c の Dconv 関数の一部)
if(fp->flags & FmtLong) {
    if(i == D_CONST)
        sprint(str, "$%lld-%lld", a->offset&0xffffffffLL, a->offset>>32);
    else {
        // ATEXT dst is not constant
        sprint(str, "!!%D", a);
    }
    goto brk;
}

この修正により、FmtLong フラグが設定されている場合でも、D_CONST でない場合にのみ "!!" が付加されるようになりました。これは、ATEXTProg の宛先が定数でない場合にのみ "!!" を表示するという、本来の意図に沿った動作です。

また、D_AUTO (スタック上の自動変数) と D_PARAM (関数パラメータ) の書式設定ロジックも簡素化され、if(a->sym) の条件分岐がより簡潔に記述されるようになりました。

さらに、src/cmd/8c/list.c では、D_CONST2 (2つのオフセットを持つ定数) の処理が追加され、FmtLong フラグが設定されていない場合に "!!$" が付加されるようになりました。これは、ATEXT 以外のコンテキストで D_CONST2 が現れるべきではないという前提に基づいています。

コードのクリーンアップ

このコミットでは、regstr 配列の初期化におけるコメントの書式も統一されています。これは機能的な変更ではありませんが、6c8clist.c ファイル間の類似性を高め、コードの保守性を向上させるためのクリーンアップです。これにより、両ファイルの差分がより明確になり、将来的な変更が容易になります。

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

diff --git a/src/cmd/6c/list.c b/src/cmd/6c/list.c
index 7e2d153289..b5a60ac9a2 100644
--- a/src/cmd/6c/list.c
+++ b/src/cmd/6c/list.c
@@ -93,7 +93,7 @@ Pconv(Fmt *fp)
 		break;
 
 	default:
-		sprint(str, "(%L)\t%A\t%D,%lD",
+		sprint(str, "(%L)\t%A\t%D,%D",
 		\tp->lineno, p->as, &p->from, &p->to);
 		break;
 	}
@@ -120,13 +120,12 @@ Dconv(Fmt *fp)
 	i = a->type;
 
 	if(fp->flags & FmtLong) {
-\t\tif(i != D_CONST) {
+\t\tif(i == D_CONST)
+\t\t\tsprint(str, "$%lld-%lld", a->offset&0xffffffffLL, a->offset>>32);
+\t\telse {
 \t\t\t// ATEXT dst is not constant
 \t\t\tsprint(str, "!!%D", a);
-\t\t\tgoto brk;
 \t\t}\n-\t\tsprint(str, "$%lld-%lld", a->offset&0xffffffffLL,
-\t\t\t(a->offset>>32)&0xffffffffLL);\n \t\tgoto brk;
 \t}\n 
 @@ -138,7 +137,6 @@ Dconv(Fmt *fp)
 \t\tgoto brk;
 \t}\n \tswitch(i) {\n-\n \tdefault:\n \t\tif(a->offset)\n \t\t\tsprint(str, "$%lld,%R", a->offset, i);\
@@ -159,24 +157,21 @@ Dconv(Fmt *fp)
 \t\tbreak;\n \n \tcase D_STATIC:\n-\t\tsprint(str, "%s<>+%lld(SB)", a->sym->name,\n-\t\t\ta->offset);\n+\t\tsprint(str, "%s<>+%lld(SB)", a->sym->name, a->offset);\n \t\tbreak;\n \n \tcase D_AUTO:\n-\t\tif(a->sym) {\n+\t\tif(a->sym)\n \t\t\tsprint(str, "%s+%lld(SP)", a->sym->name, a->offset);\n-\t\t\tbreak;\n-\t\t}\n-\t\tsprint(str, "%lld(SP)", a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%lld(SP)", a->offset);\n \t\tbreak;\n \n \tcase D_PARAM:\n-\t\tif(a->sym) {\n+\t\tif(a->sym)\n \t\t\tsprint(str, "%s+%lld(FP)", a->sym->name, a->offset);\n-\t\t\tbreak;\n-\t\t}\n-\t\tsprint(str, "%lld(FP)", a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%lld(FP)", a->offset);\n \t\tbreak;\n \n \tcase D_CONST:\
@@ -210,7 +205,7 @@ conv:
 
 char*\tregstr[] =
 {
-\t"AL",\t\t/* [D_AL] */
+\t"AL",\t/* [D_AL] */
 \t"CL",
 \t"DL",
 \t"BL",
@@ -227,7 +222,7 @@ char*\tregstr[] =
 \t"R14B",
 \t"R15B",
 
-\t"AX",\t\t/* [D_AX] */
+\t"AX",\t/* [D_AX] */
 \t"CX",
 \t"DX",
 \t"BX",
@@ -249,7 +244,7 @@ char*\tregstr[] =
 \t"DH",
 \t"BH",
 
-\t"F0",\t\t/* [D_F0] */
+\t"F0",\t/* [D_F0] */
 \t"F1",
 \t"F2",
 \t"F3",
@@ -284,20 +279,20 @@ char*\tregstr[] =
 \t"X14",
 \t"X15",
 
-\t"CS",\t\t/* [D_CS] */
+\t"CS",\t/* [D_CS] */
 \t"SS",
 \t"DS",
 \t"ES",
 \t"FS",
 \t"GS",
 
-\t"GDTR",\t\t/* [D_GDTR] */
-\t"IDTR",\t\t/* [D_IDTR] */
-\t"LDTR",\t\t/* [D_LDTR] */
-\t"MSW",\t\t/* [D_MSW] */
-\t"TASK",\t\t/* [D_TASK] */
+\t"GDTR",\t/* [D_GDTR] */
+\t"IDTR",\t/* [D_IDTR] */
+\t"LDTR",\t/* [D_LDTR] */
+\t"MSW",\t/* [D_MSW] */
+\t"TASK",\t/* [D_TASK] */
 
-\t"CR0",\t\t/* [D_CR] */
+\t"CR0",\t/* [D_CR] */
 \t"CR1",
 \t"CR2",
 \t"CR3",
@@ -314,7 +309,7 @@ char*\tregstr[] =
 \t"CR14",
 \t"CR15",
 
-\t"DR0",\t\t/* [D_DR] */
+\t"DR0",\t/* [D_DR] */
 \t"DR1",
 \t"DR2",
 \t"DR3",
@@ -323,7 +318,7 @@ char*\tregstr[] =
 \t"DR6",
 \t"DR7",
 
-\t"TR0",\t\t/* [D_TR] */
+\t"TR0",\t/* [D_TR] */
 \t"TR1",
 \t"TR2",
 \t"TR3",
@@ -332,7 +327,7 @@ char*\tregstr[] =
 \t"TR6",
 \t"TR7",
 
-\t"NONE",\t\t/* [D_NONE] */
+\t"NONE",\t/* [D_NONE] */
 };
 
 int
diff --git a/src/cmd/8c/list.c b/src/cmd/8c/list.c
index 16a41ac368..8506e08efb 100644
--- a/src/cmd/8c/list.c
+++ b/src/cmd/8c/list.c
@@ -93,7 +93,7 @@ Pconv(Fmt *fp)
 		break;
 
 	default:
-\t\tsprint(str, "(%L)\t%A\t%D,%lD",
+\t\tsprint(str, "(%L)\t%A\t%D,%D",
 \t\t\tp->lineno, p->as, &p->from, &p->to);\
 		break;\
 	}\
@@ -118,6 +118,17 @@ Dconv(Fmt *fp)
 
 \ta = va_arg(fp->args, Adr*);\
 \ti = a->type;\
+\n+\tif(fp->flags & FmtLong) {\n+\t\tif(i == D_CONST2)\n+\t\t\tsprint(str, "$%d-%d", a->offset, a->offset2);\n+\t\telse {\n+\t\t\t// ATEXT dst is not constant\n+\t\t\tsprint(str, "!!%D", a);\n+\t\t}\n+\t\tgoto brk;\n+\t}\n+\
 \tif(i >= D_INDIR) {\
 \t\tif(a->offset)\
 \t\t\tsprint(str, "%d(%R)", a->offset, i-D_INDIR);\
@@ -126,7 +137,6 @@ Dconv(Fmt *fp)
 \t\tgoto brk;\
 \t}\
 \tswitch(i) {\
-\n \tdefault:\
 \t\tif(a->offset)\
 \t\t\tsprint(str, "$%d,%R", a->offset, i);\
@@ -147,12 +157,14 @@ Dconv(Fmt *fp)
 \t\tbreak;\
 \n \tcase D_STATIC:\
-\t\tsprint(str, "%s<>+%d(SB)", a->sym->name,\n-\t\t\ta->offset);\
+\t\tsprint(str, "%s<>+%d(SB)", a->sym->name, a->offset);\
 \t\tbreak;\
 \n \tcase D_AUTO:\
-\t\tsprint(str, "%s+%d(SP)", a->sym->name, a->offset);\
+\t\tif(a->sym)\n+\t\t\tsprint(str, "%s+%d(SP)", a->sym->name, a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%d(SP)", a->offset);\
 \t\tbreak;\
 \n \tcase D_PARAM:\
@@ -167,7 +179,10 @@ Dconv(Fmt *fp)
 \t\tbreak;\
 \n \tcase D_CONST2:\
-\t\tsprint(str, "$%d-%d", a->offset, a->offset2);\
+\t\tif(!(fp->flags & FmtLong)) {\n+\t\t\t// D_CONST2 outside of ATEXT should not happen\n+\t\t\tsprint(str, "!!$%d-%d", a->offset, a->offset2);\n+\t\t}\
 \t\tbreak;\
 \n \tcase D_FCONST:\
@@ -197,7 +212,7 @@ conv:
 
 char*\tregstr[] =
 {
-\t"AL",\t/*[D_AL]*/
+\t"AL",\t/* [D_AL] */
 \t"CL",
 \t"DL",
 \t"BL",
@@ -206,7 +221,7 @@ char*\tregstr[] =
 \t"DH",
 \t"BH",
 
-\t"AX",\t/*[D_AX]*/
+\t"AX",\t/* [D_AX] */
 \t"CX",
 \t"DX",
 \t"BX",
@@ -215,7 +230,7 @@ char*\tregstr[] =
 \t"SI",
 \t"DI",
 
-\t"F0",\t/*[D_F0]*/
+\t"F0",\t/* [D_F0] */
 \t"F1",
 \t"F2",
 \t"F3",
@@ -224,20 +239,20 @@ char*\tregstr[] =
 \t"F6",
 \t"F7",
 
-\t"CS",\t/*[D_CS]*/
+\t"CS",\t/* [D_CS] */
 \t"SS",
 \t"DS",
 \t"ES",
 \t"FS",
 \t"GS",
 
-\t"GDTR",\t/*[D_GDTR]*/
-\t"IDTR",\t/*[D_IDTR]*/
-\t"LDTR",\t/*[D_LDTR]*/
-\t"MSW",\t/*[D_MSW] */
-\t"TASK",\t/*[D_TASK]*/
+\t"GDTR",\t/* [D_GDTR] */
+\t"IDTR",\t/* [D_IDTR] */
+\t"LDTR",\t/* [D_LDTR] */
+\t"MSW",\t/* [D_MSW] */
+\t"TASK",\t/* [D_TASK] */
 
-\t"CR0",\t/*[D_CR]*/
+\t"CR0",\t/* [D_CR] */
 \t"CR1",
 \t"CR2",
 \t"CR3",
@@ -246,7 +261,7 @@ char*\tregstr[] =
 \t"CR6",
 \t"CR7",
 
-\t"DR0",\t/*[D_DR]*/
+\t"DR0",\t/* [D_DR] */
 \t"DR1",
 \t"DR2",
 \t"DR3",
@@ -255,7 +270,7 @@ char*\tregstr[] =
 \t"DR6",
 \t"DR7",
 
-\t"TR0",\t/*[D_TR]*/
+\t"TR0",\t/* [D_TR] */
 \t"TR1",
 \t"TR2",
 \t"TR3",
@@ -264,7 +279,7 @@ char*\tregstr[] =
 \t"TR6",
 \t"TR7",
 
-\t"X0",\t/*[D_X0]*/
+\t"X0",\t/* [D_X0] */
 \t"X1",
 \t"X2",
 \t"X3",
@@ -273,7 +288,7 @@ char*\tregstr[] =
 \t"X6",
 \t"X7",
 
-\t"NONE",\t/*[D_NONE]*/
+\t"NONE",\t/* [D_NONE] */
 };
 
 int

コアとなるコードの解説

src/cmd/6c/list.c および src/cmd/8c/list.cPconv 関数

  • 変更点: sprint 関数の書式文字列において、%lD%D に変更されました。
  • 解説:
    • 変更前: sprint(str, "(%L)\t%A\t%D,%lD", ... &p->to);
    • 変更後: sprint(str, "(%L)\t%A\t%D,%D", ... &p->to);
    • %lD は、Dconv 関数に FmtLong フラグを強制的に渡すことを意味していました。しかし、このフラグは ATEXTProg の場合にのみ特定の目的 (64ビットオフセットの表示) で使用されるべきでした。
    • この変更により、p->to (命令の宛先オペランド) の書式設定は、Dconv 関数内の FmtLong フラグの適切なロジックに委ねられるようになります。これにより、不要な "!!" の出力が抑制されます。

src/cmd/6c/list.c および src/cmd/8c/list.cDconv 関数

  • 変更点: FmtLong フラグが設定されている場合の条件分岐ロジックが修正されました。
  • 解説:
    • 変更前: if(fp->flags & FmtLong) { if(i != D_CONST) { ... } else { ... } }
    • 変更後: if(fp->flags & FmtLong) { if(i == D_CONST) { ... } else { ... } }
    • この修正は、FmtLong フラグが設定されている場合に、D_CONST (定数) であるかどうかで処理を分岐させるようにしました。
    • D_CONST の場合 (i == D_CONST) は、$lld-lld 形式でオフセットを出力します。これは、64ビットの定数を2つの32ビット値として表示する本来の目的です。
    • D_CONST でない場合 (else ブロック) は、ATEXT の宛先が定数でないことを示唆するため、"!!" を付加して Dconv を再帰的に呼び出します。これにより、ATEXTProg の宛先が定数でない場合にのみ "!!" が表示されるようになり、他の不適切な場所での "!!" の表示がなくなります。
    • src/cmd/8c/list.c では、D_CONST2 の処理が追加され、FmtLong フラグが設定されていない場合に "!!$" が付加されるようになりました。これは、D_CONST2ATEXT 以外のコンテキストで現れるべきではないという前提に基づいています。

D_AUTO および D_PARAM の書式設定 (両ファイル共通)

  • 変更点: if(a->sym) の条件分岐が簡素化されました。
  • 解説:
    • 変更前は、if(a->sym) のブロック内に break; があり、その後に sprint(str, "%lld(SP)", a->offset); のような行が続いていました。
    • 変更後: if(a->sym) { ... } else { ... } の形式になり、シンボルがある場合とない場合の処理がより明確に分離されました。これは機能的な変更ではなく、コードの可読性と保守性を向上させるためのクリーンアップです。

regstr 配列のコメント書式 (両ファイル共通)

  • 変更点: regstr 配列の初期化におけるコメントのタブとスペースの配置が統一されました。
  • 解説: これは純粋なコードスタイルの変更であり、機能には影響しません。cmd/6c/list.ccmd/8c/list.c の間でコードの類似性を高め、diff コマンドで比較した際に真の機能的な差分がより際立つようにするためのクリーンアップです。

これらの変更により、Goコンパイラが生成するアセンブリリストの正確性と可読性が大幅に向上し、開発者がアセンブリコードを理解しやすくなりました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/cmd/6c/list.csrc/cmd/8c/list.c): https://github.com/golang/go/tree/master/src/cmd
  • Go言語のアセンブリに関するドキュメント (例: go doc cmd/asmgo tool compile -S の出力に関する情報): https://go.dev/doc/asm
  • Go言語のコンパイラに関する一般的な情報 (Goのブログやカンファレンスの発表など)
  • C言語の printf 書式指定子に関する一般的な情報 (特に %D%lD のようなカスタム書式指定子の概念を理解するため)
  • Go言語のGerrit CL (Change-list) 7128045: https://golang.org/cl/7128045 (コミットメッセージに記載されているリンク)

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

このコミットは、Go言語のコンパイラツールチェーンの一部である cmd/6c (64-bit x86アーキテクチャ向けCコンパイラ) と cmd/8c (32-bit x86アーキテクチャ向けCコンパイラ) におけるアセンブリリスト出力の書式設定に関するバグ修正とコードクリーンアップを目的としています。具体的には、Prog (プログラム命令) の出力時に FmtLong フラグが誤って適用され、アセンブリリストに不要な "!!" (感嘆符) が多数表示される問題を解決します。また、関連する list.c ファイル間のコードの類似性を高め、差分を容易に識別できるように整理しています。

コミット

commit 60826e0be6e62ae76f5771b22894bd3dc8bade10
Author: Anthony Martin <ality@pbrane.org>
Date:   Fri Jan 18 19:00:38 2013 -0800

    cmd/6c, cmd/8c: fix print format for Prog
    
    The FmtLong flag should only be used with the %D verb
    when printing an ATEXT Prog. It was erroneously used
    for every Prog except ADATA. This caused a preponderance
    of exclamation points, "!!", in the assembly listings.
    
    I also cleaned up the code so that the list.c files look
    very similar. Now the real differences are easily spotted
    with a simple diff.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7128045

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

https://github.com/golang/go/commit/60826e0be6e6ae76f5771b22894bd3dc8bade10

元コミット内容

cmd/6c, cmd/8c: fix print format for Prog

The FmtLong flag should only be used with the %D verb
when printing an ATEXT Prog. It was erroneously used
for every Prog except ADATA. This caused a preponderance
of exclamation points, "!!", in the assembly listings.

I also cleaned up the code so that the list.c files look
very similar. Now the real differences are easily spotted
with a simple diff.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/7128045

変更の背景

この変更の主な背景は、Go言語のコンパイラが生成するアセンブリリストの可読性の問題にありました。具体的には、cmd/6c および cmd/8c コンパイラがアセンブリコードを整形して出力する際に、FmtLong という書式フラグが不適切に適用されることで、本来表示されるべきではない "!!" という文字列が多数混入していました。

この "!!" は、通常、コンパイラがアドレスやオフセットの表現に問題がある、あるいは予期しない値に遭遇した場合にデバッグ目的で出力されるマーカーです。しかし、このバグでは、ATEXT (関数コードセクション) の Prog (命令) を %D (オペランドの書式指定子) で出力する際にのみ FmtLong フラグを使用すべきであるにもかかわらず、ADATA (データセクション) 以外のすべての Prog に対して誤ってこのフラグが適用されていました。結果として、正しくフォーマットされるべきアセンブリ命令のオペランドにも "!!" が付加され、アセンブリリストが非常に読みにくい状態になっていました。

このコミットは、この書式設定の誤りを修正し、アセンブリリストから不要な "!!" を排除することで、開発者が生成されたアセンブリコードをより容易にデバッグ・解析できるようにすることを目的としています。また、src/cmd/6c/list.csrc/cmd/8c/list.c のコード構造を統一することで、将来的なメンテナンスや両ファイル間の差分比較を容易にするという副次的な目的も持っています。

前提知識の解説

このコミットを理解するためには、以下のGo言語のコンパイラツールチェーンとアセンブリに関する基本的な知識が必要です。

  • Go言語のコンパイラツールチェーン: Go言語は、go build コマンドを通じてソースコードをコンパイルし、実行可能なバイナリを生成します。このプロセスには、様々なツールが関与しており、cmd/6ccmd/8c はその一部です。

    • cmd/6c: Go言語のソースコードをx86-64 (AMD64) アーキテクチャ向けのアセンブリコードに変換するCコンパイラです。Goのコンパイラは、歴史的にPlan 9のツールチェーンをベースにしており、その名残として c が付いています。
    • cmd/8c: Go言語のソースコードをx86 (IA-32) アーキテクチャ向けのアセンブリコードに変換するCコンパイラです。 これらのコンパイラは、Goのソースコードを中間表現に変換し、最終的にターゲットアーキテクチャのアセンブリコードを生成します。補足: 現在のGoコンパイラ (cmd/compile) はGo 1.5以降、Go言語自体で書かれたセルフホスト型であり、6c8c は現代のGo開発では直接使用されません。しかし、このコミットが作成された時点では、これらはGoツールチェーンの重要な一部でした。
  • アセンブリリスト (Assembly Listing): コンパイラが生成したアセンブリコードを、元のソースコードの行番号やコメントなどと共に整形して表示したものです。デバッグやパフォーマンスチューニングの際に、Goのコードがどのように機械語に変換されるかを理解するために使用されます。

  • Prog (プログラム命令): Goコンパイラの内部表現において、単一のアセンブリ命令を表す構造体です。各 Prog は、命令の種類 (例: MOV, ADD), オペランド (例: レジスタ、メモリ位置、定数), およびその他のメタデータを含みます。

  • ATEXTADATA:

    • ATEXT: アセンブリコードにおいて、実行可能な命令 (関数本体など) が配置されるセクションを指します。
    • ADATA: アセンブリコードにおいて、初期化されたデータ (グローバル変数など) が配置されるセクションを指します。
  • FmtLong フラグ: Goコンパイラの内部で、特定の値を「長い形式」でフォーマットすべきかどうかを示すフラグです。このコンテキストでは、特に64ビットのオフセット値を2つの32ビット値として表示する際に使用されることを意図していました。これはGoの標準 fmt パッケージのフラグではなく、コンパイラ内部の書式設定ロジックで使用されるカスタムフラグです。

  • %D verb (書式指定子): C言語の printf 関数やGoコンパイラの内部書式設定ルーチンで使用される書式指定子の一つです。この文脈では、Prog 構造体内のオペランド (アドレスや定数など) を整形して出力するために使用されます。

  • list.c ファイル: cmd/6ccmd/8c の両方に存在するファイルで、アセンブリリストの生成と書式設定に関するロジックが含まれています。Pconv (Prog変換) や Dconv (Data/Operand変換) といった関数が定義されており、これらがアセンブリ命令やオペランドの表示形式を決定します。

技術的詳細

このコミットの技術的な核心は、src/cmd/6c/list.csrc/cmd/8c/list.c 内の Pconv および Dconv 関数の変更にあります。これらの関数は、Goコンパイラがアセンブリリストを生成する際に、Prog 構造体やその中のアドレス (Adr 構造体) を文字列に変換する役割を担っています。

Pconv 関数の変更

Pconv 関数は、Prog 構造体全体を整形して出力します。変更前は、%lD という書式指定子を使用していました。これは、Dconv 関数が FmtLong フラグを考慮してオペランドを整形することを期待していました。しかし、この FmtLong フラグの適用ロジックに問題がありました。

// 変更前 (src/cmd/6c/list.c)
default:
    sprint(str, "(%L)\t%A\t%D,%lD",
        p->lineno, p->as, &p->from, &p->to);
    break;

// 変更後 (src/cmd/6c/list.c)
default:
    sprint(str, "(%L)\t%A\t%D,%D",
        p->lineno, p->as, &p->from, &p->to);
    break;

この変更により、p->to オペランドの書式設定において、明示的に FmtLong フラグを強制する %lD ではなく、通常の %D を使用するようになりました。これにより、Dconv 関数が FmtLong フラグを適切に処理するようになります。

Dconv 関数の変更

Dconv 関数は、Adr (アドレス) 構造体を整形して出力します。この関数内で FmtLong フラグの処理ロジックが修正されました。

変更前は、FmtLong フラグが設定されている場合、D_CONST (定数) 以外のアドレスに対しては無条件に "!!" を付加していました。そして、D_CONST の場合は $lld-lld 形式で出力していました。このロジックが、ATEXTProg の場合にのみ FmtLong を使用すべきという意図と合致していませんでした。

// 変更前 (src/cmd/6c/list.c の Dconv 関数の一部)
if(fp->flags & FmtLong) {
    if(i != D_CONST) {
        // ATEXT dst is not constant
        sprint(str, "!!%D", a);
        goto brk;
    }
    sprint(str, "$%lld-%lld", a->offset&0xffffffffLL,
        (a->offset>>32)&0xffffffffLL);
    goto brk;
}

// 変更後 (src/cmd/6c/list.c の Dconv 関数の一部)
if(fp->flags & FmtLong) {
    if(i == D_CONST)
        sprint(str, "$%lld-%lld", a->offset&0xffffffffLL, a->offset>>32);
    else {
        // ATEXT dst is not constant
        sprint(str, "!!%D", a);
    }
    goto brk;
}

この修正により、FmtLong フラグが設定されている場合でも、D_CONST でない場合にのみ "!!" が付加されるようになりました。これは、ATEXTProg の宛先が定数でない場合にのみ "!!" を表示するという、本来の意図に沿った動作です。

また、D_AUTO (スタック上の自動変数) と D_PARAM (関数パラメータ) の書式設定ロジックも簡素化され、if(a->sym) の条件分岐がより簡潔に記述されるようになりました。

さらに、src/cmd/8c/list.c では、D_CONST2 (2つのオフセットを持つ定数) の処理が追加され、FmtLong フラグが設定されていない場合に "!!$" が付加されるようになりました。これは、ATEXT 以外のコンテキストで D_CONST2 が現れるべきではないという前提に基づいています。

コードのクリーンアップ

このコミットでは、regstr 配列の初期化におけるコメントの書式も統一されています。これは機能的な変更ではありませんが、6c8clist.c ファイル間の類似性を高め、コードの保守性を向上させるためのクリーンアップです。これにより、両ファイルの差分がより明確になり、将来的な変更が容易になります。

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

diff --git a/src/cmd/6c/list.c b/src/cmd/6c/list.c
index 7e2d153289..b5a60ac9a2 100644
--- a/src/cmd/6c/list.c
+++ b/src/cmd/6c/list.c
@@ -93,7 +93,7 @@ Pconv(Fmt *fp)
 		break;
 
 	default:
-		sprint(str, "(%L)\t%A\t%D,%lD",
+		sprint(str, "(%L)\t%A\t%D,%D",
 		\tp->lineno, p->as, &p->from, &p->to);
 		break;
 	}
@@ -120,13 +120,12 @@ Dconv(Fmt *fp)
 	i = a->type;
 
 	if(fp->flags & FmtLong) {
-\t\tif(i != D_CONST) {
+\t\tif(i == D_CONST)
+\t\t\tsprint(str, "$%lld-%lld", a->offset&0xffffffffLL, a->offset>>32);
+\t\telse {
 \t\t\t// ATEXT dst is not constant
 \t\t\tsprint(str, "!!%D", a);
-\t\t\tgoto brk;
 \t\t}\n-\t\tsprint(str, "$%lld-%lld", a->offset&0xffffffffLL,
-\t\t\t(a->offset>>32)&0xffffffffLL);\n \t\tgoto brk;
 \t}\n 
 @@ -138,7 +137,6 @@ Dconv(Fmt *fp)
 \t\tgoto brk;
 \t}\n \tswitch(i) {\n-\n \tdefault:\
 \t\tif(a->offset)\
 \t\t\tsprint(str, "$%lld,%R", a->offset, i);\
@@ -159,24 +157,21 @@ Dconv(Fmt *fp)
 \t\tbreak;\n \n \tcase D_STATIC:\
-\t\tsprint(str, "%s<>+%lld(SB)", a->sym->name,\n-\t\t\ta->offset);\n+\t\tsprint(str, "%s<>+%lld(SB)", a->sym->name, a->offset);\n \t\tbreak;\n \n \tcase D_AUTO:\
-\t\tif(a->sym) {\n+\t\tif(a->sym)\n \t\t\tsprint(str, "%s+%lld(SP)", a->sym->name, a->offset);\n-\t\t\tbreak;\n-\t\t}\n-\t\tsprint(str, "%lld(SP)", a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%lld(SP)", a->offset);\n \t\tbreak;\n \n \tcase D_PARAM:\
-\t\tif(a->sym) {\n+\t\tif(a->sym)\n \t\t\tsprint(str, "%s+%lld(FP)", a->sym->name, a->offset);\n-\t\t\tbreak;\n-\t\t}\n-\t\tsprint(str, "%lld(FP)", a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%lld(FP)", a->offset);\n \t\tbreak;\n \n \tcase D_CONST:\
@@ -210,7 +205,7 @@ conv:
 
 char*\tregstr[] =
 {
-\t"AL",\t\t/* [D_AL] */
+\t"AL",\t/* [D_AL] */
 \t"CL",
 \t"DL",
 \t"BL",
@@ -227,7 +222,7 @@ char*\tregstr[] =
 \t"R14B",
 \t"R15B",
 
-\t"AX",\t\t/* [D_AX] */
+\t"AX",\t/* [D_AX] */
 \t"CX",
 \t"DX",
 \t"BX",
@@ -249,7 +244,7 @@ char*\tregstr[] =
 \t"DH",
 \t"BH",
 
-\t"F0",\t\t/* [D_F0] */
+\t"F0",\t/* [D_F0] */
 \t"F1",
 \t"F2",
 \t"F3",
@@ -284,20 +279,20 @@ char*\tregstr[] =
 \t"X14",
 \t"X15",
 
-\t"CS",\t\t/* [D_CS] */
+\t"CS",\t/* [D_CS] */
 \t"SS",
 \t"DS",
 \t"ES",
 \t"FS",
 \t"GS",
 
-\t"GDTR",\t\t/* [D_GDTR] */
-\t"IDTR",\t\t/* [D_IDTR] */
-\t"LDTR",\t\t/* [D_LDTR] */
-\t"MSW",\t\t/* [D_MSW] */
-\t"TASK",\t\t/* [D_TASK] */
+\t"GDTR",\t/* [D_GDTR] */
+\t"IDTR",\t/* [D_IDTR] */
+\t"LDTR",\t/* [D_LDTR] */
+\t"MSW",\t/* [D_MSW] */
+\t"TASK",\t/* [D_TASK] */
 
-\t"CR0",\t\t/* [D_CR] */
+\t"CR0",\t/* [D_CR] */
 \t"CR1",
 \t"CR2",
 \t"CR3",
@@ -314,7 +309,7 @@ char*\tregstr[] =
 \t"CR14",
 \t"CR15",
 
-\t"DR0",\t\t/* [D_DR] */
+\t"DR0",\t/* [D_DR] */
 \t"DR1",
 \t"DR2",
 \t"DR3",
@@ -323,7 +318,7 @@ char*\tregstr[] =
 \t"DR6",
 \t"DR7",
 
-\t"TR0",\t\t/* [D_TR] */
+\t"TR0",\t/* [D_TR] */
 \t"TR1",
 \t"TR2",
 \t"TR3",
@@ -332,7 +327,7 @@ char*\tregstr[] =
 \t"TR6",
 \t"TR7",
 
-\t"NONE",\t\t/* [D_NONE] */
+\t"NONE",\t/* [D_NONE] */
 };
 
 int
diff --git a/src/cmd/8c/list.c b/src/cmd/8c/list.c
index 16a41ac368..8506e08efb 100644
--- a/src/cmd/8c/list.c
+++ b/src/cmd/8c/list.c
@@ -93,7 +93,7 @@ Pconv(Fmt *fp)
 		break;
 
 	default:
-\t\tsprint(str, "(%L)\t%A\t%D,%lD",
+\t\tsprint(str, "(%L)\t%A\t%D,%D",
 \t\t\tp->lineno, p->as, &p->from, &p->to);\
 		break;\
 	}\
@@ -118,6 +118,17 @@ Dconv(Fmt *fp)
 
 \ta = va_arg(fp->args, Adr*);\
 \ti = a->type;\
+\n+\tif(fp->flags & FmtLong) {\n+\t\tif(i == D_CONST2)\n+\t\t\tsprint(str, "$%d-%d", a->offset, a->offset2);\n+\t\telse {\n+\t\t\t// ATEXT dst is not constant\n+\t\t\tsprint(str, "!!%D", a);\n+\t\t}\n+\t\tgoto brk;\n+\t}\n+\
 \tif(i >= D_INDIR) {\
 \t\tif(a->offset)\
 \t\t\tsprint(str, "%d(%R)", a->offset, i-D_INDIR);\
@@ -126,7 +137,6 @@ Dconv(Fmt *fp)
 \t\tgoto brk;\
 \t}\
 \tswitch(i) {\
-\n \tdefault:\
 \t\tif(a->offset)\
 \t\t\tsprint(str, "$%d,%R", a->offset, i);\
@@ -147,12 +157,14 @@ Dconv(Fmt *fp)
 \t\tbreak;\
 \n \tcase D_STATIC:\
-\t\tsprint(str, "%s<>+%d(SB)", a->sym->name,\n-\t\t\ta->offset);\
+\t\tsprint(str, "%s<>+%d(SB)", a->sym->name, a->offset);\
 \t\tbreak;\
 \n \tcase D_AUTO:\
-\t\tsprint(str, "%s+%d(SP)", a->sym->name, a->offset);\
+\t\tif(a->sym)\n+\t\t\tsprint(str, "%s+%d(SP)", a->sym->name, a->offset);\n+\t\telse\n+\t\t\tsprint(str, "%d(SP)", a->offset);\
 \t\tbreak;\
 \n \tcase D_PARAM:\
@@ -167,7 +179,10 @@ Dconv(Fmt *fp)
 \t\tbreak;\
 \n \tcase D_CONST2:\
-\t\tsprint(str, "$%d-%d", a->offset, a->offset2);\
+\t\tif(!(fp->flags & FmtLong)) {\n+\t\t\t// D_CONST2 outside of ATEXT should not happen\n+\t\t\tsprint(str, "!!$%d-%d", a->offset, a->offset2);\n+\t\t}\
 \t\tbreak;\
 \n \tcase D_FCONST:\
@@ -197,7 +212,7 @@ conv:
 
 char*\tregstr[] =
 {
-\t"AL",\t/*[D_AL]*/
+\t"AL",\t/* [D_AL] */
 \t"CL",
 \t"DL",
 \t"BL",
@@ -206,7 +221,7 @@ char*\tregstr[] =
 \t"DH",
 \t"BH",
 
-\t"AX",\t/*[D_AX]*/
+\t"AX",\t/* [D_AX] */
 \t"CX",
 \t"DX",
 \t"BX",
@@ -215,7 +230,7 @@ char*\tregstr[] =
 \t"SI",
 \t"DI",
 
-\t"F0",\t/*[D_F0]*/
+\t"F0",\t/* [D_F0] */
 \t"F1",
 \t"F2",
 \t"F3",
@@ -224,20 +239,20 @@ char*\tregstr[] =
 \t"F6",
 \t"F7",
 
-\t"CS",\t/*[D_CS]*/
+\t"CS",\t/* [D_CS] */
 \t"SS",
 \t"DS",
 \t"ES",
 \t"FS",
 \t"GS",
 
-\t"GDTR",\t/*[D_GDTR]*/
-\t"IDTR",\t/*[D_IDTR]*/
-\t"LDTR",\t/*[D_LDTR]*/
-\t"MSW",\t/*[D_MSW] */
-\t"TASK",\t/*[D_TASK]*/
+\t"GDTR",\t/* [D_GDTR] */
+\t"IDTR",\t/* [D_IDTR] */
+\t"LDTR",\t/* [D_LDTR] */
+\t"MSW",\t/* [D_MSW] */
+\t"TASK",\t/* [D_TASK] */
 
-\t"CR0",\t/*[D_CR]*/
+\t"CR0",\t/* [D_CR] */
 \t"CR1",
 \t"CR2",
 \t"CR3",
@@ -246,7 +261,7 @@ char*\tregstr[] =
 \t"CR6",
 \t"CR7",
 
-\t"DR0",\t/*[D_DR]*/
+\t"DR0",\t/* [D_DR] */
 \t"DR1",
 \t"DR2",
 \t"DR3",
@@ -255,7 +270,7 @@ char*\tregstr[] =
 \t"DR6",
 \t"DR7",
 
-\t"TR0",\t/*[D_TR]*/
+\t"TR0",\t/* [D_TR] */
 \t"TR1",
 \t"TR2",
 \t"TR3",
@@ -264,7 +279,7 @@ char*\tregstr[] =
 \t"TR6",
 \t"TR7",
 
-\t"X0",\t/*[D_X0]*/
+\t"X0",\t/* [D_X0] */
 \t"X1",
 \t"X2",
 \t"X3",
@@ -273,7 +288,7 @@ char*\tregstr[] =
 \t"X6",
 \t"X7",
 
-\t"NONE",\t/*[D_NONE]*/
+\t"NONE",\t/* [D_NONE] */
 };
 
 int

コアとなるコードの解説

src/cmd/6c/list.c および src/cmd/8c/list.cPconv 関数

  • 変更点: sprint 関数の書式文字列において、%lD%D に変更されました。
  • 解説:
    • 変更前: sprint(str, "(%L)\t%A\t%D,%lD", ... &p->to);
    • 変更後: sprint(str, "(%L)\t%A\t%D,%D", ... &p->to);
    • %lD は、Dconv 関数に FmtLong フラグを強制的に渡すことを意味していました。しかし、このフラグは ATEXTProg の場合にのみ特定の目的 (64ビットオフセットの表示) で使用されるべきでした。
    • この変更により、p->to (命令の宛先オペランド) の書式設定は、Dconv 関数内の FmtLong フラグの適切なロジックに委ねられるようになります。これにより、不要な "!!" の出力が抑制されます。

src/cmd/6c/list.c および src/cmd/8c/list.cDconv 関数

  • 変更点: FmtLong フラグが設定されている場合の条件分岐ロジックが修正されました。
  • 解説:
    • 変更前: if(fp->flags & FmtLong) { if(i != D_CONST) { ... } else { ... } }
    • 変更後: if(fp->flags & FmtLong) { if(i == D_CONST) { ... } else { ... } }
    • この修正は、FmtLong フラグが設定されている場合に、D_CONST (定数) であるかどうかで処理を分岐させるようにしました。
    • D_CONST の場合 (i == D_CONST) は、$lld-lld 形式でオフセットを出力します。これは、64ビットの定数を2つの32ビット値として表示する本来の目的です。
    • D_CONST でない場合 (else ブロック) は、ATEXT の宛先が定数でないことを示唆するため、"!!" を付加して Dconv を再帰的に呼び出します。これにより、ATEXTProg の宛先が定数でない場合にのみ "!!" が表示されるようになり、他の不適切な場所での "!!" の表示がなくなります。
    • src/cmd/8c/list.c では、D_CONST2 の処理が追加され、FmtLong フラグが設定されていない場合に "!!$" が付加されるようになりました。これは、D_CONST2ATEXT 以外のコンテキストで現れるべきではないという前提に基づいています。

D_AUTO および D_PARAM の書式設定 (両ファイル共通)

  • 変更点: if(a->sym) の条件分岐が簡素化されました。
  • 解説:
    • 変更前は、if(a->sym) のブロック内に break; があり、その後に sprint(str, "%lld(SP)", a->offset); のような行が続いていました。
    • 変更後: if(a->sym) { ... } else { ... } の形式になり、シンボルがある場合とない場合の処理がより明確に分離されました。これは機能的な変更ではなく、コードの可読性と保守性を向上させるためのクリーンアップです。

regstr 配列のコメント書式 (両ファイル共通)

  • 変更点: regstr 配列の初期化におけるコメントのタブとスペースの配置が統一されました。
  • 解説: これは純粋なコードスタイルの変更であり、機能には影響しません。cmd/6c/list.ccmd/8c/list.c の間でコードの類似性を高め、diff コマンドで比較した際に真の機能的な差分がより際立つようにするためのクリーンアップです。

これらの変更により、Goコンパイラが生成するアセンブリリストの正確性と可読性が大幅に向上し、開発者がアセンブリコードを理解しやすくなりました。

関連リンク

参考にした情報源リンク