[インデックス 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:
TEXTinstruction flags: https://go.dev/src/cmd/internal/obj/textflag.go