[インデックス 18197] ファイルの概要
このコミットは、Go言語のランタイムおよび関連ツール(特にデバッグやプロファイリングツール)で使用されるUreg
(User Registers)構造体の命名規則と利用方法を改善するものです。これまでは、異なるCPUアーキテクチャ(AMD64, ARM, x86)に対応するUreg
構造体を扱う際に、C言語のプリプロセッサディレクティブである#define
と#undef
を一時的に使用して、ジェネリックなUreg
という名前を各アーキテクチャ固有の構造体にエイリアスしていました。このコミットは、この間接的な方法を廃止し、各アーキテクチャ固有のUreg
構造体に明示的な名前(例: UregAmd64
, UregArm
, Ureg386
)を直接与えることで、コードの明確性、保守性、および一貫性を向上させています。
コミット
commit 06c0280440689be695fb3375105e09f58b44e82e
Author: Russ Cox <rsc@golang.org>
Date: Wed Jan 8 20:37:27 2014 -0500
libmach: use different names for different Ureg types
Everything was doing this already with #defines.
Do it right.
R=golang-codereviews, jsing, 0intro, iant
CC=golang-codereviews
https://golang.org/cl/49090043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/06c0280440689be695fb3375105e09f58b44e82e
元コミット内容
libmach: use different names for different Ureg types
Everything was doing this already with #defines.
Do it right.
変更の背景
Go言語のデバッグツールやプロファイリングツールは、実行中のプログラムのレジスタ状態を読み取る必要があります。このレジスタ状態は、CPUアーキテクチャによってその構造が異なります。例えば、x86アーキテクチャとAMD64アーキテクチャでは、レジスタの数や種類、サイズが異なります。
これまでの実装では、各アーキテクチャ固有のレジスタ構造体(例: struct Ureg_amd64
, struct Ureg_x86
)を定義しつつも、それらを使用するC言語のソースファイル内で、一時的に#define Ureg Ureg_amd64
のようにマクロを定義し、その後に共通のUreg
という名前でコードを記述し、最後に#undef Ureg
でマクロ定義を解除するというパターンが広く使われていました。
この方法は、コードの重複を避けるために用いられる一般的なC言語のイディオムの一つですが、以下のような問題を引き起こす可能性がありました。
- 可読性の低下:
Ureg
という名前が文脈によって異なる構造体を指すため、コードを読む際にどのアーキテクチャのUreg
が使われているのかを常に意識する必要があり、理解が難しくなる。 - デバッグの困難さ: マクロの展開によって実際の型が隠蔽されるため、デバッガでの型情報の追跡が複雑になる場合がある。
- 潜在的な名前衝突:
#define
はグローバルな置換を行うため、意図しない場所でUreg
という名前が置換され、予期せぬバグを引き起こすリスクがある。 - IDE/エディタのサポート: 多くのIDEやコードエディタは、マクロによる動的な型変更を正確に解析するのが難しく、コード補完やリファクタリングの機能が十分に機能しない場合がある。
このコミットは、このような#define
による間接的なアプローチを「正しく行う」ことを目的としています。つまり、各アーキテクチャ固有のUreg
構造体に最初から明確でユニークな名前を付与し、その名前を直接使用することで、上記の問題を解消し、コードベース全体の品質と保守性を向上させることを目指しています。
前提知識の解説
このコミットを理解するためには、以下のC言語の概念とGo言語のデバッグ/プロファイリングに関する基本的な知識が必要です。
struct
(構造体): C言語における、異なるデータ型の変数を一つにまとめるためのユーザー定義型です。ここでは、CPUのレジスタセットを表現するために使用されます。typedef
: 既存のデータ型に新しい名前(エイリアス)を付けるために使用されます。このコミットでは、struct UregAmd64
のような構造体型にUregAmd64
というより簡潔な名前を付けるために使用されています。 例:typedef struct MyStruct MyStruct;
はstruct MyStruct
をMyStruct
と書けるようにします。#define
と#undef
(プリプロセッサディレクティブ):#define
: マクロを定義します。定義されたマクロは、コンパイル前にその値に置換されます。このコミットの変更前は、#define Ureg Ureg_amd64
のように、Ureg
という名前を一時的にUreg_amd64
に置き換えるために使われていました。#undef
: 定義済みのマクロを解除します。これにより、そのマクロはそれ以降置換されなくなります。
offsetof
マクロ: C言語の標準ライブラリで提供されるマクロで、構造体のメンバーが構造体の先頭から何バイト離れているか(オフセット)を計算します。デバッグツールが特定のレジスタの値にアクセスする際に、このオフセット情報が利用されます。 例:offsetof(struct UregAmd64, ip)
は、UregAmd64
構造体内のip
メンバーのオフセットを返します。Ureg
構造体: "User Registers" の略で、CPUのレジスタの状態を保持する構造体です。デバッガやプロファイラが、プログラムの実行中に特定の時点でのCPUの状態(プログラムカウンタ、スタックポインタ、汎用レジスタなどの値)を読み取るために不可欠です。アーキテクチャごとにレジスタセットが異なるため、Ureg
構造体もアーキテクチャ固有の定義を持ちます。libmach
: Go言語のツールチェインの一部で、デバッグやプロファイリングに関連する低レベルな機械語操作やプロセス制御を行うライブラリです。このライブラリは、異なるCPUアーキテクチャやOSのレジスタ構造を抽象化して扱います。src/cmd/prof
: Go言語のプロファイリングツールであるprof
コマンドのソースコードです。このツールは、プログラムの実行プロファイルを収集し、パフォーマンスのボトルネックを特定するのに役立ちます。レジスタ情報(特にプログラムカウンタ)は、プロファイリングにおいて実行中の命令アドレスを特定するために使用されます。
技術的詳細
このコミットの技術的な変更は、主に以下の3つの側面から構成されます。
-
Ureg
構造体の明示的な命名とtypedef
の導入:include/ureg_amd64.h
,include/ureg_arm.h
,include/ureg_x86.h
といった各アーキテクチャ固有のヘッダファイルにおいて、これまで単にstruct Ureg
として定義されていた構造体が、それぞれstruct UregAmd64
,struct UregArm
,struct Ureg386
というように、アーキテクチャ名を接尾辞として含む明確な名前に変更されました。- さらに、これらの構造体に対して
typedef
が導入され、typedef struct UregAmd64 UregAmd64;
のように、構造体タグと同じ名前で型エイリアスが作成されました。これにより、struct UregAmd64
と書く代わりに、より簡潔にUregAmd64
と記述できるようになります。
変更前:
// include/ureg_amd64.h struct Ureg { u64int ax; // ... };
変更後:
// include/ureg_amd64.h typedef struct UregAmd64 UregAmd64; struct UregAmd64 { u64int ax; // ... };
-
#define Ureg ... #undef Ureg
パターンの廃止:src/cmd/prof/main.c
,src/libmach/8db.c
,src/libmach/darwin.c
,src/libmach/linux.c
などのファイルで、これまでアーキテクチャ固有のUreg
構造体を使用するために行われていた#define Ureg Ureg_xxx
と#undef Ureg
のペアが削除されました。- これにより、これらのファイルでは、各アーキテクチャ固有の
Ureg
構造体をその明示的な名前(例:UregAmd64
,Ureg386
)で直接参照するようになります。
変更前 (例: src/cmd/prof/main.c):
#define Ureg Ureg_amd64 #include <ureg_amd64.h> #undef Ureg #define Ureg Ureg_x86 #include <ureg_x86.h> #undef Ureg
変更後 (例: src/cmd/prof/main.c):
#include <ureg_amd64.h> #include <ureg_x86.h>
-
コード内の
Ureg
参照の更新:src/cmd/prof/main.c
、src/libmach/5.c
、src/libmach/6.c
、src/libmach/8.c
、src/libmach/darwin.c
、src/libmach/linux.c
など、Ureg
構造体を参照しているすべての箇所で、古いUreg
またはUreg_xxx
形式の参照が、新しい明示的な名前(例:UregAmd64
,UregArm
,Ureg386
)に更新されました。- これには、
struct Ureg_amd64
のような直接的な型参照だけでなく、offsetof
マクロやsizeof
演算子内で使用される型名も含まれます。また、REGOFF
やREGSIZE
といったカスタムマクロの定義も、新しい型名を使用するように変更されています。
変更前 (例: src/libmach/5.c):
#define REGOFF(x) (uintptr) (&((struct Ureg *) 0)->x) #define REGSIZE sizeof(struct Ureg)
変更後 (例: src/libmach/5.c):
#define REGOFF(x) (uintptr) (&((struct UregArm *) 0)->x) #define REGSIZE sizeof(struct UregArm)
これらの変更により、コードベース全体でUreg
構造体の型が明確になり、プリプロセッサによる複雑な型エイリアシングが不要になりました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイル群に集中しています。
-
include/ureg_amd64.h
-
include/ureg_arm.h
-
include/ureg_x86.h
- これらのヘッダファイルで、
struct Ureg
の定義が、それぞれstruct UregAmd64
,struct UregArm
,struct Ureg386
というアーキテクチャ固有の名前に変更され、対応するtypedef
が追加されました。
- これらのヘッダファイルで、
-
src/cmd/prof/main.c
#define Ureg ... #undef Ureg
のパターンが削除され、UregAmd64
やUreg386
といった明示的な型名が直接使用されるようになりました。
-
src/libmach/5.c
-
src/libmach/6.c
-
src/libmach/8.c
- これらのファイルで定義されている
REGOFF
やREGSIZE
マクロが、新しいアーキテクチャ固有のUreg
型名を使用するように更新されました。
- これらのファイルで定義されている
-
src/libmach/8db.c
-
src/libmach/darwin.c
-
src/libmach/linux.c
- これらのファイルから、
#define Ureg ... #undef Ureg
のパターンと、重複するtypedef
定義が削除され、コード内のUreg
参照が新しい明示的な型名に置き換えられました。
- これらのファイルから、
コアとなるコードの解説
変更の核心は、Ureg
構造体の命名と利用方法のパラダイムシフトにあります。
ヘッダファイル (include/ureg_*.h
) の変更:
以前は、各アーキテクチャのレジスタ構造体がすべてstruct Ureg
というジェネリックな名前で定義されていました。これは、C言語のインクルードガードによって、一度に一つのureg_*.h
ファイルしかインクルードされないことを前提とした設計でした。
// 変更前 (例: ureg_amd64.h)
struct Ureg {
u64int ax;
// ...
};
このコミットでは、各ヘッダファイル内で構造体自体にアーキテクチャ固有の名前を付け、さらにその名前でtypedef
を定義しています。
// 変更後 (例: ureg_amd64.h)
typedef struct UregAmd64 UregAmd64; // typedefでエイリアスを作成
struct UregAmd64 { // 構造体自体に明確な名前を付与
u64int ax;
// ...
};
この変更により、UregAmd64
という型名は、ureg_amd64.h
がインクルードされているかどうかに関わらず、常にAMD64アーキテクチャのレジスタ構造体を指すことが保証されます。これにより、異なるアーキテクチャのUreg
構造体が同時にメモリ上に存在する場合でも、名前の衝突や曖昧さがなくなります。
ソースファイル (src/cmd/prof/main.c
, src/libmach/*.c
) の変更:
ヘッダファイルの変更に伴い、これらのソースファイルでのUreg
構造体の参照方法も変更されました。
#define
の削除:
最も顕著な変更は、#define Ureg ... #undef Ureg
というパターンが完全に削除されたことです。このパターンは、一時的にUreg
という名前を特定のアーキテクチャの構造体にマッピングするために使用されていました。
// 変更前 (例: src/cmd/prof/main.c)
#define Ureg Ureg_amd64
#include <ureg_amd64.h>
#undef Ureg
// ...
struct Ureg_amd64 ureg_amd64; // Ureg_amd64 は ureg_amd64.h で定義されている
このパターンが削除されたことで、コードはより直接的で読みやすくなりました。
// 変更後 (例: src/cmd/prof/main.c)
#include <ureg_amd64.h> // UregAmd64 が直接利用可能になる
#include <ureg_x86.h> // Ureg386 が直接利用可能になる
// ...
struct UregAmd64 ureg_amd64; // UregAmd64 は ureg_amd64.h で typedef されている
offsetof
とsizeof
の利用箇所の更新:
offsetof
マクロやsizeof
演算子を使用している箇所も、新しい型名に更新されました。これは、これらの演算子が正しい構造体サイズやメンバーオフセットを計算するために、正確な型情報が必要だからです。
// 変更前 (例: src/libmach/6.c)
#define REGOFF(x) offsetof(struct Ureg, x)
#define REGSIZE sizeof(struct Ureg)
// 変更後 (例: src/libmach/6.c)
#define REGOFF(x) offsetof(struct UregAmd64, x)
#define REGSIZE sizeof(struct UregAmd64)
これらの変更は、Go言語のデバッグおよびプロファイリングインフラストラクチャの基盤となるCコードの堅牢性と保守性を大幅に向上させます。型が明確になることで、コンパイラによる型チェックがより厳密になり、開発者が意図しない型変換や誤ったレジスタアクセスを行うリスクが低減されます。
関連リンク
- Go言語のプロファイリングツール (
go tool pprof
): https://go.dev/blog/pprof - Go言語のデバッグ (
go tool objdump
,gdb
,delve
): https://go.dev/doc/gdb - Go言語のソースコードリポジトリ: https://github.com/golang/go
- Gerrit Code Review (Goプロジェクトのコードレビューシステム): https://go-review.googlesource.com/
参考にした情報源リンク
- Go言語のコミット履歴 (Gerrit): https://go-review.googlesource.com/q/project:go+status:merged
- C言語の
struct
とtypedef
: https://www.geeksforgeeks.org/structures-c/ - C言語のプリプロセッサ (
#define
,#undef
): https://www.geeksforgeeks.org/c-preprocessors/ - C言語の
offsetof
マクロ: https://en.cppreference.com/w/c/language/offsetof - Go言語の
libmach
に関する情報 (Goのソースコード内のコメントやドキュメント): Goのソースコードリポジトリ内でsrc/libmach
ディレクトリを参照。