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

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

このコミットは、Go言語の初期のコンパイラ(gc および 6g)における、主にインポート/エクスポート機構、関数およびメソッド呼び出しの処理、デバッグ出力の制御、そして式の評価順序の最適化に関する複数の改善を含んでいます。

変更されたファイルは以下の通りです。

  • src/cmd/6g/gen.c: 64-bit Goコンパイラのコード生成部分。関数呼び出しとメソッド呼び出しの処理ロジックが変更されています。
  • src/cmd/6g/gg.h: 64-bit Goコンパイラの共通ヘッダファイル。グローバル変数の宣言が調整されています。
  • src/cmd/6g/gsubr.c: 64-bit Goコンパイラのサブルーチン。アドレス解決ロジックにメソッド関連の処理が追加されています。
  • src/cmd/gc/dcl.c: Goコンパイラの宣言処理部分。デバッグ出力の制御が改善されています。
  • src/cmd/gc/export.c: Goコンパイラのインポート/エクスポート処理部分。インポートされたシンボルと型の処理が大幅に修正されています。
  • src/cmd/gc/go.h: Goコンパイラの共通ヘッダファイル。ASTノード構造体とグローバル変数が更新されています。
  • src/cmd/gc/go.y: Go言語の文法定義(Yaccファイル)。関数宣言の処理が変更されています。
  • src/cmd/gc/lex.c: Goコンパイラの字句解析部分。インポートファイルのパス処理とインポートシステムの状態管理が変更されています。
  • src/cmd/gc/walk.c: GoコンパイラのASTウォーク(走査)と最適化処理部分。式の評価順序の再編成ロジックが改善されています。

コミット

  • コミットハッシュ: 2254a8ee991899f0f72d7fbd116c189582441ea0
  • 作者: Ken Thompson ken@golang.org
  • 日付: Wed Jun 11 21:06:26 2008 -0700

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

https://github.com/golang/go/commit/2254a8ee991899f0f72d7fbd116c189582441ea0

元コミット内容

import/export

SVN=122309

変更の背景

このコミットは、Go言語がまだオープンソース化される前の、非常に初期の開発段階(2008年)に行われたものです。当時のGoコンパイラは、現在の go コマンドとは異なり、gc (Go compiler frontend) と 6g (64-bit Go compiler backend) のように、アーキテクチャ固有のバックエンドを持つ構成でした。

主な変更の背景には、以下の点が挙げられます。

  1. インポート/エクスポート機構の成熟: Go言語のモジュールシステムとパッケージ管理の基盤となるインポート/エクスポート機能は、言語の初期から重要な要素でした。このコミットは、パッケージ間のシンボル(変数、関数、型など)のやり取りをより堅牢かつ正確に行うための改善を目的としています。特に、インポートされた型の再宣言の扱いや、シンボル解決のロジックが洗練されています。
  2. 関数・メソッド呼び出しの内部処理の改善: Go言語の特徴の一つであるメソッドは、その呼び出しメカニズムがコンパイラ内部で適切に処理される必要があります。このコミットでは、ASTノードにメソッド呼び出しを示すフラグを追加し、コード生成段階でこれを適切に扱うことで、メソッド呼び出しのセマンティクスを正確に反映しようとしています。
  3. コンパイラのデバッグと診断の強化: コンパイラ開発において、内部状態のデバッグ出力は非常に重要です。このコミットでは、デバッグ出力の有効/無効をより細かく制御するためのメカニズムが導入されており、特にインポート処理中のデバッグ出力の抑制など、特定のコンテキストでの出力を調整できるようになっています。
  4. 式の評価順序の最適化: コンパイラは、ソースコードの式を効率的に評価するための最適化を行います。特に、副作用を持つ関数呼び出しを含む複雑な式の場合、評価順序は重要です。このコミットでは、式の再順序付けロジックを改善し、一時変数を活用することで、より正確で効率的なコード生成を目指しています。

これらの変更は、Goコンパイラの安定性と正確性を向上させ、言語の基本的な機能が正しく動作するための基盤を固める上で不可欠なステップでした。

前提知識の解説

このコミットの技術的詳細を理解するためには、以下の前提知識が役立ちます。

  1. Goコンパイラ (gc, 6g) の役割:
    • gc (Go Compiler Frontend): Go言語のソースコードを解析し、抽象構文木(AST)を構築し、型チェック、シンボル解決、一部の最適化など、プラットフォーム非依存の処理を行います。
    • 6g (64-bit Go Compiler Backend): gc によって生成された中間表現(AST)を受け取り、特定のアーキテクチャ(この場合はAMD64)向けの機械語コードを生成します。8g はARM64、5g はARM32など、数字はターゲットアーキテクチャを示します。
  2. 抽象構文木 (AST - Abstract Syntax Tree):
    • コンパイラがソースコードを解析した結果として内部的に構築するツリー構造のデータ表現です。ソースコードの構造を抽象化し、プログラムの意味を表現します。
    • Goコンパイラでは、Node 構造体がASTの各ノードを表します。各ノードは、演算子(op)、型(type)、シンボル(sym)、子ノード(left, right, list)などの情報を持っています。
  3. シンボルテーブル (Sym):
    • コンパイラがプログラム内で宣言された識別子(変数名、関数名、型名など)の情報を管理するために使用するデータ構造です。各シンボルは、その名前、型、スコープ、ストレージクラスなどの属性を持ちます。
    • pkglookup のような関数は、パッケージ名とシンボル名に基づいてシンボルテーブルからエントリを検索するために使用されます。
  4. インポート/エクスポート:
    • Go言語では、import キーワードを使用して他のパッケージの機能を利用します。コンパイラは、インポートされたパッケージの公開されたシンボル(エクスポートされたシンボル)を認識し、現在のパッケージで使用できるようにする必要があります。
    • エクスポートされた情報は、通常、コンパイル済みのパッケージファイル(Goの初期では .6.8 などの拡張子を持つファイル)に格納されます。
  5. Ullman Number (ウルマン数):
    • コンパイラ最適化の文脈で使われる概念で、式の評価順序を決定する際に、レジスタ割り当ての効率などを考慮するために用いられます。各ノードに割り当てられる数値で、そのノードを評価するために必要なレジスタの最小数を示唆します。walk.creorder1 関数で ullman フィールドが参照されているのは、この概念に基づいています。
  6. Yacc/Lex:
    • Yacc (Yet Another Compiler Compiler): 文法定義ファイル(.y)からパーサー(構文解析器)を生成するツールです。Goコンパイラの go.y は、Go言語の文法規則を定義しており、これに基づいてASTが構築されます。
    • Lex (Lexical Analyzer Generator): 字句定義ファイルからレキサー(字句解析器)を生成するツールです。Goコンパイラの lex.c は、ソースコードをトークン(識別子、キーワード、演算子など)に分割する処理に関連しています。

技術的詳細

このコミットは、Goコンパイラの複数の側面に対して、相互に関連する重要な変更を加えています。

1. インポート/エクスポート機構の改善 (src/cmd/gc/export.c, src/cmd/gc/go.h, src/cmd/gc/lex.c)

  • getimportsym 関数の変更と移動:
    • 以前は importfuncnam の後に定義されていた getimportsym 関数が、importlooktype の前に移動し、より汎用的に使用されるようになりました。
    • この関数は、インポートされたシンボル(ss->sym->name)とパッケージ名(ss->psym->name または pkgmyname)に基づいて、シンボルテーブルから Sym エントリを取得します。
    • 重要な変更点として、s->opackage = ss->osym->name; という行があります。これは、インポートされたシンボル s の元のパッケージ名(osym->name)を opackage フィールドに保存しています。これにより、シンボルがどのパッケージからエクスポートされたかという情報が保持され、後のシンボル解決やデバッグに役立ちます。
  • importaddtyp の型再宣言の扱い:
    • importaddtyp 関数は、インポートされた型 t をシンボル ss に関連付けます。
    • 変更前は if(s->otype == T || !eqtype(t, s->otype, 0)) という条件で、既存の型が T (unknown type) であるか、または新しい型 t と既存の型 s->otype が異なる場合にのみ addtyp を呼び出していました。
    • 変更後は、まず if(s->otype == T) で既存の型が不明な場合に addtyp を呼び出し、return します。
    • 次に if(!eqtype(t, s->otype, 0)) で型が異なる場合に print 文で再宣言を報告し、addtyp を呼び出します。
    • 最後に print("sametype %S %lT => %lT\\n", s, s->otype, t); が追加され、同じ型が再宣言された場合にもデバッグ出力が行われるようになりました。
    • この変更により、インポート時の型の一貫性チェックがより明示的になり、デバッグ情報も強化されています。
  • Node 構造体への method フィールド追加 (src/cmd/gc/go.h):
    • Node 構造体に uchar method; フィールドが追加されました。これは、ASTノードがメソッド呼び出しに関連しているかどうかを示すフラグとして使用されます。
  • インポートファイル拡張子の変更 (src/cmd/gc/lex.c):
    • importfile 関数内で、インポートするパッケージのファイル名を生成する際に、snprint(namebuf, sizeof(namebuf), "%Z.8", f->sval);snprint(namebuf, sizeof(namebuf), "%Z.6", f->sval); に変更されました。
    • これは、Goコンパイラの初期段階において、コンパイル済みパッケージファイルの拡張子がターゲットアーキテクチャを示す慣習(例: 6 はamd64、8 はarm64)に従っていたためと考えられます。この変更は、当時のデフォルトのターゲットアーキテクチャが 6 (amd64) に統一されたことを示唆している可能性があります。
  • inimportsys フラグの導入 (src/cmd/gc/go.h, src/cmd/gc/lex.c):
    • EXTERN int inimportsys;go.h に追加されました。
    • lex.cunimportfileinimportsys = 0; が設定され、cannedimportsinimportsys = 1; が設定されます。
    • このフラグは、コンパイラが内部的なシステムインポート(例えば、unsafe パッケージのような組み込みパッケージ)を処理しているかどうかを示すために使用されます。

2. 関数・メソッド呼び出しの処理の改善 (src/cmd/6g/gen.c, src/cmd/6g/gsubr.c)

  • cgen_callmeth の変更 (src/cmd/6g/gen.c):
    • cgen_callmeth はメソッド呼び出しのコード生成を担当します。
    • if(l->op != ODOTMETH) fatal("cgen_callmeth: not dotmethod: %N"); というチェックが追加され、ODOTMETH (ドット記法によるメソッド呼び出し) でない場合は致命的なエラーを発生させるようになりました。これにより、メソッド呼び出しのAST構造が期待通りであることを保証します。
  • cgen_call の変更 (src/cmd/6g/gen.c):
    • cgen_call は一般的な関数呼び出しのコード生成を担当します。
    • n->left->method = 1; という行が gins(ACALL, N, n->left); の直前に追加されました。これは、呼び出し対象のノード(n->left)がメソッドであることを明示的にマークしています。この method フラグは、go.hNode 構造体に追加されたものです。
  • naddr での method フラグの利用 (src/cmd/6g/gsubr.c):
    • naddr 関数は、ノードのアドレスを解決する際に使用されます。
    • if(n->method) ブロックが追加され、ノードがメソッドである場合に、その型(n->type)のパッケージ情報(n->type->sym->opackage)を使用してシンボルを pkglookup するロジックが追加されました。これにより、インポートされたパッケージのメソッドが正しく解決されるようになります。

3. スタックと引数管理の調整 (src/cmd/6g/gen.c, src/cmd/6g/gg.h, src/cmd/gc/go.h, src/cmd/gc/go.y)

  • maxargstksize の初期化位置の変更:
    • src/cmd/6g/gen.ccompile 関数から maxarg = 0;stksize = 0; の初期化が削除されました。
    • src/cmd/6g/gg.h から maxargstksizeEXTERN 宣言が削除されました。
    • これらの変数は src/cmd/gc/go.h に移動し、EXTERN long maxarg;EXTERN long stksize; として再宣言されました。
    • そして、src/cmd/gc/go.yxfndcl (関数宣言) ルール内で、{ maxarg = 0; stksize = 0; } というアクションが追加されました。
    • この変更は、maxarg (最大引数サイズ) と stksize (スタックサイズ) の初期化を、関数全体のコンパイル開始時ではなく、個々の関数宣言がパーサーによって処理される直前に行うように変更したことを意味します。これにより、各関数のコンパイルが独立して行われ、これらの変数が適切にリセットされることが保証されます。

4. デバッグ出力の制御強化 (src/cmd/gc/dcl.c)

  • dflag() 関数の導入:
    • dcl.cdflag() という新しい関数が導入されました。
    • この関数は、debug['d'] (一般的なデバッグフラグ) が設定されているかどうかをチェックし、さらに debug['y'] (Yaccデバッグフラグ) や inimportsys (インポートシステム内かどうか) の状態に応じて、デバッグ出力を有効にするかどうかを決定します。
    • 具体的には、inimportsystrue の場合(つまり、コンパイラが内部的なインポート処理を行っている場合)は、debug['d'] が設定されていてもデバッグ出力を抑制します。これは、内部インポート処理中に大量のデバッグ出力が発生するのを防ぐためと考えられます。
  • debug['d'] の置き換え:
    • dcl.c 内の既存の if(debug['d']) の条件がすべて if(dflag()) に置き換えられました。
    • これにより、デバッグ出力の制御が一元化され、より柔軟な条件でデバッグ情報を表示できるようになりました。

5. 式の再順序付け (reorder1) の改善 (src/cmd/gc/walk.c)

  • reorder1 関数の大幅な変更:
    • reorder1 関数は、式の評価順序を再編成し、最適化を行うための重要なパスです。特に、副作用を持つ関数呼び出しを含む複雑な式を扱う際に、評価順序を決定します。
    • 変更前は、yyerror("reorder1: too many function calls evaluating parameters"); というエラーメッセージがありましたが、これが削除されました。これは、新しいロジックがより多くの関数呼び出しを適切に処理できるようになったことを示唆しています。
    • pass2 ロジックの再構築:
      • 新しい pass2 ロジックは、関数呼び出しを一時変数に割り当てることで、評価順序を制御します。
      • g は一時変数に割り当てられた関数呼び出しのリスト、f はスタックに割り当てられた単一の関数呼び出し、r は関数呼び出しではないノードや一時変数に割り当てられたノードのリストを表します。
      • if(l->ullman < UINF): Ullman数が無限大(UINF)未満のノード(つまり、副作用がないか、評価が単純なノード)は、直接 r リストに追加されます。
      • if(f == N): f がまだ設定されていない場合、現在のノード lf に設定します。
      • 関数呼び出しの一時変数への割り当て:
        • a = nod(OXXX, N, N); tempname(a, l->right->type); で一時変数を表すノード a を作成します。
        • a = nod(OAS, a, l->right); で、関数呼び出しの結果(l->right)をこの一時変数 a に代入する OAS (assignment) ノードを作成します。
        • この代入ノード ag リスト(一時変数に割り当てられた関数呼び出しのリスト)に追加されます。
        • 元の関数呼び出しノード l の右辺(呼び出し結果)は、この一時変数 a->left に置き換えられます。
      • この複雑なロジックは、複数の関数呼び出しが引数として渡される場合などに、それらの評価順序を明確にし、中間結果を一時変数に格納することで、コード生成を簡素化し、潜在的なバグを防ぐことを目的としています。

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

src/cmd/gc/export.c (インポート処理の改善)

--- a/src/cmd/gc/export.c
+++ b/src/cmd/gc/export.c
@@ -266,18 +266,34 @@ dumpexport(void)
 /*
  * ******* import *******
  */
+Sym*
+getimportsym(Node *ss)
+{
+	char *pkg;
+	Sym *s;
+
+	if(ss->op != OIMPORT)
+		fatal("getimportsym: oops1 %N\\n", ss);
+
+	pkg = ss->psym->name;
+	if(pkgmyname != S)
+		pkg = pkgmyname->name;
+
+	s = pkglookup(ss->sym->name, pkg);
+
+	/* botch - need some diagnostic checking for the following assignment */
+	s->opackage = ss->osym->name;
+	return s;
+}
+
 Type*
 importlooktype(Node *n)
 {
 	Sym *s;
 
-\tif(n->op != OIMPORT)\n-\t\tfatal("importlooktype: oops1 %N\\n", n);\n-\n-\ts = pkglookup(n->sym->name, n->psym->name);\n+\ts = getimportsym(n);\
 	if(s->otype == T)
 		fatal("importlooktype: oops2 %S\\n", s);
-\n \treturn s->otype;\
 }
 
 Type*
@@ -367,31 +383,22 @@ importfuncnam(Type *t)
 	}
 }
 
-Sym*
-getimportsym(Node *ss)
-{
-	char *pkg;
-	Sym *s;
-
-\tpkg = ss->psym->name;\n-\tif(pkgmyname != S)\n-\t\tpkg = pkgmyname->name;\n-\n-\ts = pkglookup(ss->sym->name, pkg);\n-\t/* botch - need some diagnostic checking for the following assignment */\n-\ts->opackage = ss->osym->name;\n-\treturn s;\
-}
-\n void
+void
 importaddtyp(Node *ss, Type *t)
 {
 	Sym *s;
 
 	s = getimportsym(ss);
-\tif(s->otype == T || !eqtype(t, s->otype, 0)) {\n+\tif(s->otype == T) {\
 \t\taddtyp(newtype(s), t, PEXTERN);\
+\t\treturn;\
+\t}\
+\tif(!eqtype(t, s->otype, 0)) {\
+\t\tprint("redeclaring %S %lT => %lT\\n", s, s->otype, t);\
+\t\taddtyp(newtype(s), t, PEXTERN);\
+\t\treturn;\
 	}\
+\tprint("sametype %S %lT => %lT\\n", s, s->otype, t);\
 }
 
 /*

src/cmd/gc/go.h (Node構造体とグローバル変数)

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -137,6 +137,7 @@ struct	Node
 	uchar	trecur;		// to detect loops
 	uchar	etype;		// op for OASOP, etype for OTYPE, exclam for export
 	uchar	class;		// PPARAM, PAUTO, PEXTERN, PSTATIC
+	uchar	method;		// OCALLMETH name
 	long	vargen;		// unique name for OTYPE/ONAME
 	ulong	lineno;
 	vlong	xoffset;
@@ -353,12 +354,15 @@ EXTERN	Dcl*		externdcl;
 EXTERN	Dcl*		exportlist;
 EXTERN	int		dclcontext;	// PEXTERN/PAUTO
 EXTERN	int		importflag;
+EXTERN	int		inimportsys;
 
 EXTERN	Node*		booltrue;
 EXTERN	Node*		boolfalse;
 EXTERN	ulong		iota;
 EXTERN	long		vargen;
 EXTERN	long		exportgen;
+EXTERN	long		maxarg;
+EXTERN	long		stksize;
 
 EXTERN	Node*		retnil;
 EXTERN	Node*		fskel;

src/cmd/gc/walk.c (式の再順序付け)

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -1301,7 +1301,7 @@ Node*
 reorder1(Node *n)
 {
 	Iter save;
-\tNode *l, *r, *f;\
+\tNode *l, *r, *f, *a, *g;\
 	int c, t;\
 
 	/*
@@ -1321,10 +1321,6 @@ loop1:
 \tif(l == N) {\
 \t\tif(c == 0 || t == 1)\
 \t\t\treturn n;\
-\t\tif(c > 1) {\
-\t\t\tyyerror("reorder1: too many function calls evaluating parameters");\
-\t\t\treturn n;\
-\t\t}\
 \t\tgoto pass2;\
 \t}\
 \tif(l->op == OLIST)\
@@ -1338,23 +1334,50 @@ loop1:
 
 pass2:
 \tl = listfirst(&save, &n);\
-\tr = N;\t// rest\
-\tf = N;\t// fncall\
+\tg = N;\t// fncalls assigned to tempnames\
+\tf = N;\t// one fncall assigned to stack\
+\tr = N;\t// non fncalls and tempnames assigned to stack\
 \n loop2:\
 \tif(l == N) {\
-\t\tr = nod(OLIST, f, r);\
 \t\tr = rev(r);\
+\t\tg = rev(g);\
+\t\tif(g != N)\
+\t\t\tf = nod(OLIST, g, f);\
+\t\tr = nod(OLIST, f, r);\
 \t\treturn r;\
 \t}\
-\tif(l->ullman >= UINF)\
+\tif(l->ullman < UINF) {\
+\t\tif(r == N)\
+\t\t\tr = l;\
+\t\telse\
+\t\t\tr = nod(OLIST, l, r);\
+\t\tgoto more;\
+\t}\
+\tif(f == N) {\
 \t\tf = l;\
+\t\tgoto more;\
+\t}\
+\n\t// make assignment of fncall to tempname\
+\ta = nod(OXXX, N, N);\
+\ttempname(a, l->right->type);\
+\ta = nod(OAS, a, l->right);\
+\n\tif(g == N)\
+\t\tg = a;\
 \telse\
+\t\tg = nod(OLIST, a, g);\
+\n\t// put normal arg assignment on list\
+\t// with fncall replaced by tempname\n+\tl->right = a->left;\
 \tif(r == N)\
 \t\tr = l;\
 \telse\
 \t\tr = nod(OLIST, l, r);\
 \n+more:\
 \tl = listnext(&save);\
 \tgoto loop2;\
 }\

コアとなるコードの解説

src/cmd/gc/export.c の変更

  • getimportsym の再定義と利用:
    • この関数は、インポートされたシンボルをシンボルテーブルから検索し、そのシンボルがどのパッケージから来たのかという情報(s->opackage = ss->osym->name;)を付与する役割を担います。これにより、コンパイラはシンボルの完全な識別子(パッケージ名とシンボル名)を保持し、名前の衝突解決やデバッグに役立てることができます。
    • importlooktype から直接 pkglookup を呼び出す代わりに getimportsym を呼び出すように変更されたことで、シンボル取得ロジックが一元化され、コードの重複が排除されました。
  • importaddtyp の型再宣言ロジック:
    • この関数は、インポートされた型をシンボルテーブルに追加する際に、既存の型との整合性をチェックします。
    • 変更前は、既存の型が不明 (T) であるか、または新しい型と既存の型が異なる場合にのみ addtyp を呼び出していました。
    • 変更後は、まず既存の型が不明な場合に addtyp を呼び出し、その後、型が異なる場合に「redeclaring」というメッセージを出力して addtyp を呼び出します。最後に、型が同じ場合でも「sametype」というメッセージを出力するように変更されました。
    • これにより、インポート時の型の整合性チェックがより厳密になり、開発者が型の不一致や冗長な宣言をデバッグしやすくなりました。

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

  • Node 構造体への method フィールド追加:
    • uchar method; の追加は、ASTノードがメソッド呼び出しを表す場合に、そのノードに特別なマークを付けることを可能にします。これにより、コンパイラの他の部分(特にコード生成バックエンド)が、通常の関数呼び出しとメソッド呼び出しを区別し、それぞれに適切な処理を適用できるようになります。Go言語のメソッドは、レシーバ(メソッドが呼び出されるオブジェクト)を暗黙の第一引数として渡すなど、通常の関数とは異なる呼び出し規約を持つため、この区別は重要です。
  • maxarg, stksize, inimportsysEXTERN 宣言:
    • maxargstksizego.h に移動し、EXTERN 宣言されたことで、これらの変数がコンパイラ全体で共有されるグローバル変数であることが明確になりました。これにより、異なるコンパイルフェーズやファイル間でこれらのスタック関連の情報を共有・更新できるようになります。
    • inimportsys フラグは、コンパイラが内部的なインポート処理を行っているかどうかを示す状態変数です。これにより、デバッグ出力の抑制など、特定のコンテキストでのコンパイラの挙動を調整することが可能になります。

src/cmd/gc/walk.c の変更

  • reorder1 関数の pass2 ロジックの再構築:
    • この変更は、Goコンパイラの最適化フェーズにおける重要な改善です。reorder1 は、式の評価順序を決定し、特に副作用を持つ関数呼び出しを含む複雑な式を扱う際に、その評価順序を最適化します。
    • 以前のバージョンでは、引数評価における関数呼び出しが多すぎる場合にエラーを発生させていましたが、このエラーが削除されました。これは、新しいロジックがより多くの関数呼び出しを適切に処理できるようになったことを示唆しています。
    • 新しい pass2 ロジックは、関数呼び出しの結果を一時変数に割り当てることで、評価順序を明確に制御します。
      • l->ullman < UINF の条件は、ノードが副作用を持たないか、評価が単純な場合に真となります。これらのノードは直接 r リスト(非関数呼び出しおよび一時変数に割り当てられたノードのリスト)に追加されます。
      • 副作用を持つ関数呼び出し(l->ullman >= UINF)の場合、その結果を格納するための一時変数(a)が作成され、関数呼び出しの結果がその一時変数に代入されます(a = nod(OAS, a, l->right);)。
      • この代入ノードは g リスト(一時変数に割り当てられた関数呼び出しのリスト)に追加され、元の関数呼び出しノードの右辺(結果)は、この一時変数に置き換えられます(l->right = a->left;)。
    • このメカニズムにより、コンパイラは複雑な式をより予測可能で効率的な方法で評価できるようになり、コード生成の正確性と堅牢性が向上します。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (GitHub): https://github.com/golang/go
  • Go言語の初期のコミット履歴 (SVNからGitへの移行前の情報を含む)
  • コンパイラ設計に関する一般的な知識 (AST、シンボルテーブル、コード生成、最適化など)
  • Ullman Numberに関するコンパイラ最適化の文献
  • Yacc/Lexに関するドキュメント