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

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

このコミットは、Goコンパイラのsrc/cmd/gc/walk.cファイルに対する変更です。具体的には、walk.c内のascompatte関数におけるfunarg(関数引数、特にクロージャや関数リテラル)の戻り値の処理に関するバグ修正を目的としています。

コミット

commit a6c59ce2744182a1b6badd78c4269d1b424dd080
Author: Russ Cox <rsc@golang.org>
Date:   Sun Feb 8 11:01:52 2009 -0800

    gc funarg return fix.
    change type (to satisfy OAS) after nodarg:
    nodarg uses offset from type too,
    and must use correct offset.
    
    R=ken
    OCL=24656
    CL=24656

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

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

元コミット内容

gc funarg return fix.
change type (to satisfy OAS) after nodarg:
nodarg uses offset from type too,
and must use correct offset.

変更の背景

このコミットは、Goコンパイラの初期段階における重要なバグ修正です。funarg(関数引数、特にクロージャや関数リテラル)の戻り値の処理において、コンパイラが誤った型情報を使用する可能性がありました。

具体的には、nodargというコンパイラ内部の関数が、ノードのオフセットを計算する際に型情報に依存していました。しかし、nodargが呼び出された後に、そのノードの型が変更される場合があり、その結果、OAS(Order of Abstract Syntax Tree、ASTの順序付け)フェーズで型の一貫性が失われ、コンパイルエラーや不正なコード生成につながる可能性がありました。

この問題は、特にfunargのような複雑な型を持つ構造が関わる場合に顕在化しやすかったと考えられます。コンパイラが正しく型を認識し、それに基づいてオフセットを計算することは、メモリレイアウトの正確性やランタイムの動作に直結するため、この修正はコンパイラの安定性と正確性を確保するために不可欠でした。

前提知識の解説

このコミットを理解するためには、Goコンパイラの初期の構造と、いくつかの内部概念について知る必要があります。

  1. Goコンパイラの初期構造 (walk.c): Goコンパイラは、初期にはC言語で書かれていました。src/cmd/gc/walk.cは、そのC言語時代のコンパイラの一部であり、AST (Abstract Syntax Tree) の変換を行う「walk」フェーズを担当していました。現在のGoコンパイラでは、この機能は主にcmd/compile/internal/walkパッケージ(Go言語で記述)に移行しています。 「walk」フェーズの主な役割は以下の通りです。

    • 複雑なステートメントの分解: 高レベルなGoのステートメント(例: switch文、map操作、チャネル通信)を、より単純な個別の操作に分解します。これは「order」または「order of evaluation」とも呼ばれます。
    • 高レベルな構造のデシュガー: Goの高レベルな機能(例: deferrange)を、よりプリミティブな形式やGoランタイムへの呼び出しに変換します。
  2. nodarg: nodargは、Goコンパイラの内部で使われる関数または概念で、特定のノード(ASTの要素)に関連する引数やオフセットを処理するために使用されます。コミットメッセージにあるように、「nodarg uses offset from type too」とあることから、この関数が型情報に基づいてメモリ上のオフセットを計算する役割を担っていたことがわかります。これは、構造体のフィールドや関数の引数/戻り値のメモリ配置を決定する上で重要です。

  3. OAS (Order of Abstract Syntax Tree): OASは、ASTのノードが評価される順序を指します。Goコンパイラの「walk」フェーズの一部として、ASTの各ノードが正しい順序で処理され、適切な中間表現に変換されることを保証します。型の不一致やオフセットの誤りは、このOASフェーズでの処理に問題を引き起こす可能性があります。

  4. funarg (Functional Argument): Go言語におけるfunargは、関数を引数として渡すこと、または関数リテラル(クロージャ)を指します。Goはファーストクラス関数をサポートしており、関数を他のデータ型と同様に変数に代入したり、引数として渡したり、戻り値として返したりできます。クロージャは、それが定義されたスコープの変数をキャプチャできるため、コンパイラはこれらの変数のメモリ配置やライフタイムを正確に管理する必要があります。

  5. eqtypenoname: これは、Goコンパイラの内部で型を比較するために使用される関数です。eqtypenoname(type1, type2)は、2つの型が名前を除いて等しいかどうかをチェックします。これは、型エイリアスや匿名構造体など、名前が異なるが構造的に同じ型を比較する際に重要になります。

技術的詳細

このコミットが修正しようとしている問題は、nodargがノードのオフセットを計算する際に使用する型情報と、その後のOASフェーズでの型の整合性に関するものです。

元のコードでは、以下の条件が満たされた場合に、convas関数(おそらく型変換を行う関数)を呼び出していました。

if(l != T && r != N
&& structnext(&peekl) != T
&& listnext(&peekr) == N
&& eqtypenoname(r->type, *nl))
    return convas(nod(OAS, nodarg(r->type, fp), r));

ここで問題となるのは、nodarg(r->type, fp)が呼び出された後、nod(OAS, ..., r)が構築される前に、nodargが使用したr->typeが、OASフェーズで期待される型と異なる場合があるという点です。特に、funargのような複雑な型の場合、型推論や型変換の過程で、nodargが参照する型と、最終的にそのノードが持つべき型との間に不一致が生じる可能性がありました。

nodargは、ノードのメモリ上のオフセットを計算するために型情報を使用します。もしnodargが呼び出された時点の型が、そのノードが最終的に持つべき型と異なると、計算されたオフセットが不正になり、結果としてメモリレイアウトの誤りや、ランタイムでのデータアクセスエラーを引き起こす可能性があります。

このコミットの修正は、nodargによって生成されたノードaに対して、明示的にa->type = r->type;という行を追加することで、この問題に対処しています。これにより、nodargがオフセットを計算した後に、そのノードaの型を、右辺のノードrの型に強制的に設定し直しています。これは、OASフェーズが期待する型と、nodargが使用した型との間の不一致を解消し、コンパイラ内部での型の一貫性を保つための重要なステップです。

この修正は、コンパイラがfunargのような複雑な構造を扱う際に、型とメモリレイアウトの正確性を保証するために不可欠でした。

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

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -1905,8 +1905,11 @@ ascompatte(int op, Type **nl, Node **nr, int fp)\n 	if(l != T && r != N\n 	&& structnext(&peekl) != T\n 	&& listnext(&peekr) == N\n-\t&& eqtypenoname(r->type, *nl))\n-\t\treturn convas(nod(OAS, nodarg(r->type, fp), r));\n+\t&& eqtypenoname(r->type, *nl)) {\n+\t\ta = nodarg(*nl, fp);\n+\t\ta->type = r->type;\n+\t\treturn convas(nod(OAS, a, r));\n+\t}\n \n loop:\n \tif(l != T && isddd(l->type)) {

コアとなるコードの解説

変更はsrc/cmd/gc/walk.cファイルのascompatte関数内にあります。

元のコードでは、特定の条件(l, rが有効なノードであること、structnextlistnextのチェック、そしてrの型と*nlの型が名前を除いて等しいこと)が満たされた場合に、以下の行が実行されていました。

return convas(nod(OAS, nodarg(r->type, fp), r));

この行は、nodarg(r->type, fp)を呼び出して新しいノードを作成し、それをOASノードの左辺として、右辺にrを置いてconvas関数に渡していました。

修正後のコードでは、この部分が以下のように変更されています。

a = nodarg(*nl, fp);
a->type = r->type;
return convas(nod(OAS, a, r));

この変更のポイントは以下の2点です。

  1. nodargの引数の変更: 元のコードではnodarg(r->type, fp)と、右辺のノードrの型を直接渡していました。修正後ではnodarg(*nl, fp)と、ascompatte関数の引数として渡された*nl(おそらく期待される型)を渡しています。これは、nodargがノードを生成する際に、より正確な(または期待される)型情報に基づいてオフセットを計算するように意図されたものと考えられます。

  2. a->type = r->type;の追加: これがこのコミットの最も重要な変更点です。nodargによって生成されたノードaの型を、明示的に右辺のノードrの型に設定し直しています。 これは、nodargがノードを生成する際に一時的に使用する型と、そのノードが最終的に持つべき型(この場合は右辺rの型)との間に不一致が生じる可能性があったためです。特に、funargのような複雑な型の場合、型推論や型変換の過程で、nodargが参照する型と、最終的にそのノードが持つべき型との間に不一致が生じる可能性がありました。 この行を追加することで、nodargがオフセットを計算した後に、そのノードaの型を、右辺のノードrの型に強制的に設定し直しています。これにより、OASフェーズが期待する型と、nodargが使用した型との間の不一致を解消し、コンパイラ内部での型の一貫性を保つための重要なステップとなります。

この修正により、funargの戻り値の型が正しく処理され、nodargが計算するオフセットが常に正しい型情報に基づいていることが保証され、コンパイラの安定性と正確性が向上しました。

関連リンク

参考にした情報源リンク