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

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

src/cmd/gc/const.c は、Goコンパイラのフロントエンドの一部であり、主に定数伝播(constant propagation)と型推論(type inference)に関連する処理を担当しています。このファイルは、Go言語のソースコード内でリテラル(数値、文字列など)がどのように扱われ、異なる型間でどのように変換されるかを定義する重要な役割を担っています。特に、二項演算子(+, -, *, /, ==, != など)の両辺の型が異なる場合に、Goの型推論規則に基づいて適切な型に変換(デフォルトリテラル変換)を行うロジックが含まれています。

コミット

commit 465ff4cfc017360d5a9efbdadee7dbf431e4d69b
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Wed Mar 20 00:19:11 2013 +0100

    cmd/gc: implement revised rule for shifts in equality.
    
    R=rsc, daniel.morsing
    CC=golang-dev
    https://golang.org/cl/7613046

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

https://github.com/golang/go/commit/465ff4cfc017360d5a9efbdadee7dbf431e4d69b

元コミット内容

cmd/gc: implement revised rule for shifts in equality.

このコミットは、Goコンパイラ(cmd/gc)において、等価性比較(equality comparison)におけるシフト演算に関する改訂されたルールを実装するものです。

変更の背景

このコミットの背景には、Go言語の型システムにおける定数リテラルの扱い、特にシフト演算子(<<, >>)と等価性比較演算子(==, !=)が組み合わされた場合の型推論の挙動に関する仕様変更があります。

Go言語では、型を持たない定数(untyped constants)という概念があり、これらは特定の型に束縛されることなく、必要に応じて適切な型に変換されます。例えば、1 は単なる整数リテラルであり、int型、int32型など、文脈に応じて様々な整数型として扱われます。

しかし、シフト演算子の右オペランド(シフト量)は、符号なし整数型(uint)に変換されるという特別なルールがあります。また、等価性比較においては、両辺の型が一致している必要があります。

このコミット以前のGoコンパイラでは、シフト演算を含む等価性比較において、定数リテラルの型推論が意図しない挙動を示す場合がありました。特に、シフト量の定数リテラルが、比較対象の型に影響を与え、結果としてコンパイルエラーや予期せぬ型変換を引き起こす可能性がありました。

この「改訂されたルール」は、このような曖昧さや問題点を解消し、シフト演算を含む等価性比較における定数リテラルの型推論をより予測可能で、Go言語の設計思想に沿ったものにするために導入されました。具体的には、シフト量の定数リテラルが、比較対象の型の推論に不必要に影響を与えないように、その「理想的な型(ideal kind)」をより正確に判断し、適切な型変換を行うように修正されました。

前提知識の解説

Go言語の型システムと定数リテラル

Go言語には、int, float64, string, bool などの組み込み型があります。また、数値リテラル(例: 10, 3.14)や文字列リテラル(例: "hello")は、デフォルトでは「型を持たない定数(untyped constants)」として扱われます。これにより、柔軟な型推論が可能になります。

  • 型を持たない定数(Untyped Constants): これらは特定の型に属さず、文脈に応じて適切な型に変換されます。例えば、var i int = 10 の場合、10int 型に変換されます。
  • 定数の種類(Constant Kinds): コンパイラ内部では、型を持たない定数もその「種類(kind)」を持っています。例えば、CTINT (整数), CTFLT (浮動小数点), CTCPLX (複素数), CTRUNE (ルーン/文字), CTSTR (文字列), CTBOOL (真偽値) などがあります。これらは、定数がどのようなリテラルとして表現されているかを示します。

defaultlit2 関数

defaultlit2 関数は、Goコンパイラのgc(Go Compiler)パッケージ内の重要な関数の一つです。この関数は、二項演算子(例: +, -, ==)の両辺のオペランドが定数リテラルである場合に、それらの型を調整するために使用されます。Go言語では、異なる型のオペランドを持つ二項演算は通常許可されませんが、型を持たない定数リテラルは、もう一方のオペランドの型に合わせて自動的に型変換されることがあります。defaultlit2 はこの自動型変換(デフォルトリテラル変換)のロジックを実装しています。

例えば、var f float64 = 10 のような場合、10 は型を持たない整数定数ですが、float64 型の変数に代入されるため、float64 型に変換されます。defaultlit2 はこのような変換を処理します。

シフト演算子と型推論

Go言語におけるシフト演算子(<<>>)は、ビットシフトを行います。重要なのは、シフト演算子の右オペランド(シフト量)は、常に符号なし整数型(uint)に変換されるというルールです。

例: 1 << 5 (1を5ビット左にシフト)

このルールは、シフト演算の挙動を明確にするために重要ですが、等価性比較と組み合わせる際に、定数リテラルの型推論に複雑さをもたらすことがありました。

isconstidealkind

  • isconst(Node *n, int kind): この関数は、与えられたノード n が特定の種類の定数 kind であるかどうかをチェックするために使用されていました。しかし、これはノードが既に特定の定数種類として「確定」しているかどうかを判断するものであり、型を持たない定数の「理想的な種類」を判断するには不十分な場合がありました。
  • idealkind(Node *n): この関数は、ノード n が表す定数リテラルの「理想的な種類」を返します。これは、その定数がどのような種類の値として最も自然に扱われるべきかを示します。例えば、10CTINT3.14CTFLT といった具合です。この関数は、型を持たない定数の型推論において、より正確な判断を下すために導入されました。

技術的詳細

このコミットの技術的な核心は、defaultlit2 関数内で定数リテラルの種類を判断する際に、従来の isconst 関数から idealkind 関数への切り替えを行った点にあります。

変更前は、defaultlit2 関数内で、二つのオペランド lr のいずれかが特定の種類の定数(例: CTCPLX, CTFLT, CTRUNE)であるかを isconst を用いて直接チェックしていました。

// 変更前
if(isconst(l, CTCPLX) || isconst(r, CTCPLX)) {
    convlit(lp, types[TCOMPLEX128]);
    convlit(rp, types[TCOMPLEX128]);
    return;
}
// 同様に CTFLT, CTRUNE についても

このアプローチの問題点は、isconst がノードが既に特定の定数種類として「確定」しているかをチェックするため、型を持たない定数がまだその「理想的な種類」に基づいて型推論されていない段階では、適切な判断ができない可能性があったことです。特に、シフト演算の右オペランドのような特殊な型推論ルールが絡む場合、この問題が顕在化しました。

変更後、コミットはまず lkind = idealkind(l);rkind = idealkind(r); を導入し、各オペランドの「理想的な種類」を取得するようにしました。そして、この lkindrkind を用いて定数の種類を比較するように変更しました。

// 変更後
int lkind, rkind; // 新しく導入
lkind = idealkind(l);
rkind = idealkind(r);
if(lkind == CTCPLX || rkind == CTCPLX) {
    convlit(lp, types[TCOMPLEX128]);
    convlit(rp, types[TCOMPLEX128]);
    return;
}
// 同様に CTFLT, CTRUNE についても

この変更により、defaultlit2 は、オペランドがまだ型を持たない定数である場合でも、その定数が本来どのような種類の値であるべきか(例えば、整数リテラルは整数、浮動小数点リテラルは浮動小数点)を正確に判断できるようになりました。これにより、特にシフト演算を含む等価性比較において、定数リテラルの型推論がより堅牢になり、Go言語の仕様に沿った挙動が保証されるようになりました。

例えば、x == 1 << y のような式で、y が定数リテラルの場合、y の「理想的な種類」が整数であることを正確に認識し、それがシフト量として適切に扱われるように、そして x との比較において適切な型変換が行われるように、コンパイラのロジックが改善されました。

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

変更は src/cmd/gc/const.c ファイルの defaultlit2 関数内で行われています。

--- a/src/cmd/gc/const.c
+++ b/src/cmd/gc/const.c
@@ -1211,6 +1211,7 @@ void
 defaultlit2(Node **lp, Node **rp, int force)
 {
 	Node *l, *r;
+	int lkind, rkind; // 新しく追加された行
 
 	l = *lp;
 	r = *rp;
@@ -1230,18 +1231,20 @@ defaultlit2(Node **lp, Node **rp, int force)
 		convlit(lp, types[TBOOL]);
 		convlit(rp, types[TBOOL]);
 	}\n-	if(isconst(l, CTCPLX) || isconst(r, CTCPLX)) {
+\tlkind = idealkind(l); // 新しく追加された行
+\trkind = idealkind(r); // 新しく追加された行
+\tif(lkind == CTCPLX || rkind == CTCPLX) { // isconst から idealkind == に変更
 		convlit(lp, types[TCOMPLEX128]);
 		convlit(rp, types[TCOMPLEX128]);
 		return;
 	}\n-	if(isconst(l, CTFLT) || isconst(r, CTFLT)) {
+\tif(lkind == CTFLT || rkind == CTFLT) { // isconst から idealkind == に変更
 		convlit(lp, types[TFLOAT64]);
 		convlit(rp, types[TFLOAT64]);
 		return;
 	}
 
-\tif(isconst(l, CTRUNE) || isconst(r, CTRUNE)) {
+\tif(lkind == CTRUNE || rkind == CTRUNE) { // isconst から idealkind == に変更
 		convlit(lp, runetype);
 		convlit(rp, runetype);
 		return;

コアとなるコードの解説

このコミットの主要な変更点は、defaultlit2 関数内で定数リテラルの種類を判断するロジックが、isconst 関数から idealkind 関数を使用するように変更されたことです。

  1. int lkind, rkind; の追加:

    • これは、左オペランド l と右オペランド r の「理想的な種類」を格納するための新しい変数です。
  2. lkind = idealkind(l);rkind = idealkind(r); の追加:

    • これらの行は、各オペランドの「理想的な種類」を計算し、それぞれ lkindrkind に代入します。idealkind 関数は、ノードが表す定数リテラルが、型を持たない状態であれば、その値に基づいて最も自然に属する定数種類(例: 整数リテラルなら CTINT、浮動小数点リテラルなら CTFLT)を返します。これにより、まだ特定の型に変換されていない定数リテラルに対しても、その本質的な種類を正確に把握できるようになります。
  3. if(isconst(l, CTCPLX) || isconst(r, CTCPLX)) から if(lkind == CTCPLX || rkind == CTCPLX) への変更:

    • この変更は、複素数(CTCPLX)、浮動小数点数(CTFLT)、ルーン(CTRUNE)の各定数種類について行われています。
    • 変更前は、isconst(node, kind) を使用していました。これは、node が既に kind の定数として確定しているかをチェックします。
    • 変更後は、idealkind(node) == kind を使用しています。これは、node の「理想的な種類」が kind であるかをチェックします。
    • この違いが重要です。isconst は「既にその種類であるか」を問うのに対し、idealkind == は「その種類として扱われるべきか」を問います。型を持たない定数リテラルは、まだ特定の型や種類に確定していないため、idealkind を使うことで、その定数が最終的にどのような種類として扱われるべきかをより正確に判断し、適切な型変換(convlit)を適用できるようになります。

この修正により、Goコンパイラは、特にシフト演算の右オペランドのような、型推論に特殊なルールが適用される定数リテラルを含む等価性比較において、より正確で予測可能な型変換を行うことができるようになりました。これは、Go言語の型システムの堅牢性を高め、開発者が意図しない型推論によるバグに遭遇するリスクを低減します。

関連リンク

  • Go Change-Id: 7613046 - このコミットに対応するGoのコードレビューシステム(Gerrit)のチェンジリスト。詳細な議論や背景情報が含まれている可能性があります。

参考にした情報源リンク