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

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

このコミットは、Go言語のsync/atomicパッケージ内のアセンブリコードにおける、go vetツールによって検出された「重要ではない」エラーの修正を目的としています。具体的には、アセンブリコード内の変数名の参照のセマンティックな修正と、NOSPLIT関数におけるスタックサイズの宣言の修正が含まれます。これらの修正は、実際のバグを引き起こすものではなく、主にコードの正確性とgo vetによる静的解析の警告を解消するためのものです。

コミット

commit 42ea2eda4902469fc15be067ee4e0becfae27ec4
Author: Russ Cox <rsc@golang.org>
Date:   Thu May 15 16:31:20 2014 -0400

    sync/atomic: fix unimportant assembly errors found by go vet

    None of these are real bugs.
    The variable name in the reference is not semantically meaningful,
    except that 'go vet' will double check the offset against the name for you.

    The stack sizes being corrected really are incorrect but they are also
    in NOSPLIT functions so they typically don't matter.

    Found by vet.

    GOOS=linux GOARCH=amd64 go vet sync/atomic
    GOOS=linux GOARCH=amd64p32 go vet sync/atomic
    GOOS=linux GOARCH=386 go vet sync/atomic
    GOOS=linux GOARCH=arm go vet sync/atomic
    GOOS=freebsd GOARCH=arm go vet sync/atomic
    GOOS=netbsd GOARCH=arm go vet sync/atomic

    LGTM=r
    R=r, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/100500043

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

https://github.com/golang/go/commit/42ea2eda4902469fc15be067ee4e0becfae27ec4

元コミット内容

このコミットは、sync/atomicパッケージ内のアセンブリコードにおいて、go vetツールが検出したいくつかの問題を修正します。これらの問題は、実際の実行時バグには繋がらない「重要ではない」エラーとされています。主な修正内容は以下の通りです。

  1. アセンブリ内の変数名参照の修正: アセンブリコード内で、関数の引数や戻り値を参照する際の変数名が、セマンティックに誤っていた箇所を修正しています。例えば、Swap操作の戻り値は「新しい値」ではなく「古い値」であるため、new+X(FP)のような参照をold+X(FP)に修正しています。go vetは、これらの名前とスタックオフセットの整合性をチェックします。
  2. NOSPLIT関数におけるスタックサイズの修正: アセンブリ関数の宣言におけるスタックサイズ(例: $0-16)が不正確であった箇所を修正しています。コミットメッセージによると、これらの関数はNOSPLIT属性を持つため、通常はスタックサイズが問題になることはありませんが、正確性を期して修正されました。

これらの修正は、go vetが様々なアーキテクチャ(amd64, amd64p32, 386, arm)およびOS(Linux, FreeBSD, NetBSD)でsync/atomicパッケージに対して実行された際に発見されました。

変更の背景

Go言語の標準ライブラリ、特にsync/atomicパッケージは、並行処理におけるアトミック操作を提供するために、パフォーマンスが重要な部分でアセンブリ言語を使用しています。アセンブリコードは非常に低レベルであり、特定のCPUアーキテクチャに依存するため、記述には細心の注意が必要です。

このコミットの背景には、Goの静的解析ツールであるgo vetの進化と、コードベース全体の品質向上への取り組みがあります。go vetは、Goプログラムの潜在的なバグや疑わしい構造を検出するために設計されており、コンパイルエラーにはならないが実行時に問題を引き起こす可能性のあるコードパターンを特定します。

このケースでは、go vetがアセンブリコード内の特定のパターン(変数名の不一致やスタックサイズの宣言ミス)を「エラー」として報告しました。コミットメッセージが明示しているように、これらのエラーは「実際のバグではない」と判断されました。これは、Goのアセンブリにおける変数名が、コンパイラやリンカによって直接的に意味を持つわけではなく、主にデバッグ情報やgo vetのようなツールによる検証のために使われるためです。また、NOSPLIT関数はスタックの拡張を行わないため、宣言されたスタックサイズが多少不正確であっても、実行時の問題には繋がりにくいという特性があります。

しかし、コードの正確性を高め、将来的なツールの改善や、より厳密なチェックに対応できるようにするため、これらの「重要ではない」エラーも修正されることになりました。これは、Goプロジェクトがコード品質と保守性に対して高い基準を持っていることを示しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびアセンブリに関する前提知識が必要です。

1. Go言語のアセンブリ

Go言語は、一部のパフォーマンスクリティカルな部分(特にランタイムや標準ライブラリの低レベルな部分)でアセンブリ言語を使用します。Goのアセンブリは、一般的なGAS (GNU Assembler) 構文とは異なり、Plan 9アセンブラの構文に基づいています。主な特徴は以下の通りです。

  • 擬似レジスタ: FP (Frame Pointer), SP (Stack Pointer), PC (Program Counter) などの擬似レジスタを使用します。
    • FP: 関数フレームの引数と戻り値を参照するために使用されます。symbol+offset(FP) の形式でアクセスします。
    • SP: ローカル変数や一時的なスタック領域を参照するために使用されます。offset(SP) の形式でアクセスします。
  • シンボル参照: グローバルシンボルはpkgname·SymbolName(SB)の形式で参照されます。SB (Static Base) は、静的メモリの基点を示します。
  • 関数宣言: TEXT symbol(SB), flags, $framesize-argsize の形式で関数を宣言します。
    • framesize: 関数のスタックフレームのサイズ(ローカル変数などに使用される領域)。
    • argsize: 関数の引数と戻り値の合計サイズ。go vetはこのargsizeと、FPオフセットによる引数/戻り値のアクセスが一致するかをチェックします。
  • NOSPLITフラグ: 関数宣言のflagsの一つで、この関数がスタックの拡張(stack split)を行わないことを示します。これは、非常に短い関数や、スタックの拡張が許されない低レベルな関数(例: スケジューラの一部)で使用されます。NOSPLIT関数は、呼び出し元が十分なスタック空間を確保していることを前提とします。

2. go vetツール

go vetは、Go言語のソースコードを静的に解析し、潜在的なバグや疑わしいコード構造を報告するツールです。コンパイラが検出できないような、以下のような問題を発見するのに役立ちます。

  • フォーマット文字列の不一致
  • 到達不能なコード
  • ロックの誤用
  • 構造体タグの誤り
  • アセンブリコードにおける引数/戻り値のオフセットの不一致(今回のケース)

go vetは、Go開発ワークフローの重要な一部であり、コードレビューやCI/CDパイプラインで頻繁に利用されます。

3. sync/atomicパッケージ

sync/atomicパッケージは、Goプログラムでアトミックな(不可分な)操作を提供します。これにより、複数のゴルーチンが共有メモリに同時にアクセスする際に発生する競合状態(race condition)を防ぎます。このパッケージの関数は、CPUのハードウェアサポート(例: Compare-and-Swap (CAS) 命令)を利用して実装されており、非常に高速です。そのため、多くの場合、アセンブリ言語で直接実装されています。

技術的詳細

このコミットで行われた技術的な修正は、Goのアセンブリコードの特性とgo vetの検査ロジックに深く関連しています。

1. 変数名参照のセマンティックな修正

Goのアセンブリでは、symbol+offset(FP)という形式でスタックフレーム上のデータにアクセスします。ここでsymbolは、Goのソースコードで定義された引数や戻り値の変数名に対応します。例えば、TEXT ·SwapUint32(SB),NOSPLIT,$0-12という関数宣言において、new+4(FP)newという名前の引数に、old+8(FP)oldという名前の戻り値に対応するといった具合です。

go vetは、Goの関数シグネチャ(例: func SwapUint32(addr *uint32, new uint32) (old uint32))と、対応するアセンブリコード内のFPオフセットによる変数名参照を比較します。もし、Goのシグネチャでoldという戻り値が定義されているにもかかわらず、アセンブリコードがその戻り値をnewという名前で参照している場合、go vetは警告を発します。

今回の修正では、SwapUint32などの関数で、交換前の値(old value)を戻り値として返す際に、アセンブリコード内でnew+X(FP)と誤って参照していた箇所をold+X(FP)に修正しました。これは、アセンブリコードの動作自体を変えるものではなく、Goの関数シグネチャとアセンブリコードの間の「名前の整合性」をgo vetの観点から正しくするための修正です。

2. NOSPLIT関数におけるスタックサイズの修正

Goのアセンブリ関数宣言における$framesize-argsizeの部分は、その関数のスタックフレームのサイズと、引数および戻り値の合計サイズを宣言します。go vetは、このargsizeがGoの関数シグネチャから計算される引数と戻り値の合計サイズと一致するかをチェックします。

コミットメッセージにあるように、これらのスタックサイズは「本当に不正確」でした。しかし、これらの関数はすべてNOSPLIT属性を持っています。NOSPLIT関数は、実行時にスタックの拡張チェックを行わないため、スタックサイズが宣言と異なっていても、通常は実行時エラーには繋がりません。これは、NOSPLIT関数が非常に短いコードパスで使われるか、呼び出し元が十分なスタックを確保していることが保証されている場合にのみ使用されるためです。

それでも、go vetが警告を発する以上、コードの正確性を保つために修正されました。例えば、TEXT ·LoadInt64(SB),NOSPLIT,$0-16TEXT ·LoadInt64(SB),NOSPLIT,$0-12に修正されたのは、LoadInt64の引数と戻り値の合計サイズが12バイトであることを正確に反映するためです。

3. export_linux_arm_test.goの修正

このファイルは、Goのテストフレームワークからアセンブリ関数を呼び出すためのエクスポート宣言を含んでいます。func generalCAS64(*uint64, uint64, uint64) boolからfunc generalCAS64(addr *uint64, old uint64, new uint64) boolへの変更は、Goの関数宣言における引数に具体的な名前(addr, old, new)を付与したものです。これは、コードの可読性を向上させるとともに、go vetがアセンブリコード内のFPオフセットとこれらの名前の整合性をより正確にチェックできるようにするための変更です。

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

このコミットでは、主に以下のファイルが変更されています。これらはすべてsync/atomicパッケージ内のアセンブリソースファイルと、そのテスト関連ファイルです。

  • src/pkg/sync/atomic/asm_386.s (386アーキテクチャ用アセンブリ)
  • src/pkg/sync/atomic/asm_amd64.s (AMD64アーキテクチャ用アセンブリ)
  • src/pkg/sync/atomic/asm_amd64p32.s (AMD64の32ビットポインタモード用アセンブリ)
  • src/pkg/sync/atomic/asm_linux_arm.s (Linux ARMアーキテクチャ用アセンブリ)
  • src/pkg/sync/atomic/export_linux_arm_test.go (ARMアセンブリ関数のテスト用Go宣言)

具体的な変更例をいくつか示します。

src/pkg/sync/atomic/asm_386.s の変更例:

--- a/src/pkg/sync/atomic/asm_386.s
+++ b/src/pkg/sync/atomic/asm_386.s
@@ -13,7 +13,7 @@ TEXT ·SwapUint32(SB),NOSPLIT,$0-12
 	MOVL	addr+0(FP), BP
 	MOVL	new+4(FP), AX
 	XCHGL	AX, 0(BP)
-	MOVL	AX, new+8(FP)
+	MOVL	AX, old+8(FP)
 	RET

 TEXT ·SwapInt64(SB),NOSPLIT,$0-20
@@ -43,8 +43,8 @@ swaploop:

 	// success
 	// return DX:AX
-	MOVL	AX, new_lo+12(FP)
-	MOVL	DX, new_hi+16(FP)
+	MOVL	AX, old_lo+12(FP)
+	MOVL	DX, old_hi+16(FP)
 	RET

 TEXT ·SwapUintptr(SB),NOSPLIT,$0-12
@@ -155,10 +155,10 @@ TEXT ·LoadUint32(SB),NOSPLIT,$0-8
 	MOVL	AX, val+4(FP)
 	RET

-TEXT ·LoadInt64(SB),NOSPLIT,$0-16
+TEXT ·LoadInt64(SB),NOSPLIT,$0-12
 	JMP	·LoadUint64(SB)

-TEXT ·LoadUint64(SB),NOSPLIT,$0-16
+TEXT ·LoadUint64(SB),NOSPLIT,$0-12
 	MOVL	addr+0(FP), AX
 	TESTL	$7, AX
 	JZ	2(PC)
@@ -186,10 +186,10 @@ TEXT ·StoreUint32(SB),NOSPLIT,$0-8
 	XCHGL	AX, 0(BP)
 	RET

-TEXT ·StoreInt64(SB),NOSPLIT,$0-16
+TEXT ·StoreInt64(SB),NOSPLIT,$0-12
 	JMP	·StoreUint64(SB)

-TEXT ·StoreUint64(SB),NOSPLIT,$0-16
+TEXT ·StoreUint64(SB),NOSPLIT,$0-12
 	MOVL	addr+0(FP), AX
 	TESTL	$7, AX
 	JZ	2(PC)

src/pkg/sync/atomic/export_linux_arm_test.go の変更例:

--- a/src/pkg/sync/atomic/export_linux_arm_test.go
+++ b/src/pkg/sync/atomic/export_linux_arm_test.go
@@ -4,4 +4,4 @@

 package atomic

-func generalCAS64(*uint64, uint64, uint64) bool
+func generalCAS64(addr *uint64, old uint64, new uint64) bool

 var GeneralCAS64 = generalCAS64

コアとなるコードの解説

1. 変数名参照の修正 (new -> old)

asm_386.sTEXT ·SwapUint32(SB),NOSPLIT,$0-12関数を例に取ります。 SwapUint32は、指定されたアドレスのuint32値と新しい値を交換し、交換前の古い値を返します。

元のコード: MOVL AX, new+8(FP)

修正後のコード: MOVL AX, old+8(FP)

ここでAXレジスタには交換前の値が格納されています。Goの関数シグネチャでは、この戻り値は通常oldという名前で定義されます。FPからのオフセット+8は、この戻り値がスタックフレーム上のどこに配置されるかを示します。go vetは、Goの関数シグネチャとアセンブリコード内のこの名前の対応関係をチェックします。以前はnewという名前で参照されていましたが、これはセマンティックに誤りであり、go vetが警告を発する原因となっていました。この修正により、アセンブリコードがGoの関数シグネチャの意図と一致するようになりました。同様の修正が、SwapInt64などの他のSwap関連関数や、generalCAS64の引数/戻り値の参照にも適用されています。

2. スタックサイズの修正 ($0-16 -> $0-12 など)

asm_386.sTEXT ·LoadInt64(SB),NOSPLIT,$0-16関数を例に取ります。

元のコード: TEXT ·LoadInt64(SB),NOSPLIT,$0-16

修正後のコード: TEXT ·LoadInt64(SB),NOSPLIT,$0-12

Goのアセンブリ関数宣言の$framesize-argsizeの部分は、argsizeが引数と戻り値の合計サイズを示します。LoadInt64は、addr *int64という1つの引数を取り、int64という1つの戻り値を返します。32ビットシステム(386)では、ポインタは4バイト、int64は8バイトです。したがって、引数と戻り値の合計サイズは4 (addr) + 8 (return value) = 12バイトとなります。元の宣言の$0-16は、この合計サイズが16バイトであると誤って宣言していました。この修正により、正確な引数/戻り値のサイズである12バイトに訂正されました。

コミットメッセージにあるように、これらの関数はNOSPLIT属性を持つため、スタックサイズが不正確であっても実行時の問題は発生しにくいですが、go vetの警告を解消し、コードの正確性を高めるために修正されました。

3. export_linux_arm_test.goの引数名追加

元のコード: func generalCAS64(*uint64, uint64, uint64) bool

修正後のコード: func generalCAS64(addr *uint64, old uint64, new uint64) bool

この変更は、Goの関数宣言に引数名を追加したものです。これは、アセンブリ関数generalCAS64のGo側の宣言であり、アセンブリコード自体を変更するものではありません。引数に名前を付けることで、コードの可読性が向上し、go vetがアセンブリコード内のFPオフセットとGoの関数シグネチャの引数名の対応関係をより厳密にチェックできるようになります。これにより、開発者がアセンブリコードを理解しやすくなり、将来的なメンテナンスやデバッグが容易になります。

関連リンク

参考にした情報源リンク

  • Goのコミットメッセージと差分: https://github.com/golang/go/commit/42ea2eda4902469fc15be067ee4e0becfae27ec4
  • Go Code Review Comments (特にアセンブリに関する慣習): https://go.dev/wiki/CodeReviewComments
  • GoのIssueトラッカーやメーリングリストでの関連議論 (特定のIssueは特定していませんが、go vetの改善やアセンブリコードの正確性に関する議論は頻繁に行われています)
  • Goのソースコード(特にsrc/cmd/vetsrc/runtimesrc/sync/atomicディレクトリ)
  • Plan 9アセンブラの構文に関する一般的な情報源 (Goのアセンブリの基盤)

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

このコミットは、Go言語のsync/atomicパッケージ内のアセンブリコードにおける、go vetツールによって検出された「重要ではない」エラーの修正を目的としています。具体的には、アセンブリコード内の変数名の参照のセマンティックな修正と、NOSPLIT関数におけるスタックサイズの宣言の修正が含まれます。これらの修正は、実際のバグを引き起こすものではなく、主にコードの正確性とgo vetによる静的解析の警告を解消するためのものです。

コミット

commit 42ea2eda4902469fc15be067ee4e0becfae27ec4
Author: Russ Cox <rsc@golang.org>
Date:   Thu May 15 16:31:20 2014 -0400

    sync/atomic: fix unimportant assembly errors found by go vet

    None of these are real bugs.
    The variable name in the reference is not semantically meaningful,
    except that 'go vet' will double check the offset against the name for you.

    The stack sizes being corrected really are incorrect but they are also
    in NOSPLIT functions so they typically don't matter.

    Found by vet.

    GOOS=linux GOARCH=amd64 go vet sync/atomic
    GOOS=linux GOARCH=amd64p32 go vet sync/atomic
    GOOS=linux GOARCH=386 go vet sync/atomic
    GOOS=linux GOARCH=arm go vet sync/atomic
    GOOS=freebsd GOARCH=arm go vet sync/atomic
    GOOS=netbsd GOARCH=arm go vet sync/atomic

    LGTM=r
    R=r, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/100500043

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

https://github.com/golang/go/commit/42ea2eda4902469fc15be067ee4e0becfae27ec4

元コミット内容

このコミットは、sync/atomicパッケージ内のアセンブリコードにおいて、go vetツールが検出したいくつかの問題を修正します。これらの問題は、実際の実行時バグには繋がらない「重要ではない」エラーとされています。主な修正内容は以下の通りです。

  1. アセンブリ内の変数名参照の修正: アセンブリコード内で、関数の引数や戻り値を参照する際の変数名が、セマンティックに誤っていた箇所を修正しています。例えば、Swap操作の戻り値は「新しい値」ではなく「古い値」であるため、new+X(FP)のような参照をold+X(FP)に修正しています。go vetは、これらの名前とスタックオフセットの整合性をチェックします。
  2. NOSPLIT関数におけるスタックサイズの修正: アセンブリ関数の宣言におけるスタックサイズ(例: $0-16)が不正確であった箇所を修正しています。コミットメッセージによると、これらの関数はNOSPLIT属性を持つため、通常はスタックサイズが問題になることはありませんが、正確性を期して修正されました。

これらの修正は、go vetが様々なアーキテクチャ(amd64, amd64p32, 386, arm)およびOS(Linux, FreeBSD, NetBSD)でsync/atomicパッケージに対して実行された際に発見されました。

変更の背景

Go言語の標準ライブラリ、特にsync/atomicパッケージは、並行処理におけるアトミック操作を提供するために、パフォーマンスが重要な部分でアセンブリ言語を使用しています。アセンブリコードは非常に低レベルであり、特定のCPUアーキテクチャに依存するため、記述には細心の注意が必要です。

このコミットの背景には、Goの静的解析ツールであるgo vetの進化と、コードベース全体の品質向上への取り組みがあります。go vetは、Goプログラムの潜在的なバグや疑わしい構造を検出するために設計されており、コンパイルエラーにはならないが実行時に問題を引き起こす可能性のあるコードパターンを特定します。

このケースでは、go vetがアセンブリコード内の特定のパターン(変数名の不一致やスタックサイズの宣言ミス)を「エラー」として報告しました。コミットメッセージが明示しているように、これらのエラーは「実際のバグではない」と判断されました。これは、Goのアセンブリにおける変数名が、コンパイラやリンカによって直接的に意味を持つわけではなく、主にデバッグ情報やgo vetのようなツールによる検証のために使われるためです。また、NOSPLIT関数はスタックの拡張を行わないため、宣言されたスタックサイズが多少不正確であっても、実行時の問題には繋がりにくいという特性があります。

しかし、コードの正確性を高め、将来的なツールの改善や、より厳密なチェックに対応できるようにするため、これらの「重要ではない」エラーも修正されることになりました。これは、Goプロジェクトがコード品質と保守性に対して高い基準を持っていることを示しています。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびアセンブリに関する前提知識が必要です。

1. Go言語のアセンブリ

Go言語は、一部のパフォーマンスクリティカルな部分(特にランタイムや標準ライブラリの低レベルな部分)でアセンブリ言語を使用します。Goのアセンブリは、一般的なGAS (GNU Assembler) 構文とは異なり、Plan 9アセンブラの構文に基づいています。主な特徴は以下の通りです。

  • 擬似レジスタ: FP (Frame Pointer), SP (Stack Pointer), PC (Program Counter) などの擬似レジスタを使用します。
    • FP: 関数フレームの引数と戻り値を参照するために使用されます。symbol+offset(FP) の形式でアクセスします。
    • SP: ローカル変数や一時的なスタック領域を参照するために使用されます。offset(SP) の形式でアクセスします。
  • シンボル参照: グローバルシンボルはpkgname·SymbolName(SB)の形式で参照されます。SB (Static Base) は、静的メモリの基点を示します。
  • 関数宣言: TEXT symbol(SB), flags, $framesize-argsize の形式で関数を宣言します。
    • framesize: 関数のスタックフレームのサイズ(ローカル変数などに使用される領域)。
    • argsize: 関数の引数と戻り値の合計サイズ。go vetはこのargsizeと、FPオフセットによる引数/戻り値のアクセスが一致するかをチェックします。
  • NOSPLITフラグ: 関数宣言のflagsの一つで、この関数がスタックの拡張(stack split)を行わないことを示します。これは、非常に短い関数や、スタックの拡張が許されない低レベルな関数(例: スケジューラの一部)で使用されます。NOSPLIT関数は、呼び出し元が十分なスタック空間を確保していることを前提とします。

2. go vetツール

go vetは、Go言語のソースコードを静的に解析し、潜在的なバグや疑わしいコード構造を報告するツールです。コンパイラが検出できないような、以下のような問題を発見するのに役立ちます。

  • フォーマット文字列の不一致
  • 到達不能なコード
  • ロックの誤用
  • 構造体タグの誤り
  • アセンブリコードにおける引数/戻り値のオフセットの不一致(今回のケース)

go vetは、Go開発ワークフローの重要な一部であり、コードレビューやCI/CDパイプラインで頻繁に利用されます。

3. sync/atomicパッケージ

sync/atomicパッケージは、Goプログラムでアトミックな(不可分な)操作を提供します。これにより、複数のゴルーチンが共有メモリに同時にアクセスする際に発生する競合状態(race condition)を防ぎます。このパッケージの関数は、CPUのハードウェアサポート(例: Compare-and-Swap (CAS) 命令)を利用して実装されており、非常に高速です。そのため、多くの場合、アセンブリ言語で直接実装されています。

技術的詳細

このコミットで行われた技術的な修正は、Goのアセンブリコードの特性とgo vetの検査ロジックに深く関連しています。

1. 変数名参照のセマンティックな修正

Goのアセンブリでは、symbol+offset(FP)という形式でスタックフレーム上のデータにアクセスします。ここでsymbolは、Goのソースコードで定義された引数や戻り値の変数名に対応します。例えば、TEXT ·SwapUint32(SB),NOSPLIT,$0-12という関数宣言において、new+4(FP)newという名前の引数に、old+8(FP)oldという名前の戻り値に対応するといった具合です。

go vetは、Goの関数シグネチャ(例: func SwapUint32(addr *uint32, new uint32) (old uint32))と、対応するアセンブリコード内のFPオフセットによる変数名参照を比較します。もし、Goのシグネチャでoldという戻り値が定義されているにもかかわらず、アセンブリコードがその戻り値をnewという名前で参照している場合、go vetは警告を発します。

今回の修正では、SwapUint32などの関数で、交換前の値(old value)を戻り値として返す際に、アセンブリコード内でnew+X(FP)と誤って参照していた箇所をold+X(FP)に修正しました。これは、アセンブリコードの動作自体を変えるものではなく、Goの関数シグネチャとアセンブリコードの間の「名前の整合性」をgo vetの観点から正しくするための修正です。

2. NOSPLIT関数におけるスタックサイズの修正

Goのアセンブリ関数宣言における$framesize-argsizeの部分は、その関数のスタックフレームのサイズと、引数および戻り値の合計サイズを宣言します。go vetは、このargsizeがGoの関数シグネチャから計算される引数と戻り値の合計サイズと一致するかをチェックします。

コミットメッセージにあるように、これらのスタックサイズは「本当に不正確」でした。しかし、これらの関数はすべてNOSPLIT属性を持っています。NOSPLIT関数は、実行時にスタックの拡張チェックを行わないため、スタックサイズが宣言と異なっていても、通常は実行時エラーには繋がりません。これは、NOSPLIT関数が非常に短いコードパスで使われるか、呼び出し元が十分なスタックを確保していることが保証されている場合にのみ使用されるためです。

それでも、go vetが警告を発する以上、コードの正確性を保つために修正されました。例えば、TEXT ·LoadInt64(SB),NOSPLIT,$0-16TEXT ·LoadInt64(SB),NOSPLIT,$0-12に修正されたのは、LoadInt64の引数と戻り値の合計サイズが12バイトであることを正確に反映するためです。

3. export_linux_arm_test.goの修正

このファイルは、Goのテストフレームワークからアセンブリ関数を呼び出すためのエクスポート宣言を含んでいます。func generalCAS64(*uint64, uint64, uint64) boolからfunc generalCAS64(addr *uint64, old uint64, new uint64) boolへの変更は、Goの関数宣言における引数に具体的な名前(addr, old, new)を付与したものです。これは、コードの可読性を向上させるとともに、go vetがアセンブリコード内のFPオフセットとこれらの名前の整合性をより正確にチェックできるようにするための変更です。

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

このコミットでは、主に以下のファイルが変更されています。これらはすべてsync/atomicパッケージ内のアセンブリソースファイルと、そのテスト関連ファイルです。

  • src/pkg/sync/atomic/asm_386.s (386アーキテクチャ用アセンブリ)
  • src/pkg/sync/atomic/asm_amd64.s (AMD64アーキテクチャ用アセンブリ)
  • src/pkg/sync/atomic/asm_amd64p32.s (AMD64の32ビットポインタモード用アセンブリ)
  • src/pkg/sync/atomic/asm_linux_arm.s (Linux ARMアーキテクチャ用アセンブリ)
  • src/pkg/sync/atomic/export_linux_arm_test.go (ARMアセンブリ関数のテスト用Go宣言)

具体的な変更例をいくつか示します。

src/pkg/sync/atomic/asm_386.s の変更例:

--- a/src/pkg/sync/atomic/asm_386.s
+++ b/src/pkg/sync/atomic/asm_386.s
@@ -13,7 +13,7 @@ TEXT ·SwapUint32(SB),NOSPLIT,$0-12
 	MOVL	addr+0(FP), BP
 	MOVL	new+4(FP), AX
 	XCHGL	AX, 0(BP)
-	MOVL	AX, new+8(FP)
+	MOVL	AX, old+8(FP)
 	RET

 TEXT ·SwapInt64(SB),NOSPLIT,$0-20
@@ -43,8 +43,8 @@ swaploop:

 	// success
 	// return DX:AX
-	MOVL	AX, new_lo+12(FP)
-	MOVL	DX, new_hi+16(FP)
+	MOVL	AX, old_lo+12(FP)
+	MOVL	DX, old_hi+16(FP)
 	RET

 TEXT ·SwapUintptr(SB),NOSPLIT,$0-12
@@ -155,10 +155,10 @@ TEXT ·LoadUint32(SB),NOSPLIT,$0-8
 	MOVL	AX, val+4(FP)
 	RET

-TEXT ·LoadInt64(SB),NOSPLIT,$0-16
+TEXT ·LoadInt64(SB),NOSPLIT,$0-12
 	JMP	·LoadUint64(SB)

-TEXT ·LoadUint64(SB),NOSPLIT,$0-16
+TEXT ·LoadUint64(SB),NOSPLIT,$0-12
 	MOVL	addr+0(FP), AX
 	TESTL	$7, AX
 	JZ	2(PC)
@@ -186,10 +186,10 @@ TEXT ·StoreUint32(SB),NOSPLIT,$0-8
 	XCHGL	AX, 0(BP)
 	RET

-TEXT ·StoreInt64(SB),NOSPLIT,$0-16
+TEXT ·StoreInt64(SB),NOSPLIT,$0-12
 	JMP	·StoreUint64(SB)

-TEXT ·StoreUint64(SB),NOSPLIT,$0-16
+TEXT ·StoreUint64(SB),NOSPLIT,$0-12
 	MOVL	addr+0(FP), AX
 	TESTL	$7, AX
 	JZ	2(PC)

src/pkg/sync/atomic/export_linux_arm_test.go の変更例:

--- a/src/pkg/sync/atomic/export_linux_arm_test.go
+++ b/src/pkg/sync/atomic/export_linux_arm_test.go
@@ -4,4 +4,4 @@

 package atomic

-func generalCAS64(*uint64, uint64, uint64) bool
+func generalCAS64(addr *uint64, old uint64, new uint64) bool

 var GeneralCAS64 = generalCAS64

コアとなるコードの解説

1. 変数名参照の修正 (new -> old)

asm_386.sTEXT ·SwapUint32(SB),NOSPLIT,$0-12関数を例に取ります。 SwapUint32は、指定されたアドレスのuint32値と新しい値を交換し、交換前の古い値を返します。

元のコード: MOVL AX, new+8(FP)

修正後のコード: MOVL AX, old+8(FP)

ここでAXレジスタには交換前の値が格納されています。Goの関数シグネチャでは、この戻り値は通常oldという名前で定義されます。FPからのオフセット+8は、この戻り値がスタックフレーム上のどこに配置されるかを示します。go vetは、Goの関数シグネチャとアセンブリコード内のこの名前の対応関係をチェックします。以前はnewという名前で参照されていましたが、これはセマンティックに誤りであり、go vetが警告を発する原因となっていました。この修正により、アセンブリコードがGoの関数シグネチャの意図と一致するようになりました。同様の修正が、SwapInt64などの他のSwap関連関数や、generalCAS64の引数/戻り値の参照にも適用されています。

2. スタックサイズの修正 ($0-16 -> $0-12 など)

asm_386.sTEXT ·LoadInt64(SB),NOSPLIT,$0-16関数を例に取ります。

元のコード: TEXT ·LoadInt64(SB),NOSPLIT,$0-16

修正後のコード: TEXT ·LoadInt64(SB),NOSPLIT,$0-12

Goのアセンブリ関数宣言の$framesize-argsizeの部分は、argsizeが引数と戻り値の合計サイズを示します。LoadInt64は、addr *int64という1つの引数を取り、int64という1つの戻り値を返します。32ビットシステム(386)では、ポインタは4バイト、int64は8バイトです。したがって、引数と戻り値の合計サイズは4 (addr) + 8 (return value) = 12バイトとなります。元の宣言の$0-16は、この合計サイズが16バイトであると誤って宣言していました。この修正により、正確な引数/戻り値のサイズである12バイトに訂正されました。

コミットメッセージにあるように、これらの関数はNOSPLIT属性を持つため、スタックサイズが不正確であっても実行時の問題は発生しにくいですが、go vetの警告を解消し、コードの正確性を高めるために修正されました。

3. export_linux_arm_test.goの引数名追加

元のコード: func generalCAS64(*uint64, uint64, uint64) bool

修正後のコード: func generalCAS64(addr *uint64, old uint64, new uint64) bool

この変更は、Goの関数宣言に引数名を追加したものです。これは、アセンブリ関数generalCAS64のGo側の宣言であり、アセンブリコード自体を変更するものではありません。引数に名前を付けることで、コードの可読性が向上し、go vetがアセンブリコード内のFPオフセットとGoの関数シグネチャの引数名の対応関係をより厳密にチェックできるようになります。これにより、開発者がアセンブリコードを理解しやすくなり、将来的なメンテナンスやデバッグが容易になります。

関連リンク

参考にした情報源リンク

  • Goのコミットメッセージと差分: https://github.com/golang/go/commit/42ea2eda4902469fc15be067ee4e0becfae27ec4
  • Go Code Review Comments (特にアセンブリに関する慣習): https://go.dev/wiki/CodeReviewComments
  • GoのIssueトラッカーやメーリングリストでの関連議論 (特定のIssueは特定していませんが、go vetの改善やアセンブリコードの正確性に関する議論は頻繁に行われています)
  • Goのソースコード(特にsrc/cmd/vetsrc/runtimesrc/sync/atomicディレクトリ)
  • Plan 9アセンブラの構文に関する一般的な情報源 (Goのアセンブリの基盤)