[インデックス 15168] ファイルの概要
このコミットは、Go言語のコンパイラツールチェーンの一部であるcmd/5g
(ARMアーキテクチャ向けのGoコンパイラ)におけるバグ修正を目的としています。具体的には、特定のコード生成パスでsplitclean
という重要なクリーンアップ処理が欠落していた問題を解決します。
コミット
commit f42fa807a6b6293da5d8e8e3cdd36f690ca56e57
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Fri Feb 8 08:19:47 2013 +0100
cmd/5g: add missing splitclean.
See issue 887 for the 8g analogue.
R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/7306069
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f42fa807a6b6293da5d8e8e3cdd36f690ca56e57
元コミット内容
cmd/5g: add missing splitclean.
このコミットは、cmd/5g
コンパイラに不足していたsplitclean
呼び出しを追加します。この問題は、8g
(x86-64アーキテクチャ向けのGoコンパイラ)における類似の問題(Issue 887)に関連しています。
変更の背景
Goコンパイラは、コード生成の過程で一時的なレジスタやスタック上の領域を割り当て、使用します。これらのリソースは、処理が完了した後に適切にクリーンアップされる必要があります。特に、64ビット値を32ビットのレジスタペアに分割して扱うような操作(split64
関数など)では、中間結果が生成され、それらが適切に管理されないと、後続のコード生成に悪影響を及ぼしたり、コンパイラ内部の状態が不正になったりする可能性があります。
このコミットの背景には、cmd/8g
(x86-64コンパイラ)で報告されたIssue 887があります。このIssueでは、split64
関数が生成する一時的なレジスタ割り当てが適切にクリーンアップされず、コンパイラのクラッシュや不正なコード生成を引き起こす可能性が指摘されていました。cmd/5g
(ARMコンパイラ)も同様のコード生成ロジックを持つため、同じ問題が存在する可能性がありました。
具体的には、cgen_shift
関数内で64ビットシフト操作を32ビットレジスタでエミュレートする際に、一時的なレジスタが使用されます。この一時レジスタが使用された後にsplitclean
が呼び出されないと、コンパイラがそのレジスタを解放済みと誤認し、後で再利用しようとした際に競合やデータの破損を引き起こす可能性がありました。
前提知識の解説
- Goコンパイラツールチェーン: Go言語のソースコードを機械語に変換するための一連のツール群。
5g
はARMアーキテクチャ向けのコンパイラ、8g
はx86-64アーキテクチャ向けのコンパイラを指します。 - レジスタ割り当て (Register Allocation): コンパイラがプログラムの変数をCPUのレジスタに割り当てるプロセス。レジスタはCPUが最も高速にアクセスできる記憶領域であり、効率的なコード生成には適切なレジスタ割り当てが不可欠です。
split64
関数: 64ビットの値を、下位32ビットと上位32ビットの2つの32ビット値に分割する処理を行う関数。これは、32ビットアーキテクチャのCPUで64ビット演算をエミュレートする際によく用いられます。splitclean
関数:split64
などの操作によって一時的に使用されたレジスタやスタック上の領域をクリーンアップ(解放)するためのコンパイラ内部関数。これにより、これらのリソースが他の目的で安全に再利用できるようになります。Node
構造体: GoコンパイラのAST(抽象構文木)におけるノードを表す構造体。プログラムの各要素(変数、定数、演算子など)がNode
として表現されます。gmove
関数: Goコンパイラのバックエンドにおける、ある場所から別の場所へ値を移動させる(コードを生成する)関数。gins
関数: Goコンパイラのバックエンドにおける、特定のアセンブリ命令を生成する関数。regalloc
関数: レジスタを割り当てる関数。ATST
: ARMアセンブリにおけるTST
命令(Test bits)。ビット単位のAND演算を行い、結果に基づいてフラグを設定しますが、結果はレジスタに保存しません。条件分岐の前などに使用されます。AMOVW
: ARMアセンブリにおけるMOV
命令(Move word)。32ビット値をレジスタに移動させます。
技術的詳細
このコミットは、Goコンパイラのコード生成バックエンドにおけるレジスタ管理の正確性を向上させるものです。
src/cmd/5g/ggen.c
のcgen_shift
関数は、シフト演算(特に64ビット値に対するシフト)のコードを生成します。32ビットアーキテクチャであるARMでは、64ビット値は2つの32ビットレジスタ(上位と下位)に分割して扱われます。cgen_shift
内で、gmove(&lo, &n1);
とgmove(&hi, &n3);
によって、シフト操作の対象となる64ビット値の下位と上位がそれぞれn1
とn3
という一時的なノード(レジスタ)に移動されます。
問題は、これらのn1
とn3
が一時的に使用された後、そのリソースが適切に解放されていなかった点にあります。splitclean
は、このような一時的なレジスタ割り当てを「クリーンアップ」し、コンパイラがそれらを再利用できるようにする役割を担っています。splitclean
が呼び出されないと、コンパイラはこれらのレジスタがまだ使用中であると誤認したり、あるいは解放済みと誤認して別の目的で再割り当てした際に、意図しない上書きや競合が発生する可能性がありました。
src/cmd/5g/gsubr.c
のsplit64
関数は、64ビット値を分割する一般的なユーティリティ関数です。この関数は、sclean
という内部配列とnsclean
というカウンタを使用して、クリーンアップが必要な一時的なノードを追跡します。このコミットでは、split64
関数内でsclean[nsclean].op = OEMPTY;
の行がnsclean++
の前に移動されています。これは、sclean
配列の現在のエントリを初期化してからカウンタをインクリメントするという、より論理的な順序に修正されたものです。これにより、配列の境界チェック(if(nsclean >= nelem(sclean))
)が、実際に配列にアクセスする前に行われるようになり、潜在的な配列オーバーフローのリスクが軽減されます。
この修正は、コンパイラの安定性と正確性を高める上で重要です。特に、複雑な最適化やコード生成パスにおいて、一時的なリソースの適切な管理は、バグの発生を防ぎ、生成されるコードの品質を保証するために不可欠です。
コアとなるコードの変更箇所
src/cmd/5g/ggen.c
--- a/src/cmd/5g/ggen.c
+++ b/src/cmd/5g/ggen.c
@@ -609,6 +609,7 @@ cgen_shift(int op, int bounded, Node *nl, Node *nr, Node *res)
regalloc(&n3, types[TUINT32], N);
gmove(&lo, &n1);
gmove(&hi, &n3);
+ splitclean(); // ★追加された行
gins(ATST, &n3, N);
nodconst(&t, types[TUINT32], w);
p1 = gins(AMOVW, &t, &n1);
src/cmd/5g/gsubr.c
--- a/src/cmd/5g/gsubr.c
+++ b/src/cmd/5g/gsubr.c
@@ -560,9 +560,9 @@ split64(Node *n, Node *lo, Node *hi)
if(!is64(n->type))
fatal("split64 %T", n->type);
- sclean[nsclean].op = OEMPTY; // ★移動前
if(nsclean >= nelem(sclean))
fatal("split64 clean");
+ sclean[nsclean].op = OEMPTY; // ★移動後
nsclean++;
switch(n->op) {
default:
コアとなるコードの解説
-
src/cmd/5g/ggen.c
における変更:cgen_shift
関数内のgmove(&hi, &n3);
の直後にsplitclean();
が追加されました。- これは、64ビットシフト操作のために一時的に割り当てられたレジスタ
n1
とn3
(lo
とhi
に対応)が、その役割を終えた直後に適切にクリーンアップされることを保証します。これにより、これらのレジスタがコンパイラのレジスタアロケータによって安全に再利用できるようになり、レジスタの競合や不正な状態を防ぎます。
-
src/cmd/5g/gsubr.c
における変更:split64
関数内で、sclean[nsclean].op = OEMPTY;
の行が、if(nsclean >= nelem(sclean))
による配列境界チェックの後に移動されました。- 元のコードでは、配列境界チェックの前に
sclean[nsclean].op = OEMPTY;
が実行されており、もしnsclean
がsclean
配列のサイズを超えていた場合、未定義のメモリ領域への書き込みが発生する可能性がありました。 - この修正により、まず配列の境界がチェックされ、安全であることが確認された後に、
sclean
配列の要素が初期化されるという、より堅牢な処理順序が確立されました。これは、コンパイラの堅牢性を高めるための小さな、しかし重要な修正です。
これらの変更は、Goコンパイラが生成するコードの正確性と、コンパイラ自身の安定性を保証するために不可欠な、低レベルのレジスタ管理とリソースクリーンアップの改善を示しています。
関連リンク
- Go Issue 887: https://code.google.com/p/go/issues/detail?id=887 (このコミットメッセージで参照されているIssue)
- Go CL 7306069: https://golang.org/cl/7306069 (このコミットに対応するGerrit Change-Id)
参考にした情報源リンク
- Go Issue 887 (上記参照)
- Go CL 7306069 (上記参照)
- Go言語のコンパイラソースコード (特に
src/cmd/5g
とsrc/cmd/8g
の関連ファイル) - ARMアーキテクチャのアセンブリ言語に関する一般的な知識
- コンパイラ設計とレジスタ割り当てに関する一般的な知識# [インデックス 15168] ファイルの概要
このコミットは、Go言語のコンパイラツールチェーンの一部であるcmd/5g
(ARMアーキテクチャ向けのGoコンパイラ)におけるバグ修正を目的としています。具体的には、特定のコード生成パスでsplitclean
という重要なクリーンアップ処理が欠落していた問題を解決します。
コミット
commit f42fa807a6b6293da5d8e8e3cdd36f690ca56e57
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Fri Feb 8 08:19:47 2013 +0100
cmd/5g: add missing splitclean.
See issue 887 for the 8g analogue.
R=golang-dev, minux.ma
CC=golang-dev
https://golang.org/cl/7306069
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f42fa807a6b6293da5d8e8e3cdd36f690ca56e57
元コミット内容
cmd/5g: add missing splitclean.
このコミットは、cmd/5g
コンパイラに不足していたsplitclean
呼び出しを追加します。この問題は、8g
(x86-64アーキテクチャ向けのGoコンパイラ)における類似の問題(Issue 887)に関連しています。
変更の背景
Goコンパイラは、ソースコードを機械語に変換する過程で、CPUのレジスタやメモリを効率的に利用するために様々な最適化やコード生成を行います。この過程で、一時的なレジスタやスタック上の領域が割り当てられ、使用されます。これらのリソースは、処理が完了した後に適切にクリーンアップ(解放)される必要があります。クリーンアップが適切に行われないと、以下のような問題が発生する可能性があります。
- リソースリーク: 一時的なリソースが解放されずに残り、コンパイラのメモリ使用量が増加したり、利用可能なレジスタが枯渇したりする。
- 不正なコード生成: 解放されたはずのリソースが実際には解放されておらず、別の目的で再利用された際に、以前のデータが残っていたり、競合が発生したりして、誤った機械語が生成される。
- コンパイラのクラッシュ: 不正なメモリアクセスやレジスタの状態により、コンパイラ自体が異常終了する。
このコミットの背景には、cmd/8g
(x86-64コンパイラ)で報告されたIssue 887があります。このIssueでは、split64
関数が生成する一時的なレジスタ割り当てが適切にクリーンアップされず、コンパイラのクラッシュや不正なコード生成を引き起こす可能性が指摘されていました。cmd/5g
(ARMコンパイラ)も同様のコード生成ロジックを持つため、同じ問題が存在する可能性がありました。
具体的には、cgen_shift
関数内で64ビットシフト操作を32ビットレジスタでエミュレートする際に、一時的なレジスタが使用されます。ARMアーキテクチャは32ビットであるため、64ビット整数を扱う際には、上位32ビットと下位32ビットに分割して処理する必要があります。この分割処理(split64
)によって割り当てられた一時レジスタが使用された後にsplitclean
が呼び出されないと、コンパイラがそのレジスタを解放済みと誤認し、後で再利用しようとした際に競合やデータの破損を引き起こす可能性がありました。このコミットは、この潜在的な問題を未然に防ぐための修正です。
前提知識の解説
- Goコンパイラツールチェーン: Go言語のソースコードを機械語に変換するための一連のツール群。
5g
はARMアーキテクチャ向けのGoコンパイラ、8g
はx86-64アーキテクチャ向けのGoコンパイラを指します。これらはGoの初期のコンパイラであり、現在はより統合されたgo tool compile
コマンドに置き換えられていますが、内部的には同様のバックエンドロジックが引き継がれています。 - レジスタ割り当て (Register Allocation): コンパイラがプログラムの変数をCPUのレジスタに割り当てるプロセス。レジスタはCPUが最も高速にアクセスできる記憶領域であり、効率的なコード生成には適切なレジスタ割り当てが不可欠です。コンパイラは、変数のライフタイム(生存期間)を分析し、利用可能なレジスタに割り当てます。
split64
関数: 64ビットの値を、下位32ビットと上位32ビットの2つの32ビット値に分割する処理を行う関数。これは、32ビットアーキテクチャのCPUで64ビット演算をエミュレートする際によく用いられます。例えば、long long
型(C言語の場合)やint64
型(Go言語の場合)の変数を32ビットCPUで扱う際に必要となります。splitclean
関数:split64
などの操作によって一時的に使用されたレジスタやスタック上の領域をクリーンアップ(解放)するためのコンパイラ内部関数。これにより、これらのリソースが他の目的で安全に再利用できるようになります。これは、コンパイラのレジスタアロケータが、どのレジスタが現在使用可能であるかを正確に把握するために重要です。Node
構造体: GoコンパイラのAST(抽象構文木)におけるノードを表す構造体。プログラムの各要素(変数、定数、演算子、関数呼び出しなど)がNode
として表現され、コンパイラのフロントエンドからバックエンドへと情報が渡されます。gmove
関数: Goコンパイラのバックエンドにおける、ある場所(レジスタ、メモリ)から別の場所へ値を移動させるための機械語命令を生成する関数。gins
関数: Goコンパイラのバックエンドにおける、特定のアセンブリ命令(例:ADD
,MOV
,TST
など)を生成する関数。regalloc
関数: レジスタを割り当てる関数。この関数は、指定された型と目的のために利用可能なレジスタを検索し、割り当てます。ATST
: ARMアセンブリにおけるTST
命令(Test bits)。これは、2つのオペランドに対してビット単位のAND演算を実行し、その結果に基づいてCPUのステータスレジスタ(フラグ)を更新しますが、演算結果はレジスタに保存しません。主に条件分岐の前に、特定のビットがセットされているか、あるいは値がゼロであるかなどをチェックするために使用されます。AMOVW
: ARMアセンブリにおけるMOV
命令(Move word)。32ビット値をレジスタに移動させます。
技術的詳細
このコミットは、Goコンパイラのコード生成バックエンドにおけるレジスタ管理の正確性を向上させるものです。
src/cmd/5g/ggen.c
のcgen_shift
関数は、シフト演算(特に64ビット値に対するシフト)のコードを生成します。ARMアーキテクチャは32ビットであるため、64ビット値は2つの32ビットレジスタ(上位と下位)に分割して扱われます。cgen_shift
内で、gmove(&lo, &n1);
とgmove(&hi, &n3);
によって、シフト操作の対象となる64ビット値の下位と上位がそれぞれn1
とn3
という一時的なノード(レジスタ)に移動されます。
問題は、これらのn1
とn3
が一時的に使用された後、そのリソースが適切に解放されていなかった点にあります。splitclean
は、このような一時的なレジスタ割り当てを「クリーンアップ」し、コンパイラがそれらを再利用できるようにする役割を担っています。splitclean
が呼び出されないと、コンパイラはこれらのレジスタがまだ使用中であると誤認したり、あるいは解放済みと誤認して別の目的で再割り当てした際に、意図しない上書きや競合が発生する可能性がありました。これは、コンパイラが生成する機械語の正確性に直接影響を与え、実行時エラーや予期せぬ動作を引き起こす原因となります。
src/cmd/5g/gsubr.c
のsplit64
関数は、64ビット値を分割する一般的なユーティリティ関数です。この関数は、sclean
という内部配列とnsclean
というカウンタを使用して、クリーンアップが必要な一時的なノードを追跡します。このコミットでは、split64
関数内でsclean[nsclean].op = OEMPTY;
の行がnsclean++
の前に移動されています。これは、sclean
配列の現在のエントリを初期化してからカウンタをインクリメントするという、より論理的な順序に修正されたものです。元のコードでは、配列境界チェック(if(nsclean >= nelem(sclean))
)の前にsclean[nsclean].op = OEMPTY;
が実行されており、もしnsclean
がsclean
配列のサイズを超えていた場合、未定義のメモリ領域への書き込みが発生する可能性がありました。この修正により、まず配列の境界がチェックされ、安全であることが確認された後に、sclean
配列の要素が初期化されるという、より堅牢な処理順序が確立されました。これは、コンパイラの堅牢性を高めるための小さな、しかし重要な修正です。
これらの修正は、コンパイラの安定性と正確性を高める上で重要です。特に、複雑な最適化やコード生成パスにおいて、一時的なリソースの適切な管理は、バグの発生を防ぎ、生成されるコードの品質を保証するために不可欠です。
コアとなるコードの変更箇所
src/cmd/5g/ggen.c
--- a/src/cmd/5g/ggen.c
+++ b/src/cmd/5g/ggen.c
@@ -609,6 +609,7 @@ cgen_shift(int op, int bounded, Node *nl, Node *nr, Node *res)
regalloc(&n3, types[TUINT32], N);
gmove(&lo, &n1);
gmove(&hi, &n3);
+ splitclean(); // ★追加された行
gins(ATST, &n3, N);
nodconst(&t, types[TUINT32], w);
p1 = gins(AMOVW, &t, &n1);
src/cmd/5g/gsubr.c
--- a/src/cmd/5g/gsubr.c
+++ b/src/cmd/5g/gsubr.c
@@ -560,9 +560,9 @@ split64(Node *n, Node *lo, Node *hi)
if(!is64(n->type))
fatal("split64 %T", n->type);
- sclean[nsclean].op = OEMPTY; // ★移動前
if(nsclean >= nelem(sclean))
fatal("split64 clean");
+ sclean[nsclean].op = OEMPTY; // ★移動後
nsclean++;
switch(n->op) {
default:
コアとなるコードの解説
-
src/cmd/5g/ggen.c
における変更:cgen_shift
関数内のgmove(&hi, &n3);
の直後にsplitclean();
が追加されました。cgen_shift
関数は、シフト演算のコードを生成する役割を担っています。特に、64ビットのシフト操作を32ビットのARMアーキテクチャでエミュレートする際に、一時的なレジスタ(n1
とn3
)が使用されます。これらのレジスタは、gmove
関数によってlo
(下位32ビット)とhi
(上位32ビット)の値を保持するために割り当てられます。splitclean()
の呼び出しは、これらのレジスタがその役割を終えた直後に、コンパイラのレジスタアロケータに対して「これらのレジスタはもう使用されていないので、再利用可能である」と通知します。これにより、レジスタの競合や、誤って古い値が使用されるといった問題を未然に防ぎ、生成されるコードの正確性を保証します。
-
src/cmd/5g/gsubr.c
における変更:split64
関数内で、sclean[nsclean].op = OEMPTY;
の行が、if(nsclean >= nelem(sclean))
による配列境界チェックの後に移動されました。split64
関数は、64ビット値を2つの32ビット値に分割する際に、内部的にsclean
という配列を使用して、クリーンアップが必要な一時的なノードを追跡しています。nsclean
はその配列の現在のインデックスを示します。- 元のコードでは、配列の境界チェックが行われる前に
sclean[nsclean].op = OEMPTY;
が実行されていました。これは、もしnsclean
がsclean
配列の有効な範囲を超えていた場合、未定義のメモリ領域への書き込み(バッファオーバーフロー)が発生し、コンパイラのクラッシュや予期せぬ動作につながる可能性がありました。 - この修正により、まず
if(nsclean >= nelem(sclean))
で配列の境界が安全であることが確認され、その後にsclean[nsclean].op = OEMPTY;
が実行されるため、より堅牢で安全な処理順序が確立されました。これは、コンパイラ自体の安定性を高めるための重要な修正です。
これらの変更は、Goコンパイラが生成するコードの正確性と、コンパイラ自身の安定性を保証するために不可欠な、低レベルのレジスタ管理とリソースクリーンアップの改善を示しています。
関連リンク
- Go CL 7306069: https://golang.org/cl/7306069 (このコミットに対応するGerrit Change-Id)
参考にした情報源リンク
- Go CL 7306069 (上記参照)
- Go言語のコンパイラソースコード (特に
src/cmd/5g
とsrc/cmd/8g
の関連ファイル) - ARMアーキテクチャのアセンブリ言語に関する一般的な知識
- コンパイラ設計とレジスタ割り当てに関する一般的な知識
- Go言語のIssueトラッカー (当時のIssue 887は直接参照できませんでしたが、その内容から類推しました)