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

[インデックス 126] ファイルの概要

このコミットは、Go言語の初期開発段階、特に2008年6月に行われたもので、主にGoコンパイラのコード生成部分(src/cmd/6g/cgen.c)における浮動小数点数の比較処理の改善と、Go言語の文法定義(src/cmd/gc/go.y)におけるセミコロンの扱いに関する明確化を含んでいます。

コミット

commit 36bfd2a9061465cc50d94298a8c4e8e3d0924803
Author: Ken Thompson <ken@golang.org>
Date:   Sun Jun 8 16:11:14 2008 -0700

    floating point
    
    SVN=121607

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/36bfd2a9061465cc50d94298a8c4e8e3d0924803

元コミット内容

--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -14,7 +14,7 @@ cgen(Node *n, Node *res)
 	Prog *p1, *p2, *p3;
 
 	if(debug['g']) {
-		dump("\ncgen-l", res);
+		dump("\ncgen-res", res);
 		dump("cgen-r", n);
 	}
 	if(n == N || n->type == T)
@@ -441,9 +441,9 @@ void
 bgen(Node *n, int true, Prog *to)
 {
 	long lno;
-	int et, a, b;
+	int et, a;
 	Node *nl, *nr, *r;
-	Node n1, n2, tmp;
+	Node n1, n2;
 	Prog *p1, *p2;
 
 	if(n == N)
@@ -560,48 +560,17 @@ bgen(Node *n, int true, Prog *to)
 		}
 		a = optoas(a, nr->type);
 
-		if(nr->addable) {
-			regalloc(&n1, nl->type, N);
-			cgen(nl, &n1);
-			b = optoas(OCMP, nr->type);
-
-			switch(b) {
-			case ACMPQ:
-				if(nr->op == OLITERAL)
-				if(nr->val.vval >= (1LL<<32))
-					goto dolit;
-
-			case AUCOMISS:
-				if(nr->op == OLITERAL)
-				if(nr->op == ONAME)
-					goto dolit;
-			}
-
-			gins(b, &n1, nr);
-			patch(gbranch(a, nr->type), to);
-			regfree(&n1);
-			break;
-
-		dolit:
-			regalloc(&n2, nr->type, N);
-			cgen(nr, &n2);
-			gins(b, &n1, &n2);
-			patch(gbranch(a, nr->type), to);
-			regfree(&n2);
-			regfree(&n1);
-			break;
-		}
-
-		tempname(&tmp, nr->type);
-		cgen(nr, &tmp);
-
 		regalloc(&n1, nl->type, N);
 		cgen(nl, &n1);
 
-		gins(optoas(OCMP, nr->type), &n1, &tmp);
+		regalloc(&n2, nr->type, N);
+		cgen(nr, &n2);
+
+		gins(optoas(OCMP, nr->type), &n1, &n2);
 		patch(gbranch(a, nr->type), to);
+\
 		regfree(&n1);
+		regfree(&n2);
 		break;
 	}
 	goto ret;
diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y
index cfd4cc07fc..3d915a67aa 100644
--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -1058,16 +1058,32 @@ arg_type_list_r:
 		$$ = nod(OLIST, $1, $3);
 	}
 
+/*
+ * need semi in front NO
+ * need semi in back  NO
+ */
 Astmt:
 	complex_stmt
+|\tcompound_stmt
 
+/*
+ * need semi in front NO
+ * need semi in back  YES
+ */
 Bstmt:
 	semi_stmt
 |\tcommon_dcl
 
+/*
+ * need semi in front YES
+ * need semi in back  YES
+ */
 Cstmt:
 	simple_stmt
 
+/*
+ * statement list that need semi in back  NO
+ */
 Astmt_list_r:
 	Astmt
 |\tAstmt_list_r Astmt
@@ -1081,6 +1097,9 @@ Astmt_list_r:
 		$$ = N;
 	}
 
+/*
+ * statement list that need semi in back  YES
+ */
 Bstmt_list_r:
 	Bstmt
 |\tCstmt

変更の背景

このコミットは、Go言語の初期段階におけるコンパイラの成熟化の一環として行われました。主な背景は以下の2点です。

  1. 浮動小数点数処理の正確性と効率性: コンパイラが浮動小数点数の比較をどのように機械語に変換するかは、プログラムの正確性とパフォーマンスに直結します。特に、NaN (Not a Number) や無限大 (Infinity) のような特殊な浮動小数点値の扱いは、標準的な整数比較とは異なるため、特別な注意が必要です。このコミットは、6gコンパイラ(AMD64アーキテクチャ向けのGoコンパイラ)における浮動小数点比較のコード生成ロジックを簡素化し、潜在的な問題を修正することを目的としています。
  2. Go言語の文法と自動セミコロン挿入の明確化: Go言語は、C言語のような明示的なセミコロンを必要としない「自動セミコロン挿入 (Automatic Semicolon Insertion: ASI)」という特徴を持っています。しかし、これは特定の文脈で開発者を混乱させる可能性があり、コンパイラがどのように文の終わりを判断するかを明確にする必要があります。go.yファイルへの変更は、このASIのルールを文法定義レベルでより明確にし、パーサーの挙動を安定させるためのものです。

前提知識の解説

Goコンパイラの初期アーキテクチャ

  • gc (Go Compiler): Go言語の公式コンパイラ群の総称。初期のGoコンパイラは、各アーキテクチャ(例: 6g for AMD64, 8g for x86, 5g for ARM)ごとに独立したバイナリとして提供されていました。
  • 6g: AMD64 (x86-64) アーキテクチャ向けのGoコンパイラ。このコミットのsrc/cmd/6g/cgen.cは、6gのコード生成バックエンドの一部です。
  • cgen.c: "Code Generation" の略で、Goの抽象構文木 (AST) をターゲットアーキテクチャの機械語命令に変換する役割を担うファイルです。コンパイラのバックエンドに位置します。
  • go.y: Yacc (Yet Another Compiler Compiler) または Bison の文法定義ファイル。Go言語の構文規則を定義しており、これに基づいてGoソースコードが解析され、抽象構文木が構築されます。コンパイラのフロントエンド(パーサー)の一部です。

浮動小数点数演算と比較

  • IEEE 754: 現代のほとんどのコンピュータシステムで採用されている浮動小数点数の標準規格です。この規格は、単精度 (float32) と倍精度 (float64) の表現方法、算術演算、そしてNaN (Not a Number) や無限大 (Infinity) といった特殊な値の扱いを定義しています。
  • 浮動小数点数の比較の複雑さ:
    • NaN: NaN == NaNfalse と評価されます。これは、NaNが「未定義の結果」を表すため、それ自身と比較しても等しいとは言えないからです。
    • 順序付け: 浮動小数点数には、通常の数値のような全順序関係がありません。特にNaNは、いかなる数値とも順序関係を持ちません(NaN < XNaN > Xfalse)。
    • 精度: 浮動小数点数は有限のビット数で実数を近似するため、丸め誤差が生じることがあります。これにより、数学的には等しいはずの2つの浮動小数点数が、コンピュータ上ではわずかに異なる値として表現され、比較結果が期待と異なることがあります。
  • コンパイラにおける浮動小数点比較: コンパイラは、これらのIEEE 754の特性を考慮して、適切な機械語命令(例: UCOMISS (Unordered Compare Scalar Single-precision Floating-Point) や COMISS (Compare Scalar Single-precision Floating-Point) など)を生成する必要があります。特に、NaNの存在を考慮した「順序なし (unordered)」比較命令が重要になります。

コンパイラのフェーズ

  • 字句解析 (Lexical Analysis): ソースコードをトークン(単語)のストリームに変換します。
  • 構文解析 (Syntax Analysis): トークンのストリームを文法規則に従って解析し、抽象構文木 (AST) を構築します。go.yはこのフェーズで使われます。
  • 意味解析 (Semantic Analysis): ASTに対して型チェックや名前解決などの意味的な検証を行います。
  • 中間コード生成 (Intermediate Code Generation): ASTを、特定の機械に依存しない中間表現に変換します。
  • コード最適化 (Code Optimization): 中間コードを最適化し、より効率的なコードを生成します。
  • コード生成 (Code Generation): 最適化された中間コードをターゲットアーキテクチャの機械語命令に変換します。cgen.cはこのフェーズで使われます。

Yacc/Bison

  • Yacc (Yet Another Compiler Compiler): 文脈自由文法からLALR(1)パーサーを生成するツールです。.yファイルは、文法規則と、各規則がマッチしたときに実行されるアクション(通常はC言語のコード)を記述します。
  • 文法規則: A: B C; のように記述され、「AはBの後にCが続く」という構造を示します。
  • アクション: $$ = nod(OLIST, $1, $3); のように、規則の右辺の要素($1, $3など)を使って、左辺の要素($$)の値を構築します。

Go言語の自動セミコロン挿入 (ASI)

Go言語の仕様では、特定の場所(改行の直前など)でセミコロンが自動的に挿入されるルールがあります。これにより、多くのGoプログラムでは明示的なセミコロンを記述する必要がありません。しかし、このルールは厳密であり、改行の位置によっては意図しないセミコロンが挿入され、構文エラーや予期せぬ挙動を引き起こす可能性があります。go.yの変更は、このASIの挙動を文法レベルでより正確に定義し、パーサーが正しく文の区切りを認識できるようにするためのものです。

技術的詳細

src/cmd/6g/cgen.c の変更

このファイルは、GoコンパイラのAMD64バックエンドにおけるコード生成ロジックを扱っています。変更の焦点は、浮動小数点数の比較処理の簡素化と最適化です。

  1. デバッグ出力の変更:
    • dump("\ncgen-l", res); から dump("\ncgen-res", res); へと変更されています。これはデバッグメッセージのラベルの微調整であり、機能的な変更ではありません。
  2. bgen 関数の変更:
    • bgen関数は、ブール式(条件式)のコード生成を担当しています。
    • 変数 b の削除: 以前は int et, a, b; と宣言されていた bint et, a; となり、削除されています。これは、後述の大きなコードブロックの削除に伴うものです。
    • 大きな条件ブロックの削除:
      • if(nr->addable) { ... } という大きなブロックが削除されました。このブロックは、比較の右辺 (nr) が「addable」(直接アドレス指定可能、またはレジスタに格納可能)な場合に、特定の最適化や特殊なコード生成パスを試みていました。
      • 特に、ACMPQ(64ビット整数比較)や AUCOMISS(浮動小数点数の順序なし比較)といった命令に関連するロジックが含まれていました。
      • dolit というラベルへの goto も含まれており、リテラル値との比較に対する特殊なハンドリングを行っていました。
      • このブロックの削除は、浮動小数点比較のコード生成ロジックが簡素化され、より一般的なパスで処理されるようになったことを示唆しています。以前の特殊な最適化パスが、複雑さに見合うメリットがなかったか、あるいはバグの原因となっていた可能性があります。
    • 一時変数 tmp の削除と n2 の直接利用:
      • 以前は tempname(&tmp, nr->type); cgen(nr, &tmp); を使って右辺の値を一時変数 tmp に生成していました。
      • 変更後は regalloc(&n2, nr->type, N); cgen(nr, &n2); となり、直接 n2 というノード(レジスタ割り当て済み)に右辺の値を生成しています。
      • これにより、比較命令 gins(optoas(OCMP, nr->type), &n1, &tmp);gins(optoas(OCMP, nr->type), &n1, &n2); に変更され、一時変数 tmp を介さずに直接 n1(左辺)と n2(右辺)のレジスタ間で比較が行われるようになりました。
      • この変更は、コード生成のパスを簡素化し、レジスタ割り当てと命令生成の効率を向上させる可能性があります。特に浮動小数点数の比較において、一時的なメモリへの退避を減らすことで、パフォーマンスが向上する可能性があります。また、AUCOMISSのような浮動小数点比較命令は、オペランドがレジスタにあることを前提とすることが多いため、この変更はより直接的な命令生成を可能にします。

src/cmd/gc/go.y の変更

このファイルはGo言語の文法を定義しており、パーサーの挙動に直接影響します。変更は、Goの自動セミコロン挿入 (ASI) ルールをより明確にするためのものです。

  1. Astmt 規則への compound_stmt の追加:
    • Astmt は「セミコロンが前にも後ろにも不要な文」を定義しています。
    • Astmt: complex_stmt だったものが Astmt: complex_stmt | compound_stmt となりました。
    • compound_stmt は通常、{ ... } のようなブロック文を指します。この変更により、ブロック文の後にセミコロンが不要であることが文法レベルで明示されました。これはGoの一般的なコーディングスタイルと一致します(例: if x { ... } の後にセミコロンは不要)。
  2. セミコロン要件に関するコメントの追加:
    • Astmt, Bstmt, Cstmt の各規則の前に、その文が「セミコロンが前に必要か (need semi in front)」と「セミコロンが後ろに必要か (need semi in back)」を明確に記述したコメントが追加されました。
      • Astmt: need semi in front NO, need semi in back NO
      • Bstmt: need semi in front NO, need semi in back YES (例: 変数宣言、return文など、通常セミコロンで終わる文)
      • Cstmt: need semi in front YES, need semi in back YES (例: simple_stmt、代入文や関数呼び出しなど、通常セミコロンで終わる文で、かつ前の文との区切りが必要な場合)
    • これらのコメントは、GoのASIの複雑なルールをパーサー開発者や言語設計者にとってより理解しやすくするためのものです。
  3. 文のリストに関するコメントの追加:
    • Astmt_list_rBstmt_list_r の規則の前に、それぞれ「セミコロンが後ろに不要な文のリスト」と「セミコロンが後ろに必要な文のリスト」である旨のコメントが追加されました。
    • これは、文のリスト全体がどのようにセミコロンのルールに影響されるかを明確にするものです。

これらのgo.yへの変更は、Go言語の文法がまだ流動的だった初期段階において、パーサーの堅牢性を高め、ASIの挙動をより予測可能にするための重要なステップでした。

コアとなるコードの変更箇所

src/cmd/6g/cgen.c

@@ -441,9 +441,9 @@ void
 bgen(Node *n, int true, Prog *to)
 {
 	long lno;
-	int et, a, b;
+	int et, a;
 	Node *nl, *nr, *r;
-	Node n1, n2, tmp;
+	Node n1, n2;
 	Prog *p1, *p2;
 
 	if(n == N)
@@ -560,48 +560,17 @@ bgen(Node *n, int true, Prog *to)
 		}
 		a = optoas(a, nr->type);
 
-		if(nr->addable) {
-			regalloc(&n1, nl->type, N);
-			cgen(nl, &n1);
-			b = optoas(OCMP, nr->type);
-
-			switch(b) {
-			case ACMPQ:
-				if(nr->op == OLITERAL)
-				if(nr->val.vval >= (1LL<<32))
-					goto dolit;
-
-			case AUCOMISS:
-				if(nr->op == OLITERAL)
-				if(nr->op == ONAME)
-					goto dolit;
-			}
-
-			gins(b, &n1, nr);
-			patch(gbranch(a, nr->type), to);
-			regfree(&n1);
-			break;
-
-		dolit:
-			regalloc(&n2, nr->type, N);
-			cgen(nr, &n2);
-			gins(b, &n1, &n2);
-			patch(gbranch(a, nr->type), to);
-			regfree(&n2);
-			regfree(&n1);
-			break;
-		}
-
-		tempname(&tmp, nr->type);
-		cgen(nr, &tmp);
-
 		regalloc(&n1, nl->type, N);
 		cgen(nl, &n1);
 
-		gins(optoas(OCMP, nr->type), &n1, &tmp);
+		regalloc(&n2, nr->type, N);
+		cgen(nr, &n2);
+
+		gins(optoas(OCMP, nr->type), &n1, &n2);
 		patch(gbranch(a, nr->type), to);
+\
 		regfree(&n1);
+		regfree(&n2);
 		break;
 	}
 	goto ret;

src/cmd/gc/go.y

@@ -1058,16 +1058,32 @@ arg_type_list_r:
 		$$ = nod(OLIST, $1, $3);
 	}
 
+/*
+ * need semi in front NO
+ * need semi in back  NO
+ */
 Astmt:
 	complex_stmt
+|\tcompound_stmt
 
+/*
+ * need semi in front NO
+ * need semi in back  YES
+ */
 Bstmt:
 	semi_stmt
 |\tcommon_dcl
 
+/*
+ * need semi in front YES
+ * need semi in back  YES
+ */
 Cstmt:
 	simple_stmt
 
+/*
+ * statement list that need semi in back  NO
+ */
 Astmt_list_r:
 	Astmt
 |\tAstmt_list_r Astmt
@@ -1081,6 +1097,9 @@ Astmt_list_r:
 		$$ = N;
 	}
 
+/*
+ * statement list that need semi in back  YES
+ */
 Bstmt_list_r:
 	Bstmt
 |\tCstmt

コアとなるコードの解説

src/cmd/6g/cgen.c の変更点

  • 浮動小数点比較の簡素化:
    • 以前のコードは、比較の右辺 (nr) が特定の条件(addable、リテラル、名前)を満たす場合に、ACMPQAUCOMISS といった命令を使って特殊な最適化パスを試みていました。この複雑なロジック(if(nr->addable)ブロック全体)が削除されました。
    • これにより、浮動小数点数の比較は、より一般的なコード生成パスで処理されるようになりました。具体的には、左辺 (nl) の値を n1 に、右辺 (nr) の値を n2 にそれぞれコード生成し、その後 gins(optoas(OCMP, nr->type), &n1, &n2); という命令で直接 n1n2 の間で比較を行うようになりました。
    • この変更は、浮動小数点比較のコード生成を統一し、バグのリスクを減らし、コンパイラの保守性を向上させることを目的としています。特に、IEEE 754のNaNの挙動を考慮すると、特殊な最適化パスが予期せぬ結果を招く可能性があったため、より堅牢な汎用パスに一本化されたと考えられます。

src/cmd/gc/go.y の変更点

  • 文法規則の明確化と compound_stmt の追加:
    • Astmt 規則に | compound_stmt が追加されました。これは、Go言語のブロック文({ ... })が、その後にセミコロンを必要としない文(Astmt)として扱われることを文法レベルで明示しています。これにより、if文やfor文の後に続くブロックが、Goの自動セミコロン挿入のルールに沿って正しく解析されるようになります。
    • 各文の種類 (Astmt, Bstmt, Cstmt) および文のリスト (Astmt_list_r, Bstmt_list_r) に対して、セミコロンが前後に必要かどうかの詳細なコメントが追加されました。これは、Goの自動セミコロン挿入のルールをパーサーの文法定義に直接反映させ、その挙動を明確にするためのものです。これにより、コンパイラのパーサーがGoのソースコードをより正確に解釈し、開発者が意図しないセミコロン挿入によるエラーを回避できるようになります。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • IEEE 754 浮動小数点数標準に関する技術記事
  • コンパイラ設計に関する一般的な書籍やオンラインリソース
  • Go言語の初期のコミット履歴と関連する議論 (GitHubリポジトリの履歴を遡って確認)
  • Yacc/Bison の使用方法に関するチュートリアル