[インデックス 18653] ファイルの概要
このコミットは、Goコンパイラの5g(ARMアーキテクチャ向け)と8g(x86アーキテクチャ向け)におけるデッドコードの削除を目的としています。具体的には、maxstksizeという変数の使用を廃止し、c >= 4という条件が常に偽となる不要なコードブロックを削除しています。これにより、コンパイラのコードベースが整理され、保守性が向上します。
コミット
commit d97d37188a7e58250b2a655dc86dbf644637d034
Author: Josh Bleecher Snyder <josharian@gmail.com>
Date: Tue Feb 25 14:43:53 2014 -0800
5g, 8g: remove dead code
maxstksize is superfluous and appears to be vestigial. 6g does not use it.
c >= 4 cannot occur; c = w % 4.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/68750043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d97d37188a7e58250b2a655dc86dbf644637d034
元コミット内容
元のコミットメッセージは以下の通りです。
5g, 8g: remove dead code
maxstksize is superfluous and appears to be vestigial. 6g does not use it.
c >= 4 cannot occur; c = w % 4.
LGTM=rsc
R=rsc
CC=golang-codereviews
https://golang.org/cl/68750043
変更の背景
このコミットの背景には、Goコンパイラのコードベースの整理と最適化があります。
-
maxstksizeの削除:- Goコンパイラは、関数呼び出しに必要なスタックフレームのサイズを計算します。
maxstksizeは、おそらく過去のバージョンで、関数の実行中に必要となる最大スタックサイズを追跡するために使用されていた変数と考えられます。 - コミットメッセージには「
6g(x86-64アーキテクチャ向けのGoコンパイラ)では使用されていない」と明記されており、これはmaxstksizeが他のアーキテクチャ向けコンパイラ(5g,8g)でも不要になっていることを示唆しています。 - Goコンパイラの進化に伴い、スタック管理のロジックが変更され、この変数が冗長になったか、より効率的な方法でスタックサイズが決定されるようになったため、デッドコードとして削除されました。
- Goコンパイラは、関数呼び出しに必要なスタックフレームのサイズを計算します。
-
c >= 4条件の削除:- Goコンパイラは、メモリ操作(特にゼロクリアやコピー)を行う際に、バイト単位ではなくワード単位(例えば4バイトや8バイト)で処理を行うことがあります。
c = w % 4という計算は、残りのバイト数(ワード境界に満たない部分)を処理するために使用されることが多いです。wが何らかのサイズを表し、それを4で割った余りcは、0, 1, 2, 3のいずれかの値しか取りません。- したがって、
c >= 4という条件は数学的に常に偽となり、その内部のコードブロックは決して実行されないデッドコードとなります。このようなコードは、コンパイラの可読性を低下させ、誤解を招く可能性があるため、削除されました。
これらの変更は、Goコンパイラのコードベースをよりクリーンで理解しやすいものにし、将来的な開発やメンテナンスを容易にすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の知識が役立ちます。
-
Goコンパイラの種類(5g, 8g, 6g):
- Go言語の初期のコンパイラは、ターゲットアーキテクチャごとに異なる名前を持っていました。
5g: ARMアーキテクチャ(例: ARMv5, ARMv6, ARMv7)向けのコンパイラ。8g: x86アーキテクチャ(32ビット)向けのコンパイラ。6g: x86-64アーキテクチャ(64ビット)向けのコンパイラ。
- これらのコンパイラは、Goのソースコードを各アーキテクチャの機械語に変換する役割を担っていました。現在では、
go buildコマンドが内部的に適切なコンパイラを選択するため、ユーザーが直接これらの名前を意識することは少なくなっています。
- Go言語の初期のコンパイラは、ターゲットアーキテクチャごとに異なる名前を持っていました。
-
スタックフレームとスタックサイズ:
- 関数が呼び出されると、その関数に必要なローカル変数、引数、戻りアドレスなどを格納するために、メモリの「スタック」領域に「スタックフレーム」が割り当てられます。
stksizeは、現在の関数に必要なスタックの合計サイズを指します。maxargは、関数が呼び出す可能性のある他の関数に渡す最大の引数サイズを指します。これは、呼び出し元が呼び出し先の引数をスタックにプッシュするために必要な領域です。rnd(size, align)関数は、sizeをalignの倍数に切り上げる(アラインメントする)ために使用されます。これは、メモリのアクセス効率を向上させるためによく行われます。widthptrは、ポインタのサイズ(32ビットシステムでは4バイト、64ビットシステムでは8バイト)を表す定数で、スタックのアラインメントによく使用されます。
-
デッドコード(Dead Code):
- プログラム内で決して実行されないコードのことです。デッドコードは、バグの原因となったり、コードの可読性を低下させたり、コンパイル時間を無駄にしたりするため、通常は削除されます。
-
モジュロ演算子(%):
w % 4のようなモジュロ演算子は、wを4で割った余りを計算します。この結果は常に0、1、2、3のいずれかになります。
技術的詳細
このコミットは、Goコンパイラのバックエンドにおけるスタックフレームの管理と、特定のメモリ操作の最適化に関連する部分を修正しています。
maxstksizeの削除に関する詳細
-
src/cmd/5g/gg.hおよびsrc/cmd/8g/gg.h:- これらのヘッダーファイルから
EXTERN int maxstksize;の宣言が削除されました。EXTERNは、変数が外部リンケージを持つことを示すGoコンパイラ内部のキーワードです。これにより、maxstksizeがグローバルにアクセス可能な変数として定義されていたことがわかります。
- これらのヘッダーファイルから
-
src/cmd/5g/ggen.cおよびsrc/cmd/8g/ggen.cのdefframe関数:defframe関数は、Goコンパイラが関数のスタックフレームを定義する際に呼び出される重要な関数です。この関数内で、スタックフレームの最終的なサイズが決定されます。- 削除されたコード:
このコードは、if(stksize > maxstksize) maxstksize = stksize; frame = rnd(maxstksize+maxarg, widthptr); // ... maxstksize = 0;stksize(現在の関数のスタックサイズ)がmaxstksizeよりも大きい場合にmaxstksizeを更新し、そのmaxstksizeを使って最終的なframeサイズを計算していました。そして、関数の終わりにmaxstksizeを0にリセットしていました。これは、おそらく関数内で動的にスタックサイズが変化する可能性を考慮していたか、あるいはネストされた関数呼び出しの最大スタック使用量を追跡するためのものであったと推測されます。 - 変更後のコード:
変更後は、frame = rnd(stksize+maxarg, widthptr);maxstksizeを介さずに、直接stksizeとmaxargの合計をアラインメントしてframeサイズを計算しています。これは、Goのスタック管理がより洗練され、maxstksizeのような一時的な最大値を追跡する必要がなくなったことを示唆しています。Goのランタイムは、必要に応じてスタックを動的に拡張する(スタックの分割/スプリット)メカニズムを持っているため、コンパイル時に厳密な最大値を事前に計算して固定する必要性が薄れた可能性があります。
c >= 4条件の削除に関する詳細
src/cmd/8g/ggen.cのclearfat関数:clearfat関数は、おそらく大きなデータ構造("fat"な構造体や配列)をゼロクリアするために使用される関数です。- 削除されたコード:
このコードは、if(c >= 4) { gconreg(AMOVL, c, D_CX); gins(AREP, N, N); // repeat gins(ASTOSB, N, N); // STOB AL,*(DI)+ } else while(c > 0) { gins(ASTOSB, N, N); // STOB AL,*(DI)+ c--; }cの値に基づいてゼロクリアの戦略を分岐していました。c >= 4の場合、REP STOSB命令(x86アセンブリ)を使用して、cバイトを効率的にゼロクリアしようとしていました。REP STOSBは、CXレジスタで指定された回数だけALレジスタの内容をDIレジスタが指すメモリ位置に書き込み、DIをインクリメントする命令です。 - コミットメッセージにあるように、
c = w % 4であるため、cは常に0, 1, 2, 3のいずれかです。したがって、c >= 4という条件は決して真になりません。このため、ifブロック内のコードは実行されることがなく、デッドコードでした。 - 変更後は、
while(c > 0)ループのみが残り、cが0になるまで1バイトずつゼロクリアするSTOSB命令が実行されます。これは、残りの1〜3バイトを処理するのに十分効率的であり、不要な分岐を削除することでコードが簡素化されました。
コアとなるコードの変更箇所
src/cmd/5g/gg.h
--- a/src/cmd/5g/gg.h
+++ b/src/cmd/5g/gg.h
@@ -28,7 +28,6 @@ EXTERN Node* panicindex;
EXTERN Node* panicslice;
EXTERN Node* throwreturn;
extern long unmappedzero;
-EXTERN int maxstksize;
/*
* gen.c
src/cmd/5g/ggen.c
--- a/src/cmd/5g/ggen.c
+++ b/src/cmd/5g/ggen.c
@@ -22,11 +22,8 @@ defframe(Prog *ptxt)
ptxt->to.offset2 = rnd(curfn->type->argwid, widthptr);
// fill in final stack size
- if(stksize > maxstksize)
- maxstksize = stksize;
- frame = rnd(maxstksize+maxarg, widthptr);
+ frame = rnd(stksize+maxarg, widthptr);
ptxt->to.offset = frame;
- maxstksize = 0;
p = ptxt;
if(stkzerosize > 0) {
src/cmd/8g/gg.h
--- a/src/cmd/8g/gg.h
+++ b/src/cmd/8g/gg.h
@@ -31,7 +31,6 @@ EXTERN Node* panicindex;
EXTERN Node* panicslice;
EXTERN Node* panicdiv;
EXTERN Node* throwreturn;
-EXTERN int maxstksize;
extern uint32 unmappedzero;
src/cmd/8g/ggen.c
--- a/src/cmd/8g/ggen.c
+++ b/src/cmd/8g/ggen.c
@@ -21,11 +21,8 @@ defframe(Prog *ptxt)
ptxt->to.offset2 = rnd(curfn->type->argwid, widthptr);
// fill in final stack size
- if(stksize > maxstksize)
- maxstksize = stksize;
- frame = rnd(maxstksize+maxarg, widthptr);
+ frame = rnd(stksize+maxarg, widthptr);
ptxt->to.offset = frame;
- maxstksize = 0;
// insert code to contain ambiguously live variables
// so that garbage collector only sees initialized values
@@ -137,11 +134,6 @@ clearfat(Node *nl)
q--;
}
- if(c >= 4) {
- gconreg(AMOVL, c, D_CX);
- gins(AREP, N, N); // repeat
- gins(ASTOSB, N, N); // STOB AL,*(DI)+
- } else
while(c > 0) {
gins(ASTOSB, N, N); // STOB AL,*(DI)+
c--;
コアとなるコードの解説
maxstksizeの削除
src/cmd/5g/gg.hとsrc/cmd/8g/gg.hからEXTERN int maxstksize;の行が削除されました。これは、この変数がもはやコンパイラ全体で必要とされないことを示しています。src/cmd/5g/ggen.cとsrc/cmd/8g/ggen.cのdefframe関数内で、maxstksizeを更新し、それを使用してframeサイズを計算するロジックが削除されました。- 変更前:
if(stksize > maxstksize) maxstksize = stksize; frame = rnd(maxstksize+maxarg, widthptr); // ... maxstksize = 0; - 変更後:
frame = rnd(stksize+maxarg, widthptr);
stksize)と最大引数サイズ(maxarg)のみに基づいて直接計算されるようになりました。これは、Goランタイムのスタック管理戦略が進化し、コンパイル時にmaxstksizeを追跡する必要がなくなったことを明確に示しています。- 変更前:
c >= 4条件の削除
src/cmd/8g/ggen.cのclearfat関数内で、if(c >= 4)という条件分岐とその内部のコードブロックが完全に削除されました。- 削除されたコード:
if(c >= 4) { gconreg(AMOVL, c, D_CX); gins(AREP, N, N); // repeat gins(ASTOSB, N, N); // STOB AL,*(DI)+ } else - この
ifブロックは、cがw % 4の結果であるため、cが0, 1, 2, 3のいずれかの値しか取らないという数学的事実により、決して実行されることがありませんでした。 - 結果として、
while(c > 0)ループのみが残り、残りのバイトを1バイトずつゼロクリアする処理が実行されます。これにより、コンパイラのコードから到達不能なデッドコードが取り除かれ、コードの健全性が向上しました。
- 削除されたコード:
これらの変更は、Goコンパイラの内部ロジックをより効率的かつ正確にし、不要な複雑さを排除するものです。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Go言語のコンパイラに関するドキュメント(一般的な情報): https://go.dev/doc/
参考にした情報源リンク
- Go言語のコミット履歴(GitHub): https://github.com/golang/go/commits/master
- Go言語のコードレビューシステム (Gerrit): https://go-review.googlesource.com/ (コミットメッセージに記載されている
https://golang.org/cl/68750043は、Gerritの変更リストへのリンクです。) - Goコンパイラの内部構造に関する一般的な情報源(例: "Go compiler internals"などで検索)
- x86アセンブリ命令(
REP STOSBなど)に関する情報源 - スタックフレームとアラインメントに関するコンピュータサイエンスの基本概念