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

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

このコミットは、Goコンパイラ(gc)における構造体と配列の比較に関するバグ修正です。具体的には、小規模な構造体や配列の比較において、コンパイラの最適化が「ideal bool」からの暗黙的な型キャストを正しく処理できていなかった問題(Issue #3351)を解決します。これにより、比較結果の式の型が正しく設定されるようになります。

コミット

commit e1f22bdcc56b52a163ecccfe6e95aaf75addcdce
Author: Anthony Martin <ality@pbrane.org>
Date:   Mon Mar 19 15:57:28 2012 -0700

    gc: fix struct and array comparisons for new bool rules
    
    The two optimizations for small structs and arrays
    were missing the implicit cast from ideal bool.
    
    Fixes #3351.
    
    R=rsc, lvd
    CC=golang-dev
    https://golang.org/cl/5848062

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

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

元コミット内容

gc: fix struct and array comparisons for new bool rules

The two optimizations for small structs and arrays
were missing the implicit cast from ideal bool.

Fixes #3351.

R=rsc, lvd
CC=golang-dev
https://golang.org/cl/5848062

変更の背景

このコミットは、Go言語のコンパイラ(gc)が、構造体や配列の比較を行う際の特定の最適化処理に存在したバグを修正するために行われました。報告されたバグ(Issue #3351)は、特に要素数が少ない(「small」)構造体や配列の比較において、コンパイラが比較結果の型を正しく扱えないというものでした。

Go言語では、==!= といった比較演算子を用いた式の結果はブール値(true または false)になります。コンパイラは、これらの比較結果を内部的に「ideal bool」という型のないブール値として扱うことがあります。その後、この「ideal bool」は、必要に応じて明示的または暗黙的にGoの組み込み型である bool 型にキャストされます。

問題は、コンパイラが小規模な構造体や配列の比較を最適化する際に、この「ideal bool」から最終的な bool 型への暗黙的なキャストに必要な型情報を、比較結果の式ノードに正しく伝播させていなかった点にありました。これにより、コンパイル時に型エラーが発生したり、予期せぬ動作を引き起こす可能性がありました。

この修正は、比較結果の式ノードに適切な型情報を明示的に設定することで、この型キャストの欠落を補い、コンパイラが比較結果を正しく処理できるようにすることを目的としています。

前提知識の解説

Go言語の型システムと型推論

Go言語は静的型付け言語であり、変数は使用前に型を宣言する必要があります。しかし、Goには強力な型推論機能があり、コンパイラが文脈から変数の型を自動的に判断できる場合があります。比較演算子のような式の結果も、コンパイラによって型が推論されます。

Go言語における構造体 (struct) と配列 (array) の比較

Go言語では、構造体や配列は要素ごとに比較することができます。

  • 構造体の比較: 同じ型の構造体同士は、そのすべてのフィールドが比較可能(等価演算子 == が定義されている)であれば、==!= で比較できます。比較はフィールドごとに再帰的に行われ、すべてのフィールドが等しい場合に構造体全体が等しいと判断されます。
  • 配列の比較: 同じ型の配列同士は、その要素の型が比較可能であれば、==!= で比較できます。比較は要素ごとに順に行われ、すべての要素が等しい場合に配列全体が等しいと判断されます。

Goコンパイラの最適化

Goコンパイラは、生成されるバイナリのパフォーマンスを向上させるために様々な最適化を行います。小規模な構造体や配列の比較もその対象となることがあります。例えば、特定の条件下では、より効率的な機械語命令に変換されることがあります。しかし、これらの最適化が型システムのルールと完全に整合しない場合に、今回のようなバグが発生することがあります。

"ideal bool" の概念

Goコンパイラの内部では、リテラル(例: true, false)や比較演算子(例: x == y)の結果として生成されるブール値は、一時的に「型のないブール値(untyped boolean value)」として扱われることがあります。これを本コミットの文脈では「ideal bool」と呼んでいます。これは、数値リテラルが「ideal int」や「ideal float」として扱われるのと似ています。これらの型のない値は、最終的にGoの組み込み型(bool, int, float64 など)に変換される必要があります。この変換は、文脈に応じて暗黙的に行われることもあります。

Goコンパイラ (gc) の役割

Go言語の標準コンパイラは gc と呼ばれます。gc はソースコードを解析し、抽象構文木(AST)を構築し、型チェック、最適化、コード生成などの様々なフェーズを経て実行可能なバイナリを生成します。このコミットで変更された src/cmd/gc/walk.c は、コンパイラの「ウォーク(walk)」フェーズの一部であり、ASTを走査しながら型チェックや一部の最適化を行う役割を担っています。

技術的詳細

この修正は、Goコンパイラの src/cmd/gc/walk.c ファイル内の walkcompare 関数に焦点を当てています。walkcompare 関数は、Goプログラム内の比較演算子(==, != など)を処理する役割を担っています。

問題は、特定の最適化パス(特に小規模な構造体や配列の比較に適用されるもの)において、比較結果を表す式ノード(expr)の型情報が正しく設定されていなかったことにありました。比較演算の結果はブール値ですが、コンパイラ内部では一時的に「ideal bool」として扱われます。この「ideal bool」は、最終的にGoの bool 型に変換される必要があります。しかし、最適化された比較処理では、この変換に必要な型情報が expr ノードに適切に伝播されず、結果として expr の型が未定義のままになっていました。

修正は、walkcompare 関数内の2つの箇所に expr->type = n->type; という行を追加することによって行われました。

  • nodbool(n->op == OEQ) は、比較演算の結果に基づいてブール値のノードを作成します。
  • typecheck(&expr, Erv) は、そのノードの型チェックを行います。
  • walkexpr(&expr, init) は、その式をさらにウォーク(処理)します。

この修正により、walkexpr が完了した後、expr ノードの型が、元の比較演算子ノード n の型(つまり、期待される bool 型)に明示的に設定されるようになります。これにより、「ideal bool」が最終的な bool 型に正しくキャストされ、コンパイラが比較結果を適切に処理できるようになります。

test/fixedbugs/bug427.go は、この修正を検証するために追加された新しいテストファイルです。このテストは、4つまたは5つの整数フィールドを持つ構造体と、4つまたは5つの整数要素を持つ配列を定義し、それらの比較を行います。そして、比較結果を interface{} 型の変数に代入しています。このテストは、コンパイルが成功すること(// compile ディレクティブ)を確認することで、修正が正しく機能し、以前のバグによって発生していたコンパイルエラーが解消されたことを保証します。

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

src/cmd/gc/walk.c ファイルにおいて、以下の2箇所に1行ずつコードが追加されました。

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -2514,6 +2514,7 @@ walkcompare(Node **np, NodeList **init)
 		expr = nodbool(n->op == OEQ);
 		typecheck(&expr, Erv);
 		walkexpr(&expr, init);
+		expr->type = n->type;
 		*np = expr;
 		return;
 	}
@@ -2534,6 +2535,7 @@ walkcompare(Node **np, NodeList **init)
 		expr = nodbool(n->op == OEQ);
 		typecheck(&expr, Erv);
 		walkexpr(&expr, init);
+		expr->type = n->type;
 		*np = expr;
 		return;
 	}

また、以下のテストファイルが新規追加されました。

--- /dev/null
+++ b/test/fixedbugs/bug427.go
@@ -0,0 +1,39 @@
+// compile
+
+// Copyright 2012 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// http://code.google.com/p/go/issues/detail?id=3351
+
+package main
+
+// struct with four fields of basic type
+type S struct {a, b, c, d int}
+
+// struct with five fields of basic type
+type T struct {a, b, c, d, e int}
+
+// array with four elements
+type A [4]int
+
+// array with five elements
+type B [5]int
+
+func main() {
+	var i interface{}
+
+	var s1, s2 S
+	i = s1 == s2
+
+	var t1, t2 T
+	i = t1 == t2
+
+	var a1, a2 A
+	i = a1 == a2
+
+	var b1, b2 B
+	i = b1 == b2
+
+	_ = i
+}

コアとなるコードの解説

追加された expr->type = n->type; という行は、Goコンパイラの型システムにおける重要な修正です。

  • expr: これは、比較演算の結果を表す抽象構文木(AST)のノードです。例えば、s1 == s2 という比較があった場合、この expr はその比較の結果であるブール値(true または false)を表すノードになります。
  • n: これは、元の比較演算子(OEQ は等価比較 == を表す)を含むASTノードです。このノードは、その結果がどのような型になるべきか(この場合は bool 型)という情報を持っています。

修正前のコードでは、walkexpr(&expr, init); が実行された後、expr ノードの型情報が正しく設定されていない可能性がありました。特に、コンパイラが小規模な構造体や配列の比較に対して特定の最適化を適用する際に、この型情報の伝播が欠落していたと考えられます。

expr->type = n->type; を追加することで、expr ノードの型が、元の比較演算子ノード n が持つ型(つまり、bool 型)に明示的に設定されます。これにより、コンパイラは exprbool 型であることを認識し、その後の処理(例えば、interface{} への代入など)において、「ideal bool」からGoの bool 型への暗黙的なキャストを正しく実行できるようになります。

この修正は、コンパイラの内部的な型管理の正確性を保証し、Go言語の型システムが期待通りに機能することを確実にします。

関連リンク

参考にした情報源リンク