[インデックス 16824] ファイルの概要
このコミットは、Goランタイムにおける関数の引数サイズの表現方法のクリーンアップに関するものです。主にGoのアセンブラ(cmd/5a, cmd/6a, cmd/8a)とコンパイラ(cmd/5c, cmd/6c, cmd/8c, cmd/cc)、リンカ(cmd/ld)のコード、そしてランタイムのヘッダファイル(src/pkg/runtime/funcdata.h, src/pkg/runtime/runtime.h)に影響を与えています。
具体的には、以下のファイルが変更されました。
src/cmd/5a/a.ysrc/cmd/5a/y.tab.csrc/cmd/5a/y.tab.hsrc/cmd/5c/sgen.csrc/cmd/6a/a.ysrc/cmd/6a/y.tab.csrc/cmd/6a/y.tab.hsrc/cmd/6c/sgen.csrc/cmd/8a/a.ysrc/cmd/8a/y.tab.csrc/cmd/8a/y.tab.hsrc/cmd/8c/sgen.csrc/cmd/cc/pgen.csrc/cmd/ld/lib.csrc/cmd/ld/lib.hsrc/pkg/runtime/funcdata.hsrc/pkg/runtime/runtime.hsrc/pkg/runtime/traceback_arm.csrc/pkg/runtime/traceback_x86.c
これらの変更は、主にアセンブラの文法解析器(Bisonによって生成されたy.tab.cファイル群)と、アセンブラが生成するオブジェクトコードにおける関数引数サイズのメタデータ表現に集中しています。
コミット
commit 6fc49c18540938cd4699c1eb8cb05bd00ff9f59c
Author: Keith Randall <khr@golang.org>
Date: Fri Jul 19 11:19:18 2013 -0700
runtime: cleanup: use ArgsSizeUnknown to mark all functions
whose argument size is unknown (C vararg functions, and
assembly code without an explicit specification).
We used to use 0 to mean "unknown" and 1 to mean "zero".
Now we use ArgsSizeUnknown (0x80000000) to mean "unknown".
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/11590043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6fc49c18540938cd4699c1eb8cb05bd00ff9f59c
元コミット内容
runtime: cleanup: use ArgsSizeUnknown to mark all functions
whose argument size is unknown (C vararg functions, and
assembly code without an explicit specification).
We used to use 0 to mean "unknown" and 1 to mean "zero".
Now we use ArgsSizeUnknown (0x80000000) to mean "unknown".
変更の背景
Goランタイムでは、関数の引数サイズに関するメタデータを管理しています。これは、ガベージコレクション(GC)やスタックトレースバックなどのランタイム機能が、スタック上の引数領域を正確に識別するために不可欠です。
このコミット以前は、関数の引数サイズを示すために、0という値が二重の意味で使われていました。
- 引数サイズがゼロであること:関数が引数を全く取らない場合。
- 引数サイズが不明であること:C言語の可変長引数関数(
vararg関数)や、明示的な引数サイズ指定がないアセンブリコードで書かれた関数など、コンパイル時に引数サイズを静的に決定できない場合。
この二重の意味は、ランタイムが引数サイズを解釈する際に曖昧さを生じさせ、潜在的なバグや複雑な条件分岐の原因となっていました。特に、引数サイズが本当にゼロなのか、それとも単に不明なのかを区別する必要がある場合に問題が生じます。例えば、GCがスタックをスキャンする際、引数サイズが不明な関数では、その領域を安全にスキップしたり、特別な処理を適用したりする必要がありますが、引数サイズがゼロの関数ではその必要がありません。この曖昧さを解消し、ランタイムの堅牢性と保守性を向上させることが、この変更の主な背景です。
前提知識の解説
Goランタイムと関数メタデータ
Goプログラムは、Goランタイムと呼ばれる小さな実行環境上で動作します。このランタイムは、スケジューラ、ガベージコレクタ、メモリ管理、スタック管理など、Goプログラムの実行を支える多くの低レベルな機能を提供します。
Goランタイムは、実行中の関数に関する様々なメタデータ(関数名、ファイル名、行番号、スタックフレームサイズ、引数サイズなど)を保持しています。これらのメタデータは、コンパイラやアセンブラ、リンカによって生成され、実行可能ファイルに埋め込まれます。特に、関数の引数サイズは、スタックの巻き戻し(traceback)やガベージコレクションにおいて、スタック上のどの領域が引数として使用されているかを正確に判断するために重要です。
C言語の可変長引数関数(C vararg functions)
C言語には、printfやscanfのように引数の数が可変である関数を定義する機能があります。これらの関数は、コンパイル時に引数の型や数が確定しないため、呼び出し規約によっては、呼び出し側がスタックに積む引数の総サイズを静的に決定することが困難な場合があります。GoランタイムがCコードと連携する場合、このような関数の引数サイズを正確に追跡することは複雑になります。
アセンブリコードにおける引数サイズ指定
Goの標準ライブラリやランタイムの一部は、パフォーマンス上の理由からアセンブリ言語で書かれています。アセンブリコードで関数を記述する場合、通常は呼び出し規約に従って引数をスタックに配置し、そのサイズを明示的に指定する必要があります。しかし、アセンブリコードの記述によっては、この引数サイズ情報が欠落したり、明示的に指定されなかったりする場合があります。
ArgsSizeUnknown
このコミットで導入されたArgsSizeUnknownは、Goランタイム内部で使用される特殊な定数です。これは、関数の引数サイズがコンパイル時に静的に決定できないことを示すマーカーとして機能します。コミットメッセージにあるように、この値は0x80000000(符号なし32ビット整数の最上位ビットがセットされた値、または符号付き32ビット整数で表現される最小値)として定義されています。この値は、通常の有効な引数サイズ(非負の整数)と衝突しないように選ばれています。
技術的詳細
このコミットの核心は、関数の引数サイズを表現するためのセマンティクスを変更し、曖昧さを排除することにあります。
変更前:
0: 引数サイズがゼロ(引数なし)0: 引数サイズが不明(C vararg関数、明示的な指定がないアセンブリ関数)
変更後:
0: 引数サイズがゼロ(引数なし)ArgsSizeUnknown(0x80000000): 引数サイズが不明
この変更により、ランタイムは引数サイズの値を見るだけで、その関数が引数を全く取らないのか、それとも引数サイズが不明な特殊な関数なのかを明確に区別できるようになります。
具体的な影響は以下の通りです。
-
アセンブラの変更:
- Goのアセンブラ(
cmd/5a,cmd/6a,cmd/8a)は、アセンブリコードを解析し、オブジェクトファイルを生成する際に、関数のメタデータとして引数サイズを記録します。 - 以前は、アセンブリコードで引数サイズが明示的に指定されていない場合、またはC vararg関数としてマークされるべき関数に対して、アセンブラは引数サイズを
0と記録していました。 - この変更により、アセンブラはこれらのケースで
ArgsSizeUnknownを記録するように修正されました。特に、a.y(Bisonの文法定義ファイル)の変更は、アセンブラの構文解析ロジックに影響を与え、特定の命令(例:LTYPEB name ',' imm)がArgsSizeUnknownを生成するように変更されています。 y.tab.cファイルはBisonによって生成されるため、a.yの変更に伴い、その内容が大幅に更新されています。これは、文法解析器の内部状態やアクションが変更されたことを意味します。
- Goのアセンブラ(
-
コンパイラの変更:
- Goのコンパイラ(
cmd/5c,cmd/6c,cmd/8c,cmd/cc)も、Goソースコードからオブジェクトファイルを生成する際に、関数の引数サイズ情報を扱います。 - C vararg関数を呼び出すGoコードや、GoとCの連携部分において、コンパイラが生成するメタデータに
ArgsSizeUnknownが適切に反映されるように修正が必要です。
- Goのコンパイラ(
-
リンカの変更:
- Goのリンカ(
cmd/ld)は、複数のオブジェクトファイルを結合して最終的な実行可能ファイルを生成します。この過程で、リンカは関数に関するすべてのメタデータを集約し、最終的な実行可能ファイルに埋め込みます。 - リンカは、
ArgsSizeUnknownという新しい特殊な値の存在を認識し、それを正しく処理するように更新されました。
- Goのリンカ(
-
ランタイムの変更:
src/pkg/runtime/funcdata.hは、関数に関するメタデータの構造を定義するヘッダファイルです。ここにArgsSizeUnknownの定義が追加されました。src/pkg/runtime/runtime.hやsrc/pkg/runtime/traceback_arm.c,src/pkg/runtime/traceback_x86.cなどのランタイムコードは、この新しいArgsSizeUnknownのセマンティクスを理解し、それに基づいてスタックトレースバックやGCスキャンなどの処理を調整する必要があります。例えば、スタックトレースバックの際に引数サイズがArgsSizeUnknownである関数に遭遇した場合、ランタイムは通常の引数サイズ計算ロジックではなく、特別なフォールバックメカニズムを使用する可能性があります。
この変更は、Goランタイムの内部的な整合性を高め、将来的な機能拡張(特にGCの改善やデバッグツールの強化)のためのより堅牢な基盤を築くものです。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主にアセンブラの文法定義ファイル(a.y)と、ランタイムの関数データ定義ファイル(funcdata.h)に見られます。
src/pkg/runtime/funcdata.h
--- a/src/pkg/runtime/funcdata.h
+++ b/src/pkg/runtime/funcdata.h
@@ -1,5 +1,6 @@
// 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.
+// ArgsSizeUnknown is a special value in Func.argsize meaning
+// the argument size is unknown.
+#define ArgsSizeUnknown 0x80000000
+
// Func.args is the total size of the arguments on the stack.
// It is used by stack unwinding, gc, etc.
// If args is 0x80000000, the argument size is unknown.
この変更は、ArgsSizeUnknownという新しい定数を0x80000000として定義しています。これは、関数の引数サイズが不明であることを示す特殊なマーカーとして使用されます。
src/cmd/5a/a.y (ARMアセンブラの文法定義)
--- a/src/cmd/5a/a.y
+++ b/src/cmd/5a/a.y
@@ -33,6 +33,7 @@
#include <stdio.h> /* if we don't, bison will, and a.h re-#defines getc */
#include <libc.h>
#include "a.h"
+#include "../../pkg/runtime/funcdata.h"
%}
%union
{
@@ -217,18 +218,18 @@ inst:
*/
| LTYPEB name ',' imm
{
+\t\t$4.type = D_CONST2;
+\t\t$4.offset2 = ArgsSizeUnknown;
\t\toutcode($1, Always, &$2, 0, &$4);
}
| LTYPEB name ',' con ',' imm
{
+\t\t$6.type = D_CONST2;
+\t\t$6.offset2 = ArgsSizeUnknown;
\t\toutcode($1, Always, &$2, $4, &$6);
}
| LTYPEB name ',' con ',' imm '-' con
{
-\t\t// Change explicit 0 argument size to 1
-\t\t// so that we can distinguish it from missing.
-\t\tif($8 == 0)
-\t\t\t$8 = 1;
\t\t$6.type = D_CONST2;
\t\t$6.offset2 = $8;
\t\toutcode($1, Always, &$2, $4, &$6);
同様の変更がsrc/cmd/6a/a.y (x86-64アセンブラ) と src/cmd/8a/a.y (x86アセンブラ) にも適用されています。
コアとなるコードの解説
ArgsSizeUnknownの定義
src/pkg/runtime/funcdata.hに追加された#define ArgsSizeUnknown 0x80000000は、このコミットの基盤です。この定数は、Goランタイムが関数の引数サイズを表現する際に、不明なサイズを示すために使用するビットパターンを定義しています。0x80000000は、32ビット符号付き整数で表現できる最小値であり、通常の正の引数サイズとは決して衝突しないため、明確な区別が可能です。
アセンブラの文法変更 (a.yファイル群)
a.yファイルは、Goのアセンブラがアセンブリコードを解析するための文法規則を定義しています。このファイルはBison(YaccのGNU版)によって処理され、C言語のソースファイル(y.tab.c)が生成されます。
変更されたセクションは、アセンブリ命令の引数として関数名と引数サイズが指定される部分です。
-
LTYPEB name ',' immおよびLTYPEB name ',' con ',' immのケース: これらの規則は、アセンブリコードで関数を定義する際に、その関数の引数サイズを明示的に指定しない場合にマッチします。以前は、これらのケースでは引数サイズが0として扱われていました。 変更後、$4.offset2 = ArgsSizeUnknown;または$6.offset2 = ArgsSizeUnknown;という行が追加されました。これは、アセンブラがこれらの命令を処理する際に、生成される関数のメタデータにArgsSizeUnknownをセットすることを意味します。これにより、引数サイズが不明な関数が0と誤って解釈されることがなくなります。 -
LTYPEB name ',' con ',' imm '-' conのケース: この規則は、アセンブリコードで引数サイズが明示的に指定される場合にマッチします(例:TEXT ·foo(SB), $0-8のような形式で、8が引数サイズ)。 以前のコードには、if($8 == 0) $8 = 1;という行がありました。これは、「明示的に0と指定された引数サイズを1に変更する」というロジックです。このロジックは、引数サイズがゼロであることと、引数サイズが不明であることの0という値の衝突を回避するための暫定的な措置でした。しかし、これは「引数サイズが本当にゼロである」という情報を歪めてしまう問題がありました。 このコミットでは、ArgsSizeUnknownが導入されたため、この曖昧さを回避するための暫定的なロジックは不要となり、if($8 == 0) $8 = 1;の行が削除されました。これにより、アセンブラは明示的に指定された引数サイズ($8)をそのまま使用できるようになり、引数サイズがゼロの関数は正しく0として記録されるようになります。
これらの変更により、Goのアセンブラは、関数の引数サイズに関するメタデータをより正確かつ明確に生成するようになり、ランタイムがこの情報を利用する際の曖昧さが解消されました。これは、Goのスタック管理、ガベージコレクション、およびデバッグ機能の正確性と堅牢性を向上させる上で重要な改善です。
関連リンク
- GoのIssueトラッカーでの関連する変更リスト: https://golang.org/cl/11590043
- GitHub上のコミットページ: https://github.com/golang/go/commit/6fc49c18540938cd4699c1eb8cb05bd00ff9f59c
参考にした情報源リンク
ArgsSizeUnknownに関する情報:- https://h-da.de/
- https://p2hp.com/
- https://golang.org/