[インデックス 16637] ファイルの概要
このコミットは、Go言語のコンパイラの一つであるgccgoが、特定のシフト演算子を含む式を誤って拒否していた問題に対するテストケースの追加です。具体的には、1 << s
のようなシフト式が、gccgoによって不正なものとして扱われていた状況を修正するためのものです。
コミット
commit 05cf6fe0c1c1a61fa95e4a3ab25c5b4f1a0ad499
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Tue Jun 25 08:06:34 2013 +0200
test: add shift expression incorrectly rejected by gccgo.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/10483045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/05cf6fe0c1c1a61fa95e4a3ab25c5b4f1a0ad499
元コミット内容
このコミットの目的は、gccgo
が誤って拒否していたシフト式をテストケースとして追加することです。これにより、gccgo
のコンパイラがGo言語の仕様に準拠してシフト演算を正しく処理できるようになることを保証します。
変更の背景
Go言語には、ビットシフト演算子(<<
左シフト、>>
右シフト)が存在します。これらの演算子は、数値のビットパターンを指定された数だけ左右に移動させるために使用されます。Go言語の仕様では、シフト演算の左オペランドは整数型でなければならず、右オペランドは符号なし整数型であるか、またはコンパイル時に定数である必要があります。また、シフト量は左オペランドの型のビット幅を超えることはできません。
このコミットが作成された2013年当時、Go言語のコンパイラには主に公式のgc
(Go Compiler)と、GCC(GNU Compiler Collection)をバックエンドとするgccgo
の二種類がありました。gccgo
は、GCCの最適化やクロスコンパイルの恩恵を受けられる一方で、Go言語の仕様の解釈や実装においてgc
と異なる挙動を示すことが稀にありました。
このコミットの背景にあるのは、gccgo
がGo言語のシフト演算の特定のケース、特に1 << s
のような式(ここでs
は変数)を誤って不正な式として扱っていたというバグです。Go言語の仕様では、このような式は有効であり、1
の型はint
として推論され、シフト結果もint
型となります。しかし、gccgo
はこの推論と処理を正しく行えていなかったため、コンパイルエラーが発生していました。この問題は、Goプログラムの移植性や、gc
とgccgo
間での互換性に影響を与える可能性がありました。
このコミットは、このgccgo
のバグを特定し、将来の回帰を防ぐためのテストケースを追加することで、コンパイラの堅牢性とGo言語仕様への準拠を向上させることを目的としています。
前提知識の解説
Go言語のシフト演算子
Go言語におけるシフト演算子 <<
(左シフト) と >>
(右シフト) は、以下のような特徴を持ちます。
- オペランドの型:
- 左オペランド (シフトされる値): 整数型である必要があります。
- 右オペランド (シフト量): 符号なし整数型であるか、または非負の定数である必要があります。
- 型推論:
- シフト演算の結果の型は、左オペランドの型によって決まります。
- もし左オペランドが型付けされていない定数(例:
1
)である場合、その型はシフト演算の結果が使用される文脈によって推論されます。例えば、var i = 1 << s
の場合、1
はint
型として扱われ、i
もint
型になります。
- シフト量の制限:
- シフト量は、左オペランドの型のビット幅を超えることはできません。例えば、
int
型が64ビットの場合、シフト量は63以下である必要があります。これを超えると、結果はゼロになるか、未定義の動作を引き起こす可能性があります(Goの仕様では、シフト量が型のビット幅以上の場合、結果はゼロになります)。
- シフト量は、左オペランドの型のビット幅を超えることはできません。例えば、
gccgo
gccgo
は、Go言語のプログラムをコンパイルするためのGCCフロントエンドです。これは、Go言語のコードをGCCの中間表現に変換し、その後GCCの強力な最適化バックエンドを利用して、様々なアーキテクチャ向けのネイティブコードを生成します。gccgo
は、Go言語の公式コンパイラであるgc
とは異なる実装アプローチを取っており、Go言語の仕様に厳密に準拠しつつも、GCCのエコシステムとの統合を目指しています。
コンパイラのバグとテスト駆動開発
コンパイラは非常に複雑なソフトウェアであり、言語仕様の解釈やコード生成の過程でバグが混入することは珍しくありません。特に、型推論や定数伝播、最適化の段階で、言語仕様の微妙なニュアンスが誤って解釈されることがあります。
このようなバグを発見し修正するためには、再現可能なテストケースが不可欠です。テスト駆動開発(TDD)の原則は、バグを修正する際にも適用されます。つまり、まずバグを再現する失敗するテストケースを作成し、そのテストがパスするようにコードを修正し、最後にテストがパスすることを確認するという流れです。このコミットも、まさにこのアプローチに従っています。
技術的詳細
このコミットが修正しようとしているgccgo
のバグは、Go言語の型推論とシフト演算の組み合わせに関するものです。
Go言語の仕様では、型付けされていない定数(例: 1
)がシフト演算の左オペランドとして使用され、その結果が変数に代入される場合、その定数は代入先の変数の型、または文脈から推論される型に変換されます。
例えば、以下のコードを考えます。
var s uint = 33
var l = g(1 << s) // ここで g は int を引数にとる関数
ここで、1
は型付けされていない定数です。1 << s
というシフト演算が行われる際、1
はデフォルトのint
型として扱われます。s
はuint
型ですが、シフト量としては有効です。問題は、gccgo
がこの1 << s
という式を、g
関数に渡す際に、1
の型推論やシフト演算の結果の型を正しく処理できていなかった点にあります。
具体的には、1 << s
の1
はint
型であり、s
はuint
型です。Goの仕様では、シフト演算の右オペランドは符号なし整数型であれば変数でも構いません。この場合、1
はint
型として扱われ、s
によるシフトが行われます。結果として得られる値はint
型となります。
gccgo
は、この1 << s
という式を、あたかも左オペランドが型付けされていない浮動小数点数であるかのように誤解釈し、シフト演算の規則に違反すると判断してコンパイルエラーを出力していた可能性があります。Go言語では、浮動小数点数をシフト演算の左オペランドにすることはできません(例: 1.0 << s
は、1.0
がint
型に変換可能であれば合法ですが、そうでなければ不正です)。
このコミットで追加されたテストケースは、まさにこのl = g(1 << s)
という行を含んでおり、gccgo
がこの有効なシフト式を正しくコンパイルできることを検証します。このテストが追加されたということは、以前のgccgo
のバージョンではこの行がコンパイルエラーを引き起こしていたことを示唆しています。
コアとなるコードの変更箇所
変更は test/shift2.go
ファイルに1行追加されています。
--- a/test/shift2.go
+++ b/test/shift2.go
@@ -20,6 +20,7 @@ var (
i = 1 << s // 1 has type int
j int32 = 1 << s // 1 has type int32; j == 0
k = uint64(1 << s) // 1 has type uint64; k == 1<<33
+\tl = g(1 << s) // 1 has type int
m int = 1.0 << s // legal: 1.0 has type int
w int64 = 1.0 << 33 // legal: 1.0<<33 is a constant shift expression
)
コアとなるコードの解説
追加された行は以下の通りです。
l = g(1 << s) // 1 has type int
この行は、test/shift2.go
ファイル内のグローバル変数宣言ブロックに追加されています。このファイルは、Go言語のシフト演算に関する様々なエッジケースや型推論の挙動をテストするために設計されています。
g
は、このテストファイル内で定義されている関数で、おそらくint
型の引数を受け取るシンプルな関数です。1 << s
は、このテストケースの核心となるシフト式です。1
は型付けされていない定数です。s
は、このファイルの他の場所でvar s uint = 33
のように定義されているuint
型の変数です。
- コメント
// 1 has type int
は、Go言語の仕様において、この文脈での1
の型がint
として推論されることを明示しています。これは、g
関数がint
型の引数を期待しているため、コンパイラが1 << s
の結果をint
型として評価する必要があることを示唆しています。
このテストケースの追加により、gccgo
が以下の点を正しく処理できるようになったことを検証します。
- 型付けされていない定数
1
が、シフト演算の左オペランドとして使用された際に、文脈(ここではg
関数の引数)に基づいて正しくint
型に推論されること。 - 変数
s
がシフト量として使用された際に、それがuint
型であっても正しくシフト演算が実行されること。 - シフト演算
1 << s
の結果が、int
型として正しく評価され、g
関数に渡されること。
このテストが成功することは、gccgo
がGo言語のシフト演算に関する型推論とセマンティクスを、gc
コンパイラと同様に正しく実装していることを意味します。
関連リンク
- Go言語の仕様 - シフト演算子: https://go.dev/ref/spec#Shift_operators
- Go言語の仕様 - 型付けされていない定数: https://go.dev/ref/spec#Untyped_constants
- Go言語のコンパイラ
gc
とgccgo
について (一般的な情報): https://go.dev/doc/install/gccgo
参考にした情報源リンク
- https://github.com/golang/go/commit/05cf6fe0c1c1a61fa95e4a3ab25c5b4f1a0ad499
- https://golang.org/cl/10483045 (Go Code Review - CL 10483045)
- Go言語の公式ドキュメント (Go言語の仕様)
- GCCのドキュメント (gccgoに関する一般的な情報)
- Go言語のコンパイラに関する一般的な知識