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

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

このコミットは、Go言語のランタイムライブラリの一部であるlib9におけるコンパイル時の問題を解決するためのものです。具体的には、__fixargv0という関数の宣言が使用箇所よりも後に行われていたため、Plan 9コンパイラが型情報を認識できず、リンク時に型シグネチャの問題を引き起こしていた点を修正しています。

この修正は、src/lib9/argv0.cファイルを削除し、その内容(argv0変数と__fixargv0関数)をsrc/lib9/flag.cに移動することで実現されています。これにより、__fixargv0関数がflag.c内で使用される前に適切に宣言されるようになり、コンパイラのエラーが解消されました。また、flag.c内のflagparse関数から__fixargv0()の明示的な呼び出しが削除されています。これは、関数が同じファイル内で宣言されたことで、コンパイラがその存在を認識し、不要になったためと考えられます。

コミット

commit b9f0a6bf6833f2f70caf6451133919a2807d0943
Author: Akshat Kumar <seed@mail.nanosouffle.net>
Date:   Tue Jan 22 17:23:36 2013 -0500

    lib9: declare __fixargv0 before use in flag.c
    
    The Plan 9 compilers complain about not
    having type information for the function,
    which sets off type signature problems
    during the linking stage.
    
    R=rsc, ality, iant
    CC=golang-dev
    https://golang.org/cl/7058054

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

https://github.com/golang/go/commit/b9f0a6bf6833f2f70caf6451133919a2807d0943

元コミット内容

lib9: declare __fixargv0 before use in flag.c

Plan 9コンパイラが、関数の型情報がないことについて不平を言い、 それがリンクステージで型シグネチャの問題を引き起こす。

変更の背景

このコミットの背景には、Go言語の初期のコンパイラツールチェインがPlan 9オペレーティングシステムの設計思想やツールに強く影響を受けていたという歴史があります。特に、GoのコンパイラはPlan 9のコンパイラ(8c, 6c, 5cなど)をベースにして開発されました。

問題は、__fixargv0という特定の関数がsrc/lib9/argv0.cで定義され、src/lib9/flag.c内で使用されていたにもかかわらず、flag.c内でその関数の前方宣言(プロトタイプ宣言)が行われていなかったことにありました。Plan 9コンパイラは、関数が使用される前にその型情報(引数の型や戻り値の型)が明確に宣言されていることを厳密に要求します。型情報がないまま関数が呼び出されると、コンパイラは警告を発し、さらにリンク時に型シグネチャの不一致によるエラー(type signature problems during the linking stage)を引き起こす可能性がありました。

この問題は、特にクロスコンパイル環境や異なるコンパイラバージョンで顕在化しやすく、ビルドの安定性を損なう要因となっていました。このコミットは、このようなコンパイル/リンク時の問題を解消し、ビルドプロセスをより堅牢にすることを目的としています。

前提知識の解説

Plan 9 from User Space (Plan 9 from Bell Labs)

Plan 9は、ベル研究所で開発された分散オペレーティングシステムです。Go言語の開発者の一部(Rob Pike, Ken Thompson, Russ Coxなど)はPlan 9の開発にも深く関わっており、Go言語の設計思想やツールチェインにはPlan 9の影響が色濃く見られます。特に、Goの初期のコンパイラはPlan 9のコンパイラ(8c for ARM, 6c for x86, 5c for MIPSなど)を基盤としていました。これらのコンパイラは、C言語の標準とは異なる独自の厳格な型チェックやリンケージルールを持つことがあり、それが本コミットで言及されている問題の原因となりました。

__fixargv0 関数と argv0 変数

  • argv0: C言語のプログラムでは、main関数の第一引数argv[0]は通常、実行可能ファイルの名前(パスを含む場合もある)を指します。Go言語のランタイムにおいても、このプログラム名を保持するためのグローバル変数argv0が存在します。これは、エラーメッセージの表示や、プログラム自身のパスを特定する際などに利用されます。
  • __fixargv0: コミットメッセージ内のコードコメントにもあるように、この関数は「Mac OS can't deal with files that only declare data. ARGBEGIN mentions this function so that this file gets pulled in.」と説明されています。これは、Mac OSのリンカが、データのみを宣言するファイル(この場合はargv0変数を宣言するargv0.c)を適切にリンクしない、あるいは最適化によって削除してしまう可能性があることへのワークアラウンドです。__fixargv0という空の関数を定義し、他のコードから参照されることで、リンカがこのファイル(およびargv0変数)を確実に含めるようにするためのハックです。

コンパイラとリンカの動作、型情報

  • コンパイラ: ソースコード(例: .cファイル)を機械語のオブジェクトファイル(例: .oファイル)に変換するプログラムです。コンパイル時には、関数の呼び出しが行われる際に、その関数のプロトタイプ宣言(引数の型、戻り値の型)が既知である必要があります。もし宣言がない場合、コンパイラは警告を発したり、デフォルトの型(C言語ではint)を仮定したりすることがあります。
  • リンカ: 複数のオブジェクトファイルやライブラリを結合して、最終的な実行可能ファイルを生成するプログラムです。リンカは、異なるオブジェクトファイル間で参照される関数や変数のアドレスを解決します。コンパイル時に型情報が不正確であったり、リンカが期待する型シグネチャと実際の定義が異なったりすると、「型シグネチャの問題」としてリンクエラーが発生します。これは、関数呼び出しの規約(引数の渡し方、スタックの管理など)が、呼び出し側と定義側で一致しない場合に特に問題となります。

技術的詳細

この問題の核心は、C言語のコンパイルとリンクのプロセスにおける「宣言と定義の分離」および「前方宣言の必要性」にあります。

  1. 元の構造:

    • src/lib9/argv0.c: char *argv0;void __fixargv0(void) { } を定義。
    • src/lib9/flag.c: flagparse関数内で __fixargv0(); を呼び出し。
    • flag.cには__fixargv0のプロトタイプ宣言がなかった。
  2. Plan 9コンパイラの厳格性: Plan 9コンパイラは、関数が呼び出される前にそのプロトタイプ宣言がスコープ内にあることを厳しく要求します。一般的なCコンパイラでは、プロトタイプ宣言がない場合でも、戻り値をint、引数を不定(可変引数)と仮定してコンパイルを進めることがありますが、Plan 9コンパイラはより厳格で、型情報がないこと自体を問題視しました。

  3. リンク時の問題: コンパイル時に型情報が不足していると、コンパイラは関数呼び出しのコードを生成する際に誤った仮定をする可能性があります。その結果、リンカがargv0.cから提供される__fixargv0の実際の定義と、flag.cから生成された呼び出しコードの型シグネチャが一致しないと判断し、リンクエラー(type signature problems during the linking stage)を引き起こしました。これは、関数呼び出し規約(calling convention)の不一致に起因することが多いです。

  4. 解決策: このコミットでは、__fixargv0の定義とargv0変数の宣言を、その関数が使用されるflag.cファイルに直接移動させました。これにより、__fixargv0flag.c内で使用される際には、すでにその定義(したがって型情報)がコンパイラに既知の状態となります。結果として、明示的な前方宣言が不要となり、Plan 9コンパイラの不満が解消され、リンク時の型シグネチャの問題も解決されました。 また、flag.c内のflagparse関数から__fixargv0()の明示的な呼び出しが削除されています。これは、__fixargv0の本来の目的が「データのみのファイルがリンカに削除されないようにする」ことであり、flag.cに移動されたことで、flag.c自体がデータとコードの両方を持つファイルになったため、この明示的な呼び出しが不要になったためと考えられます。リンカは、ファイル内にコードが存在すれば、そのファイルを削除することなくリンクします。

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

このコミットによる変更は以下の2つのファイルに集中しています。

  1. src/lib9/argv0.c:

    • このファイルは完全に削除されました。
    • 削除された内容:
      /*
      Plan 9 from User Space src/lib9/argv0.c
      ... (Copyright and License) ...
      */
      
      #include <u.h>
      #include <libc.h>
      
      char *argv0;
      
      /*
       * Mac OS can't deal with files that only declare data.
       * ARGBEGIN mentions this function so that this file gets pulled in.
       */
      void __fixargv0(void) { }
      
  2. src/lib9/flag.c:

    • argv0変数の宣言と__fixargv0関数の定義が追加されました。これらはargv0.cから移動されたものです。
      --- a/src/lib9/flag.c
      +++ b/src/lib9/flag.c
      @@ -26,6 +26,14 @@ static Flag *curflag;
       static Flag *fhash[512];
       static Flag *first, *last;
       
      +char *argv0;
      +
      +/*
      + * Mac OS can't deal with files that only declare data.
      + * ARGBEGIN mentions this function so that this file gets pulled in.
      + */
      +void __fixargv0(void) { }
      +
       // FNV-1 hash. http://isthe.com/chongo/tech/comp/fnv/
       static uint32
        fnv(char *p, int n)
      @@ -221,7 +229,6 @@ flagparse(int *argcp, char ***argvp, void (*usage)(void))\n         	argc = *argcp;\n         	argv = *argvp;\n         \n        -\t__fixargv0();
       	argv0 = argv[0];
       	argc--;
       	argv++;
      
    • flagparse関数内の__fixargv0();という呼び出しが削除されました。

コアとなるコードの解説

このコミットのコアとなる変更は、argv0変数と__fixargv0関数の定義をsrc/lib9/argv0.cからsrc/lib9/flag.cへ移動した点です。

  • src/lib9/argv0.cの削除: argv0.cは、argv0というグローバル変数と、Mac OSのリンカの挙動を回避するための空関数__fixargv0を定義するだけのファイルでした。このファイルが削除されたのは、その内容がflag.cに統合されたため、独立したファイルとして存在する必要がなくなったからです。

  • src/lib9/flag.cへの移動と配置: flag.cは、コマンドライン引数を解析するためのフラグ処理ロジックを含むファイルです。このファイル内でargv0変数と__fixargv0関数が定義されることで、以下の利点が得られます。

    1. 前方宣言の問題の解消: flag.c内で__fixargv0が定義されるため、同じファイル内のflagparse関数が__fixargv0を参照する際に、コンパイラはすでにその完全な型情報を知っています。これにより、Plan 9コンパイラが型情報がないと不平を言う問題が根本的に解決されます。
    2. リンカの挙動の最適化: __fixargv0の本来の目的は、データのみのファイルがリンカによって削除されるのを防ぐことでした。flag.cはすでに多くのコードとデータを持つファイルであるため、__fixargv0がこのファイル内に存在することで、この関数が呼び出されなくても、flag.c全体が確実にリンクされるようになります。
    3. __fixargv0()呼び出しの削除: flagparse関数内にあった__fixargv0();という明示的な呼び出しは、もはや不要になりました。これは、__fixargv0flag.c内で定義されたことで、リンカがこのファイル(およびargv0変数)を確実に含めるための「トリガー」としての役割がなくなったためです。関数が同じコンパイル単位(ファイル)内に存在すれば、コンパイラとリンカは自動的にその存在を認識し、必要に応じて処理します。

この変更は、コードの物理的な配置を最適化することで、特定のコンパイラ(Plan 9コンパイラ)の厳格な要件を満たし、ビルドプロセスにおける潜在的な問題を解消する、クリーンで効果的な解決策と言えます。

関連リンク

参考にした情報源リンク