[インデックス 19556] ファイルの概要
このコミットは、Goコンパイラにおけるgo:nosplitアノテーション(プラグマ)の機能を再導入するものです。以前のコミットで、Windowsビルドの不具合の原因と誤解され一時的に無効化されていましたが、その後の調査でgo:nosplitが問題ではないと判明したため、このコミットでその機能が元に戻されました。これにより、特定の関数がスタックの拡張チェックを行わないようにコンパイラに指示する能力が回復し、Goランタイムの低レベルな部分でのパフォーマンスと安全性が確保されます。
コミット
commit 5ce6d3e03e48cd453e414a0f64090004af9f319a
Author: Keith Randall <khr@golang.org>
Date: Tue Jun 17 08:10:21 2014 -0700
undo CL 105260044 / afd6f214cc81
The go:nosplit change wasn't the problem, reinstating.
««« original CL description
undo CL 93380044 / 7f0999348917
Partial undo, just of go:nosplit annotation. Somehow it
is breaking the windows builders.
TBR=bradfitz
««« original CL description
runtime: implement string ops in Go
Also implement go:nosplit annotation. Not really needed
for now, but we'll definitely need it for other conversions.
benchmark old ns/op new ns/op delta
BenchmarkRuneIterate 534 474 -11.24%
BenchmarkRuneIterate2 535 470 -12.15%
LGTM=bradfitz
R=golang-codereviews, dave, bradfitz, minux
CC=golang-codereviews
https://golang.org/cl/93380044
»»»
TBR=bradfitz
CC=golang-codereviews
https://golang.org/cl/105260044
»»»
TBR=bradfitz
R=bradfitz, golang-codereviews
CC=golang-codereviews
https://golang.org/cl/103490043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/5ce6d3e03e48cd453e414a0f64090004af9f319a
元コミット内容
このコミットは、以前の変更セット(CL 105260044 / afd6f214cc81)を元に戻すものです。そのCLは、go:nosplitの変更がWindowsビルドを壊していると誤解されたために行われました。しかし、実際にはgo:nosplitが問題ではなかったため、このコミットでその機能を再有効化しています。
元々、go:nosplitアノテーションは、Goで文字列操作を実装する際に導入されました(CL 93380044 / 7f0999348917)。このアノテーションは、将来的により多くの変換で必要になると考えられていました。ベンチマークでは、BenchmarkRuneIterateとBenchmarkRuneIterate2でそれぞれ11.24%と12.15%の性能向上が見られました。
変更の背景
このコミットの背景には、Goランタイムのスタック管理とコンパイラプラグマの進化があります。
-
go:nosplitの導入 (CL 93380044): Goのランタイムは、ゴルーチンごとに小さなスタックを割り当て、必要に応じて動的に拡張する「スタック分割(stack splitting)」というメカニズムを採用しています。しかし、ガベージコレクションやシグナルハンドリング、あるいは非常に頻繁に呼び出される低レベルな関数など、特定の状況下ではスタック分割チェックのオーバーヘッドが問題になったり、スタックを直接操作するためにスタック分割が安全でなかったりする場合があります。go:nosplitプラグマは、このような関数に対してコンパイラにスタック分割チェックを挿入しないように指示するために導入されました。これにより、これらの関数の実行パスが最適化され、低レイテンシが保証されます。このプラグマは、Goで文字列操作を実装する際に初めて導入され、パフォーマンスの向上が確認されました。 -
go:nosplitの一時的な無効化 (CL 105260044):go:nosplitが導入された後、Windowsビルドで問題が発生しました。当時の開発者は、この問題の原因がgo:nosplitアノテーションにあると推測し、一時的にその機能を無効化するコミット(CL 105260044)を行いました。これは、問題の切り分けとビルドの安定化を目的とした緊急措置でした。 -
go:nosplitの再有効化 (本コミット CL 103490043): その後の調査で、Windowsビルドの問題はgo:nosplitとは無関係であることが判明しました。go:nosplitはGoランタイムの重要な最適化機能であり、不要な無効化は性能低下や将来的な開発の妨げとなるため、このコミットでgo:nosplitの機能が元に戻されることになりました。これは、Goのコンパイラとランタイムの安定性を確保しつつ、必要な最適化を維持するための判断です。
前提知識の解説
1. Goのスタック管理とスタック分割 (Stack Splitting)
Goのゴルーチンは、非常に軽量な並行処理の単位です。各ゴルーチンは、最初は非常に小さなスタック(通常は数KB)を持って開始します。これは、数百万ものゴルーチンを同時に実行できるようにするためです。しかし、関数呼び出しが深くなったり、大きなローカル変数が割り当てられたりすると、スタック領域が不足する可能性があります。
Goランタイムは、この問題を解決するために「スタック分割(Stack Splitting)」というメカニズムを採用しています。これは、関数が呼び出される際に、その関数のプロローグ(関数の冒頭部分)で現在のスタックに残っている領域が十分であるかをチェックするコードを挿入するものです。もしスタックが不足していると判断された場合、ランタイムはより大きな新しいスタックを割り当て、古いスタックの内容を新しいスタックにコピーし、実行を新しいスタックに切り替えます。このプロセスは、Goのユーザーからは透過的に行われます。
2. go:nosplitプラグマ
go:nosplitは、Goコンパイラ(gc)に対する特別な指示(プラグマまたはアノテーション)です。このプラグマが関数宣言の直前に記述されている場合、コンパイラはその関数に対してスタック分割チェックのコードを挿入しません。
このプラグマは、主に以下のような状況で使用されます。
- 低レベルなランタイム関数: ガベージコレクタやスケジューラなど、Goランタイムの非常に低レベルな部分で動作する関数は、スタックを直接操作したり、スタック分割チェック自体がデッドロックや再帰的なスタック拡張を引き起こす可能性があるため、
go:nosplitが適用されます。 - パフォーマンスクリティカルな関数: 非常に頻繁に呼び出され、スタック分割チェックのオーバーヘッドが無視できないほど大きい関数に対して、性能向上のために使用されることがあります。
- シグナルハンドラ: シグナルハンドラは非同期に呼び出されるため、スタックの状態が予測困難な場合があり、スタック分割チェックが安全でないことがあります。
go:nosplitが適用された関数は、その関数が実行される間、スタックが拡張されないことを保証する必要があります。もしスタックが不足した場合、プログラムはクラッシュする可能性があります。そのため、このプラグマは慎重に使用され、通常はGoランタイムの内部でのみ見られます。
3. Goコンパイラ (gc) の構造
Goコンパイラ(gc)は、Go言語のソースコードを機械語に変換する役割を担っています。その内部は複数のステージに分かれており、このコミットで変更されたファイルは、主にコンパイラのフロントエンドと中間表現の処理に関連しています。
src/cmd/gc/: Goコンパイラのソースコードが格納されているディレクトリです。go.h: コンパイラ内部で使用されるデータ構造や定数の定義が含まれるヘッダファイルです。go.y: Yacc(Yet Another Compiler Compiler)の文法定義ファイルで、Go言語の構文解析ルールが記述されています。これにより、ソースコードが抽象構文木(AST)に変換されます。lex.c: 字句解析器(lexer)の実装ファイルで、ソースコードをトークンに分割する役割を担います。go:nosplitのようなプラグマもここで認識されます。pgen.c: プログラム生成(program generation)に関連するファイルで、ASTから最終的な機械語コードを生成する過程で、関数のプロパティ(例:TEXTFLAG)を設定します。fmt.c: 型のフォーマットなど、コンパイラの出力に関連する処理を行うファイルです。y.tab.c:go.yから生成されるC言語のソースファイルで、実際の構文解析ロジックが含まれます。
4. TEXTFLAG
Goランタイムの関数は、コンパイル時に特定の属性を持つことができます。これらの属性は、生成されるアセンブリコードの関数のヘッダ部分にTEXTFLAGとして埋め込まれます。NOSPLITは、その関数がスタック分割チェックを行わないことを示すフラグです。コンパイラは、go:nosplitプラグマを検出すると、対応する関数のTEXTFLAGにNOSPLITを設定します。
技術的詳細
このコミットは、go:nosplitプラグマをGoコンパイラ(gc)に再統合するための複数の変更を含んでいます。これらの変更は、字句解析、構文解析、中間表現、そして最終的なコード生成の各段階に影響を与えます。
-
src/cmd/gc/go.hの変更:Node構造体にuchar nosplit;フィールドが追加されました。Node構造体は、Goの抽象構文木(AST)における各ノード(関数、変数、式など)を表すために使用されます。nosplitフィールドは、そのノードが関数である場合に、スタック分割チェックを無効にする必要があるかどうかを示すフラグとして機能します。EXTERN int nosplit;が追加されました。これは、go:nosplitプラグマの状態をグローバルに保持するための変数です。
-
src/cmd/gc/lex.cの変更:- 字句解析器(lexer)に
go:nosplitという文字列を認識するロジックが追加されました。strcmp(lexbuf, "go:nosplit") == 0という条件で、入力ストリームからgo:nosplitというプラグマが読み取られた場合に、グローバル変数nosplitを1に設定します。これにより、コンパイラは後続の関数宣言がgo:nosplitの対象であることを認識できます。
- 字句解析器(lexer)に
-
src/cmd/gc/go.yおよびsrc/cmd/gc/y.tab.cの変更:- Go言語の文法定義ファイル(
go.y)と、それから生成されるy.tab.cに、関数宣言を処理するルール(xfndcl)と宣言リストを処理するルール(xdcl_list)が変更されました。 - 関数ノードが構築される際に、グローバル変数
nosplitの値が$$->nosplit(現在の関数ノードのnosplitフィールド)に代入されます。これにより、go:nosplitプラグマが適用された関数は、AST上でその属性を持つことになります。 - また、宣言リストの処理の最後に
nosplit = 0;が追加され、go:nosplitの状態がリセットされるようになりました。これは、go:nosplitプラグマが特定の関数にのみ適用され、その後の関数宣言に影響を与えないようにするためです。
- Go言語の文法定義ファイル(
-
src/cmd/gc/pgen.cの変更:compile関数内で、関数のコンパイル時にfn->nosplit(関数ノードのnosplitフィールド)が真である場合、生成されるテキストセクションのTEXTFLAGにNOSPLITフラグが設定されるようになりました。TEXTFLAGは、Goランタイムが関数をロードする際にその特性を識別するために使用するビットフラグの集合です。NOSPLITフラグが設定されることで、ランタイムはその関数がスタック分割チェックを行わないことを認識し、適切な処理を行います。
-
src/cmd/gc/fmt.cの変更:- コメントが更新され、
fmtmode == FTypeId || fmtmode == FErrの条件が、引数名だけでなく"noescape"や"nosplit"タグも含まないことを明確にしました。これは、主にコードの可読性とドキュメントの正確性に関する変更です。
- コメントが更新され、
これらの変更は連携して機能し、go:nosplitプラグマがGoソースコード内で指定されたときに、コンパイラがそれを正しく解釈し、最終的なバイナリコードに関数のNOSPLIT属性を適切に設定することを保証します。
コアとなるコードの変更箇所
このコミットでは、以下のファイルが変更されています。
-
src/cmd/gc/fmt.c:- コメントの更新:
fmtstrcpy(fp, "(");の行のコメントが// no argument names on function signature, and no "noescape"/"nosplit" tagsに変更され、nosplitタグも含まれないことが明記されました。
- コメントの更新:
-
src/cmd/gc/go.h:Node構造体にuchar nosplit; // func should not execute on separate stackが追加されました。- グローバル変数として
EXTERN int nosplit;が追加されました。
-
src/cmd/gc/go.y:xfndclルール内で、関数ノードのnosplitフィールドにグローバル変数nosplitの値が代入されるようになりました:$$->nosplit = nosplit;xdcl_listルール内で、宣言処理の最後にグローバル変数nosplitが0にリセットされるようになりました:nosplit = 0;
-
src/cmd/gc/lex.c:go:プラグマを処理する部分に、go:nosplitプラグマを認識するロジックが追加されました。if(strcmp(lexbuf, "go:nosplit") == 0)の条件でnosplit = 1;が実行されます。
-
src/cmd/gc/pgen.c:compile関数内で、関数ノードfnのnosplitフィールドが真の場合に、生成されるテキストセクションのTEXTFLAGにNOSPLITフラグが設定されるようになりました:if(fn->nosplit) ptxt->TEXTFLAG |= NOSPLIT;
-
src/cmd/gc/y.tab.c:go.yから生成されるファイルであり、go.yの変更が反映されています。yyreduce関数内の対応する箇所で(yyval.node)->nosplit = nosplit;とnosplit = 0;が追加されています。
コアとなるコードの解説
src/cmd/gc/go.h
struct Node
{
// ...
uchar noescape; // func arguments do not escape
uchar nosplit; // func should not execute on separate stack
uchar builtin; // built-in name, like len or close
// ...
};
// ...
EXTERN int noescape;
EXTERN int nosplit;
Node構造体へのnosplitフィールド追加:NodeはGoコンパイラがソースコードを解析して生成する抽象構文木(AST)の各要素を表します。関数宣言を表すNodeには、このnosplitフィールドが設定され、その関数がスタック分割チェックを必要としないことを示します。uchar型は1バイトの符号なし整数で、フラグとして使用されます。EXTERN int nosplit;: これは、go:nosplitプラグマが字句解析器によって検出されたときに、その状態を一時的に保持するためのグローバル変数です。この変数は、後続の構文解析フェーズで関数ノードのnosplitフィールドに値を伝播するために使用されます。
src/cmd/gc/lex.c
go:
// ...
if(strcmp(lexbuf, "go:nosplit") == 0) {
nosplit = 1;
goto out;
}
// ...
go:nosplitプラグマの認識:lex.cはGoソースコードをトークンに分割する字句解析器です。go:で始まるプラグマを処理するセクションで、lexbuf(現在のトークン文字列)が"go:nosplit"と一致するかどうかをチェックします。一致した場合、グローバル変数nosplitを1に設定します。これにより、コンパイラの次のフェーズ(構文解析)で、このプラグマが適用される関数が適切にマークされるようになります。goto out;は、プラグマの処理を終えて字句解析を続行するためのジャンプです。
src/cmd/gc/go.y および src/cmd/gc/y.tab.c
xfndcl:
// ...
{
$$->nbody = $3;
$$->endlineno = lineno;
$$->noescape = noescape;
$$->nosplit = nosplit; // ここでnosplitフラグを関数ノードに設定
funcbody($$);
}
// ...
xdcl_list:
// ...
{
testdclstack();
nointerface = 0;
noescape = 0;
nosplit = 0; // 宣言リストの終わりにnosplitフラグをリセット
}
- 関数ノードへの
nosplit設定:xfndclは関数宣言を構文解析するルールです。ここで、新しく構築される関数ノード($$)のnosplitフィールドに、字句解析器によって設定されたグローバル変数nosplitの値を代入します。これにより、go:nosplitプラグマが適用された関数は、AST上でその属性を持つことになります。 nosplitフラグのリセット:xdcl_listは宣言のリストを処理するルールです。宣言リストの処理が完了した後に、グローバル変数nosplitを0にリセットします。これは非常に重要で、go:nosplitプラグマがその直後の関数にのみ適用され、それ以降の関数宣言に誤って影響を与えないようにするためです。
src/cmd/gc/pgen.c
compile(Node *fn)
{
// ...
if(fn->nosplit)
ptxt->TEXTFLAG |= NOSPLIT; // NOSPLITフラグを設定
// ...
}
NOSPLITTEXTFLAGの設定:pgen.cは、ASTから最終的な機械語コードを生成する過程で、関数のプロパティを処理します。compile関数は個々の関数をコンパイルする際に呼び出されます。ここで、関数ノードfnのnosplitフィールドが真(つまり、go:nosplitプラグマが適用されている)であれば、生成されるアセンブリコードの関数のヘッダ部分に埋め込まれるTEXTFLAGにNOSPLITビットを設定します。このNOSPLITフラグは、Goランタイムがこの関数を呼び出す際にスタック分割チェックをスキップするために使用されます。
これらの変更により、go:nosplitプラグマはGoコンパイラのフロントエンドからバックエンドまで一貫して処理され、最終的に生成されるバイナリコードに関数の特性として反映されるようになります。
関連リンク
- 本コミット (CL 103490043): https://golang.org/cl/103490043
go:nosplitを一時的に無効化したコミット (CL 105260044): https://golang.org/cl/105260044go:nosplitを最初に導入したコミット (CL 93380044): https://golang.org/cl/93380044
参考にした情報源リンク
- Goのスタック管理と
go:nosplitに関する一般的な情報源(Web検索結果に基づく):- "go:nosplit pragma"
- "golang stack splitting"
- "golang runtime TEXTFLAG NOSPLIT"
- Goのソースコード(
src/runtime/ディレクトリ内の関連ファイル) - Goのコンパイラソースコード(
src/cmd/gc/ディレクトリ内の関連ファイル) - Goの公式ドキュメントやブログ記事(特にランタイムやコンパイラに関するもの)
- GoのIssueトラッカーやコードレビューシステム(
golang.org/cl) - Goの低レベルな実装に関する技術ブログや解説記事
- Goのコンパイラ設計に関する論文やプレゼンテーション
# [インデックス 19556] ファイルの概要
このコミットは、Goコンパイラにおける`go:nosplit`アノテーション(プラグマ)の機能を再導入するものです。以前のコミットで、Windowsビルドの不具合の原因と誤解され一時的に無効化されていましたが、その後の調査で`go:nosplit`が問題ではないと判明したため、このコミットでその機能が元に戻されました。これにより、特定の関数がスタックの拡張チェックを行わないようにコンパイラに指示する能力が回復し、Goランタイムの低レベルな部分でのパフォーマンスと安全性が確保されます。
## コミット
commit 5ce6d3e03e48cd453e414a0f64090004af9f319a Author: Keith Randall khr@golang.org Date: Tue Jun 17 08:10:21 2014 -0700
undo CL 105260044 / afd6f214cc81
The go:nosplit change wasn't the problem, reinstating.
««« original CL description
undo CL 93380044 / 7f0999348917
Partial undo, just of go:nosplit annotation. Somehow it
is breaking the windows builders.
TBR=bradfitz
««« original CL description
runtime: implement string ops in Go
Also implement go:nosplit annotation. Not really needed
for now, but we'll definitely need it for other conversions.
benchmark old ns/op new ns/op delta
BenchmarkRuneIterate 534 474 -11.24%
BenchmarkRuneIterate2 535 470 -12.15%
LGTM=bradfitz
R=golang-codereviews, dave, bradfitz, minux
CC=golang-codereviews
https://golang.org/cl/93380044
»»»
TBR=bradfitz
CC=golang-codereviews
https://golang.org/cl/105260044
»»»
TBR=bradfitz
R=bradfitz, golang-codereviews
CC=golang-codereviews
https://golang.org/cl/103490043
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/5ce6d3e03e48cd453e414a0f64090004af9f319a](https://github.org/golang/go/commit/5ce6d3e03e48cd453e414a0f64090004af9f319a)
## 元コミット内容
このコミットは、以前の変更セット(CL 105260044 / afd6f214cc81)を元に戻すものです。そのCLは、`go:nosplit`の変更がWindowsビルドを壊していると誤解されたために行われました。しかし、実際には`go:nosplit`が問題ではなかったため、このコミットでその機能を再有効化しています。
元々、`go:nosplit`アノテーションは、Goで文字列操作を実装する際に導入されました(CL 93380044 / 7f0999348917)。このアノテーションは、将来的により多くの変換で必要になると考えられていました。ベンチマークでは、`BenchmarkRuneIterate`と`BenchmarkRuneIterate2`でそれぞれ11.24%と12.15%の性能向上が見られました。
## 変更の背景
このコミットの背景には、Goランタイムのスタック管理とコンパイラプラグマの進化があります。
1. **`go:nosplit`の導入 (CL 93380044)**:
Goのランタイムは、ゴルーチンごとに小さなスタックを割り当て、必要に応じて動的に拡張する「スタック分割(stack splitting)」というメカニズムを採用しています。しかし、ガベージコレクションやシグナルハンドリング、あるいは非常に頻繁に呼び出される低レベルな関数など、特定の状況下ではスタック分割チェックのオーバーヘッドが問題になったり、スタックを直接操作するためにスタック分割が安全でなかったりする場合があります。
`go:nosplit`プラグマは、このような関数に対してコンパイラにスタック分割チェックを挿入しないように指示するために導入されました。これにより、これらの関数の実行パスが最適化され、低レイテンシが保証されます。このプラグマは、Goで文字列操作を実装する際に初めて導入され、パフォーマンスの向上が確認されました。
2. **`go:nosplit`の一時的な無効化 (CL 105260044)**:
`go:nosplit`が導入された後、Windowsビルドで問題が発生しました。当時の開発者は、この問題の原因が`go:nosplit`アノテーションにあると推測し、一時的にその機能を無効化するコミット(CL 105260044)を行いました。これは、問題の切り分けとビルドの安定化を目的とした緊急措置でした。
3. **`go:nosplit`の再有効化 (本コミット CL 103490043)**:
その後の調査で、Windowsビルドの問題は`go:nosplit`とは無関係であることが判明しました。`go:nosplit`はGoランタイムの重要な最適化機能であり、不要な無効化は性能低下や将来的な開発の妨げとなるため、このコミットで`go:nosplit`の機能が元に戻されることになりました。これは、Goのコンパイラとランタイムの安定性を確保しつつ、必要な最適化を維持するための判断です。
## 前提知識の解説
### 1. Goのスタック管理とスタック分割 (Stack Splitting)
Goのゴルーチンは、非常に軽量な並行処理の単位です。各ゴルーチンは、最初は非常に小さなスタック(通常は数KB)を持って開始します。これは、数百万ものゴルーチンを同時に実行できるようにするためです。しかし、関数呼び出しが深くなったり、大きなローカル変数が割り当てられたりすると、スタック領域が不足する可能性があります。
Goランタイムは、この問題を解決するために「スタック分割(Stack Splitting)」というメカニズムを採用しています。これは、関数が呼び出される際に、その関数のプロローグ(関数の冒頭部分)で現在のスタックに残っている領域が十分であるかをチェックするコードを挿入するものです。もしスタックが不足していると判断された場合、ランタイムはより大きな新しいスタックを割り当て、古いスタックの内容を新しいスタックにコピーし、実行を新しいスタックに切り替えます。このプロセスは、Goのユーザーからは透過的に行われます。
### 2. `go:nosplit`プラグマ
`go:nosplit`は、Goコンパイラ(`gc`)に対する特別な指示(プラグマまたはアノテーション)です。このプラグマが関数宣言の直前に記述されている場合、コンパイラはその関数に対してスタック分割チェックのコードを挿入しません。
このプラグマは、主に以下のような状況で使用されます。
* **低レベルなランタイム関数**: ガベージコレクタやスケジューラなど、Goランタイムの非常に低レベルな部分で動作する関数は、スタックを直接操作したり、スタック分割チェック自体がデッドロックや再帰的なスタック拡張を引き起こす可能性があるため、`go:nosplit`が適用されます。
* **パフォーマンスクリティカルな関数**: 非常に頻繁に呼び出され、スタック分割チェックのオーバーヘッドが無視できないほど大きい関数に対して、性能向上のために使用されることがあります。
* **シグナルハンドラ**: シグナルハンドラは非同期に呼び出されるため、スタックの状態が予測困難な場合があり、スタック分割チェックが安全でないことがあります。
`go:nosplit`が適用された関数は、その関数が実行される間、スタックが拡張されないことを保証する必要があります。もしスタックが不足した場合、プログラムはクラッシュする可能性があります。そのため、このプラグマは慎重に使用され、通常はGoランタイムの内部でのみ見られます。
### 3. Goコンパイラ (`gc`) の構造
Goコンパイラ(`gc`)は、Go言語のソースコードを機械語に変換する役割を担っています。その内部は複数のステージに分かれており、このコミットで変更されたファイルは、主にコンパイラのフロントエンドと中間表現の処理に関連しています。
* **`src/cmd/gc/`**: Goコンパイラのソースコードが格納されているディレクトリです。
* **`go.h`**: コンパイラ内部で使用されるデータ構造や定数の定義が含まれるヘッダファイルです。
* **`go.y`**: Yacc(Yet Another Compiler Compiler)の文法定義ファイルで、Go言語の構文解析ルールが記述されています。これにより、ソースコードが抽象構文木(AST)に変換されます。
* **`lex.c`**: 字句解析器(lexer)の実装ファイルで、ソースコードをトークンに分割する役割を担います。`go:nosplit`のようなプラグマもここで認識されます。
* **`pgen.c`**: プログラム生成(program generation)に関連するファイルで、ASTから最終的な機械語コードを生成する過程で、関数のプロパティ(例: `TEXTFLAG`)を設定します。
* **`fmt.c`**: 型のフォーマットなど、コンパイラの出力に関連する処理を行うファイルです。
* **`y.tab.c`**: `go.y`から生成されるC言語のソースファイルで、実際の構文解析ロジックが含まれます。
### 4. `TEXTFLAG`
Goランタイムの関数は、コンパイル時に特定の属性を持つことができます。これらの属性は、生成されるアセンブリコードの関数のヘッダ部分に`TEXTFLAG`として埋め込まれます。`NOSPLIT`は、その関数がスタック分割チェックを行わないことを示すフラグです。コンパイラは、`go:nosplit`プラグマを検出すると、対応する関数の`TEXTFLAG`に`NOSPLIT`を設定します。
## 技術的詳細
このコミットは、`go:nosplit`プラグマをGoコンパイラ(`gc`)に再統合するための複数の変更を含んでいます。これらの変更は、字句解析、構文解析、中間表現、そして最終的なコード生成の各段階に影響を与えます。
1. **`src/cmd/gc/go.h` の変更**:
* `Node`構造体に`uchar nosplit;`フィールドが追加されました。`Node`構造体は、Goの抽象構文木(AST)における各ノード(関数、変数、式など)を表すために使用されます。`nosplit`フィールドは、そのノードが関数である場合に、スタック分割チェックを無効にする必要があるかどうかを示すフラグとして機能します。
* `EXTERN int nosplit;`が追加されました。これは、`go:nosplit`プラグマの状態をグローバルに保持するための変数です。
2. **`src/cmd/gc/lex.c` の変更**:
* 字句解析器(lexer)に`go:nosplit`という文字列を認識するロジックが追加されました。`strcmp(lexbuf, "go:nosplit") == 0`という条件で、入力ストリームから`go:nosplit`というプラグマが読み取られた場合に、グローバル変数`nosplit`を`1`に設定します。これにより、コンパイラは後続の関数宣言が`go:nosplit`の対象であることを認識できます。
3. **`src/cmd/gc/go.y` および `src/cmd/gc/y.tab.c` の変更**:
* Go言語の文法定義ファイル(`go.y`)と、それから生成される`y.tab.c`に、関数宣言を処理するルール(`xfndcl`)と宣言リストを処理するルール(`xdcl_list`)が変更されました。
* 関数ノードが構築される際に、グローバル変数`nosplit`の値が`$$->nosplit`(現在の関数ノードの`nosplit`フィールド)に代入されます。これにより、`go:nosplit`プラグマが適用された関数は、AST上でその属性を持つことになります。
* また、宣言リストの処理の最後に`nosplit = 0;`が追加され、`go:nosplit`の状態がリセットされるようになりました。これは、`go:nosplit`プラグマが特定の関数にのみ適用され、その後の関数宣言に影響を与えないようにするためです。
4. **`src/cmd/gc/pgen.c` の変更**:
* `compile`関数内で、関数のコンパイル時に`fn->nosplit`(関数ノードの`nosplit`フィールド)が真である場合、生成されるテキストセクションの`TEXTFLAG`に`NOSPLIT`フラグが設定されるようになりました。`TEXTFLAG`は、Goランタイムが関数をロードする際にその特性を識別するために使用するビットフラグの集合です。`NOSPLIT`フラグが設定されることで、ランタイムはその関数がスタック分割チェックを行わないことを認識し、適切な処理を行います。
5. **`src/cmd/gc/fmt.c` の変更**:
* コメントが更新され、`fmtmode == FTypeId || fmtmode == FErr`の条件が、引数名だけでなく`"noescape"`や`"nosplit"`タグも含まないことを明確にしました。これは、主にコードの可読性とドキュメントの正確性に関する変更です。
これらの変更は連携して機能し、`go:nosplit`プラグマがGoソースコード内で指定されたときに、コンパイラがそれを正しく解釈し、最終的なバイナリコードに関数の`NOSPLIT`属性を適切に設定することを保証します。
## コアとなるコードの変更箇所
このコミットでは、以下のファイルが変更されています。
* **`src/cmd/gc/fmt.c`**:
* コメントの更新: `fmtstrcpy(fp, "(");` の行のコメントが `// no argument names on function signature, and no "noescape"/"nosplit" tags` に変更され、`nosplit`タグも含まれないことが明記されました。
* **`src/cmd/gc/go.h`**:
* `Node`構造体に `uchar nosplit; // func should not execute on separate stack` が追加されました。
* グローバル変数として `EXTERN int nosplit;` が追加されました。
* **`src/cmd/gc/go.y`**:
* `xfndcl` ルール内で、関数ノードの `nosplit` フィールドにグローバル変数 `nosplit` の値が代入されるようになりました: `$$->nosplit = nosplit;`
* `xdcl_list` ルール内で、宣言処理の最後にグローバル変数 `nosplit` が `0` にリセットされるようになりました: `nosplit = 0;`
* **`src/cmd/gc/lex.c`**:
* `go:` プラグマを処理する部分に、`go:nosplit` プラグマを認識するロジックが追加されました。`if(strcmp(lexbuf, "go:nosplit") == 0)` の条件で `nosplit = 1;` が実行されます。
* **`src/cmd/gc/pgen.c`**:
* `compile` 関数内で、関数ノード `fn` の `nosplit` フィールドが真の場合に、生成されるテキストセクションの `TEXTFLAG` に `NOSPLIT` フラグが設定されるようになりました: `if(fn->nosplit) ptxt->TEXTFLAG |= NOSPLIT;`
* **`src/cmd/gc/y.tab.c`**:
* `go.y` から生成されるファイルであり、`go.y` の変更が反映されています。`yyreduce` 関数内の対応する箇所で `(yyval.node)->nosplit = nosplit;` と `nosplit = 0;` が追加されています。
## コアとなるコードの解説
### `src/cmd/gc/go.h`
```c
struct Node
{
// ...
uchar noescape; // func arguments do not escape
uchar nosplit; // func should not execute on separate stack
uchar builtin; // built-in name, like len or close
// ...
};
// ...
EXTERN int noescape;
EXTERN int nosplit;
Node構造体へのnosplitフィールド追加:NodeはGoコンパイラがソースコードを解析して生成する抽象構文木(AST)の各要素を表します。関数宣言を表すNodeには、このnosplitフィールドが設定され、その関数がスタック分割チェックを必要としないことを示します。uchar型は1バイトの符号なし整数で、フラグとして使用されます。EXTERN int nosplit;: これは、go:nosplitプラグマが字句解析器によって検出されたときに、その状態を一時的に保持するためのグローバル変数です。この変数は、後続の構文解析フェーズで関数ノードのnosplitフィールドに値を伝播するために使用されます。
src/cmd/gc/lex.c
go:
// ...
if(strcmp(lexbuf, "go:nosplit") == 0) {
nosplit = 1;
goto out;
}
// ...
go:nosplitプラグマの認識:lex.cはGoソースコードをトークンに分割する字句解析器です。go:で始まるプラグマを処理するセクションで、lexbuf(現在のトークン文字列)が"go:nosplit"と一致するかどうかをチェックします。一致した場合、グローバル変数nosplitを1に設定します。これにより、コンパイラの次のフェーズ(構文解析)で、このプラグマが適用される関数が適切にマークされるようになります。goto out;は、プラグマの処理を終えて字句解析を続行するためのジャンプです。
src/cmd/gc/go.y および src/cmd/gc/y.tab.c
xfndcl:
// ...
{
$$->nbody = $3;
$$->endlineno = lineno;
$$->noescape = noescape;
$$->nosplit = nosplit; // ここでnosplitフラグを関数ノードに設定
funcbody($$);
}
// ...
xdcl_list:
// ...
{
testdclstack();
nointerface = 0;
noescape = 0;
nosplit = 0; // 宣言リストの終わりにnosplitフラグをリセット
}
- 関数ノードへの
nosplit設定:xfndclは関数宣言を構文解析するルールです。ここで、新しく構築される関数ノード($$)のnosplitフィールドに、字句解析器によって設定されたグローバル変数nosplitの値を代入します。これにより、go:nosplitプラグマが適用された関数は、AST上でその属性を持つことになります。 nosplitフラグのリセット:xdcl_listは宣言のリストを処理するルールです。宣言リストの処理が完了した後に、グローバル変数nosplitを0にリセットします。これは非常に重要で、go:nosplitプラグマがその直後の関数にのみ適用され、それ以降の関数宣言に誤って影響を与えないようにするためです。
src/cmd/gc/pgen.c
compile(Node *fn)
{
// ...
if(fn->nosplit)
ptxt->TEXTFLAG |= NOSPLIT; // NOSPLITフラグを設定
// ...
}
NOSPLITTEXTFLAGの設定:pgen.cは、ASTから最終的な機械語コードを生成する過程で、関数のプロパティを処理します。compile関数は個々の関数をコンパイルする際に呼び出されます。ここで、関数ノードfnのnosplitフィールドが真(つまり、go:nosplitプラグマが適用されている)であれば、生成されるアセンブリコードの関数のヘッダ部分に埋め込まれるTEXTFLAGにNOSPLITビットを設定します。このNOSPLITフラグは、Goランタイムがこの関数を呼び出す際にスタック分割チェックをスキップするために使用されます。
これらの変更により、go:nosplitプラグマはGoコンパイラのフロントエンドからバックエンドまで一貫して処理され、最終的に生成されるバイナリコードに関数の特性として反映されるようになります。
関連リンク
- 本コミット (CL 103490043): https://golang.org/cl/103490043
go:nosplitを一時的に無効化したコミット (CL 105260044): https://golang.org/cl/105260044go:nosplitを最初に導入したコミット (CL 93380044): https://golang.org/cl/93380044
参考にした情報源リンク
- Goのスタック管理と
go:nosplitに関する一般的な情報源(Web検索結果に基づく):- "go:nosplit pragma"
- "golang stack splitting"
- "golang runtime TEXTFLAG NOSPLIT"
- Goのソースコード(
src/runtime/ディレクトリ内の関連ファイル) - Goのコンパイラソースコード(
src/cmd/gc/ディレクトリ内の関連ファイル) - Goの公式ドキュメントやブログ記事(特にランタイムやコンパイラに関するもの)
- GoのIssueトラッカーやコードレビューシステム(
golang.org/cl) - Goの低レベルな実装に関する技術ブログや解説記事
- Goのコンパイラ設計に関する論文やプレゼンテーション