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

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

このコミットは、Go言語のリンカ (cmd/ld) とランタイム (src/pkg/runtime) における、プログラムカウンタ-行番号テーブル (PC-Line Table, pclntab) の構造変更に関するものです。具体的には、従来の圧縮された pclntabfunctab を廃止し、新しい連続した pclntab を導入することで、ランタイムでのデコード処理を簡素化し、パフォーマンスを向上させることを目的としています。

コミット

commit c3de91bb15cf23bd1757c63c581f9da4b7e14598
Author: Russ Cox <rsc@golang.org>
Date:   Thu Jul 18 10:43:22 2013 -0400

    cmd/ld, runtime: use new contiguous pcln table
    
    R=golang-dev, r, dave
    CC=golang-dev
    https://golang.org/cl/11494043

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

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

元コミット内容

cmd/ld, runtime: use new contiguous pcln table

このコミットは、Goリンカとランタイムが新しい連続したPC-Lineテーブルを使用するように変更します。

変更の背景

Go言語の実行可能ファイルには、プログラムカウンタ (PC) とソースコードの行番号、ファイル名、関数名をマッピングするための重要なランタイムデータ構造である pclntab (Program Counter Line Table) が含まれています。このテーブルは、クラッシュ時のスタックトレース生成や runtime.GetStack の呼び出しなど、デバッグやプロファイリングにおいて不可欠な情報を提供します。

Go 1.2より前のバージョンでは、Goリンカは pclntab の圧縮バージョンを実行可能ファイルに埋め込んでいました。Goランタイムはプログラムの初期化時にこのテーブルを解凍する必要がありました。この圧縮と解凍のプロセスは、特に大規模なアプリケーションにおいて、起動時間のオーバーヘッドやメモリフットプリントの増加につながる可能性がありました。

このコミットの主な背景は、Go 1.2で導入された pclntab の設計変更にあります。この変更の動機は、アプリケーションの起動時間を改善し、潜在的にメモリフットプリントを削減することでした。pclntab を実行可能ファイル内で事前に展開された(非圧縮の)形式で格納することで、ランタイムでの解凍処理が不要になります。これにより、オペレーティングシステムは必要に応じてこのテーブルのセクションをデマンドページングできるようになり、特にガベージコレクション時など、ランタイムが頻繁にアクセスする際に効率が向上します。

ただし、この変更にはトレードオフも存在します。pclntab が圧縮されなくなるため、Go実行可能ファイルのサイズが増加します。このサイズ増加は、プログラム内の関数の数に比例して大きくなる可能性があります。

前提知識の解説

  • プログラムカウンタ (PC): CPUが次に実行する命令のアドレスを指すレジスタ。Goのランタイムでは、PC値から対応するソースコードの情報を取得するために pclntab が使用されます。
  • PC-Line Table (pclntab): Goの実行可能ファイルに埋め込まれているデータ構造で、プログラムカウンタ (PC) の値と、それが対応するソースコードのファイル名、行番号、関数名などの情報をマッピングします。スタックトレースの生成やデバッグ情報に利用されます。
  • リンカ (cmd/ld): Goのコンパイラによって生成されたオブジェクトファイルを結合し、実行可能ファイルを生成するツールです。このプロセス中に、pclntab などのランタイム情報が実行可能ファイルに埋め込まれます。
  • ランタイム (src/pkg/runtime): Goプログラムの実行を管理する部分で、ガベージコレクション、スケジューリング、スタック管理、エラー処理など、低レベルの操作を担当します。pclntab の情報はランタイムによって利用されます。
  • functab: 従来のGoランタイムで使用されていた関数情報テーブル。このコミットによって pclntab に統合され、廃止されます。
  • golang.org/s/go12symtab: Go 1.2におけるシンボルテーブル形式の変更に関する設計ドキュメントへのショートリンクです。このドキュメントは、pclntab の新しい構造と、それがどのようにランタイムの効率に貢献するかを詳細に説明しています。

技術的詳細

このコミットの核心は、Goの実行可能ファイル内の関数およびPC-行番号情報の表現方法を根本的に変更することです。

従来のGoのリンカは、functabpclntab という2つの別々のテーブルを生成していました。

  • functab は関数のエントリポイントと、その関数に関するメタデータ(引数のサイズ、ローカル変数のサイズ、ポインタ情報など)へのポインタを格納していました。
  • pclntab はPC値と行番号のマッピングを圧縮形式で格納していました。

このコミットでは、これらのテーブルを統合し、pclntab という単一の連続したデータ構造として表現します。この新しい pclntab は、Go 1.2のシンボルテーブル形式 (golang.org/s/go12symtab で詳細が説明されています) に準拠しており、以下の特徴を持ちます。

  1. 非圧縮形式: pclntab は実行可能ファイル内で非圧縮形式で格納されます。これにより、ランタイムでのデコード処理が不要になり、起動時のパフォーマンスが向上します。
  2. 単一のデータ構造: 関数情報(エントリポイント、名前オフセット、引数サイズ、ローカル変数サイズなど)とPC-行番号マッピングがすべて pclntab 内に連続して配置されます。これにより、ランタイムがこれらの情報にアクセスする際のキャッシュ効率が向上します。
  3. ポインタオフセットの利用: Func 構造体内のポインタ関連情報 (ptrsoff, ptrslen) が、pclntab のベースアドレスからのオフセットとして表現されるようになります。これにより、Func 構造体自体が pclntab の一部として扱われ、メモリレイアウトがより効率的になります。
  4. 可変長整数 (varint) エンコーディング: PC-行番号マッピングのデータは、引き続き可変長整数でエンコードされます。これにより、テーブルのサイズをある程度最適化しつつ、ランタイムでのデコードを効率的に行えます。
  5. ヘッダの導入: pclntab の先頭には、マジックナンバー、PC量子、ポインタサイズなどの情報を含むヘッダが追加されます。これにより、ランタイムはテーブルの形式を検証し、正しく解析できます。

この変更により、リンカ (src/cmd/ld/lib.c) は functab() 関数を廃止し、pclntab() 関数内で新しい連続したテーブルを構築するようになります。ランタイム (src/pkg/runtime/symtab.c, src/pkg/runtime/runtime.h) は、この新しい pclntab の構造を理解し、関数情報やPC-行番号マッピングにアクセスするためのロジックを更新します。特に、Func 構造体から *name フィールドが削除され、nameoff (名前へのオフセット) が導入されています。

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

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

  1. src/cmd/5l/obj.c, src/cmd/6l/obj.c, src/cmd/8l/obj.c:

    • リンカのメイン関数から functab() の呼び出しが削除され、pclntab() の呼び出しのみが残されています。これは、functabpclntab に統合されたことを示しています。
  2. src/cmd/ld/lib.c:

    • pclntab() 関数の実装が大幅に変更されています。
      • 従来の pclntab() 関数と functab() 関数が統合され、新しい pclntab() が単一の連続したテーブルを生成する責任を負います。
      • ftabaddstring というヘルパー関数が追加され、文字列を pclntab に追加し、そのオフセットを返すようになりました。
      • pclntab のヘッダ (マジックナンバー、PC量子、ポインタサイズなど) が書き込まれます。
      • 各関数のエントリポイントと、その関数に対応する情報構造体へのオフセットが pclntab に書き込まれます。
      • 関数情報構造体 (エントリPC、名前オフセット、引数サイズ、ローカル変数サイズ、フレームサイズ、ポインタ情報、PCデータテーブルへのオフセット、関数データテーブルへのオフセットなど) が pclntab 内に直接構築されます。
      • ファイル名テーブルも pclntab の一部として、その末尾に配置されるようになりました。
    • genasmsym 関数から、従来の functab に関連するシンボル出力ロジックが削除されています。
  3. src/cmd/ld/lib.h:

    • SPCLNTAB の定義が変更され、TODO: move to unmapped section のコメントが削除されています。これは、pclntab が実行可能ファイル内の特定のセクションに配置されることを示唆しています。
    • functab() 関数のプロトタイプ宣言が削除されています。
  4. src/pkg/runtime/mgc0.c:

    • addframeroots 関数内で、f->ptrsoffpclntab のベースアドレスからのオフセットとして使用されるように変更されています。これにより、ポインタ情報が pclntab 内に連続して格納されていることを前提としたアクセスが行われます。
  5. src/pkg/runtime/runtime.h:

    • struct Func の定義が変更されています。
      • String *name; フィールドが削除され、代わりに int32 nameoff; フィールドが追加されています。これは、関数名が pclntab 内のオフセットとして参照されるようになったことを示しています。
      • コメントが // Layout of in-memory per-function information prepared by linker と更新され、// See http://golang.org/s/go12symtab. への参照が追加されています。
    • runtime·funcname(Func*) という新しい関数プロトタイプが追加されています。
  6. src/pkg/runtime/symtab.c:

    • Ftab 構造体の定義が変更され、Func *func;uintptr funcoff; に置き換えられています。これは、関数情報へのポインタではなく、pclntab 内のオフセットとして関数情報が参照されるようになったことを示しています。
    • pclntabextern byte pclntab[]; として宣言され、そのベースアドレスがランタイムからアクセス可能になっています。
    • runtime·symtabinit() 関数が大幅に更新され、新しい pclntab のヘッダを検証し、関数テーブルとファイルテーブルを pclntab から解析するロジックが追加されています。
    • pcvalue() 関数や funcline() 関数など、PC-行番号マッピングにアクセスする関数が、pclntab のベースアドレスとオフセットを使用して情報を取得するように変更されています。
    • runtime·funcname() 関数が追加され、Func 構造体の nameoff を使用して関数名文字列を pclntab から取得するようになりました。
    • runtime·findfunc() 関数も、pclntab 内のオフセットを使用して Func 構造体を取得するように変更されています。
  7. src/pkg/runtime/traceback_arm.c, src/pkg/runtime/traceback_x86.c:

    • スタックトレース生成ロジック内で、関数名を表示する際に *f->name の代わりに runtime·funcname(f) を使用するように変更されています。

コアとなるコードの解説

このコミットの最も重要な変更は、src/cmd/ld/lib.cpclntab() 関数と、src/pkg/runtime/runtime.h および src/pkg/runtime/symtab.c における Func 構造体と関連するランタイム関数の変更です。

src/cmd/ld/lib.cpclntab() 関数:

// 変更前 (抜粋)
void
pclntab(void) { /* ... */ }

void
functab(void) { /* ... */ }

// 変更後 (抜粋)
void
pclntab(void)
{
    // ...
    // See golang.org/s/go12symtab for the format. Briefly:
    //  8-byte header
    //  nfunc [PtrSize bytes]
    //  function table, alternating PC and offset to func struct [each entry PtrSize bytes]
    //  end PC [PtrSize bytes]
    //  offset to file table [4 bytes]
    // ...
    setuint32(ftab, 0, 0xfffffffb); // Magic number
    setuint8(ftab, 6, MINLC);      // PC Quantum
    setuint8(ftab, 7, PtrSize);    // Pointer size
    setuintxx(ftab, 8, nfunc, PtrSize); // Number of functions

    // Iterate through functions and build the table
    for(cursym = textp; cursym != nil; cursym = cursym->next, nfunc++) {
        // ...
        setaddr(ftab, 8+PtrSize+nfunc*2*PtrSize, cursym); // Function entry PC
        setuintxx(ftab, 8+PtrSize+nfunc*2*PtrSize+PtrSize, funcstart, PtrSize); // Offset to func struct

        // Build func struct within pclntab
        // ...
        setuint32(ftab, nameoff, ftabaddstring(ftab, cursym->name)); // Function name offset
        // ...
    }

    // File table at the end
    // ...
}

変更後の pclntab() は、0xfffffffb というマジックナンバーで始まるヘッダを書き込み、その後にGoプログラム内の全関数の情報が連続して配置されるようにテーブルを構築します。各関数エントリは、その関数の開始PCと、pclntab 内のその関数に対応する詳細な Func 構造体へのオフセットのペアで構成されます。この Func 構造体には、関数のエントリPC、名前へのオフセット、引数サイズ、ローカル変数サイズ、フレームサイズ、ポインタ情報、PCデータテーブルへのオフセット、関数データテーブルへのオフセットなどが含まれます。関数名自体は、pclntab の別の領域に文字列として格納され、Func 構造体からはその文字列へのオフセット (nameoff) で参照されます。これにより、文字列の重複を避け、テーブル全体のサイズを最適化しています。

src/pkg/runtime/runtime.hstruct Func:

// 変更前
struct Func
{
    String  *name;  // function name
    uintptr entry;  // start pc
    // ...
};

// 変更後
struct Func
{
    uintptr entry;  // start pc
    int32   nameoff;    // function name
    // ...
};

Func 構造体から String *name; が削除され、int32 nameoff; が追加されたことは、関数名が直接ポインタで参照されるのではなく、pclntab 内のオフセットとして格納されるようになったことを明確に示しています。これにより、Func 構造体自体が pclntab の一部として、よりコンパクトに配置されることが可能になります。

src/pkg/runtime/symtab.cruntime·symtabinit()runtime·funcname():

// 変更前 (抜粋)
void
runtime·symtabinit(void)
{
    ftab = (Ftab*)(functab+1);
    nftab = functab[0];
    // ...
}

// 変更後 (抜粋)
extern byte pclntab[]; // base for f->ptrsoff

void
runtime·symtabinit(void)
{
    // ...
    // See golang.org/s/go12symtab for header: 0xfffffffb,
    // two zero bytes, a byte giving the PC quantum,
    // and a byte giving the pointer width in bytes.
    if(*(uint32*)pclntab != 0xfffffffb || pclntab[4] != 0 || pclntab[5] != 0 || pclntab[6] != PCQuantum || pclntab[7] != sizeof(void*)) {
        runtime·throw("invalid function symbol table\\n");
    }

    nftab = *(uintptr*)(pclntab+8); // Number of functions
    ftab = (Ftab*)(pclntab+8+sizeof(void*)); // Start of function table
    // ...
}

int8*
runtime·funcname(Func *f)
{
    if(f == nil || f->nameoff == 0)
        return nil;
    return (int8*)(pclntab + f->nameoff);
}

runtime·symtabinit() は、プログラム起動時に pclntab のヘッダを検証し、その中から関数テーブルとファイルテーブルの開始位置を特定します。これにより、ランタイムは pclntab 全体を単一のメモリブロックとして扱い、必要な情報に効率的にアクセスできるようになります。

runtime·funcname() は、Func 構造体の nameoff フィールドを利用して、pclntab のベースアドレスからオフセットを計算し、関数名文字列へのポインタを返します。これは、関数名が pclntab 内に埋め込まれている新しい設計を反映しています。

これらの変更により、Goの実行可能ファイルは、関数とPC-行番号情報をよりコンパクトで効率的な形式で格納できるようになり、ランタイムがこれらの情報にアクセスする際のパフォーマンスが向上します。

関連リンク

参考にした情報源リンク

  • golang.org/s/go12symtab (Go 1.2 シンボルテーブル形式の設計ドキュメント)
  • Stack Overflow: "What is the purpose of the Go pclntab section?"
  • Cockroach Labs Blog: "Go 1.2: The New Contiguous Pcln Table" (類似の変更に関する解説)
  • Y Combinator News: "Go 1.2: The New Contiguous Pcln Table" (関連する議論)