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

[インデックス 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)によって生成されたパーサーを使用しています。yaccbisonは、文法定義ファイル(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系システムで標準的に使用されています。YaccBisonは機能的に非常に似ていますが、細部の動作や生成されるコード、デバッグ出力の形式に違いがあります。

コンパイラにおけるパーサーの役割は、ソースコードのトークン列(字句解析器によって生成される)が、言語の文法規則に適合しているかを検証し、抽象構文木(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は、yaccbisonによって生成されるパーサーのスタックの最大深度を定義するマクロです。パーサーは、入力されたトークンを処理する際に内部スタックを使用します。複雑な文法や深くネストされた構造を持つコードを解析する場合、このスタックが深く使用されることがあります。YYMAXDEPTHが小さすぎると、スタックオーバーフローが発生し、パーサーが正常に動作しなくなる可能性があります。この値は、通常、yaccbisonの生成するコード内で参照されます。

技術的詳細

このコミットは、主に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 9 yaccの出力かを判別します。
    • もしand go toが含まれていればGNU Bisonの形式として$7を使用し、そうでなければPlan 9 yaccの形式として$3(3番目のフィールド)から状態番号を抽出するように変更されました。これは、Plan 9 yaccの出力がより簡潔で、状態番号が早いフィールドに現れることを示唆しています。
  • ゴートゥー(go to/goto)アクションの解析:

    • 同様に、変更前はgo to stateというパターンを想定し、状態番号を$5から抽出していました。
    • 変更後は、go toまたはgotoという単語でマッチさせ、go toを含むかどうかでGNU Bisonの出力かPlan 9 yaccの出力かを判別します。
    • GNU Bisonの場合は$5、Plan 9 yaccの場合は$3から状態番号を抽出します。
  • 還元(reduce)アクションの解析:

    • 変更前は、reduce using ruleというパターンを想定し、ルール番号を$5から抽出していました。
    • 変更後は、reduceという単語でマッチさせ、reduce using ruleを含むかどうかでGNU Bisonの出力かPlan 9 yaccの出力かを判別します。
    • GNU Bisonの場合は$5、Plan 9 yaccの場合は$3からルール番号を抽出します。
  • Plan 9 yaccのサマリー情報のスキップ:

    • nonterminals$/,/^maximum spread/ { next }という行が追加されました。これは、Plan 9 yaccが生成する可能性のある「nonterminals」から「maximum spread」までのサマリー情報をスキップするためのものです。これにより、スクリプトが不要な行を誤って解析することを防ぎます。
  • 還元トークン(reducetoken)の処理:

    • reducetoken[state,j] == tok || reducetoken[state,j] == "$default"という条件が、t == tok || t == "$default" || t == "."に拡張されました。これは、Plan 9 yaccがデフォルトの還元アクションを示すために"$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.」というコメントが追加されています。

この変更の理由は、yaccbisonがパーサーコードを生成する際に、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)