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

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

このコミットは、Goコンパイラ(gcおよび6g)とランタイムにおける複数の重要な変更を含んでいます。主な目的は、fmtツール(おそらくgo fmtまたはfmtパッケージの動作)を正しく機能させるためのデバッグであり、そのために式の評価順序の改善と浮動小数点数演算のサポート強化が行われています。

具体的には、以下のファイルが変更されています。

  • src/cmd/6g/gen.c: 6gコンパイラのコード生成部分。主に代入演算(cgen_asop)におけるレジスタ割り当てと命令生成ロジックが修正されています。
  • src/cmd/gc/go.h: Goコンパイラのヘッダーファイル。式の再順序付け(reordering)に関連する新しい関数プロトタイプ(reorder1からreorder4)が追加され、古いreorder関数が削除されています。また、浮動小数点数関連の組み込み関数(frexp, ldexp, modf)の宣言が追加されています。
  • src/cmd/gc/sys.go: Goコンパイラのシステム関連のGoコード。frexp, ldexp, modfといった浮動小数点数操作関数がエクスポートされるように変更されています。
  • src/cmd/gc/sysimport.c: Goコンパイラのシステムインポート定義。内部的な型IDが更新されており、これは新しい関数や型の追加に伴う自動生成または手動更新の結果と考えられます。
  • src/cmd/gc/walk.c: GoコンパイラのAST(抽象構文木)ウォーク処理。式の評価順序を制御するreorder関連のロジックが大幅に修正・拡張され、reorder1からreorder4という複数の関数に分割されています。関数呼び出しや多値代入の処理も影響を受けています。
  • src/runtime/runtime.c: GoランタイムのC言語実装。IEEE 754浮動小数点数標準に基づいたfrexp, ldexp, modfといった数学関数の具体的な実装が追加されています。また、NaN(非数)やInf(無限大)の判定・生成に関するヘルパー関数も含まれています。

コミット

commit 0b3093f0a5a71749c44835a9e3703853238d8d4d
Author: Ken Thompson <ken@golang.org>
Date:   Tue Jun 10 21:29:57 2008 -0700

    debugging to get fmt to run
    
    SVN=122046

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

https://github.com/golang/go/commit/0b3093f0a5a71749c44835a9e3703853238d8d4d

元コミット内容

debugging to get fmt to run

SVN=122046

変更の背景

このコミットの背景には、Go言語の初期開発段階におけるコンパイラとツールチェインの成熟があります。コミットメッセージ「debugging to get fmt to run」が示すように、Goのコードフォーマッタであるgo fmt(またはfmtパッケージ自体)が正しく動作しないという問題に直面していたと考えられます。

go fmtのようなツールは、コードの構文解析結果(AST)に基づいてコードを整形します。もしコンパイラが生成するASTが不正確であったり、式の評価順序に関するセマンティクスが曖昧であったりすると、go fmtが期待通りに動作しない可能性があります。特に、多値の関数呼び出しや複雑な代入式、浮動小数点数演算などは、コンパイラが正しく処理しなければならない複雑なケースです。

このコミットでは、主に以下の2つの側面からこの問題に対処しています。

  1. 式の評価順序の厳密化と最適化: walk.cにおけるreorder関数の分割と改善は、コンパイラがASTを走査し、コード生成の準備をする際に、式の副作用や依存関係を正しく考慮し、最適な評価順序を決定するためのものです。これにより、fmtツールがコードの構造を正確に理解し、一貫したフォーマットを適用できるようになります。
  2. 浮動小数点数演算の正確なサポート: runtime.cにおけるfrexp, ldexp, modfといった標準的な浮動小数点数関数の追加は、Goプログラムが浮動小数点数をより正確かつ効率的に扱えるようにするためのものです。これらの関数は、科学技術計算や数値解析において不可欠であり、Go言語が幅広い用途で利用されるための基盤を強化します。fmtツールが浮動小数点数のリテラルや計算結果を正しく表示するためにも、これらの基盤が重要であった可能性があります。

Go言語は、その設計思想として「シンプルさ」と「実用性」を重視しており、初期段階から堅牢なツールチェインの構築に力を入れていました。go fmtはその象徴的なツールの一つであり、その安定稼働は言語の普及にとって不可欠でした。このコミットは、その目標達成に向けた重要な一歩と言えます。

前提知識の解説

Goコンパイラ (gc, 6g)

Go言語の公式コンパイラは、当初はgc(Go Compiler)と呼ばれていました。これは、Go言語自体で書かれたコンパイラであり、クロスコンパイルをサポートしています。6gは、gcコンパイラの一部で、AMD64(64ビットIntel/AMDアーキテクチャ)向けのコードを生成するバックエンドコンパイラを指します。Goコンパイラは、ソースコードを解析してAST(抽象構文木)を構築し、それを中間表現に変換し、最終的にターゲットアーキテクチャの機械語にコンパイルします。

AST (Abstract Syntax Tree)

ASTは、ソースコードの構文構造を木構造で表現したものです。コンパイラは、ソースコードを字句解析(トークン化)し、構文解析(パース)してASTを構築します。ASTは、その後の意味解析、最適化、コード生成の各段階で利用されます。このコミットで変更されているwalk.cは、ASTを走査(ウォーク)し、意味解析や中間表現への変換を行う部分です。

式の評価順序とUllman数

プログラミング言語において、複雑な式(例: a + b * c)は、その構成要素が特定の順序で評価される必要があります。この評価順序は、式の意味論に影響を与え、特に副作用を持つ操作(例: 関数呼び出し、代入)が含まれる場合には重要になります。

**Ullman数(Ullman Number)**は、コンパイラ最適化の文脈で用いられる概念で、式のノード(部分式)を評価するために必要なレジスタの最小数を示すヒューリスティックな値です。Ullman数は、式の木構造を下から上に計算され、レジスタの競合を最小限に抑えつつ、効率的なコードを生成するためのスケジューリングに利用されます。

  • 葉ノード(定数、変数など)のUllman数は1(レジスタが1つ必要)。
  • 二項演算子ノードの場合、左右の子ノードのUllman数を比較し、大きい方に1を加える(例: max(U(left), U(right)) + 1)。ただし、左右の子ノードのUllman数が同じ場合は、U(left) + 1となる。
  • Ullman数が高いノードは、より多くのレジスタを必要とするか、より複雑な計算を伴う可能性があり、評価順序を決定する上で考慮されます。

このコミットでは、ullmanというフィールドがコード中に見られ、式の評価順序を決定する際にUllman数が考慮されていることが示唆されます。特に、関数呼び出し(l->ullman >= UINF)のように副作用が大きく、評価コストが高い式は、特別な扱いを受けることがあります。UINF(おそらく"Ullman Infinity")は、非常に高いUllman数を示す定数で、関数呼び出しのような複雑な操作に割り当てられることがあります。

IEEE 754 浮動小数点数標準

IEEE 754は、浮動小数点数演算に関する国際標準です。コンピュータが浮動小数点数をどのように表現し、どのように演算するかを定めています。この標準は、浮動小数点数の精度、範囲、特殊な値(無限大 Inf、非数 NaN)の扱いなどを定義しており、異なるシステム間での浮動小数点数演算の互換性を保証します。

Go言語のfloat64型は、IEEE 754の倍精度浮動小数点数(64ビット)に準拠しています。runtime.cで追加されたfrexp, ldexp, modfといった関数は、この標準に則って浮動小数点数を操作するための基本的なビルディングブロックです。

  • frexp(x): 浮動小数点数 x を、正規化された仮数部 m と指数部 e に分解します。x = m * 2^e の形式で表現され、0.5 <= |m| < 1.0 となります。
  • ldexp(m, e): 仮数部 m と指数部 e から浮動小数点数を再構築します。m * 2^e を計算します。
  • modf(x): 浮動小数点数 x を、整数部と小数部に分解します。

これらの関数は、浮動小数点数の内部表現を直接操作することなく、数学的な分解・合成を行うために利用されます。

技術的詳細

このコミットの技術的詳細は、Goコンパイラのフロントエンド(gc)とバックエンド(6g)、そしてランタイム(runtime)にまたがっています。

src/cmd/6g/gen.c の変更

cgen_asop関数は、代入演算(=)のコードを生成する役割を担っています。変更前は、右辺(nr)のUllman数が左辺(nl)より大きい場合にfatal("gcgen_asopen")というエラーを発生させていました。これは、評価順序に関する制約があったことを示唆しています。

変更後、特にnl->addable(左辺がアドレス可能、つまりメモリ上の場所を指す場合)のケースで、レジスタ割り当てと命令生成のロジックがより複雑になっています。

// BOTCH make special case for DIVQ

a = optoas(op, nl->type);
if(nl->addable) {
    regalloc(&n2, nr->type, N); // 右辺のレジスタを割り当て
    cgen(nr, &n2);              // 右辺のコードを生成
    regalloc(&n1, nl->type, N); // 左辺のレジスタを割り当て
    cgen(nl, &n1);              // 左辺のコードを生成
    gins(a, &n2, &n1);          // 命令を生成 (a, n2, n1)
    gmove(&n1, nl);             // n1の内容をnlに移動
    regfree(&n1);               // n1を解放
    regfree(&n2);               // n2を解放
    return;
}

if(nr->ullman > nl->ullman) {
    fatal("gcgen_asopen");
}

この変更は、代入演算の左右のオペランドの評価順序とレジスタ管理をより柔軟かつ正確に行うためのものです。特に、右辺と左辺がそれぞれ複雑な式である場合に、レジスタの競合を避けつつ効率的なコードを生成しようとしています。BOTCH make special case for DIVQというコメントは、DIVQ(おそらく64ビット除算命令)のような特定の命令が特別な扱いを必要とすることを示しており、この時点でのコード生成の未熟さや、特定のアーキテクチャ固有の最適化の必要性を示唆しています。

src/cmd/gc/go.h および src/cmd/gc/sys.go の変更

go.hでは、reorder関数が削除され、代わりにreorder1からreorder4までの複数の関数が追加されています。これは、式の再順序付けロジックがより細分化され、特定のコンテキスト(関数呼び出しの引数、多値代入など)に応じて異なる再順序付け戦略が適用されるようになったことを示しています。

また、sys.goでは、frexp, ldexp, modfといった浮動小数点数操作関数がGoの組み込み関数としてエクスポートされるように宣言されています。これにより、Goプログラムからこれらの低レベルな浮動小数点数操作を利用できるようになります。

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

このファイルは、Goコンパイラが内部的に使用するシステム関数の型定義を保持しています。変更内容は、_oXXX_iXXXといった内部的な型IDの数値が更新されていることです。これは、新しい関数(frexp, ldexp, modf)の追加や、既存の型の定義変更に伴って、コンパイラが自動的に生成する内部IDが再割り当てされた結果と考えられます。これらのIDはコンパイラの内部実装に依存するため、直接的な意味を持つものではありませんが、システム定義が更新されたことを示しています。

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

walk.cは、このコミットで最も大きく変更されたファイルです。主な変更点は以下の通りです。

  1. reorder関数の分割と適用:

    • 以前は単一のreorder関数が使われていましたが、これがreorder1, reorder2, reorder3, reorder4に分割され、それぞれのコンテキストで呼び出されるようになりました。
    • OCALLINTER, OCALL, OCALLMETH(関数呼び出し)のケースではreorder1が使われています。
    • OAS(代入)のケースで、多値代入(a,b = f())のような場合にreorder2が使われています。
    • ascompateea,b = c,dのような同時代入)のケースではreorder3が使われています。
    • ORETURN(return文)のケースではreorder4が使われています。
  2. ascompat関数のrev(nn)の導入:

    • ascompatee, ascompatet, ascompatteといった、複数の式や型リスト間の互換性をチェックし、代入ノードを生成する関数で、return nn;return rev(nn);に変更されています。
    • rev関数は、おそらくノードのリストを逆順にする関数です。これは、コンパイラがASTを構築する際に、評価順序の都合上、ノードを逆順に連結する必要がある場合に用いられます。例えば、OLISTノードがnod(OLIST, a, nn)のように構築される場合、新しい要素aがリストの先頭に追加されるため、最終的なリストは逆順になります。これを正しい評価順序に戻すためにrevが使われていると考えられます。
  3. 新しいreorder関数の実装:

    • reorder1(Node *n): 関数呼び出しの引数を評価する際の再順序付けを担当します。特に、引数の中に副作用を持つ関数呼び出し(l->ullman >= UINF)が複数ある場合に、エラーを発生させるロジックが含まれています。これは、Goの関数呼び出しの引数評価順序が未定義である(または特定の順序に依存しない)ことを前提としつつ、副作用を持つ引数の評価を制御しようとしていることを示唆します。もし副作用を持つ関数呼び出しが1つだけなら、それを最初に評価し、残りの引数を後で評価するようなロジックになっています。
    • reorder2(Node *n): 多値代入(a,b = f())の戻り値を処理する際の再順序付けを担当します。この関数は、戻り値の式の中に副作用を持つ関数呼び出しがないことを保証しようとしています。もしあれば、戻り値が上書きされる可能性があるため、エラーとなります。
    • reorder3(Node *n): 同時代入(a,b = c,d)の再順序付けを担当します。コメントには「earlier lvalueの後の使用があるかもしれない」とあり、これは代入の左辺が後の式で再利用される可能性があるため、評価順序が重要であることを示唆しています。ただし、この関数自体はreturn n;と、特に何もしない実装になっています。これは、この時点では特別な再順序付けが不要であるか、あるいは将来的な拡張ポイントとして残されていることを意味します。
    • reorder4(Node *n): return文の式を処理する際の再順序付けを担当します。コメントには「問題はないかもしれない」とあり、これもreorder3と同様に、この時点では特別な再順序付けが不要であるか、将来的な拡張ポイントとして残されていることを示唆しています。

これらの変更は、Goコンパイラが式の評価順序をより厳密に制御し、特に副作用を持つ式や多値の操作において、予測可能で正しいコードを生成するためのものです。これは、go fmtのようなツールがASTを正確に解釈し、一貫したフォーマットを適用するために不可欠な基盤となります。

src/runtime/runtime.c の変更

このファイルでは、IEEE 754浮動小数点数標準に基づいた低レベルの浮動小数点数操作関数がC言語で実装されています。

  • isInf, NaN, isNaN, Inf: これらの関数は、浮動小数点数が無限大(Inf)であるか、非数(NaN)であるかを判定したり、これらの特殊な値を生成したりするためのヘルパー関数です。IEEE 754では、特定のビットパターンがこれらの特殊な値を表すように定義されています。

    • uvnan, uvinf, uvneginfといったuint64型の定数は、それぞれの特殊な浮動小数点数値を表すビットパターンです。
    • isInfは、与えられたfloat64値が無限大であるかを判定します。
    • NaNは、非数(Not a Number)を生成します。
    • isNaNは、与えられたfloat64値が非数であるかを判定します。
    • Infは、正または負の無限大を生成します。
  • frexp, ldexp, modf: これらの関数は、標準Cライブラリにも存在する数学関数であり、浮動小数点数を仮数部と指数部に分解したり、再構築したり、整数部と小数部に分解したりする機能を提供します。

    • frexp(d, ep): dm * 2^(*ep)の形式に分解し、mを返します。
    • ldexp(d, e): d * 2^eを計算して返します。
    • modf(d, ip): dを整数部と小数部に分解し、小数部を返します。整数部は*ipに格納されます。
  • sys_frexp, sys_ldexp, sys_modf: これらの関数は、Goランタイムから呼び出されるC言語のラッパー関数です。Goの関数シグネチャに合わせて引数を受け取り、上記のC言語実装のfrexp, ldexp, modfを呼び出しています。FLUSHマクロは、おそらくレジスタにキャッシュされた値をメモリに書き出すためのもので、関数の戻り値が正しく伝播されるようにするためのものです。

これらの浮動小数点数関数の追加は、Go言語が数値計算においてより強力な機能を提供し、IEEE 754標準に準拠した正確な浮動小数点数演算をサポートするための重要なステップです。

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

src/cmd/6g/gen.c における代入演算のコード生成

--- a/src/cmd/6g/gen.c
+++ b/src/cmd/6g/gen.c
@@ -692,19 +692,26 @@ cgen_asop(Node *nl, Node *nr, int op)
 		fatal("cgen_asop both sides call");
 	}
 
-	a = optoas(op, nl->type);
-	if(nr->ullman > nl->ullman) {
-		fatal("gcgen_asopen");
-	}
+// BOTCH make special case for DIVQ
 
-	regalloc(&n1, nl->type, N);
+	a = optoas(op, nl->type);
 	if(nl->addable) {
-		cgen(nr, &n1);
-		gins(a, &n1, nl);
+		regalloc(&n2, nr->type, N);
+		cgen(nr, &n2);
+		regalloc(&n1, nl->type, N);
+		cgen(nl, &n1);
+		gins(a, &n2, &n1);
+		gmove(&n1, nl);
 		regfree(&n1);
+		regfree(&n2);
 		return;
 	}
 
+	if(nr->ullman > nl->ullman) {
+		fatal("gcgen_asopen");
+	}
+
+	regalloc(&n1, nl->type, N);
 	igen(nl, &n2, N);
 	cgen(nr, &n1);
 	gins(a, &n1, &n2);

src/cmd/gc/go.h におけるreorder関数の変更と浮動小数点数関数の追加

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -529,8 +529,11 @@ Node*	nodpanic(long);
 Node*	newcompat(Node*);
 Node*	stringop(Node*);
 Node*	convas(Node*);
-Node*	reorder(Node*);
 void	arrayconv(Type*, Node*);
+Node*	reorder1(Node*);
+Node*	reorder2(Node*);
+Node*	reorder3(Node*);
+Node*	reorder4(Node*);
 
 /*
  *	const.c
--- a/src/cmd/gc/sys.go
+++ b/src/cmd/gc/sys.go
@@ -23,6 +23,10 @@ func	intstring(int64) string;
 func	byteastring(*byte, int32) string;
 func	mkiface(*byte, *byte, *struct{}) interface{};
 
+func	frexp(float64) (int32, float64);	// break fp into exp,fract
+func	ldexp(int32, float64) float64;		// make fp from exp,fract
+func	modf(float64) (float64, float64);	// break fp into double.double
+
 export
 	mal
 	breakpoint
@@ -41,4 +45,8 @@ export
 	intstring
 	byteastring
 	mkiface
+
+	frexp
+	ldexp
+	modf
 	;

src/cmd/gc/walk.c におけるreorder関数の呼び出し箇所の変更と新規実装

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -173,12 +170,12 @@ loop:
 
 		case OCALLINTER:
 			l = ascompatte(n->op, getinarg(t), &n->right, 0);
-			n->right = reorder(l);
+			n->right = reorder1(l);
 			break;
 
 		case OCALL:
 			l = ascompatte(n->op, getinarg(t), &n->right, 0);
-			n->right = reorder(l);
+			n->right = reorder1(l);
 			break;
 
 		case OCALLMETH:
@@ -187,7 +184,7 @@ loop:
 			r = ascompatte(n->op, getthis(t), &n->left->left, 0);
 			if(l != N)
 				r = nod(OLIST, r, l);
-			n->right = reorder(r);
+			n->right = reorder1(r);
 			break;
 		}
 		goto ret;
@@ -204,12 +201,12 @@ loop:
 		goto ret;
 
 		if(r->op == OCALL && l->op == OLIST) {
-			// botch callmulti - need to do more
 			walktype(l, 0);
 			walktype(r, 0);
 			l = ascompatet(n->op, &n->left, &r->type, 0);
-			if(l != N && l->op == OAS)
-				*n = *reorder(l);
+			if(l != N) {
+				*n = *nod(OLIST, r, reorder2(l));
+			}
 			goto ret;
 		}
 
@@ -217,7 +214,7 @@ loop:
 		walktype(r, 0);
 		l = ascompatee(n->op, &n->left, &n->right);
 		if(l != N)
-			*n = *reorder(l);
+			*n = *reorder3(l);
 		goto ret;
 
 	case OBREAK:
@@ -296,7 +293,7 @@ loop:
 		walktype(n->left, 0);
 		l = ascompatte(n->op, getoutarg(curfn->type), &n->left, 1);
 		if(l != N)
-			n->left = reorder(l);
+			n->left = reorder4(l);
 		goto ret;
 
 	case ONOT:
@@ -842,7 +839,7 @@ loop:
 	if(l == N || r == N) {
 		if(l != r)
 			yyerror("error in shape across assignment");
-		return nn;
+		return rev(nn);
 	}
 
 	convlit(r, l->type);
@@ -856,18 +853,13 @@ loop:
 	if(nn == N)
 		nn = a;
 	else
-		nn = nod(OLIST, nn, a);
+		nn = nod(OLIST, a, nn);
 
 	l = listnext(&savel);
 	r = listnext(&saver);
 	goto loop;
 }
 
-/*
- * check assign type list to
- * a expression list. called in
- *\texpr-list = func()
- */
 Node*
 ascompatet(int op, Node **nl, Type **nr, int fp)
 {
@@ -883,7 +865,7 @@ loop:
 	if(l == N || r == T) {
 		if(l != N || r != T)
 			yyerror("error in shape across assignment");
-		return nn;
+		return rev(nn);
 	}
 
 	if(!ascompat(l->type, r->type)) {
@@ -896,7 +878,7 @@ loop:
 	if(nn == N)
 		nn = a;
 	else
-		nn = nod(OLIST, nn, a);
+		nn = nod(OLIST, a, nn);
 
 	l = listnext(&savel);
 	r = structnext(&saver);
@@ -904,12 +886,6 @@ loop:
 	goto loop;
 }
 
-/*
- * check assign expression list to
- * a type list. called in
- *\treturn expr-list
- *\tfunc(expr-list)
- */
 Node*
 ascompatte(int op, Type **nl, Node **nr, int fp)
 {
@@ -925,7 +901,7 @@ loop:
 	if(l == T || r == N) {
 		if(l != T || r != N)
 			yyerror("error in shape across assignment");
-		return nn;
+		return rev(nn);
 	}
 
 	convlit(r, l->type);
@@ -939,7 +915,7 @@ loop:
 	if(nn == N)
 		nn = a;
 	else
-		nn = nod(OLIST, nn, a);
+		nn = nod(OLIST, a, nn);
 
 	l = structnext(&savel);
 	r = listnext(&saver);
@@ -1277,12 +1268,6 @@ ret:
 	return n;
 }
 
-Node*
-reorder(Node *n)
-{
-	return n;
-}
-
 void
 arrayconv(Type *t, Node *n)
 {
@@ -1310,3 +1295,125 @@ loop:
 	l = listnext(&save);
 	goto loop;
 }
+
+Node*
+reorder1(Node *n)
+{
+	Iter save;
+	Node *l, *r, *f;
+	int c, t;
+
+	/*
+	 * from ascompat[te]
+	 * evaluating actual function arguments.
+	 *	f(a,b)
+	 * if there is exactly one function expr,
+	 * then it is done first. otherwise must
+	 * make temp variables
+	 */
+
+	l = listfirst(&save, &n);
+	c = 0;	// function calls
+	t = 0;	// total parameters
+
+loop1:
+	if(l == N) {
+		if(c == 0 || t == 1)
+			return n;
+		if(c > 1) {
+			yyerror("reorder1: too many funcation calls evaluating parameters");
+			return n;
+		}
+		goto pass2;
+	}
+	if(l->op == OLIST)
+		fatal("reorder1 OLIST");
+
+	t++;
+	if(l->ullman >= UINF)
+		c++;
+	l = listnext(&save);
+	goto loop1;
+
+pass2:
+	l = listfirst(&save, &n);
+	f = N;	// isolated function call
+	r = N;	// rest of them
+
+loop2:
+	if(l == N) {
+		if(r == N || f == N)
+			fatal("reorder1 not nil 1");
+		r = nod(OLIST, f, r);
+		return rev(r);
+	}
+	if(l->ullman >= UINF) {
+		if(f != N)
+			fatal("reorder1 not nil 2");
+		f = l;
+	} else
+	if(r == N)
+		r = l;
+	else
+		r = nod(OLIST, l, r);
+
+	l = listnext(&save);
+	goto loop2;
+}
+
+Node*
+reorder2(Node *n)
+{
+	Iter save;
+	Node *l;
+	int c;
+
+	/*
+	 * from ascompat[et]
+	 *	a,b = f()
+	 * return of a multi.
+	 * there can be no function calls at all,
+	 * or they will over-write the return values.
+	 */
+
+	l = listfirst(&save, &n);
+	c = 0;
+
+loop1:
+	if(l == N) {
+		if(c > 0)
+			yyerror("reorder2: too many funcation calls evaluating parameters");
+		return n;
+	}
+	if(l->op == OLIST)
+		fatal("reorder2 OLIST");
+
+	if(l->ullman >= UINF)
+		c++;
+	l = listnext(&save);
+	goto loop1;
+}
+
+Node*
+reorder3(Node *n)
+{
+	/*
+	 * from ascompat[ee]
+	 *	a,b = c,d
+	 * simultaneous assignment. there can be
+	 * later use of an earlier lvalue.
+	 */
+	return n;
+}
+
+Node*
+reorder4(Node *n)
+{
+	/*
+	 * from ascompat[te]
+	 *	return c,d
+	 * return expression assigned to output
+	 * parameters. there may be no problems.
+	 */
+	return n;
+}

src/runtime/runtime.c における浮動小数点数関数の実装

--- a/src/runtime/runtime.c
+++ b/src/runtime/runtime.c
@@ -563,6 +563,160 @@ sys_ifacei2s(Sigs *ss, Map *m, void *s)
 	}
 }
 
+enum
+{
+	NANEXP		= 2047<<20,
+	NANMASK		= 2047<<20,
+	NANSIGN		= 1<<31,
+};
+
+static	uint64	uvnan		= 0x7FF0000000000001;
+static	uint64	uvinf		= 0x7FF0000000000000;
+static	uint64	uvneginf	= 0xFFF0000000000000;
+
+static int32
+isInf(float64 d, int32 sign)
+{
+	uint64 x;
+
+	x = *(uint64*)&d;
+	if(sign == 0) {
+		if(x == uvinf || x == uvneginf)
+			return 1;
+		return 0;
+	}
+	if(sign > 0) {
+		if(x == uvinf)
+			return 1;
+		return 0;
+	}
+	if(x == uvneginf)
+		return 1;
+	return 0;
+}
+
+static float64
+NaN(void)
+{
+	return *(float64*)&uvnan;
+}
+
+static int32
+isNaN(float64 d)
+{
+	uint64 x;
+
+	return ((uint32)x>>32)==0x7FF00000 && !isInf(d, 0);
+}
+
+static float64
+Inf(int32 sign)
+{
+	if(sign < 0)
+		return *(float64*)&uvinf;
+	else
+		return *(float64*)&uvneginf;
+}
+
+enum
+{
+	MASK	= 0x7ffL,
+	SHIFT	= 64-11-1,
+	BIAS	= 1022L,
+};
+
+static float64
+frexp(float64 d, int32 *ep)
+{
+	uint64 x;
+
+	if(d == 0) {
+		*ep = 0;
+		return 0;
+	}
+	x = *(uint64*)&d;
+	*ep = (int32)((x >> SHIFT) & MASK) - BIAS;
+	x &= ~((uint64)MASK << SHIFT);
+	x |= (uint64)BIAS << SHIFT;
+	return *(float64*)&x;
+}
+
+static float64
+ldexp(float64 d, int32 e)
+{
+	uint64 x;
+
+	if(d == 0)
+		return 0;
+	x = *(uint64*)&d;
+	e += (int32)(x >> SHIFT) & MASK;
+	if(e <= 0)
+		return 0;	/* underflow */
+	if(e >= MASK){		/* overflow */
+		if(d < 0)
+			return Inf(-1);
+		return Inf(1);
+	}
+	x &= ~((uint64)MASK << SHIFT);
+	x |= (uint64)e << SHIFT;
+	return *(float64*)&x;
+}
+
+static float64
+modf(float64 d, float64 *ip)
+{
+	float64 dd;
+	uint64 x;
+	int32 e;
+
+	if(d < 1) {
+		if(d < 0) {
+			d = modf(-d, ip);
+			*ip = -*ip;
+			return -d;
+		}
+		*ip = 0;
+		return d;
+	}
+
+	x = *(uint64*)&d;
+	e = (int32)((x >> SHIFT) & MASK) - BIAS;
+
+	/*
+	 * Keep the top 11+e bits; clear the rest.
+	 */
+	if(e <= 64-11)
+		x &= ~((uint64)1 << (64-11-e))-1;
+	dd = *(float64*)&x;
+	*ip = dd;
+	return d - dd;
+}
+
+// func frexp(float64) (int32, float64); // break fp into exp,fract
+void
+sys_frexp(float64 din, int32 iou, float64 dou)
+{
+	dou = frexp(din, &iou);
+	FLUSH(&dou);
+}
+
+//func	ldexp(int32, float64) float64;	// make fp from exp,fract
+void
+sys_ldexp(float64 din, int32 ein, float64 dou)
+{
+	dou = ldexp(din, ein);
+	FLUSH(&dou);
+}
+
+//func	modf(float64) (float64, float64);	// break fp into double+double
+float64
+sys_modf(float64 din, float64 dou1, float64 dou2)
+{
+	dou1 = modf(din, &dou2);
+	FLUSH(&dou2);
+}
+
 void
 check(void)
 {

コアとなるコードの解説

src/cmd/6g/gen.c の代入演算コード生成の改善

変更されたcgen_asop関数は、代入演算子(=)のコード生成ロジックを改善しています。特に注目すべきは、nl->addable(左辺がアドレス可能、つまりメモリ上の変数などである場合)のブロックです。

変更前は、右辺のコードを生成し、その結果を直接左辺に代入するようなシンプルな処理でした。しかし、変更後は、右辺と左辺の両方に対してレジスタを割り当て(regalloc)、それぞれのコードを生成(cgen)しています。そして、gins(a, &n2, &n1)で実際の代入命令を生成し、gmove(&n1, nl)で左辺のレジスタの内容を最終的な左辺のメモリ位置に移動させています。最後に、使用したレジスタを解放(regfree)しています。

この変更は、より複雑な式が代入の左右辺に来る場合に、レジスタの競合を避け、正しい評価順序を保証し、効率的な機械語コードを生成するためのものです。特に、左辺が単なる変数ではなく、ポインタのデリファレンスや配列の要素など、アドレス計算が必要な場合に対応していると考えられます。

src/cmd/gc/walk.creorder関数の分割と多値代入の処理

walk.cにおける最も重要な変更は、単一のreorder関数がreorder1からreorder4に分割され、それぞれのコンテキストで呼び出されるようになった点です。これにより、コンパイラは式の種類や文脈に応じて、よりきめ細やかな評価順序の制御が可能になりました。

  • reorder1: 関数呼び出しの引数評価に特化しています。引数の中に副作用を持つ関数呼び出し(l->ullman >= UINF)が複数ある場合にエラーを報告するロジックは、Go言語の引数評価順序が未定義であるという特性を考慮しつつ、予期せぬ動作を防ぐためのものです。もし副作用を持つ関数呼び出しが1つだけなら、それを最初に評価し、残りの引数を後で評価することで、特定の最適化や動作の予測可能性を確保しようとしています。
  • reorder2: 多値代入(例: a, b = f())の戻り値を処理する際に使用されます。この関数は、戻り値の式の中に副作用を持つ関数呼び出しがないことを保証します。これは、戻り値が生成される前に、その戻り値を上書きするような副作用が発生するのを防ぐためです。
  • ascompat関数のrev(nn): ascompatee, ascompatet, ascompatteといった関数で、生成されたノードリストをrev(nn)で逆順にしている点が重要です。これは、GoコンパイラがASTノードをリストとして構築する際に、新しい要素をリストの先頭に追加する(nod(OLIST, a, nn))という慣習があるため、最終的なリストが逆順になってしまうのを修正するためのものです。これにより、正しい評価順序が保証されます。

これらの変更は、Go言語のセマンティクス、特に多値の戻り値や副作用を持つ式の評価順序をコンパイラが正確に処理するための基盤を強化しています。

src/runtime/runtime.c の浮動小数点数関数の実装

runtime.cに追加された浮動小数点数関数は、IEEE 754標準に厳密に準拠した実装です。

  • frexp: 浮動小数点数dのビットパターンをuint64として読み取り、指数部(x >> SHIFT) & MASK)を抽出し、バイアス(BIAS)を引くことで実際の指数*epを計算します。仮数部は、指数部に対応するビットをクリアし、正規化された仮数部のバイアス付き指数部を再設定することで得られます。
  • ldexp: 浮動小数点数dのビットパターンから指数部を抽出し、与えられた指数eを加算します。結果の指数がアンダーフロー(e <= 0)またはオーバーフロー(e >= MASK)する場合、それぞれ0または無限大を返します。それ以外の場合、新しい指数部をビットパターンに設定し、浮動小数点数として返します。
  • modf: 浮動小数点数dが1未満の場合(絶対値)、整数部は0となり、d自体が小数部となります。それ以外の場合、dのビットパターンから指数部を抽出し、その指数部に基づいて整数部と小数部の境界を決定します。x &= ~((uint64)1 << (64-11-e))-1;の行は、指定されたビット位置より下位のビットをクリアすることで、整数部を抽出する操作です。

これらの関数は、Go言語が低レベルで正確な浮動小数点数操作を必要とするアプリケーション(例: 数値シミュレーション、グラフィックス)をサポートするための重要な機能を提供します。また、NaNInfといった特殊な値の扱いもIEEE 754標準に則っており、浮動小数点数演算の堅牢性を高めています。

関連リンク

  • Go言語の初期のコミット履歴は、Go言語の進化を理解する上で貴重な情報源です。
  • Goコンパイラの内部構造に関する公式ドキュメントは、Goのソースコードリポジトリ内のsrc/cmd/compile/internalディレクトリや、Goの設計ドキュメント(Go Design Documents)に一部見られます。
  • IEEE 754浮動小数点数標準に関する情報は、Wikipediaや専門の書籍で詳細に解説されています。

参考にした情報源リンク

  • Go Programming Language
  • IEEE 754 - Wikipedia
  • Ullman's Algorithm - Wikipedia (Ullman数に関する一般的な情報)
  • Go言語の初期の設計ドキュメントやメーリングリストのアーカイブは、当時の議論や決定の背景を理解するのに役立ちますが、このコミットの時期(2008年)のものは公開されていない可能性があります。