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

[インデックス 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言語のイディオムの一つですが、以下のような問題を引き起こす可能性がありました。

  1. 可読性の低下: Uregという名前が文脈によって異なる構造体を指すため、コードを読む際にどのアーキテクチャのUregが使われているのかを常に意識する必要があり、理解が難しくなる。
  2. デバッグの困難さ: マクロの展開によって実際の型が隠蔽されるため、デバッガでの型情報の追跡が複雑になる場合がある。
  3. 潜在的な名前衝突: #defineはグローバルな置換を行うため、意図しない場所でUregという名前が置換され、予期せぬバグを引き起こすリスクがある。
  4. IDE/エディタのサポート: 多くのIDEやコードエディタは、マクロによる動的な型変更を正確に解析するのが難しく、コード補完やリファクタリングの機能が十分に機能しない場合がある。

このコミットは、このような#defineによる間接的なアプローチを「正しく行う」ことを目的としています。つまり、各アーキテクチャ固有のUreg構造体に最初から明確でユニークな名前を付与し、その名前を直接使用することで、上記の問題を解消し、コードベース全体の品質と保守性を向上させることを目指しています。

前提知識の解説

このコミットを理解するためには、以下のC言語の概念とGo言語のデバッグ/プロファイリングに関する基本的な知識が必要です。

  1. struct (構造体): C言語における、異なるデータ型の変数を一つにまとめるためのユーザー定義型です。ここでは、CPUのレジスタセットを表現するために使用されます。
  2. typedef: 既存のデータ型に新しい名前(エイリアス)を付けるために使用されます。このコミットでは、struct UregAmd64のような構造体型にUregAmd64というより簡潔な名前を付けるために使用されています。 例: typedef struct MyStruct MyStruct;struct MyStructMyStruct と書けるようにします。
  3. #define#undef (プリプロセッサディレクティブ):
    • #define: マクロを定義します。定義されたマクロは、コンパイル前にその値に置換されます。このコミットの変更前は、#define Ureg Ureg_amd64のように、Uregという名前を一時的にUreg_amd64に置き換えるために使われていました。
    • #undef: 定義済みのマクロを解除します。これにより、そのマクロはそれ以降置換されなくなります。
  4. offsetof マクロ: C言語の標準ライブラリで提供されるマクロで、構造体のメンバーが構造体の先頭から何バイト離れているか(オフセット)を計算します。デバッグツールが特定のレジスタの値にアクセスする際に、このオフセット情報が利用されます。 例: offsetof(struct UregAmd64, ip) は、UregAmd64構造体内のipメンバーのオフセットを返します。
  5. Ureg 構造体: "User Registers" の略で、CPUのレジスタの状態を保持する構造体です。デバッガやプロファイラが、プログラムの実行中に特定の時点でのCPUの状態(プログラムカウンタ、スタックポインタ、汎用レジスタなどの値)を読み取るために不可欠です。アーキテクチャごとにレジスタセットが異なるため、Ureg構造体もアーキテクチャ固有の定義を持ちます。
  6. libmach: Go言語のツールチェインの一部で、デバッグやプロファイリングに関連する低レベルな機械語操作やプロセス制御を行うライブラリです。このライブラリは、異なるCPUアーキテクチャやOSのレジスタ構造を抽象化して扱います。
  7. src/cmd/prof: Go言語のプロファイリングツールであるprofコマンドのソースコードです。このツールは、プログラムの実行プロファイルを収集し、パフォーマンスのボトルネックを特定するのに役立ちます。レジスタ情報(特にプログラムカウンタ)は、プロファイリングにおいて実行中の命令アドレスを特定するために使用されます。

技術的詳細

このコミットの技術的な変更は、主に以下の3つの側面から構成されます。

  1. 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;
        // ...
    };
    
  2. #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>
    
  3. コード内のUreg参照の更新:

    • src/cmd/prof/main.csrc/libmach/5.csrc/libmach/6.csrc/libmach/8.csrc/libmach/darwin.csrc/libmach/linux.cなど、Ureg構造体を参照しているすべての箇所で、古いUregまたはUreg_xxx形式の参照が、新しい明示的な名前(例: UregAmd64, UregArm, Ureg386)に更新されました。
    • これには、struct Ureg_amd64のような直接的な型参照だけでなく、offsetofマクロやsizeof演算子内で使用される型名も含まれます。また、REGOFFREGSIZEといったカスタムマクロの定義も、新しい型名を使用するように変更されています。

    変更前 (例: 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構造体の型が明確になり、プリプロセッサによる複雑な型エイリアシングが不要になりました。

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

このコミットにおけるコアとなるコードの変更は、主に以下のファイル群に集中しています。

  1. include/ureg_amd64.h

  2. include/ureg_arm.h

  3. include/ureg_x86.h

    • これらのヘッダファイルで、struct Uregの定義が、それぞれstruct UregAmd64, struct UregArm, struct Ureg386というアーキテクチャ固有の名前に変更され、対応するtypedefが追加されました。
  4. src/cmd/prof/main.c

    • #define Ureg ... #undef Uregのパターンが削除され、UregAmd64Ureg386といった明示的な型名が直接使用されるようになりました。
  5. src/libmach/5.c

  6. src/libmach/6.c

  7. src/libmach/8.c

    • これらのファイルで定義されているREGOFFREGSIZEマクロが、新しいアーキテクチャ固有のUreg型名を使用するように更新されました。
  8. src/libmach/8db.c

  9. src/libmach/darwin.c

  10. 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 されている

offsetofsizeofの利用箇所の更新: 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コードの堅牢性と保守性を大幅に向上させます。型が明確になることで、コンパイラによる型チェックがより厳密になり、開発者が意図しない型変換や誤ったレジスタアクセスを行うリスクが低減されます。

関連リンク

参考にした情報源リンク