[インデックス 17159] ファイルの概要
このコミットは、Go言語のmathパッケージ内のアセンブリファイル群にわたる変更です。具体的には、src/pkg/math/ディレクトリ配下にある、様々な数値計算関数(Abs, Asin, Atan2, Dim, Exp, Floor, Frexp, Hypot, Ldexp, Log, Mod, Modf, Remainder, Sin, Sincos, Sqrt, Tanなど)の各アーキテクチャ(386, amd64, arm)向けアセンブリ実装ファイルが対象となっています。
変更されたファイルは以下の通りです。
src/pkg/math/abs_386.ssrc/pkg/math/abs_amd64.ssrc/pkg/math/abs_arm.ssrc/pkg/math/asin_386.ssrc/pkg/math/asin_amd64.ssrc/pkg/math/asin_arm.ssrc/pkg/math/atan2_386.ssrc/pkg/math/atan2_amd64.ssrc/pkg/math/atan2_arm.ssrc/pkg/math/atan_386.ssrc/pkg/math/atan_amd64.ssrc/pkg/math/atan_arm.ssrc/pkg/math/dim_386.ssrc/pkg/math/dim_amd64.ssrc/pkg/math/dim_arm.ssrc/pkg/math/exp2_386.ssrc/pkg/math/exp2_amd64.ssrc/pkg/math/exp2_arm.ssrc/pkg/math/exp_386.ssrc/pkg/math/exp_amd64.ssrc/pkg/math/exp_arm.ssrc/pkg/math/expm1_386.ssrc/pkg/math/expm1_amd64.ssrc/pkg/math/expm1_arm.ssrc/pkg/math/floor_386.ssrc/pkg/math/floor_amd64.ssrc/pkg/math/floor_arm.ssrc/pkg/math/fltasm_amd64.ssrc/pkg/math/frexp_386.ssrc/pkg/math/frexp_amd64.ssrc/pkg/math/frexp_arm.ssrc/pkg/math/hypot_386.ssrc/pkg/math/hypot_amd64.ssrc/pkg/math/hypot_arm.ssrc/pkg/math/ldexp_386.ssrc/pkg/math/ldexp_amd64.ssrc/pkg/math/ldexp_arm.ssrc/pkg/math/log10_386.ssrc/pkg/math/log10_amd64.ssrc/pkg/math/log10_arm.ssrc/pkg/math/log1p_386.ssrc/pkg/math/log1p_amd64.ssrc/pkg/math/log1p_arm.ssrc/pkg/math/log_386.ssrc/pkg/math/log_amd64.ssrc/pkg/math/log_arm.ssrc/pkg/math/mod_386.ssrc/pkg/math/mod_amd64.ssrc/pkg/math/mod_arm.ssrc/pkg/math/modf_386.ssrc/pkg/math/modf_amd64.ssrc/pkg/math/modf_arm.ssrc/pkg/math/remainder_386.ssrc/pkg/math/remainder_amd64.ssrc/pkg/math/remainder_arm.ssrc/pkg/math/sin_386.ssrc/pkg/math/sin_amd64.ssrc/pkg/math/sin_arm.ssrc/pkg/math/sincos_386.ssrc/pkg/math/sincos_amd64.ssrc/pkg/math/sincos_arm.ssrc/pkg/math/sqrt_386.ssrc/pkg/math/sqrt_amd64.ssrc/pkg/math/sqrt_arm.ssrc/pkg/math/tan_386.ssrc/pkg/math/tan_amd64.ssrc/pkg/math/tan_arm.s
コミット
commit 1f7966346e4de8bda27699900e190d7925c1650c
Author: Keith Randall <khr@golang.org>
Date: Mon Aug 12 10:25:18 2013 -0700
math: convert textflags from numbers to symbols
R=golang-dev, bradfitz
CC=golang-dev
https://golang.org/cl/12773044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1f7966346e4de8bda27699900e190d7925c1650c
元コミット内容
math: convert textflags from numbers to symbols
変更の背景
このコミットの背景には、Go言語のアセンブリコードにおける可読性と保守性の向上が挙げられます。Goのアセンブリ言語(Plan 9アセンブラの派生)では、関数の定義にTEXTディレクティブを使用します。このTEXTディレクティブには、関数の特性を示すためのフラグ(textflags)を指定できます。
以前は、これらのフラグが数値(ビットマスク)として直接記述されていました。例えば、多くの関数で7という数値が使われていました。しかし、数値はそれが具体的に何を意味するのか直感的に理解しにくく、textflag.hのような定義ファイルを参照しないと、その意図が不明瞭でした。
このコミットでは、数値で表現されていたtextflagsを、より意味が明確なシンボル(例: NOSPLIT)に置き換えることで、コードの意図をより明確にし、将来的な変更やデバッグを容易にすることを目的としています。特に、NOSPLITフラグはGoのランタイムにおけるスタック管理と密接に関連しており、その意味を明示することは重要です。
前提知識の解説
Goアセンブリ(Plan 9アセンブラ)
Go言語は、パフォーマンスが要求される一部のコード(特にランタイムや標準ライブラリの低レベルな部分)でアセンブリ言語を使用します。Goのアセンブリ言語は、一般的なx86/x64アセンブリとは異なり、Plan 9オペレーティングシステムのアセンブラをベースにしています。主な特徴としては、レジスタの命名規則や擬似命令(例: TEXT, MOVLなど)が挙げられます。
TEXTディレクティブ
TEXTディレクティブは、Goアセンブリにおいて関数の開始を宣言するために使用されます。その基本的な構文は以下の通りです。
TEXT ·FunctionName(SB), textflags, $framesize
·FunctionName(SB): 定義する関数の名前です。·はパッケージ内のローカルシンボルであることを示し、SBは静的ベースポインタ(Static Base Pointer)で、グローバルシンボルや関数への参照に使われます。textflags: 関数の特性を示すビットマスクです。このコミットの主要な変更点です。$framesize: 関数のスタックフレームサイズを示します。
textflags
textflagsは、Goのリンカやランタイムに対して、その関数が持つべき特定の振る舞いを指示するためのビットマスクです。これらのフラグは、src/cmd/ld/textflag.h(またはGoのバージョンによってはsrc/runtime/textflag.h)に定義されています。
このコミットで特に重要なのは、NOSPLITフラグです。
NOSPLITフラグ
NOSPLITは、textflagsの一つで、値は4です。このフラグが設定された関数は、Goランタイムによるスタックの自動拡張(stack split)を行わないことを意味します。
Goのgoroutineは、非常に小さなスタック(通常は数KB)で開始し、必要に応じて自動的にスタックを拡張します。このスタック拡張は、関数のプロローグ(開始部分)に挿入される「スタックチェック」コードによって行われます。関数が呼び出される際に、現在のスタックが十分なサイズを持っているかを確認し、足りなければ新しい、より大きなスタックを割り当てて、古いスタックの内容をコピーします。
NOSPLITフラグが設定された関数は、このスタックチェックをスキップします。これは通常、以下のような場合に用いられます。
- 非常に短い関数: スタックをほとんど消費せず、スタックオーバーフローの心配がない関数。スタックチェックのオーバーヘッドを避けるため。
- 低レベルのランタイム関数: ガベージコレクションやスタック管理自体に関わる関数など、スタックチェックが不適切または危険な場合。これらの関数は、スタックチェックが無限ループを引き起こしたり、ランタイムの整合性を損なう可能性があるため、
NOSPLITが必須となります。 - スタックフレームが固定されている関数: 呼び出し元がスタックサイズを保証できる場合。
NOSPLITを使用する場合、その関数自身およびその関数が呼び出す全ての関数が、利用可能なスタック空間内に収まることが保証されなければなりません。
数値 7 の意味
コミット前のコードで頻繁に見られた数値7は、textflag.hの定義に基づくと、以下のフラグの組み合わせでした。
NOSPLIT(4)RODATA(2)DUPLDATA(1)
したがって、TEXT ·FunctionName(SB),7,$0は、NOSPLIT | RODATA | DUPLDATAというフラグの組み合わせを意味していました。このコミットでは、この数値表現をシンボルに置き換えることで、コードの意図をより明確にしています。
技術的詳細
このコミットの技術的な核心は、GoアセンブリコードのTEXTディレクティブにおけるtextflagsの表現方法の変更です。
変更前: 数値リテラル
変更前は、TEXTディレクティブのtextflags引数に、フラグの組み合わせを直接数値リテラルで指定していました。例えば、多くのmathパッケージのアセンブリ関数では、7という数値が使われていました。これは、NOSPLIT(4)、RODATA(2)、DUPLDATA(1)のビットマスクの合計です。
この数値リテラルによる指定は、以下のような問題点がありました。
- 可読性の低さ:
7という数値だけでは、それがどのような特性を持つ関数なのか、コードを読んだだけでは理解できません。textflag.hを参照しない限り、その意味は不明瞭です。 - 保守性の問題: 将来的に新しいフラグが追加されたり、既存のフラグの値が変更されたりした場合、数値リテラルを直接変更する必要があり、エラーのリスクが高まります。また、どのフラグが設定されているのかを把握するために、ビット演算の知識が必要になります。
- 意図の不明瞭さ: なぜその数値が選ばれたのか、その関数にどのような特性が求められているのかが、コードから読み取れません。
変更後: シンボリック定数とインクルード
このコミットでは、これらの問題を解決するために、以下の2つの変更が行われました。
-
#include "../../cmd/ld/textflag.h"の追加: 各アセンブリファイルの冒頭に、textflag.hヘッダファイルをインクルードするディレクティブが追加されました。このヘッダファイルには、NOSPLITなどのtextflagsに対応するシンボリック定数が定義されています。これにより、アセンブリコード内でこれらのシンボルを直接使用できるようになります。 -
数値リテラルからシンボリック定数への置き換え:
TEXTディレクティブのtextflags引数が、数値リテラル(例:7)から、インクルードされたヘッダファイルで定義されているシンボリック定数(例:NOSPLIT)に置き換えられました。例えば、
TEXT ·Abs(SB),7,$0はTEXT ·Abs(SB),NOSPLIT,$0に変更されました。ここで注目すべきは、元の
7がNOSPLITに置き換えられている点です。これは、元の7がNOSPLIT | RODATA | DUPLDATAの組み合わせであったにもかかわらず、コミットメッセージが「convert textflags from numbers to symbols」とだけ述べていることです。これは、RODATAとDUPLDATAが、Goのリンカやアセンブラの内部的な挙動に関連するフラグであり、通常のアセンブリプログラマが直接意識する必要がない、あるいはNOSPLITが設定されていれば暗黙的に適用されるような性質のフラグであった可能性を示唆しています。あるいは、この時点でのGoのツールチェインの進化により、これらのフラグが不要になったか、デフォルトで適用されるようになった可能性も考えられます。コミットの意図としては、最も重要な意味を持つNOSPLITを明示することが主眼であったと推測されます。
変更のメリット
この変更により、以下のようなメリットがもたらされます。
- 可読性の向上:
NOSPLITというシンボルを見るだけで、その関数がスタック拡張を行わないことが明確に理解できます。 - 保守性の向上:
textflag.hで定義されたシンボルを使用することで、将来的なフラグの変更や追加があっても、アセンブリコード自体を大幅に修正する必要がなくなります。 - 意図の明確化: コードの意図がより明確になり、開発者が関数の振る舞いを容易に把握できるようになります。
- エラーの削減: 数値のビットマスクを手動で計算する際に発生しうるヒューマンエラーが減少します。
この変更は、Go言語の低レベルな部分におけるコード品質と開発者体験を向上させるための、典型的なリファクタリングの一例と言えます。
コアとなるコードの変更箇所
このコミットでは、Goのmathパッケージ内の全てのアセンブリファイルにおいて、TEXTディレクティブのtextflagsの指定方法が変更されています。
具体的な変更は以下の2点です。
-
ファイルの先頭に、
textflag.hをインクルードする行が追加されました。--- a/src/pkg/math/abs_386.s +++ b/src/pkg/math/abs_386.s @@ -2,8 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include "../../cmd/ld/textflag.h" + // func Abs(x float64) float64 -TEXT ·Abs(SB),7,$0 +TEXT ·Abs(SB),NOSPLIT,$0 FMOVD x+0(FP), F0 // F0=x FABS // F0=|x| FMOVDP F0, ret+8(FP) -
TEXTディレクティブのtextflags部分が、数値リテラル(例:7)からシンボリック定数(例:NOSPLIT)に置き換えられました。--- a/src/pkg/math/abs_386.s +++ b/src/pkg/math/abs_386.s @@ -2,8 +2,10 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file.\n +#include "../../cmd/ld/textflag.h" + // func Abs(x float64) float64 -TEXT ·Abs(SB),7,$0 +TEXT ·Abs(SB),NOSPLIT,$0 FMOVD x+0(FP), F0 // F0=x FABS // F0=|x| FMOVDP F0, ret+8(FP)
このパターンが、コミットに含まれる全ての.sファイルに適用されています。
コアとなるコードの解説
上記の変更は、Goアセンブリコードの可読性と保守性を大幅に向上させます。
#include "../../cmd/ld/textflag.h"
この行はC言語のプリプロセッサディレクティブと同様に機能し、指定されたパスにあるtextflag.hファイルの内容を、このアセンブリファイルに含めます。textflag.hには、NOSPLITなどのシンボリック定数が定義されており、これによりアセンブリコード内でこれらのシンボルを数値の代わりに直接使用できるようになります。
例えば、src/cmd/ld/textflag.h(当時のパス)には、以下のような定義が含まれていました。
// textflag.h
#define NOSPLIT 4
// ...その他のフラグ定義
このインクルードにより、アセンブラはNOSPLITというシンボルを4という数値として解釈できるようになります。
TEXT ·Abs(SB),7,$0 から TEXT ·Abs(SB),NOSPLIT,$0 へ
この変更は、TEXTディレクティブのtextflags引数を、数値リテラルからシンボリック定数に置き換えるものです。
-
変更前:
TEXT ·Abs(SB),7,$07という数値は、NOSPLIT(4)、RODATA(2)、DUPLDATA(1)のビットマスクの合計です。この数値だけでは、関数がどのような特性を持つのか、コードを読んだだけでは理解できません。
-
変更後:
TEXT ·Abs(SB),NOSPLIT,$0NOSPLITというシンボルを使用することで、この関数がスタック拡張を行わないことが明確に示されます。これにより、コードの意図が直感的に理解できるようになります。- 元の
7に含まれていたRODATAやDUPLDATAといったフラグが明示的に記述されていないのは、これらのフラグがNOSPLITが設定されていれば暗黙的に適用されるか、あるいはこの時点でのGoのツールチェインの進化により、これらのフラグが不要になったためと考えられます。コミットの主な目的は、最も重要な意味を持つNOSPLITを明確にすることにありました。
この変更は、Goの低レベルなアセンブリコードのメンテナンス性を高め、開発者がコードの振る舞いをより容易に理解できるようにするための重要な改善です。
関連リンク
- Go CL (Code Review) 12773044: https://golang.org/cl/12773044
参考にした情報源リンク
- Go assembly TEXT directive: https://sgmansfield.com/go-assembly-text-directive/
- Go assembly TEXT directive and NOSPLIT: https://symbolcrash.com/2020/09/07/go-assembly-text-directive-and-nosplit/
- Go assembly TEXT directive and NOSPLIT (GitHub Gist): https://gist.github.com/mcyoung/6509896
- Go assembly TEXT directive (GitBook): https://go-internals.gitbook.io/go-internals/go-assembly/text-directive
- Go source code
src/runtime/textflag.h: https://go.dev/src/runtime/textflag.h - Go source code
src/cmd/asm/internal/asm/asm.go(forTEXTdirective parsing): https://github.com/golang/go/blob/master/src/cmd/asm/internal/asm/asm.go