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

[インデックス 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つの主要な問題に対処しています。

  1. ARMアセンブリコードの修正: 以前の変更(CL 5989057)において、ARMアーキテクチャ向けのアセンブリコードでスタックポインタ(SP)のオフセット指定に誤りがありました。具体的には、"MOVW.W R4, -4(SP)"という命令が、リンカ(5l)がSPのオフセットを書き換える挙動を考慮すると、正しくは"MOVW.W R4, -4(R13)"であるべきでした。この修正により、Linux/ARM環境でのビルドが正常に行われるようになります。
  2. misc/cgo/test/issue1560.goのテスト調整: issue1560.goのテストは、並行して実行される1秒間のスリープ処理のデッドラインを検証するものです。しかし、ARM環境ではこのデッドラインが頻繁に満たされない(テストが失敗する)問題がありました。これは、ARMプロセッサの特性や、当時のGoランタイムのスケジューリングの精度に起因する可能性があります。この問題を回避するため、ARM環境でのみスリープ時間を2秒に延長する変更が加えられました。

変更の背景

このコミットは、Go言語のLinux/ARMアーキテクチャにおける安定性と信頼性を向上させるために行われました。

  1. アセンブリコードのバグ: Go言語のランタイムは、パフォーマンスが重要な部分や、特定のアーキテクチャに依存する処理のためにアセンブリ言語で記述されたコードを含んでいます。CL 5989057での変更は、おそらくGoのランタイムにおけるスタック管理や関数呼び出し規約に関連するものでしたが、その際にARMアーキテクチャ特有のリンカの挙動(5lがSPのオフセットを書き換える)を見落としたため、ビルドエラーや実行時エラーが発生する可能性がありました。このコミットは、その見落としを修正し、ARM環境でのGoプログラムの安定した動作を保証します。
  2. テストの信頼性向上: 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環境の現実的な実行時間に対して厳しすぎたのです。
  • 修正:
    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(...)
    }
    
    この変更により、ARM環境では各スリープが2秒になり、テストのデッドラインも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アーキテクチャである場合にsleepSec2に上書きされます。これにより、ARM環境でのみスリープ時間が延長されます。
  • parallelSleep関数への引数がハードコードされた1からsleepSec変数に変更されました。
  • テストのデッドラインをチェックする条件式も変更されました。元の1300*time.Millisecondtime.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のリンカ5lSPに対するオフセットを再配置する際に発生する問題を回避するためのものです。R13はARMアーキテクチャにおいてスタックポインタとして機能するレジスタであり、これを明示的に使用することで、リンカによる意図しないオフセットの書き換えを防ぎ、Goランタイムがスタックを正しく管理できるようになります。これは、特にcgocallbackのようなCgo関連の低レベルな処理において、スタックの整合性を保つために非常に重要です。

関連リンク

参考にした情報源リンク