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

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

このコミットは、GoランタイムにおけるARMアーキテクチャでの整数除算の実装を改善し、プロファイリングツールが正しくスタックトレースを生成できるようにするための変更です。具体的には、非標準のスタックフレームがプロファイリングを妨げる問題を解決し、関連するトレースバックのバグも修正しています。

コミット

commit b0db472ea29a9f8283888e0cb5f7545f86dbc32c
Author: Russ Cox <rsc@golang.org>
Date:   Wed Oct 30 18:50:34 2013 +0000

    cmd/5l, runtime: make ARM integer division profiler-friendly
    
    The implementation of division constructed non-standard
    stack frames that could not be handled by the traceback
    routines.
    
    CL 13239052 left the frames non-standard but fixed them
    for the specific case of a divide-by-zero panic.
    A profiling signal can arrive at any time, so that fix
    is not sufficient.
    
    Change the division to store the extra argument in the M struct
    instead of in a new stack slot. That keeps the frames bog standard
    at all times.
    
    Also fix a related bug in the traceback code: when starting
    a traceback, the LR register should be ignored if the current
    function has already allocated its stack frame and saved the
    original LR on the stack. The stack copy should be used, as the
    LR register may have been modified.
    
    Combined, these make the torture test from issue 6681 pass.
    
    Fixes #6681.
    
    R=golang-dev, r, josharian
    CC=golang-dev
    https://golang.org/cl/19810043

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

https://github.com/golang/go/commit/b0db472ea29a9f8383888e0cb5f7545f86dbc32c

元コミット内容

GoランタイムにおけるARMアーキテクチャでの整数除算の実装が、非標準のスタックフレームを構築していました。これにより、トレースバックルーチンがこれらのスタックフレームを正しく処理できず、特にプロファイリング時に問題が発生していました。以前の変更(CL 13239052)では、ゼロ除算パニックの特定のケースに対しては修正が適用されましたが、プロファイリングシグナルは任意のタイミングで到着するため、この修正だけでは不十分でした。

このコミットでは、除算処理が追加の引数を新しいスタック領域ではなくM構造体に格納するように変更することで、スタックフレームが常に標準的な形式を保つようにします。また、トレースバックコードにおける関連するバグも修正しています。具体的には、トレースバックを開始する際に、現在の関数がすでにスタックフレームを割り当て、元のLR(リンクレジスタ)をスタックに保存している場合、LRレジスタは無視され、スタックに保存されたコピーが使用されるべきであるという修正です。これは、LRレジスタが変更されている可能性があるためです。

これらの変更により、Issue 6681で報告された「torture test」がパスするようになります。

変更の背景

Goのプロファイリングツール(pprofなど)は、プログラムの実行中に定期的にシグナルを送信し、その時点でのスタックトレースを収集することで、CPU使用率の高い箇所やボトルネックを特定します。このスタックトレースの収集は、各関数のスタックフレームが標準的な形式で構築されていることを前提としています。

しかし、ARMアーキテクチャにおけるGoの整数除算の実装は、最適化のために非標準のスタックフレームを使用していました。これにより、プロファイリングシグナルが除算処理中に到着した場合、トレースバックルーチンがスタックフレームを正しく解釈できず、不正確なプロファイルデータが生成される、あるいはプロファイリング自体がクラッシュする可能性がありました。

以前の変更(CL 13239052)は、ゼロ除算パニックという特定の例外ケースに対してはスタックフレームの処理を修正しましたが、これはプロファイリングのような任意のタイミングで発生するイベントには対応していませんでした。プロファイリングは、プログラムの正常な実行中にいつでも発生しうるため、スタックフレームが常に標準的であることが求められます。

また、トレースバック処理自体にも、LRレジスタの扱いに関するバグが存在していました。LRレジスタは関数のリターンアドレスを保持しますが、関数が自身のスタックフレームを構築し、元のLRをスタックに保存した後、LRレジスタは他の目的で使用される可能性があります。この場合、トレースバックルーチンがLRレジスタの現在の値に依存すると、誤ったリターンアドレスを辿ってしまうことになります。

これらの問題が複合的に作用し、GoのARM環境でのプロファイリングの信頼性を損ねていました。Issue 6681は、これらの問題を顕在化させる「torture test」を提供し、修正の必要性を示していました。

前提知識の解説

  • ARMアーキテクチャ: モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャ。GoはARMを含む様々なアーキテクチャをサポートしています。
  • スタックフレーム: 関数が呼び出されるたびに、その関数のローカル変数、引数、リターンアドレスなどを格納するためにスタック上に確保されるメモリ領域。スタックフレームの構造は、プロファイリングやデバッグにおいてスタックトレースを生成するために非常に重要です。
  • トレースバックルーチン: プログラムの実行中に、現在の関数呼び出しの連鎖(コールスタック)を遡って、どの関数がどの関数を呼び出したかを特定する処理。デバッグ時のスタックトレース表示や、プロファイリングでの関数呼び出しパスの特定に利用されます。
  • プロファイリング: プログラムの実行時のパフォーマンス特性(CPU使用率、メモリ使用量など)を測定・分析するプロセス。Goではpprofツールが標準で提供されており、CPUプロファイリングは定期的にスタックトレースをサンプリングすることで実現されます。
  • LR (Link Register): ARMプロセッサのレジスタの一つで、関数呼び出し時に呼び出し元へのリターンアドレスが格納されます。関数がスタックフレームを構築する際に、このLRの値をスタックに保存することが一般的です。
  • M構造体 (Machine): Goランタイムにおける重要なデータ構造の一つで、OSのスレッド(カーネルスレッド)を表します。各Mは、Goルーチンを実行するためのコンテキスト(スタック、レジスタなど)を保持し、スケジューラによって管理されます。M構造体は、スレッド固有のデータや、Goルーチン間の切り替えに必要な情報を格納するために使用されます。
  • cmd/5l: Goのツールチェーンの一部で、ARMアーキテクチャ向けのリンカ(5lはARMの古いアーキテクチャ名に由来)。アセンブリコードやコンパイルされたオブジェクトファイルをリンクして実行可能ファイルを生成する際に、特定の命令シーケンスを挿入するなどの処理を行います。
  • NOSPLIT: Goのアセンブリ関数におけるディレクティブの一つ。このディレクティブが付与された関数は、スタックの拡張(stack split)を行わないことを示します。これは、非常に短い関数や、スタックの拡張処理がオーバーヘッドとなるような低レベルのランタイム関数でよく使用されます。

技術的詳細

このコミットは、主に以下の2つの技術的な問題に対処しています。

  1. ARM整数除算における非標準スタックフレームの解消: GoのARM向け整数除算ルーチン(_divu, _modu, _div, _modなど)は、リンカによって特定の命令シーケンスに展開されます。以前の実装では、これらのルーチンが追加の引数(除数など)をスタック上の新しいスロットに格納していました。このスタックレイアウトがGoランタイムのトレースバックルーチンが期待する標準的なスタックフレームの形式と異なっていたため、プロファイリング時に問題が発生しました。 このコミットでは、追加の引数をスタックではなく、現在のM構造体(runtime.hで定義されるM構造体)の新しいフィールドdivmodに格納するように変更します。これにより、除算ルーチンが呼び出されてもスタックフレームの構造は標準的なままであり、トレースバックルーチンが常に正しくスタックを辿れるようになります。

  2. トレースバックコードにおけるLRレジスタの誤った使用の修正: トレースバックルーチンは、関数のリターンアドレスを特定するためにLRレジスタを参照します。しかし、Goの関数は、呼び出された直後に自身のスタックフレームを構築し、その過程で元のLRレジスタの値をスタックに保存することがあります。その後、LRレジスタは関数内で一時的なレジスタとして再利用される可能性があります。 以前のトレースバックコードでは、このような状況でLRレジスタの現在の値に依存してしまうことがありました。このコミットでは、src/pkg/runtime/traceback_arm.cにおいて、トレースバックを開始する際に、現在の関数がすでにスタックフレームを割り当ててLRをスタックに保存している場合、LRレジスタの現在の値ではなく、スタックに保存されたLRのコピーを使用するように修正します。これにより、LRレジスタが変更されていても、常に正しいリターンアドレスを特定できるようになります。

これらの変更は、GoのARM環境におけるプロファイリングの正確性と安定性を大幅に向上させます。

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

このコミットでは、主に以下のファイルが変更されています。

  1. src/cmd/5l/noop.c:

    • リンカがARMの除算命令を擬似命令から実際の呼び出しシーケンスに変換する部分が変更されています。
    • 以前は除算の引数をスタックにプッシュしていましたが、この変更により、引数をM構造体の新しいフィールドに格納するように修正されています。具体的には、MOV a,4(SP)のようなスタック操作がMOV a,4(M)のようなM構造体へのアクセスに変わっています。
    • スタックポインタの調整(ADD $8,SPSUB $8,SP)に関するコードが削除されています。これは、スタックフレームの非標準的な使用がなくなったためです。
  2. src/pkg/runtime/pprof/pprof_test.go:

    • TestMathBigDivideという新しいテスト関数が追加されています。
    • このテストは、math/bigパッケージのDivメソッド(内部で整数除算を使用する)を繰り返し実行し、CPUプロファイリングが正しく機能するかどうかを検証します。これは、Issue 6681で報告された問題の「torture test」を再現し、修正が有効であることを確認するためのものです。
  3. src/pkg/runtime/runtime.h:

    • M構造体(struct M)に新しいフィールドuint32 divmod; // div/mod denominator on armが追加されています。
    • このフィールドは、ARMアーキテクチャでの整数除算および剰余演算の際に、除数(denominator)を一時的に格納するために使用されます。
  4. src/pkg/runtime/traceback_arm.c:

    • runtime·gentraceback関数内で、LRレジスタの扱いに関する条件が変更されています。
    • 以前はif(frame.lr == 0)という条件でしたが、if((n == 0 && frame.fp > frame.sp) || frame.lr == 0)という条件に拡張されています。
    • これは、関数がスタックフレームを既に割り当てており(frame.fp > frame.sp)、かつ最初のフレームである場合(n == 0)、LRレジスタの現在の値ではなく、スタックに保存されたLRのコピー(*(uintptr*)frame.sp)を使用するように修正しています。
  5. src/pkg/runtime/vlop_arm.s:

    • ARMアセンブリで記述された整数除算ルーチン(_divu, _modu, _div, _mod)が変更されています。
    • これらのルーチンは、除数(denominator)をスタックから読み取る代わりに、M構造体の新しいフィールドm_divmod(m)から読み取るように修正されています。
    • ゼロ除算パニックを処理するudiv_by_0ルーチンも簡素化され、非標準のスタックアンワインド処理が削除されています。

コアとなるコードの解説

src/cmd/5l/noop.c の変更

このファイルはGoのリンカの一部であり、ARMアーキテクチャにおける特定の擬似命令(ここでは整数除算)を、実際の機械語命令シーケンスに展開する役割を担っています。

変更前は、除算のオペランド(特に除数)をスタックに一時的に保存していました。例えば、MOV a,4(SP)のような命令が生成され、これはスタックポインタ(SP)からのオフセットでメモリにアクセスしていました。このスタックへの一時的な保存が、Goランタイムのトレースバックルーチンが期待する標準的なスタックフレームのレイアウトを崩していました。

変更後、このコミットでは、除算のオペランドをスタックではなく、M構造体(GoランタイムのOSスレッドを表す構造体)の新しいフィールドdivmodに格納するように変更しました。これにより、スタックフレームは常に標準的な形式を保つことができます。

--- a/src/cmd/5l/noop.c
+++ b/src/cmd/5l/noop.c
@@ -401,26 +401,27 @@ noops(void)
 					break;
 				if(p->to.type != D_REG)
 					break;
-				q1 = p;
+
+				orig = *p;
 	
-				/* MOV a,4(SP) */
-				p = appendp(p);
+				/* MOV a,4(M) */
 				p->as = AMOVW;
-				p->line = q1->line;
+				p->line = orig.line;
 				p->from.type = D_REG;
-				p->from.reg = q1->from.reg;
+				p->from.reg = orig.from.reg;
+				p->reg = NREG;
 				p->to.type = D_OREG;
-				p->to.reg = REGSP;
+				p->to.reg = REGM;
 				p->to.offset = 4;
 	
 				/* MOV b,REGTMP */
 				p = appendp(p);
 				p->as = AMOVW;
-				p->line = q1->line;
+				p->line = orig.line;
 				p->from.type = D_REG;
-				p->from.reg = q1->reg;
-				if(q1->reg == NREG)
-					p->from.reg = q1->to.reg;
+				p->from.reg = orig.reg;
+				if(orig.reg == NREG)
+					p->from.reg = orig.to.reg;
 				p->to.type = D_REG;
 				p->to.reg = REGTMP;
 				p->to.offset = 0;
@@ -428,7 +429,7 @@ noops(void)
 				/* CALL appropriate */
 				p = appendp(p);
 				p->as = ABL;
-				p->line = q1->line;
+				p->line = orig.line;
 				p->to.type = D_BRANCH;
 				p->cond = p;
 				switch(o) {
@@ -453,34 +454,12 @@ noops(void)
 				/* MOV REGTMP, b */
 				p = appendp(p);
 				p->as = AMOVW;
-				p->line = q1->line;
+				p->line = orig.line;
 				p->from.type = D_REG;
 				p->from.reg = REGTMP;
 				p->from.offset = 0;
 				p->to.type = D_REG;
-				p->to.reg = q1->to.reg;
-	
-				/* ADD $8,SP */
-				p = appendp(p);
-				p->as = AADD;
-				p->line = q1->line;
-				p->from.type = D_CONST;
-				p->from.reg = NREG;
-				p->from.offset = 8;
-				p->reg = NREG;
-				p->to.type = D_REG;
-				p->to.reg = REGSP;
-				p->spadj = -8;
-	
-				/* SUB $8,SP */
-				q1->as = ASUB;
-				q1->from.type = D_CONST;
-				q1->from.offset = 8;
-				q1->from.reg = NREG;
-				q1->reg = NREG;
-				q1->to.type = D_REG;
-				q1->to.reg = REGSP;
-				q1->spadj = 8;
+				p->to.reg = orig.to.reg;
 	
 				break;
 			case AMOVW:

src/pkg/runtime/runtime.h の変更

M構造体にdivmodフィールドが追加されました。これは、ARMアーキテクチャでの除算操作中に除数を一時的に保持するためのものです。これにより、除算ルーチンがスタックを非標準的に使用する必要がなくなります。

--- a/src/pkg/runtime/runtime.h
+++ b/src/pkg/runtime/runtime.h
@@ -290,6 +290,7 @@ struct	G
 struct	M
 {
 	G*		g0;		// goroutine with scheduling stack
+	uint32	divmod;		// div/mod denominator on arm
 	void*	moreargp;	// argument pointer for more stack
 	Gobuf	morebuf;	// gobuf arg to morestack
 

src/pkg/runtime/vlop_arm.s の変更

このファイルには、ARMアーキテクチャ向けの低レベルな整数除算および剰余演算のアセンブリルーチンが含まれています。

変更前は、これらのルーチン(_divu, _modu, _div, _mod)は、除数をスタックから読み取っていました(例: MOVW 0(FP), R(q))。これは、リンカが生成する非標準のスタックフレームと連携していました。

変更後、これらのルーチンは、除数をM構造体の新しいフィールドm_divmod(m)から読み取るように修正されました。これにより、スタックフレームのレイアウトが標準的なままで、除算操作に必要な情報が渡されるようになります。

また、ゼロ除算パニックを処理するudiv_by_0ルーチンも簡素化されました。以前は、トレースバックルーチンが期待するスタックレイアウトに合わせるために、複雑なスタックアンワインドとLRレジスタの操作を行っていましたが、非標準スタックフレームの問題が解決されたため、これらの処理は不要になりました。

--- a/src/pkg/runtime/vlop_arm.s
+++ b/src/pkg/runtime/vlop_arm.s
@@ -105,7 +105,7 @@ s = 2 // three temporary variables
 M = 3
 a = 11
 // Be careful: R(a) == R11 will be used by the linker for synthesized instructions.
-TEXT udiv<>(SB),NOSPLIT,$-4
+TEXT udiv<>(SB),NOSPLIT,$-4-0
 	CLZ 	R(q), R(s) // find normalizing shift
 	MOVW.S	R(q)<<R(s), R(a)
 	MOVW	$fast_udiv_tab<>-64(SB), R(M)
@@ -165,22 +165,8 @@ udiv_by_0_or_1:\
 	RET
 
 udiv_by_0:\
--	// The ARM toolchain expects it can emit references to DIV and MOD
--	// instructions. The linker rewrites each pseudo-instruction into
--	// a sequence that pushes two values onto the stack and then calls
--	// _divu, _modu, _div, or _mod (below), all of which have a 16-byte
--	// frame plus the saved LR. The traceback routine knows the expanded
--	// stack frame size at the pseudo-instruction call site, but it
--	// doesn't know that the frame has a non-standard layout. In particular,
--	// it expects to find a saved LR in the bottom word of the frame.
--	// Unwind the stack back to the pseudo-instruction call site, copy the
--	// saved LR where the traceback routine will look for it, and make it
--	// appear that panicdivide was called from that PC.
--	MOVW	0(R13), LR
--	ADD	$20, R13
--	MOVW	8(R13), R1 // actual saved LR
--	MOVW	R1, 0(R13) // expected here for traceback
--	B 	runtime·panicdivide(SB)
-+	MOVW 	$runtime·panicdivide(SB),R11
-+	B	(R11)
+	MOVW 	$runtime·panicdivide(SB),R11
+	B	(R11)
 
 TEXT fast_udiv_tab<>(SB),NOSPLIT,$-4
 	// var tab [64]byte
@@ -207,14 +193,16 @@ TEXT fast_udiv_tab<>(SB),NOSPLIT,$-4
 // expects the result in R(TMP)
 TMP = 11
 
-TEXT _divu(SB), NOSPLIT, $16
+TEXT _divu(SB), NOSPLIT, $16-0
 	MOVW	R(q), 4(R13)
 	MOVW	R(r), 8(R13)
 	MOVW	R(s), 12(R13)
 	MOVW	R(M), 16(R13)
 
 	MOVW	R(TMP), R(r)		/* numerator */
-	MOVW	0(FP), R(q) 		/* denominator */
+	MOVW	m_divmod(m), R(q) 	/* denominator */
+	MOVW	$0, R(s)
+	MOVW	R(s), m_divmod(m)
 	BL  	udiv<>(SB)
 	MOVW	R(q), R(TMP)
 	MOVW	4(R13), R(q)
@@ -223,14 +211,16 @@ TEXT _divu(SB), NOSPLIT, $16
 	MOVW	16(R13), R(M)
 	RET
 
-TEXT _modu(SB), NOSPLIT, $16
+TEXT _modu(SB), NOSPLIT, $16-0
 	MOVW	R(q), 4(R13)
 	MOVW	R(r), 8(R13)
 	MOVW	R(s), 12(R13)
 	MOVW	R(M), 16(R13)
 
 	MOVW	R(TMP), R(r)		/* numerator */
-	MOVW	0(FP), R(q) 		/* denominator */
+	MOVW	m_divmod(m), R(q) 	/* denominator */
+	MOVW	$0, R(s)
+	MOVW	R(s), m_divmod(m)
 	BL  	udiv<>(SB)
 	MOVW	R(r), R(TMP)
 	MOVW	4(R13), R(q)
@@ -239,13 +229,15 @@ TEXT _modu(SB), NOSPLIT, $16
 	MOVW	16(R13), R(M)
 	RET
 
-TEXT _div(SB),NOSPLIT,$16
+TEXT _div(SB),NOSPLIT,$16-0
 	MOVW	R(q), 4(R13)
 	MOVW	R(r), 8(R13)
 	MOVW	R(s), 12(R13)
 	MOVW	R(M), 16(R13)
 	MOVW	R(TMP), R(r)		/* numerator */
-	MOVW	0(FP), R(q) 		/* denominator */
+	MOVW	m_divmod(m), R(q) 		/* denominator */
+	MOVW	$0, R(s)
+	MOVW	R(s), m_divmod(m)
 	CMP 	$0, R(r)
 	BGE 	d1
 	RSB 	$0, R(r), R(r)
@@ -265,13 +257,15 @@ d2:\
 	RSB		$0, R(q), R(TMP)
 	B   	out
 
-TEXT _mod(SB),NOSPLIT,$16
+TEXT _mod(SB),NOSPLIT,$16-0
 	MOVW	R(q), 4(R13)
 	MOVW	R(r), 8(R13)
 	MOVW	R(s), 12(R13)
 	MOVW	R(M), 16(R13)
 	MOVW	R(TMP), R(r)		/* numerator */
-	MOVW	0(FP), R(q) 		/* denominator */
+	MOVW	m_divmod(m), R(q) 		/* denominator */
+	MOVW	$0, R(s)
+	MOVW	R(s), m_divmod(m)
 	CMP 	$0, R(q)
 	RSB.LT	$0, R(q), R(q)
 	CMP 	$0, R(r)

src/pkg/runtime/traceback_arm.c の変更

このファイルは、Goランタイムのトレースバック処理を実装しています。特にARMアーキテクチャに特化した部分です。

変更前は、トレースバックルーチンがLRレジスタの値を直接参照してリターンアドレスを決定していました。しかし、関数がスタックフレームを構築し、元のLRをスタックに保存した後、LRレジスタが他の目的で再利用される可能性がありました。この場合、LRレジスタの現在の値は正しいリターンアドレスを示しません。

変更後、runtime·gentraceback関数内の条件が修正され、より堅牢なLRレジスタの処理が行われるようになりました。

--- a/src/pkg/runtime/traceback_arm.c
+++ b/src/pkg/runtime/traceback_arm.c
@@ -84,7 +84,7 @@ runtime·gentraceback(uintptr pc0, uintptr sp0, uintptr lr0, G *gp, int32 skip,\
 			frame.lr = 0;
 			flr = nil;
 		} else {
-			if(frame.lr == 0)
+			if((n == 0 && frame.fp > frame.sp) || frame.lr == 0)
 				frame.lr = *(uintptr*)frame.sp;
 			flr = runtime·findfunc(frame.lr);
 			if(flr == nil) {

新しい条件 (n == 0 && frame.fp > frame.sp) || frame.lr == 0 の意味は以下の通りです。

  • n == 0: 現在処理しているスタックフレームが、トレースバックの最初のフレームであることを示します。
  • frame.fp > frame.sp: 現在の関数がすでにスタックフレームを割り当てていることを示します(フレームポインタがスタックポインタよりも大きい場合、スタックフレームが構築されている)。
  • frame.lr == 0: LRレジスタがゼロである場合(これは通常、無効なリターンアドレスを示唆します)。

この条件が真の場合、LRレジスタの現在の値ではなく、スタックに保存されたLRのコピー(*(uintptr*)frame.sp)を使用します。これにより、LRレジスタが再利用されていても、常に正しいリターンアドレスを特定できるようになります。

src/pkg/runtime/pprof/pprof_test.go の変更

このファイルには、Goのプロファイリング機能のテストが含まれています。

TestMathBigDivideという新しいテスト関数が追加されました。このテストは、math/bigパッケージのDivメソッドを繰り返し呼び出すことで、Goの整数除算が頻繁に行われる状況をシミュレートします。math/bigパッケージの除算は、内部でGoランタイムの低レベルな整数除算ルーチンを使用するため、このテストはARMアーキテクチャでの除算プロファイリングの問題を顕在化させるのに適しています。

--- a/src/pkg/runtime/pprof/pprof_test.go
+++ b/src/pkg/runtime/pprof/pprof_test.go
@@ -8,6 +8,7 @@ import (
 	"bytes"
 	"fmt"
 	"hash/crc32"
+	"math/big"
 	"os/exec"
 	"regexp"
 	"runtime"
@@ -123,6 +124,10 @@ func testCPUProfile(t *testing.T, need []string, f func()) {
 		}
 	})
 
+	if len(need) == 0 {
+		return
+	}
+
 	var total uintptr
 	for i, name := range need {
 		total += have[i]
@@ -237,6 +242,26 @@ func TestGoroutineSwitch(t *testing.T) {
 	}
 }
 
+// Test that profiling of division operations is okay, especially on ARM. See issue 6681.
+func TestMathBigDivide(t *testing.T) {
+	testCPUProfile(t, nil, func() {
+		t := time.After(5 * time.Second)
+		pi := new(big.Int)
+		for {
+			for i := 0; i < 100; i++ {
+				n := big.NewInt(2646693125139304345)
+				d := big.NewInt(842468587426513207)
+				pi.Div(n, d)
+			}
+			select {
+			case <-t:
+				return
+			default:
+			}
+		}
+	})
+}
+
 // Operating systems that are expected to fail the tests. See issue 6047.
 var badOS = map[string]bool{
 	"darwin":  true,

このテストは、testCPUProfileヘルパー関数を使用してCPUプロファイリングを有効にし、math/big.Int.Divを繰り返し呼び出します。これにより、除算操作がプロファイルデータに正しく反映され、スタックトレースが破損しないことを確認します。このテストがパスすることは、Issue 6681で報告された問題が解決されたことを意味します。

関連リンク

  • Go issue 6681: 残念ながら、Goの公式イシュートラッカーで「issue 6681」を直接検索しても関連情報が見つかりませんでした。しかし、コミットメッセージから、このイシューがARM環境での整数除算のプロファイリングに関する問題と、それを検証する「torture test」に関連していることが明確に示されています。
  • Go CL 13239052: このコミットで言及されている以前の変更。ゼロ除算パニックの特定のケースに対する修正が含まれていましたが、プロファイリングには不十分でした。
  • Go CL 19810043: このコミット自体のGo Code Reviewサイトへのリンク。

参考にした情報源リンク

  • Goの公式ドキュメント(Goランタイム、プロファイリング、ARMアーキテクチャに関する情報)
  • Goのソースコード(特にsrc/cmd/5lsrc/pkg/runtimeディレクトリ)
  • ARMアーキテクチャのリファレンスマニュアル(スタックフレーム、レジスタの動作に関する情報)
  • math/bigパッケージのドキュメント(整数除算の動作に関する情報)