[インデックス 18213] ファイルの概要
このコミットは、Go言語のリンカ(liblink
)におけるフォーマット動詞の衝突を解消し、コンパイラとリンカ間のコード重複を削減することを目的としています。具体的には、cmd/gc
(Goコンパイラ)が内部的に使用する%S
(Sym構造体)と%N
(Node構造体)というフォーマット動詞が、liblink
でも異なる目的で使用されていた問題を解決します。これにより、将来的にコンパイラとリンカで共通のフォーマットルーチンを使用できるよう、基盤を整備しています。
コミット
commit d155f6a309feea100207cdf707b4bb349851b9e0
Author: Anthony Martin <ality@pbrane.org>
Date: Thu Jan 9 19:01:08 2014 -0800
liblink: adjust format verbs to avoid collisions
The %S and %N format verbs are used by cmd/gc to
represent Sym and Node structures, respectively.
In liblink, these two verbs are used only by the %D
format routine and never referenced externally.
This change will allow us to delete the duplicated
code for the %A, %D, %P, and %R format routines in
both the compiler and linker.
R=golang-codereviews, rsc
CC=golang-codereviews
https://golang.org/cl/49720043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d155f6a309feea100207cdf707b4bb349851b9e0
元コミット内容
liblink: adjust format verbs to avoid collisions
%S
と%N
というフォーマット動詞は、cmd/gc
(Goコンパイラ)によってそれぞれSym
構造体とNode
構造体を表現するために使用されています。
liblink
では、これら2つの動詞は%D
フォーマットルーチン内でのみ使用され、外部からは参照されていませんでした。
この変更により、コンパイラとリンカの両方で重複している%A
、%D
、%P
、%R
フォーマットルーチンのコードを削除できるようになります。
変更の背景
Go言語のツールチェイン、特にコンパイラ(cmd/gc
)とリンカ(liblink
)は、内部的に様々なデータ構造や値を文字列として表現するために、独自のフォーマットシステムを使用しています。これはC言語のprintf
のような機能を提供しますが、Goの内部構造に特化しています。
このコミット以前は、%S
と%N
という特定のフォーマット動詞が、cmd/gc
ではSym
(シンボル)とNode
(抽象構文木のノード)という重要なデータ構造の表示に使用されていました。しかし、liblink
でもこれらのフォーマット動詞が、%D
(アドレス/オペランド)のフォーマットルーチン内で、異なる意味合いで使われていました。
このようなフォーマット動詞の「衝突」は、以下のような問題を引き起こします。
- コードの重複: コンパイラとリンカが同じようなフォーマット機能を持つにもかかわらず、異なる意味で同じフォーマット動詞を使用しているため、共通化が困難でした。結果として、
%A
、%D
、%P
、%R
といった共通のフォーマットルーチンが両方のツールで重複して実装されており、メンテナンスの負担となっていました。 - 将来的な統合の妨げ: Goツールチェインの進化において、コンパイラとリンカのコードベースをより密接に統合し、共通のユーティリティ関数やデータ構造を利用することは、コードの品質向上、バグの削減、開発効率の向上に繋がります。フォーマット動詞の衝突は、このような統合を阻害する要因となっていました。
このコミットは、この衝突を解消し、将来的なコードの共通化と重複の削除を可能にするための、重要な基盤整備の一環です。
前提知識の解説
このコミットを理解するためには、以下のGo言語ツールチェインの内部構造とC言語のフォーマット関数に関する知識が必要です。
-
Goツールチェインの構造:
cmd/gc
: Goコンパイラの主要部分です。Goのソースコードを解析し、抽象構文木(AST)を構築し、最終的にアセンブリコードを生成します。liblink
: Goリンカのライブラリ部分です。コンパイラによって生成されたオブジェクトファイルや、他のライブラリを結合して実行可能ファイルを生成します。fmtinstall
: Goツールチェインの内部で使用される、C言語のprintf
に似たカスタムフォーマットシステムの一部です。特定の文字(フォーマット動詞、例:%D
,%S
)と、それに対応するフォーマット関数(例:Dconv
,Sconv
)を関連付けます。これにより、sprint
などの関数で、登録されたフォーマット動詞を使って構造体や値を文字列に変換できるようになります。Fmt *fp
:fmtinstall
で登録されるフォーマット関数が受け取る引数で、フォーマットの状態を管理する構造体へのポインタです。sprint
: Goツールチェイン内部で使われる、C言語のsprintf
に似た関数です。フォーマット文字列と引数を受け取り、結果の文字列をバッファに書き込みます。
-
フォーマット動詞:
%A
: オペコード(命令のニーモニック)をフォーマットするために使用されます。%D
: アドレスや命令のオペランドをフォーマットするために使用されます。%P
: 命令(Prog
構造体)をフォーマットするために使用されます。%R
: レジスタをフォーマットするために使用されます。%S
(旧):cmd/gc
ではSym
構造体(シンボル)を、liblink
では文字列定数(a->u.sval
)をフォーマットするために使用されていました。%N
(旧):cmd/gc
ではNode
構造体(ASTノード)を、liblink
ではAddr
構造体(アドレス)をフォーマットするために使用されていました。
-
Sym
とNode
構造体:Sym
: Goコンパイラがプログラム内のシンボル(変数名、関数名など)を管理するために使用するデータ構造です。Node
: GoコンパイラがGoのソースコードを解析して構築する抽象構文木(AST)の各ノードを表すデータ構造です。
このコミットは、これらのフォーマット動詞の役割と、それらがGoツールチェインの異なる部分でどのように使用されていたかを理解することで、その重要性が明確になります。
技術的詳細
このコミットの技術的な核心は、liblink
内部で使用されていたフォーマット動詞%S
と%N
を、それぞれ%$
と%M
という新しい動詞に置き換えることです。これにより、cmd/gc
が使用する%S
と%N
との衝突を解消します。
具体的な変更点は以下の通りです。
-
フォーマット関数のリネーム:
src/liblink/list5.c
において、static int Sconv(Fmt *fp);
はstatic int DSconv(Fmt *fp);
に、static int Nconv(Fmt *fp);
はstatic int Mconv(Fmt *fp);
にそれぞれリネームされました。これは、これらの関数が新しいフォーマット動詞に対応することを示すためです。
-
fmtinstall
の変更:src/liblink/list5.c
、src/liblink/list6.c
、src/liblink/list8.c
の各listinit
関数(listinit5
,listinit6
,listinit8
)内で、fmtinstall
の呼び出しが変更されました。fmtinstall('S', Sconv);
はfmtinstall('$', DSconv);
に変更されました。fmtinstall('N', Nconv);
はfmtinstall('M', Mconv);
に変更されました(list5.c
のみ)。
- これにより、
liblink
は独自のフォーマット動詞$
とM
を使用するようになり、cmd/gc
の%S
と%N
との名前空間の分離が実現されます。
-
Dconv
ルーチン内のsprint
呼び出しの変更:src/liblink/list5.c
、src/liblink/list6.c
、src/liblink/list8.c
のDconv
関数内で、sprint
のフォーマット文字列が更新されました。%N
を使用していた箇所は%M
に置き換えられました。%S
を使用していた箇所は%$
に置き換えられました。
- これは、
Dconv
が内部的に文字列やアドレスをフォーマットする際に、新しいliblink
固有のフォーマット動詞を使用するようにするためです。
-
コメントの追加:
src/liblink/list6.c
には、新しいフォーマット動詞%$
に関する説明がコメントとして追加されました。これは、この動詞が「文字列定数アドレス(内部使用のみ)」を意味することを示しています。
これらの変更により、liblink
はcmd/gc
とは独立したフォーマット動詞のセットを持つことになります。コミットメッセージにあるように、liblink
における%S
と%N
の使用は%D
ルーチン内部に限定されており、外部からは参照されていなかったため、この変更はliblink
の外部インターフェースに影響を与えることなく実施できました。
この分離は、将来的にコンパイラとリンカで共通のフォーマットルーチン(%A
, %D
, %P
, %R
など)を共有し、重複するコードを削除するための重要なステップです。これにより、Goツールチェイン全体のコードベースの保守性と一貫性が向上します。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルとコードの変更箇所は以下の通りです。
src/liblink/list5.c
--- a/src/liblink/list5.c
+++ b/src/liblink/list5.c
@@ -41,18 +41,18 @@ enum
static int Aconv(Fmt *fp);\
static int Dconv(Fmt *fp);\
-static int Nconv(Fmt *fp);\
+static int Mconv(Fmt *fp);\
static int Pconv(Fmt *fp);\
static int Rconv(Fmt *fp);\
-static int Sconv(Fmt *fp);\
+static int DSconv(Fmt *fp);\
void
listinit5(void)
{
fmtinstall('A', Aconv);
fmtinstall('P', Pconv);
-\tfmtinstall('S', Sconv);\
-\tfmtinstall('N', Nconv);\
+\tfmtinstall('$', DSconv);\
+\tfmtinstall('M', Mconv);\
fmtinstall('D', Dconv);
fmtinstall('R', Rconv);
}
@@ -139,14 +139,14 @@ Dconv(Fmt *fp)\
case D_NONE:\
str[0] = 0;\
if(a->name != D_NONE || a->reg != NREG || a->sym != nil)\
-\t\t\tsprint(str, "%N(R%d)(NONE)", a, a->reg);\
+\t\t\tsprint(str, "%M(R%d)(NONE)", a, a->reg);\
break;\
case D_CONST:\
if(a->reg != NREG)\
-\t\t\tsprint(str, "$%N(R%d)", a, a->reg);\
+\t\t\tsprint(str, "$%M(R%d)", a, a->reg);\
else
-\t\t\tsprint(str, "$%N", a);\
+\t\t\tsprint(str, "$%M", a);\
break;\
case D_CONST2:\
@@ -166,27 +166,27 @@ Dconv(Fmt *fp)\
case D_OREG:\
if(a->reg != NREG)\
-\t\t\tsprint(str, "%N(R%d)", a, a->reg);\
+\t\t\tsprint(str, "%M(R%d)", a, a->reg);\
else
-\t\t\tsprint(str, "%N", a);\
+\t\t\tsprint(str, "%M", a);\
break;\
case D_REG:\
sprint(str, "R%d", a->reg);\
if(a->name != D_NONE || a->sym != nil)\
-\t\t\tsprint(str, "%N(R%d)(REG)", a, a->reg);\
+\t\t\tsprint(str, "%M(R%d)(REG)", a, a->reg);\
break;\
case D_FREG:\
sprint(str, "F%d", a->reg);\
if(a->name != D_NONE || a->sym != nil)\
-\t\t\tsprint(str, "%N(R%d)(REG)", a, a->reg);\
+\t\t\tsprint(str, "%M(R%d)(REG)", a, a->reg);\
break;\
case D_PSR:\
sprint(str, "PSR");
if(a->name != D_NONE || a->sym != nil)\
-\t\t\tsprint(str, "%N(PSR)(REG)", a);\
+\t\t\tsprint(str, "%M(PSR)(REG)", a);\
break;\
case D_BRANCH:\
@@ -203,7 +203,7 @@ Dconv(Fmt *fp)\
break;\
case D_SCONST:\
-\t\tsprint(str, "$\"%S\"", a->u.sval);\
+\t\tsprint(str, "$\"%$\"", a->u.sval);\
break;\
}\
return fmtstrcpy(fp, str);\
@@ -242,7 +242,7 @@ Rconv(Fmt *fp)\
}\
static int\
-Sconv(Fmt *fp)\
+DSconv(Fmt *fp)\
{\
int i, c;\
char str[STRINGSZ], *p, *a;\
@@ -289,7 +289,7 @@ Sconv(Fmt *fp)\
}\
static int\
-Nconv(Fmt *fp)\
+Mconv(Fmt *fp)\
{\
char str[STRINGSZ];\
Addr *a;\
src/liblink/list6.c
--- a/src/liblink/list6.c
+++ b/src/liblink/list6.c
@@ -34,11 +34,24 @@
#include <link.h>\
#include "../cmd/6l/6.out.h"\
+//\
+// Format conversions\
+// %A int Opcodes (instruction mnemonics)\
+//\
+// %D Addr* Addresses (instruction operands)\
+// Flags: "%lD": seperate the high and low words of a constant by "-"\
+//\
+// %P Prog* Instructions\
+//\
+// %R int Registers\
+//\
+// %$ char* String constant addresses (for internal use only)\
+\
static int Aconv(Fmt *fp);\
static int Dconv(Fmt *fp);\
static int Pconv(Fmt *fp);\
static int Rconv(Fmt *fp);\
-static int Sconv(Fmt *fp);\
+static int DSconv(Fmt *fp);\
enum
{
@@ -50,7 +63,7 @@ listinit6(void)\
{\
fmtinstall('A', Aconv);\
fmtinstall('P', Pconv);\
-\tfmtinstall('S', Sconv);\
+\tfmtinstall('$', DSconv);\
fmtinstall('D', Dconv);\
fmtinstall('R', Rconv);\
}\
@@ -174,7 +187,7 @@ Dconv(Fmt *fp)\
break;\
case D_SCONST:\
-\t\tsprint(str, "$\"%S\"", a->u.sval);\
+\t\tsprint(str, "$\"%$\"", a->u.sval);\
break;\
case D_ADDR:\
@@ -337,7 +350,7 @@ Rconv(Fmt *fp)\
}\
static int\
-Sconv(Fmt *fp)\
+DSconv(Fmt *fp)\
{\
int i, c;\
char str[STRINGSZ], *p, *a;\
src/liblink/list8.c
--- a/src/liblink/list8.c
+++ b/src/liblink/list8.c
@@ -38,7 +38,7 @@ static int Aconv(Fmt *fp);\
static int Dconv(Fmt *fp);\
static int Pconv(Fmt *fp);\
static int Rconv(Fmt *fp);\
-static int Sconv(Fmt *fp);\
+static int DSconv(Fmt *fp);\
enum
{
@@ -50,7 +50,7 @@ listinit8(void)\
{\
fmtinstall('A', Aconv);\
fmtinstall('P', Pconv);\
-\tfmtinstall('S', Sconv);\
+\tfmtinstall('$', DSconv);\
fmtinstall('D', Dconv);\
fmtinstall('R', Rconv);\
}\
@@ -181,7 +181,7 @@ Dconv(Fmt *fp)\
break;\
case D_SCONST:\
-\t\tsprint(str, "$\"%S\"", a->u.sval);\
+\t\tsprint(str, "$\"%$\"", a->u.sval);\
break;\
case D_ADDR:\
@@ -298,7 +298,7 @@ Rconv(Fmt *fp)\
}\
static int\
-Sconv(Fmt *fp)\
+DSconv(Fmt *fp)\
{\
int i, c;\
char str[STRINGSZ], *p, *a;\
コアとなるコードの解説
このコミットの核心は、Goリンカ(liblink
)が内部的に使用するフォーマット動詞の命名規則を変更し、Goコンパイラ(cmd/gc
)との衝突を避けることにあります。
-
Sconv
からDSconv
へ、Nconv
からMconv
へのリネーム:src/liblink/list5.c
において、Sconv
関数はDSconv
に、Nconv
関数はMconv
にそれぞれ名前が変更されました。これらの関数は、それぞれ文字列(Sconv
)とアドレス(Nconv
)のフォーマットを担当していました。- このリネームは、関数名自体がその役割をより明確にし、かつ
cmd/gc
の同名の関数との混同を避けるための措置です。
-
fmtinstall
のフォーマット動詞の変更:fmtinstall
は、特定の文字(フォーマット動詞)と、その文字がsprint
などのフォーマット関数内で使用されたときに呼び出される関数を関連付ける役割を担っています。- 変更前は、
fmtinstall('S', Sconv);
とfmtinstall('N', Nconv);
のように、'S'
と'N'
という文字がフォーマット動詞として登録されていました。 - 変更後は、
fmtinstall('$', DSconv);
とfmtinstall('M', Mconv);
となりました。 - これにより、
liblink
は独自のフォーマット動詞'$'
と'M'
を使用するようになります。'$'
は文字列定数、'M'
はアドレス(Addr
構造体)のフォーマットに対応します。
-
Dconv
関数内のsprint
呼び出しの更新:Dconv
関数は、アドレスやオペランドをフォーマットするためのルーチンです。この関数内部では、さらに詳細な情報を表示するために、他のフォーマット動詞(旧%N
や%S
)を使用していました。- 例えば、
sprint(str, "%N(R%d)(NONE)", a, a->reg);
のような行は、sprint(str, "%M(R%d)(NONE)", a, a->reg);
に変更されました。 - また、文字列定数を扱う
D_SCONST
ケースでは、sprint(str, "$\"%S\"", a->u.sval);
がsprint(str, "$\"%$\"", a->u.sval);
に変更されました。 - これらの変更は、
liblink
が内部的に使用するフォーマット動詞が、新しい'$'
と'M'
に統一されたことを反映しています。
これらの変更は、Goツールチェインの内部的な整合性を高め、将来的なコードベースの共通化と重複排除を可能にするための重要なステップです。特に、cmd/gc
とliblink
がそれぞれ独立したフォーマット動詞の名前空間を持つことで、両者の開発がよりスムーズに進むようになります。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- このコミットのChange-ID:
49720043
(GoのコードレビューシステムGerritのID)
参考にした情報源リンク
- Go言語のソースコード(特に
src/liblink
およびsrc/cmd/gc
ディレクトリ) - Go言語のツールチェインに関するドキュメントやブログ記事(一般的なGoのコンパイラ/リンカの仕組みについて)
- C言語の
printf
フォーマット指定子に関する一般的な知識 - GoのGerritコードレビューシステムに関する情報 (例: https://go-review.googlesource.com/)