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

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

このコミットは、Goコンパイラツールチェーンにおけるcmd/cccmd/gccmd/ldといった各ツールが持つ出力フォーマットルーチンを、共通のliblinkライブラリに集約することを目的としています。これにより、コードの重複を排除し、保守性を向上させることが期待されます。

コミット

commit 2cae0591cd77cf307a661a9adc44cff2a56c64a9
Author: Anthony Martin <ality@pbrane.org>
Date:   Wed Feb 12 14:29:11 2014 -0500

    cmd/cc, cmd/gc, cmd/ld: consolidate print format routines
    
    We now use the %A, %D, %P, and %R routines from liblink
    across the board.
    
    Fixes #7178.
    Fixes #7055.
    
    LGTM=iant
    R=golang-codereviews, gobot, rsc, dave, iant, remyoudompheng
    CC=golang-codereviews
    https://golang.org/cl/49170043

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

https://github.com/golang/go/commit/2cae0591cd77cf307a661a9adc44cff2a56c64a9

元コミット内容

cmd/cc, cmd/gc, cmd/ld: print format routinesを統合

liblinkの%A, %D, %P, %Rルーチンを全体的に使用するようになりました。

Fixes #7178. Fixes #7055.

変更の背景

このコミットの主な背景は、Goコンパイラツールチェーン内のコードの重複を減らし、保守性を高めることです。以前は、cmd/cc (Cコンパイラ)、cmd/gc (Goコンパイラ)、cmd/ld (リンカ) といった異なるツールが、それぞれ独自の出力フォーマットルーチン(例えば、アセンブリ命令、アドレス、プログラム構造体、レジスタなどを文字列に変換する機能)を持っていました。これは、各ツールが独立して開発された歴史的経緯や、特定のアーキテクチャ(5g/5lはARM、6g/6lはAMD64、8g/8lはx86)に特化した実装を持っていたためと考えられます。

しかし、このような重複は、機能の追加やバグ修正が必要になった際に、複数の場所で同じ変更を行う必要があり、開発効率の低下やバグの混入リスクを高めます。このコミットは、これらの共通のフォーマットルーチンをliblinkという共有ライブラリに集約することで、一度の変更で全てのツールに反映されるようにし、コードベース全体の整合性と品質を向上させることを目指しています。

コミットメッセージにはFixes #7178Fixes #7055と記載されていますが、これらのIssueに関する詳細な情報は現在のところ見つかっていません。しかし、一般的にこのような変更は、特定の出力フォーマットの不整合や、新しいアーキテクチャサポートの際に既存のフォーマットルーチンを再利用したいといった要望から発生することが多いです。

前提知識の解説

このコミットを理解するためには、以下のGoコンパイラツールチェーンの構成要素とC言語のフォーマット機能に関する知識が必要です。

Goコンパイラツールチェーンの主要コンポーネント

  • cmd/gc (Go Compiler): Go言語のソースコードを中間表現(アセンブリに近い形式)にコンパイルするGo言語のコンパイラです。各アーキテクチャ(例: 5g for ARM, 6g for AMD64, 8g for x86)ごとに存在していました。
  • cmd/cc (C Compiler): Goのランタイムや一部の標準ライブラリはC言語で書かれているため、それらをコンパイルするためのCコンパイラです。
  • cmd/ld (Linker): コンパイルされたオブジェクトファイルやライブラリを結合し、実行可能なバイナリを生成するリンカです。各アーキテクチャ(例: 5l for ARM, 6l for AMD64, 8l for x86)ごとに存在していました。
  • liblink: Goツールチェーン内で共有される低レベルのライブラリで、主にリンカやコンパイラのバックエンドが共通して利用するユーティリティ関数やデータ構造を提供します。アセンブリコードの生成やシンボル解決など、プラットフォームに依存しない共通の処理が含まれます。

C言語のフォーマット機能 (fmtinstall, varargck)

Goコンパイラツールチェーンの多くの部分はC言語で書かれており、出力処理にはPlan 9 Cコンパイラ由来のfmtライブラリが使用されています。

  • fmtinstall(char verb, Fmtconv conv): これは、fmtライブラリの拡張機能です。printfのようなフォーマット関数で使用されるカスタムの「動詞」(%d, %sのようなもの)を登録するために使用されます。
    • verb: 登録するカスタム動詞の文字(例: 'A', 'D', 'P', 'R')。
    • conv: その動詞が指定されたときに呼び出される変換関数へのポインタ。この関数は、対応する引数を文字列に変換し、出力ストリームに書き込む責任を持ちます。
  • #pragma varargck type "verb" Type: これは、Plan 9 Cコンパイラ(Goツールチェーンのビルドにも使用される)のコンパイラディレクティブ(プラグマ)です。printfのような可変引数関数において、特定のカスタム動詞(verb)に対応する引数の型(Type)をコンパイラに教えるために使用されます。これにより、コンパイラは型チェックを行い、誤った型の引数が渡された場合に警告やエラーを出すことができます。
    • 例えば、#pragma varargck type "P" Prog* は、%Pという動詞がProg*型の引数を期待することを示します。

このコミットでは、各ツールが個別に持っていたPconv, Aconv, Dconv, Rconvといった変換関数をliblinkに移動し、各ツールからはliblinkの共通関数を呼び出すように変更しています。

技術的詳細

このコミットの核心は、Goコンパイラおよびリンカの内部で利用されるアセンブリコードやデータ構造のテキスト表現を生成するルーチンの一元化です。具体的には、以下の4つの主要なフォーマット動詞(およびそれに対応する変換関数)が対象となります。

  • %A (Assembly instruction): アセンブリ命令(例: MOV, ADD, CALLなど)を文字列に変換します。
  • %D (Address): メモリのアドレスやオペランドを文字列に変換します。これには、レジスタ、オフセット、シンボルなどが含まれます。
  • %P (Program instruction): Goコンパイラやリンカの内部表現であるProg構造体(単一のアセンブリ命令とそのオペランド、メタデータを含む)全体を整形して出力します。
  • %R (Register): レジスタ(例: R0, AX, SPなど)を文字列に変換します。

変更前は、これらの変換関数(例: Aconv, Dconv, Pconv, Rconv)が、src/cmd/5g/list.c, src/cmd/6g/list.c, src/cmd/8g/list.cといった各アーキテクチャのコンパイラや、リンカのsrc/cmd/5l/list.cなどに個別に存在していました。それぞれの実装は似ていますが、細かな差異や重複がありました。

このコミットでは、以下の手順で統合が行われました。

  1. liblinkへのルーチンの移動: 各cmd/*/list.cファイルから、共通のフォーマット変換関数(Pconv, Aconv, Dconv, Rconvなど)の実装が削除され、src/liblink/list5.c, src/liblink/list6.c, src/liblink/list8.cといったliblink内のファイルに集約されました。これらのファイルは、それぞれARM、AMD64、x86アーキテクチャに対応する共通のフォーマットルーチンを提供します。
  2. fmtinstallの変更: 各ツール(例: src/cmd/5a/lex.c, src/cmd/6a/lex.c, src/cmd/8a/lex.cなど)の初期化ルーチン(listinit関数内)から、個別のfmtinstall呼び出しが削除されました。代わりに、liblinkが提供する共通の初期化関数(例: listinit5(), listinit6(), listinit8())が呼び出されるようになりました。これらの共通初期化関数内で、liblinkに移動された変換関数がfmtinstallによって登録されます。
  3. #pragma varargckの調整: include/link.hに、%A, %D, %P, %Rなどの新しいvarargckプラグマが追加されました。これにより、liblinkの共通ルーチンが期待する引数の型が明示され、コンパイラによる型チェックが強化されます。同時に、各ツール固有のヘッダファイル(例: src/cmd/5c/gc.h, src/cmd/6c/gc.h, src/cmd/5l/l.hなど)から、重複するvarargckプラグマや、もはや不要になった変換関数のプロトタイプ宣言が削除されました。
  4. 内部表現の統一: 一部のファイルでは、Prog構造体のフィールド名がlocからpcに変更されています。これは、プログラムカウンタ(Program Counter)を意味するpcという名称に統一することで、より明確な意味を持たせるための変更です。

この変更により、GoツールチェーンのコードベースはよりDRY (Don't Repeat Yourself) になり、将来的なメンテナンスや機能拡張が容易になりました。例えば、新しいアーキテクチャのサポートを追加する際に、既存のフォーマットルーチンを再利用できるため、開発コストが削減されます。

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

このコミットでは、多数のファイルが変更されていますが、特に重要な変更箇所は以下の通りです。

  • include/link.h:
    • Prog構造体からuint32 loc;が削除され、代わりにpcが使用されるようになったことを示唆しています。
    • #pragma varargckディレクティブが追加され、%A, %D, %P, %Rなどのフォーマット動詞に対応する型が定義されています。これは、これらのフォーマットルーチンがliblinkに集約され、共通のインターフェースを持つようになったことを示しています。
  • include/plan9/link.h:
    • 多数の#pragma varargckディレクティブが削除されています。これは、これらの定義がinclude/link.hに移動され、一元化されたことを意味します。
  • src/cmd/5c/gc.h, src/cmd/6c/gc.h, src/cmd/8c/gc.h:
    • Var構造体の定義や、Pconv, Aconv, Dconvなどの個別の変換関数のプロトタイプ宣言が削除されています。これは、これらの機能がliblinkに移動されたため、各コンパイラのヘッダからは不要になったことを示します。
  • src/cmd/5c/list.c, src/cmd/6c/list.c, src/cmd/8c/list.c:
    • Bconvなどの変換関数の実装が削除され、listinit関数内でlistinit5()などのliblinkの初期化関数を呼び出すように変更されています。
  • src/cmd/5g/list.c, src/cmd/6g/list.c, src/cmd/8g/list.c:
    • これらのファイルは削除されています。これは、各アーキテクチャのGoコンパイラが独自に持っていた出力フォーマットルーチンが完全にliblinkに統合されたことを意味する最も顕著な変更です。
  • src/cmd/5l/l.h, src/cmd/6l/l.h, src/cmd/8l/l.h:
    • リンカのヘッダファイルからも、個別の#pragma varargckディレクティブや変換関数のプロトタイプ宣言が削除されています。
  • src/cmd/5l/list.c, src/cmd/6l/list.c, src/cmd/8l/list.c:
    • リンカのlist.cファイルからも、個別の変換関数の実装が削除され、liblinkの初期化関数を呼び出すように変更されています。
  • src/liblink/list5.c, src/liblink/list6.c, src/liblink/list8.c:
    • これらのファイルに、削除された各cmd/*/list.cファイルから移動されたPconv, Aconv, Dconv, Rconvなどの共通のフォーマット変換ルーチンが追加されています。

コアとなるコードの解説

このコミットのコアとなる変更は、Goツールチェーンのビルドシステムにおけるコードの再利用性と保守性の向上にあります。

例えば、src/cmd/5g/list.cのようなファイルが完全に削除されたことは、以前はARMアーキテクチャのGoコンパイラが独自にアセンブリ命令やアドレスの表示方法を定義していたことを意味します。同様に、他のアーキテクチャ(AMD64, x86)のコンパイラやリンカもそれぞれ独自の定義を持っていました。

これらの重複するコードは、src/liblink/list5.c, src/liblink/list6.c, src/liblink/list8.cといったliblink内のファイルに集約されました。これにより、例えば%Dフォーマット(アドレス表示)のロジックにバグが見つかった場合、以前は各アーキテクチャのコンパイラとリンカの合計6箇所以上を修正する必要があったかもしれませんが、変更後はliblink内の対応するファイル(例: list5.c)を修正するだけで、そのアーキテクチャの全てのツールに修正が適用されるようになります。

#pragma varargckの変更は、この統合されたフォーマットルーチンが期待する引数の型をコンパイラに正確に伝えるためのものです。これにより、開発者はフォーマット文字列と引数の型が一致しない場合にコンパイル時に警告を受け取ることができ、実行時エラーのリスクを減らすことができます。

また、Prog構造体のlocフィールドがpcに変更されたことは、コードの可読性と意味の明確化に貢献します。pc(プログラムカウンタ)という用語は、アセンブリや低レベルプログラミングにおいて命令のアドレスを指す標準的な用語であり、より直感的に理解できます。

全体として、このコミットはGoツールチェーンの内部構造をよりモジュール化し、共通の機能を共有ライブラリに集約することで、長期的な開発とメンテナンスを容易にするための重要なリファクタリングです。

関連リンク

参考にした情報源リンク