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

[インデックス 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コンパイラのコードベースの整理と最適化があります。

  1. maxstksizeの削除:

    • Goコンパイラは、関数呼び出しに必要なスタックフレームのサイズを計算します。maxstksizeは、おそらく過去のバージョンで、関数の実行中に必要となる最大スタックサイズを追跡するために使用されていた変数と考えられます。
    • コミットメッセージには「6g(x86-64アーキテクチャ向けのGoコンパイラ)では使用されていない」と明記されており、これはmaxstksizeが他のアーキテクチャ向けコンパイラ(5g, 8g)でも不要になっていることを示唆しています。
    • Goコンパイラの進化に伴い、スタック管理のロジックが変更され、この変数が冗長になったか、より効率的な方法でスタックサイズが決定されるようになったため、デッドコードとして削除されました。
  2. c >= 4条件の削除:

    • Goコンパイラは、メモリ操作(特にゼロクリアやコピー)を行う際に、バイト単位ではなくワード単位(例えば4バイトや8バイト)で処理を行うことがあります。
    • c = w % 4という計算は、残りのバイト数(ワード境界に満たない部分)を処理するために使用されることが多いです。wが何らかのサイズを表し、それを4で割った余りcは、0, 1, 2, 3のいずれかの値しか取りません。
    • したがって、c >= 4という条件は数学的に常に偽となり、その内部のコードブロックは決して実行されないデッドコードとなります。このようなコードは、コンパイラの可読性を低下させ、誤解を招く可能性があるため、削除されました。

これらの変更は、Goコンパイラのコードベースをよりクリーンで理解しやすいものにし、将来的な開発やメンテナンスを容易にすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の知識が役立ちます。

  1. Goコンパイラの種類(5g, 8g, 6g):

    • Go言語の初期のコンパイラは、ターゲットアーキテクチャごとに異なる名前を持っていました。
      • 5g: ARMアーキテクチャ(例: ARMv5, ARMv6, ARMv7)向けのコンパイラ。
      • 8g: x86アーキテクチャ(32ビット)向けのコンパイラ。
      • 6g: x86-64アーキテクチャ(64ビット)向けのコンパイラ。
    • これらのコンパイラは、Goのソースコードを各アーキテクチャの機械語に変換する役割を担っていました。現在では、go buildコマンドが内部的に適切なコンパイラを選択するため、ユーザーが直接これらの名前を意識することは少なくなっています。
  2. スタックフレームとスタックサイズ:

    • 関数が呼び出されると、その関数に必要なローカル変数、引数、戻りアドレスなどを格納するために、メモリの「スタック」領域に「スタックフレーム」が割り当てられます。
    • stksizeは、現在の関数に必要なスタックの合計サイズを指します。
    • maxargは、関数が呼び出す可能性のある他の関数に渡す最大の引数サイズを指します。これは、呼び出し元が呼び出し先の引数をスタックにプッシュするために必要な領域です。
    • rnd(size, align)関数は、sizealignの倍数に切り上げる(アラインメントする)ために使用されます。これは、メモリのアクセス効率を向上させるためによく行われます。
    • widthptrは、ポインタのサイズ(32ビットシステムでは4バイト、64ビットシステムでは8バイト)を表す定数で、スタックのアラインメントによく使用されます。
  3. デッドコード(Dead Code):

    • プログラム内で決して実行されないコードのことです。デッドコードは、バグの原因となったり、コードの可読性を低下させたり、コンパイル時間を無駄にしたりするため、通常は削除されます。
  4. モジュロ演算子(%):

    • 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.cdefframe 関数:

    • 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を介さずに、直接stksizemaxargの合計をアラインメントしてframeサイズを計算しています。これは、Goのスタック管理がより洗練され、maxstksizeのような一時的な最大値を追跡する必要がなくなったことを示唆しています。Goのランタイムは、必要に応じてスタックを動的に拡張する(スタックの分割/スプリット)メカニズムを持っているため、コンパイル時に厳密な最大値を事前に計算して固定する必要性が薄れた可能性があります。

c >= 4条件の削除に関する詳細

  • src/cmd/8g/ggen.cclearfat 関数:
    • 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.hsrc/cmd/8g/gg.hからEXTERN int maxstksize;の行が削除されました。これは、この変数がもはやコンパイラ全体で必要とされないことを示しています。
  • src/cmd/5g/ggen.csrc/cmd/8g/ggen.cdefframe関数内で、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.cclearfat関数内で、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ブロックは、cw % 4の結果であるため、cが0, 1, 2, 3のいずれかの値しか取らないという数学的事実により、決して実行されることがありませんでした。
    • 結果として、while(c > 0)ループのみが残り、残りのバイトを1バイトずつゼロクリアする処理が実行されます。これにより、コンパイラのコードから到達不能なデッドコードが取り除かれ、コードの健全性が向上しました。

これらの変更は、Goコンパイラの内部ロジックをより効率的かつ正確にし、不要な複雑さを排除するものです。

関連リンク

参考にした情報源リンク

  • 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など)に関する情報源
  • スタックフレームとアラインメントに関するコンピュータサイエンスの基本概念