[インデックス 10422] ファイルの概要
このコミットは、Goコンパイラ(gc
)がPlan 9のyacc
ツールを使用してビルドされる際の互換性を確保するための変更を導入しています。具体的には、yacc
の出力形式の違いに対応するため、パーサーのエラー処理スクリプトと、パーサーのスタック深度を定義するヘッダーファイルが修正されています。
コミット
- コミットハッシュ:
920df48fb1e1b30f5ea86f1a49b599098fa81793
- 作者: Anthony Martin ality@pbrane.org
- コミット日時: 2011年11月16日 16:58:02 -0500
- 変更ファイル数: 2
src/cmd/gc/bisonerrors
src/cmd/gc/go.h
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/920df48fb1e1b30f5ea86f1a49b599098fa81793
元コミット内容
gc: support for building with Plan 9 yacc
I've modified Plan 9's yacc to work with
the grammar in go.y. These are the only
changes necessary on the Go side.
R=rsc
CC=golang-dev
https://golang.org/cl/5375104
変更の背景
このコミットの主な目的は、Goコンパイラ(gc
)のビルドプロセスにおいて、GNU BisonではなくPlan 9オペレーティングシステムに由来するyacc
ツールを使用できるようにすることです。
Goコンパイラは、その構文解析部分にyacc
(またはbison
)によって生成されたパーサーを使用しています。yacc
やbison
は、文法定義ファイル(Goの場合、go.y
)からC言語のソースコードを生成するツールです。しかし、異なるyacc
の実装(例えばGNU BisonとPlan 9 yacc
)では、生成されるパーサーの動作や、特にデバッグ情報やエラーレポートの出力形式に微妙な違いがあることがあります。
このコミット以前は、Goコンパイラのビルドシステムは主にGNU Bisonの出力形式を前提としていたと考えられます。Plan 9 yacc
を使用すると、bisonerrors
スクリプトが期待する出力形式と異なるため、ビルドプロセスやエラー解析に問題が生じる可能性がありました。
コミットメッセージにある「I've modified Plan 9's yacc to work with the grammar in go.y. These are the only changes necessary on the Go side.」という記述から、作者がPlan 9 yacc
自体にもGoの文法に対応するための変更を加えた上で、Goリポジトリ側では最小限の変更で済むようにしたことが伺えます。このコミットは、その「Go側で必要な変更」を具体的に適用するものです。
前提知識の解説
Yacc/Bison
Yacc
(Yet Another Compiler Compiler)は、コンパイラの構文解析器(パーサー)を自動生成するためのツールです。文法規則を記述したファイル(通常.y
拡張子)を読み込み、その文法に従って入力ストリームを解析するC言語のコードを生成します。
Bison
は、GNUプロジェクトによるYacc
の互換実装であり、多くのUnix系システムで標準的に使用されています。Yacc
とBison
は機能的に非常に似ていますが、細部の動作や生成されるコード、デバッグ出力の形式に違いがあります。
コンパイラにおけるパーサーの役割は、ソースコードのトークン列(字句解析器によって生成される)が、言語の文法規則に適合しているかを検証し、抽象構文木(AST)などの内部表現を構築することです。
Plan 9
Plan 9 from Bell Labsは、ベル研究所で開発された分散オペレーティングシステムです。Unixの設計思想をさらに推し進めたもので、すべてのリソースをファイルとして扱うという特徴があります。Plan 9には、独自のツールチェインが含まれており、その中には独自のyacc
実装も存在します。このPlan 9 yacc
は、GNU Bisonとは異なる挙動や出力形式を持つことがあります。
Goコンパイラ (gc
)
gc
は、Go言語の公式コンパイラです。Goのソースコードを機械語に変換する役割を担っています。gc
の内部では、Go言語の文法を解析するために、yacc
(またはbison
)によって生成されたパーサーが利用されています。
bisonerrors
スクリプト
src/cmd/gc/bisonerrors
は、Goコンパイラのソースツリー内に存在するスクリプトです。その名前から、bison
(またはyacc
)が生成するエラーやデバッグ情報を処理するために使用されると考えられます。具体的には、パーサーの「状態」(state)に関する情報(どのトークンでシフトするか、どのルールで還元するかなど)を解析し、おそらくはコンパイラのデバッグや最適化、あるいは特定のビルドプロセスで使用されるデータを生成する目的で利用されます。このスクリプトは、awk
のようなテキスト処理言語で書かれている可能性が高いです。
YYMAXDEPTH
YYMAXDEPTH
は、yacc
やbison
によって生成されるパーサーのスタックの最大深度を定義するマクロです。パーサーは、入力されたトークンを処理する際に内部スタックを使用します。複雑な文法や深くネストされた構造を持つコードを解析する場合、このスタックが深く使用されることがあります。YYMAXDEPTH
が小さすぎると、スタックオーバーフローが発生し、パーサーが正常に動作しなくなる可能性があります。この値は、通常、yacc
やbison
の生成するコード内で参照されます。
技術的詳細
このコミットは、主にsrc/cmd/gc/bisonerrors
スクリプトとsrc/cmd/gc/go.h
ヘッダーファイルの2箇所に変更を加えています。
src/cmd/gc/bisonerrors
の変更
このスクリプトは、yacc
/bison
の出力(特に状態遷移に関する情報)を解析するために使用されるawk
スクリプトであると推測されます。変更の核心は、GNU BisonとPlan 9 yacc
の出力形式の違いを吸収することにあります。
-
シフト(shift)アクションの解析:
- 変更前は、
shift, and go to state
というパターンを想定し、状態番号を$7
(7番目のフィールド)から抽出していました。 - 変更後は、まず
shift
という単語でマッチさせ、その行がand go to
を含むかどうかでGNU Bisonの出力かPlan 9yacc
の出力かを判別します。 - もし
and go to
が含まれていればGNU Bisonの形式として$7
を使用し、そうでなければPlan 9yacc
の形式として$3
(3番目のフィールド)から状態番号を抽出するように変更されました。これは、Plan 9yacc
の出力がより簡潔で、状態番号が早いフィールドに現れることを示唆しています。
- 変更前は、
-
ゴートゥー(go to/goto)アクションの解析:
- 同様に、変更前は
go to state
というパターンを想定し、状態番号を$5
から抽出していました。 - 変更後は、
go to
またはgoto
という単語でマッチさせ、go to
を含むかどうかでGNU Bisonの出力かPlan 9yacc
の出力かを判別します。 - GNU Bisonの場合は
$5
、Plan 9yacc
の場合は$3
から状態番号を抽出します。
- 同様に、変更前は
-
還元(reduce)アクションの解析:
- 変更前は、
reduce using rule
というパターンを想定し、ルール番号を$5
から抽出していました。 - 変更後は、
reduce
という単語でマッチさせ、reduce using rule
を含むかどうかでGNU Bisonの出力かPlan 9yacc
の出力かを判別します。 - GNU Bisonの場合は
$5
、Plan 9yacc
の場合は$3
からルール番号を抽出します。
- 変更前は、
-
Plan 9
yacc
のサマリー情報のスキップ:nonterminals$/,/^maximum spread/ { next }
という行が追加されました。これは、Plan 9yacc
が生成する可能性のある「nonterminals」から「maximum spread」までのサマリー情報をスキップするためのものです。これにより、スクリプトが不要な行を誤って解析することを防ぎます。
-
還元トークン(reducetoken)の処理:
reducetoken[state,j] == tok || reducetoken[state,j] == "$default"
という条件が、t == tok || t == "$default" || t == "."
に拡張されました。これは、Plan 9yacc
がデフォルトの還元アクションを示すために"$default"
の代わりに"."
を使用する可能性があることを示唆しています。
これらの変更により、bisonerrors
スクリプトは、GNU BisonとPlan 9 yacc
の両方の出力形式に対応できるようになり、Goコンパイラのビルドシステムがより柔軟になりました。
src/cmd/gc/go.h
の変更
このファイルは、Goコンパイラの内部で使用されるC言語のヘッダーファイルです。
YYMAXDEPTH
の定義変更:- 変更前は、
enum
ブロック内でYYMAXDEPTH = 500,
として定義されていました。 - 変更後は、
#define YYMAXDEPTH 500
というプリプロセッサマクロとして定義され、enum
ブロックからは削除されました。 - 新しい定義には、「The parser's maximum stack size. We have to use a #define macro here since yacc or bison will check for its definition and use a potentially smaller value if it is undefined.」というコメントが追加されています。
- 変更前は、
この変更の理由は、yacc
やbison
がパーサーコードを生成する際に、YYMAXDEPTH
というマクロの存在をチェックし、もし定義されていなければデフォルトの(しばしばより小さい)値を使用する可能性があるためです。enum
定数として定義されている場合、プリプロセッサはそれをマクロとして認識しないため、yacc
/bison
が期待する動作とならない可能性があります。#define
を使用することで、コンパイル前のプリプロセス段階でYYMAXDEPTH
が確実に定義され、yacc
/bison
が生成するコードが意図したスタック深度を使用するようになります。これにより、パーサーのスタックオーバーフローのリスクを軽減し、より堅牢なパーサーを生成できます。
コアとなるコードの変更箇所
--- a/src/cmd/gc/bisonerrors
+++ b/src/cmd/gc/bisonerrors
@@ -46,24 +46,36 @@ bison && /^state 0/ { grammar = 0; states = 1 }\n states && /^state / { state = $2 }\n states { statetext[state] = statetext[state] $0 "\\n" }\n
-states && / shift, and go to state/ {
+states && / shift/ {
n = nshift[state]++
- shift[state,n] = $7
+ if($0 ~ /and go to/)
+ shift[state,n] = $7 # GNU Bison
+ else
+ shift[state,n] = $3 # Plan 9 Yacc
shifttoken[state,n] = $1
next
}
-states && / go to state/ {
+states && / (go to|goto)/ {
n = nshift[state]++
- shift[state,n] = $5
+ if($0 ~ /go to/)
+ shift[state,n] = $5 # GNU Bison
+ else
+ shift[state,n] = $3 # Plan 9 Yacc
shifttoken[state,n] = $1
next
}
-states && / reduce using rule/ {
+states && / reduce/ {
n = nreduce[state]++
- reduce[state,n] = $5
+ if($0 ~ /reduce using rule/)
+ reduce[state,n] = $5 # GNU Bison
+ else
+ reduce[state,n] = $3 # Plan 9 yacc
reducetoken[state,n] = $1
next
-}
+}
+
+# Skip over the summary information printed by Plan 9 yacc.
+/nonterminals$/,/^maximum spread/ { next }
# First // comment marks the beginning of the pattern file.
/^\\/\\// { bison = 0; grammar = 0; state = 0 }\n@@ -96,7 +108,8 @@ $1 == "%" {
if(found)
continue
for(j=0; j<nreduce[state]; j++) {
- if(reducetoken[state,j] == tok || reducetoken[state,j] == "$default") {
+ t = reducetoken[state,j]
+ if(t == tok || t == "$default" || t == ".") {
stack[nstack++] = state
rule = reduce[state,j]
nstack -= rulesize[rule]
diff --git a/src/cmd/gc/go.h b/src/cmd/gc/go.h
index a473c2fb50..217456c9a4 100644
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -16,6 +16,12 @@
#undef BUFSIZ
+// The parser's maximum stack size.
+// We have to use a #define macro here since yacc
+// or bison will check for its definition and use
+// a potentially smaller value if it is undefined.
+#define YYMAXDEPTH 500
+
enum
{
NHUNK = 50000,
@@ -23,7 +29,6 @@ enum
NSYMB = 500,\n NHASH = 1024,\n STRINGSZ = 200,\n-\tYYMAXDEPTH = 500,\n MAXALIGN = 7,\n UINF = 100,\n HISTSZ = 10,\n```
## コアとなるコードの解説
### `src/cmd/gc/bisonerrors`
この`awk`スクリプトの変更は、主に`yacc`/`bison`の出力行をパターンマッチングし、必要な情報を抽出するロジックを強化しています。
* **`states && / shift/ { ... }` ブロック**:
* 以前は`shift, and go to state`という厳密なパターンにのみ反応していましたが、`shift`という単語を含む行であれば処理を開始するように緩和されました。
* 内部で`if($0 ~ /and go to/)`という条件分岐が追加され、現在の行(`$0`)が`"and go to"`という文字列を含むかどうかで、GNU Bisonの出力形式(`shift[state,n] = $7`)か、Plan 9 `yacc`の出力形式(`shift[state,n] = $3`)かを判断し、適切なフィールドから状態番号を抽出します。これにより、異なる`yacc`実装の出力に対応できるようになりました。
* **`states && / (go to|goto)/ { ... }` ブロック**:
* 同様に、以前は`go to state`というパターンでしたが、`go to`または`goto`のいずれかを含む行に反応するように変更されました。
* `if($0 ~ /go to/)`でGNU Bisonの形式(`shift[state,n] = $5`)か、Plan 9 `yacc`の形式(`shift[state,n] = $3`)かを判断します。
* **`states && / reduce/ { ... }` ブロック**:
* 以前は`reduce using rule`という厳密なパターンでしたが、`reduce`という単語を含む行であれば処理を開始するように緩和されました。
* `if($0 ~ /reduce using rule/)`でGNU Bisonの形式(`reduce[state,n] = $5`)か、Plan 9 `yacc`の形式(`reduce[state,n] = $3`)かを判断します。
* **`# Skip over the summary information printed by Plan 9 yacc.`**:
* 新しく追加されたコメントと行`nonterminals$/,/^maximum spread/ { next }`は、Plan 9 `yacc`が生成する可能性のある特定のサマリーブロック(`nonterminals`で始まり、`maximum spread`で終わる範囲)を完全にスキップするためのものです。`next`キーワードは、現在の行の残りの処理をスキップし、次の入力行の処理に移ることを意味します。これにより、スクリプトが不要な情報を誤って解析することを防ぎます。
* **`if(t == tok || t == "$default" || t == ".") { ... }`**:
* `reducetoken`の比較条件に`|| t == "."`が追加されました。これは、Plan 9 `yacc`がデフォルトの還元アクションを示すために、GNU Bisonの`"$default"`の代わりに`"."`という記号を使用する可能性があるためです。これにより、スクリプトがPlan 9 `yacc`の出力におけるデフォルト還元を正しく認識できるようになります。
### `src/cmd/gc/go.h`
この変更は、`YYMAXDEPTH`の定義方法を修正するものです。
* **`- YYMAXDEPTH = 500,`**:
* `enum`ブロック内での`YYMAXDEPTH`の定義が削除されました。`enum`定数はコンパイル時に解決される整数定数であり、プリプロセッサマクロとは異なります。
* **`#define YYMAXDEPTH 500`**:
* `YYMAXDEPTH`がプリプロセッサマクロとして再定義されました。
* 追加されたコメントが示すように、`yacc`や`bison`のようなパーサー生成ツールは、生成するコード内で`YYMAXDEPTH`というマクロの存在をチェックし、その値を使用してパーサースタックの最大深度を設定します。もしこのマクロが定義されていない場合、ツールはデフォルトの(しばしばより小さい)値を使用する可能性があります。
* `#define`を使用することで、コンパイルのプリプロセス段階で`YYMAXDEPTH`が確実に定義され、`yacc`/`bison`が生成するパーサーが意図した500というスタック深度制限を持つことが保証されます。これにより、Goコンパイラのパーサーが複雑なGoコードを解析する際に、スタックオーバーフローを起こすリスクが低減されます。
これらの変更は、Goコンパイラが異なる`yacc`実装(特にPlan 9 `yacc`)との互換性を高め、ビルドシステムの柔軟性を向上させるために不可欠なものです。
## 関連リンク
* Go Gerrit Change-Id: [https://golang.org/cl/5375104](https://golang.org/cl/5375104)
## 参考にした情報源リンク
* Yacc (Wikipedia): [https://ja.wikipedia.org/wiki/Yacc](https://ja.wikipedia.org/wiki/Yacc)
* GNU Bison (Wikipedia): [https://ja.wikipedia.org/wiki/GNU_Bison](https://ja.wikipedia.org/wiki/GNU_Bison)
* Plan 9 from Bell Labs (Wikipedia): [https://ja.wikipedia.org/wiki/Plan_9_from_Bell_Labs](https://ja.wikipedia.org/wiki/Plan_9_from_Bell_Labs)
* Go Programming Language (Official Website): [https://go.dev/](https://go.dev/)
* C Preprocessor (Wikipedia): [https://ja.wikipedia.org/wiki/C%E3%83%97%E3%83%AA%E3%83%97%E3%83%AD%E3%82%BB%E3%83%83%E3%82%B5](https://ja.wikipedia.org/wiki/C%E3%83%97%E3%83%AA%E3%83%97%E3%83%AD%E3%82%BB%E3%83%83%E3%82%B5)
* Awk (Wikipedia): [https://ja.wikipedia.org/wiki/Awk](https://ja.wikipedia.org/wiki/Awk)