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

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

このコミットは、Go言語の初期のコンパイラ(6gおよびgc)における複数の重要な修正と改善をまとめたものです。主に、配列の境界チェックのバグ修正、最適化の改善、そしてコンパイラのシンボル処理に関するリファクタリングが含まれています。

コミット

commit 8e3fe10ee381cb0200a683dfe116189aa8b41d9f
Author: Ken Thompson <ken@golang.org>
Date:   Mon Nov 24 14:01:12 2008 -0800

    1. retract general field names
    2. array bounds bug
    3. ... optimization bug
    
    R=r
    OCL=19927
    CL=19927

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

https://github.com/golang/go/commit/8e3fe10ee381cb0200a683dfe116189aa8b41d9f

元コミット内容

  1. 一般的なフィールド名の撤回(retract general field names
  2. 配列の境界チェックのバグ修正(array bounds bug
  3. 最適化のバグ修正(optimization bug

変更の背景

このコミットは、Go言語がまだ活発に開発されていた初期段階、具体的には2008年11月に行われました。Go言語のコンパイラは、当初Plan 9 Cコンパイラをベースにしており、6gはamd64アーキテクチャ向けのGoコンパイラ、gcはGoコンパイラのフロントエンド(Go言語の構文解析と中間表現生成)を指します。

当時のGoコンパイラはまだ成熟しておらず、基本的な機能の実装と安定化が最優先されていました。特に、メモリ安全性と実行時エラーの防止はGo言語の設計思想の核となる部分であり、配列の境界チェックはこれらを保証するための重要な機能です。このコミットは、コンパイラが配列のインデックスアクセス時に正しく境界チェックを行うようにするためのバグ修正と、それに伴う最適化の改善を目的としています。

また、「retract general field names」という記述は、コンパイラ内部でのシンボルやフィールド名の扱いに関する初期の設計判断の変更を示唆しています。これは、コンパイラの内部構造や命名規則の整理の一環であったと考えられます。

前提知識の解説

  • Goコンパイラ (6g, gc):
    • gc (Go Compiler): Go言語の公式コンパイラのフロントエンド部分。Goソースコードを解析し、中間表現を生成します。
    • 6g: gcによって生成された中間表現をamd64アーキテクチャ向けの機械語に変換するバックエンドコンパイラ。Go言語の初期には、ターゲットアーキテクチャごとに8g (ARM), 5g (PowerPC) などが存在しました。
  • 配列の境界チェック (Array Bounds Checking): プログラムが配列にアクセスする際に、指定されたインデックスが配列の有効な範囲内にあるかを確認するプロセスです。範囲外のインデックスにアクセスしようとすると、通常は実行時エラー(パニック)が発生します。Go言語では、このチェックがデフォルトで有効になっており、メモリ破壊やセキュリティ脆弱性を防ぐ上で非常に重要です。
  • go.y: Goコンパイラの構文解析器を生成するためのYacc/Bison形式の文法定義ファイルです。Go言語の構文規則が記述されており、コンパイラがソースコードをどのように解釈するかを定義します。
  • cgen.c: 6gコンパイラの一部で、C言語で書かれたコード生成(code generation)を担当するファイルです。中間表現からターゲットアーキテクチャの機械語命令を生成するロジックが含まれています。
  • reg.c: 6gコンパイラの一部で、レジスタ割り当て(register allocation)やシンボル管理に関連する処理を行うファイルです。

技術的詳細

このコミットは、主に以下の3つの領域にわたる変更を含んでいます。

  1. 配列の境界チェックの強化と修正 (src/cmd/6g/cgen.c):

    • 定数インデックスの境界チェック: agen関数(アドレス生成)において、配列のインデックスが定数である場合の境界チェックが追加されました。isptrdarray(ポインタ配列)やisptrarray(通常の配列)の型に応じて、コンパイル時にインデックスが配列の範囲外である場合にyyerror("out of bounds on array")としてコンパイルエラーを発生させるようになりました。これは、実行時エラーを未然に防ぐための重要な改善です。
    • 動的インデックスの境界チェックの修正: 動的なインデックス(変数など)に対する境界チェックの比較演算がTUINT64(64ビット符号なし整数)からTUINT32(32ビット符号なし整数)に変更されました。これは、当時のGoコンパイラが32ビットのインデックスを想定していたか、または特定のアーキテクチャでの最適化を意図していた可能性があります。throwindexは、実行時に境界違反が発生した場合に呼び出されるランタイム関数です。
    • offsetof(Array, nel)offsetof(Array, array)の使用は、Goの内部的なArray構造体から配列の要素数(nel)と実際のデータ(array)へのオフセットを取得していることを示しています。これにより、コンパイラは配列のメタデータにアクセスして境界チェックを行うことができます。
  2. シンボル名の処理の調整 (src/cmd/6g/reg.c):

    • mkvar関数において、シンボル名が!または.で始まる場合に特定の処理をスキップする条件が追加されました。これは、Goコンパイラ内部で使用される特殊なシンボル(例えば、匿名フィールドや内部的な生成シンボル)が、通常の変数として扱われないようにするための変更と考えられます。これにより、コンパイラの内部的な整合性が保たれ、予期せぬ動作を防ぎます。
  3. 文法定義のクリーンアップ (src/cmd/gc/go.y):

    • sym2ルールからLTYPE, LFUNC, LVARといったトークンが削除されました。これは、Go言語の文法解析において、これらのキーワードがシンボルとして扱われる方法が変更されたことを意味します。おそらく、これらのキーワードはより上位の文法ルールで処理されるようになったか、あるいはコンパイラの内部的なシンボルテーブル管理がリファクタリングされ、go.yのこの部分での明示的な定義が不要になったと考えられます。これにより、文法定義がより簡潔になり、保守性が向上します。

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

src/cmd/6g/cgen.c

--- a/src/cmd/6g/cgen.c
+++ b/src/cmd/6g/cgen.c
@@ -406,17 +406,44 @@ agen(Node *n, Node *res)
 		if(w == 0)
 			fatal("index is zero width");
 
+		// constant index
 		if(whatis(nr) == Wlitint) {
+			v = mpgetfix(nr->val.u.xval);
 			if(isptrdarray(nl->type)) {
+
+				if(!debug['B']) {
+					n1 = n3;
+					n1.op = OINDREG;
+					n1.type = types[tptr];
+					n1.xoffset = offsetof(Array, nel);
+					nodconst(&n2, types[TUINT64], v);
+					gins(optoas(OCMP, types[TUINT32]), &n1, &n2);
+					p1 = gbranch(optoas(OGT, types[TUINT32]), T);
+					gins(ACALL, N, throwindex);
+					patch(p1, pc);
+				}
+
 				n1 = n3;
 				n1.op = OINDREG;
 				n1.type = types[tptr];
 				n1.xoffset = offsetof(Array, array);
 				gmove(&n1, &n3);
+			} else
+			if(!debug['B']) {
+				if(v < 0)
+					yyerror("out of bounds on array");
+				else
+				if(isptrarray(nl->type)) {
+					if(v >= nl->type->type->bound)
+						yyerror("out of bounds on array");
+				} else
+				if(v >= nl->type->bound)
+					yyerror("out of bounds on array");
 			}
-			v = mpgetfix(nr->val.u.xval);
+
 			nodconst(&n2, types[tptr], v*w);
 			gins(optoas(OADD, types[tptr]), &n2, &n3);
+
 			gmove(&n3, res);
 			regfree(&n3);
 			break;
@@ -443,8 +470,8 @@ agen(Node *n, Node *res)
 				if(isptrarray(nl->type))
 					nodconst(&n1, types[TUINT64], nl->type->type->bound);
 			}
-			gins(optoas(OCMP, types[TUINT64]), &n2, &n1);
-			p1 = gbranch(optoas(OLT, types[TUINT64]), T);
+			gins(optoas(OCMP, types[TUINT32]), &n2, &n1);
+			p1 = gbranch(optoas(OLT, types[TUINT32]), T);
 			gins(ACALL, N, throwindex);
 			patch(p1, pc);
 		}

src/cmd/6g/reg.c

--- a/src/cmd/6g/reg.c
+++ b/src/cmd/6g/reg.c
@@ -767,8 +767,8 @@ mkvar(Reg *r, Adr *a)
 	s = a->sym;
 	if(s == S)
 		goto none;
-//	if(s->name[0] == '.')
-//		goto none;
+	if(s->name[0] == '!' || s->name[0] == '.')
+		goto none;
 	et = a->etype;
 	o = a->offset;
 	v = var;

src/cmd/gc/go.y

--- a/src/cmd/gc/go.y
+++ b/src/cmd/gc/go.y
@@ -993,9 +993,6 @@ sym1:
  */
 sym2:
 	sym1
-|\tLTYPE
-|\tLFUNC
-|\tLVAR
 
 /*
  * keywords that can be variables
@@ -2014,7 +2011,7 @@ hidden_importsym:
  * to check whether the rest of the grammar is free of
  * reduce/reduce conflicts, comment this section out by
  * removing the slash on the next line.
- *
+ */
 lpack:
 	LATYPE
 	{

コアとなるコードの解説

  • src/cmd/6g/cgen.c の変更:
    • agen関数は、Goの配列やスライスへのアクセス時に、そのアドレスを計算する役割を担っています。
    • if(whatis(nr) == Wlitint)ブロックは、インデックスがリテラル(定数)である場合の処理です。
      • if(isptrdarray(nl->type))ブロック内では、ポインタ配列に対する境界チェックが追加されています。debug['B']フラグが設定されていない場合(つまり、デバッグモードでない場合)、Array構造体のnelフィールド(要素数)と定数インデックスvを比較し、インデックスが範囲外であればthrowindexを呼び出すコードが生成されます。
      • else if(!debug['B'])ブロックでは、通常の配列に対するコンパイル時境界チェックが実装されています。インデックスvが負の値であったり、配列のbound(サイズ)以上である場合にyyerrorを呼び出し、コンパイルエラーとします。
    • 動的なインデックスに対する境界チェックでは、gins(optoas(OCMP, types[TUINT64]), &n2, &n1);p1 = gbranch(optoas(OLT, types[TUINT64]), T);TUINT32 に変更されています。これは、比較演算の型を64ビット符号なし整数から32ビット符号なし整数に調整したことを示しており、当時のGoコンパイラの内部的な型システムやターゲットアーキテクチャのレジスタサイズに合わせた最適化である可能性があります。
  • src/cmd/6g/reg.c の変更:
    • mkvar関数は、変数をレジスタに割り当てる際などにシンボル情報を処理します。
    • if(s->name[0] == '!' || s->name[0] == '.') の追加により、!または.で始まるシンボル名(Goコンパイラ内部で特殊な意味を持つ可能性のあるシンボル)が、通常の変数として扱われないようにスキップされます。これにより、コンパイラの内部処理の正確性が向上します。
  • src/cmd/gc/go.y の変更:
    • sym2ルールからLTYPE, LFUNC, LVARが削除されたことは、Go言語の構文解析器がこれらのキーワードをシンボルとして直接扱うのではなく、より抽象的なレベルで処理するようになったことを示唆しています。これは、文法定義の簡素化と、コンパイラのフロントエンドにおけるシンボル解決のロジックの改善を目的としていると考えられます。

これらの変更は、Go言語の初期段階におけるコンパイラの堅牢性、安全性(特にメモリ安全性)、および内部構造の洗練に大きく貢献しました。

関連リンク

参考にした情報源リンク

  • Go言語の公式GitHubリポジトリ: https://github.com/golang/go
  • Go言語の初期のコンパイラ設計に関する議論やドキュメント(当時のメーリングリストやデザインドキュメントなど、一般には公開されていない可能性が高いが、Goの歴史を辿る上で重要)
  • Yacc/Bisonのドキュメント(go.yファイルの理解のため)
  • Go言語の配列とスライスの内部表現に関する情報
  • Go言語の境界チェックに関するブログ記事や論文(もしあれば)
  • Ken ThompsonのGo言語への貢献に関する情報