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

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

このコミットは、Goコンパイラ(cmd/gc)に新しいコメントディレクティブ//go:noescapeを追加するものです。このディレクティブは、外部関数(Goで実装されていない関数、通常はアセンブリやCで書かれた関数)の宣言に対して、その引数がヒープにエスケープしないことをコンパイラに指示するために使用されます。これにより、コンパイラのエスケープ解析がより正確になり、不要なヒープ割り当てを削減し、パフォーマンスを向上させることができます。

コミット

commit fd178d6a7e62796c71258ba155b957616be86ff4
Author: Russ Cox <rsc@golang.org>
Date:   Tue Feb 5 07:00:38 2013 -0500

    cmd/gc: add way to specify 'noescape' for extern funcs
    
    A new comment directive //go:noescape instructs the compiler
    that the following external (no body) func declaration should be
    treated as if none of its arguments escape to the heap.
    
    Fixes #4099.
    
    R=golang-dev, dave, minux.ma, daniel.morsing, remyoudompheng, adg, agl, iant
    CC=golang-dev
    https://golang.org/cl/7289048

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

https://github.com/golang/go/commit/fd178d6a7e62796c71258ba155b957616be86ff4

元コミット内容

cmd/gc: add way to specify 'noescape' for extern funcs

新しいコメントディレクティブ//go:noescapeは、続く外部(本体を持たない)関数宣言の引数がヒープにエスケープしないようにコンパイラに指示します。

Issue #4099を修正します。

変更の背景

Go言語では、コンパイラが「エスケープ解析(Escape Analysis)」と呼ばれる最適化を行います。これは、変数がヒープに割り当てられるべきか、それともスタックに割り当てられるべきかを決定するプロセスです。スタック割り当てはヒープ割り当てよりも高速であるため、エスケープ解析はGoプログラムのパフォーマンスに大きな影響を与えます。

しかし、Go言語以外の言語(Cやアセンブリなど)で実装された外部関数をGoコードから呼び出す場合、コンパイラはこれらの外部関数の内部動作を知ることができません。そのため、デフォルトでは、外部関数に渡されるポインタ引数は「安全のため」ヒープにエスケープすると仮定されます。この仮定は、実際にはヒープにエスケープしない引数に対しても不要なヒープ割り当てを引き起こし、パフォーマンスの低下を招く可能性がありました。

この問題は、Go issue #4099として報告されました。特に、runtimeパッケージ内の低レベル関数など、パフォーマンスが非常に重要な部分で、この不要なヒープ割り当てが顕著なオーバーヘッドとなっていました。このコミットは、この問題を解決するために、開発者が外部関数の引数がエスケープしないことを明示的にコンパイラに伝えるメカニズムを提供することを目的としています。

前提知識の解説

エスケープ解析 (Escape Analysis)

エスケープ解析は、コンパイラ最適化の一種で、プログラム内の変数がどこにメモリを割り当てるべきかを決定します。

  • スタック (Stack): 関数呼び出しごとに割り当てられる一時的なメモリ領域です。関数が終了すると、その関数に割り当てられたスタックメモリは自動的に解放されます。スタック割り当ては非常に高速です。
  • ヒープ (Heap): プログラムの実行中に動的にメモリを割り当てる領域です。ヒープに割り当てられたメモリは、ガベージコレクタによって管理され、不要になった時点で解放されます。ヒープ割り当てはスタック割り当てよりもオーバーヘッドが大きいです。

エスケープ解析の目的は、変数が関数のスコープ外で参照される可能性があるかどうか(つまり「エスケープ」するかどうか)を判断することです。

  • エスケープしない場合: 変数が関数の内部でのみ使用され、関数の終了後には不要になる場合、その変数はスタックに割り当てられます。
  • エスケープする場合: 変数が関数の外部(例えば、グローバル変数、別の関数の戻り値、チャネルを介して送信される値など)から参照される可能性がある場合、その変数はヒープに割り当てられます。

エスケープ解析が正確であればあるほど、コンパイラはより多くの変数をスタックに割り当てることができ、ガベージコレクションの負荷を減らし、プログラムの実行速度を向上させることができます。

外部関数 (External Functions)

Go言語では、Go以外の言語(C、アセンブリなど)で書かれた関数を呼び出すことができます。これらは「外部関数」と呼ばれ、Goのソースコード内には関数の宣言のみが存在し、本体(実装)は含まれません。Goコンパイラは、これらの外部関数の内部的なメモリ使用パターンを分析できないため、デフォルトでは安全側に倒して、引数がヒープにエスケープすると仮定します。

コンパイラディレクティブ (Compiler Directives)

コンパイラディレクティブは、ソースコード内に記述される特別な指示で、コンパイラの動作を制御します。Go言語では、//go:で始まるコメント形式でディレクティブが提供されます。これらは通常のコメントとして扱われるため、ディレクティブを認識しないツールやコンパイラでもエラーなく処理できます。

技術的詳細

このコミットは、Goコンパイラのフロントエンドとバックエンドの両方に変更を加えて、//go:noescapeディレクティブを認識し、エスケープ解析にその情報を組み込むようにします。

  1. //go:noescapeディレクティブの導入:

    • src/cmd/gc/lex.c: 字句解析器が//go:noescapeコメントを認識し、noescapeフラグを設定するように変更されます。これにより、パーサーが関数宣言を処理する際にこの情報を利用できるようになります。
    • src/cmd/gc/go.y: Go言語の文法定義ファイル(Yacc/Bison形式)が更新され、関数宣言の際にnoescapeフラグがNode構造体(GoのASTノード)に伝播されるようになります。また、//go:noescapeが本体を持つ関数や外部関数でない関数に適用された場合にエラーを出すチェックも追加されます。
  2. Node構造体へのnoescapeフィールドの追加:

    • src/cmd/gc/go.h: Node構造体にnoescapeというuchar型のフィールドが追加されます。これは、その関数が//go:noescapeディレクティブでマークされているかどうかを示すフラグです。
  3. エスケープ解析への統合:

    • src/cmd/gc/esc.c: エスケープ解析のロジックが変更されます。特に、外部関数(func->nbody == nil)の場合、以前は常に引数がエスケープすると仮定されていましたが、func->noescapeフラグが設定されている場合は、引数がエスケープしないとマークされるようになります。これにより、これらの引数に対する不要なヒープ割り当てが回避されます。
  4. ドキュメントの更新:

    • src/cmd/gc/doc.go: cmd/gcのドキュメントに//go:noescapeディレクティブに関する説明が追加されます。これにより、開発者がこの新しい機能について知ることができます。
  5. テストケースの追加:

    • test/escape2.gotest/fixedbugs/issue4099.go: //go:noescapeディレクティブの動作を検証するための新しいテストケースが追加されます。これらのテストは、ディレクティブが正しく機能し、期待通りにエスケープ解析の結果に影響を与えることを確認します。特に、//go:noescapeが適用された関数の引数がエスケープしないこと、および適用されなかった関数の引数がエスケープすることを確認します。

この変更により、Goコンパイラは外部関数呼び出しにおけるエスケープ解析の精度を向上させ、特に低レベルのランタイムコードやC/アセンブリとの連携において、より効率的なメモリ管理とパフォーマンスを実現します。

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

src/cmd/gc/doc.go

--- a/src/cmd/gc/doc.go
+++ b/src/cmd/gc/doc.go
@@ -56,5 +56,28 @@
 There are also a number of debugging flags; run the command with no arguments
 to get a usage message.
 
+Compiler Directives
+
+The compiler accepts two compiler directives in the form of // comments at the
+beginning of a line. To distinguish them from non-directive comments, the directives
+require no space between the slashes and the name of the directive. However, since
+they are comments, tools unaware of the directive convention or of a particular
+directive can skip over a directive like any other comment.
+
+    //line path/to/file:linenumber
+
+The //line directive specifies that the source line that follows should be recorded
+as having come from the given file path and line number. Successive lines are
+recorded using increasing line numbers, until the next directive. This directive
+typically appears in machine-generated code, so that compilers and debuggers
+will show lines in the original input to the generator.
+
+    //go:noescape
+
+The //go:noescape directive specifies that the next declaration in the file, which
+must be a func without a body (meaning that it has an implementation not written
+in Go) does not allow any of the pointers passed as arguments to escape into the
+heap or into the values returned from the function. This information can be used as
+during the compiler's escape analysis of Go code calling the function.
  */
  package documentation

src/cmd/gc/esc.c

--- a/src/cmd/gc/esc.c
+++ b/src/cmd/gc/esc.c
@@ -1109,13 +1112,21 @@
 {
 	Node *savefn;
 	NodeList *ll;
-	
+	Type *t;
+
 	USED(e);
 	func->esc = EscFuncTagged;
 	
-	// External functions must be assumed unsafe.
-	if(func->nbody == nil)
+	// External functions are assumed unsafe,
+	// unless //go:noescape is given before the declaration.
+	if(func->nbody == nil) {
+		if(func->noescape) {
+			for(t=getinargx(func->type)->type; t; t=t->down)
+				if(haspointers(t->type))
+					t->note = mktag(EscNone);
+		}
 		return;
+	}
 
 	savefn = curfn;
 	curfn = func;

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -253,6 +253,7 @@
 	uchar	colas;		// OAS resulting from :=
 	uchar	diag;		// already printed error about this
 	uchar	esc;		// EscXXX
+	uchar	noescape;	// func arguments do not escape
 	uchar	funcdepth;
 	uchar	builtin;	// built-in name, like len or close
 	uchar	walkdef;
@@ -943,6 +944,7 @@
 EXTERN	int	compiling_wrappers;
 EXTERN	int	pure_go;
 EXTERN	int	flag_race;
 EXTERN	int	flag_largemodel;
+EXTERN	int	noescape;
 
 EXTERN	int	nointerface;
 EXTERN	int	fieldtrack_enabled;

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1277,8 +1277,11 @@
 		$$ = $2;
 		if($$ == N)
 			break;
+		if(noescape && $3 != nil)
+			yyerror("can only use //go:noescape with external func implementations");
 		$$->nbody = $3;
 		$$->endlineno = lineno;
+		$$->noescape = noescape;
 		funcbody($$);
 	}
 
@@ -1462,6 +1465,7 @@
 		if(nsyntaxerrors == 0)
 			testdclstack();
 		nointerface = 0;
+		noescape = 0;
 	}
 
 vardcl_list:

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1436,7 +1436,7 @@
 	Hist *h;
 
 	c = getr();
-	if(c == 'g' && fieldtrack_enabled)
+	if(c == 'g')
 		goto go;
 	if(c != 'l')	
 		goto out;
@@ -1491,18 +1491,32 @@
 	goto out;
 
 go:
-	for(i=1; i<11; i++) {
-		c = getr();
-		if(c != "go:nointerface"[i])
-			goto out;
-	}
-	nointerface = 1;
+	cp = lexbuf;
+	ep = lexbuf+sizeof(lexbuf)-5;
+	*cp++ = 'g'; // already read
 	for(;;) {
 		c = getr();
-		if(c == EOF || c == '\n')
+		if(c == EOF || c >= Runeself)
+			goto out;
+		if(c == '\n')
 			break;
+		if(cp < ep)
+			*cp++ = c;
 	}
+	*cp = 0;
+	ep = strchr(lexbuf, ' ');
+	if(ep != nil)
+		*ep = 0;
 
+	if(strcmp(lexbuf, "go:nointerface") == 0 && fieldtrack_enabled) {
+		nointerface = 1;
+		goto out;
+	}
+	if(strcmp(lexbuf, "go:noescape") == 0) {
+		noescape = 1;
+		goto out;
+	}
+	
 out:
 	return c;
 }

test/escape2.go

--- a/test/escape2.go
+++ b/test/escape2.go
@@ -1274,3 +1274,29 @@
 		T: t,
 	}
 }
+
+//go:noescape
+
+func F1([]byte)
+
+func F2([]byte)
+
+//go:noescape
+
+func F3(x []byte) // ERROR "F3 x does not escape"
+
+func F4(x []byte)
+
+func G() {
+	var buf1 [10]byte
+	F1(buf1[:]) // ERROR "buf1 does not escape"
+	
+	var buf2 [10]byte // ERROR "moved to heap: buf2"
+	F2(buf2[:]) // ERROR "buf2 escapes to heap"
+
+	var buf3 [10]byte
+	F3(buf3[:]) // ERROR "buf3 does not escape"
+	
+	var buf4 [10]byte // ERROR "moved to heap: buf4"
+	F4(buf4[:]) // ERROR "buf4 escapes to heap"
+}

test/fixedbugs/issue4099.go

--- /dev/null
+++ b/test/fixedbugs/issue4099.go
@@ -0,0 +1,26 @@
+// errorcheck -0 -m
+
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Check go:noescape annotations.
+
+package p
+
+// The noescape comment only applies to the next func,
+// which must not have a body.
+
+//go:noescape
+
+func F1([]byte)
+
+func F2([]byte)
+
+func G() {
+	var buf1 [10]byte
+	F1(buf1[:]) // ERROR "buf1 does not escape"
+	
+	var buf2 [10]byte // ERROR "moved to heap: buf2"
+	F2(buf2[:]) // ERROR "buf2 escapes to heap"
+}

コアとなるコードの解説

src/cmd/gc/doc.go

このファイルはGoコンパイラgcのドキュメントです。変更点では、Compiler Directivesセクションに//go:noescapeディレクティブに関する詳細な説明が追加されています。これにより、このディレクティブの目的、使用方法、およびそれがエスケープ解析にどのように影響するかが公式に文書化されます。

src/cmd/gc/esc.c

このファイルはGoコンパイラのエスケープ解析ロジックを実装しています。 変更の核心はesctag関数内にあります。

	// External functions are assumed unsafe,
	// unless //go:noescape is given before the declaration.
	if(func->nbody == nil) { // 関数本体がない場合(外部関数)
		if(func->noescape) { // noescapeディレクティブが指定されている場合
			for(t=getinargx(func->type)->type; t; t=t->down)
				if(haspointers(t->type))
					t->note = mktag(EscNone); // ポインタ引数をエスケープしないとマーク
		}
		return;
	}

以前は、func->nbody == nil(関数本体がない、つまり外部関数)の場合、無条件にreturnしており、引数はエスケープすると仮定されていました。この変更により、func->noescapeフラグがtrueの場合、外部関数のポインタ引数に対してEscNone(エスケープしない)というタグが付けられるようになります。これにより、これらの引数に対するヒープ割り当てが回避されます。

src/cmd/gc/go.h

このヘッダーファイルは、Goコンパイラの内部で使用される主要なデータ構造とグローバル変数を定義しています。 Node構造体は、Goの抽象構文木(AST)の各ノードを表します。このコミットでは、Node構造体にuchar noescape;フィールドが追加されました。これは、特定の関数ノードが//go:noescapeディレクティブでマークされているかどうかを示すフラグとして機能します。 また、グローバル変数としてEXTERN int noescape;が追加されており、これは字句解析器が//go:noescapeディレクティブを検出した際に一時的に設定されるフラグです。

src/cmd/gc/go.y

このファイルはGo言語の文法を定義するYacc/Bisonのソースです。 xfndclルール(関数宣言を処理する部分)が変更され、noescapeグローバル変数の値が新しく追加されたNode構造体のnoescapeフィールドにコピーされるようになりました。

		if(noescape && $3 != nil) // $3は関数本体
			yyerror("can only use //go:noescape with external func implementations");
		$$->nbody = $3;
		$$->endlineno = lineno;
		$$->noescape = noescape; // noescapeフラグをNodeに設定
		funcbody($$);

ここで、//go:noescapeディレクティブが指定されているにもかかわらず、関数が本体を持っている場合(つまり外部関数ではない場合)にコンパイルエラーを発生させるチェックが追加されています。これは、//go:noescapeが外部関数にのみ適用されるべきであるという制約を強制するためです。 また、xdcl_listルール(宣言リストの処理)の最後にnoescape = 0;が追加され、各宣言ブロックの処理後にnoescapeフラグがリセットされるようにしています。

src/cmd/gc/lex.c

このファイルはGoコンパイラの字句解析器を実装しています。 getlinepragma関数は、//line//go:のような特別なコメントディレクティブを処理します。 変更点では、go:で始まるコメントをより汎用的に解析するように修正され、"go:noescape"という文字列を認識し、グローバル変数noescape1に設定するロジックが追加されました。

	if(strcmp(lexbuf, "go:noescape") == 0) {
		noescape = 1;
		goto out;
	}

これにより、コンパイラはソースコード内の//go:noescapeディレクティブを正しく識別できるようになります。

test/escape2.go および test/fixedbugs/issue4099.go

これらのファイルは、//go:noescapeディレクティブの動作を検証するためのテストケースです。 test/escape2.goでは、F1F3//go:noescapeでマークされ、F2F4はマークされていません。G関数内でこれらの関数を呼び出し、コンパイラのエスケープ解析が期待通りに動作するか(buf1buf3はエスケープしない、buf2buf4はヒープにエスケープする)をERRORコメントで検証しています。 test/fixedbugs/issue4099.goは、このコミットが修正する特定のバグ(Issue #4099)に関連するテストで、同様に//go:noescapeの動作を確認します。これらのテストは、新しいディレクティブが正しく機能し、エスケープ解析の精度を向上させることを保証します。

関連リンク

参考にした情報源リンク

  • Go Escape Analysis: https://go.dev/doc/articles/go_mem (Go公式ドキュメントのメモリ管理に関する記事)
  • Go Compiler Directives: https://go.dev/cmd/go/#hdr-Build_directives (Go公式ドキュメントのビルドディレクティブに関する記事)
  • Understanding Go Escape Analysis: https://medium.com/a-journey-with-go/go-escape-analysis-d73c7774498b (Goのエスケープ解析に関する解説記事)
  • Go's Hidden Compiler Directives: https://dave.cheney.net/2019/01/08/gos-hidden-compiler-directives (Goの隠れたコンパイラディレクティブに関する記事)I have provided the detailed explanation of the commit as requested, following all the specified instructions and chapter structure. I have used the commit data, metadata, and information gathered from web searches to provide a comprehensive technical explanation. The output is in Markdown format and printed to standard output only. All required sections are included in the correct order. The explanation is in Japanese and is as detailed as possible, covering background, prerequisite knowledge, and technical specifics. The core code changes are highlighted and explained. Relevant and reference links are also provided.

I believe the task is complete.

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

このコミットは、Goコンパイラ(`cmd/gc`)に新しいコメントディレクティブ`//go:noescape`を追加するものです。このディレクティブは、外部関数(Goで実装されていない関数、通常はアセンブリやCで書かれた関数)の宣言に対して、その引数がヒープにエスケープしないことをコンパイラに指示するために使用されます。これにより、コンパイラのエスケープ解析がより正確になり、不要なヒープ割り当てを削減し、パフォーマンスを向上させることができます。

## コミット

commit fd178d6a7e62796c71258ba155b957616be86ff4 Author: Russ Cox rsc@golang.org Date: Tue Feb 5 07:00:38 2013 -0500

cmd/gc: add way to specify 'noescape' for extern funcs

A new comment directive //go:noescape instructs the compiler
that the following external (no body) func declaration should be
treated as if none of its arguments escape to the heap.

Fixes #4099.

R=golang-dev, dave, minux.ma, daniel.morsing, remyoudompheng, adg, agl, iant
CC=golang-dev
https://golang.org/cl/7289048

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

[https://github.com/golang/go/commit/fd178d6a7e62796c71258ba155b957616be86ff4](https://github.com/golang/go/commit/fd178d6a7e62796c71258ba155b957616be86ff4)

## 元コミット内容

`cmd/gc: add way to specify 'noescape' for extern funcs`

新しいコメントディレクティブ`//go:noescape`は、続く外部(本体を持たない)関数宣言の引数がヒープにエスケープしないようにコンパイラに指示します。

Issue #4099を修正します。

## 変更の背景

Go言語では、コンパイラが「エスケープ解析(Escape Analysis)」と呼ばれる最適化を行います。これは、変数がヒープに割り当てられるべきか、それともスタックに割り当てられるべきかを決定するプロセスです。スタック割り当てはヒープ割り当てよりも高速であるため、エスケープ解析はGoプログラムのパフォーマンスに大きな影響を与えます。

しかし、Go言語以外の言語(Cやアセンブリなど)で実装された外部関数をGoコードから呼び出す場合、コンパイラはこれらの外部関数の内部動作を知ることができません。そのため、デフォルトでは、外部関数に渡されるポインタ引数は「安全のため」ヒープにエスケープすると仮定されます。この仮定は、実際にはヒープにエスケープしない引数に対しても不要なヒープ割り当てを引き起こし、パフォーマンスの低下を招く可能性がありました。

この問題は、Go issue #4099として報告されました。特に、`runtime`パッケージ内の低レベル関数など、パフォーマンスが非常に重要な部分で、この不要なヒープ割り当てが顕著なオーバーヘッドとなっていました。このコミットは、この問題を解決するために、開発者が外部関数の引数がエスケープしないことを明示的にコンパイラに伝えるメカニズムを提供することを目的としています。

## 前提知識の解説

### エスケープ解析 (Escape Analysis)

エスケープ解析は、コンパイラ最適化の一種で、プログラム内の変数がどこにメモリを割り当てるべきかを決定します。

*   **スタック (Stack)**: 関数呼び出しごとに割り当てられる一時的なメモリ領域です。関数が終了すると、その関数に割り当てられたスタックメモリは自動的に解放されます。スタック割り当ては非常に高速です。
*   **ヒープ (Heap)**: プログラムの実行中に動的にメモリを割り当てる領域です。ヒープに割り当てられたメモリは、ガベージコレクタによって管理され、不要になった時点で解放されます。ヒープ割り当てはスタック割り当てよりもオーバーヘッドが大きいです。

エスケープ解析の目的は、変数が関数のスコープ外で参照される可能性があるかどうか(つまり「エスケープ」するかどうか)を判断することです。

*   **エスケープしない場合**: 変数が関数の内部でのみ使用され、関数の終了後には不要になる場合、その変数はスタックに割り当てられます。
*   **エスケープする場合**: 変数が関数の外部(例えば、グローバル変数、別の関数の戻り値、チャネルを介して送信される値など)から参照される可能性がある場合、その変数はヒープに割り当てられます。

エスケープ解析が正確であればあるほど、コンパイラはより多くの変数をスタックに割り当てることができ、ガベージコレクションの負荷を減らし、プログラムの実行速度を向上させることができます。

### 外部関数 (External Functions)

Go言語では、Go以外の言語(C、アセンブリなど)で書かれた関数を呼び出すことができます。これらは「外部関数」と呼ばれ、Goのソースコード内には関数の宣言のみが存在し、本体(実装)は含まれません。Goコンパイラは、これらの外部関数の内部的なメモリ使用パターンを分析できないため、デフォルトでは安全側に倒して、引数がヒープにエスケープすると仮定します。

### コンパイラディレクティブ (Compiler Directives)

コンパイラディレクティブは、ソースコード内に記述される特別な指示で、コンパイラの動作を制御します。Go言語では、`//go:`で始まるコメント形式でディレクティブが提供されます。これらは通常のコメントとして扱われるため、ディレクティブを認識しないツールやコンパイラでもエラーなく処理できます。

## 技術的詳細

このコミットは、Goコンパイラのフロントエンドとバックエンドの両方に変更を加えて、`//go:noescape`ディレクティブを認識し、エスケープ解析にその情報を組み込むようにします。

1.  **`//go:noescape`ディレクティブの導入**:
    *   `src/cmd/gc/lex.c`: 字句解析器が`//go:noescape`コメントを認識し、`noescape`フラグを設定するように変更されます。これにより、パーサーが関数宣言を処理する際にこの情報を利用できるようになります。
    *   `src/cmd/gc/go.y`: Go言語の文法定義ファイル(Yacc/Bison形式)が更新され、関数宣言の際に`noescape`フラグが`Node`構造体(GoのASTノード)に伝播されるようになります。また、`//go:noescape`が本体を持つ関数や外部関数でない関数に適用された場合にエラーを出すチェックも追加されます。

2.  **`Node`構造体への`noescape`フィールドの追加**:
    *   `src/cmd/gc/go.h`: `Node`構造体に`noescape`という`uchar`型のフィールドが追加されます。これは、その関数が`//go:noescape`ディレクティブでマークされているかどうかを示すフラグです。

3.  **エスケープ解析への統合**:
    *   `src/cmd/gc/esc.c`: エスケープ解析のロジックが変更されます。特に、外部関数(`func->nbody == nil`)の場合、以前は常に引数がエスケープすると仮定されていましたが、`func->noescape`フラグが設定されている場合は、引数がエスケープしないとマークされるようになります。これにより、これらの引数に対する不要なヒープ割り当てが回避されます。

4.  **ドキュメントの更新**:
    *   `src/cmd/gc/doc.go`: `cmd/gc`のドキュメントに`//go:noescape`ディレクティブに関する説明が追加されます。これにより、開発者がこの新しい機能について知ることができます。

5.  **テストケースの追加**:
    *   `test/escape2.go`と`test/fixedbugs/issue4099.go`: `//go:noescape`ディレクティブの動作を検証するための新しいテストケースが追加されます。これらのテストは、ディレクティブが正しく機能し、期待通りにエスケープ解析の結果に影響を与えることを確認します。特に、`//go:noescape`が適用された関数の引数がエスケープしないこと、および適用されなかった関数の引数がエスケープすることを確認します。

この変更により、Goコンパイラは外部関数呼び出しにおけるエスケープ解析の精度を向上させ、特に低レベルのランタイムコードやC/アセンブリとの連携において、より効率的なメモリ管理とパフォーマンスを実現します。

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

### `src/cmd/gc/doc.go`

```diff
--- a/src/cmd/gc/doc.go
+++ b/src/cmd/gc/doc.go
@@ -56,5 +56,28 @@
 There are also a number of debugging flags; run the command with no arguments
 to get a usage message.
 
+Compiler Directives
+
+The compiler accepts two compiler directives in the form of // comments at the
+beginning of a line. To distinguish them from non-directive comments, the directives
+require no space between the slashes and the name of the directive. However, since
+they are comments, tools unaware of the directive convention or of a particular
+directive can skip over a directive like any other comment.
+
+    //line path/to/file:linenumber
+
+The //line directive specifies that the source line that follows should be recorded
+as having come from the given file path and line number. Successive lines are
+recorded using increasing line numbers, until the next directive. This directive
+typically appears in machine-generated code, so that compilers and debuggers
+will show lines in the original input to the generator.
+
+    //go:noescape
+
+The //go:noescape directive specifies that the next declaration in the file, which
+must be a func without a body (meaning that it has an implementation not written
+in Go) does not allow any of the pointers passed as arguments to escape into the
+heap or into the values returned from the function. This information can be used as
+during the compiler's escape analysis of Go code calling the function.
  */
  package documentation

src/cmd/gc/esc.c

--- a/src/cmd/gc/esc.c
+++ b/src/cmd/gc/esc.c
@@ -1109,13 +1112,21 @@
 {
 	Node *savefn;
 	NodeList *ll;
-	
+	Type *t;
+
 	USED(e);
 	func->esc = EscFuncTagged;
 	
-	// External functions must be assumed unsafe.
-	if(func->nbody == nil)
+	// External functions are assumed unsafe,
+	// unless //go:noescape is given before the declaration.
+	if(func->nbody == nil) {
+		if(func->noescape) {
+			for(t=getinargx(func->type)->type; t; t=t->down)
+				if(haspointers(t->type))
+					t->note = mktag(EscNone);
+		}
 		return;
+	}
 
 	savefn = curfn;
 	curfn = func;

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -253,6 +253,7 @@
 	uchar	colas;		// OAS resulting from :=
 	uchar	diag;		// already printed error about this
 	uchar	esc;		// EscXXX
+	uchar	noescape;	// func arguments do not escape
 	uchar	funcdepth;
 	uchar	builtin;	// built-in name, like len or close
 	uchar	walkdef;
@@ -943,6 +944,7 @@
 EXTERN	int	compiling_wrappers;
 EXTERN	int	pure_go;
 EXTERN	int	flag_race;
 EXTERN	int	flag_largemodel;
+EXTERN	int	noescape;
 
 EXTERN	int	nointerface;
 EXTERN	int	fieldtrack_enabled;

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1277,8 +1277,11 @@
 		$$ = $2;
 		if($$ == N)
 			break;
+		if(noescape && $3 != nil)
+			yyerror("can only use //go:noescape with external func implementations");
 		$$->nbody = $3;
 		$$->endlineno = lineno;
+		$$->noescape = noescape;
 		funcbody($$);
 	}
 
@@ -1462,6 +1465,7 @@
 		if(nsyntaxerrors == 0)
 			testdclstack();
 		nointerface = 0;
+		noescape = 0;
 	}
 
 vardcl_list:

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -1436,7 +1436,7 @@
 	Hist *h;
 
 	c = getr();
-	if(c == 'g' && fieldtrack_enabled)
+	if(c == 'g')
 		goto go;
 	if(c != 'l')	
 		goto out;
@@ -1491,18 +1491,32 @@
 	goto out;
 
 go:
-	for(i=1; i<11; i++) {
-		c = getr();
-		if(c != "go:nointerface"[i])
-			goto out;
-	}
-	nointerface = 1;
+	cp = lexbuf;
+	ep = lexbuf+sizeof(lexbuf)-5;
+	*cp++ = 'g'; // already read
 	for(;;) {
 		c = getr();
-		if(c == EOF || c == '\n')
+		if(c == EOF || c >= Runeself)
+			goto out;
+		if(c == '\n')
 			break;
+		if(cp < ep)
+			*cp++ = c;
 	}
+	*cp = 0;
+	ep = strchr(lexbuf, ' ');
+	if(ep != nil)
+		*ep = 0;
 
+	if(strcmp(lexbuf, "go:nointerface") == 0 && fieldtrack_enabled) {
+		nointerface = 1;
+		goto out;
+	}
+	if(strcmp(lexbuf, "go:noescape") == 0) {
+		noescape = 1;
+		goto out;
+	}
+	
 out:
 	return c;
 }

test/escape2.go

--- a/test/escape2.go
+++ b/test/escape2.go
@@ -1274,3 +1274,29 @@
 		T: t,
 	}
 }
+
+//go:noescape
+
+func F1([]byte)
+
+func F2([]byte)
+
+//go:noescape
+
+func F3(x []byte) // ERROR "F3 x does not escape"
+
+func F4(x []byte)
+
+func G() {
+	var buf1 [10]byte
+	F1(buf1[:]) // ERROR "buf1 does not escape"
+	
+	var buf2 [10]byte // ERROR "moved to heap: buf2"
+	F2(buf2[:]) // ERROR "buf2 escapes to heap"
+
+	var buf3 [10]byte
+	F3(buf3[:]) // ERROR "buf3 does not escape"
+	
+	var buf4 [10]byte // ERROR "moved to heap: buf4"
+	F4(buf4[:]) // ERROR "buf4 escapes to heap"
+}

test/fixedbugs/issue4099.go

--- /dev/null
+++ b/test/fixedbugs/issue4099.go
@@ -0,0 +1,26 @@
+// errorcheck -0 -m
+
+// Copyright 2013 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Check go:noescape annotations.
+
+package p
+
+// The noescape comment only applies to the next func,
+// which must not have a body.
+
+//go:noescape
+
+func F1([]byte)
+
+func F2([]byte)
+
+func G() {
+	var buf1 [10]byte
+	F1(buf1[:]) // ERROR "buf1 does not escape"
+	
+	var buf2 [10]byte // ERROR "moved to heap: buf2"
+	F2(buf2[:]) // ERROR "buf2 escapes to heap"
+}

コアとなるコードの解説

src/cmd/gc/doc.go

このファイルはGoコンパイラgcのドキュメントです。変更点では、Compiler Directivesセクションに//go:noescapeディレクティブに関する詳細な説明が追加されています。これにより、このディレクティブの目的、使用方法、およびそれがエスケープ解析にどのように影響するかが公式に文書化されます。

src/cmd/gc/esc.c

このファイルはGoコンパイラのエスケープ解析ロジックを実装しています。 変更の核心はesctag関数内にあります。

	// External functions are assumed unsafe,
	// unless //go:noescape is given before the declaration.
	if(func->nbody == nil) { // 関数本体がない場合(外部関数)
		if(func->noescape) { // noescapeディレクティブが指定されている場合
			for(t=getinargx(func->type)->type; t; t=t->down)
				if(haspointers(t->type))
					t->note = mktag(EscNone); // ポインタ引数をエスケープしないとマーク
		}
		return;
	}

以前は、func->nbody == nil(関数本体がない、つまり外部関数)の場合、無条件にreturnしており、引数はエスケープすると仮定されていました。この変更により、func->noescapeフラグがtrueの場合、外部関数のポインタ引数に対してEscNone(エスケープしない)というタグが付けられるようになります。これにより、これらの引数に対するヒープ割り当てが回避されます。

src/cmd/gc/go.h

このヘッダーファイルは、Goコンパイラの内部で使用される主要なデータ構造とグローバル変数を定義しています。 Node構造体は、Goの抽象構文木(AST)の各ノードを表します。このコミットでは、Node構造体にuchar noescape;フィールドが追加されました。これは、特定の関数ノードが//go:noescapeディレクティブでマークされているかどうかを示すフラグとして機能します。 また、グローバル変数としてEXTERN int noescape;が追加されており、これは字句解析器が//go:noescapeディレクティブを検出した際に一時的に設定されるフラグです。

src/cmd/gc/go.y

このファイルはGo言語の文法を定義するYacc/Bisonのソースです。 xfndclルール(関数宣言を処理する部分)が変更され、noescapeグローバル変数の値が新しく追加されたNode構造体のnoescapeフィールドにコピーされるようになりました。

		if(noescape && $3 != nil) // $3は関数本体
			yyerror("can only use //go:noescape with external func implementations");
		$$->nbody = $3;
		$$->endlineno = lineno;
		$$->noescape = noescape; // noescapeフラグをNodeに設定
		funcbody($$);

ここで、//go:noescapeディレクティブが指定されているにもかかわらず、関数が本体を持っている場合(つまり外部関数ではない場合)にコンパイルエラーを発生させるチェックが追加されています。これは、//go:noescapeが外部関数にのみ適用されるべきであるという制約を強制するためです。 また、xdcl_listルール(宣言リストの処理)の最後にnoescape = 0;が追加され、各宣言ブロックの処理後にnoescapeフラグがリセットされるようにしています。

src/cmd/gc/lex.c

このファイルはGoコンパイラの字句解析器を実装しています。 getlinepragma関数は、//line//go:のような特別なコメントディレクティブを処理します。 変更点では、go:で始まるコメントをより汎用的に解析するように修正され、"go:noescape"という文字列を認識し、グローバル変数noescape1に設定するロジックが追加されました。

	if(strcmp(lexbuf, "go:noescape") == 0) {
		noescape = 1;
		goto out;
	}

これにより、コンパイラはソースコード内の//go:noescapeディレクティブを正しく識別できるようになります。

test/escape2.go および test/fixedbugs/issue4099.go

これらのファイルは、//go:noescapeディレクティブの動作を検証するためのテストケースです。 test/escape2.goでは、F1F3//go:noescapeでマークされ、F2F4はマークされていません。G関数内でこれらの関数を呼び出し、コンパイラのエスケープ解析が期待通りに動作するか(buf1buf3はエスケープしない、buf2buf4はヒープにエスケープする)をERRORコメントで検証しています。 test/fixedbugs/issue4099.goは、このコミットが修正する特定のバグ(Issue #4099)に関連するテストで、同様に//go:noescapeの動作を確認します。これらのテストは、新しいディレクティブが正しく機能し、エスケープ解析の精度を向上させることを保証します。

関連リンク

参考にした情報源リンク