[インデックス 13032] ファイルの概要
このコミットは、Go言語のランタイムとmisc/cgo/test
パッケージにおけるLinux/ARMアーキテクチャ向けのビルド修正を目的としています。具体的には、ARMアセンブリコード内のスタックポインタ(SP)のオフセットに関する誤りを修正し、cgo
テストのタイムアウト問題をARM環境向けに調整しています。
コミット
commit 018bcc35350c83956f8d77550873cd6886acd011
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Sat May 5 01:35:13 2012 +0800
runtime, misc/cgo/test: fix build for Linux/ARM
1. In CL 5989057, I made a mistake in the last minute change.
"MOVW.W R4, -4(SP)" should really be "MOVW.W R4, -4(R13)",
as 5l will rewrite offset for SP.
2. misc/cgo/test/issue1560.go tests for parallel sleep of 1s,
but on ARM, the deadline is frequently missed, so change sleep
time to 2s on ARM.
R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6202043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/018bcc35350c83956f8d77550873cd6886acd011
元コミット内容
このコミットは、以下の2つの主要な問題に対処しています。
- ARMアセンブリコードの修正: 以前の変更(CL 5989057)において、ARMアーキテクチャ向けのアセンブリコードでスタックポインタ(SP)のオフセット指定に誤りがありました。具体的には、
"MOVW.W R4, -4(SP)"
という命令が、リンカ(5l
)がSPのオフセットを書き換える挙動を考慮すると、正しくは"MOVW.W R4, -4(R13)"
であるべきでした。この修正により、Linux/ARM環境でのビルドが正常に行われるようになります。 misc/cgo/test/issue1560.go
のテスト調整:issue1560.go
のテストは、並行して実行される1秒間のスリープ処理のデッドラインを検証するものです。しかし、ARM環境ではこのデッドラインが頻繁に満たされない(テストが失敗する)問題がありました。これは、ARMプロセッサの特性や、当時のGoランタイムのスケジューリングの精度に起因する可能性があります。この問題を回避するため、ARM環境でのみスリープ時間を2秒に延長する変更が加えられました。
変更の背景
このコミットは、Go言語のLinux/ARMアーキテクチャにおける安定性と信頼性を向上させるために行われました。
- アセンブリコードのバグ: Go言語のランタイムは、パフォーマンスが重要な部分や、特定のアーキテクチャに依存する処理のためにアセンブリ言語で記述されたコードを含んでいます。
CL 5989057
での変更は、おそらくGoのランタイムにおけるスタック管理や関数呼び出し規約に関連するものでしたが、その際にARMアーキテクチャ特有のリンカの挙動(5l
がSPのオフセットを書き換える)を見落としたため、ビルドエラーや実行時エラーが発生する可能性がありました。このコミットは、その見落としを修正し、ARM環境でのGoプログラムの安定した動作を保証します。 - テストの信頼性向上:
misc/cgo/test/issue1560.go
は、cgo
(GoとC言語の相互運用)における並行処理の正確性を検証するためのテストです。テストが特定の環境(ARM)で頻繁に失敗する場合、それは実際のバグを示している可能性もありますが、多くの場合、テスト自体の設計がその環境の特性(例: スケジューリングの粒度、コンテキストスイッチのオーバーヘッド、プロセッサの速度)を十分に考慮していないことを示します。この場合、テストのデッドラインが厳しすぎたため、ARM環境でのテストの信頼性が低下していました。スリープ時間を延長することで、テストが不安定になることなく、本来検証したい並行処理のロジックを正しく評価できるようになります。これは、CI/CDパイプラインにおけるテストの安定性を確保する上でも重要です。
前提知識の解説
- Go言語のランタイム (runtime): Goプログラムの実行を管理する低レベルのコンポーネントです。ガベージコレクション、ゴルーチンのスケジューリング、メモリ管理、システムコールなど、Goプログラムが動作するために必要な多くの機能を提供します。一部のクリティカルな部分は、パフォーマンスのためにアセンブリ言語で記述されています。
- ARMアーキテクチャ: Advanced RISC Machineの略で、モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。Go言語は、ARMを含む多くのアーキテクチャをサポートしています。
- アセンブリ言語: コンピュータのプロセッサが直接理解できる機械語に非常に近い低レベルのプログラミング言語です。特定のレジスタ操作やメモリアクセスを直接制御できるため、OSカーネル、デバイスドライバ、高性能なライブラリなど、速度やハードウェア制御が重要な場面で用いられます。
- スタックポインタ (SP): プログラムのスタックの現在のトップを指すレジスタです。関数呼び出し時に引数やローカル変数をスタックにプッシュしたり、関数から戻る際にポップしたりするために使用されます。
- R13レジスタ (ARM): ARMアーキテクチャにおけるレジスタの一つで、通常はスタックポインタ(SP)として使用されます。ARMの命令セットでは、SPを明示的にR13として参照することが一般的です。
5l
(Goリンカ): Goツールチェーンの一部であるリンカです。コンパイルされたオブジェクトファイルを結合し、実行可能ファイルを生成します。このリンカは、アセンブリコード内の特定のシンボルやオフセットを解決・書き換える役割を担っています。cgo
: Go言語とC言語のコードを相互運用するためのGoの機能です。GoプログラムからC関数を呼び出したり、CプログラムからGo関数を呼び出したりすることを可能にします。- 並行処理 (Concurrency): 複数のタスクが同時に進行しているように見える状態を指します。Go言語ではゴルーチンとチャネルによって強力な並行処理がサポートされています。
- デッドライン (Deadline): 特定の処理が完了しなければならない期限です。テストにおいて、処理がこのデッドラインを超過するとテスト失敗とみなされます。
技術的詳細
1. ARMアセンブリコードの修正 (src/pkg/runtime/asm_arm.s
)
GoのランタイムにおけるARMアセンブリコードでは、スタックポインタ(SP)を基準としたメモリ操作が行われます。問題となったのは、MOVW.W R4, -4(SP)
という命令でした。
MOVW.W R4, -4(SP)
: これは、レジスタR4
の値を、現在のスタックポインタSP
から-4バイト(つまり、スタックの現在のトップから4バイト下)のアドレスに書き込む命令です。- 問題点: Goのリンカである
5l
は、アセンブリコード内のSP
に対するオフセットを、最終的な実行可能ファイルを生成する際に内部的に書き換える(調整する)ことがあります。これは、Goのランタイムが独自のスタック管理メカニズムを持っているため、アセンブリコードが直接参照するSP
が、必ずしもOSレベルのスタックポインタと一致しない場合があるためです。 - 修正: 開発者は、この
5l
の挙動を考慮し、SP
の代わりにR13
(ARMアーキテクチャでSPとして使われるレジスタ)を明示的に使用するように変更しました。MOVW.W R4, -4(R13)
とすることで、リンカによる意図しないオフセットの書き換えを避け、期待通りのメモリ位置に値が書き込まれるようになります。これにより、Goランタイムのスタックフレーム管理がARM環境で正しく機能するようになります。
2. misc/cgo/test/issue1560.go
のテスト調整
このテストは、C.twoSleep(1)
とC.twoSleep(1)
という2つのC関数呼び出しを並行して実行し、合計のスリープ時間が2秒ではなく、並行実行によって1秒強に収まることを検証していました。
- 元のロジック:
start := time.Now() parallelSleep(1) // 内部でC.twoSleep(1)を並行実行 dt := time.Now().Sub(start) if dt >= 1300*time.Millisecond { // 1.3秒を超えたら失敗 t.Fatalf(...) }
- 問題点: ARM環境では、
dt >= 1300*time.Millisecond
という条件が頻繁に真となり、テストが失敗していました。これは、ARMプロセッサの処理速度、Goランタイムのスケジューリングのオーバーヘッド、あるいはCgo呼び出しのオーバーヘッドが、他のアーキテクチャ(例: x86)と比較して相対的に大きかったためと考えられます。1.3秒というデッドラインが、ARM環境の現実的な実行時間に対して厳しすぎたのです。 - 修正:
この変更により、ARM環境では各スリープが2秒になり、テストのデッドラインもsleepSec := 1 if runtime.GOARCH == "arm" { sleepSec = 2 // ARM環境ではスリープ時間を2秒に延長 } start := time.Now() parallelSleep(sleepSec) dt := time.Now().Sub(start) if dt >= time.Duration(sleepSec)*1300*time.Millisecond { // 新しいデッドライン t.Fatalf(...) }
2 * 1300ms = 2.6s
に緩和されます。これにより、テストが不安定になることなく、並行処理の意図された動作を検証できるようになりました。runtime.GOARCH
は、現在のビルドターゲットのアーキテクチャを示すGoの定数です。
コアとなるコードの変更箇所
misc/cgo/test/issue1560.go
--- a/misc/cgo/test/issue1560.go
+++ b/misc/cgo/test/issue1560.go
@@ -15,6 +15,7 @@ void twoSleep(int);
import "C"
import (
+ "runtime" // runtimeパッケージのインポートを追加
"testing"
"time"
)
@@ -35,11 +36,17 @@ func BackgroundSleep(n int) {
}
func testParallelSleep(t *testing.T) {
+ sleepSec := 1 // デフォルトのスリープ時間
+ if runtime.GOARCH == "arm" {
+ // ARM環境では、1.3秒のデッドラインが頻繁に満たされないため、
+ // スリープ時間を2秒に増やす
+ sleepSec = 2
+ }
start := time.Now()
- parallelSleep(1) // 元は1秒
+ parallelSleep(sleepSec) // sleepSecを使用
dt := time.Now().Sub(start)
- // バグにより、スリープが直列に実行され、2秒の遅延が発生していた。
- if dt >= 1300*time.Millisecond { // 元のデッドライン
- t.Fatalf("parallel 1-second sleeps slept for %f seconds", dt.Seconds())
+ // バグにより、スリープが直列に実行され、2*sleepSec秒の遅延が発生していた。
+ if dt >= time.Duration(sleepSec)*1300*time.Millisecond { // 新しいデッドライン
+ t.Fatalf("parallel %d-second sleeps slept for %f seconds", sleepSec, dt.Seconds())
}
}
src/pkg/runtime/asm_arm.s
--- a/src/pkg/runtime/asm_arm.s
+++ b/src/pkg/runtime/asm_arm.s
@@ -289,7 +289,7 @@ TEXT runtime·cgocallback(SB),7,$16
// 現在のm->g0->sched.spをスタックに保存し、SPに設定する。
MOVW m_g0(m), R3
MOVW (g_sched+gobuf_sp)(R3), R4
- MOVW.W R4, -4(SP) // 元はSPを使用
+ MOVW.W R4, -4(R13) // R13を使用するように修正
MOVW R13, (g_sched+gobuf_sp)(R3)
// m->curgスタックに切り替えてruntime.cgocallbackgを呼び出す
コアとなるコードの解説
misc/cgo/test/issue1560.go
の変更点
runtime
パッケージがインポートされました。これにより、runtime.GOARCH
定数にアクセスできるようになります。testParallelSleep
関数内にsleepSec
変数が導入されました。この変数はデフォルトで1
に設定されます。if runtime.GOARCH == "arm"
ブロックが追加され、実行環境がARMアーキテクチャである場合にsleepSec
が2
に上書きされます。これにより、ARM環境でのみスリープ時間が延長されます。parallelSleep
関数への引数がハードコードされた1
からsleepSec
変数に変更されました。- テストのデッドラインをチェックする条件式も変更されました。元の
1300*time.Millisecond
がtime.Duration(sleepSec)*1300*time.Millisecond
となり、sleepSec
の値に応じてデッドラインが動的に調整されるようになりました。これにより、ARM環境ではデッドラインが2.6秒に緩和され、テストの不安定性が解消されます。 t.Fatalf
のメッセージもsleepSec
の値を含むように更新され、より正確な情報を提供するようになりました。
src/pkg/runtime/asm_arm.s
の変更点
MOVW.W R4, -4(SP)
という命令がMOVW.W R4, -4(R13)
に変更されました。- この変更は、Goのリンカ
5l
がSP
に対するオフセットを再配置する際に発生する問題を回避するためのものです。R13
はARMアーキテクチャにおいてスタックポインタとして機能するレジスタであり、これを明示的に使用することで、リンカによる意図しないオフセットの書き換えを防ぎ、Goランタイムがスタックを正しく管理できるようになります。これは、特にcgocallback
のようなCgo関連の低レベルな処理において、スタックの整合性を保つために非常に重要です。
関連リンク
- Go言語の
runtime
パッケージ: https://pkg.go.dev/runtime - Go言語の
cgo
に関するドキュメント: https://go.dev/blog/cgo - ARMアーキテクチャのレジスタとスタックポインタに関する一般的な情報 (Go固有ではない): https://developer.arm.com/documentation/dui0489/c/arm-and-thumb-instructions/data-transfer-instructions/ldr-and-str (一般的なARM命令セットの例)
参考にした情報源リンク
- GoのChange List (CL) 6202043: https://golang.org/cl/6202043
- GoのChange List (CL) 5989057 (このコミットで言及されている以前の変更): https://golang.org/cl/5989057 (Web検索で確認)
- Goのリンカ
5l
に関する情報 (Goの内部実装に関する議論やドキュメント): https://go.dev/doc/asm (Goのアセンブリに関する公式ドキュメント) - Goのテストとベンチマークに関する情報: https://go.dev/blog/testing