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

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

このコミットは、Goコンパイラのcmd/8g(386アーキテクチャ向けコンパイラ)におけるレジスタリークのバグを修正するものです。具体的には、シフト操作のコード生成時にsplitcleanの呼び出しが不足していた問題を解決し、レジスタの適切な解放を保証します。

コミット

commit 7594440ef134ddebc2864d207815eb325adda13f
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 7 17:55:25 2013 -0500

    cmd/8g: add a few missing splitclean
    
    Fixes #887.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7303061

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

https://github.com/golang/go/commit/7594440ef134ddebc2864d207815eb325adda13f

元コミット内容

cmd/8g: add a few missing splitclean

このコミットは、cmd/8gコンパイラにおいて、いくつかのsplitclean呼び出しが不足していた箇所を追加するものです。これにより、Go issue #887で報告された問題が修正されます。

変更の背景

この変更は、Go言語のIssue #887「8g leaks registers on shift operations」を修正するために行われました。この問題は、386アーキテクチャ向けのGoコンパイラ(8g)が、特定のシフト操作(<<>>)のコードを生成する際に、一時的に使用したレジスタを適切に解放しないために発生するレジスタリークでした。

レジスタリークが発生すると、コンパイラが利用可能なレジスタを使い果たし、結果としてコード生成の効率が低下したり、最悪の場合、コンパイルエラーや不正なコードが生成される可能性がありました。特に、uint64のような64ビット値を扱うシフト操作では、32ビットアーキテクチャ上で複数のレジスタを組み合わせて64ビット値を表現するため、レジスタ管理がより複雑になります。この複雑さの中で、splitcleanの呼び出し忘れがレジスタリークを引き起こしていました。

前提知識の解説

  • Goコンパイラ (cmd/8g): Go言語の初期のコンパイラ群は、ターゲットアーキテクチャごとに命名されていました。8gはIntel 386(32ビット)アーキテクチャ向けのGoコンパイラを指します。Goのコンパイラは、ソースコードを中間表現に変換し、最終的にターゲットアーキテクチャの機械語に変換する役割を担います。
  • レジスタ (Registers): CPU内部にある高速な記憶領域で、プログラムの実行中にデータやアドレスを一時的に保持するために使用されます。レジスタの数は限られており、効率的なプログラム実行のためには、レジスタを適切に割り当て、使用後に解放する「レジスタ割り当て (Register Allocation)」が非常に重要です。
  • レジスタリーク (Register Leak): プログラム(この場合はコンパイラ)が、一時的に使用したレジスタを解放し忘れることで、そのレジスタが不必要に占有され続ける状態を指します。これにより、利用可能なレジスタが減少し、コンパイラがより低速なメモリ操作に頼らざるを得なくなったり、レジスタ不足に陥る可能性があります。
  • splitclean: Goコンパイラのバックエンドにおける内部関数の一つで、主に64ビット値を32ビットアーキテクチャ上で扱う際に、その値を構成する上位32ビットと下位32ビット(hilo)を一時的に保持するために割り当てられたレジスタを解放・クリーンアップする役割を担います。Goコンパイラでは、split64関数が64ビット値をlohiの2つの32ビット値に分割し、これらをレジスタに割り当てます。splitcleanは、これらのレジスタが不要になったときに、それらをレジスタプールに戻すためのメカニズムです。

技術的詳細

Goコンパイラ(特に8g)は、64ビット整数型(uint64など)を32ビットアーキテクチャ上で処理する際に、その値を2つの32ビット部分(上位と下位)に分割して扱います。この分割処理はsplit64関数によって行われ、分割された各部分は一時的にレジスタに割り当てられます。

問題となっていたのは、シフト操作(cgen_shift関数内で処理される)のコード生成パスにおいて、このsplit64によって割り当てられた一時レジスタが、操作完了後に適切にsplitcleanによって解放されていなかった点です。特に、シフト量が64ビット値で表現される場合や、シフト量が定数でない場合に、このレジスタリークが発生していました。

splitcleanは、コンパイラのレジスタ割り当てシステムの一部として機能し、一時レジスタのライフサイクルを管理します。splitcleanが呼び出されないと、これらのレジスタは「使用中」とマークされたままになり、他の目的で再利用できなくなります。これが繰り返されると、コンパイラが利用できるレジスタが枯渇し、レジスタ割り当ての失敗や、よりコストの高いメモリへのスピル(レジスタの内容をメモリに退避させること)が発生し、コンパイルされたコードの性能低下につながります。

このコミットでは、cgen_shift関数内の特定のコードパス、特にシフト量を表すノード(nr)が64ビット幅を持つ場合や、シフト量が定数でない場合に、splitclean()の呼び出しを追加しています。これにより、シフト操作のために一時的に使用されたレジスタが、操作が完了した直後にシステムに返却され、レジスタリークが防止されます。

また、gsubr.cにおけるsplit64関数の変更は、sclean配列(splitcleanが管理するレジスタクリーンアップリスト)への追加ロジックの順序を修正するものです。これは、sclean配列がオーバーフローする可能性のあるエッジケースを修正し、splitcleanメカニズム自体の堅牢性を高めるためのものです。

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

  1. src/cmd/8g/ggen.c:

    • cgen_shift関数内で、split64(&nt, &lo, &hi);の直後にsplitclean();が追加されました。
    • cgen_shift関数内で、p1 = gbranch(optoas(OLT, types[TUINT32]), T, +1);の直後にsplitclean();が追加されました。
  2. src/cmd/8g/gsubr.c:

    • split64関数内で、if(nsclean >= nelem(sclean))のチェックの前にあったsclean[nsclean].op = OEMPTY;の行が、チェックの後に移動されました。
  3. test/fixedbugs/issue887.go:

    • レジスタリークを再現する新しいテストケースが追加されました。このテストは、byte型の変数とuint64型のシフト量を用いたシフト操作をswitch文で複数回実行することで、問題が顕在化するように設計されています。

コアとなるコードの解説

  • src/cmd/8g/ggen.cにおける変更: cgen_shift関数は、Goのシフト演算子(<<, >>)のコードを生成する役割を担っています。この関数内で、シフト量(nr)が64ビット型である場合や、シフト量が動的に決定される場合に、split64関数が呼び出され、64ビットのシフト量を2つの32ビットレジスタに分割して扱います。 追加されたsplitclean();の呼び出しは、これらの分割されたシフト量が一時的に使用された直後に、それらを保持していたレジスタを解放することを保証します。これにより、レジスタが不必要に占有され続けることがなくなり、レジスタリークが解消されます。

  • src/cmd/8g/gsubr.cにおける変更: split64関数は、64ビットのノードを2つの32ビットのノード(lohi)に分割し、それらをレジスタに割り当てます。sclean配列は、splitcleanが後でクリーンアップする必要があるノードを追跡するために使用されます。 元のコードでは、sclean[nsclean].op = OEMPTY;nscleanの境界チェックの前に実行されていました。これは、nscleanが配列の最大値に達している場合に、配列の範囲外アクセスを引き起こす可能性がありました。変更後、この代入は境界チェックの後に移動され、sclean配列へのアクセスが常に安全であることを保証します。これは、splitcleanメカニズム全体の堅牢性を高めるための重要な修正です。

  • test/fixedbugs/issue887.goの追加: このテストファイルは、8gコンパイラがレジスタリークを起こしていた具体的なシナリオを再現します。f(x byte, y uint64)関数内で、yuint64型)をシフト量としてxをシフトする操作をswitch文で複数回繰り返しています。このようなパターンは、コンパイラがレジスタを繰り返し割り当て、解放し忘れることで、レジスタリークが顕在化する典型的なケースです。このテストの追加により、将来的に同様の回帰バグが発生しないことを保証します。

これらの変更により、8gコンパイラはシフト操作においてレジスタを適切に管理できるようになり、コンパイルの安定性と効率が向上しました。

関連リンク

参考にした情報源リンク

このコミットは、Goコンパイラのcmd/8g(386アーキテクチャ向けコンパイラ)におけるレジスタリークのバグを修正するものです。具体的には、シフト操作のコード生成時にsplitcleanの呼び出しが不足していた問題を解決し、レジスタの適切な解放を保証します。

コミット

commit 7594440ef134ddebc2864d207815eb325adda13f
Author: Russ Cox <rsc@golang.org>
Date:   Thu Feb 7 17:55:25 2013 -0500

    cmd/8g: add a few missing splitclean
    
    Fixes #887.
    
    R=ken2
    CC=golang-dev
    https://golang.org/cl/7303061

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

https://github.com/golang/go/commit/7594440ef134ddebc2864d207815eb325adda13f

元コミット内容

cmd/8g: add a few missing splitclean

このコミットは、cmd/8gコンパイラにおいて、いくつかのsplitclean呼び出しが不足していた箇所を追加するものです。これにより、Go issue #887で報告された問題が修正されます。

変更の背景

この変更は、Go言語のIssue #887「8g leaks registers on shift operations」を修正するために行われました。この問題は、386アーキテクチャ向けのGoコンパイラ(8g)が、特定のシフト操作(<<>>)のコードを生成する際に、一時的に使用したレジスタを適切に解放しないために発生するレジスタリークでした。

レジスタリークが発生すると、コンパイラが利用可能なレジスタを使い果たし、結果としてコード生成の効率が低下したり、最悪の場合、コンパイルエラーや不正なコードが生成される可能性がありました。特に、uint64のような64ビット値を扱うシフト操作では、32ビットアーキテクチャ上で複数のレジスタを組み合わせて64ビット値を表現するため、レジスタ管理がより複雑になります。この複雑さの中で、splitcleanの呼び出し忘れがレジスタリークを引き起こしていました。

前提知識の解説

  • Goコンパイラ (cmd/8g): Go言語の初期のコンパイラ群は、ターゲットアーキテクチャごとに命名されていました。8gはIntel 386(32ビット)アーキテクチャ向けのGoコンパイラを指します。Goのコンパイラは、ソースコードを中間表現に変換し、最終的にターゲットアーキテクチャの機械語に変換する役割を担います。
  • レジスタ (Registers): CPU内部にある高速な記憶領域で、プログラムの実行中にデータやアドレスを一時的に保持するために使用されます。レジスタの数は限られており、効率的なプログラム実行のためには、レジスタを適切に割り当て、使用後に解放する「レジスタ割り当て (Register Allocation)」が非常に重要です。
  • レジスタリーク (Register Leak): プログラム(この場合はコンパイラ)が、一時的に使用したレジスタを解放し忘れることで、そのレジスタが不必要に占有され続ける状態を指します。これにより、利用可能なレジスタが減少し、コンパイラがより低速なメモリ操作に頼らざるを得なくなったり、レジスタ不足に陥る可能性があります。
  • splitclean: Goコンパイラのバックエンドにおける内部関数の一つで、主に64ビット値を32ビットアーキテクチャ上で扱う際に、その値を構成する上位32ビットと下位32ビット(hilo)を一時的に保持するために割り当てられたレジスタを解放・クリーンアップする役割を担います。Goコンパイラでは、split64関数が64ビット値をlohiの2つの32ビット値に分割し、これらをレジスタに割り当てます。splitcleanは、これらのレジスタが不要になったときに、それらをレジスタプールに戻すためのメカニズムです。

技術的詳細

Goコンパイラ(特に8g)は、64ビット整数型(uint64など)を32ビットアーキテクチャ上で処理する際に、その値を2つの32ビット部分(上位と下位)に分割して扱います。この分割処理はsplit64関数によって行われ、分割された各部分は一時的にレジスタに割り当てられます。

問題となっていたのは、シフト操作(cgen_shift関数内で処理される)のコード生成パスにおいて、このsplit64によって割り当てられた一時レジスタが、操作完了後に適切にsplitcleanによって解放されていなかった点です。特に、シフト量が64ビット値で表現される場合や、シフト量が定数でない場合に、このレジスタリークが発生していました。

splitcleanは、コンパイラのレジスタ割り当てシステムの一部として機能し、一時レジスタのライフサイクルを管理します。splitcleanが呼び出されないと、これらのレジスタは「使用中」とマークされたままになり、他の目的で再利用できなくなります。これが繰り返されると、コンパイラが利用できるレジスタが枯渇し、レジスタ割り当ての失敗や、よりコストの高いメモリへのスピル(レジスタの内容をメモリに退避させること)が発生し、コンパイルされたコードの性能低下につながります。

このコミットでは、cgen_shift関数内の特定のコードパス、特にシフト量を表すノード(nr)が64ビット幅を持つ場合や、シフト量が定数でない場合に、splitclean()の呼び出しを追加しています。これにより、シフト操作のために一時的に使用されたレジスタが、操作が完了した直後にシステムに返却され、レジスタリークが防止されます。

また、gsubr.cにおけるsplit64関数の変更は、sclean配列(splitcleanが管理するレジスタクリーンアップリスト)への追加ロジックの順序を修正するものです。これは、sclean配列がオーバーフローする可能性のあるエッジケースを修正し、splitcleanメカニズム自体の堅牢性を高めるためのものです。

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

  1. src/cmd/8g/ggen.c:

    • cgen_shift関数内で、split64(&nt, &lo, &hi);の直後にsplitclean();が追加されました。
    • cgen_shift関数内で、p1 = gbranch(optoas(OLT, types[TUINT32]), T, +1);の直後にsplitclean();が追加されました。
  2. src/cmd/8g/gsubr.c:

    • split64関数内で、if(!is64(n->type))のチェックの後にあったsclean[nsclean].op = OEMPTY;の行が、if(nsclean >= nelem(sclean))のチェックの後に移動されました。
  3. test/fixedbugs/issue887.go:

    • レジスタリークを再現する新しいテストケースが追加されました。このテストは、byte型の変数とuint64型のシフト量を用いたシフト操作をswitch文で複数回実行することで、問題が顕在化するように設計されています。

コアとなるコードの解説

  • src/cmd/8g/ggen.cにおける変更: cgen_shift関数は、Goのシフト演算子(<<, >>)のコードを生成する役割を担っています。この関数内で、シフト量(nr)が64ビット型である場合や、シフト量が動的に決定される場合に、split64関数が呼び出され、64ビットのシフト量を2つの32ビットレジスタに分割して扱います。 追加されたsplitclean();の呼び出しは、これらの分割されたシフト量が一時的に使用された直後に、それらを保持していたレジスタを解放することを保証します。これにより、レジスタが不必要に占有され続けることがなくなり、レジスタリークが解消されます。

  • src/cmd/8g/gsubr.cにおける変更: split64関数は、64ビットのノードを2つの32ビットのノード(lohi)に分割し、それらをレジスタに割り当てます。sclean配列は、splitcleanが後でクリーンアップする必要があるノードを追跡するために使用されます。 元のコードでは、sclean[nsclean].op = OEMPTY;nscleanの境界チェックの前に実行されていました。これは、nscleanが配列の最大値に達している場合に、配列の範囲外アクセスを引き起こす可能性がありました。変更後、この代入は境界チェックの後に移動され、sclean配列へのアクセスが常に安全であることを保証します。これは、splitcleanメカニズム全体の堅牢性を高めるための重要な修正です。

  • test/fixedbugs/issue887.goの追加: このテストファイルは、8gコンパイラがレジスタリークを起こしていた具体的なシナリオを再現します。f(x byte, y uint64)関数内で、yuint64型)をシフト量としてxをシフトする操作をswitch文で複数回繰り返しています。このようなパターンは、コンパイラがレジスタを繰り返し割り当て、解放し忘れることで、レジスタリークが顕在化する典型的なケースです。このテストの追加により、将来的に同様の回帰バグが発生しないことを保証します。

これらの変更により、8gコンパイラはシフト操作においてレジスタを適切に管理できるようになり、コンパイルの安定性と効率が向上しました。

関連リンク

参考にした情報源リンク