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

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

このコミットは、Goコンパイラ(cmd/gc)において、定数を16進数形式でエクスポートおよび処理する機能を追加・改善するものです。具体的には、コンパイラの内部表現で扱われる数値定数(特に浮動小数点数)が、16進数表記(例: 0x1.Fp+0)として適切にパースされ、また必要に応じて16進数形式で出力されるように変更が加えられています。これにより、Go言語の数値リテラル表現の柔軟性が向上し、特に浮動小数点数の正確な表現や、低レベルなビット操作に関連する定数の記述が容易になります。

コミット

commit fbaf59bf1ede6d8f42ab0d8e0f238582de5f9888
Author: Jan Ziak <0xe2.0x9a.0x9b@gmail.com>
Date:   Tue May 22 13:53:38 2012 -0400

    cmd/gc: export constants in hexadecimal
    
    R=golang-dev, r, rsc, iant, remyoudompheng, dave
    CC=golang-dev
    https://golang.org/cl/6206077
---
 src/cmd/gc/fmt.c                |   2 +
 src/cmd/gc/lex.c                |   2 +
 src/cmd/gc/mparith1.c           | 170 ++++++++++++++++++++++++++++++++--------
 src/pkg/exp/types/gcimporter.go |   2 +-\n 4 files changed, 142 insertions(+), 34 deletions(-)

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

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

元コミット内容

このコミットの目的は、Goコンパイラ(cmd/gc)が数値定数を16進数形式でエクスポートできるようにすることです。これは、コンパイラが生成する中間コードやデバッグ情報において、数値定数をより正確かつ効率的に表現するために行われます。特に浮動小数点数においては、10進数表現では丸め誤差が生じやすいのに対し、16進数浮動小数点表現はIEEE 754標準に直接対応しており、バイナリ表現を正確に記述できる利点があります。

変更の背景

Go言語は、その設計思想としてシンプルさと実用性を重視していますが、数値リテラル、特に浮動小数点数の表現においては、より高度な制御が求められる場合があります。従来の10進数表記のみでは、特定のビットパターンを持つ浮動小数点数を正確に表現することが困難でした。例えば、0.1という10進数は、IEEE 754倍精度浮動小数点数では正確に表現できず、無限小数となります。しかし、0x1.999999999999ap-4のような16進数浮動小数点リテラルを使用すれば、そのバイナリ表現を直接指定できます。

この変更の背景には、以下のようなニーズがあったと考えられます。

  1. 浮動小数点数の正確な表現: IEEE 754標準に準拠した16進数浮動小数点リテラルをサポートすることで、開発者が浮動小数点数のバイナリ表現を直接指定できるようになり、丸め誤差を意識したプログラミングや、特定のビットパターンを持つ値を扱う際に非常に有用です。
  2. コンパイラ内部での効率的な処理: コンパイラが数値を内部で処理する際、16進数表現はバイナリ表現と直接対応するため、パースや変換のオーバーヘッドを削減できる可能性があります。
  3. 既存のC言語コンパイラとの互換性: 多くのC言語コンパイラは、古くから16進数浮動小数点リテラルをサポートしており、Goコンパイラも同様の機能を持つことで、より幅広い数値表現に対応できるようになります。
  4. gc の機能強化: gc はGo言語の主要なコンパイラであり、その機能強化は言語全体の表現力と実用性の向上に直結します。

このコミットは、Go言語がより成熟し、多様な数値計算のニーズに応えるための重要な一歩と言えます。

前提知識の解説

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

1. Go言語の数値リテラル

Go言語は、整数リテラル、浮動小数点リテラル、虚数リテラルをサポートしています。

  • 整数リテラル: 10進数(例: 123)、8進数(0o123 または 0123)、16進数(0x123)、2進数(0b101)で記述できます。
  • 浮動小数点リテラル: 10進数(例: 123.451.23e+5)で記述できます。このコミットにより、16進数浮動小数点リテラル(例: 0x1.Fp+0)もサポートされるようになります。

2. cmd/gc (Goコンパイラ)

cmd/gc は、Go言語のソースコードを機械語にコンパイルする主要なコンパイラです。Go言語のツールチェインの一部であり、字句解析、構文解析、型チェック、最適化、コード生成などの段階を経て実行可能ファイルを生成します。このコミットでは、特に字句解析(lex.c)、フォーマット(fmt.c)、多倍長演算(mparith1.c)に関連する部分が変更されています。

3. mparith ライブラリ (多倍長整数・浮動小数点演算)

mparith は、Goコンパイラ内部で使用される多倍長整数および浮動小数点数演算ライブラリです。Go言語の組み込み型では表現できない非常に大きな数値や、高い精度が求められる数値計算を扱うために使用されます。このコミットでは、特に16進数文字列から多倍長整数への変換 (mphextofix) や、多倍長浮動小数点数への変換 (mpatoflt)、そして多倍長整数を16進数文字列としてフォーマットする機能が追加・変更されています。

4. fmt.clex.c の役割

  • fmt.c: コンパイラ内部のデータ構造を文字列としてフォーマットする機能を提供します。デバッグ出力や、コンパイラが生成する中間表現の可読性を高めるために使用されます。このコミットでは、数値定数を16進数形式で出力する機能が追加されています。
  • lex.c: 字句解析器(lexer)の実装が含まれています。ソースコードを読み込み、トークン(キーワード、識別子、リテラルなど)に分割する役割を担います。このコミットでは、16進数浮動小数点リテラルを正しく認識するための変更が加えられています。

5. src/pkg/exp/types パッケージ

src/pkg/exp/types は、Go言語の型システムに関する実験的な機能や、コンパイラが型情報を扱うためのユーティリティを提供するパッケージです。このコミットでは、数値リテラルのパースに関連する部分が変更されています。

6. IEEE 754 浮動小数点標準と16進浮動小数点表現

IEEE 754は、浮動小数点数の表現と演算に関する国際標準です。多くのプログラミング言語やハードウェアで採用されています。16進数浮動小数点リテラルは、この標準に準拠した浮動小数点数を直接表現するための記法です。

一般的な形式は 0xH.HpH のようになります。

  • 0x: 16進数であることを示すプレフィックス。
  • H.H: 16進数の仮数部。小数点を含むことができます。
  • p または P: 指数部が2のべき乗であることを示すプレフィックス。
  • H: 10進数の指数部。これは2のべき乗の指数を表します。

例: 0x1.0p01.0 * 2^0 = 1.0 を表します。 0x1.0p-11.0 * 2^-1 = 0.5 を表します。 0x1.Fp+0(1 + 15/16) * 2^0 = 1.9375 を表します。

この表現は、浮動小数点数の内部バイナリ表現と直接対応するため、丸め誤差を回避し、正確な値を指定する際に非常に強力です。

技術的詳細

このコミットは、Goコンパイラが16進数形式の数値定数を適切に処理できるように、複数のファイルにわたる変更を加えています。

1. src/cmd/gc/fmt.c の変更

fmt.c は、コンパイラ内部の数値型 Mpint (多倍長整数) を文字列に変換する Bconv 関数を含んでいます。このコミットでは、Bconv 関数に FmtSharp フラグ(%#B フォーマット指定子に対応)が渡された場合に、数値を10進数ではなく16進数でフォーマットするロジックが追加されました。

  • 変更前: Mpint を常に10進数でフォーマットしていました。
  • 変更後:
    • fp->flags & FmtSharp が真の場合、sixteen (16) を基数として繰り返し除算を行い、余りを16進数桁(0-9, A-F)に変換してバッファに格納します。最後に 0x プレフィックスを追加します。
    • それ以外の場合は、従来通り10進数でフォーマットします。
  • また、Vconv 関数(Val 型の値を変換)においても、CTINT 型の定数に対して FmtSharp フラグまたは FExp モードが設定されている場合に、%#B フォーマットを使用して16進数で出力するように変更されています。
  • Fconv 関数(浮動小数点数を変換)では、snprint のフォーマット文字列が %Bp+%d から %#Bp+%d に変更され、浮動小数点数の仮数部が常に16進数で出力されるようになりました。

2. src/cmd/gc/lex.c の変更

lex.c は字句解析器であり、ソースコード中の数値リテラルを識別します。

  • 変更前: 16進数リテラルは整数のみを対象としていました。
  • 変更後: tnum ラベルの箇所に、16進数リテラルが p (指数部) を含む場合に casep ラベルにジャンプするロジックが追加されました。これにより、0x...p... 形式の16進数浮動小数点リテラルが正しく認識されるようになります。

3. src/cmd/gc/mparith1.c の変更

mparith1.c は多倍長整数および浮動小数点数演算の実装を含んでいます。このファイルには最も大きな変更が加えられています。

  • mphextofix 関数の追加:
    • この新しい関数は、16進数文字列 s とその長さ n を受け取り、それを Mpint 型の多倍長整数 a に変換します。
    • 文字列の先頭の 0 をスキップし、オーバーフローチェックを行います。
    • 文字列を逆順に走査し、各16進数桁を4ビットのバイナリ値に変換して Mpint の内部表現に組み込みます。これは、Mpint がワードの配列としてビットを格納しているため、ビットシフトとビットOR演算を組み合わせて行われます。
  • mpatoflt 関数の変更:
    • この関数は文字列を浮動小数点数に変換します。
    • 基数検出ロジックの追加: 入力文字列の先頭を走査し、0x プレフィックスの有無によって基数(10進数または16進数)を自動的に判別するロジックが追加されました。
    • 16進数浮動小数点数のパース: 基数が16進数と判別された場合、0x プレフィックスの後に続く16進数文字列を mphextofix を使用して Mpflt の仮数部 (a->val) に変換します。その後、mpnorm を呼び出して正規化を行います。
    • 小数点 (.) の処理において、基数が16進数の場合はエラー (goto bad) となるように変更されました。これは、16進数浮動小数点リテラルでは小数点と指数部の間に p が必要であり、0x1.23 のような形式は整数リテラルとして扱われるためです。
  • mpatofix 関数の変更:
    • この関数は文字列を多倍長整数に変換します。
    • 16進数文字列のパースロジックが大幅に簡素化され、mphextofix 関数を呼び出すように変更されました。これにより、コードの重複が排除され、より堅牢な16進数変換が実現されました。

4. src/pkg/exp/types/gcimporter.go の変更

gcimporter.go は、Goコンパイラが生成するバイナリ形式の型情報をインポートする際に使用されます。

  • parseInt および parseNumber 関数の変更:
    • new(big.Int).SetString(sign+val, 10)new(big.Int).SetString(sign+val, 0) に変更されました。
    • SetString メソッドの第2引数 base0 を指定すると、文字列のプレフィックス(0x0o0b など)に基づいて基数を自動的に推論するようになります。これにより、インポートされる数値定数が10進数だけでなく、16進数などの他の基数でも正しくパースされるようになります。

これらの変更により、Goコンパイラは16進数浮動小数点リテラルを含む、より多様な数値定数を正確に処理し、内部で表現できるようになりました。

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

このコミットにおけるコアとなるコードの変更箇所は以下の通りです。

  • src/cmd/gc/fmt.c:
    • Vconv 関数: CTINT 型の定数に対して16進数フォーマット (%#B) を適用する条件を追加。
    • Bconv 関数: FmtSharp フラグが設定されている場合に Mpint を16進数でフォーマットするロジックを追加。
    • Fconv 関数: 浮動小数点数の仮数部を常に16進数で出力するようフォーマット文字列を変更。
  • src/cmd/gc/lex.c:
    • tnum ラベル: 16進数リテラルが p を含む場合に casep にジャンプするロジックを追加。
  • src/cmd/gc/mparith1.c:
    • mphextofix 関数: 新規追加。16進数文字列を Mpint に変換。
    • mpatoflt 関数: 16進数浮動小数点リテラルのパースロジックと基数検出ロジックを追加。
    • mpatofix 関数: 16進数文字列のパースに mphextofix を利用するように変更。
  • src/pkg/exp/types/gcimporter.go:
    • parseNumber 関数: big.Int.SetString の基数を 10 から 0 に変更し、自動基数推論を有効化。

コアとなるコードの解説

src/cmd/gc/fmt.cBconv 関数

// 変更前 (抜粋)
// ...
// 	mpmovecfix(&ten, 10);
//
// 	p = &buf[sizeof(buf)];
// 	*--p = 0;
// 	for(;;) {
// 		mpdivmodfixfix(&q, &r, &q, &ten);
// 		*--p = mpgetfix(&r) + '0';
// 		if(mptestfix(&q) <= 0)
// 			break;
// 	}
// ...

// 変更後 (抜粋)
// ...
// 	if(fp->flags & FmtSharp) {
// 		// Hexadecimal
// 		mpmovecfix(&sixteen, 16);
// 		for(;;) {
// 			mpdivmodfixfix(&q, &r, &q, &sixteen);
// 			digit = mpgetfix(&r);
// 			if(digit < 10)
// 				*--p = digit + '0';
// 			else
// 				*--p = digit - 10 + 'A';
// 			if(mptestfix(&q) <= 0)
// 				break;
// 		}
// 		*--p = 'x';
// 		*--p = '0';
// 	} else {
// 		// Decimal
// 		mpmovecfix(&ten, 10);
// 		for(;;) {
// 			mpdivmodfixfix(&q, &r, &q, &ten);
// 			*--p = mpgetfix(&r) + '0';
// 			if(mptestfix(&q) <= 0)
// 				break;
// 		}
// 	}
// ...

この変更は、Mpint 型の数値を文字列に変換する際のフォーマットロジックを拡張しています。FmtSharp フラグ(%#B フォーマット指定子に対応)が設定されている場合、基数を16として繰り返し除算を行い、余りを16進数桁(0-9, A-F)に変換してバッファに格納します。最後に 0x プレフィックスを追加することで、完全な16進数表記を生成します。これにより、コンパイラが内部で扱う数値を16進数形式で出力できるようになります。

src/cmd/gc/mparith1.cmphextofix 関数 (新規追加)

static void
mphextofix(Mpint *a, char *s, int n)
{
	char *hexdigitp, *end, c;
	long d;
	int bit;

	while(*s == '0') { // 先頭の0をスキップ
		s++;
		n--;
	}

	// overflow
	if(4*n > Mpscale*Mpprec) { // 4*n はビット数。Mpscale*Mpprec はMpintの最大ビット数
		a->ovf = 1;
		return;
	}

	end = s+n-1;
	for(hexdigitp=end; hexdigitp>=s; hexdigitp--) { // 文字列を逆順に走査
		c = *hexdigitp;
		if(c >= '0' && c <= '9')
			d = c-'0';
		else if(c >= 'A' && c <= 'F')
			d = c-'A'+10;
		else
			d = c-'a'+10;

		bit = 4*(end - hexdigitp); // 現在の桁が表すビット位置
		while(d > 0) {
			if(d & 1) // 最下位ビットが1なら
				a->a[bit/Mpscale] |= (long)1 << (bit%Mpscale); // 対応するMpintのワードにビットをセット
			bit++;
			d = d >> 1; // 次のビットへ
		}
	}
}

この関数は、16進数文字列を多倍長整数 Mpint に変換する核心部分です。16進数文字列の各桁を4ビットのバイナリ値として扱い、それを Mpint の内部表現(a->a 配列に格納されたワード)に組み込んでいきます。bit/Mpscale でワードのインデックスを、bit%Mpscale でワード内のビット位置を計算し、ビットOR演算 (|=) でビットをセットします。これにより、任意の長さの16進数文字列を正確な多倍長整数に変換できます。

src/cmd/gc/mparith1.cmpatoflt 関数

// 変更前 (抜粋)
// ...
// void
// mpatoflt(Mpflt *a, char *as)
// {
// 	Mpflt b;
// 	int dp, c, f, ef, ex, eb;
// 	char *s;
// ...

// 変更後 (抜粋)
// ...
// void
// mpatoflt(Mpflt *a, char *as)
// {
// 	Mpflt b;
// 	int dp, c, f, ef, ex, eb, base;
// 	char *s, *start;
//
// 	while(*as == ' ' || *as == '\t')
// 		as++;
//
// 	/* determine base */
// 	s = as;
// 	base = -1;
// 	while(base == -1) {
// 		switch(c = *s++) {
// 		case '-':
// 		case '+':
// 			break;
//
// 		case '0':
// 			if(*s == 'x')
// 				base = 16;
// 			else
// 				base = 10;
// 			break;
//
// 		default:
// 			base = 10;
// 		}
// 	}
//
// 	s = as;
// 	dp = 0;		/* digits after decimal point */
// 	f = 0;		/* sign */
// 	ef = 0;		/* exponent sign */
// 	ex = 0;		/* exponent */
// 	eb = 0;		/* binary point */
//
// 	mpmovecflt(a, 0.0);
// 	if(base == 16) { // 16進数浮動小数点数のパースロジック
// 		start = nil;
// 		for(;;) {
// 			c = *s;
// 			if(c == '-') {
// 				f = 1;
// 				s++;
// 			}
// 			else if(c == '+') {
// 				s++;
// 			}
// 			else if(c == '0' && s[1] == 'x') {
// 				s += 2;
// 				start = s;
// 			}
// 			else if((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
// 				s++;
// 			}
// 			else {
// 				break;
// 			}
// 		}
// 		if(start == nil)
// 			goto bad;
//
// 		mphextofix(&a->val, start, s-start); // 仮数部をmphextofixで変換
// 		if(a->val.ovf)
// 			goto bad;
// 		a->exp = 0;
// 		mpnorm(a);
// 	}
// 	for(;;) {
// 		switch(c = *s++) {
// 		// ... 既存の10進数パースロジック ...
// 		case '.':
// 			if(base == 16) // 16進数の場合は小数点エラー
// 				goto bad;
// 			dp = 1;
// 			continue;
// 		// ...
// 	}
// ...

mpatoflt 関数は、入力文字列から浮動小数点数をパースする際に、まず基数を自動的に判別するロジックが追加されました。文字列が 0x で始まる場合、基数を16進数と認識し、新しく追加された mphextofix 関数を使用して仮数部を変換します。これにより、0x1.Fp+0 のような16進数浮動小数点リテラルが正しく処理されるようになります。また、16進数モードでは小数点 (.) が許可されないように変更され、厳密なパースが保証されます。

src/pkg/exp/types/gcimporter.goparseNumber 関数

// 変更前
// mant, ok := new(big.Int).SetString(sign+val, 10)

// 変更後
// mant, ok := new(big.Int).SetString(sign+val, 0)

この変更は非常に小さいですが、重要な意味を持ちます。big.Int.SetString メソッドの第2引数 base0 を指定することで、Goの math/big パッケージが文字列のプレフィックス(0x0o0b など)に基づいて基数を自動的に推論するようになります。これにより、コンパイラが生成する型情報に含まれる数値定数が、10進数だけでなく、16進数などの他の基数でも正しくインポートされるようになります。これは、コンパイラが16進数定数をエクスポートするようになったことと密接に関連しています。

これらの変更により、Goコンパイラは16進数浮動小数点リテラルを含む、より多様な数値定数を正確に処理し、内部で表現できるようになりました。

関連リンク

参考にした情報源リンク