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

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

このコミットは、Go言語のリンカ(liblink)とコンパイラ(cmd/gc)がPlan 9オペレーティングシステム上で動作する際に発生していた、型シグネチャの非互換性の問題を修正するものです。具体的には、NodeSectionArrayといった構造体の定義が、異なるコンパイルユニット間で不整合を起こすことを防ぐために、#pragma incompleteディレクティブが追加されています。

コミット

commit 4321beba85d2317b86911401dd25bb87a48677e7
Author: David du Colombier <0intro@gmail.com>
Date:   Tue Dec 10 08:42:41 2013 -0500

    liblink, cmd/gc: fix incompatible type signatures on Plan 9
    
    R=ality, golang-dev, r, rsc
    CC=golang-dev
    https://golang.org/cl/39640043

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

https://github.com/golang/go/commit/4321beba85d2317b86911401dd25bb87a48677e7

元コミット内容

liblink, cmd/gc: fix incompatible type signatures on Plan 9

このコミットは、Go言語のリンカ(liblink)とコンパイラ(cmd/gc)において、Plan 9環境での型シグネチャの非互換性を修正します。

変更の背景

Go言語のツールチェインは、様々なオペレーティングシステムをサポートするように設計されています。その中には、ベル研究所で開発された分散オペレーティングシステムであるPlan 9も含まれます。Goのコンパイラ(cmd/gc)やリンカ(liblink)は、C言語で書かれた部分とGo言語で書かれた部分が混在しており、特にC言語で書かれた部分では、異なるコンパイルユニット(例えば、リンカとコンパイラ、あるいはリンカ内部の異なるモジュール)間で共通のデータ構造を共有する必要があります。

このコミットが修正しようとしている問題は、Plan 9環境において、これらの共通データ構造(NodeSectionArrayなど)の型定義が、異なるコンパイルユニット間で「非互換な型シグネチャ」として認識されてしまうことでした。これは通常、ヘッダファイルのインクルード順序、マクロ定義、またはコンパイラの特定の挙動によって、同じ構造体名であっても異なる内部表現やアラインメントが適用されてしまう場合に発生します。結果として、リンカやコンパイラが正しく連携できず、ビルドエラーや実行時エラーを引き起こす可能性がありました。

特に、Goのツールチェインでは、8l(Plan 9向けのリンカ)や8g(Plan 9向けのGoコンパイラ)といった、ターゲットアーキテクチャ(この場合は8l/8gは386アーキテクチャを指すことが多い)に特化したツールが存在します。これらのツールが、liblinklibgc(Goコンパイラのバックエンドライブラリ)と連携する際に、型定義の不一致が問題となっていたと考えられます。

前提知識の解説

  • Plan 9: ベル研究所で開発された分散オペレーティングシステム。UNIXの思想をさらに推し進め、すべてのリソースをファイルとして扱うという特徴を持つ。Go言語の開発者の一部はPlan 9の開発にも携わっており、Go言語の設計思想にも影響を与えている。
  • Go言語のツールチェイン: Go言語のプログラムをビルド、実行するために必要な一連のツール群。主要なものとして、goコマンド、コンパイラ(cmd/gc)、リンカ(liblink)、アセンブラ(cmd/goasm)などがある。
  • cmd/gc: Go言語の公式コンパイラ。Goのソースコードを機械語に変換する役割を担う。
  • liblink: Go言語のリンカのライブラリ部分。コンパイルされたオブジェクトファイルを結合し、実行可能ファイルを生成する。
  • 8l / 8g: Plan 9の命名規則に由来するGoのツールチェインのコンポーネント名。8はIntel 80386アーキテクチャを指し、lはリンカ、gはGoコンパイラを指す。つまり、8lは386アーキテクチャ向けのリンカ、8gは386アーキテクチャ向けのGoコンパイラを意味する。
  • 型シグネチャの非互換性: C言語において、同じ名前の構造体や関数であっても、異なるコンパイルユニットで定義された際に、その内部表現(メンバの順序、サイズ、アラインメントなど)が異なってしまうこと。これは、ヘッダファイルのインクルードミス、コンパイラオプションの違い、または特定のプラットフォームにおけるコンパイラの挙動によって発生しうる。結果として、異なるコンパイルユニット間でデータをやり取りする際に、メモリレイアウトの不一致によりデータ破損やクラッシュを引き起こす。
  • #pragma incomplete: C言語のプリプロセッサディレクティブの一つ。これは、特定の構造体や共用体が「不完全型(incomplete type)」であることをコンパイラに伝えるために使用される。不完全型とは、そのサイズやメンバがまだ完全に定義されていない型のこと。通常、前方宣言(struct Node;)を行うことで不完全型として扱われるが、#pragma incompleteは、特にリンカや他のツールとの連携において、型定義の完全性を強制しないようにするために使用されることがある。これにより、異なるコンパイルユニットが同じ構造体を参照する際に、その完全な定義がなくてもコンパイルを進めることができ、リンカが最終的にすべての定義を解決することを期待する。この文脈では、異なるコンパイルユニットが同じ構造体を参照する際に、その完全な定義がなくてもコンパイルを進めることができ、リンカが最終的にすべての定義を解決することを期待する。

技術的詳細

このコミットの核心は、C言語のプリプロセッサディレクティブである#pragma incompleteを使用して、特定の構造体(NodeSectionArray)の型シグネチャの非互換性を回避することです。

通常、C言語では、構造体を使用する前にその完全な定義が必要です。しかし、相互参照する構造体や、異なるコンパイルユニット間で共有されるが、それぞれのコンパイルユニットではその完全な定義が必要ない(あるいは、完全な定義が別の場所にある)場合に、前方宣言(struct Node;)が用いられます。これにより、コンパイラはその構造体が存在することを知り、ポインタ型として扱うことができます。

しかし、Plan 9環境における特定のコンパイラ(8l8g)の挙動、またはGoツールチェインのビルドシステムにおける複雑な依存関係により、liblinkcmd/gcの間で、これらの構造体の「完全な」型定義が異なる形で解釈されてしまう問題が発生していました。これは、例えば、あるファイルでは構造体のメンバが完全に定義されているが、別のファイルではその構造体へのポインタのみが使用され、その際にコンパイラが異なる内部表現を生成してしまう、といった状況が考えられます。

#pragma incomplete struct Nodeのようなディレクティブは、コンパイラに対して「Nodeという構造体は存在するが、このコンパイルユニットではその完全な定義は期待しない(あるいは、不完全な型として扱う)」という指示を与えます。これにより、コンパイラは型チェックを緩和し、異なるコンパイルユニット間で型定義の厳密な一致を強制することなく、コンパイルを進めることができます。最終的な型解決はリンカの役割となります。

このアプローチは、特にクロスコンパイル環境や、異なるコンパイラバージョン、あるいは特定のOS環境(この場合はPlan 9)のコンパイラが、標準Cの挙動とは異なる微妙な解釈をする場合に有効な回避策となります。Goのツールチェインは、C言語で書かれた部分が多いため、このようなC言語のコンパイラ固有の問題に対処する必要がありました。

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

diff --git a/include/link.h b/include/link.h
index 1d6aec49ee..abaa6ad401 100644
--- a/include/link.h
+++ b/include/link.h
@@ -43,6 +43,9 @@ typedef	struct	Library	Library;\n typedef	struct	Pcln	Pcln;\n typedef	struct	Pcdata	Pcdata;\n \n+// prevent incompatible type signatures between liblink and 8l on Plan 9\n+#pragma incomplete struct Node\n+\n struct	Addr\n {\n \tvlong	offset;\n@@ -111,6 +114,9 @@ struct	Prog\n 	char	mode;	/* 16, 32, or 64 */\n };\n \n+// prevent incompatible type signatures between liblink and 8l on Plan 9\n+#pragma incomplete struct Section\n+\n struct	LSym\n {\n 	char*	name;\
diff --git a/src/cmd/gc/go.h b/src/cmd/gc/go.h
index 6faf4c446c..cc9a5eeaf8 100644
--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -129,6 +129,9 @@ struct	Val\n 	} u;\n };\n \n+// prevent incompatible type signatures between libgc and 8g on Plan 9\n+#pragma incomplete struct Array\n+\n typedef	struct	Array	Array;\n typedef	struct	Bvec	Bvec;\n typedef	struct	Pkg Pkg;\

コアとなるコードの解説

このコミットでは、以下の3つのファイルに#pragma incompleteディレクティブが追加されています。

  1. include/link.h:

    • typedef struct Library Library;
    • typedef struct Pcln Pcln;
    • typedef struct Pcdata Pcdata; これらのtypedef宣言の直後に、Node構造体に対する#pragma incompleteが追加されています。
    // prevent incompatible type signatures between liblink and 8l on Plan 9
    #pragma incomplete struct Node
    

    これは、liblink(リンカ)と8l(Plan 9向けのリンカ)の間でNode構造体の型シグネチャが非互換になるのを防ぐためのものです。NodeはGoのAST(抽象構文木)のノードを表す重要な構造体であり、コンパイラとリンカの間で共有される可能性があります。

    さらに、struct Progの定義の後に、Section構造体に対する#pragma incompleteが追加されています。

    // prevent incompatible type signatures between liblink and 8l on Plan 9
    #pragma incomplete struct Section
    

    Sectionは、実行可能ファイルのセクション(コードセクション、データセクションなど)を表す構造体であり、リンカが主に扱うものです。ここでもliblink8l間の非互換性を防ぐ目的があります。

  2. src/cmd/gc/go.h:

    • struct Valの定義の後に、Array構造体に対する#pragma incompleteが追加されています。
    // prevent incompatible type signatures between libgc and 8g on Plan 9
    #pragma incomplete struct Array
    

    これは、libgc(Goコンパイラのバックエンドライブラリ)と8g(Plan 9向けのGoコンパイラ)の間でArray構造体の型シグネチャが非互換になるのを防ぐためのものです。ArrayはGoの配列型を表す構造体であり、コンパイラ内部で頻繁に利用されます。

これらの変更は、GoツールチェインのC言語部分において、Plan 9環境特有のコンパイラの挙動やビルドシステムの問題に起因する型定義の不整合を、#pragma incompleteというプリプロセッサディレクティブを用いて回避することを目的としています。これにより、異なるコンパイルユニットが同じ構造体を参照する際に、その完全な定義がなくてもコンパイルを進めることができ、リンカが最終的にすべての定義を解決することを期待します。

関連リンク

参考にした情報源リンク