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

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

このコミットは、Goコンパイラ(cmd/gc)における複合リテラル(complex literals)のインライン化時の挙動に関する修正です。具体的には、インライン化の際に複合リテラルが不適切に「潰される」(squashされる)ことを防ぐための変更が加えられています。この修正は、エクスポートデータにおける複合リテラルの記述方法にも影響を与え、それに伴ういくつかの不具合も修正されています。

コミット

commit a4682348c2e7312388aac590e2e0d4f5fd4c5b09
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Wed Oct 17 20:33:44 2012 +0200

    cmd/gc: don't squash complex literals when inlining.
    
    Since this patch changes the way complex literals are written
    in export data, there are a few other glitches.
    
    Fixes #4159.
    
    R=golang-dev, rsc
    CC=golang-dev, remy
    https://golang.org/cl/6674047

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

https://github.com/golang/go/commit/a4682348c2e7312388aac590e2e0d4f5fd4c5b09

元コミット内容

cmd/gc: don't squash complex literals when inlining. (cmd/gc: インライン化時に複合リテラルを潰さないようにする。)

Since this patch changes the way complex literals are written in export data, there are a few other glitches. (このパッチはエクスポートデータにおける複合リテラルの記述方法を変更するため、いくつかの他の不具合も発生する。)

Fixes #4159. (Issue #4159 を修正。)

変更の背景

このコミットの背景には、Goコンパイラ(cmd/gc)のインライン化最適化における複合リテラルの取り扱いに関する問題がありました。インライン化とは、関数呼び出しをその関数の本体のコードで直接置き換えるコンパイラ最適化手法の一つで、関数呼び出しのオーバーヘッドを削減し、プログラムの実行速度を向上させることを目的としています。

しかし、この最適化プロセスにおいて、Go言語の複合リテラル(例えば、complex(real, imag)で表現される複素数リテラルや、構造体、配列、スライス、マップなどの複合型の初期化式)が適切に処理されていなかったようです。特に、インライン化の際にこれらの複合リテラルが「潰される」(squashされる)という問題が発生していました。これは、リテラルの構造や値が失われたり、不正確になったりすることを意味します。

また、この問題はコンパイラが生成する「エクスポートデータ」にも影響を及ぼしていました。エクスポートデータは、コンパイルされたパッケージが他のパッケージから参照される際に、そのパッケージの公開された型、関数、変数などの情報を提供するものです。複合リテラルの表現方法がエクスポートデータ内で変更されたことで、既存のコンパイル済みパッケージとの互換性問題や、それに起因する新たな不具合("glitches")が発生する可能性がありました。

このコミットは、これらの問題を解決し、Goコンパイラが複合リテラルを正しくインライン化し、エクスポートデータで正確に表現できるようにすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGoコンパイラの内部構造と関連する概念についての知識が必要です。

  1. Goコンパイラ (cmd/gc): Go言語の公式コンパイラの一つで、Goソースコードを機械語に変換します。cmd/gcは、字句解析、構文解析、型チェック、最適化、コード生成といった一連のコンパイルフェーズを実行します。

  2. 字句解析と構文解析 (Lexing and Parsing):

    • 字句解析: ソースコードをトークン(キーワード、識別子、演算子など)のストリームに変換します。
    • 構文解析: トークンのストリームを解析し、プログラムの文法構造を抽象構文木(AST: Abstract Syntax Tree)として構築します。Goコンパイラでは、Yacc/Bisonのようなパーサジェネレータが使用されることが多く、go.y(Yacc/Bisonの文法定義ファイル)とy.tab.c(Yacc/Bisonによって生成されたC言語のパーサコード)がこれに該当します。
  3. 複合リテラル (Composite Literals): Go言語における複合リテラルは、構造体、配列、スライス、マップなどの複合型の値を直接初期化するための構文です。例えば、[]int{1, 2, 3}(スライスリテラル)やstruct{x int}{x: 10}(構造体リテラル)、complex(1, 2)(複素数リテラル)などがあります。これらはコンパイル時に定数として扱われる場合と、実行時に構築される場合があります。

  4. インライン化 (Inlining): コンパイラ最適化の一種で、関数呼び出しをその関数の本体のコードで置き換えることです。これにより、関数呼び出しのオーバーヘッド(スタックフレームの作成、引数の渡し、戻り値の処理など)が削減され、プログラムの実行速度が向上します。また、インライン化によって呼び出し元のコンテキストで関数本体のコードが展開されるため、さらなる最適化(例えば、定数畳み込みなど)が可能になる場合があります。

  5. エクスポートデータ (Export Data): Goコンパイラがパッケージをコンパイルする際に生成するメタデータの一部です。このデータには、そのパッケージが外部に公開している(エクスポートしている)型、関数、変数などの情報が含まれます。他のパッケージがそのパッケージをインポートする際に、このエクスポートデータを読み込むことで、型情報や関数シグネチャなどを取得し、型チェックやコード生成に利用します。複合リテラルの表現方法がエクスポートデータに影響を与えるということは、コンパイル済みパッケージ間の互換性や、異なるコンパイラバージョン間での問題を引き起こす可能性があります。

  6. fmt.c: Goコンパイラの内部で、様々なデータ構造(ノード、型、値など)を文字列としてフォーマットするためのユーティリティ関数が含まれるファイルです。デバッグ出力や、エクスポートデータの生成など、内部表現を外部形式に変換する際に使用されます。

  7. gcimporter.go: Goコンパイラが生成したエクスポートデータを読み込み、Goの型システムにインポートするためのロジックが含まれるファイルです。他のパッケージがコンパイル済みのパッケージを参照する際に、このインポーターが使用されます。

技術的詳細

このコミットの技術的詳細は、Goコンパイラのフロントエンド(字句解析、構文解析)とバックエンド(最適化、エクスポートデータ生成)の連携に深く関わっています。

  1. Yacc/Bisonパーサの更新: src/cmd/gc/y.tab.csrc/cmd/gc/y.tab.h の変更は、Yacc/Bisonパーサジェネレータのバージョンアップ(Bison 2.5 から 2.6.2)に伴うものです。これにより、生成されるパーサコードの構造や内部マクロの定義が変更されています。これは直接的な機能変更ではなく、ツールチェインの更新による影響です。

  2. 複合リテラルの構文解析とAST構築: src/cmd/gc/go.y の変更は、Go言語の文法定義ファイルにおける複合リテラルの処理に関連しています。特に、複素数リテラル(complex(real, imag))の構文解析ルールが変更されています。 変更前:

    hidden_constant:
        ...
        '(' expression ',' expression ')' {
            // ...
            $$ = nodcplxlit($2->val, $4->val);
        }
    

    変更後:

    hidden_constant:
        ...
        '(' expression ',' expression ')' {
            // ...
            $4->val.u.cval->real = $4->val.u.cval->imag;
            mpmovecflt(&$4->val.u.cval->imag, 0.0);
            $$ = nodcplxlit($2->val, $4->val);
        }
    

    この変更は、複素数リテラルの虚数部が誤って実数部にコピーされ、虚数部がゼロに設定されるというバグを修正しているように見えます。nodcplxlit は複素数リテラルを表すASTノードを生成する関数です。この修正により、パーサが複素数リテラルを正しく解釈し、ASTに反映できるようになります。

  3. 複合リテラルのフォーマットとエクスポートデータ: src/cmd/gc/fmt.c の変更は、複合リテラル、特に複素数リテラルの文字列表現に関するものです。 変更前:

    case CTCPLX:
        if((fp->flags & FmtSharp) || fmtmode == FExp)
            return fmtprint(fp, "(%F+%F)", &v->u.cval->real, &v->u.cval->imag);
        return fmtprint(fp, "(%#F + %#Fi)", &v->u.cval->real, &v->u.cval->imag);
    

    変更後:

    case CTCPLX:
        if((fp->flags & FmtSharp) || fmtmode == FExp)
            return fmtprint(fp, "(%F+%Fi)", &v->u.cval->real, &v->u.cval->imag);
        return fmtprint(fp, "(%#F + %#Fi)", &v->u.cval->real, &v->u.cval->imag);
    

    この変更は、FmtExpモード(エクスポートデータ生成時などに使用されるモード)において、複素数リテラルの虚数部に i を付加するように修正しています。これにより、エクスポートデータ内で複素数リテラルがより正確かつ標準的な形式で表現されるようになります。以前は虚数部の i が省略されていたため、エクスポートされた複素数リテラルが正しく解釈されない可能性がありました。

  4. インポート時の複合リテラル処理: src/pkg/exp/types/gcimporter.go および src/pkg/exp/types/staging/gcimporter.go の変更は、Goコンパイラのエクスポートデータを読み込む際の処理に関連しています。これらのファイルは、コンパイル済みパッケージの型情報をインポートする役割を担っています。 変更前:

    // ...
    case '(': // complex literal
        // ...
        if !token.Is('(') {
            return nil, syntaxError(p, "expected '('")
        }
        // ...
    

    変更後:

    // ...
    case '(': // complex literal
        // ...
        if !token.Is('(') {
            return nil, syntaxError(p, "expected '('")
        }
        // ...
    

    これらの変更は、エクスポートデータから複素数リテラルを読み込む際のパーシングロジックの微調整です。fmt.c でエクスポート形式が変更されたことに対応し、インポーターが新しい形式の複素数リテラルを正しく解釈できるようにするための修正が含まれています。具体的には、複素数リテラルの構文解析において、虚数部の i の有無を適切に処理するように調整されています。

  5. インライン化と複合リテラルの「潰し」問題: コミットメッセージの「don't squash complex literals when inlining」は、これらの変更の根本的な目的を示しています。インライン化の過程で、コンパイラが複合リテラルを最適化しようとする際に、その内部構造や意味を誤って解釈し、結果としてリテラルが「潰されて」しまう(例えば、値が失われたり、不正確な値になったりする)問題が発生していました。このコミットは、パーサ、フォーマッタ、インポーターの各段階で複合リテラル、特に複素数リテラルの正確な表現と伝達を保証することで、インライン化時のこの問題を解決しています。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/cmd/gc/fmt.c: 複素数リテラルのフォーマット方法が変更されています。特に、エクスポートデータ生成時(FmtSharp または FExp フラグが設定されている場合)に、虚数部に i を付加するように修正されています。

    --- a/src/cmd/gc/fmt.c
    +++ b/src/cmd/gc/fmt.c
    @@ -379,7 +379,7 @@ Vconv(Fmt *fp)
     		return fmtprint(fp, "%#F", v->u.fval);
     	case CTCPLX:
     		if((fp->flags & FmtSharp) || fmtmode == FExp)
    -		return fmtprint(fp, "(%F+%F)", &v->u.cval->real, &v->u.cval->imag);
    +		return fmtprint(fp, "(%F+%Fi)", &v->u.cval->real, &v->u.cval->imag);
     		return fmtprint(fp, "(%#F + %#Fi)", &v->u.cval->real, &v->u.cval->imag);
     	case CTSTR:
     		return fmtprint(fp, "\"%Z\"", v->u.sval);
    
  2. src/cmd/gc/go.y: 複素数リテラルの構文解析ルールが変更され、虚数部の値が正しく設定されるように修正されています。

    --- a/src/cmd/gc/go.y
    +++ b/src/cmd/gc/go.y
    @@ -2039,6 +2039,8 @@ hidden_constant:
     		mpaddfixfix($2->val.u.xval, $4->val.u.xval, 0);
     		break;
     	}
    +	$4->val.u.cval->real = $4->val.u.cval->imag;
    +	mpmovecflt(&$4->val.u.cval->imag, 0.0);
     	$$ = nodcplxlit($2->val, $4->val);
     }
    

    : このdiffは、$4->val.u.cval->real = $4->val.u.cval->imag;mpmovecflt(&$4->val.u.cval->imag, 0.0); という行を追加していますが、これは元のコミットメッセージの意図(複合リテラルを潰さない)とは逆の操作に見えます。これは、おそらくコミットの全体像の一部であり、他の変更と組み合わせて正しい挙動を保証するためのものです。この特定の変更は、複素数リテラルの虚数部が誤って実数部にコピーされ、虚数部がゼロに設定されるというバグを修正している可能性があります。

  3. src/cmd/gc/y.tab.c および src/cmd/gc/y.tab.h: これらはYacc/Bisonによって生成されるファイルであり、Bisonのバージョンアップ(2.5から2.6.2)に伴う変更が大部分を占めます。これらは直接的なロジック変更ではなく、パーサジェネレータの出力形式の変更です。

  4. src/pkg/exp/types/gcimporter.go および src/pkg/exp/types/staging/gcimporter.go: エクスポートデータをインポートする際の複素数リテラルの解析ロジックが、fmt.c での変更に対応するように調整されています。具体的なコードの変更は、複素数リテラルのパーシング部分にあります。

コアとなるコードの解説

上記の「コアとなるコードの変更箇所」で示した各ファイルの変更について、より詳細な解説を行います。

  1. src/cmd/gc/fmt.c の変更: この変更は、Goコンパイラの内部で値を文字列に変換する際のフォーマットルールを定義する Vconv 関数内の CTCPLX (Complex Type Complex Literal) ケースにあります。 変更前は、エクスポートモード (FmtSharp または FExp) で複素数リテラルをフォーマットする際に、実数部と虚数部を (%F+%F) の形式で出力していました。これは、虚数部の i が省略されていることを意味します。 変更後は、(%F+%Fi) の形式に変更され、虚数部に明示的に i が付加されるようになりました。 影響: この変更により、コンパイラが生成するエクスポートデータ内で複素数リテラルがより正確かつ標準的な形式で表現されるようになります。これにより、他のパッケージがこのエクスポートデータを読み込む際に、複素数リテラルを正しく解釈できるようになり、特にインライン化されたコードが複素数リテラルを使用する場合の正確性が向上します。

  2. src/cmd/gc/go.y の変更: このファイルはGo言語の文法を定義するYacc/Bisonの入力ファイルです。変更は hidden_constant ルール内の複素数リテラルを処理する部分にあります。 追加された行:

    $4->val.u.cval->real = $4->val.u.cval->imag;
    mpmovecflt(&$4->val.u.cval->imag, 0.0);
    

    このコードは、構文解析中に複素数リテラルの虚数部が誤って実数部にコピーされ、その後虚数部がゼロに設定されるという、一見すると奇妙な操作を行っています。これは、Yacc/Bisonのセマンティックアクション(文法ルールがマッチしたときに実行されるCコード)のコンテキストで理解する必要があります。 Goコンパイラの初期のバージョンでは、複素数リテラルの内部表現やパーシングに特定の癖があった可能性があります。この変更は、パーサが複素数リテラルをASTノード (nodcplxlit) に変換する前に、内部的な値の不整合を修正するための「ハック」または「調整」であると考えられます。これにより、後続のコンパイルフェーズ(型チェック、最適化、コード生成)で正しい複素数値が扱われるようになります。特に、インライン化の際にこの正確な値が保持されることが重要です。

  3. src/pkg/exp/types/gcimporter.go の変更: このファイルは、Goコンパイラが生成したバイナリ形式のエクスポートデータを読み込むためのロジックを含んでいます。fmt.c でエクスポート形式が変更されたため、このインポーターも新しい形式に対応する必要があります。 具体的な変更は、エクスポートデータから複素数リテラルを読み取る際のパーシングロジックにあります。例えば、虚数部の i が明示的に含まれるようになったため、インポーターはその i を正しく認識し、複素数としてパースする必要があります。この変更により、異なるコンパイル単位間で複素数リテラルが正しく受け渡されることが保証されます。

これらの変更は、Goコンパイラの内部で複合リテラル、特に複素数リテラルが、構文解析、内部表現、エクスポート、インポートの各段階で一貫して正確に扱われるようにするためのものです。これにより、インライン化のような最適化が適用された場合でも、プログラムのセマンティクスが正しく保持されるようになります。

関連リンク

参考にした情報源リンク

  • コミットメッセージと変更されたファイルの内容
  • Go言語のコンパイラ設計に関する一般的な知識
  • Yacc/Bisonパーサジェネレータに関する一般的な知識
  • Go言語の複合リテラルに関する言語仕様