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

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

このコミットは、Goコンパイラ(特に6g、当時の64ビットアーキテクチャ向けコンパイラ)における定数の振る舞いを、Ken Thompson氏の提案に基づいて大幅に改修したものです。定数に関する様々なバグ修正とテストケースの追加が含まれており、Go言語の定数システムにおける「型なし定数(untyped constants)」の概念の基礎を築く重要な変更となっています。

コミット

commit 8f194bf5ff15e3f62ce02669bf48d54a6342260e
Author: Russ Cox <rsc@golang.org>
Date:   Thu Mar 12 19:04:38 2009 -0700

    make 6g constants behave as ken proposes.  (i hope.)
    various bug fixes and tests involving constants.
    
    test/const1.go is the major new test case.
    
    R=ken
    OCL=26216
    CL=26224

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

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

元コミット内容

make 6g constants behave as ken proposes. (i hope.)
various bug fixes and tests involving constants.

test/const1.go is the major new test case.

R=ken
OCL=26216
CL=26224

変更の背景

Go言語の初期段階において、定数の型推論と振る舞いはまだ完全に洗練されていませんでした。特に、異なる型の定数間の演算や、定数が変数に代入される際の型変換のルールが不明確であったり、予期せぬ挙動を引き起こしたりする可能性がありました。

このコミットの背景には、Go言語の設計者の一人であるKen Thompson氏からの提案があり、定数をより柔軟かつ直感的に扱えるようにするための根本的な変更が求められていました。具体的には、定数がその値の範囲内で可能な限り高い精度を保ち、必要に応じてのみ特定の型に「降格」されるようなメカニズムの導入が目指されました。これにより、例えば1 << 63のような大きな整数定数が、int型に収まらない場合でもコンパイルエラーにならず、uint64などの適切な型に自動的に適合できるようになるなど、より強力な定数表現が可能になります。

また、既存の定数関連のバグを修正し、新たなテストケースを追加することで、定数システムの堅牢性を高めることも目的とされました。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびコンパイラの基本的な概念を理解しておく必要があります。

  1. Go言語の定数 (Constants):

    • Go言語の定数は、コンパイル時に値が決定される不変のエンティティです。
    • 数値、真偽値、文字列の定数があります。
    • Goの定数には「型付き(typed)」と「型なし(untyped)」の概念があります。
    • 型なし定数 (Untyped Constants): このコミットで導入された、またはその基礎が強化された概念です。型なし定数は、特定のGoの型(int, float64, stringなど)を持たず、その値が表現できる任意の精度で内部的に保持されます。これにより、例えば1.0という浮動小数点定数が、必要に応じてfloat32にもfloat64にも適合できる柔軟性を提供します。型なし定数は、変数に代入されるか、型付きのコンテキストで使用される際に、そのコンテキストの型に「降格(demote)」されます。
    • 型付き定数 (Typed Constants): const x int = 10のように明示的に型が指定された定数です。これらは通常の変数と同様に厳密な型チェックを受けます。
  2. Goコンパイラの構造:

    • Goコンパイラは、主にフロントエンド(src/cmd/gc)とバックエンド(src/cmd/6g, src/cmd/8gなど)に分かれています。
    • フロントエンド (src/cmd/gc): ソースコードの字句解析、構文解析、型チェック、抽象構文木(AST)の構築、定数評価、最適化など、言語のセマンティクスに関わる処理を担当します。このコミットの変更の大部分はここに集中しています。
    • バックエンド (src/cmd/6g): フロントエンドが生成した中間表現(AST)を受け取り、特定のアーキテクチャ(この場合はAMD64)向けの機械語コードを生成します。
  3. mp (multi-precision) 演算ライブラリ:

    • Goコンパイラ内部では、任意精度整数(Mpint)や任意精度浮動小数点数(Mpflt)を扱うためのmpライブラリが使用されています。これにより、Goの型なし数値定数が、通常のCPUレジスタの範囲を超える大きな値や高い精度で表現・計算できます。
  4. AST (Abstract Syntax Tree):

    • コンパイラがソースコードを解析して生成する、プログラムの構造を木構造で表現したものです。定数もASTノードとして表現され、その値や型情報が格納されます。

技術的詳細

このコミットの核となる技術的変更は、Goコンパイラが定数を扱う方法の根本的な再設計です。

  1. 「理想型(Ideal Type)」の導入 (TIDEAL, TNIL):

    • 以前は、定数はWlitint, Wlitfloatなどの「何であるか(whatis)」を示すカテゴリで分類されていました。このコミットでは、これらのカテゴリを廃止し、go.hTIDEALTNILという新しい擬似型(pseudo-types)が導入されました。
    • TIDEALは、数値定数(整数、浮動小数点数)が特定のGoの型にバインドされる前の状態を表します。これにより、定数はその値が許す限り最高の精度で保持され、演算が行われます。例えば、1 + 2.0のような演算では、1TIDEALな整数として、2.0TIDEALな浮動小数点数として扱われ、演算結果はTIDEALな浮動小数点数になります。
    • TNILは、nil定数を表す擬似型です。nilはポインタ、スライス、マップ、チャネル、インターフェースなど、複数の型に適合できるため、特定の型にバインドされる前の状態として扱われます。
  2. 定数評価ロジックの刷新 (src/cmd/gc/const.c):

    • evconst関数(定数式を評価する関数)が大幅に書き換えられました。
    • whatis関数の代わりに、consttype(Node *n)isconst(Node *n, int ct)という新しいヘルパー関数が導入されました。これらは、ノードが定数であるかどうか、そしてその定数のカテゴリ(CTINT, CTFLT, CTSTR, CTBOOL, CTNIL)をより明確に識別します。
    • 定数演算(加算、減算、乗算、除算、ビット演算、比較など)のロジックが、TUP(n->op, v.ctype)(演算子と定数型)に基づいてディスパッチされるようになりました。これにより、整数と浮動小数点数の間の暗黙的な型変換(例えば、整数定数と浮動小数点定数の演算で、整数が浮動小数点数に変換される)がより正確に処理されます。
    • toflt, toint, tostr, overflow, copyvalといった新しい内部ヘルパー関数が追加され、定数間の型変換、オーバーフローチェック、値のコピーがより構造化されました。特にoverflow関数は、定数が特定の型に変換される際に、その型の範囲を超えていないかをチェックする役割を担います。
    • ゼロ除算のチェックが定数評価ロジックに組み込まれ、コンパイル時にエラーが報告されるようになりました。
  3. 定数ノードの生成と管理の変更:

    • booltrueboolfalseといったグローバルな真偽値定数ノードが廃止され、代わりにnodbool(1)nodbool(0)といった関数呼び出しで動的に真偽値定数ノードが生成されるようになりました。これにより、定数ノードの管理がより柔軟になります。
    • nodlit(Val v)関数が導入され、Val構造体(定数の値と型カテゴリを保持)からOLITERALノードを生成する統一的な方法が提供されました。
    • nodintconst関数がint32からint64を受け取るように変更され、より大きな整数定数を扱えるようになりました。また、生成されるノードの型がTINTからTIDEALに変更され、型なし定数としての振る舞いを反映しています。
  4. 型変換ロジックの改善 (convlit1 -> convlit):

    • convlit1関数がconvlitに改名され、定数を特定の型に変換するロジックが強化されました。
    • 明示的な変換(explicit引数)の概念が導入され、例えばstring(65)のように整数を文字列に変換するようなケースがサポートされました。
    • 型なし定数が型付きのコンテキストで使用される際の「降格」ルールがより厳密に定義されました。
  5. コンパイラバックエンドへの影響:

    • src/cmd/6g/cgen.csrc/cmd/6g/gsubr.cなどのバックエンドのコードは、フロントエンドの変更に合わせて、whatisの代わりにisconstを使用したり、booltrue/boolfalseの代わりにnodboolを使用したりするように修正されました。これにより、新しい定数表現がコード生成に正しく反映されます。

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

このコミットで最も重要な変更は、src/cmd/gc/const.cファイルに集中しています。

  • src/cmd/gc/const.c:

    • truncfltlit: 浮動小数点リテラルを指定された型精度に丸める関数。
    • convlit1 (-> convlit): 定数ノードを特定の型に変換する主要な関数。型なし定数から型付き定数への変換ロジックが大幅に強化されました。
    • copyval, toflt, toint, tostr, overflow: convlitevconstで利用される新しいヘルパー関数群。特にoverflowは、定数がターゲット型の範囲に収まるかチェックします。
    • consttype, isconst: ノードの定数型を識別する新しい関数。
    • evconst: 定数式を評価する関数。二項演算子や単項演算子に対する定数評価ロジックが、新しい定数型システムに合わせて全面的に書き換えられました。特に、TUP(n->op, v.ctype)による演算ディスパッチが導入されました。
    • nodlit: Val構造体からOLITERALノードを生成する新しい関数。
    • defaultlit, defaultlit2: 型なし定数にデフォルトの型を割り当てる関数。
  • src/cmd/gc/go.h:

    • enum NTYPETIDEALTNILが追加されました。
    • enum CTxxxからCTSINT, CTUINTが削除され、CTINTに統合されました。
    • whatis関連のenumが完全に削除されました。
    • booltrue, boolfalseEXTERN宣言が削除されました。
    • 新しい関数プロトタイプ(nodlit, nodbool, nodnil, consttype, isconstなど)が追加されました。
  • src/cmd/gc/subr.c:

    • nodintconst: int32からint64を受け取るように変更され、生成されるノードの型がTINTからTIDEALに変更されました。
    • nodnil, nodbool: nilと真偽値の定数ノードを生成する新しい関数。
    • whatis関数が完全に削除されました。

コアとなるコードの解説

src/cmd/gc/const.cにおける変更の核心

このファイルは、Goコンパイラが定数をどのように表現し、評価し、型変換するかを定義する中心的な場所です。

  1. TIDEALTNILによる定数表現の統一: 以前は、定数の種類(整数、浮動小数点数、真偽値、文字列、nil)をWlitintのようなwhatisカテゴリで区別していました。このコミットでは、これらのカテゴリを廃止し、数値定数にはTIDEALnilにはTNILという「理想型」を導入しました。これにより、定数は特定のGoの型に縛られず、その値が許す限り最高の精度で内部的に扱われます。例えば、1は単なるintではなく、任意精度の整数としてTIDEAL型で表現されます。

  2. convlit関数による柔軟な型変換: convlit(旧convlit1)は、定数ノードを特定のGoの型tに変換する役割を担います。

    • 型なし定数の「降格」: n->type->etype != TIDEAL && n->type->etype != TNIL && !explicitの条件は、既に型付きの定数(またはnil以外の型なし定数)が暗黙的に型変換されるのを防ぎます。explicit引数(明示的な型変換、例: int(3.14))がtrueの場合のみ、この制限が緩和されます。
    • 数値定数間の変換: CTINTCTFLT(整数と浮動小数点数)の間で相互変換を行うロジックが追加されました。例えば、浮動小数点定数を整数型に変換する際には、toint関数が呼び出され、小数点以下が切り捨てられます。この際、mpmovefltfixが非整数値を変換した場合に-1を返すことで、エラーを検出できるようになりました。
    • オーバーフローチェック: overflow(n->val, t)関数が導入され、定数がターゲット型の範囲に収まるかを厳密にチェックし、オーバーフローがあればエラーを報告します。
    • 文字列への変換: et == TSTRING && ct == CTINT && explicitの条件で、整数定数を文字列に明示的に変換する(例: string(65)"A"になる)機能が追加されました。
  3. evconst関数による高精度な定数評価: evconstは、定数式(例: 1 + 2, 3.0 * 4.0)をコンパイル時に評価し、その結果を新しい定数ノードとして生成します。

    • 型なし定数の演算: 左右のオペランドがTIDEAL型の場合、copyvalで値がコピーされ、tofltで必要に応じて浮動小数点数に変換されます。これにより、例えば1 + 2.5のような演算では、1が内部的に浮動小数点数に変換されてから演算が行われ、結果は浮動小数点数になります。
    • 演算ディスパッチ: switch(TUP(n->op, v.ctype))という構造により、演算子(OADD, OSUBなど)と定数型(CTINT, CTFLT, CTSTR, CTBOOL, CTNIL)の組み合わせに基づいて適切な演算ロジックが実行されます。
    • ゼロ除算の検出: ODIVOMOD演算において、右オペランドがゼロである場合にコンパイルエラーを報告するロジックが追加されました。
    • 結果の型設定: 評価結果のノードの型は、元の型名(例: type T int; const A T = 1;A)を失い、その基本型(例: int)になります。これは、定数演算の結果が常に「理想型」またはその基本型になるというGoのセマンティクスを反映しています。

src/cmd/gc/subr.cにおける変更

  • nodintconstの変更: この関数は整数定数ノードを生成します。以前はint32を受け取り、生成されるノードの型をtypes[TINT](Goの組み込みint型)に設定していました。変更後はint64を受け取るようになり、より大きな整数値を扱えるようになりました。さらに重要なのは、生成されるノードの型がtypes[TIDEAL]に設定されるようになった点です。これは、nodintconstが生成する整数定数が、特定のGoの型に縛られない「型なし定数」として扱われることを意味します。これにより、コンパイラはこれらの定数を任意精度で保持し、必要に応じて適切な型に変換できるようになります。

  • nodnilnodboolの追加:

    • nodnil(): nil定数ノードを生成します。その型はtypes[TNIL]nilの理想型)に設定されます。
    • nodbool(int b): 真偽値定数ノードを生成します。b1ならtrue0ならfalseを表し、型はtypes[TBOOL]に設定されます。 以前はbooltrueboolfalseというグローバルなノードが存在しましたが、これらの関数によって動的に生成されるようになりました。

これらの変更により、Goコンパイラは定数をより柔軟かつ強力に扱えるようになり、Go言語の定数システムの重要な基盤が確立されました。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(特にsrc/cmd/gc/ディレクトリ)
  • Go言語の設計に関する議論(GoのメーリングリストやIssueトラッカーなど、当時の情報源)
  • Go言語のコンパイラに関する一般的な知識
  • 任意精度算術ライブラリに関する一般的な知識