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

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

コミット

commit 263777752701a7cc180cbf19c319f897d821d78f
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Nov 27 01:42:01 2012 +0800

    runtime: duplicate code for runtime.nanotime to avoid stack overflow in vDSO clock_gettime
    Fixes #4402.
    
    R=remyoudompheng, shivakumar.gn, random0x00, rsc
    CC=golang-dev
    https://golang.org/cl/6842063

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

https://github.com/golang/go/commit/263777752701a7cc180cbf19c319f897d821d78f

元コミット内容

runtime: duplicate code for runtime.nanotime to avoid stack overflow in vDSO clock_gettime Fixes #4402.

このコミットは、Goランタイムにおいて、runtime.nanotime関数がvDSO clock_gettimeを呼び出す際に発生する可能性のあるスタックオーバーフローを回避するために、コードを重複させたことを示しています。これは、GoのIssue #4402を修正するものです。

変更の背景

このコミットの背景には、Linuxシステムにおける時間取得のメカニズムと、Goランタイムのスタック管理の特性が関係しています。特に、vDSO (virtual Dynamic Shared Object) を介したシステムコールと、Goの軽量なゴルーチンスタックの相互作用が問題を引き起こしていました。

time.nowruntime.nanotimeは、Goプログラム内で現在時刻を取得するために使用される関数です。これらの関数は、Linuxシステムでは通常、clock_gettimegettimeofdayといったシステムコールを呼び出します。これらのシステムコールは、パフォーマンス向上のため、vDSOと呼ばれるカーネル空間のコードをユーザー空間にマッピングして直接呼び出すことで、システムコールトラップのオーバーヘッドを削減しています。

しかし、vDSOclock_gettimeの実装は、特定の条件下で比較的大きなスタック空間を必要とすることがありました。Goのゴルーチンは、初期スタックサイズが非常に小さく(当時は数KB程度)、必要に応じて動的に拡張されます。time.nowからruntime.nanotimeを呼び出すような関数呼び出しの連鎖があった場合、vDSO clock_gettimeが要求するスタック空間が、Goランタイムが確保しているスタック空間を超過し、スタックオーバーフローを引き起こす可能性がありました。

この問題は、特にGoのランタイム内部で頻繁に時刻取得が行われる場合や、スタックがすでに限界に近い状況でnanotimeが呼び出される場合に顕在化しました。スタックオーバーフローはプログラムのクラッシュに直結するため、安定性に関わる重要な問題でした。

前提知識の解説

vDSO (virtual Dynamic Shared Object)

vDSOは、Linuxカーネルがユーザー空間に提供する共有ライブラリのようなものです。これにより、ユーザープログラムは、gettimeofdayclock_gettimeのような一部のシステムコールを、カーネルモードへの切り替えなしに直接実行できます。これにより、システムコールトラップのオーバーヘッドが削減され、これらの操作のパフォーマンスが大幅に向上します。vDSOは、カーネルが管理するメモリ領域にマッピングされ、ユーザープログラムからは通常の共有ライブラリのように見えますが、実際にはカーネルの一部です。

clock_gettimegettimeofday

  • clock_gettime: POSIX標準で定義されている関数で、高精度な時間情報を提供します。様々なクロックID(例: CLOCK_REALTIMECLOCK_MONOTONIC)を指定して、異なる種類の時間を取得できます。CLOCK_REALTIMEはシステムの実時間(壁時計時間)を返します。
  • gettimeofday: システムの現在の時刻とタイムゾーン情報を取得する関数です。マイクロ秒単位の精度を提供しますが、clock_gettimeに比べて精度や機能の面で劣る場合があります。

Goランタイムのスタック管理

Goのゴルーチンは、非常に軽量なスレッドのようなものです。各ゴルーチンは独自のスタックを持ち、そのスタックは最初は非常に小さく(数KB)、必要に応じて自動的に拡張されます。この「スタックの動的拡張」はGoの並行処理モデルの重要な要素ですが、スタックの拡張にはある程度のコストがかかり、また、スタックの拡張が間に合わない場合や、特定の条件下でスタックが不足する場合にはスタックオーバーフローが発生する可能性があります。

アセンブリ言語 (x86-64)

このコミットで変更されているファイルはsrc/pkg/runtime/sys_linux_amd64.sであり、これはx86-64アーキテクチャ向けのGoランタイムのアセンブリコードです。アセンブリコードは、CPUが直接実行できる機械語に近い低レベルの言語であり、レジスタ操作、メモリ操作、関数呼び出し規約などを直接制御します。

  • TEXT: Goのアセンブリでは、関数の定義を開始するために使用されます。
  • MOVQ: 64ビットの値を移動する命令です。
  • CMPQ: 64ビットの値を比較する命令です。
  • JEQ: 比較結果が等しい場合にジャンプする命令です。
  • LEAQ: アドレスをレジスタにロードする命令です。
  • CALL: 関数を呼び出す命令です。
  • RET: 関数から戻る命令です。
  • SP: スタックポインタレジスタ。現在のスタックの最上位を指します。
  • FP: フレームポインタレジスタ。現在のスタックフレームのベースを指します。
  • AX, DX, DI, SI: 汎用レジスタ。関数呼び出しの引数や戻り値、一時的な計算に使用されます。

技術的詳細

このコミットの核心は、runtime.nanotime関数がtime.now関数を呼び出すことによって発生するスタックオーバーフローの問題を解決することです。

元のコードでは、runtime.nanotimeは単にtime.nowを呼び出し、その結果からナノ秒部分を抽出していました。

TEXT runtime·nanotime(SB), 7, $32
	CALL	time·now(SB)
	MOVQ	0(SP), AX	// sec
	MOVL	8(SP), DX	// nsec
	// ...

ここで注目すべきは、time.nowが呼び出される際に、その関数が自身のスタックフレームを確保し、さらにvDSO clock_gettimevDSO gettimeofdayが呼び出される際に、これらの関数もスタックを使用する点です。time.nowのコメントにあるように、「We're guaranteed 128 bytes on entry, and we've taken 16, and the call uses another 8. That leaves 104 for the gettime code to use. Hope that's enough!」という記述から、スタック空間が非常に限られていることがわかります。

time.nowvDSO clock_gettimeを呼び出す際、clock_gettimeは引数としてtimespec構造体へのポインタを受け取ります。この構造体はスタック上に配置されます。同様に、gettimeofdaytimeval構造体へのポインタを受け取ります。これらの構造体のサイズは小さいですが、vDSOの実装によっては、内部でさらにスタックを使用する可能性があります。

runtime.nanotimetime.nowを呼び出すことで、スタックフレームが二重に積み重なり、さらにvDSOの呼び出しが加わることで、Goのゴルーチンに割り当てられた初期スタックサイズを超過し、スタックオーバーフローが発生するリスクが高まりました。

この問題を解決するために、コミットではruntime.nanotimeの内部でtime.nowのロジックを重複させました。つまり、runtime.nanotimeはもはやtime.nowを呼び出すのではなく、time.nowが実行していたvDSO clock_gettimeまたはvDSO gettimeofdayを直接呼び出すようになりました。

これにより、runtime.nanotimeが呼び出された際に、time.nowのスタックフレームを介する必要がなくなり、スタックの使用量を削減できます。コミットメッセージにある「duplicate code for runtime.nanotime to avoid stack overflow in vDSO clock_gettime」という説明は、このスタック使用量の削減が目的であることを明確に示しています。

具体的には、time.nowruntime.nanotimeの両方で、__vdso_clock_gettime_sym(またはフォールバックとして__vdso_gettimeofday_sym)を直接呼び出すコードパスが独立して存在することになります。これにより、関数呼び出しの深さが浅くなり、スタックオーバーフローのリスクが低減されます。

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

変更はsrc/pkg/runtime/sys_linux_amd64.sファイルに集中しています。

--- a/src/pkg/runtime/sys_linux_amd64.s
+++ b/src/pkg/runtime/sys_linux_amd64.s
@@ -101,36 +101,59 @@ TEXT runtime·mincore(SB),7,$0-24
  	RET
  
  // func now() (sec int64, nsec int32)
-TEXT time·now(SB), 7, $32
+TEXT time·now(SB),7,$16
+\t// Be careful. We're calling a function with gcc calling convention here.
+\t// We're guaranteed 128 bytes on entry, and we've taken 16, and the
+\t// call uses another 8.
+\t// That leaves 104 for the gettime code to use. Hope that's enough!
  	MOVQ	runtime·__vdso_clock_gettime_sym(SB), AX
  	CMPQ	AX, $0
  	JEQ	fallback_gtod
  	MOVL	$0, DI // CLOCK_REALTIME
-\tLEAQ	8(SP), SI
+\tLEAQ	0(SP), SI
  	CALL	AX
-\tMOVQ	8(SP), AX	// sec
-\tMOVQ	16(SP), DX	// nsec
+\tMOVQ	0(SP), AX	// sec
+\tMOVQ	8(SP), DX	// nsec
  	MOVQ	AX, sec+0(FP)
  	MOVL	DX, nsec+8(FP)
  	RET
  fallback_gtod:
-\tLEAQ	8(SP), DI
+\tLEAQ	0(SP), DI
  	MOVQ	$0, SI
  	MOVQ	runtime·__vdso_gettimeofday_sym(SB), AX
  	CALL	AX
-\tMOVQ	8(SP), AX	// sec
-\tMOVL	16(SP), DX	// usec
+\tMOVQ	0(SP), AX	// sec
+\tMOVL	8(SP), DX	// usec
  	IMULQ	$1000, DX
  	MOVQ	AX, sec+0(FP)
  	MOVL	DX, nsec+8(FP)
  	RET
  
-TEXT runtime·nanotime(SB), 7, $32
-\tCALL	time·now(SB)
+TEXT runtime·nanotime(SB),7,$16
+\t// Duplicate time.now here to avoid using up precious stack space.
+\t// See comment above in time.now.
+\tMOVQ	runtime·__vdso_clock_gettime_sym(SB), AX
+\tCMPQ	AX, $0
+\tJEQ	fallback_gtod_nt
+\tMOVL	$0, DI // CLOCK_REALTIME
+\tLEAQ	0(SP), SI
+\tCALL	AX
  	MOVQ	0(SP), AX	// sec
-\tMOVL	8(SP), DX	// nsec
-\n-\t// sec is in AX, usec in DX
+\tMOVQ	8(SP), DX	// nsec
+\t// sec is in AX, nsec in DX
+\t// return nsec in AX
+\tIMULQ	$1000000000, AX
+\tADDQ	DX, AX
+\tRET
+fallback_gtod_nt:
+\tLEAQ	0(SP), DI
+\tMOVQ	$0, SI
+\tMOVQ	runtime·__vdso_gettimeofday_sym(SB), AX
+\tCALL	AX
+\tMOVQ	0(SP), AX	// sec
+\tMOVL	8(SP), DX	// usec
+\tIMULQ	$1000, DX
+\t// sec is in AX, nsec in DX
  	// return nsec in AX
  	IMULQ	$1000000000, AX
  	ADDQ	DX, AX

コアとなるコードの解説

この変更は、time·nowruntime·nanotimeという2つのアセンブリ関数の定義に影響を与えています。

  1. time·now関数の変更:

    • TEXT time·now(SB), 7, $32TEXT time·now(SB),7,$16 に変更されています。これは、関数のスタックフレームサイズが32バイトから16バイトに削減されたことを示唆しています。これは、スタックの使用量を全体的に減らすための最適化の一部である可能性があります。
    • LEAQ 8(SP), SILEAQ 0(SP), SI に変更されています。これは、clock_gettimegettimeofdayに渡すtimespecまたはtimeval構造体のアドレスを、スタックポインタのオフセット8からオフセット0に変更したことを意味します。つまり、スタックフレームの開始位置に直接構造体を配置するように変更されたと考えられます。これにより、スタックの利用効率が向上します。
    • MOVQ 8(SP), AXMOVQ 16(SP), DXMOVQ 0(SP), AXMOVQ 8(SP), DX に変更されています。これは、clock_gettimegettimeofdayからの戻り値(秒とナノ秒/マイクロ秒)をスタックのオフセット8と16から、オフセット0と8に変更したことを示しています。これは、上記のLEAQの変更と一貫しており、スタックレイアウトの変更を反映しています。
  2. runtime·nanotime関数の変更:

    • 最も重要な変更は、CALL time·now(SB) の行が削除され、代わりにtime·now関数とほぼ同じロジックがruntime·nanotime内に直接コピーされたことです。
    • TEXT runtime·nanotime(SB), 7, $32TEXT runtime·nanotime(SB),7,$16 に変更され、time·nowと同様にスタックフレームサイズが削減されています。
    • time·nowと同様に、MOVQ runtime·__vdso_clock_gettime_sym(SB), AX から始まるvDSO clock_gettimeの呼び出しロジックと、そのフォールバックであるvDSO gettimeofdayの呼び出しロジックがruntime·nanotime内に複製されています。
    • 新しいコメント // Duplicate time.now here to avoid using up precious stack space. が追加され、このコード重複の意図が明確に説明されています。

この変更により、runtime.nanotimetime.nowを呼び出すという関数呼び出しのネストが解消され、runtime.nanotimeは直接vDSOの時刻取得関数を呼び出すようになりました。これにより、runtime.nanotimeが実行される際のスタック使用量が削減され、特にスタックが逼迫している状況でのスタックオーバーフローのリスクが大幅に低減されました。

関連リンク

  • Go Issue #4402 (元のIssueトラッカーでの情報が見つからないため、直接的なリンクは提供できませんが、コミットメッセージに記載されています)
  • Goのランタイムに関するドキュメント (Goのバージョンによって異なるため、一般的な情報源となります)

参考にした情報源リンク

  • Goのコミット履歴: https://github.com/golang/go/commit/263777752701a7cc180cbf19c319f897d821d78f
  • Linux vDSOに関する一般的な情報 (例: Wikipedia, Linuxカーネルのドキュメント)
  • x86-64アセンブリ言語の命令セットに関する情報
  • Goのランタイムとスタック管理に関する情報 (Goの公式ドキュメントやブログ記事など)
  • clock_gettimegettimeofdayに関するmanページやPOSIX標準のドキュメント

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

コミット

commit 263777752701a7cc180cbf19c319f897d821d78f
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Tue Nov 27 01:42:01 2012 +0800

    runtime: duplicate code for runtime.nanotime to avoid stack overflow in vDSO clock_gettime
    Fixes #4402.
    
    R=remyoudompheng, shivakumar.gn, random0x00, rsc
    CC=golang-dev
    https://golang.org/cl/6842063

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

https://github.com/golang/go/commit/263777752701a7cc180cbf19c319f897d821d78f

元コミット内容

runtime: duplicate code for runtime.nanotime to avoid stack overflow in vDSO clock_gettime Fixes #4402.

このコミットは、Goランタイムにおいて、runtime.nanotime関数がvDSO clock_gettimeを呼び出す際に発生する可能性のあるスタックオーバーフローを回避するために、コードを重複させたことを示しています。これは、GoのIssue #4402を修正するものです。

変更の背景

このコミットの背景には、Linuxシステムにおける時間取得のメカニズムと、Goランタイムのスタック管理の特性が関係しています。特に、vDSO (virtual Dynamic Shared Object) を介したシステムコールと、Goの軽量なゴルーチンスタックの相互作用が問題を引き起こしていました。

time.nowruntime.nanotimeは、Goプログラム内で現在時刻を取得するために使用される関数です。これらの関数は、Linuxシステムでは通常、clock_gettimegettimeofdayといったシステムコールを呼び出します。これらのシステムコールは、パフォーマンス向上のため、vDSOと呼ばれるカーネル空間のコードをユーザー空間にマッピングして直接呼び出すことで、システムコールトラップのオーバーヘッドを削減しています。

しかし、vDSOclock_gettimeの実装は、特定の条件下で比較的大きなスタック空間を必要とすることがありました。Goのゴルーチンは、初期スタックサイズが非常に小さく(当時は数KB程度)、必要に応じて動的に拡張されます。time.nowからruntime.nanotimeを呼び出すような関数呼び出しの連鎖があった場合、vDSO clock_gettimeが要求するスタック空間が、Goランタイムが確保しているスタック空間を超過し、スタックオーバーフローを引き起こす可能性がありました。

この問題は、特にGoのランタイム内部で頻繁に時刻取得が行われる場合や、スタックがすでに限界に近い状況でnanotimeが呼び出される場合に顕在化しました。スタックオーバーフローはプログラムのクラッシュに直結するため、安定性に関わる重要な問題でした。

前提知識の解説

vDSO (virtual Dynamic Shared Object)

vDSOは、Linuxカーネルがユーザー空間に提供する共有ライブラリのようなものです。これにより、ユーザープログラムは、gettimeofdayclock_gettimeのような一部のシステムコールを、カーネルモードへの切り替えなしに直接実行できます。これにより、システムコールトラップのオーバーヘッドが削減され、これらの操作のパフォーマンスが大幅に向上します。vDSOは、カーネルが管理するメモリ領域にマッピングされ、ユーザープログラムからは通常の共有ライブラリのように見えますが、実際にはカーネルの一部です。

clock_gettimegettimeofday

  • clock_gettime: POSIX標準で定義されている関数で、高精度な時間情報を提供します。様々なクロックID(例: CLOCK_REALTIMECLOCK_MONOTONIC)を指定して、異なる種類の時間を取得できます。CLOCK_REALTIMEはシステムの実時間(壁時計時間)を返します。
  • gettimeofday: システムの現在の時刻とタイムゾーン情報を取得する関数です。マイクロ秒単位の精度を提供しますが、clock_gettimeに比べて精度や機能の面で劣る場合があります。

Goランタイムのスタック管理

Goのゴルーチンは、非常に軽量なスレッドのようなものです。各ゴルーチンは独自のスタックを持ち、そのスタックは最初は非常に小さく(数KB)、必要に応じて自動的に拡張されます。この「スタックの動的拡張」はGoの並行処理モデルの重要な要素ですが、スタックの拡張にはある程度のコストがかかり、また、スタックの拡張が間に合わない場合や、特定の条件下でスタックが不足する場合にはスタックオーバーフローが発生する可能性があります。

アセンブリ言語 (x86-64)

このコミットで変更されているファイルはsrc/pkg/runtime/sys_linux_amd64.sであり、これはx86-64アーキテクチャ向けのGoランタイムのアセンブリコードです。アセンブリコードは、CPUが直接実行できる機械語に近い低レベルの言語であり、レジスタ操作、メモリ操作、関数呼び出し規約などを直接制御します。

  • TEXT: Goのアセンブリでは、関数の定義を開始するために使用されます。
  • MOVQ: 64ビットの値を移動する命令です。
  • CMPQ: 64ビットの値を比較する命令です。
  • JEQ: 比較結果が等しい場合にジャンプする命令です。
  • LEAQ: アドレスをレジスタにロードする命令です。
  • CALL: 関数を呼び出す命令です。
  • RET: 関数から戻る命令です。
  • SP: スタックポインタレジスタ。現在のスタックの最上位を指します。
  • FP: フレームポインタレジスタ。現在のスタックフレームのベースを指します。
  • AX, DX, DI, SI: 汎用レジスタ。関数呼び出しの引数や戻り値、一時的な計算に使用されます。

技術的詳細

このコミットの核心は、runtime.nanotime関数がtime.now関数を呼び出すことによって発生するスタックオーバーフローの問題を解決することです。

元のコードでは、runtime.nanotimeは単にtime.nowを呼び出し、その結果からナノ秒部分を抽出していました。

TEXT runtime·nanotime(SB), 7, $32
	CALL	time·now(SB)
	MOVQ	0(SP), AX	// sec
	MOVL	8(SP), DX	// nsec
	// ...

ここで注目すべきは、time.nowが呼び出される際に、その関数が自身のスタックフレームを確保し、さらにvDSO clock_gettimevDSO gettimeofdayが呼び出される際に、これらの関数もスタックを使用する点です。time.nowのコメントにあるように、「We're guaranteed 128 bytes on entry, and we've taken 16, and the call uses another 8. That leaves 104 for the gettime code to use. Hope that's enough!」という記述から、スタック空間が非常に限られていることがわかります。

time.nowvDSO clock_gettimeを呼び出す際、clock_gettimeは引数としてtimespec構造体へのポインタを受け取ります。この構造体はスタック上に配置されます。同様に、gettimeofdaytimeval構造体へのポインタを受け取ります。これらの構造体のサイズは小さいですが、vDSOの実装によっては、内部でさらにスタックを使用する可能性があります。

runtime.nanotimetime.nowを呼び出すことで、スタックフレームが二重に積み重なり、さらにvDSOの呼び出しが加わることで、Goのゴルーチンに割り当てられた初期スタックサイズを超過し、スタックオーバーフローが発生するリスクが高まりました。

この問題を解決するために、コミットではruntime.nanotimeの内部でtime.nowのロジックを重複させました。つまり、runtime.nanotimeはもはやtime.nowを呼び出すのではなく、time.nowが実行していたvDSO clock_gettimeまたはvDSO gettimeofdayを直接呼び出すようになりました。

これにより、runtime.nanotimeが呼び出された際に、time.nowのスタックフレームを介する必要がなくなり、スタックの使用量を削減できます。コミットメッセージにある「duplicate code for runtime.nanotime to avoid stack overflow in vDSO clock_gettime」という説明は、このスタック使用量の削減が目的であることを明確に示しています。

具体的には、time.nowruntime.nanotimeの両方で、__vdso_clock_gettime_sym(またはフォールバックとして__vdso_gettimeofday_sym)を直接呼び出すコードパスが独立して存在することになります。これにより、関数呼び出しの深さが浅くなり、スタックオーバーフローのリスクが低減されます。

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

変更はsrc/pkg/runtime/sys_linux_amd64.sファイルに集中しています。

--- a/src/pkg/runtime/sys_linux_amd64.s
+++ b/src/pkg/runtime/sys_linux_amd64.s
@@ -101,36 +101,59 @@ TEXT runtime·mincore(SB),7,$0-24
  	RET
  
  // func now() (sec int64, nsec int32)
-TEXT time·now(SB), 7, $32
+TEXT time·now(SB),7,$16
+\t// Be careful. We're calling a function with gcc calling convention here.
+\t// We're guaranteed 128 bytes on entry, and we've taken 16, and the
+\t// call uses another 8.
+\t// That leaves 104 for the gettime code to use. Hope that's enough!
  	MOVQ	runtime·__vdso_clock_gettime_sym(SB), AX
  	CMPQ	AX, $0
  	JEQ	fallback_gtod
  	MOVL	$0, DI // CLOCK_REALTIME
-\tLEAQ	8(SP), SI
+\tLEAQ	0(SP), SI
  	CALL	AX
-\tMOVQ	8(SP), AX	// sec
-\tMOVQ	16(SP), DX	// nsec
+\tMOVQ	0(SP), AX	// sec
+\tMOVQ	8(SP), DX	// nsec
  	MOVQ	AX, sec+0(FP)
  	MOVL	DX, nsec+8(FP)
  	RET
  fallback_gtod:
-\tLEAQ	8(SP), DI
+\tLEAQ	0(SP), DI
  	MOVQ	$0, SI
  	MOVQ	runtime·__vdso_gettimeofday_sym(SB), AX
  	CALL	AX
-\tMOVQ	8(SP), AX	// sec
-\tMOVL	16(SP), DX	// usec
+\tMOVQ	0(SP), AX	// sec
+\tMOVL	8(SP), DX	// usec
  	IMULQ	$1000, DX
  	MOVQ	AX, sec+0(FP)
  	MOVL	DX, nsec+8(FP)
  	RET
  
-TEXT runtime·nanotime(SB), 7, $32
-\tCALL	time·now(SB)
+TEXT runtime·nanotime(SB),7,$16
+\t// Duplicate time.now here to avoid using up precious stack space.
+\t// See comment above in time.now.
+\tMOVQ	runtime·__vdso_clock_gettime_sym(SB), AX
+\tCMPQ	AX, $0
+\tJEQ	fallback_gtod_nt
+\tMOVL	$0, DI // CLOCK_REALTIME
+\tLEAQ	0(SP), SI
+\tCALL	AX
  	MOVQ	0(SP), AX	// sec
-\tMOVL	8(SP), DX	// nsec
-\n-\t// sec is in AX, usec in DX
+\tMOVQ	8(SP), DX	// nsec
+\t// sec is in AX, nsec in DX
+\t// return nsec in AX
+\tIMULQ	$1000000000, AX
+\tADDQ	DX, AX
+\tRET
+fallback_gtod_nt:
+\tLEAQ	0(SP), DI
+\tMOVQ	$0, SI
+\tMOVQ	runtime·__vdso_gettimeofday_sym(SB), AX
+\tCALL	AX
+\tMOVQ	0(SP), AX	// sec
+\tMOVL	8(SP), DX	// usec
+\tIMULQ	$1000, DX
+\t// sec is in AX, nsec in DX
  	// return nsec in AX
  	IMULQ	$1000000000, AX
  	ADDQ	DX, AX

コアとなるコードの解説

この変更は、time·nowruntime·nanotimeという2つのアセンブリ関数の定義に影響を与えています。

  1. time·now関数の変更:

    • TEXT time·now(SB), 7, $32TEXT time·now(SB),7,$16 に変更されています。これは、関数のスタックフレームサイズが32バイトから16バイトに削減されたことを示唆しています。これは、スタックの使用量を全体的に減らすための最適化の一部である可能性があります。
    • LEAQ 8(SP), SILEAQ 0(SP), SI に変更されています。これは、clock_gettimegettimeofdayに渡すtimespecまたはtimeval構造体のアドレスを、スタックポインタのオフセット8からオフセット0に変更したことを意味します。つまり、スタックフレームの開始位置に直接構造体を配置するように変更されたと考えられます。これにより、スタックの利用効率が向上します。
    • MOVQ 8(SP), AXMOVQ 16(SP), DXMOVQ 0(SP), AXMOVQ 8(SP), DX に変更されています。これは、clock_gettimegettimeofdayからの戻り値(秒とナノ秒/マイクロ秒)をスタックのオフセット8と16から、オフセット0と8に変更したことを示しています。これは、上記のLEAQの変更と一貫しており、スタックレイアウトの変更を反映しています。
  2. runtime·nanotime関数の変更:

    • 最も重要な変更は、CALL time·now(SB) の行が削除され、代わりにtime·now関数とほぼ同じロジックがruntime·nanotime内に直接コピーされたことです。
    • TEXT runtime·nanotime(SB), 7, $32TEXT runtime·nanotime(SB),7,$16 に変更され、time·nowと同様にスタックフレームサイズが削減されています。
    • time·nowと同様に、MOVQ runtime·__vdso_clock_gettime_sym(SB), AX から始まるvDSO clock_gettimeの呼び出しロジックと、そのフォールバックであるvDSO gettimeofdayの呼び出しロジックがruntime·nanotime内に複製されています。
    • 新しいコメント // Duplicate time.now here to avoid using up precious stack space. が追加され、このコード重複の意図が明確に説明されています。

この変更により、runtime.nanotimetime.nowを呼び出すという関数呼び出しのネストが解消され、runtime.nanotimeは直接vDSOの時刻取得関数を呼び出すようになりました。これにより、runtime.nanotimeが実行される際のスタック使用量が削減され、特にスタックが逼迫している状況でのスタックオーバーフローのリスクが大幅に低減されました。

関連リンク

  • Go Issue #4402 (元のIssueトラッカーでの情報が見つからないため、直接的なリンクは提供できませんが、コミットメッセージに記載されています)
  • Goのランタイムに関するドキュメント (Goのバージョンによって異なるため、一般的な情報源となります)

参考にした情報源リンク

  • Goのコミット履歴: https://github.com/golang/go/commit/263777752701a7cc180cbf19c319f897d821d78f
  • Linux vDSOに関する一般的な情報 (例: Wikipedia, Linuxカーネルのドキュメント)
  • x86-64アセンブリ言語の命令セットに関する情報
  • Goのランタイムとスタック管理に関する情報 (Goの公式ドキュメントやブログ記事など)
  • clock_gettimegettimeofdayに関するmanページやPOSIX標準のドキュメント