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

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

このコミットは、Goランタイムにおけるgogetcallerpc関数の修正と、アセンブリコードのgovetツールによるクリーンアップを目的としています。また、Goランタイムの内部的な文字列構造体のリファクタリングと、C言語からGo言語への移行に関するコメントの更新も含まれています。

コミット

commit 14c8143c31fc38fc661188247aa6d3b9d25ae394
Author: Keith Randall <khr@golang.org>
Date:   Tue Jun 17 21:59:50 2014 -0700

    runtime: fix gogetcallerpc.
    
    Make assembly govet-clean.
    Clean up fixes for CL 93380044.
    
    LGTM=rsc
    R=rsc
    CC=golang-codereviews
    https://golang.org/cl/107160047

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

https://github.com/golang/go/commit/14c8143c31fc38fc661188247aa6d3b9d25ae394

元コミット内容

  • runtime: fix gogetcallerpc.
  • Make assembly govet-clean.
  • Clean up fixes for CL 93380044.

変更の背景

このコミットの主な目的は、Goランタイムの内部関数であるgogetcallerpcの修正と、Goのアセンブリコードがgovetツールによって「クリーン」であること、すなわちgovetの静的解析チェックをパスするようにすることです。

gogetcallerpcは、Goの実行環境において、現在のゴルーチン(Goの軽量スレッド)の呼び出し元のプログラムカウンタ(PC)を取得するために使用される重要な関数です。この情報は、デバッグ、プロファイリング、スタックトレースの生成など、ランタイムの低レベルな操作で不可欠となります。このコミットでは、この関数のアセンブリ実装における引数や戻り値の扱いをより明確にし、コードの可読性と正確性を向上させています。

また、「Make assembly govet-clean」という記述は、Goプロジェクトにおけるコード品質と一貫性への強いコミットメントを示しています。govetはGoのコードを静的に解析し、潜在的なバグや疑わしい構造を報告するツールですが、このコミットではアセンブリコードもこのツールのチェック対象となるように修正が加えられました。これにより、アセンブリレベルのコードであっても、Goの標準的な品質基準に準拠することが保証されます。

さらに、「Clean up fixes for CL 93380044」という記述は、以前の変更(Gerrit Change-ID CL 93380044)で導入された可能性のある問題や、改善の余地がある部分を整理し、コードベース全体の品質と一貫性を向上させるためのクリーンアップ作業であることを示唆しています。これは、大規模なプロジェクトにおいて、継続的なリファクタリングと改善が重要であることを物語っています。

前提知識の解説

このコミットの変更内容を深く理解するためには、以下の概念について基本的な知識が必要です。

  • プログラムカウンタ (PC): CPUの内部レジスタの一つで、次に実行されるべき命令のメモリ上のアドレスを保持しています。Goランタイムでは、gogetcallerpcのような関数を通じて、現在の実行コンテキスト(どの関数がどの場所から呼び出されたか)を特定するためにPCが利用されます。これは、エラー発生時のスタックトレースの生成や、デバッガがプログラムの実行フローを追跡する際に不可欠な情報です。

  • Goランタイム: Goプログラムの実行を管理する低レベルな部分を指します。これには、ガベージコレクション(GC)、ゴルーチンのスケジューリング、メモリ管理、プリミティブな型操作などが含まれます。Goランタイムの大部分はGo言語自体で書かれていますが、OSとのインタフェース部分や、極限のパフォーマンスが求められるクリティカルなパスでは、アセンブリ言語が使用されます。

  • アセンブリ言語: 特定のCPUアーキテクチャ(例: x86, ARM)の命令セットに直接対応する、非常に低レベルなプログラミング言語です。Goランタイムでは、Go言語では直接アクセスできないハードウェア機能の利用、またはGoコンパイラが生成するコードよりもさらに最適化されたコードが必要な場合にアセンブリが使用されます。アセンブリコードは、レジスタの操作、メモリへの直接アクセス、ジャンプ命令などを通じて、CPUの動作を直接制御します。

  • govet: Go言語の静的解析ツールの一つで、Goのソースコードを検査し、潜在的なバグや疑わしい構造を報告します。例えば、printfフォーマット文字列の不一致、到達不能なコード、誤った構造体タグの使用などを検出できます。このコミットのように、アセンブリコードに対しても、特定の命名規則や構造に関するチェックを行うことで、コードベース全体の品質と一貫性を維持する役割を担います。

  • NOSPLITディレクティブ: Goのアセンブリ関数で使用される特別なディレクティブです。このディレクティブが付与された関数は、スタックの拡張チェック(スタックが足りなくなった場合に自動的にスタックを拡張する処理)を行わないことを示します。これは、非常に小さく、スタックをほとんど消費しない、頻繁に呼び出される関数において、スタックチェックのオーバーヘッドを避けるために使用されます。ランタイムのパフォーマンスが重要な部分でよく見られます。

  • フレームポインタ (FP): 関数が呼び出された際に、その関数のスタックフレームの基点を示すレジスタです。スタックフレームには、関数の引数、ローカル変数、戻りアドレスなどが格納されます。x+0(FP)p+0(FP)のような表記は、フレームポインタからの相対的なオフセットを用いて、スタック上の特定のデータ(この場合は引数や戻り値)にアクセスしていることを意味します。

  • unsafe.Pointeruintptr: Go言語のunsafeパッケージは、Goの型安全性を意図的にバイパスし、低レベルなメモリ操作を可能にするための機能を提供します。

    • unsafe.Pointer: 任意の型のポインタに変換できる汎用ポインタです。これにより、異なる型のポインタ間で変換を行ったり、Goの型システムでは通常許可されないメモリ操作を行ったりすることが可能になります。
    • uintptr: ポインタのアドレスを保持できる整数型です。unsafe.Pointerと組み合わせて使用することで、ポインタのアドレスを整数として操作したり、整数アドレスをポインタに変換したりすることができます。これらは、Goの型システムでは表現できないような、メモリの直接操作や、Goの型とC言語の型との間の変換を行う際に使用されます。
  • Gerrit Change-ID (CL): Goプロジェクトでは、コードレビューと変更管理にGerritというシステムを使用しています。https://golang.org/cl/107160047のようなURLは、このコミットに対応するGerritの変更ページへのリンクであり、そこでは詳細なレビューのやり取りや、関連する変更履歴を確認することができます。

技術的詳細

このコミットは、Goランタイムの複数の側面に対する細かな、しかし重要な改善を含んでいます。

  • gogetcallerpc関数のアセンブリ実装の修正:

    • このコミットの主要な変更点の一つは、runtime·gogetcallerpc関数のアセンブリ実装における引数と戻り値の扱い方の改善です。具体的には、asm_386.sasm_amd64.sasm_amd64p32.sasm_arm.sといった各アーキテクチャのアセンブリファイルで、スタック上の引数へのアクセスを示すシンボル名がxからpへ、戻り値の格納先を示すシンボル名がrからretへと変更されました。
    • これらの変更は、機能的な動作を変えるものではありませんが、コードの意図をより明確にし、アセンブリコードの可読性を向上させます。また、govetのような静的解析ツールがアセンブリコードをより正確に理解し、潜在的な問題を検出できるようにするための「govet-clean」化の一環でもあります。Goのアセンブリコードは、Goコンパイラが生成するコードと連携して動作するため、その命名規則や構造の一貫性は、コードベース全体の健全性を保つ上で非常に重要です。
    • 特にamd64アーキテクチャ向けのruntime·gogetcallerpcでは、スタックフレームサイズがNOSPLIT,$0-8からNOSPLIT,$0-16に増加しました。これは、関数内でより多くのスタック領域が必要になったことを示唆しています。具体的な理由はコミットメッセージからは読み取れませんが、通常は、追加のレジスタの退避、一時的な変数の格納、または将来的な機能拡張のための準備など、内部的な処理の変更に伴うものです。
    • 一方で、amd64p32アーキテクチャ向けのruntime·stackguardでは、スタックフレームサイズがNOSPLIT,$0-16からNOSPLIT,$0-8に縮小されました。これは、スタック使用量の最適化、または不要なスタック領域の削除によるものと考えられます。これらのスタックフレームサイズの調整は、Goランタイムが異なるアーキテクチャで効率的に動作するための継続的な最適化の一部です。
  • string.goにおける文字列構造体のリファクタリング:

    • src/pkg/runtime/string.goファイルでは、cstringToGo関数内で、i := _string{}.lenという初期化がi := 0に修正されました。_string{}.lenは、Goの内部的な文字列構造体のゼロ値のlenフィールドを参照しており、結果的に0を返しますが、i := 0の方が初期化の意図がより明確で直接的です。これは、コードの可読性を向上させるための小さなリファクタリングです。
    • より重要な変更は、内部的な文字列構造体の型名が_stringからstringStructにリネームされ、その定義が明示的に追加されたことです。type stringStruct struct { str *byte; len int }という定義が追加され、cstringToGo関数内でt := (*_string)(unsafe.Pointer(&s))t := (*stringStruct)(unsafe.Pointer(&s))に変更されました。この変更は、Goランタイムの内部型に対する命名規則の標準化と、コードの自己文書化を促進するためのものです。stringStructは、Goの文字列が内部的にどのように表現されているか(バイト列へのポインタと長さ)を示す重要な構造体であり、その明確な定義は、ランタイムの動作を理解する上で役立ちます。
  • stubs.gocにおけるコメントの更新:

    • src/pkg/runtime/stubs.gocファイルでは、GoランタイムのC言語からGo言語への移行("conversion")に関するコメントが更新されました。以前のコメントは、変換中にスプリッタブルな関数を呼び出すことが一時的に許可されることを示唆していましたが、新しいコメントは、「これらの不変条件はまだ確立されていないが、CからGoへの変換が完了すれば確立される」という、より長期的な目標と現状を明確にしています。
    • これは、Goランタイムの進化における重要なマイルストーンと、その過程での設計上の考慮事項を示しています。GoランタイムのコードベースをC言語からGo言語へ移行させることは、Go言語の自己ホスティング能力を高め、開発の効率化、そして将来的な最適化の可能性を広げるための重要な戦略です。このコメントの更新は、その移行プロセスが進行中であり、特定の設計上の制約(例: 関数がブロックしない、NOSPLIT関数のみを呼び出す)が最終的には厳密に適用されることを示唆しています。

これらの変更は、Goランタイムの堅牢性、パフォーマンス、そしてメンテナンス性を継続的に向上させるための、Go開発チームによる細心の注意と努力を反映しています。

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

以下に、このコミットにおける主要なコード変更箇所を抜粋して示します。

src/pkg/runtime/asm_386.s

--- a/src/pkg/runtime/asm_386.s
+++ b/src/pkg/runtime/asm_386.s
@@ -782,9 +782,9 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$0-4
 	RET
 
 TEXT runtime·gogetcallerpc(SB),NOSPLIT,$0-8
-	MOVL	x+0(FP),AX		// addr of first arg
+	MOVL	p+0(FP),AX		// addr of first arg
 	MOVL	-4(AX),AX		// get calling pc
-	MOVL	AX, r+4(FP)
+	MOVL	AX, ret+4(FP)
 	RET
 
 TEXT runtime·setcallerpc(SB),NOSPLIT,$0-8

src/pkg/runtime/asm_amd64.s

--- a/src/pkg/runtime/asm_amd64.s
+++ b/src/pkg/runtime/asm_amd64.s
@@ -858,10 +858,10 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$0-8
 	MOVQ	-8(AX),AX		// get calling pc
 	RET
 
-TEXT runtime·gogetcallerpc(SB),NOSPLIT,$0-8
-	MOVQ	x+0(FP),AX		// addr of first arg
+TEXT runtime·gogetcallerpc(SB),NOSPLIT,$0-16
+	MOVQ	p+0(FP),AX		// addr of first arg
 	MOVQ	-8(AX),AX		// get calling pc
-	MOVQ	AX,r+4(FP)
+	MOVQ	AX,ret+8(FP)
 	RET
 
 TEXT runtime·setcallerpc(SB),NOSPLIT,$0-16

src/pkg/runtime/asm_amd64p32.s

--- a/src/pkg/runtime/asm_amd64p32.s
+++ b/src/pkg/runtime/asm_amd64p32.s
@@ -664,9 +664,9 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$0-8
 	RET
 
 TEXT runtime·gogetcallerpc(SB),NOSPLIT,$0-8
-	MOVL	x+0(FP),AX		// addr of first arg
+	MOVL	p+0(FP),AX		// addr of first arg
 	MOVL	-8(AX),AX		// get calling pc
-	MOVL	AX, r+4(FP)
+	MOVL	AX, ret+4(FP)
 	RET
 
 TEXT runtime·setcallerpc(SB),NOSPLIT,$0-16
@@ -686,7 +686,7 @@ TEXT runtime·cputicks(SB),NOSPLIT,$0-0
 	ADDQ	DX, AX
 	RET
 
-TEXT runtime·stackguard(SB),NOSPLIT,$0-16
+TEXT runtime·stackguard(SB),NOSPLIT,$0-8
 	MOVL	SP, DX
 	MOVL	DX, sp+0(FP)
 	get_tls(CX)

src/pkg/runtime/asm_arm.s

--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -561,7 +561,7 @@ TEXT runtime·getcallerpc(SB),NOSPLIT,$-4-4
 	RET
 
 TEXT runtime·gogetcallerpc(SB),NOSPLIT,$-4-8
-	MOVW	R14, 4(FP)
+	MOVW	R14, ret+4(FP)
 	RET
 
 TEXT runtime·setcallerpc(SB),NOSPLIT,$-4-8

src/pkg/runtime/string.go

--- a/src/pkg/runtime/string.go
+++ b/src/pkg/runtime/string.go
@@ -143,15 +143,19 @@ func slicerunetostring(a []rune) string {
 	return s[:size2]
 }
 
+type stringStruct struct {
+	str *byte
+	len int
+}
+
 func cstringToGo(str uintptr) (s string) {
 -	// Note: we need i to be the same type as _string.len and to start at 0.
 -	i := _string{}.len
 +	i := 0
 	for ; ; i++ {
 		if *(*byte)(unsafe.Pointer(str + uintptr(i))) == 0 {
 			break
 		}
 	}
 -	t := (*_string)(unsafe.Pointer(&s))\n +	t := (*stringStruct)(unsafe.Pointer(&s))\n 	t.str = (*byte)(unsafe.Pointer(str))
 	t.len = i
 	return

src/pkg/runtime/stubs.goc

--- a/src/pkg/runtime/stubs.goc
+++ b/src/pkg/runtime/stubs.goc
@@ -19,9 +19,8 @@ package runtime
 //    out to only NOSPLIT functions (recursively).
 // 2) Functions should not block.
 
-// During conversion, we can still call out to splittable
-// functions.  But once conversion is done the invariants
-// above should hold.
+// These invariants do not hold yet but will be established once we have
+// finished converting runtime support code from C to Go.
 
 #pragma textflag NOSPLIT
 func rawstring(size intgo) (s String, b Slice) {

コアとなるコードの解説

  • アセンブリファイルの変更 (asm_*.s):

    • runtime·gogetcallerpc関数におけるx+0(FP)からp+0(FP)への変更、およびr+4(FP)(またはr+8(FP))からret+4(FP)(またはret+8(FP))への変更は、スタック上の引数と戻り値へのアクセスをより明確なシンボル名で行うようにしたものです。これは、アセンブリコードの可読性を高め、govetのような静的解析ツールがコードの意図をより正確に把握できるようにするためのリファクタリングです。Goのアセンブリコードは、Goコンパイラが生成するコードと密接に連携するため、このような命名規則の統一は、コードベース全体の品質とメンテナンス性を向上させます。
    • amd64アーキテクチャのruntime·gogetcallerpcのスタックフレームサイズが$0-8から$0-16に増加したことは、この関数が内部的に使用するスタック領域が増えたことを示しています。これは、追加のレジスタの退避や、一時的な変数の格納など、内部処理の変更に伴うものと考えられます。
    • amd64p32アーキテクチャのruntime·stackguardのスタックフレームサイズが$0-16から$0-8に減少したことは、スタック使用量の最適化、または不要なスタック領域の削除によるものと考えられます。これらのスタックフレームサイズの調整は、Goランタイムが異なるアーキテクチャで効率的に動作するための継続的な最適化の一環です。
  • string.goの変更:

    • cstringToGo関数内のi := _string{}.lenからi := 0への変更は、初期化の意図をより明確にするためのコードの改善です。_string{}.lenは常に0を返すため、機能的な変更はありませんが、コードの可読性が向上します。
    • _string型がstringStructにリネームされ、その定義が明示的に追加されたことは、Goランタイムの内部型に対する命名規則の標準化と、コードの自己文書化を促進するための重要な変更です。stringStructは、Goの文字列が内部的にどのように表現されているか(バイト列へのポインタと長さ)を示す重要な構造体であり、その明確な定義は、ランタイムの動作を理解する上で役立ちます。
  • stubs.gocのコメント変更:

    • このコメントの更新は、GoランタイムがC言語からGo言語への移行プロセスにあることを明確に示しています。以前のコメントは、変換中に特定の不変条件が一時的に緩和されることを示唆していましたが、新しいコメントは、これらの不変条件がまだ完全に確立されていないものの、CからGoへの変換が完了すれば確立されるという、より長期的な目標と現状を明確にしています。これは、Goランタイムの設計思想と、その進化の方向性を示す重要な情報であり、Go言語の自己ホスティング能力を高めるための継続的な取り組みを反映しています。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメントおよびソースコード内のコメント。
  • Goコミュニティの議論やメーリングリスト(GerritのCLページなど)。
  • アセンブリ言語の一般的な知識(特にx86, ARMアーキテクチャのスタックフレームとレジスタの利用法)。
  • unsafeパッケージとuintptrに関するGoのドキュメント。