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

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

このコミットは、Go言語のツールチェーンの一部である cmd/dist 内の goc2c ツールにおけるバグ修正に関するものです。具体的には、goc2c がGoのソースファイル(.goc)からC言語のソースファイル(.c)を生成する際に、生成されるCファイル内の #line ディレクティブの行番号が誤ってオフセットされてしまう問題を修正しています。これにより、デバッグ時やエラー報告時に正確な元のGoソースコードの行番号が参照できるようになります。

コミット

  • コミットハッシュ: d33f09bcc0814d900510c23ad5285b3a7c3b2e5c
  • Author: Anthony Martin ality@pbrane.org
  • Date: Tue Apr 23 16:02:50 2013 -0700

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

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

元コミット内容

    cmd/dist: fix line numbers in goc2c generated files
    
    We have to reset the global lineno variable before
    processing each file otherwise line numbers will be
    offset by the number of lines in the previous file.
    
    The following examples are from the beginning of the
    ztime_linux_amd64.c file which is generated from
    time.goc in the runtime package.
    
    Before:
        #line 2483 "/home/apm/src/go/src/pkg/runtime/time.goc"
        static Timers timers;
        static void addtimer ( Timer* ) ;
        void
        time·Sleep(int64 ns)
        {
        #line 2492 "/home/apm/src/go/src/pkg/runtime/time.goc"
    
    After:
        #line 16 "/home/apm/src/go/src/pkg/runtime/time.goc"
        static Timers timers;
        static void addtimer ( Timer* ) ;
        void
        time·Sleep(int64 ns)
        {
        #line 25 "/home/apm/src/go/src/pkg/runtime/time.goc"
    
    R=golang-dev, minux.ma, iant, r, adg
    CC=golang-dev
    https://golang.org/cl/8653045

変更の背景

Go言語の初期のバージョンでは、一部のランタイムコードはGoとCのハイブリッド形式で記述されており、.goc という拡張子を持つファイルが使用されていました。これらの .goc ファイルは、cmd/dist ツールチェーンの一部である goc2c というツールによってC言語のソースファイル(.c)に変換されていました。

この変換プロセスにおいて、goc2c は変換元のGoソースコードの行番号情報を、生成されるCファイル内の #line プリプロセッサディレクティブとして埋め込んでいました。これは、Cコンパイラがエラーや警告を報告する際に、元のGoソースコードの正確な行番号を示すために非常に重要です。

しかし、このコミット以前の goc2c にはバグがありました。複数の .goc ファイルを処理する際に、内部で使用されるグローバルな行番号カウンタ lineno がファイルごとにリセットされていませんでした。このため、2番目以降のファイルから生成されたCコードでは、#line ディレクティブの行番号が、前のファイルの行数分だけオフセットされてしまい、誤った行番号が報告される問題が発生していました。

コミットメッセージに示されている例では、runtime パッケージの time.goc から生成された ztime_linux_amd64.c ファイルの冒頭部分で、本来 16 行目であるべき #line ディレクティブが 2483 行目と誤って表示されていることが示されています。これは、time.goc の前に処理された別の .goc ファイルの行数が加算されてしまった結果です。この誤った行番号は、デバッグの際に開発者を混乱させ、問題の特定を困難にする可能性がありました。

このコミットは、この行番号のオフセット問題を解決し、生成されるCファイル内の #line ディレクティブが常に元のGoソースコードの正確な行番号を指すようにすることを目的としています。

前提知識の解説

cmd/dist

cmd/dist は、Go言語のビルドシステムの中核をなすツールです。Goのソースコードからコンパイラ、リンカ、アセンブラなどのツールチェーン全体をビルドするために使用されます。Goのソースコード自体もGoで書かれているため、cmd/dist はブートストラッププロセスにおいて重要な役割を果たします。

goc2c

goc2ccmd/dist の一部として機能するツールで、Go言語のランタイムの一部が記述されていた .goc ファイルをC言語のソースファイルに変換する役割を担っていました。Go言語の初期段階では、パフォーマンスが重要な部分や、既存のCコードとの連携が必要な部分で、GoとCのハイブリッドコードが用いられることがありました。goc2c は、このようなハイブリッドコードをビルドプロセスに統合するための橋渡し役でした。

.goc ファイル

.goc ファイルは、Go言語の初期のランタイム実装で使われていた特殊なファイル形式です。Goの構文とCの構文が混在しており、goc2c によってCコードに変換されていました。現在では、Goランタイムのほとんどが純粋なGoで記述されており、.goc ファイルはほとんど使われていません。

#line ディレクティブ

C/C++言語のプリプロセッサには #line ディレクティブというものがあります。これは、コンパイラに対して、現在のソースコードの行番号とファイル名を指定された値に変更するように指示するものです。 構文は以下の通りです。

#line number "filename"
  • number: 次の行から始まる行番号を指定します。
  • filename: 現在のファイル名を指定します。

このディレクティブは、主に以下のような目的で使用されます。

  1. コード生成ツール: 別の言語やテンプレートからC/C++コードを生成するツールが、生成されたコードのエラーメッセージを元のソースファイルの行番号とファイル名で報告するために使用します。goc2c の場合がこれに該当します。
  2. デバッグ: デバッガがソースコードの正確な位置を特定できるようにします。
  3. マクロ展開: 複雑なマクロが展開された結果のコードをデバッグしやすくするために使用されることもあります。

このコミットの文脈では、goc2ctime.goc から ztime_linux_amd64.c を生成する際に、time.goc 内のGoコードの行番号を ztime_linux_amd64.c 内の #line ディレクティブとして埋め込んでいました。

技術的詳細

この問題の根本原因は、goc2c ツールが複数の .goc ファイルを処理する際に、内部で管理している行番号カウンタ lineno を各ファイルの処理開始時にリセットしていなかったことにあります。

goc2c は、.goc ファイルを読み込み、Cコードを生成する過程で、元のGoコードの行番号を追跡し、それを #line ディレクティブとして出力します。この追跡には、static unsigned int lineno = 1; というグローバル変数(またはそれに準ずるスコープの変数)が使用されていました。

問題は、goc2c が最初の .goc ファイルの処理を終えた後も lineno の値が保持され、次の .goc ファイルの処理がその保持された値から開始されてしまっていた点です。これにより、2番目以降のファイルで生成される #line ディレクティブの行番号は、前のファイルの総行数分だけ加算された、誤った値になっていました。

解決策はシンプルかつ効果的です。goc2c が新しい .goc ファイルの処理を開始する直前に、lineno 変数を 1 に明示的にリセットすることです。これにより、各ファイルは独立して行番号がカウントされ、生成されるCファイル内の #line ディレクティブは常に元のGoソースコードの正確な行番号を指すようになります。

コミットメッセージの例で示されているように、この修正により、ztime_linux_amd64.c の冒頭の #line ディレクティブが 2483 から 16 に修正され、正しい行番号が反映されるようになりました。

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

--- a/src/cmd/dist/goc2c.c
+++ b/src/cmd/dist/goc2c.c
@@ -66,7 +66,7 @@ static int gcc;
 
 /* File and line number */
 static const char *file;
-static unsigned int lineno = 1;
+static unsigned int lineno;
 
 /* List of names and types.  */
 struct params {
@@ -754,6 +754,7 @@ goc2c(char *goc, char *c)\n \tinput = bstr(&in);\n \toutput = &out;\n \n+\tlineno = 1;\n \tprocess_file();\n \t\n \twritefile(&out, c, 0);\n```

## コアとなるコードの解説

変更は `src/cmd/dist/goc2c.c` ファイルの2箇所で行われています。

1.  **`static unsigned int lineno = 1;` から `static unsigned int lineno;` への変更**:
    *   元のコードでは、`lineno` 変数が宣言時に `1` で初期化されていました。これは、プログラムが開始されたときに一度だけ行われる初期化です。
    *   変更後、`lineno` は宣言時に初期化されなくなりました(デフォルト値は0)。これは、後続の明示的な初期化に依存することを示唆しています。この変更自体が直接的な修正というよりは、次の変更と合わせて意図を明確にするためのものです。

2.  **`goc2c` 関数の冒頭に `lineno = 1;` の追加**:
    *   `goc2c` 関数は、個々の `.goc` ファイルをCファイルに変換する主要なロジックを含んでいます。
    *   この変更により、`goc2c` 関数が呼び出されるたびに、つまり新しい `.goc` ファイルの処理が開始されるたびに、`lineno` 変数が明示的に `1` にリセットされるようになりました。
    *   `process_file()` は、実際にファイルの内容を読み込み、変換処理を行う関数です。この `process_file()` が呼び出される前に `lineno` がリセットされることで、各ファイルの行番号が常に `1` から正しくカウントされることが保証されます。

この2つの変更により、`goc2c` が複数の `.goc` ファイルを連続して処理しても、それぞれのファイルに対して行番号が独立して `1` から始まるようになり、生成されるCファイル内の `#line` ディレクティブが常に正確な元のGoソースコードの行番号を指すようになりました。

## 関連リンク

- Go CL 8653045: [https://golang.org/cl/8653045](https://golang.org/cl/8653045)

## 参考にした情報源リンク

- C言語の `#line` ディレクティブに関する一般的な情報源 (例: GCC documentation, C standard draftsなど)
- Go言語の `cmd/dist` および `goc2c` に関するGoプロジェクトのドキュメントやソースコード (GoのGitHubリポジトリ)
- Go言語の初期のランタイム実装に関する歴史的情報 (Goのメーリングリストや設計ドキュメントなど)
- `time.goc` や `ztime_linux_amd64.c` といったファイル名から、Goランタイムの内部構造に関する情報 (GoのGitHubリポジトリ)