[インデックス 10971] ファイルの概要
このコミットは、Goコンパイラ(gc
)におけるインライン化された関数の行番号の扱いを改善するものです。具体的には、インライン化によって生成されたコードのデバッグ情報における行番号の正確性を向上させ、デバッグ時のユーザー体験を改善することを目的としています。
コミット
commit 86deacc0bc271a7188db6f4413be6491f013f233
Author: Luuk van Dijk <lvd@golang.org>
Date: Thu Dec 22 17:31:54 2011 +0100
gc: better linenumbers for inlined functions
Fixes #2580 up to a point.
R=rsc
CC=golang-dev
https://golang.org/cl/5498068
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/86deacc0bc271a7188db6f4413be6491f013f233
元コミット内容
gc: better linenumbers for inlined functions
Fixes #2580 up to a point.
R=rsc
CC=golang-dev
https://golang.org/cl/5498068
変更の背景
このコミットの背景には、Goコンパイラが関数をインライン化する際に発生していた、デバッグ情報の行番号の不正確さという問題があります。具体的には、GoのIssue #2580「inlined code has invalid line numbers」(インライン化されたコードの行番号が不正である)がこの問題を示しています。
関数がインライン化されると、呼び出し元のコードに関数の本体が直接埋め込まれます。これにより、関数呼び出しのオーバーヘッドが削減され、パフォーマンスが向上する可能性があります。しかし、デバッグ時には、インライン化されたコードが元のソースファイルのどの行に対応するのかを正確に追跡することが重要になります。もし行番号が正しくない場合、デバッガでステップ実行したり、スタックトレースを解析したりする際に、ユーザーは混乱し、問題の特定が困難になります。
このコミットは、この行番号の不正確さを解消し、インライン化された関数に対してもより正確なデバッグ情報を提供することを目指しています。「up to a point」(ある程度まで)という表現は、このコミットが問題の完全な解決ではなく、段階的な改善であることを示唆しています。
前提知識の解説
Goコンパイラ (gc
)
Go言語の公式コンパイラであり、Goソースコードを機械語に変換する役割を担っています。gc
は最適化の一環として関数のインライン化を行います。
関数のインライン化 (Function Inlining)
コンパイラ最適化の一種で、関数呼び出しの代わりに、呼び出される関数の本体を呼び出し元のコードに直接挿入する手法です。 メリット:
- 関数呼び出しのオーバーヘッド(スタックフレームの作成、引数のプッシュ、戻り値の処理など)を削減し、実行速度を向上させます。
- インライン化されたコードに対して、さらにコンパイラが最適化を適用できる機会が増えます。 デメリット:
- コードサイズが増加する可能性があります(特に小さな関数が多数インライン化される場合)。
- デバッグが難しくなることがあります。元の関数呼び出しのコンテキストが失われたり、行番号が混乱したりするためです。
行番号 (Line Numbers)
ソースコードの各行に対応する番号です。デバッグ情報において非常に重要であり、エラーメッセージ、スタックトレース、デバッガでのステップ実行などで、プログラムの実行がソースコードのどの位置にあるかを示すために使用されます。
抽象構文木 (Abstract Syntax Tree, AST)
ソースコードの抽象的な構文構造を木構造で表現したものです。コンパイラはソースコードをパースしてASTを構築し、このASTに対して様々な解析や変換(最適化など)を行います。Goコンパイラも内部でASTを扱っており、Node
はASTの各ノードを表す構造体です。
Node
とlineno
GoコンパイラのASTにおけるNode
構造体は、プログラムの各要素(変数、式、文、関数など)を表します。Node
には通常、そのノードがソースコードのどの行に対応するかを示すlineno
(行番号)フィールドが含まれています。
ONAME
ノード
GoコンパイラのASTにおけるONAME
は、名前(識別子)を表すノードの一種です。変数名、関数名などがこれに該当します。
技術的詳細
このコミットは、Goコンパイラのインライン化処理を行うsrc/cmd/gc/inl.c
ファイルに修正を加えています。主な変更点は、インライン化されたコードのASTノードに行番号を適切に伝播させるための新しいヘルパー関数setlno
とsetlnolist
の導入です。
以前のmkinlcall
関数では、インライン化された関数の呼び出しを表すcall
ノードのlineno
を、呼び出し元のノードn
のlineno
に直接設定していました。しかし、これだけではインライン化された関数の本体内の各ノードに正しい行番号が伝播されず、デバッグ時に問題が生じていました。
新しいsetlno
関数は、与えられたNode
とその子ノード(left
, right
, list
, rlist
, ninit
, ntest
, nincr
, nbody
, nelse
など)を再帰的に走査し、指定された行番号lno
を設定します。重要なのは、ONAME
ノードの場合、既存のlineno
が0
(つまり、新しく合成されたノードである可能性が高い)でない限り、lineno
を上書きしないという条件がある点です。これは、コンパイラが内部的に生成した一時的な名前など、ソースコードに直接対応しないノードの行番号を不必要に変更しないための配慮と考えられます。
setlnolist
関数は、NodeList
(Node
のリンクリスト)を走査し、リスト内の各Node
に対してsetlno
を呼び出します。
これらの関数を導入することで、mkinlcall
がインライン化された関数のASTを構築した後、setlno(call, n->lineno);
を呼び出すことで、インライン化された関数全体のASTツリーに対して、呼び出し元の行番号を適切に「塗りつける」ことができるようになります。これにより、デバッガがインライン化されたコードをステップ実行する際に、より意味のある行番号情報を提供できるようになります。
コアとなるコードの変更箇所
変更はsrc/cmd/gc/inl.c
ファイルに集中しています。
-
static void setlno(Node*, int);
の追加:@@ -28,6 +28,8 @@ static Node* newlabel(void); static Node* inlsubst(Node *n); static NodeList* inlsubstlist(NodeList *ll); +static void setlno(Node*, int); + // Used during inlsubst[list] static Node *inlfn; // function currently being inlined static Node *inlretlabel; // target of the goto substituted in place of a return
setlno
関数の前方宣言が追加されました。 -
mkinlcall
関数内の変更:@@ -496,9 +498,10 @@ mkinlcall(Node **np, Node *fn)\n call->nbody = body;\n call->rlist = inlretvars;\n call->type = n->type;\n-\tcall->lineno = n->lineno;\n \tcall->typecheck = 1;\n \n+\tsetlno(call, n->lineno);\n+\n \t*np = call;\n \n \tinlfn = saveinlfn;
call->lineno = n->lineno;
の直接代入が削除され、代わりにsetlno(call, n->lineno);
が呼び出されています。これにより、call
ノードだけでなく、その子孫ノードにも行番号が再帰的に設定されるようになります。 -
setlnolist
関数の追加:@@ -686,3 +689,32 @@ inlsubst(Node *n)\n \n return m;\n }\n+\n+// Plaster over linenumbers\n+static void\n+setlnolist(NodeList *ll, int lno)\n+{\n+\tfor(;ll;ll=ll->next)\n+\t\tsetlno(ll->n, lno);\n+}\n ``` `NodeList`内の各ノードに対して`setlno`を呼び出すヘルパー関数が追加されました。
-
setlno
関数の追加:+static void\n+setlno(Node *n, int lno)\n+{\n+\tif(!n)\n+\t\treturn;\n+\n+\t// don\'t clobber names, unless they\'re freshly synthesized\n+\tif(n->op != ONAME || n->lineno == 0)\n+\t\tn->lineno = lno;\n+\t\n+\tsetlno(n->left, lno);\n+\tsetlno(n->right, lno);\n+\tsetlnolist(n->list, lno);\n+\tsetlnolist(n->rlist, lno);\n+\tsetlnolist(n->ninit, lno);\n+\tsetlno(n->ntest, lno);\n+\tsetlno(n->nincr, lno);\n+\tsetlnolist(n->nbody, lno);\n+\tsetlnolist(n->nelse, lno);\n+}\n ``` ASTノードを再帰的に走査し、行番号を設定する主要なロジックが実装されています。`ONAME`ノードに対する特別な処理が含まれています。
コアとなるコードの解説
setlno
関数
static void
setlno(Node *n, int lno)
{
if(!n)
return;
// don't clobber names, unless they're freshly synthesized
if(n->op != ONAME || n->lineno == 0)
n->lineno = lno;
setlno(n->left, lno);
setlno(n->right, lno);
setlnolist(n->list, lno);
setlnolist(n->rlist, lno);
setlnolist(n->ninit, lno);
setlno(n->ntest, lno);
setlno(n->nincr, lno);
setlnolist(n->nbody, lno);
setlnolist(n->nelse, lno);
}
この関数は、GoコンパイラのASTノードn
に対して、指定された行番号lno
を設定します。
if(!n) return;
: nullポインタチェック。if(n->op != ONAME || n->lineno == 0) n->lineno = lno;
: この条件がこの関数の核心です。n->op != ONAME
: 現在のノードがONAME
(名前を表すノード)でない場合、無条件に行番号を設定します。n->lineno == 0
: 現在のノードがONAME
であっても、そのlineno
が0
である場合(これはコンパイラによって新しく合成されたノードであることを示唆します)、行番号を設定します。- この条件により、ソースコードに直接対応する既存の
ONAME
ノードの行番号が、インライン化によって不適切に上書きされることを防ぎつつ、インライン化によって生成された新しいノードには適切な行番号が割り当てられるようになります。
- 続く
setlno
やsetlnolist
の呼び出しは、現在のノードの子ノードや関連するノードリストに対して再帰的に同じ行番号設定処理を適用し、ASTツリー全体に行番号を伝播させます。これにより、インライン化された関数の本体内のすべての要素が、呼び出し元の行番号を持つことになります。
setlnolist
関数
static void
setlnolist(NodeList *ll, int lno)
{
for(;ll;ll=ll->next)
setlno(ll->n, lno);
}
この関数は、NodeList
(Node
のリンクリスト)を受け取り、リスト内の各Node
に対してsetlno
関数を呼び出します。これは、複数のノードがリストとして管理されている場合に、それらすべてに行番号を適用するためのユーティリティ関数です。
mkinlcall
関数内の変更
mkinlcall
は、インライン化された関数呼び出しを表すASTノードを構築する関数です。
以前はcall->lineno = n->lineno;
と直接代入していましたが、これはcall
ノード自体にしか行番号を設定しませんでした。
setlno(call, n->lineno);
に変更することで、call
ノードをルートとするインライン化された関数のASTサブツリー全体に、呼び出し元の行番号n->lineno
が再帰的に適用されるようになります。これにより、インライン化されたコードのデバッグ情報がより正確になります。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/86deacc0bc271a7188db6f4413be6491f013f233
- Go Issue #2580: https://github.com/golang/go/issues/2580 (Web検索結果より推測されるリンク)
- Go Changelist 5498068: https://golang.org/cl/5498068
参考にした情報源リンク
- Go Issue #2580に関するWeb検索結果
- Go言語のコンパイラに関する一般的な知識
- 抽象構文木(AST)に関する一般的な知識
- 関数のインライン化に関する一般的な知識
src/cmd/gc/inl.c
のコード構造とGoコンパイラの内部動作に関する推測