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

[インデックス 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プログラムの移植性や、gcgccgo間での互換性に影響を与える可能性がありました。

このコミットは、このgccgoのバグを特定し、将来の回帰を防ぐためのテストケースを追加することで、コンパイラの堅牢性とGo言語仕様への準拠を向上させることを目的としています。

前提知識の解説

Go言語のシフト演算子

Go言語におけるシフト演算子 << (左シフト) と >> (右シフト) は、以下のような特徴を持ちます。

  • オペランドの型:
    • 左オペランド (シフトされる値): 整数型である必要があります。
    • 右オペランド (シフト量): 符号なし整数型であるか、または非負の定数である必要があります。
  • 型推論:
    • シフト演算の結果の型は、左オペランドの型によって決まります。
    • もし左オペランドが型付けされていない定数(例: 1)である場合、その型はシフト演算の結果が使用される文脈によって推論されます。例えば、var i = 1 << s の場合、1int 型として扱われ、iint 型になります。
  • シフト量の制限:
    • シフト量は、左オペランドの型のビット幅を超えることはできません。例えば、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型として扱われます。suint型ですが、シフト量としては有効です。問題は、gccgoがこの1 << sという式を、g関数に渡す際に、1の型推論やシフト演算の結果の型を正しく処理できていなかった点にあります。

具体的には、1 << s1int型であり、suint型です。Goの仕様では、シフト演算の右オペランドは符号なし整数型であれば変数でも構いません。この場合、1int型として扱われ、sによるシフトが行われます。結果として得られる値はint型となります。

gccgoは、この1 << sという式を、あたかも左オペランドが型付けされていない浮動小数点数であるかのように誤解釈し、シフト演算の規則に違反すると判断してコンパイルエラーを出力していた可能性があります。Go言語では、浮動小数点数をシフト演算の左オペランドにすることはできません(例: 1.0 << sは、1.0int型に変換可能であれば合法ですが、そうでなければ不正です)。

このコミットで追加されたテストケースは、まさにこの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. 型付けされていない定数 1 が、シフト演算の左オペランドとして使用された際に、文脈(ここではg関数の引数)に基づいて正しくint型に推論されること。
  2. 変数 s がシフト量として使用された際に、それがuint型であっても正しくシフト演算が実行されること。
  3. シフト演算 1 << s の結果が、int型として正しく評価され、g関数に渡されること。

このテストが成功することは、gccgoがGo言語のシフト演算に関する型推論とセマンティクスを、gcコンパイラと同様に正しく実装していることを意味します。

関連リンク

参考にした情報源リンク