[インデックス 10752] ファイルの概要
このコミットは、Go言語のmath
パッケージにおけるビルドプロセスの正規化と、アセンブリコードの統合方法の改善を目的としています。特に、godoc
ツールとの連携を改善し、ビルドシステムがより自動化されたツール(goinstall
や新しいgo tool
)で扱いやすくなるように、Goの標準ライブラリにおけるアセンブリ実装の慣習を変更しています。これにより、Goファイル数を削減し、宣言と実装の重複を避けることが可能になりました。
コミット
- コミットハッシュ:
dd8dc6f0595ffc2c4951c0ce8ff6b63228effd97
- Author: Russ Cox rsc@golang.org
- Date: Tue Dec 13 15:20:12 2011 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dd8dc6f0595ffc2c4951c0ce8ff6b63228effd97
元コミット内容
math: regularize build
This will be nicer to the automatic tools.
It requires a few more assembly stubs
but fewer Go files.
There are a few instances where it looks like
there are new blobs of code, but they are just
being copied out of deleted files.
There is no new code here.
Suppose you have a portable implementation for Sin
and a 386-specific assembly one. The old way to
do this was to write three files
sin_decl.go
func Sin(x float64) float64 // declaration only
sin_386.s
assembly implementation
sin_port.go
func Sin(x float64) float64 { ... } // pure-Go impl
and then link in either sin_decl.go+sin_386.s or
just sin_port.go. The Makefile actually did the magic
of linking in only the _port.go files for those without
assembly and only the _decl.go files for those with
assembly, or at least some of that magic.
The biggest problem with this, beyond being hard
to explain to the build system, is that once you do
explain it to the build system, godoc knows which
of sin_port.go or sin_decl.go are involved on a given
architecture, and it (correctly) ignores the other.
That means you have to put identical doc comments
in both files.
The new approach, which is more like what we did
in the later packages math/big and sync/atomic,
is to have
sin.go
func Sin(x float64) float64 // decl only
func sin(x float64) float64 {...} // pure-Go impl
sin_386.s
// assembly for Sin (ignores sin)
sin_amd64.s
// assembly for Sin: jmp sin
sin_arm.s
// assembly for Sin: jmp sin
Once we abandon Makefiles we can put all the assembly
stubs in one source file, so the number of files will
actually go down.
Chris asked whether the branches cost anything.
Given that they are branching to pure-Go implementations
that are not typically known for their speed, the single
direct branch is not going to be noticeable. That is,
it's on the slow path.
An alternative would have been to preserve the old
"only write assembly files when there's an implementation"
and still have just one copy of the declaration of Sin
(and thus one doc comment) by doing:
sin.go
func Sin(x float64) float64 { return sin(x) }
sin_decl.go
func sin(x float64) float64 // declaration only
sin_386.s
// assembly for sin
sin_port.go
func sin(x float64) float64 { portable code }
In this version everyone would link in sin.go and
then either sin_decl.go+sin_386.s or sin_port.go.
This has an extra function call on all paths, including
the "fast path" to get to assembly, and it triples the
number of Go files involved compared to what I did
in this CL. On the other hand you don't have to
write assembly stubs. After starting down this path
I decided that the assembly stubs were the easier
approach.
As for generating the assembly stubs on the fly, much
of the goal here is to eliminate magic from the build
process, so that zero-configuration tools like goinstall
or the new go tool can handle this package.
R=golang-dev, r, cw, iant, r
CC=golang-dev
https://golang.org/cl/5488057
変更の背景
このコミットの主な背景は、Go言語のビルドシステムとドキュメンテーションツール(godoc
)の効率化と簡素化です。以前のGoのmath
パッケージでは、特定の数学関数のアセンブリ実装とポータブルなGo実装を切り替えるために、複雑なファイル構成とMakefileのロジックが使用されていました。
具体的には、以下の問題点がありました。
- ビルドシステムの複雑性: アセンブリ実装の有無に応じて
_decl.go
と_port.go
ファイルを切り替える必要があり、Makefileがその「魔法」を管理していました。これはビルドシステムにとって理解しにくく、自動化ツール(goinstall
や後のgo tool
)での取り扱いを困難にしていました。 godoc
の課題:godoc
は特定のアーキテクチャに特化したファイル(例:sin_386.s
)を認識し、それ以外のファイルを無視するため、sin_decl.go
とsin_port.go
の両方に同じドキュメントコメントを記述する必要がありました。これはドキュメントの重複と管理の手間を招いていました。- ファイル数の増加: 各関数に対して
_decl.go
、_port.go
、そして複数のアーキテクチャごとのアセンブリファイルが存在することで、ファイル数が不必要に増加していました。
このコミットは、これらの問題を解決し、よりクリーンで保守しやすいコードベースを目指しています。特に、goinstall
や新しいgo tool
のようなゼロコンフィギュレーションツールがmath
パッケージを容易に扱えるように、ビルドプロセスから「魔法」を排除することが重要な目標でした。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドシステムとアセンブリ統合に関する知識が必要です。
- Goのパッケージ構造とビルド: Goのソースコードはパッケージとして組織され、各パッケージはディレクトリに対応します。Goのビルドツール(
go build
など)は、これらのパッケージをコンパイルし、実行可能ファイルやライブラリを生成します。Goのビルドシステムは、特定のファイル名サフィックス(例:_amd64.go
、_linux.go
)やビルドタグ(例:// +build linux,amd64
)を使用して、特定のOSやアーキテクチャ向けのコードを条件付きでコンパイルする仕組みを持っています。 - Goにおけるアセンブリ言語の利用: Goは、パフォーマンスが重要な部分や、特定のハードウェア機能にアクセスする必要がある場合に、アセンブリ言語(Goアセンブリ)で関数を実装することをサポートしています。Goのアセンブリファイルは通常
.s
拡張子を持ち、Goの関数とリンクされます。 goinstall
とgo tool
:goinstall
は、Go 1.0以前に存在したGoパッケージのインストールツールです。これは、Goのソースコードリポジトリからパッケージをフェッチし、ビルドしてインストールする機能を提供しました。go tool
は、Go 1.0以降に導入された統一されたコマンドラインツール群です。go build
、go run
、go test
、go fmt
など、Go開発に必要な様々なサブコマンドを提供します。このコミットが作成された2011年時点では、go tool
への移行が進められており、ビルドプロセスの簡素化が強く求められていました。
godoc
:godoc
はGoのソースコードからドキュメンテーションを生成し、表示するツールです。Goの関数や型のコメントを解析し、HTML形式で表示したり、コマンドラインで参照したりできます。godoc
は、特定のビルド環境(OSやアーキテクチャ)で実際にコンパイルされるコードに基づいてドキュメントを生成するため、条件付きコンパイルされたファイル(例:_decl.go
や_port.go
)の扱いが問題となることがありました。- 関数宣言と実装の分離(旧来の慣習): 以前のGoでは、アセンブリ実装を持つ関数について、Goコードで関数シグネチャのみを宣言する
_decl.go
ファイルと、純粋なGoで実装された_port.go
ファイル(ポータブル版)の2つを用意し、Makefileでどちらか一方をビルド時に選択するという慣習がありました。これにより、アセンブリ実装がない環境でもGo実装が利用できました。
技術的詳細
このコミットで導入された新しいアプローチは、Goのmath
パッケージにおけるアセンブリ実装の管理方法を大幅に簡素化します。
旧アプローチ(変更前):
sin_decl.go
:func Sin(x float64) float64
のように、関数の宣言のみを含むGoファイル。アセンブリ実装が存在する場合にビルドされる。sin_386.s
:Sin
関数の386アーキテクチャ向けアセンブリ実装。sin_port.go
:func Sin(x float64) float64 { ... }
のように、純粋なGoで実装されたポータブル版のGoファイル。アセンブリ実装が存在しない場合にビルドされる。
この方式では、Makefileが_decl.go
と_port.go
のどちらをリンクするかを決定する複雑なロジックを持っていました。また、godoc
が特定のアーキテクチャのファイルのみを認識するため、_decl.go
と_port.go
の両方に同じドキュメントコメントを記述する必要がありました。
新アプローチ(変更後):
-
単一のGoファイル (
sin.go
):- 公開APIとしての関数宣言:
func Sin(x float64) float64
- 純粋なGoによる内部実装:
func sin(x float64) float64 { ... }
(小文字で始まるため、パッケージ外からは直接呼び出せない) このsin.go
ファイルは、すべてのビルド環境で常にコンパイルされます。
- 公開APIとしての関数宣言:
-
アセンブリスタブファイル (
sin_386.s
,sin_amd64.s
,sin_arm.s
など):- 各アーキテクチャ向けに、公開API関数(例:
Sin
)から内部の純粋Go実装関数(例:sin
)へジャンプするアセンブリスタブが用意されます。 - 例:
TEXT ·Sin(SB),7,$0\n\tJMP ·sin(SB)
(amd64の場合) またはTEXT ·Sin(SB),7,$0\n\tB ·sin(SB)
(ARMの場合) - これらのアセンブリファイルは、対応するアーキテクチャでのみビルドされます。
- 各アーキテクチャ向けに、公開API関数(例:
この新しいアプローチの利点は以下の通りです。
- ビルドの簡素化: Makefileの複雑な条件付きリンクロジックが不要になります。Goのビルドツールは、アーキテクチャ固有のアセンブリファイルを自動的に選択し、それ以外を無視します。
godoc
の改善: 公開APIの宣言とドキュメントコメントはsin.go
ファイルに一箇所にまとめられるため、ドキュメントの重複が解消されます。godoc
はsin.go
を読み込み、適切なドキュメントを表示できます。- ファイル数の削減:
_decl.go
や_port.go
といった補助的なGoファイルが不要になり、全体的なファイル数が削減されます。 - パフォーマンスへの影響: 公開APIから内部実装へのジャンプは、アセンブリ実装が存在しない「遅いパス」でのみ発生するため、パフォーマンスへの影響は無視できるレベルであると判断されています。アセンブリ実装が存在する場合は、アセンブリコードが直接実行されるため、このジャンプは発生しません。
この変更は、math/big
やsync/atomic
といった後のパッケージで採用された慣習に合わせたものであり、Goの標準ライブラリ全体で一貫したアセンブリ統合パターンを確立する一環です。
コアとなるコードの変更箇所
このコミットでは、src/pkg/math/
ディレクトリ内の多数のファイルが変更されています。主な変更パターンは以下の通りです。
-
Makefile
の変更:- アーキテクチャ固有の
OFILES_
変数が削除され、OFILES
が$(GOARCH)
サフィックスを持つオブジェクトファイルを直接参照するように変更されました。 ALLGOFILES
から_port.go
ファイルが削除され、NOGOFILES
とGOFILES
の計算ロジックが簡素化されました。
- アーキテクチャ固有の
-
_decl.go
ファイルの削除:src/pkg/math/asin_decl.go
,src/pkg/math/dim_decl.go
,src/pkg/math/expm1_decl.go
,src/pkg/math/floor_decl.go
,src/pkg/math/frexp_decl.go
,src/pkg/math/hypot_decl.go
,src/pkg/math/ldexp_decl.go
,src/pkg/math/log10_decl.go
,src/pkg/math/log1p_decl.go
,src/pkg/math/mod_decl.go
,src/pkg/math/modf_decl.go
,src/pkg/math/remainder_decl.go
,src/pkg/math/sin_decl.go
,src/pkg/math/sincos_decl.go
,src/pkg/math/sqrt_decl.go
など、多くの_decl.go
ファイルが削除されました。
-
_port.go
ファイルの削除:src/pkg/math/exp_port.go
,src/pkg/math/hypot_port.go
,src/pkg/math/sqrt_port.go
など、純粋なGo実装を含む_port.go
ファイルが削除されました。
-
既存のGoファイルの変更 (
.go
ファイル):src/pkg/math/abs.go
,src/pkg/math/asin.go
,src/pkg/math/atan.go
,src/pkg/math/atan2.go
,src/pkg/math/dim.go
,src/pkg/math/exp.go
,src/pkg/math/expm1.go
,src/pkg/math/floor.go
,src/pkg/math/frexp.go
,src/pkg/math/hypot.go
,src/pkg/math/ldexp.go
,src/pkg/math/log.go
,src/pkg/math/log10.go
,src/pkg/math/log1p.go
,src/pkg/math/mod.go
,src/pkg/math/modf.go
,src/pkg/math/remainder.go
,src/pkg/math/sin.go
,src/pkg/math/sincos.go
,src/pkg/math/sqrt.go
など、多くのGoファイルが変更されました。- これらのファイルでは、公開API関数(例:
func Abs(x float64) float64
)が宣言のみとなり、実際のGo実装は小文字で始まる内部関数(例:func abs(x float64) float64 { ... }
)として定義されました。
-
新しいアセンブリスタブファイルの追加/リネーム (
.s
ファイル):src/pkg/math/abs_arm.s
(旧log_decl.go
からリネーム),src/pkg/math/asin_amd64.s
,src/pkg/math/asin_arm.s
,src/pkg/math/atan_amd64.s
,src/pkg/math/atan_arm.s
,src/pkg/math/atan2_amd64.s
(旧exp_decl.go
からリネーム),src/pkg/math/atan2_arm.s
(旧tan_decl.go
からリネーム),src/pkg/math/dim_386.s
,src/pkg/math/dim_arm.s
,src/pkg/math/exp2_amd64.s
,src/pkg/math/exp2_arm.s
,src/pkg/math/exp_arm.s
,src/pkg/math/expm1_amd64.s
,src/pkg/math/expm1_arm.s
,src/pkg/math/floor_amd64.s
,src/pkg/math/floor_arm.s
,src/pkg/math/frexp_amd64.s
,src/pkg/math/frexp_arm.s
,src/pkg/math/hypot_arm.s
(旧atan_decl.go
からリネーム),src/pkg/math/ldexp_amd64.s
,src/pkg/math/ldexp_arm.s
,src/pkg/math/log_arm.s
,src/pkg/math/log10_amd64.s
,src/pkg/math/log10_arm.s
,src/pkg/math/log1p_amd64.s
,src/pkg/math/log1p_arm.s
,src/pkg/math/mod_amd64.s
,src/pkg/math/mod_arm.s
,src/pkg/math/modf_amd64.s
,src/pkg/math/modf_arm.s
,src/pkg/math/remainder_amd64.s
(旧atan2_decl.go
からリネーム),src/pkg/math/remainder_arm.s
,src/pkg/math/sin_amd64.s
(旧exp2_decl.go
からリネーム),src/pkg/math/sin_arm.s
(旧abs_decl.go
からリネーム),src/pkg/math/sincos_arm.s
,src/pkg/runtime/arm/softfloat.c
など、多数のアセンブリファイルが追加またはリネームされました。これらのファイルには、公開関数から内部Go実装へのジャンプ命令が含まれています。
-
テストファイルの変更:
src/pkg/math/all_test.go
に、新しいHypotSqrtGo
やHypotNoSqrtGo
といったテスト関数が追加され、ベンチマーク関数も更新されました。src/pkg/math/exp_test.go
,src/pkg/math/hypot_test.go
,src/pkg/math/sqrt_test.go
など、一部のテストファイルが削除されました。src/pkg/math/export_test.go
が新規作成され、内部関数をテストからアクセスできるようにエクスポートしています。
これらの変更は、Goのmath
パッケージ全体にわたる大規模なリファクタリングであり、ビルドシステムとドキュメンテーションの整合性を高めるための重要なステップでした。
コアとなるコードの解説
このコミットの核心は、Goの関数とアセンブリ実装の連携方法の変更にあります。
変更前(旧来の慣習の例: Sin
関数):
sin_decl.go
:
package math
func Sin(x float64) float64 // 宣言のみ
sin_386.s
:
// Sin関数の386アセンブリ実装
sin_port.go
:
package math
func Sin(x float64) float64 { /* 純粋なGo実装 */ }
この方式では、Sin
関数の宣言がsin_decl.go
とsin_port.go
の両方に存在し、ビルド時にMakefileがどちらか一方を選択してリンクしていました。アセンブリ実装が存在するアーキテクチャではsin_decl.go
とsin_386.s
が、それ以外ではsin_port.go
が使われました。
変更後(新しい慣習の例: Sin
関数):
sin.go
:
package math
// Sin returns the sine of x.
// Special cases are:
// Sin(±0) = ±0
// Sin(±Inf) = NaN
// Sin(NaN) = NaN
func Sin(x float64) float64 // 公開APIの宣言のみ
func sin(x float64) float64 {
// ここに純粋なGoによるSin関数の実装
// 例:
// const (
// PI4A = 7.85398125648498535156E-1
// ...
// )
// ...
return 0.0 // 簡略化
}
sin_amd64.s
(amd64アーキテクチャの場合):
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT ·Sin(SB),7,$0
JMP ·sin(SB) // 公開関数Sinから内部実装sinへジャンプ
sin_arm.s
(ARMアーキテクチャの場合):
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT ·Sin(SB),7,$0
B ·sin(SB) // 公開関数Sinから内部実装sinへブランチ
この新しいアプローチでは、Sin
という公開関数はsin.go
内で宣言のみが行われ、実際のGoによる実装はsin
という小文字で始まる(つまり、パッケージ外からは直接呼び出せない)内部関数として同じファイル内に記述されます。
アセンブリ実装が存在するアーキテクチャ(例: amd64, ARM)では、sin_amd64.s
やsin_arm.s
のようなアセンブリファイルがビルドされます。これらのアセンブリファイルは、公開関数Sin
が呼び出された際に、内部のGo実装関数sin
へ直接ジャンプ(またはブランチ)するスタブとして機能します。これにより、アセンブリ実装が優先され、Go実装が呼び出されます。
もし特定のアーキテクチャ向けのアセンブリスタブが存在しない場合、Goのビルドシステムは自動的にsin.go
内のSin
関数の宣言と、その内部実装であるsin
関数をリンクします。この場合、Sin
関数は直接sin
関数を呼び出す形になります。
この変更により、以下のメリットが生まれます。
- 単一のGoファイル:
Sin
関数の宣言とGo実装がsin.go
という単一のファイルに集約されます。これにより、コードの可読性と保守性が向上し、godoc
がドキュメントを生成する際にも一貫性が保たれます。 - 明確な役割分担: 公開API (
Sin
) と内部実装 (sin
) の役割が明確に分離されます。アセンブリコードは、公開APIが内部実装を呼び出すための「フック」として機能します。 - ビルドの自動化: Goのビルドツールは、アーキテクチャ固有のアセンブリファイルを自動的に検出し、適切なリンクを行います。Makefileのような手動のビルドロジックが不要になり、
go tool
のような自動化ツールとの相性が良くなります。
このパターンは、math/big
やsync/atomic
といった他のGo標準ライブラリパッケージでも採用されており、Go言語全体でのアセンブリ統合の標準的な慣習となりました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goアセンブリのドキュメント: https://go.dev/doc/asm
- Goのビルドコマンドに関するドキュメント: https://go.dev/cmd/go/
godoc
に関するドキュメント: https://pkg.go.dev/cmd/godoc
参考にした情報源リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/dd8dc6f0595ffc2c4951c0ce8ff6b63228effd97
- Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/5488057
- Go言語のビルドシステムに関する一般的な情報 (Go 1.0リリースノートなど): https://go.dev/doc/go1
- Go言語の歴史と進化に関する情報 (特にビルドツール周り): https://go.dev/blog/go1 (Go 1のリリースブログ記事)
- Goの
math
パッケージのソースコード (現在の実装): https://github.com/golang/go/tree/master/src/math
[インデックス 10752] ファイルの概要
このコミットは、Go言語のmath
パッケージにおけるビルドプロセスの正規化と、アセンブリコードの統合方法の改善を目的としています。特に、godoc
ツールとの連携を改善し、ビルドシステムがより自動化されたツール(goinstall
や新しいgo tool
)で扱いやすくなるように、Goの標準ライブラリにおけるアセンブリ実装の慣習を変更しています。これにより、Goファイル数を削減し、宣言と実装の重複を避けることが可能になりました。
コミット
- コミットハッシュ:
dd8dc6f0595ffc2c4951c0ce8ff6b63228effd97
- Author: Russ Cox rsc@golang.org
- Date: Tue Dec 13 15:20:12 2011 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/dd8dc6f0595ffc2c4951c0ce8ff6b63228effd97
元コミット内容
math: regularize build
This will be nicer to the automatic tools.
It requires a few more assembly stubs
but fewer Go files.
There are a few instances where it looks like
there are new blobs of code, but they are just
being copied out of deleted files.
There is no new code here.
Suppose you have a portable implementation for Sin
and a 386-specific assembly one. The old way to
do this was to write three files
sin_decl.go
func Sin(x float64) float64 // declaration only
sin_386.s
assembly implementation
sin_port.go
func Sin(x float64) float64 { ... } // pure-Go impl
and then link in either sin_decl.go+sin_386.s or
just sin_port.go. The Makefile actually did the magic
of linking in only the _port.go files for those without
assembly and only the _decl.go files for those with
assembly, or at least some of that magic.
The biggest problem with this, beyond being hard
to explain to the build system, is that once you do
explain it to the build system, godoc knows which
of sin_port.go or sin_decl.go are involved on a given
architecture, and it (correctly) ignores the other.
That means you have to put identical doc comments
in both files.
The new approach, which is more like what we did
in the later packages math/big and sync/atomic,
is to have
sin.go
func Sin(x float64) float64 // decl only
func sin(x float64) float64 {...} // pure-Go impl
sin_386.s
// assembly for Sin (ignores sin)
sin_amd64.s
// assembly for Sin: jmp sin
sin_arm.s
// assembly for Sin: jmp sin
Once we abandon Makefiles we can put all the assembly
stubs in one source file, so the number of files will
actually go down.
Chris asked whether the branches cost anything.
Given that they are branching to pure-Go implementations
that are not typically known for their speed, the single
direct branch is not going to be noticeable. That is,
it's on the slow path.
An alternative would have been to preserve the old
"only write assembly files when there's an implementation"
and still have just one copy of the declaration of Sin
(and thus one doc comment) by doing:
sin.go
func Sin(x float64) float64 { return sin(x) }
sin_decl.go
func sin(x float64) float64 // declaration only
sin_386.s
// assembly for sin
sin_port.go
func sin(x float64) float64 { portable code }
In this version everyone would link in sin.go and
then either sin_decl.go+sin_386.s or sin_port.go.
This has an extra function call on all paths, including
the "fast path" to get to assembly, and it triples the
number of Go files involved compared to what I did
in this CL. On the other hand you don't have to
write assembly stubs. After starting down this path
I decided that the assembly stubs were the easier
approach.
As for generating the assembly stubs on the fly, much
of the goal here is to eliminate magic from the build
process, so that zero-configuration tools like goinstall
or the new go tool can handle this package.
R=golang-dev, r, cw, iant, r
CC=golang-dev
https://golang.org/cl/5488057
変更の背景
このコミットの主な背景は、Go言語のビルドシステムとドキュメンテーションツール(godoc
)の効率化と簡素化です。以前のGoのmath
パッケージでは、特定の数学関数のアセンブリ実装とポータブルなGo実装を切り替えるために、複雑なファイル構成とMakefileのロジックが使用されていました。
具体的には、以下の問題点がありました。
- ビルドシステムの複雑性: アセンブリ実装の有無に応じて
_decl.go
と_port.go
ファイルを切り替える必要があり、Makefileがその「魔法」を管理していました。これはビルドシステムにとって理解しにくく、自動化ツール(goinstall
や後のgo tool
)での取り扱いを困難にしていました。 godoc
の課題:godoc
は特定のアーキテクチャに特化したファイル(例:sin_386.s
)を認識し、それ以外のファイルを無視するため、sin_decl.go
とsin_port.go
の両方に同じドキュメントコメントを記述する必要がありました。これはドキュメントの重複と管理の手間を招いていました。- ファイル数の増加: 各関数に対して
_decl.go
、_port.go
、そして複数のアーキテクチャごとのアセンブリファイルが存在することで、ファイル数が不必要に増加していました。
このコミットは、これらの問題を解決し、よりクリーンで保守しやすいコードベースを目指しています。特に、goinstall
や新しいgo tool
のようなゼロコンフィギュレーションツールがmath
パッケージを容易に扱えるように、ビルドプロセスから「魔法」を排除することが重要な目標でした。
前提知識の解説
このコミットを理解するためには、以下のGo言語のビルドシステムとアセンブリ統合に関する知識が必要です。
- Goのパッケージ構造とビルド: Goのソースコードはパッケージとして組織され、各パッケージはディレクトリに対応します。Goのビルドツール(
go build
など)は、これらのパッケージをコンパイルし、実行可能ファイルやライブラリを生成します。Goのビルドシステムは、特定のファイル名サフィックス(例:_amd64.go
、_linux.go
)やビルドタグ(例:// +build linux,amd64
)を使用して、特定のOSやアーキテクチャ向けのコードを条件付きでコンパイルする仕組みを持っています。 - Goにおけるアセンブリ言語の利用: Goは、パフォーマンスが重要な部分や、特定のハードウェア機能にアクセスする必要がある場合に、アセンブリ言語(Goアセンブリ)で関数を実装することをサポートしています。Goのアセンブリファイルは通常
.s
拡張子を持ち、Goの関数とリンクされます。 goinstall
とgo tool
:goinstall
は、Go 1.0以前に存在したGoパッケージのインストールツールです。これは、Goのソースコードリポジトリからパッケージをフェッチし、ビルドしてインストールする機能を提供しました。go tool
は、Go 1.0以降に導入された統一されたコマンドラインツール群です。go build
、go run
、go test
、go fmt
など、Go開発に必要な様々なサブコマンドを提供します。このコミットが作成された2011年時点では、go tool
への移行が進められており、ビルドプロセスの簡素化が強く求められていました。
godoc
:godoc
はGoのソースコードからドキュメンテーションを生成し、表示するツールです。Goの関数や型のコメントを解析し、HTML形式で表示したり、コマンドラインで参照したりできます。godoc
は、特定のビルド環境(OSやアーキテクチャ)で実際にコンパイルされるコードに基づいてドキュメントを生成するため、条件付きコンパイルされたファイル(例:_decl.go
や_port.go
)の扱いが問題となることがありました。- 関数宣言と実装の分離(旧来の慣習): 以前のGoでは、アセンブリ実装を持つ関数について、Goコードで関数シグネチャのみを宣言する
_decl.go
ファイルと、純粋なGoで実装された_port.go
ファイル(ポータブル版)の2つを用意し、Makefileでどちらか一方をビルド時に選択するという慣習がありました。これにより、アセンブリ実装がない環境でもGo実装が利用できました。
技術的詳細
このコミットで導入された新しいアプローチは、Goのmath
パッケージにおけるアセンブリ実装の管理方法を大幅に簡素化します。
旧アプローチ(変更前):
sin_decl.go
:func Sin(x float64) float64
のように、関数の宣言のみを含むGoファイル。アセンブリ実装が存在する場合にビルドされる。sin_386.s
:Sin
関数の386アーキテクチャ向けアセンブリ実装。sin_port.go
:func Sin(x float64) float64 { ... }
のように、純粋なGoで実装されたポータブル版のGoファイル。アセンブリ実装が存在しない場合にビルドされる。
この方式では、Makefileが_decl.go
と_port.go
のどちらをリンクするかを決定する複雑なロジックを持っていました。また、godoc
が特定のアーキテクチャのファイルのみを認識するため、_decl.go
と_port.go
の両方に同じドキュメントコメントを記述する必要がありました。
新アプローチ(変更後):
-
単一のGoファイル (
sin.go
):- 公開APIとしての関数宣言:
func Sin(x float64) float64
- 純粋なGoによる内部実装:
func sin(x float64) float64 { ... }
(小文字で始まるため、パッケージ外からは直接呼び出せない) このsin.go
ファイルは、すべてのビルド環境で常にコンパイルされます。
- 公開APIとしての関数宣言:
-
アセンブリスタブファイル (
sin_386.s
,sin_amd64.s
,sin_arm.s
など):- 各アーキテクチャ向けに、公開API関数(例:
Sin
)から内部の純粋Go実装関数(例:sin
)へジャンプするアセンブリスタブが用意されます。 - 例:
TEXT ·Sin(SB),7,$0\n\tJMP ·sin(SB)
(amd64の場合) またはTEXT ·Sin(SB),7,$0\n\tB ·sin(SB)
(ARMの場合) - これらのアセンブリファイルは、対応するアーキテクチャでのみビルドされます。
- 各アーキテクチャ向けに、公開API関数(例:
この新しいアプローチの利点は以下の通りです。
- ビルドの簡素化: Makefileの複雑な条件付きリンクロジックが不要になります。Goのビルドツールは、アーキテクチャ固有のアセンブリファイルを自動的に選択し、それ以外を無視します。
godoc
の改善: 公開APIの宣言とドキュメントコメントはsin.go
ファイルに一箇所にまとめられるため、ドキュメントの重複が解消されます。godoc
はsin.go
を読み込み、適切なドキュメントを表示できます。- ファイル数の削減:
_decl.go
や_port.go
といった補助的なGoファイルが不要になり、全体的なファイル数が削減されます。 - パフォーマンスへの影響: 公開APIから内部実装へのジャンプは、アセンブリ実装が存在しない「遅いパス」でのみ発生するため、パフォーマンスへの影響は無視できるレベルであると判断されています。アセンブリ実装が存在する場合は、アセンブリコードが直接実行されるため、このジャンプは発生しません。
このパターンは、math/big
やsync/atomic
といった後のパッケージで採用された慣習に合わせたものであり、Goの標準ライブラリ全体で一貫したアセンブリ統合パターンを確立する一環です。
コアとなるコードの変更箇所
このコミットでは、src/pkg/math/
ディレクトリ内の多数のファイルが変更されています。主な変更パターンは以下の通りです。
-
Makefile
の変更:- アーキテクチャ固有の
OFILES_
変数が削除され、OFILES
が$(GOARCH)
サフィックスを持つオブジェクトファイルを直接参照するように変更されました。 ALLGOFILES
から_port.go
ファイルが削除され、NOGOFILES
とGOFILES
の計算ロジックが簡素化されました。
- アーキテクチャ固有の
-
_decl.go
ファイルの削除:src/pkg/math/asin_decl.go
,src/pkg/math/dim_decl.go
,src/pkg/math/expm1_decl.go
,src/pkg/math/floor_decl.go
,src/pkg/math/frexp_decl.go
,src/pkg/math/hypot_decl.go
,src/pkg/math/ldexp_decl.go
,src/pkg/math/log10_decl.go
,src/pkg/math/log1p_decl.go
,src/pkg/math/mod_decl.go
,src/pkg/math/modf_decl.go
,src/pkg/math/remainder_decl.go
,src/pkg/math/sin_decl.go
,src/pkg/math/sincos_decl.go
,src/pkg/math/sqrt_decl.go
など、多くの_decl.go
ファイルが削除されました。
-
_port.go
ファイルの削除:src/pkg/math/exp_port.go
,src/pkg/math/hypot_port.go
,src/pkg/math/sqrt_port.go
など、純粋なGo実装を含む_port.go
ファイルが削除されました。
-
既存のGoファイルの変更 (
.go
ファイル):src/pkg/math/abs.go
,src/pkg/math/asin.go
,src/pkg/math/atan.go
,src/pkg/math/atan2.go
,src/pkg/math/dim.go
,src/pkg/math/exp.go
,src/pkg/math/expm1.go
,src/pkg/math/floor.go
,src/pkg/math/frexp.go
,src/pkg/math/hypot.go
,src/pkg/math/ldexp.go
,src/pkg/math/log.go
,src/pkg/math/log10.go
,src/pkg/math/log1p.go
,src/pkg/math/mod.go
,src/pkg/math/modf.go
,src/pkg/math/remainder.go
,src/pkg/math/sin.go
,src/pkg/math/sincos.go
,src/pkg/math/sqrt.go
など、多くのGoファイルが変更されました。- これらのファイルでは、公開API関数(例:
func Abs(x float64) float64
)が宣言のみとなり、実際のGo実装は小文字で始まる内部関数(例:func abs(x float64) float64 { ... }
)として定義されました。
-
新しいアセンブリスタブファイルの追加/リネーム (
.s
ファイル):src/pkg/math/abs_arm.s
(旧log_decl.go
からリネーム),src/pkg/math/asin_amd64.s
,src/pkg/math/asin_arm.s
,src/pkg/math/atan_amd64.s
,src/pkg/math/atan_arm.s
,src/pkg/math/atan2_amd64.s
(旧exp_decl.go
からリネーム),src/pkg/math/atan2_arm.s
(旧tan_decl.go
からリネーム),src/pkg/math/dim_386.s
,src/pkg/math/dim_arm.s
,src/pkg/math/exp2_amd64.s
,src/pkg/math/exp2_arm.s
,src/pkg/math/exp_arm.s
,src/pkg/math/expm1_amd64.s
,src/pkg/math/expm1_arm.s
,src/pkg/math/floor_amd64.s
,src/pkg/math/floor_arm.s
,src/pkg/math/frexp_amd64.s
,src/pkg/math/frexp_arm.s
,src/pkg/math/hypot_arm.s
(旧atan_decl.go
からリネーム),src/pkg/math/ldexp_amd64.s
,src/pkg/math/ldexp_arm.s
,src/pkg/math/log_arm.s
,src/pkg/math/log10_amd64.s
,src/pkg/math/log10_arm.s
,src/pkg/math/log1p_amd64.s
,src/pkg/math/log1p_arm.s
,src/pkg/math/mod_amd64.s
,src/pkg/math/mod_arm.s
,src/pkg/math/modf_amd64.s
,src/pkg/math/modf_arm.s
,src/pkg/math/remainder_amd64.s
(旧atan2_decl.go
からリネーム),src/pkg/math/remainder_arm.s
,src/pkg/math/sin_amd64.s
(旧exp2_decl.go
からリネーム),src/pkg/math/sin_arm.s
(旧abs_decl.go
からリネーム),src/pkg/math/sincos_arm.s
,src/pkg/runtime/arm/softfloat.c
など、多数のアセンブリファイルが追加またはリネームされました。これらのファイルには、公開関数から内部Go実装へのジャンプ命令が含まれています。
-
テストファイルの変更:
src/pkg/math/all_test.go
に、新しいHypotSqrtGo
やHypotNoSqrtGo
といったテスト関数が追加され、ベンチマーク関数も更新されました。src/pkg/math/exp_test.go
,src/pkg/math/hypot_test.go
,src/pkg/math/sqrt_test.go
など、一部のテストファイルが削除されました。src/pkg/math/export_test.go
が新規作成され、内部関数をテストからアクセスできるようにエクスポートしています。
これらの変更は、Goのmath
パッケージ全体にわたる大規模なリファクタリングであり、ビルドシステムとドキュメンテーションの整合性を高めるための重要なステップでした。
コアとなるコードの解説
このコミットの核心は、Goの関数とアセンブリ実装の連携方法の変更にあります。
変更前(旧来の慣習の例: Sin
関数):
sin_decl.go
:
package math
func Sin(x float64) float64 // 宣言のみ
sin_386.s
:
// Sin関数の386アセンブリ実装
sin_port.go
:
package math
func Sin(x float64) float64 { /* 純粋なGo実装 */ }
この方式では、Sin
関数の宣言がsin_decl.go
とsin_port.go
の両方に存在し、ビルド時にMakefileがどちらか一方を選択してリンクしていました。アセンブリ実装が存在するアーキテクチャではsin_decl.go
とsin_386.s
が、それ以外ではsin_port.go
が使われました。
変更後(新しい慣習の例: Sin
関数):
sin.go
:
package math
// Sin returns the sine of x.
// Special cases are:
// Sin(±0) = ±0
// Sin(±Inf) = NaN
// Sin(NaN) = NaN
func Sin(x float64) float64 // 公開APIの宣言のみ
func sin(x float64) float64 {
// ここに純粋なGoによるSin関数の実装
// 例:
// const (
// PI4A = 7.85398125648498535156E-1
// ...
// )
// ...
return 0.0 // 簡略化
}
sin_amd64.s
(amd64アーキテクチャの場合):
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT ·Sin(SB),7,$0
JMP ·sin(SB) // 公開関数Sinから内部実装sinへジャンプ
sin_arm.s
(ARMアーキテクチャの場合):
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
TEXT ·Sin(SB),7,$0
B ·sin(SB) // 公開関数Sinから内部実装sinへブランチ
この新しいアプローチでは、Sin
という公開関数はsin.go
内で宣言のみが行われ、実際のGoによる実装はsin
という小文字で始まる(つまり、パッケージ外からは直接呼び出せない)内部関数として同じファイル内に記述されます。
アセンブリ実装が存在するアーキテクチャ(例: amd64, ARM)では、sin_amd64.s
やsin_arm.s
のようなアセンブリファイルがビルドされます。これらのアセンブリファイルは、公開関数Sin
が呼び出された際に、内部のGo実装関数sin
へ直接ジャンプ(またはブランチ)するスタブとして機能します。これにより、アセンブリ実装が優先され、Go実装が呼び出されます。
もし特定のアーキテクチャ向けのアセンブリスタブが存在しない場合、Goのビルドシステムは自動的にsin.go
内のSin
関数の宣言と、その内部実装であるsin
関数をリンクします。この場合、Sin
関数は直接sin
関数を呼び出す形になります。
この変更により、以下のメリットが生まれます。
- 単一のGoファイル:
Sin
関数の宣言とGo実装がsin.go
という単一のファイルに集約されます。これにより、コードの可読性と保守性が向上し、godoc
がドキュメントを生成する際にも一貫性が保たれます。 - 明確な役割分担: 公開API (
Sin
) と内部実装 (sin
) の役割が明確に分離されます。アセンブリコードは、公開APIが内部実装を呼び出すための「フック」として機能します。 - ビルドの自動化: Goのビルドツールは、アーキテクチャ固有のアセンブリファイルを自動的に検出し、適切なリンクを行います。Makefileのような手動のビルドロジックが不要になり、
go tool
のような自動化ツールとの相性が良くなります。
このパターンは、math/big
やsync/atomic
といった他のGo標準ライブラリパッケージでも採用されており、Go言語全体でのアセンブリ統合の標準的な慣習となりました。
関連リンク
- Go言語の公式ドキュメント: https://golang.org/doc/
- Goアセンブリのドキュメント: https://go.dev/doc/asm
- Goのビルドコマンドに関するドキュメント: https://go.dev/cmd/go/
godoc
に関するドキュメント: https://pkg.go.dev/cmd/godoc
参考にした情報源リンク
- GitHub上のコミットページ: https://github.com/golang/go/commit/dd8dc6f0595ffc2c4951c0ce8ff6b63228effd97
- Goのコードレビューシステム (Gerrit) の変更リスト: https://golang.org/cl/5488057
- Go言語のビルドシステムに関する一般的な情報 (Go 1.0リリースノートなど): https://go.dev/doc/go1
- Go言語の歴史と進化に関する情報 (特にビルドツール周り): https://go.dev/blog/go1 (Go 1のリリースブログ記事)
- Goの
math
パッケージのソースコード (現在の実装): https://github.com/golang/go/tree/master/src/math