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

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

このコミットは、Goコンパイラのcmd/gc-liveという新しいデバッグフラグを追加するものです。このフラグは、Goプログラムのライブネス解析(liveness analysis)のデバッグを目的としており、特にガベージコレクション(GC)の正確性に不可欠なライブネスマップの生成過程を詳細に検査できるようにします。

コミット

commit 8bd8cede03e32c55844cfc433f66fc6da0564c8a
Author: Russ Cox <rsc@golang.org>
Date:   Tue Jan 14 10:40:16 2014 -0500

    cmd/gc: add -live flag for debugging liveness maps
    
    R=khr
    CC=golang-codereviews
    https://golang.org/cl/51820043

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

https://github.com/golang/go/commit/8bd8cede03e32c55844cfc433f66fc6da0564c8a

元コミット内容

cmd/gc: add -live flag for debugging liveness maps

R=khr
CC=golang-codereviews
https://golang.org/cl/51820043

変更の背景

Goコンパイラ(cmd/gc)は、プログラムの実行中にどの変数が「生きている」(将来使用される可能性がある)かを判断するライブネス解析を行います。この情報は、ガベージコレクタが不要なメモリを正確に解放するために不可欠な「ライブネスマップ」を生成するために使用されます。ライブネス解析は複雑なプロセスであり、特にコンパイラの最適化や新しい言語機能の導入に伴い、その正確性を検証することが重要になります。

このコミットの背景には、ライブネス解析のデバッグと検証を容易にする必要性がありました。これまでは、ライブネスマップの内部状態を詳細に検査するための直接的なメカニズムが不足していた可能性があります。-liveフラグの導入により、開発者はコンパイル時にライブネス解析の過程で生成される情報を詳細に出力させることができ、問題の特定や解析の改善に役立てることができます。

前提知識の解説

ライブネス解析 (Liveness Analysis)

ライブネス解析は、コンパイラのデータフロー解析の一種で、プログラムの特定のポイントにおいて、どの変数が「ライブ」であるか(つまり、その変数の値が将来の計算で読み取られる可能性があるか)を決定します。逆に、ライブでない変数は「デッド」と呼ばれ、その変数が占めるメモリは解放しても安全であると判断されます。

Go言語のようなガベージコレクションを持つ言語では、ライブネス解析はガベージコレクタが正確に動作するために極めて重要です。ガベージコレクタは、ライブなオブジェクトのみを保持し、デッドなオブジェクトが占めるメモリを再利用します。もしライブネス解析が誤っていると、まだ必要なオブジェクトが誤って解放されたり(Use-After-Freeバグ)、不要なオブジェクトが解放されずにメモリリークを引き起こしたりする可能性があります。

ライブネスマップ (Liveness Maps)

ライブネスマップは、特定のプログラム実行ポイント(特に関数呼び出しやGCセーフポイント)において、スタック上のどの位置にポインタが含まれており、それらのポインタがライブであるかを示すビットマップまたは同様のデータ構造です。Goランタイムのガベージコレクタは、このマップを参照して、スタックをスキャンし、ライブなポインタを識別してマークします。これにより、ヒープ上の到達可能なオブジェクトを追跡し、到達不能なオブジェクトを回収することができます。

cmd/gc

cmd/gcは、Go言語の公式コンパイラです。Goのソースコードを中間表現に変換し、最適化を行い、最終的に実行可能なバイナリを生成します。ライブネス解析は、このコンパイルプロセスの一部としてcmd/gc内で実行されます。

コンパイラフラグ

コンパイラフラグは、コンパイラの動作を制御するためのコマンドライン引数です。例えば、最適化レベルの指定、デバッグ情報の生成、特定の機能の有効/無効化などがあります。-liveフラグもその一つで、ライブネス解析のデバッグ出力を有効にするために導入されました。

技術的詳細

このコミットの主要な技術的変更点は、cmd/gcコンパイラにライブネス解析のデバッグ出力を有効にするための新しいフラグ-liveを追加したことです。

  1. debuglive変数の導入:

    • src/cmd/gc/go.hEXTERN int debuglive;が追加されました。これは、-liveフラグの状態を保持するためのグローバル変数です。EXTERNキーワードは、この変数が他のファイルで定義されていることを示します。
  2. -liveフラグの登録:

    • src/cmd/gc/lex.cmain関数内で、flagcount("live", "debug liveness analysis", &debuglive);が追加されました。flagcountは、指定されたフラグ(ここでは"live")がコマンドラインで出現するたびに、対応する整数変数(ここではdebuglive)をインクリメントする関数です。これにより、go build -gcflags="-live"のようにコンパイル時に-liveフラグを指定すると、debuglive変数が非ゼロになり、デバッグ出力が有効になります。
  3. plive.cにおけるライブネスデバッグ出力の追加:

    • src/cmd/gc/plive.cは、Goコンパイラのライブネス解析の主要なロジックを含むファイルです。このファイルに最も大きな変更が加えられました。
    • getvariables関数の変更: getvariables関数のシグネチャがgetvariables(Node *fn)からgetvariables(Node *fn, int allvalues)に変更されました。新しいallvalues引数は、ポインタを含む変数だけでなく、すべての変数(ポインタを含まないものも含む)をライブネス解析の対象として収集するかどうかを制御します。これは、デバッグ時にすべての変数のライブネス状態を検査したい場合に有用です。getvariables(fn, deadsym != nil)という呼び出し箇所から、デッド値解析(deadsymが存在する場合)が有効な際には、すべての変数を収集するようになっていることがわかります。
    • livenessepilogue関数の変更: この関数は、ライブネス解析の最終段階で、ライブネスマップを生成し、必要に応じてデッド値情報を処理します。
      • debugliveが有効な場合、livenessepilogueは、各セーフポイント(関数呼び出しやテキスト命令の開始点など、GCが実行される可能性があるポイント)で、その時点でのライブ変数の詳細なリストを標準出力に整形して出力するようになりました。
      • 出力には、行番号、命令の種類(CALLまたはTEXT)、そしてライブと判断された各変数の名前が含まれます。これは、fmtstrinit, fmtprint, fmtstrflushといったフォーマットユーティリティを使用して行われます。
      • このデバッグ出力は、ライブネス解析が期待通りに動作しているか、あるいは特定の変数が誤ってライブまたはデッドと判断されていないかを検証するのに役立ちます。

これらの変更により、Goコンパイラの開発者やGoランタイムのデバッグを行うユーザーは、ライブネス解析の内部動作をより深く理解し、潜在的な問題を診断するための強力なツールを手に入れることになります。

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

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -972,6 +972,7 @@ EXTERN	char*	flag_installsuffix;
 EXTERN	int	flag_race;
 EXTERN	int	flag_largemodel;
 EXTERN	int	noescape;
+EXTERN	int	debuglive;
 EXTERN	Link*	ctxt;
 
 EXTERN	int	nointerface;

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -264,6 +264,7 @@ main(int argc, char *argv[])
 	flagstr("installsuffix", "pkg directory suffix", &flag_installsuffix);
 	flagcount("j", "debug runtime-initialized variables", &debug['j']);
 	flagcount("l", "disable inlining", &debug['l']);
+	flagcount("live", "debug liveness analysis", &debuglive);
 	flagcount("m", "print optimization decisions", &debug['m']);
 	flagstr("o", "obj: set output file", &outfile);
 	flagstr("p", "path: set expected package import path", &myimportpath);

src/cmd/gc/plive.c

--- a/src/cmd/gc/plive.c
+++ b/src/cmd/gc/plive.c
@@ -238,10 +238,9 @@ blockany(BasicBlock *bb, int (*callback)(Prog*))
 }
 
 // Collects and returns and array of Node*s for functions arguments and local
-// variables.  TODO(cshapiro): only return pointer containing nodes if we are
-// not also generating a dead value map.
+// variables.
 static Array*
-getvariables(Node *fn)
+getvariables(Node *fn, int allvalues)
 {
 	Array *result;
 	NodeList *ll;
@@ -249,11 +248,13 @@ getvariables(Node *fn)
 	result = arraynew(0, sizeof(Node*));
 	for(ll = fn->dcl; ll != nil; ll = ll->next) {
 		if(ll->n->op == ONAME) {
-\t\t\tswitch(ll->n->class & ~PHEAP) {
+\t\t\tswitch(ll->n->class) {
 			case PAUTO:
 			case PPARAM:
 			case PPARAMOUT:
-\t\t\t\tarrayadd(result, &ll->n);
+\t\t\t\tif(haspointers(ll->n->type) || allvalues)
+\t\t\t\t\tarrayadd(result, &ll->n);
+\t\t\t\tbreak;
 			}
 		}
 	}
@@ -657,7 +658,7 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
 			case PPARAMOUT:
 				pos = arrayindexof(vars, from->node);
 				if(pos == -1)
-\t\t\t\t\tfatal("progeffects: variable %N is unknown", prog->from.node);
+\t\t\t\t\tgoto Next;
 				if(info.flags & (LeftRead | LeftAddr))
 					bvset(uevar, pos);
 				if(info.flags & LeftWrite)
@@ -666,6 +667,7 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
 			}
 		}
 	}\n+Next:\n 	if(info.flags & (RightRead | RightWrite | RightAddr)) {
 		to = &prog->to;
 		if (to->node != nil && to->sym != nil && !isfunny(to->node)) {
@@ -675,7 +677,7 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
 			case PPARAMOUT:
 				pos = arrayindexof(vars, to->node);
 				if(pos == -1)
-\t\t\t\t\tfatal("progeffects: variable %N is unknown", to->node);
+\t\t\t\t\tgoto Next1;
 				if(info.flags & (RightRead | RightAddr))
 					bvset(uevar, pos);
 				if(info.flags & RightWrite)
@@ -684,6 +686,7 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
 			}
 		}
 	}\n+Next1:;\n }
 
 // Constructs a new liveness structure used to hold the global state of the
@@ -1304,22 +1307,19 @@ static void
 livenessepilogue(Liveness *lv)
 {
 	BasicBlock *bb;
-\tBvec *livein;
-\tBvec *liveout;
-\tBvec *uevar;
-\tBvec *varkill;
-\tBvec *args;
-\tBvec *locals;
+\tBvec *livein, *liveout, *uevar, *varkill, *args, *locals;
 	Prog *p, *next;
-\tint32 i;
-\tint32 nvars;
-\tint32 pos;
+\tint32 i, j, nmsg, nvars, pos;
+\tchar **msg;
+\tFmt fmt;
 
 	nvars = arraylength(lv->vars);
 	livein = bvalloc(nvars);
 	liveout = bvalloc(nvars);
 	uevar = bvalloc(nvars);
 	varkill = bvalloc(nvars);
+\tmsg = nil;
+\tnmsg = 0;
 
 	for(i = 0; i < arraylength(lv->cfg); i++) {
 		bb = *(BasicBlock**)arrayget(lv->cfg, i);
@@ -1347,6 +1347,13 @@ livenessepilogue(Liveness *lv)
 			\t\t\t\tarrayadd(lv->deadvalues, &locals);
 			\t\t\t}
 			\t\t}\n+\t\t\n+\t\tif(debuglive) {\n+\t\t\tnmsg = arraylength(lv->livepointers);\n+\t\t\tmsg = xmalloc(nmsg*sizeof msg[0]);\n+\t\t\tfor(j=0; j<nmsg; j++)\n+\t\t\t\tmsg[j] = nil;\n+\t\t}\n \n \t\t// walk backward, emit pcdata and populate the maps
 \t\tpos = arraylength(lv->livepointers) - 1;
 
 \t\tfor(p = bb->last; p != nil; p = next) {
@@ -1372,8 +1379,35 @@ livenessepilogue(Liveness *lv)
 			\t\t}\n \t\t\tif(issafepoint(p)) {
 			\t\t\t\t// Found an interesting instruction, record the
-\t\t\t\t\t\t// corresponding liveness information.  Only
-\t\t\t\t\t\t// CALL instructions need a PCDATA annotation.
+\t\t\t\t\t\t// corresponding liveness information.  
+\n+\t\t\t\t\tif(debuglive) {\n+\t\t\t\t\t\tfmtstrinit(&fmt);\n+\t\t\t\t\t\tfmtprint(&fmt, "%L: live at ", p->lineno);\n+\t\t\t\t\t\tif(p->as == ACALL)\n+\t\t\t\t\t\t\tfmtprint(&fmt, "CALL %lS:", p->to.sym);\n+\t\t\t\t\t\telse\n+\t\t\t\t\t\t\tfmtprint(&fmt, "TEXT %lS:", p->from.sym);\n+\t\t\t\t\t\tfor(j = 0; j < arraylength(lv->vars); j++)\n+\t\t\t\t\t\t\tif(bvget(liveout, j))\n+\t\t\t\t\t\t\t\tfmtprint(&fmt, " %N", *(Node**)arrayget(lv->vars, j));\n+\t\t\t\t\t\tfmtprint(&fmt, "\\n");\n+\t\t\t\t\t\tmsg[pos] = fmtstrflush(&fmt);\n+\t\t\t\t\t}\n+\n+\t\t\t\t\t// Record live pointers.\n+\t\t\t\t\targs = *(Bvec**)arrayget(lv->argslivepointers, pos);\n+\t\t\t\t\tlocals = *(Bvec**)arrayget(lv->livepointers, pos);\n+\t\t\t\t\ttwobitlivepointermap(lv, liveout, lv->vars, args, locals);\n+\n+\t\t\t\t\t// Record dead values.\n+\t\t\t\t\tif(lv->deadvalues != nil) {\n+\t\t\t\t\t\targs = *(Bvec**)arrayget(lv->argsdeadvalues, pos);\n+\t\t\t\t\t\tlocals = *(Bvec**)arrayget(lv->deadvalues, pos);\n+\t\t\t\t\t\ttwobitdeadvaluemap(lv, liveout, lv->vars, args, locals);\n+\t\t\t\t\t}\n+\n+\t\t\t\t\t// Only CALL instructions need a PCDATA annotation.\n \t\t\t\t// The TEXT instruction annotation is implicit.
 \t\t\t\t\tif(p->as == ACALL) {
 \t\t\t\t\t\tif(isdeferreturn(p)) {
@@ -1394,21 +1428,17 @@ livenessepilogue(Liveness *lv)
 			\t\t\t\t\t}\n \t\t\t\t}\n \n-\t\t\t\t// Record live pointers.\n-\t\t\t\targs = *(Bvec**)arrayget(lv->argslivepointers, pos);\n-\t\t\t\tlocals = *(Bvec**)arrayget(lv->livepointers, pos);\n-\t\t\t\ttwobitlivepointermap(lv, liveout, lv->vars, args, locals);\n-\n-\t\t\t\t// Record dead values.\n-\t\t\t\tif(lv->deadvalues != nil) {\n-\t\t\t\t\targs = *(Bvec**)arrayget(lv->argsdeadvalues, pos);\n-\t\t\t\t\tlocals = *(Bvec**)arrayget(lv->deadvalues, pos);\n-\t\t\t\t\ttwobitdeadvaluemap(lv, liveout, lv->vars, args, locals);\n-\t\t\t\t}\n-\n \t\t\t\tpos--;
 \t\t\t}\n \t\t}\n+\t\tif(debuglive) {\n+\t\t\tfor(j=0; j<nmsg; j++) \n+\t\t\t\tif(msg[j] != nil)\n+\t\t\t\t\tprint("%s", msg[j]);\n+\t\t\tfree(msg);\n+\t\t\tmsg = nil;\n+\t\t\tnmsg = 0;\n+\t\t}\n \t}\n \n \tfree(livein);
@@ -1497,7 +1527,7 @@ liveness(Node *fn, Prog *firstp, Sym *argssym, Sym *livesym, Sym *deadsym)
 	// Construct the global liveness state.
 	cfg = newcfg(firstp);
 	if(0) printcfg(cfg);
-\tvars = getvariables(fn);\n+\tvars = getvariables(fn, deadsym != nil);\n \tlv = newliveness(fn, firstp, cfg, vars, deadsym != nil);
 
 	// Run the dataflow framework.

コアとなるコードの解説

src/cmd/gc/go.hsrc/cmd/gc/lex.c の変更

これらのファイルへの変更は、新しいデバッグフラグ-liveをGoコンパイラに統合するための標準的な手順です。go.hdebuglive変数を宣言し、lex.cでこの変数をコマンドラインフラグ-liveに紐付けます。これにより、ユーザーが-liveフラグを指定すると、debuglive変数の値が設定され、plive.c内のライブネス解析ロジックがデバッグモードで動作するようになります。

src/cmd/gc/plive.c の変更

plive.cへの変更は、ライブネス解析のデバッグ機能の中核をなします。

  1. getvariables関数の変更:

    • getvariables関数は、ライブネス解析の対象となる変数(引数とローカル変数)のリストを収集します。
    • 変更前は、ポインタを含むノードのみを対象としていた可能性があります(コメントのTODOから推測)。
    • 変更後は、allvaluesという新しい引数が追加され、haspointers(ll->n->type) || allvaluesという条件で変数を追加するようになりました。これは、allvaluestrueの場合、ポインタの有無にかかわらずすべての変数を収集することを意味します。
    • liveness関数からの呼び出しがgetvariables(fn, deadsym != nil)となっていることから、デッド値解析が有効な場合(deadsymnilでない場合)には、すべての変数を対象とすることがわかります。これは、デッド値解析のデバッグにおいて、ポインタ以外の変数の状態も確認する必要があるためと考えられます。
  2. progeffects関数の変更:

    • fatal呼び出しがgoto Nextgoto Next1に置き換えられています。fatalはプログラムを終了させるため、デバッグ中に特定の変数が不明な場合でも解析を続行できるようにするための変更と考えられます。これにより、問題が発生してもすぐにコンパイラが終了せず、より多くのデバッグ情報を収集できるようになります。
  3. livenessepilogue関数の変更:

    • この関数は、ライブネス解析の最終段階で、各命令ポイントでのライブネス情報を処理します。
    • debuglivetrueの場合、nmsgmsgという変数が導入され、デバッグメッセージを一時的に保持するためのメモリが確保されます。
    • 最も重要な変更は、セーフポイント(issafepoint(p)trueとなる命令)が見つかったときに、詳細なライブネス情報を出力するロジックが追加されたことです。
      • fmtstrinit, fmtprint, fmtstrflushを使用して、行番号、命令の種類(CALLまたはTEXT)、そしてその時点でのライブ変数(bvget(liveout, j)でライブと判断された変数)の名前が整形されて出力されます。
      • この出力は、msg配列に格納され、livenessepilogue関数の最後にまとめてprintされます。これにより、ライブネス解析の各ステップでの変数のライブ状態を時系列で追跡することが可能になり、ライブネスマップの正確性を検証する上で非常に強力なデバッグ機能となります。

これらの変更は、Goコンパイラのライブネス解析のデバッグ能力を大幅に向上させ、コンパイラの開発者がより堅牢で正確なガベージコレクションメカニズムを構築するのに貢献します。

関連リンク

  • Go言語のガベージコレクションに関する公式ドキュメントやブログ記事
  • Goコンパイラの内部構造に関するドキュメント(もしあれば)
  • データフロー解析、特にライブネス解析に関する一般的なコンパイラ理論の資料

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/cmd/gcディレクトリ)
  • Go言語の公式ドキュメント
  • コンパイラ設計に関する一般的な教科書やオンラインリソース(データフロー解析、ライブネス解析の章)
  • GitHubのコミット履歴と関連するコードレビュー(CL)

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

このコミットは、Goコンパイラのcmd/gc-liveという新しいデバッグフラグを追加するものです。このフラグは、Goプログラムのライブネス解析(liveness analysis)のデバッグを目的としており、特にガベージコレクション(GC)の正確性に不可欠なライブネスマップの生成過程を詳細に検査できるようにします。

コミット

commit 8bd8cede03e32c55844cfc433f66fc6da0564c8a
Author: Russ Cox <rsc@golang.org>
Date:   Tue Jan 14 10:40:16 2014 -0500

    cmd/gc: add -live flag for debugging liveness maps
    
    R=khr
    CC=golang-codereviews
    https://golang.org/cl/51820043

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

https://github.com/golang/go/commit/8bd8cede03e32c55844cfc433f66fc6da0564c8a

元コミット内容

cmd/gc: add -live flag for debugging liveness maps

R=khr
CC=golang-codereviews
https://golang.org/cl/51820043

変更の背景

Goコンパイラ(cmd/gc)は、プログラムの実行中にどの変数が「生きている」(将来使用される可能性がある)かを判断するライブネス解析を行います。この情報は、ガベージコレクタが不要なメモリを正確に解放するために不可欠な「ライブネスマップ」を生成するために使用されます。ライブネス解析は複雑なプロセスであり、特にコンパイラの最適化や新しい言語機能の導入に伴い、その正確性を検証することが重要になります。

このコミットの背景には、ライブネス解析のデバッグと検証を容易にする必要性がありました。これまでは、ライブネスマップの内部状態を詳細に検査するための直接的なメカニズムが不足していた可能性があります。-liveフラグの導入により、開発者はコンパイル時にライブネス解析の過程で生成される情報を詳細に出力させることができ、問題の特定や解析の改善に役立てることができます。

前提知識の解説

ライブネス解析 (Liveness Analysis)

ライブネス解析は、コンパイラのデータフロー解析の一種で、プログラムの特定のポイントにおいて、どの変数が「ライブ」であるか(つまり、その変数の値が将来の計算で読み取られる可能性があるか)を決定します。逆に、ライブでない変数は「デッド」と呼ばれ、その変数が占めるメモリは解放しても安全であると判断されます。

Go言語のようなガベージコレクションを持つ言語では、ライブネス解析はガベージコレクタが正確に動作するために極めて重要です。ガベージコレクタは、ライブなオブジェクトのみを保持し、デッドなオブジェクトが占めるメモリを再利用します。もしライブネス解析が誤っていると、まだ必要なオブジェクトが誤って解放されたり(Use-After-Freeバグ)、不要なオブジェクトが解放されずにメモリリークを引き起こしたりする可能性があります。

ライブネスマップ (Liveness Maps)

ライブネスマップは、特定のプログラム実行ポイント(特に関数呼び出しやGCセーフポイント)において、スタック上のどの位置にポインタが含まれており、それらのポインタがライブであるかを示すビットマップまたは同様のデータ構造です。Goランタイムのガベージコレクタは、このマップを参照して、スタックをスキャンし、ライブなポインタを識別してマークします。これにより、ヒープ上の到達可能なオブジェクトを追跡し、到達不能なオブジェクトを回収することができます。

cmd/gc

cmd/gcは、Go言語の公式コンパイラです。Goのソースコードを中間表現に変換し、最適化を行い、最終的に実行可能なバイナリを生成します。ライブネス解析は、このコンパイルプロセスの一部としてcmd/gc内で実行されます。

コンパイラフラグ

コンパイラフラグは、コンパイラの動作を制御するためのコマンドライン引数です。例えば、最適化レベルの指定、デバッグ情報の生成、特定の機能の有効/無効化などがあります。-liveフラグもその一つで、ライブネス解析のデバッグ出力を有効にするために導入されました。

技術的詳細

このコミットの主要な技術的変更点は、cmd/gcコンパイラにライブネス解析のデバッグ出力を有効にするための新しいフラグ-liveを追加したことです。

  1. debuglive変数の導入:

    • src/cmd/gc/go.hEXTERN int debuglive;が追加されました。これは、-liveフラグの状態を保持するためのグローバル変数です。EXTERNキーワードは、この変数が他のファイルで定義されていることを示します。
  2. -liveフラグの登録:

    • src/cmd/gc/lex.cmain関数内で、flagcount("live", "debug liveness analysis", &debuglive);が追加されました。flagcountは、指定されたフラグ(ここでは"live")がコマンドラインで出現するたびに、対応する整数変数(ここではdebuglive)をインクリメントする関数です。これにより、go build -gcflags="-live"のようにコンパイル時に-liveフラグを指定すると、debuglive変数が非ゼロになり、デバッグ出力が有効になります。
  3. plive.cにおけるライブネスデバッグ出力の追加:

    • src/cmd/gc/plive.cは、Goコンパイラのライブネス解析の主要なロジックを含むファイルです。このファイルに最も大きな変更が加えられました。
    • getvariables関数の変更: getvariables関数のシグネチャがgetvariables(Node *fn)からgetvariables(Node *fn, int allvalues)に変更されました。新しいallvalues引数は、ポインタを含む変数だけでなく、すべての変数(ポインタを含まないものも含む)をライブネス解析の対象として収集するかどうかを制御します。これは、デバッグ時にすべての変数のライブネス状態を検査したい場合に有用です。getvariables(fn, deadsym != nil)という呼び出し箇所から、デッド値解析(deadsymが存在する場合)が有効な際には、すべての変数を収集するようになっていることがわかります。
    • livenessepilogue関数の変更: この関数は、ライブネス解析の最終段階で、ライブネスマップを生成し、必要に応じてデッド値情報を処理します。
      • debugliveが有効な場合、livenessepilogueは、各セーフポイント(関数呼び出しやテキスト命令の開始点など、GCが実行される可能性があるポイント)で、その時点でのライブ変数の詳細なリストを標準出力に整形して出力するようになりました。
      • 出力には、行番号、命令の種類(CALLまたはTEXT)、そしてライブと判断された各変数の名前が含まれます。これは、fmtstrinit, fmtprint, fmtstrflushといったフォーマットユーティリティを使用して行われます。
      • このデバッグ出力は、ライブネス解析が期待通りに動作しているか、あるいは特定の変数が誤ってライブまたはデッドと判断されていないかを検証するのに役立ちます。

これらの変更により、Goコンパイラの開発者やGoランタイムのデバッグを行うユーザーは、ライブネス解析の内部動作をより深く理解し、潜在的な問題を診断するための強力なツールを手に入れることになります。

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

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -972,6 +972,7 @@ EXTERN	char*	flag_installsuffix;
 EXTERN	int	flag_race;
 EXTERN	int	flag_largemodel;
 EXTERN	int	noescape;
+EXTERN	int	debuglive;
 EXTERN	Link*	ctxt;
 
 EXTERN	int	nointerface;

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -264,6 +264,7 @@ main(int argc, char *argv[])
 	flagstr("installsuffix", "pkg directory suffix", &flag_installsuffix);
 	flagcount("j", "debug runtime-initialized variables", &debug['j']);
 	flagcount("l", "disable inlining", &debug['l']);
+	flagcount("live", "debug liveness analysis", &debuglive);
 	flagcount("m", "print optimization decisions", &debug['m']);
 	flagstr("o", "obj: set output file", &outfile);
 	flagstr("p", "path: set expected package import path", &myimportpath);

src/cmd/gc/plive.c

--- a/src/cmd/gc/plive.c
+++ b/src/cmd/gc/plive.c
@@ -238,10 +238,9 @@ blockany(BasicBlock *bb, int (*callback)(Prog*))
 }
 
 // Collects and returns and array of Node*s for functions arguments and local
-// variables.  TODO(cshapiro): only return pointer containing nodes if we are
-// not also generating a dead value map.
+// variables.
 static Array*
-getvariables(Node *fn)
+getvariables(Node *fn, int allvalues)
 {
 	Array *result;
 	NodeList *ll;
@@ -249,11 +248,13 @@ getvariables(Node *fn)
 	result = arraynew(0, sizeof(Node*));
 	for(ll = fn->dcl; ll != nil; ll = ll->next) {
 		if(ll->n->op == ONAME) {
-\t\t\tswitch(ll->n->class & ~PHEAP) {
+\t\t\tswitch(ll->n->class) {
 			case PAUTO:
 			case PPARAM:
 			case PPARAMOUT:
-\t\t\t\tarrayadd(result, &ll->n);
+\t\t\t\tif(haspointers(ll->n->type) || allvalues)
+\t\t\t\t\tarrayadd(result, &ll->n);
+\t\t\t\tbreak;
 			}
 		}
 	}
@@ -657,7 +658,7 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
 			case PPARAMOUT:
 				pos = arrayindexof(vars, from->node);
 				if(pos == -1)
-\t\t\t\t\tfatal("progeffects: variable %N is unknown", prog->from.node);
+\t\t\t\t\tgoto Next;
 				if(info.flags & (LeftRead | LeftAddr))
 					bvset(uevar, pos);
 				if(info.flags & LeftWrite)
@@ -666,6 +667,7 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
 			}
 		}
 	}\n+Next:\n 	if(info.flags & (RightRead | RightWrite | RightAddr)) {
 		to = &prog->to;
 		if (to->node != nil && to->sym != nil && !isfunny(to->node)) {
@@ -675,7 +677,7 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
 			case PPARAMOUT:
 				pos = arrayindexof(vars, to->node);
 				if(pos == -1)
-\t\t\t\t\tfatal("progeffects: variable %N is unknown", to->node);
+\t\t\t\t\tgoto Next1;
 				if(info.flags & (RightRead | RightAddr))
 					bvset(uevar, pos);
 				if(info.flags & RightWrite)
@@ -684,6 +686,7 @@ progeffects(Prog *prog, Array *vars, Bvec *uevar, Bvec *varkill)
 			}
 		}
 	}\n+Next1:;\n }
 
 // Constructs a new liveness structure used to hold the global state of the
@@ -1304,22 +1307,19 @@ static void
 livenessepilogue(Liveness *lv)
 {
 	BasicBlock *bb;
-\tBvec *livein;
-\tBvec *liveout;
-\tBvec *uevar;
-\tBvec *varkill;
-\tBvec *args;
-\tBvec *locals;
+\tBvec *livein, *liveout, *uevar, *varkill, *args, *locals;
 	Prog *p, *next;
-\tint32 i;
-\tint32 nvars;
-\tint32 pos;
+\tint32 i, j, nmsg, nvars, pos;
+\tchar **msg;
+\tFmt fmt;
 
 	nvars = arraylength(lv->vars);
 	livein = bvalloc(nvars);
 	liveout = bvalloc(nvars);
 	uevar = bvalloc(nvars);
 	varkill = bvalloc(nvars);
+\tmsg = nil;
+\tnmsg = 0;
 
 	for(i = 0; i < arraylength(lv->cfg); i++) {
 		bb = *(BasicBlock**)arrayget(lv->cfg, i);
@@ -1347,6 +1347,13 @@ livenessepilogue(Liveness *lv)
 			\t\t\t\tarrayadd(lv->deadvalues, &locals);
 			\t\t\t}
 			\t\t}\n+\t\t\n+\t\tif(debuglive) {\n+\t\t\tnmsg = arraylength(lv->livepointers);\n+\t\t\tmsg = xmalloc(nmsg*sizeof msg[0]);\n+\t\t\tfor(j=0; j<nmsg; j++)\n+\t\t\t\tmsg[j] = nil;\n+\t\t}\n \n \t\t// walk backward, emit pcdata and populate the maps
 \t\tpos = arraylength(lv->livepointers) - 1;
 
 \t\tfor(p = bb->last; p != nil; p = next) {
@@ -1372,8 +1379,35 @@ livenessepilogue(Liveness *lv)
 			\t\t}\n \t\t\tif(issafepoint(p)) {
 			\t\t\t\t// Found an interesting instruction, record the
-\t\t\t\t\t\t// corresponding liveness information.  Only
-\t\t\t\t\t\t// CALL instructions need a PCDATA annotation.
+\t\t\t\t\t\t// corresponding liveness information.  
+\n+\t\t\t\t\tif(debuglive) {\n+\t\t\t\t\t\tfmtstrinit(&fmt);\n+\t\t\t\t\t\tfmtprint(&fmt, "%L: live at ", p->lineno);\n+\t\t\t\t\t\tif(p->as == ACALL)\n+\t\t\t\t\t\t\tfmtprint(&fmt, "CALL %lS:", p->to.sym);\n+\t\t\t\t\t\telse\n+\t\t\t\t\t\t\tfmtprint(&fmt, "TEXT %lS:", p->from.sym);\n+\t\t\t\t\t\tfor(j = 0; j < arraylength(lv->vars); j++)\n+\t\t\t\t\t\t\tif(bvget(liveout, j))\n+\t\t\t\t\t\t\t\tfmtprint(&fmt, " %N", *(Node**)arrayget(lv->vars, j));\n+\t\t\t\t\t\tfmtprint(&fmt, "\\n");\n+\t\t\t\t\t\tmsg[pos] = fmtstrflush(&fmt);\n+\t\t\t\t\t}\n+\n+\t\t\t\t\t// Record live pointers.\n+\t\t\t\t\targs = *(Bvec**)arrayget(lv->argslivepointers, pos);\n+\t\t\t\t\tlocals = *(Bvec**)arrayget(lv->livepointers, pos);\n+\t\t\t\t\ttwobitlivepointermap(lv, liveout, lv->vars, args, locals);\n+\n+\t\t\t\t\t// Record dead values.\n+\t\t\t\t\tif(lv->deadvalues != nil) {\n+\t\t\t\t\t\targs = *(Bvec**)arrayget(lv->argsdeadvalues, pos);\n+\t\t\t\t\t\tlocals = *(Bvec**)arrayget(lv->deadvalues, pos);\n+\t\t\t\t\t\ttwobitdeadvaluemap(lv, liveout, lv->vars, args, locals);\n+\t\t\t\t\t}\n+\n+\t\t\t\t\t// Only CALL instructions need a PCDATA annotation.\n \t\t\t\t// The TEXT instruction annotation is implicit.
 \t\t\t\t\tif(p->as == ACALL) {
 \t\t\t\t\t\tif(isdeferreturn(p)) {
@@ -1394,21 +1428,17 @@ livenessepilogue(Liveness *lv)
 			\t\t\t\t\t}\n \t\t\t\t}\n \n-\t\t\t\t// Record live pointers.\n-\t\t\t\targs = *(Bvec**)arrayget(lv->argslivepointers, pos);\n-\t\t\t\tlocals = *(Bvec**)arrayget(lv->livepointers, pos);\n-\t\t\t\ttwobitlivepointermap(lv, liveout, lv->vars, args, locals);\n-\n-\t\t\t\t// Record dead values.\n-\t\t\t\tif(lv->deadvalues != nil) {\n-\t\t\t\t\targs = *(Bvec**)arrayget(lv->argsdeadvalues, pos);\n-\t\t\t\t\tlocals = *(Bvec**)arrayget(lv->deadvalues, pos);\n-\t\t\t\t\ttwobitdeadvaluemap(lv, liveout, lv->vars, args, locals);\n-\t\t\t\t}\n-\n \t\t\t\tpos--;
 \t\t\t}\n \t\t}\n+\t\tif(debuglive) {\n+\t\t\tfor(j=0; j<nmsg; j++) \n+\t\t\t\tif(msg[j] != nil)\n+\t\t\t\t\tprint("%s", msg[j]);\n+\t\t\tfree(msg);\n+\t\t\tmsg = nil;\n+\t\t\tnmsg = 0;\n+\t\t}\n \t}\n \n \tfree(livein);
@@ -1497,7 +1527,7 @@ liveness(Node *fn, Prog *firstp, Sym *argssym, Sym *livesym, Sym *deadsym)
 	// Construct the global liveness state.
 	cfg = newcfg(firstp);
 	if(0) printcfg(cfg);
-\tvars = getvariables(fn);\n+\tvars = getvariables(fn, deadsym != nil);\n \tlv = newliveness(fn, firstp, cfg, vars, deadsym != nil);
 
 	// Run the dataflow framework.

コアとなるコードの解説

src/cmd/gc/go.hsrc/cmd/gc/lex.c の変更

これらのファイルへの変更は、新しいデバッグフラグ-liveをGoコンパイラに統合するための標準的な手順です。go.hdebuglive変数を宣言し、lex.cでこの変数をコマンドラインフラグ-liveに紐付けます。これにより、ユーザーが-liveフラグを指定すると、debuglive変数の値が設定され、plive.c内のライブネス解析ロジックがデバッグモードで動作するようになります。

src/cmd/gc/plive.c の変更

plive.cへの変更は、ライブネス解析のデバッグ機能の中核をなします。

  1. getvariables関数の変更:

    • getvariables関数は、ライブネス解析の対象となる変数(引数とローカル変数)のリストを収集します。
    • 変更前は、ポインタを含むノードのみを対象としていた可能性があります(コメントのTODOから推測)。
    • 変更後は、allvaluesという新しい引数が追加され、haspointers(ll->n->type) || allvaluesという条件で変数を追加するようになりました。これは、allvaluestrueの場合、ポインタの有無にかかわらずすべての変数を収集することを意味します。
    • liveness関数からの呼び出しがgetvariables(fn, deadsym != nil)となっていることから、デッド値解析が有効な場合(deadsymnilでない場合)には、すべての変数を対象とすることがわかります。これは、デッド値解析のデバッグにおいて、ポインタ以外の変数の状態も確認する必要があるためと考えられます。
  2. progeffects関数の変更:

    • fatal呼び出しがgoto Nextgoto Next1に置き換えられています。fatalはプログラムを終了させるため、デバッグ中に特定の変数が不明な場合でも解析を続行できるようにするための変更と考えられます。これにより、問題が発生してもすぐにコンパイラが終了せず、より多くのデバッグ情報を収集できるようになります。
  3. livenessepilogue関数の変更:

    • この関数は、ライブネス解析の最終段階で、各命令ポイントでのライブネス情報を処理します。
    • debuglivetrueの場合、nmsgmsgという変数が導入され、デバッグメッセージを一時的に保持するためのメモリが確保されます。
    • 最も重要な変更は、セーフポイント(issafepoint(p)trueとなる命令)が見つかったときに、詳細なライブネス情報を出力するロジックが追加されたことです。
      • fmtstrinit, fmtprint, fmtstrflushを使用して、行番号、命令の種類(CALLまたはTEXT)、そしてその時点でのライブ変数(bvget(liveout, j)でライブと判断された変数)の名前が整形されて出力されます。
      • この出力は、msg配列に格納され、livenessepilogue関数の最後にまとめてprintされます。これにより、ライブネス解析の各ステップでの変数のライブ状態を時系列で追跡することが可能になり、ライブネスマップの正確性を検証する上で非常に強力なデバッグ機能となります。

これらの変更は、Goコンパイラのライブネス解析のデバッグ能力を大幅に向上させ、コンパイラの開発者がより堅牢で正確なガベージコレクションメカニズムを構築するのに貢献します。

関連リンク

  • Go言語のガベージコレクションに関する公式ドキュメントやブログ記事
  • Goコンパイラの内部構造に関するドキュメント(もしあれば)
  • データフロー解析、特にライブネス解析に関する一般的なコンパイラ理論の資料

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/cmd/gcディレクトリ)
  • Go言語の公式ドキュメント
  • コンパイラ設計に関する一般的な教科書やオンラインリソース(データフロー解析、ライブネス解析の章)
  • GitHubのコミット履歴と関連するコードレビュー(CL)
  • Go compiler cmd/gc liveness analysis - Google Search Results (https://www.google.com/search?q=Go+compiler+cmd%2Fgc+liveness+analysis)