[インデックス 16783] ファイルの概要
このコミットは、Go言語のツールチェインにおけるアセンブラ(cmd/5a
、cmd/6a
、cmd/8a
)がPCDATA
ディレクティブを正しくパースできるようにするための変更です。特にcmd/5a
(ARMアーキテクチャ向けアセンブラ)においては、TEXT
命令における引数サイズの指定をサポートするよう拡張されています。これにより、Goランタイムがアセンブリコードからより正確なメタデータ(特にガベージコレクションに必要なポインタ情報)を抽出できるようになり、異なるアーキテクチャでのGoプログラムの正確な動作と最適化に貢献します。
コミット
commit 47b89a37d9e83858ebcecb331ab3a0cfbd66dffc
Author: Russ Cox <rsc@golang.org>
Date: Tue Jul 16 16:23:28 2013 -0400
cmd/5a, cmd/6a, cmd/8a: parse PCDATA
In cmd/5a, also add support for argument size in TEXT instruction.
R=ken2
CC=golang-dev
https://golang.org/cl/11357044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/47b89a37d9e83858ebcecb331ab3a0cfbd66dffc
元コミット内容
cmd/5a, cmd/6a, cmd/8a: parse PCDATA
In cmd/5a, also add support for argument size in TEXT instruction.
R=ken2
CC=golang-dev
https://golang.org/cl/11357044
変更の背景
Go言語のランタイム、特にガベージコレクタは、実行時にスタックフレーム内のポインタの位置を正確に把握する必要があります。この情報は、アセンブリコード内でPCDATA
(Program Counter Data)ディレクティブとして埋め込まれます。しかし、Goの初期のアセンブラ(cmd/5a
, cmd/6a
, cmd/8a
)は、このPCDATA
ディレクティブを適切にパースする機能が不足していました。
また、TEXT
命令はGoアセンブリにおいて関数の宣言とプロパティ定義に使用されますが、特にcmd/5a
(ARMアーキテクチャ)においては、関数の引数サイズをTEXT
命令内で直接指定する機能が欠けていました。これは、スタックフレームの管理や関数呼び出し規約の正確な表現において制約となっていました。
このコミットは、これらの問題を解決し、Goランタイムがアセンブリコードから必要なメタデータをより正確に抽出し、異なるアーキテクチャ(特にARM)でのガベージコレクションの効率と正確性を向上させることを目的としています。これにより、Goプログラムのクロスプラットフォーム対応とランタイムの堅牢性が強化されます。
前提知識の解説
PCDATA (Program Counter Data)
PCDATA
は、Goアセンブリ言語におけるディレクティブの一つで、Goランタイム、特にガベージコレクタのためにメタデータを埋め込むために使用されます。PCDATA $INDEX, $VALUE
という形式で記述され、特定のプログラムカウンタ(PC)位置に$INDEX
と$VALUE
のペアを関連付けます。この情報は、ガベージコレクタがスタックフレームやグローバルデータ内のポインタを正確に識別し、効率的なメモリ管理とスタックスキャンを実行するために不可欠です。PCDATA
ディレクティブは通常、開発者が手動で記述するものではなく、Goコンパイラによって自動的に生成されます。
cmd/5a, cmd/6a, cmd/8a
これらは、Goツールチェインの一部であるアセンブラを指します。これらはPlan 9オペレーティングシステムのツールセットに由来しています。
cmd/5a
: ARMアーキテクチャ向けのアセンブラ(特にGOARM=5
を指すことが多い)。cmd/6a
: x86-64(amd64
)アーキテクチャ向けのアセンブラ。cmd/8a
: x86(386
)アーキテクチャ向けのアセンブラ。 これらのアセンブラは、Goコンパイラが生成したアセンブリコードを機械語に変換する役割を担っています。
TEXT命令
GoアセンブリにおけるTEXT
命令は、関数の宣言とそのプロパティを定義するために使用されるディレクティブです。TEXT symbol(SB), [flags,] $framesize [-argumentsize]
という構文を持ちます。
symbol(SB)
: 関数名と、それがプログラムのアドレス空間の原点からの定数オフセットであることを示すSB
(Static Base)擬似レジスタ。flags
(オプション): 関数の追加情報を提供するビットマスクフラグ(例:NOSPLIT
、NOPROF
)。$framesize
: 関数がローカル変数用に割り当てるスタックフレームのサイズ(バイト単位)。-argumentsize
(オプション): 呼び出し元から関数に渡される引数のサイズ(バイト単位)。NOSPLIT
が指定されていない場合、この引数サイズは必須です。
TEXT
命令に続く命令が関数の本体を構成し、通常はRET
(return)擬似命令で終了します。
Yacc (Yet Another Compiler Compiler) と Lex (Lexical Analyzer Generator)
YaccとLexは、コンパイラやインタプリタの構築に広く使用されるツールです。
- Lex: 字句解析器(スキャナまたはレキサ)を生成するプログラムです。入力ストリームをトークン(キーワード、識別子、演算子など)のシーケンスに変換します。正規表現とそれに対応するCコードを記述することで、字句解析器のCソースコードを生成します。
- Yacc: 構文解析器(パーサ)を生成するプログラムです。形式文法(BNFに似た形式)を読み込み、字句解析器が生成したトークンストリームの構文構造を解析するパーサを生成します。文法規則とそれに対応するCコードを記述することで、構文解析器のCソースコードを生成します。
Lexが言語の「単語」(トークン)を扱い、Yaccがそれらの単語から構成される「文」(文法構造)を扱うことで、両者は連携してコンパイラの字句解析と構文解析のフェーズを担います。このコミットで変更されているy.tab.c
ファイルは、Yacc(またはその互換ツールであるBison)によって生成されたC言語のパーサコードです。
技術的詳細
このコミットの技術的詳細には、主に以下の2つの側面があります。
-
PCDATAディレクティブのパース対応:
cmd/5a
,cmd/6a
,cmd/8a
の各アセンブラにおいて、PCDATA
ディレクティブを認識し、その引数をパースする機能が追加されました。これは、アセンブラの字句解析器(Lexによって生成されるlex.c
)と構文解析器(Yacc/Bisonによって生成されるa.y
およびy.tab.c
)の両方に変更が加えられています。a.y
ファイルには、PCDATA imm ',' imm
という新しい文法規則が追加され、PCDATA
命令が2つの即値引数を取ることを定義しています。lex.c
ファイルには、PCDATA
キーワードを認識するためのエントリが追加され、対応するトークン型(LTYPEPC
)が定義されています。y.tab.c
とy.tab.h
ファイルは、Yacc/Bisonのバージョンアップ(Bison 2.4.1から2.3への変更が一部見られますが、これは生成されたファイルのヘッダ情報であり、実質的な機能追加はa.y
とlex.c
の変更によるものです)と、新しいトークン型LTYPEPC
の追加に伴い、再生成されています。これらのファイルは、パーサの内部状態遷移テーブルやトークン定義を保持しており、文法変更に応じて大規模な差分が生じます。
-
cmd/5a
におけるTEXT
命令の引数サイズサポート:cmd/5a
(ARMアセンブラ)に特化して、TEXT
命令が引数サイズを明示的に指定できるようになりました。これは、a.y
ファイル内のinst
(命令)の定義に新しい規則が追加されたことで実現されています。LTYPEB name ',' con ',' imm '-' con
という新しい文法規則が追加されました。これは、TEXT
命令の構文がTEXT symbol(SB), $framesize - argumentsize
の形式をサポートすることを示しています。- 以前は引数サイズが0の場合、それが「指定されていない」のか「明示的に0」なのか区別がつきませんでしたが、この変更により、明示的に0が指定された場合は内部的に1として扱われるようになり、欠落している場合と区別できるようになりました。これは、
Gen
構造体にoffset2
フィールドが追加され、D_CONST2
という新しいタイプが導入されたことと関連しています。offset2
は引数サイズを格納するために使用されます。
これらの変更により、Goのアセンブラは、Goランタイムがガベージコレクションやその他のランタイム情報のために必要とするメタデータを、より正確かつ詳細にアセンブリコードから抽出できるようになりました。特にPCDATA
のパースは、Goのガベージコレクタの正確性と効率に直結する重要な機能です。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、主にsrc/cmd/5a/a.y
、src/cmd/5a/lex.c
、そしてsrc/cmd/5a/a.h
に見られます。cmd/6a
とcmd/8a
についても同様の変更がa.y
とlex.c
に適用されていますが、TEXT
命令の引数サイズに関する変更はcmd/5a
に特化しています。
src/cmd/5a/a.h
--- a/src/cmd/5a/a.h
+++ b/src/cmd/5a/a.h
@@ -98,6 +98,7 @@ struct Gen
{
Sym* sym;
int32 offset;
+ int32 offset2;
short type;
short reg;
short name;
Gen
構造体にoffset2
フィールドが追加されています。これは、TEXT
命令の引数サイズを格納するために使用されます。
src/cmd/5a/a.y
--- a/src/cmd/5a/a.y
+++ b/src/cmd/5a/a.y
@@ -54,7 +54,7 @@
%token <lval>\tLTYPEG LTYPEH LTYPEI LTYPEJ LTYPEK
%token <lval>\tLTYPEL LTYPEM LTYPEN LTYPEBX LTYPEPLD
%token <lval>\tLCONST LSP LSB LFP LPC
-%token <lval>\tLTYPEX LR LREG LF LFREG LC LCREG LPSR LFCR
+%token <lval>\tLTYPEX LTYPEPC LR LREG LF LFREG LC LCREG LPSR LFCR
%token <lval>\tLCOND LS LAT
%token <dval>\tLFCONST
%token <sval>\tLSCONST
@@ -223,6 +223,16 @@ inst:
{
outcode($1, Always, &$2, $4, &$6);
}
+|\tLTYPEB name ',' con ',' imm '-' con
+\t{
+\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);
+\t}
/*
* DATA
*/
@@ -309,6 +319,13 @@ inst:
{
outcode($1, Always, &$2, NREG, &nullgen);
}
+/*
+ * PCDATA
+ */
+|\tLTYPEPC imm ',' imm
+\t{
+\t\toutcode($1, Always, &$2, NREG, &$4);
+\t}
/*
* END
*/
%token
定義にLTYPEPC
が追加され、PCDATA
キーワードに対応するトークン型が導入されています。inst
規則に、LTYPEB name ',' con ',' imm '-' con
という新しいプロダクションが追加されています。これはTEXT
命令の引数サイズをパースするためのものです。$8 == 0
の場合に$8 = 1
とするロジックは、明示的な0と欠落を区別するためのものです。$6.type = D_CONST2; $6.offset2 = $8;
は、引数サイズをGen
構造体のoffset2
フィールドに格納し、タイプをD_CONST2
に設定しています。inst
規則に、LTYPEPC imm ',' imm
という新しいプロダクションが追加されています。これはPCDATA
ディレクティブをパースするためのものです。
src/cmd/5a/lex.c
--- a/src/cmd/5a/lex.c
+++ b/src/cmd/5a/lex.c
@@ -414,6 +414,7 @@ struct
"MULAWB", LTYPEN, AMULAWB,
"USEFIELD", LTYPEN, AUSEFIELD,
+ "PCDATA", LTYPEPC, APCDATA,
0
};
@@ -523,6 +524,13 @@ zaddr(Gen *a, int s)
Bputc(&obuf, a->offset);
break;
+ case D_CONST2:
+ l = a->offset2;
+ Bputc(&obuf, l);
+ Bputc(&obuf, l>>8);
+ Bputc(&obuf, l>>16);
+ Bputc(&obuf, l>>24);
+ // fall through
case D_OREG:
case D_CONST:
case D_BRANCH:
- キーワードテーブルに
PCDATA
が追加され、LTYPEPC
トークン型とAPCDATA
アセンブリ命令コードにマッピングされています。 zaddr
関数にD_CONST2
ケースが追加され、offset2
に格納された4バイトの引数サイズをバイナリ出力に書き込む処理が実装されています。
コアとなるコードの解説
src/cmd/5a/a.h
の Gen
構造体への offset2
追加
Gen
構造体は、アセンブリ命令のオペランド(引数)を表すために使用されます。offset2
フィールドの追加は、TEXT
命令の引数サイズという、従来のoffset
フィールドでは表現しきれなかった追加の数値情報を格納する必要が生じたためです。これにより、アセンブラは関数の引数サイズを明示的に追跡し、ランタイムに正確な情報を提供できるようになります。
src/cmd/5a/a.y
における TEXT
命令の引数サイズパース
|\tLTYPEB name ',' con ',' imm '-' con
\t{
\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);
\t}
このYaccプロダクションは、TEXT
命令の新しい構文TEXT symbol(SB), $framesize - argumentsize
を処理します。
LTYPEB
はTEXT
命令のトークンです。name
は関数名(例:symbol(SB)
)。con
は$framesize
。imm '-' con
は-argumentsize
の部分をパースします。$8
がargumentsize
に対応します。if($8 == 0) $8 = 1;
というロジックは重要です。これは、引数サイズが明示的に0
と指定された場合と、引数サイズが全く指定されなかった場合を区別するためのものです。Goランタイムは、引数サイズが0
の場合でも、それが明示的に指定されたことを示すために1
としてエンコードします。これにより、アセンブラはランタイムに正確なメタデータを提供できます。$6.type = D_CONST2; $6.offset2 = $8;
は、パースされた引数サイズをGen
構造体のoffset2
フィールドに格納し、そのタイプをD_CONST2
に設定します。これにより、後続のコード生成フェーズでこの情報が適切に処理されるようになります。outcode
関数は、パースされた命令とオペランドから機械語コードを生成する役割を担います。
src/cmd/5a/a.y
における PCDATA
ディレクティブパース
/*
* PCDATA
*/
|\tLTYPEPC imm ',' imm
\t{
\t\toutcode($1, Always, &$2, NREG, &$4);
\t}
このYaccプロダクションは、PCDATA
ディレクティブの構文PCDATA $INDEX, $VALUE
を処理します。
LTYPEPC
はPCDATA
キーワードのトークンです。imm ',' imm
は、$INDEX
と$VALUE
という2つの即値引数をパースします。$2
が$INDEX
、$4
が$VALUE
に対応します。outcode
関数は、これらの引数とともにPCDATA
命令を処理し、ランタイムが利用できる形式でメタデータを埋め込みます。
src/cmd/5a/lex.c
における PCDATA
キーワードの追加と D_CONST2
の処理
lex.c
は字句解析器のCソースコードであり、PCDATA
キーワードを認識するためのエントリが追加されたことで、アセンブラはPCDATA
を有効なトークンとして扱えるようになります。
case D_CONST2:
l = a->offset2;
Bputc(&obuf, l);
Bputc(&obuf, l>>8);
Bputc(&obuf, l>>16);
Bputc(&obuf, l>>24);
// fall through
zaddr
関数内のD_CONST2
ケースは、Gen
構造体のoffset2
フィールドに格納された引数サイズ(4バイトの整数)を、バイト単位で出力バッファobuf
に書き込む役割を担います。これは、TEXT
命令の引数サイズが最終的なバイナリに正確にエンコードされることを保証します。fall through
コメントは、このケースの処理後にD_OREG
などの次のケースに処理が続くことを示唆しています。
これらの変更は、Goのアセンブラがより複雑なアセンブリ構文を理解し、Goランタイムが必要とする詳細なメタデータを生成するための基盤を強化するものです。
関連リンク
- Go Assembly Language: https://go.dev/doc/asm
- Go's runtime.Func and PCDATA: https://go.dev/src/runtime/symtab.go (PCDATAの利用例が確認できます)
参考にした情報源リンク
- PCDATA in Go assembly: https://stackoverflow.com/questions/50999560/what-is-pcdata-in-go-assembly
- Go cmd/5a cmd/6a cmd/8a: https://go.googlesource.com/go/+/refs/heads/master/src/cmd/6a/doc.go
- TEXT instruction in Go assembly: https://go.dev/doc/asm#text
- Yacc Lex parser generator: https://en.wikipedia.org/wiki/Yacc
- Yacc Lex parser generator: https://en.wikipedia.org/wiki/Lex_(software)
- Go Assembly: TEXT instruction: https://www.gitbook.com/book/go-assembly/go-assembly/details/text-instruction.md
- Go Assembly: PCDATA: https://www.gitbook.com/book/go-assembly/go-assembly/details/pcdata.md
- Go Assembly:
TEXT
instruction flags: https://go.dev/src/cmd/internal/obj/textflag.go