[インデックス 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.now
とruntime.nanotime
は、Goプログラム内で現在時刻を取得するために使用される関数です。これらの関数は、Linuxシステムでは通常、clock_gettime
やgettimeofday
といったシステムコールを呼び出します。これらのシステムコールは、パフォーマンス向上のため、vDSO
と呼ばれるカーネル空間のコードをユーザー空間にマッピングして直接呼び出すことで、システムコールトラップのオーバーヘッドを削減しています。
しかし、vDSO
のclock_gettime
の実装は、特定の条件下で比較的大きなスタック空間を必要とすることがありました。Goのゴルーチンは、初期スタックサイズが非常に小さく(当時は数KB程度)、必要に応じて動的に拡張されます。time.now
からruntime.nanotime
を呼び出すような関数呼び出しの連鎖があった場合、vDSO clock_gettime
が要求するスタック空間が、Goランタイムが確保しているスタック空間を超過し、スタックオーバーフローを引き起こす可能性がありました。
この問題は、特にGoのランタイム内部で頻繁に時刻取得が行われる場合や、スタックがすでに限界に近い状況でnanotime
が呼び出される場合に顕在化しました。スタックオーバーフローはプログラムのクラッシュに直結するため、安定性に関わる重要な問題でした。
前提知識の解説
vDSO (virtual Dynamic Shared Object)
vDSOは、Linuxカーネルがユーザー空間に提供する共有ライブラリのようなものです。これにより、ユーザープログラムは、gettimeofday
やclock_gettime
のような一部のシステムコールを、カーネルモードへの切り替えなしに直接実行できます。これにより、システムコールトラップのオーバーヘッドが削減され、これらの操作のパフォーマンスが大幅に向上します。vDSOは、カーネルが管理するメモリ領域にマッピングされ、ユーザープログラムからは通常の共有ライブラリのように見えますが、実際にはカーネルの一部です。
clock_gettime
とgettimeofday
clock_gettime
: POSIX標準で定義されている関数で、高精度な時間情報を提供します。様々なクロックID(例:CLOCK_REALTIME
、CLOCK_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_gettime
やvDSO 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.now
がvDSO clock_gettime
を呼び出す際、clock_gettime
は引数としてtimespec
構造体へのポインタを受け取ります。この構造体はスタック上に配置されます。同様に、gettimeofday
もtimeval
構造体へのポインタを受け取ります。これらの構造体のサイズは小さいですが、vDSO
の実装によっては、内部でさらにスタックを使用する可能性があります。
runtime.nanotime
がtime.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.now
とruntime.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·now
とruntime·nanotime
という2つのアセンブリ関数の定義に影響を与えています。
-
time·now
関数の変更:TEXT time·now(SB), 7, $32
がTEXT time·now(SB),7,$16
に変更されています。これは、関数のスタックフレームサイズが32バイトから16バイトに削減されたことを示唆しています。これは、スタックの使用量を全体的に減らすための最適化の一部である可能性があります。LEAQ 8(SP), SI
がLEAQ 0(SP), SI
に変更されています。これは、clock_gettime
やgettimeofday
に渡すtimespec
またはtimeval
構造体のアドレスを、スタックポインタのオフセット8からオフセット0に変更したことを意味します。つまり、スタックフレームの開始位置に直接構造体を配置するように変更されたと考えられます。これにより、スタックの利用効率が向上します。MOVQ 8(SP), AX
とMOVQ 16(SP), DX
がMOVQ 0(SP), AX
とMOVQ 8(SP), DX
に変更されています。これは、clock_gettime
やgettimeofday
からの戻り値(秒とナノ秒/マイクロ秒)をスタックのオフセット8と16から、オフセット0と8に変更したことを示しています。これは、上記のLEAQ
の変更と一貫しており、スタックレイアウトの変更を反映しています。
-
runtime·nanotime
関数の変更:- 最も重要な変更は、
CALL time·now(SB)
の行が削除され、代わりにtime·now
関数とほぼ同じロジックがruntime·nanotime
内に直接コピーされたことです。 TEXT runtime·nanotime(SB), 7, $32
がTEXT 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.nanotime
がtime.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_gettime
とgettimeofday
に関する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.now
とruntime.nanotime
は、Goプログラム内で現在時刻を取得するために使用される関数です。これらの関数は、Linuxシステムでは通常、clock_gettime
やgettimeofday
といったシステムコールを呼び出します。これらのシステムコールは、パフォーマンス向上のため、vDSO
と呼ばれるカーネル空間のコードをユーザー空間にマッピングして直接呼び出すことで、システムコールトラップのオーバーヘッドを削減しています。
しかし、vDSO
のclock_gettime
の実装は、特定の条件下で比較的大きなスタック空間を必要とすることがありました。Goのゴルーチンは、初期スタックサイズが非常に小さく(当時は数KB程度)、必要に応じて動的に拡張されます。time.now
からruntime.nanotime
を呼び出すような関数呼び出しの連鎖があった場合、vDSO clock_gettime
が要求するスタック空間が、Goランタイムが確保しているスタック空間を超過し、スタックオーバーフローを引き起こす可能性がありました。
この問題は、特にGoのランタイム内部で頻繁に時刻取得が行われる場合や、スタックがすでに限界に近い状況でnanotime
が呼び出される場合に顕在化しました。スタックオーバーフローはプログラムのクラッシュに直結するため、安定性に関わる重要な問題でした。
前提知識の解説
vDSO (virtual Dynamic Shared Object)
vDSOは、Linuxカーネルがユーザー空間に提供する共有ライブラリのようなものです。これにより、ユーザープログラムは、gettimeofday
やclock_gettime
のような一部のシステムコールを、カーネルモードへの切り替えなしに直接実行できます。これにより、システムコールトラップのオーバーヘッドが削減され、これらの操作のパフォーマンスが大幅に向上します。vDSOは、カーネルが管理するメモリ領域にマッピングされ、ユーザープログラムからは通常の共有ライブラリのように見えますが、実際にはカーネルの一部です。
clock_gettime
とgettimeofday
clock_gettime
: POSIX標準で定義されている関数で、高精度な時間情報を提供します。様々なクロックID(例:CLOCK_REALTIME
、CLOCK_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_gettime
やvDSO 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.now
がvDSO clock_gettime
を呼び出す際、clock_gettime
は引数としてtimespec
構造体へのポインタを受け取ります。この構造体はスタック上に配置されます。同様に、gettimeofday
もtimeval
構造体へのポインタを受け取ります。これらの構造体のサイズは小さいですが、vDSO
の実装によっては、内部でさらにスタックを使用する可能性があります。
runtime.nanotime
がtime.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.now
とruntime.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·now
とruntime·nanotime
という2つのアセンブリ関数の定義に影響を与えています。
-
time·now
関数の変更:TEXT time·now(SB), 7, $32
がTEXT time·now(SB),7,$16
に変更されています。これは、関数のスタックフレームサイズが32バイトから16バイトに削減されたことを示唆しています。これは、スタックの使用量を全体的に減らすための最適化の一部である可能性があります。LEAQ 8(SP), SI
がLEAQ 0(SP), SI
に変更されています。これは、clock_gettime
やgettimeofday
に渡すtimespec
またはtimeval
構造体のアドレスを、スタックポインタのオフセット8からオフセット0に変更したことを意味します。つまり、スタックフレームの開始位置に直接構造体を配置するように変更されたと考えられます。これにより、スタックの利用効率が向上します。MOVQ 8(SP), AX
とMOVQ 16(SP), DX
がMOVQ 0(SP), AX
とMOVQ 8(SP), DX
に変更されています。これは、clock_gettime
やgettimeofday
からの戻り値(秒とナノ秒/マイクロ秒)をスタックのオフセット8と16から、オフセット0と8に変更したことを示しています。これは、上記のLEAQ
の変更と一貫しており、スタックレイアウトの変更を反映しています。
-
runtime·nanotime
関数の変更:- 最も重要な変更は、
CALL time·now(SB)
の行が削除され、代わりにtime·now
関数とほぼ同じロジックがruntime·nanotime
内に直接コピーされたことです。 TEXT runtime·nanotime(SB), 7, $32
がTEXT 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.nanotime
がtime.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_gettime
とgettimeofday
に関するmanページやPOSIX標準のドキュメント