[インデックス 16908] ファイルの概要
コミット
このコミットは、GoランタイムにおけるARMアーキテクチャでのビルド問題を修正するものです。具体的には、システムコール中に発生する「stack split during syscall」という致命的なエラーに対処しています。このエラーは、_addv
関数内でスタック分割が適切に行われなかったことに起因しているようです。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/64db2ec915f7d3a7ea515ac3050afd4063cd7438
元コミット内容
commit 64db2ec915f7d3a7ea515ac3050afd4063cd7438
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Mon Jul 29 23:41:12 2013 +0400
runtime: fix arm build
The current failure is:
fatal error: runtime: stack split during syscall
goroutine 2 [stack split]:
_addv(0xb6fa0f28, 0xd0a5112e, 0x13156d6e, 0xf8475800, 0xd)
/usr/local/go/src/pkg/runtime/vlrt_arm.c:66 fp=0xb6fa0ef8
notetsleep(0xb6fa0f9c, 0xf8475800, 0xd, 0x0, 0x0, ...)\n /usr/local/go/src/pkg/runtime/lock_futex.c:156 +0xd0 fp=0xb6fa0f18
runtime.notetsleepg(0xb6fa0f9c, 0xf8475800, 0xd)
/usr/local/go/src/pkg/runtime/lock_futex.c:197 +0x74 fp=0xb6fa0f3c
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/12052043
変更の背景
このコミットは、GoランタイムのARMビルドにおける特定のバグを修正するために行われました。報告されたエラーメッセージ「fatal error: runtime: stack split during syscall」は、Goのランタイムがシステムコール中にスタックを適切に拡張または切り替えることができなかったことを示しています。これは通常、スタックの制約、アセンブリコードの誤ったスタックポインタ操作、または特定の関数呼び出し規約の不一致によって発生します。
スタックトレースから、問題が_addv
関数(vlrt_arm.c
の66行目)で発生していることがわかります。この関数は、Goランタイムが内部的に使用する多倍長整数演算の一部である可能性が高いです。notetsleep
やruntime.notetsleepg
といった関数もスタックトレースに現れており、これはランタイムのスケジューラや同期プリミティブに関連する部分で問題が顕在化したことを示唆しています。
ARMアーキテクチャは、レジスタの使用方法や関数呼び出し規約が他のアーキテクチャ(x86-64など)と異なるため、低レベルのアセンブリコードやCコードで記述されたランタイム部分では、アーキテクチャ固有の調整が必要となることがよくあります。この問題も、ARM固有のスタック管理や関数呼び出し規約の誤解釈、または最適化の副作用によって引き起こされたと考えられます。
前提知識の解説
- Goランタイム: Goプログラムの実行を管理する低レベルのシステムです。ガベージコレクション、ゴルーチン管理、スケジューリング、システムコールインターフェースなどを担当します。Goの「並行性」と「シンプルさ」の多くは、このランタイムによって実現されています。
- スタック分割 (Stack Splitting): Goのゴルーチンは、非常に小さなスタック(通常は数KB)で開始されます。関数呼び出しによってスタックが不足しそうになると、Goランタイムは自動的にスタックを拡張(より大きなスタックにコピー)します。これをスタック分割と呼びます。これにより、Goは数百万のゴルーチンを効率的に実行できます。
- システムコール (Syscall): プログラムがオペレーティングシステム(OS)のサービス(ファイルI/O、ネットワーク通信、メモリ管理など)を要求するためのメカニズムです。システムコール中は、ユーザーモードからカーネルモードへの切り替えが発生し、スタックの扱いが通常とは異なる場合があります。
- ARMアーキテクチャ: モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのCPUアーキテクチャです。x86とは異なる命令セット、レジスタセット、関数呼び出し規約を持ちます。
TEXT
ディレクティブ (Goアセンブリ): Goのアセンブリ言語(Plan 9アセンブラ構文)で使用されるディレクティブで、関数の定義を開始します。TEXT symbol(SB), flags, $framesize
の形式を取り、flags
は関数の特性(例: スタック分割を無効にするなど)を定義し、$framesize
は関数のスタックフレームサイズを指定します。#pragma textflag
(Go Cコード): GoのCコード(gc
コンパイラでコンパイルされるCコード)で使用されるディレクティブで、続く関数のGoアセンブリリンケージフラグを設定します。これは、GoアセンブリのTEXT
ディレクティブのflags
引数に相当します。
技術的詳細
このコミットの技術的な核心は、GoランタイムがARMアーキテクチャ上でシステムコール中にスタックを適切に管理できなかった問題の解決にあります。
スタックトレースに示されている「fatal error: runtime: stack split during syscall」は、Goのスタック分割メカニズムがシステムコール中に失敗したことを明確に示しています。通常、システムコール中はスタックの切り替えや拡張が制限されるか、特別な注意が必要です。Goランタイムは、システムコールに入る際にスタックを固定したり、特別なスタックに切り替えたりすることがあります。このプロセスがARM固有のコードで正しく処理されていなかったと考えられます。
変更点を見ると、src/pkg/runtime/vlop_arm.s
とsrc/pkg/runtime/vlrt_arm.c
という2つのファイルが修正されています。これらのファイル名は、ARMアーキテクチャにおける「vlong」(おそらく64ビット整数またはそれ以上のサイズの整数)の演算(vlop
は"vlong operations"、vlrt
は"vlong runtime"の略か)に関連する低レベルのコードであることを示唆しています。
具体的な変更は以下の通りです。
-
src/pkg/runtime/vlop_arm.s
の変更:TEXT _mulv(SB), $0
がTEXT _mulv(SB), 7, $0
に変更されました。- ここで追加された
7
というフラグは、GoアセンブリのTEXT
ディレクティブにおけるNOSPLIT
フラグ(値は1
)とNOFRAME
フラグ(値は2
)、そしてWRAPPER
フラグ(値は4
)の組み合わせである可能性があります。NOSPLIT
(1
): この関数ではスタック分割を行わないことを示します。これは、関数が非常に短く、スタックを消費しない場合や、スタック分割が安全でないコンテキスト(例: システムコール中や、スタックポインタが厳密に管理されている低レベルコード)で使用されます。NOFRAME
(2
): この関数がスタックフレームを持たないことを示します。これは、レジスタのみを使用し、ローカル変数や引数をスタックに保存しない非常に最適化されたアセンブリ関数で使われます。WRAPPER
(4
): この関数が他の関数をラップするラッパー関数であることを示します。これは、プロファイリングやトレースなどの目的で使われることがあります。
_mulv
関数は、多倍長整数の乗算を行うアセンブリ関数であると推測されます。この関数にNOSPLIT
フラグを追加することで、システムコール中にこの関数が呼び出された際に、予期せぬスタック分割が発生するのを防ぎ、スタックの整合性を保つことを目的としていると考えられます。
-
src/pkg/runtime/vlrt_arm.c
の変更:_addv
関数の定義の直前に#pragma textflag 7
が追加されました。- これは、Cコードで記述された
_addv
関数に対しても、GoアセンブリのTEXT
ディレクティブと同じフラグ7
(NOSPLIT | NOFRAME | WRAPPER
)を適用することを意味します。 _addv
関数は、多倍長整数の加算を行うC関数であると推測されます。スタックトレースで問題が発生したのがこの関数であったため、この関数もシステムコール中にスタック分割を避ける必要があると判断されたのでしょう。
これらの変更は、ARMアーキテクチャにおけるGoランタイムの低レベルなスタック管理と関数呼び出し規約の厳密な要件に対応するためのものです。特に、システムコールのようなクリティカルなパスでは、スタックの安定性が非常に重要であり、NOSPLIT
フラグはこのような状況でのランタイムの堅牢性を高めるために使用されます。
コアとなるコードの変更箇所
src/pkg/runtime/vlop_arm.s
--- a/src/pkg/runtime/vlop_arm.s
+++ b/src/pkg/runtime/vlop_arm.s
@@ -27,7 +27,7 @@ arg=0
/* replaced use of R10 by R11 because the former can be the data segment base register */
-TEXT _mulv(SB), $0
+TEXT _mulv(SB), 7, $0
MOVW 0(FP), R0
MOVW 4(FP), R2 /* l0 */
MOVW 8(FP), R11 /* h0 */
src/pkg/runtime/vlrt_arm.c
--- a/src/pkg/runtime/vlrt_arm.c
+++ b/src/pkg/runtime/vlrt_arm.c
@@ -62,6 +62,7 @@ struct Vlong
void runtime·abort(void);
+#pragma textflag 7
void
_addv(Vlong *r, Vlong a, Vlong b)
{
コアとなるコードの解説
src/pkg/runtime/vlop_arm.s
の変更
TEXT _mulv(SB), 7, $0
の変更は、Goアセンブリで定義された_mulv
関数に特定のフラグを設定しています。
-
_mulv
: 多倍長整数の乗算を行うアセンブリ関数。 -
(SB)
: シンボルが静的セグメント(Static Base)に属することを示します。 -
7
: これはフラグのビットマスクです。Goのランタイムアセンブリにおけるフラグは以下の通りです(当時のGoのバージョンに基づく推測):1
(0x1):NOSPLIT
- この関数はスタック分割を行いません。つまり、この関数が実行中にスタックが不足しても、ランタイムはスタックを拡張しようとしません。これは、スタックポインタが厳密に管理されている低レベルのコードや、システムコールのようなスタック切り替えが絡むコンテキストで重要です。2
(0x2):NOFRAME
- この関数はスタックフレームを持ちません。これは、レジスタのみを使用し、ローカル変数や引数をスタックに保存しない、非常に最適化されたアセンブリ関数で使われます。4
(0x4):WRAPPER
- この関数が他の関数をラップするラッパー関数であることを示します。プロファイリングやトレースなどの目的で使われることがあります。
したがって、
7
はNOSPLIT | NOFRAME | WRAPPER
の組み合わせを意味します。この変更により、_mulv
関数がシステムコール中に呼び出された際に、スタック分割による問題が発生するのを防ぎ、スタックの整合性を保証します。 -
$0
: この関数のスタックフレームサイズが0バイトであることを示します。これはNOFRAME
フラグと一致し、関数がスタックを消費しないことを意味します。
src/pkg/runtime/vlrt_arm.c
の変更
#pragma textflag 7
の追加は、C言語で記述された_addv
関数に、GoアセンブリのTEXT
ディレクティブと同じフラグ7
を適用するためのものです。
#pragma textflag
: GoのCコンパイラ(gc
)がGoアセンブリリンケージフラグを設定するために使用する非標準のプラグマです。7
:vlop_arm.s
の_mulv
関数と同様に、NOSPLIT | NOFRAME | WRAPPER
の組み合わせを意味します。
_addv
関数は、スタックトレースで問題の発生源として特定された関数です。この関数にNOSPLIT
フラグを適用することで、システムコール中にこの関数が呼び出された際に、スタック分割が試みられてエラーになることを防ぎます。これは、_addv
がシステムコールパスの一部として、またはシステムコール中に呼び出される可能性のあるコードパスから呼び出される場合に特に重要です。
これらの変更は、ARMアーキテクチャにおけるGoランタイムのスタック管理の厳密な要件を満たすためのものであり、特にシステムコールのようなクリティカルなコンテキストでの安定性を向上させます。
関連リンク
- Go issue tracker (CL 12052043): https://golang.org/cl/12052043 (コミットメッセージに記載されているCLへのリンク)
参考にした情報源リンク
- Goのスタック分割に関するドキュメントや議論 (一般的な情報源):
- Goのスタック管理に関するブログ記事や公式ドキュメント
- Goのソースコード内の
runtime
パッケージのコメントやドキュメント
- Goアセンブリの
TEXT
ディレクティブに関する情報:- Goの公式ドキュメント: https://go.dev/doc/asm
- Goのソースコード内のアセンブリファイルのコメント
- ARMアーキテクチャの関数呼び出し規約に関する情報 (一般的な情報源):
- ARM Architecture Reference Manual
- ARMのABI (Application Binary Interface) ドキュメント
#pragma textflag
に関する情報:- Goのソースコード内のCファイルやコンパイラのドキュメント
- Goのコンパイラに関する技術的な議論やブログ記事
- Goの
fatal error: runtime: stack split during syscall
に関する情報:- Goのissue trackerやメーリングリストでの過去の議論
- Goのランタイムに関する技術ブログや記事
- Stack OverflowなどのQ&Aサイトでの関連質問と回答
これらの情報源は、Goランタイムの内部動作、特にスタック管理、アセンブリ言語の規約、およびアーキテクチャ固有の考慮事項を理解する上で役立ちます。