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

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

このコミットは、GoランタイムのアセンブリコードにおけるTEXTディレクティブのフラグ表現を、数値からシンボリック定数へと変更するものです。また、NOPROFDUPOKという特定のフラグが多くの関数から削除されています。これにより、アセンブリコードの可読性と保守性が向上し、Goリンカの進化に合わせた変更が加えられています。

コミット

commit 0273dc131e4d5c63875824784e4240d0c8bb23bc
Author: Keith Randall <khr@golang.org>
Date:   Wed Aug 7 12:20:05 2013 -0700

    runtime: convert .s textflags from numbers to symbolic constants.
    Remove NOPROF/DUPOK from everything.
    
    Edits done with a script, except pclinetest.asm which depended
    on the DUPOK flag on main().
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/12613044

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

https://github.com/golang/go/commit/0273dc131e4d5c63875824784e4240d0c8bb23bc

元コミット内容

runtime: convert .s textflags from numbers to symbolic constants.
Remove NOPROF/DUPOK from everything.

Edits done with a script, except pclinetest.asm which depended
on the DUPOK flag on main().

R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12613044

変更の背景

この変更の主な背景には、Goのアセンブリコードの可読性と保守性の向上が挙げられます。以前のGoのアセンブリでは、TEXTディレクティブに続くフラグが数値で表現されていました。これは、各ビットが特定の意味を持つビットマスクとして機能していましたが、数値だけではその意味を直感的に理解することが困難でした。

また、NOPROFDUPOKという特定のフラグが多くの関数から削除されています。これは、Goのプロファイリングツールやリンカの機能が進化し、これらのフラグが不要になったか、あるいは異なる方法で処理されるようになったためと考えられます。特にDUPOKは、同じ名前のシンボルが複数存在することを許可するフラグであり、リンカの挙動に影響を与えます。pclinetest.asmmain()関数がDUPOKフラグに依存していたという記述から、このフラグが特定のテストケースや特殊なリンキングシナリオで用いられていたことが示唆されます。

この変更は、スクリプトによって広範囲に適用されており、Go開発チームがコードベース全体の品質と一貫性を向上させるための継続的な取り組みの一環であることがわかります。

前提知識の解説

Goアセンブリ

Go言語は、そのランタイムや一部の標準ライブラリにおいて、パフォーマンスが要求される部分やOSとのインタフェーションのためにアセンブリ言語を使用しています。Goのアセンブリは、AT&T構文に似ていますが、Go独自の擬似命令やレジスタの命名規則を持っています。

Goアセンブリの関数定義は、通常TEXTディレクティブで始まります。 TEXT symbol(SB), flags, $framesize-argsize

  • symbol(SB): 関数のシンボル名。SBは"static base"を意味し、グローバルシンボルであることを示します。
  • flags: 関数の特性を示すフラグ。このコミットの変更対象です。
  • $framesize: 関数のスタックフレームサイズ。
  • argsize: 関数の引数の合計サイズ。

TEXTディレクティブのフラグ(旧形式と新形式)

GoのアセンブリにおけるTEXTディレクティブのフラグは、関数の挙動やリンカに対する指示を制御するために使用されます。

旧形式(数値): このコミット以前は、フラグは数値のビットマスクとして表現されていました。例えば、TEXT runtime·memclr(SB),7,$0-8のように、7という数値がフラグとして使われていました。この7は、バイナリで111となり、複数のフラグが同時に設定されていることを意味します。しかし、この数値だけでは、どのフラグが設定されているのかを即座に判断することは困難でした。

新形式(シンボリック定数): このコミットにより、数値の代わりにシンボリック定数が使用されるようになりました。例えば、TEXT runtime·memclr(SB),NOSPLIT,$0-8のように、NOSPLITというシンボリック定数が使われています。これにより、フラグの意味が明確になり、コードの可読性が大幅に向上します。

このコミットで言及されている主なフラグは以下の通りです。

  • NOPROF: このフラグが設定された関数は、プロファイリングの対象から除外されます。Goのプロファイラは、実行時間の統計を収集するために関数呼び出しを計測しますが、NOPROFが設定されていると、その関数は計測されません。これは、非常に頻繁に呼び出される低レベルのランタイム関数などで、プロファイリングのオーバーヘッドを避けたい場合に使用されていました。
  • DUPOK: このフラグは、同じ名前のシンボルが複数存在することをリンカに許可します。通常、リンカは同じ名前のシンボルが複数定義されているとエラーを報告しますが、DUPOKが設定されている場合は、そのエラーを抑制します。これは、特定のプラットフォームや特殊なリンキングシナリオで、意図的に重複するシンボルを許可する必要がある場合に使用されていました。
  • NOSPLIT: このフラグは、関数がスタックの分割(stack split)を行わないことを示します。Goのランタイムは、必要に応じて関数のスタックを動的に拡張する「スタック分割」というメカニズムを持っています。しかし、一部の低レベル関数やアセンブリ関数では、スタック分割のオーバーヘッドを避けたり、スタックの挙動を厳密に制御したりするために、NOSPLITが使用されます。このフラグが設定された関数は、呼び出し時に十分なスタック空間があることを前提とします。

../../cmd/ld/textflag.h

このコミットで多くのファイルに追加されている#include "../../cmd/ld/textflag.h"は、Goのリンカ(cmd/ld)が使用するtextflag.hというヘッダファイルをインクルードしていることを示します。このヘッダファイルには、NOSPLITなどのシンボリック定数が定義されており、アセンブリコード内でこれらの定数を使用できるようにします。これにより、アセンブリコードとリンカの間でフラグの定義が共有され、一貫性が保たれます。

技術的詳細

このコミットの技術的詳細は、GoランタイムのアセンブリコードにおけるTEXTディレクティブのフラグ管理の近代化にあります。

  1. 数値フラグからシンボリック定数への移行: 以前は、TEXTディレクティブのフラグは数値(例: 7)で指定されていました。この数値はビットマスクとして解釈され、各ビットが特定のフラグ(例: NOSPLIT, NOPROF, DUPOKなど)に対応していました。例えば、7はバイナリで111であり、3つの異なるフラグが同時に設定されていることを意味します。しかし、この数値表現は、コードを読む開発者にとって直感的ではなく、どのフラグが設定されているのかを理解するためには、ビットマスクの定義を別途参照する必要がありました。 このコミットでは、この数値表現をNOSPLITのようなシンボリック定数に置き換えることで、コードの可読性を大幅に向上させています。これにより、開発者はフラグの意味を即座に理解できるようになり、コードの保守性が向上します。この変更は、#include "../../cmd/ld/textflag.h"をアセンブリファイルに追加することで実現されています。このヘッダファイルには、これらのシンボリック定数が定義されており、アセンブラがそれらを認識できるようにします。

  2. NOPROFDUPOKフラグの削除: コミットメッセージには、「Remove NOPROF/DUPOK from everything.」と明記されています。これは、これらのフラグがGoランタイムの進化に伴い、もはや必要とされなくなったことを示唆しています。

    • NOPROFの削除: NOPROFはプロファイリングの対象から関数を除外するためのフラグでした。このフラグが削除されたということは、Goのプロファイリングインフラが改善され、これらの低レベルのアセンブリ関数をプロファイリングしてもオーバーヘッドが許容範囲になったか、あるいはプロファイリングの対象をより高レベルで制御するメカニズムが導入された可能性があります。これにより、Goプログラム全体のプロファイリングカバレッジが向上し、より正確なパフォーマンス分析が可能になります。
    • DUPOKの削除: DUPOKは、同じ名前のシンボルが複数存在することをリンカに許可するフラグでした。このフラグの削除は、Goのリンカがシンボル解決のロジックを改善し、重複シンボルをより適切に処理できるようになったか、あるいは重複シンボルを必要とするような特殊なケースがGoランタイムから排除されたことを意味します。pclinetest.asmmain()関数がDUPOKに依存していたという記述は、このフラグが特定のテストや特殊なリンキングシナリオで用いられていたことを示唆しており、その依存関係が解消されたことを意味します。
  3. スクリプトによる変更と例外処理: コミットメッセージには、「Edits done with a script, except pclinetest.asm which depended on the DUPOK flag on main().」とあります。これは、この変更が手作業ではなく、自動化されたスクリプトによって広範囲のファイルに適用されたことを示しています。これにより、変更の適用が効率的かつ一貫して行われたことがわかります。 しかし、pclinetest.asmだけは例外として手動で編集されたことが示されています。これは、pclinetest.asmmain()関数がDUPOKフラグに特殊な依存関係を持っていたため、スクリプトによる単純な置換では問題が発生する可能性があったことを意味します。この例外処理は、Go開発チームがコードベースの特定のコーナーケースや複雑な依存関係を慎重に扱っていることを示しています。

全体として、このコミットはGoランタイムの内部構造をより現代的で保守しやすいものにするための重要なステップであり、Goのツールチェイン(特にリンカとプロファイラ)の成熟を反映しています。

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

このコミットは、Goランタイムの多くのアセンブリファイルにわたる広範な変更を含んでいます。ここでは、代表的な変更箇所として、src/pkg/runtime/memclr_arm.ssrc/pkg/runtime/rt0_darwin_386.sの変更を抜粋して示します。

src/pkg/runtime/memclr_arm.s

--- a/src/pkg/runtime/memclr_arm.s
+++ b/src/pkg/runtime/memclr_arm.s
@@ -23,12 +23,14 @@
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // THE SOFTWARE.
 
+#include "../../cmd/ld/textflag.h"
+
 TO = 8
 TOE = 11
 N = 12
 TMP = 12				/* N and TMP don't overlap */
 
-TEXT runtime·memclr(SB),7,$0-8
+TEXT runtime·memclr(SB),NOSPLIT,$0-8
 	MOVW	ptr+0(FP), R(TO)
 	MOVW	n+4(FP), R(N)
 	MOVW	$0, R(0)

src/pkg/runtime/rt0_darwin_386.s

--- a/src/pkg/runtime/rt0_darwin_386.s
+++ b/src/pkg/runtime/rt0_darwin_386.s
@@ -2,7 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-TEXT _rt0_386_darwin(SB),7,$8
+#include "../../cmd/ld/textflag.h"
+
+TEXT _rt0_386_darwin(SB),NOSPLIT,$8
 	MOVL	8(SP), AX
 	LEAL	12(SP), BX
 	MOVL	AX, 0(SP)
@@ -10,5 +12,5 @@ TEXT _rt0_386_darwin(SB),7,$8
 	CALL	main(SB)
 	INT	$3
 
-TEXT main(SB),7,$0
+TEXT main(SB),NOSPLIT,$0
 	JMP	_rt0_go(SB)

コアとなるコードの解説

上記のコード変更は、Goアセンブリにおける関数定義のTEXTディレクティブのフラグ部分に焦点を当てています。

src/pkg/runtime/memclr_arm.s の変更解説

  • #include "../../cmd/ld/textflag.h" の追加: この行は、Goのリンカが使用するtextflag.hヘッダファイルをインクルードしています。このヘッダファイルには、NOSPLITのようなシンボリック定数が定義されており、アセンブリコード内でこれらの定数を使用できるようにします。これにより、数値のビットマスクを直接記述する代わりに、意味のある名前でフラグを指定できるようになります。

  • TEXT runtime·memclr(SB),7,$0-8 から TEXT runtime·memclr(SB),NOSPLIT,$0-8 への変更:

    • 旧形式 (7): 以前は、runtime·memclr関数のフラグとして数値の7が指定されていました。この7は、バイナリで111であり、複数のフラグ(この場合はNOSPLIT, NOPROF, DUPOK)がビットマスクとして設定されていることを意味していました。しかし、この数値だけでは、どのフラグが設定されているのかをコードを読むだけでは判断できませんでした。
    • 新形式 (NOSPLIT): 変更後、フラグはNOSPLITというシンボリック定数に置き換えられました。これは、この関数がスタック分割を行わないことを明確に示しています。また、この変更により、以前設定されていたNOPROFDUPOKフラグが明示的に削除されたことになります。これは、Goのプロファイリングやリンカの挙動が進化し、これらのフラグが不要になったためと考えられます。memclrのような低レベルのメモリ操作関数は、非常に頻繁に呼び出されるため、スタック分割のオーバーヘッドを避けるためにNOSPLITが適切です。

src/pkg/runtime/rt0_darwin_386.s の変更解説

このファイルは、Darwin (macOS) 上での386アーキテクチャ向けGoプログラムのランタイム初期化コード(rt0、runtime zero)を含んでいます。

  • #include "../../cmd/ld/textflag.h" の追加: memclr_arm.sと同様に、シンボリック定数を使用可能にするためにtextflag.hがインクルードされています。

  • TEXT _rt0_386_darwin(SB),7,$8 から TEXT _rt0_386_darwin(SB),NOSPLIT,$8 への変更:

    • 旧形式 (7): _rt0_386_darwin関数も、以前は数値7をフラグとして使用していました。
    • 新形式 (NOSPLIT): NOSPLITに変更されています。_rt0_386_darwinはプログラムのエントリポイントであり、Goランタイムの初期化を行う非常に重要な関数です。このような初期化コードでは、スタックの挙動を厳密に制御する必要があるため、NOSPLITフラグは理にかなっています。ここでもNOPROFDUPOKが削除されています。
  • TEXT main(SB),7,$0 から TEXT main(SB),NOSPLIT,$0 への変更:

    • 旧形式 (7): main関数(Goプログラムのユーザー定義のmain関数とは異なる、ランタイム内部のmainシンボル)も同様に数値7を使用していました。
    • 新形式 (NOSPLIT): NOSPLITに変更されています。このmainシンボルは、_rt0_goというGoランタイムのメインエントリポイントにジャンプする役割を担っています。これもまた、初期化パスの一部であり、スタックの挙動を予測可能にするためにNOSPLITが適切です。

これらの変更は、Goのアセンブリコードの可読性を向上させ、リンカやプロファイラの進化に合わせてフラグの管理を合理化するという、Go開発チームの継続的な努力を反映しています。

関連リンク

参考にした情報源リンク