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

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

このコミットは、Goコンパイラ内部で使用されていた cas() という関数の名称を newcase() に変更するものです。これは、cas() が一般的に「compare and swap (比較と交換)」操作を指す名称として広く使われているため、特にNIXランタイムとの名前衝突を避ける目的で行われました。

コミット

commit 1bc1caa802e9ec8170f6e971712579d0c2d321f6
Author: Ron Minnich <rminnich@gmail.com>
Date:   Wed Oct 26 15:27:59 2011 -0700

    cc: change cas to newcase
    
    Change the name of cas() in cc to newcase() to avoid a NIX conflict.
    cas() is used in cc to create a new Case struct. There is a name
    conflict in that cas() is a commonly-used
    name for compare and swap. Since cas() is only used internally
    in the compiler in 3 places, change the name to avoid a wider
    conflict with the NIX runtime. This issue might well come up on
    other OSes in the future anyway, as the name is fairly common.
    
    R=rsc
    CC=golang-dev
    https://golang.org/cl/5294071

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

https://github.com/golang/go/commit/1bc1caa802e9ec8170f6e971712579d0c2d321f6

元コミット内容

Goコンパイラ(cc)内で cas() という関数名を newcase() に変更します。これは、cas() が新しい Case 構造体を作成するために使用されていましたが、この名前が「compare and swap (比較と交換)」という一般的な操作名と衝突するためです。特にNIXランタイムとの衝突を避けるため、また将来的に他のOSでも同様の衝突が発生する可能性を考慮して、この変更が行われました。cas() はコンパイラ内部の3箇所でのみ使用されているため、影響は限定的です。

変更の背景

この変更の背景には、ソフトウェア開発における「名前空間の衝突 (Namespace Collision)」という一般的な問題があります。特に、システムプログラミングやコンパイラのような低レベルな領域では、OSのランタイムライブラリや他のシステムコンポーネントが提供する関数名と、アプリケーションやツール内部で定義された関数名が偶然一致してしまうことがあります。

今回のケースでは、Goコンパイラ内部で Case 構造体を生成する関数が cas() と命名されていました。しかし、cas (Compare And Swap) は、マルチスレッドプログラミングにおいて非常に重要なアトミック操作(複数のスレッドから同時にアクセスされても、その操作が中断されずに完全に実行されることが保証される操作)を指す略語として広く認知されています。多くのOSやCPUアーキテクチャは、この「compare and swap」操作をサポートするための低レベルなAPIや命令を提供しており、その関数名やマクロ名として cas が使われることが一般的です。

コミットメッセージにある「NIX conflict」とは、NIXというパッケージマネージャーやそのエコシステムにおけるランタイム環境で、cas という名前が既に別の目的で使用されており、Goコンパイラが生成するコードやコンパイラ自体がNIX環境下で動作する際に、名前の衝突によって予期せぬ挙動やビルドエラーが発生する可能性があったことを示唆しています。このような衝突は、リンカがどの cas 関数をリンクすべきか判断に迷ったり、誤った関数を呼び出したりすることで、プログラムのクラッシュや不正な動作を引き起こす可能性があります。

Go言語はクロスプラットフォーム対応を重視しているため、特定のOSやランタイム環境での名前衝突は、将来的に他の環境でも同様の問題を引き起こす可能性があると判断され、早期に解決する必要がありました。コンパイラ内部の関数名であるため、外部への影響は少ないと判断し、名称変更という形で対応されました。

前提知識の解説

1. Goコンパイラ (src/cmd/5c, 6c, 8c, cc)

Go言語のコンパイラは、複数のアーキテクチャに対応するために、それぞれ異なるディレクトリにコードが配置されています。

  • src/cmd/5c: Plan 9 (ARM) 向けのCコンパイラ
  • src/cmd/6c: AMD64 (x86-64) 向けのCコンパイラ
  • src/cmd/8c: x86 (32-bit) 向けのCコンパイラ
  • src/cmd/cc: 共通のCコンパイラフロントエンド(パーサー、コード生成など)

これらのコンパイラは、Go言語のソースコードを機械語に変換する過程で、C言語で書かれた中間コードを生成し、それをさらにアセンブリ言語に変換する役割を担っています。gc.h はこれらのコンパイラで使用される共通のヘッダーファイルであり、関数プロトタイプや構造体定義などが含まれています。pgen.c はパーサーが生成した構文木からコードを生成する部分、pswt.cswitch 文の処理に関連する部分です。

2. Compare And Swap (CAS)

Compare And Swap (CAS) は、並行プログラミングにおいて非常に重要なアトミック操作です。これは、メモリ上の特定のアドレスにある値が、期待する値と一致する場合にのみ、その値を新しい値に更新するという操作です。この操作は不可分(アトミック)であり、複数のスレッドが同時にCAS操作を試みても、一度に成功するのは1つのスレッドのみです。

CASは、ロックを使用せずに共有データを安全に更新するための「ロックフリー (lock-free)」アルゴリズムや「非ブロッキング (non-blocking)」アルゴリズムを実装する際の基盤となります。例えば、リンクリストやキューのようなデータ構造を並行環境で操作する際に、CASを用いてノードの追加や削除を安全に行うことができます。

多くのプログラミング言語やライブラリは、CAS操作をサポートするための関数やプリミティブを提供しています。C言語では、GCCの組み込み関数である __sync_bool_compare_and_swap__sync_val_compare_and_swap などがこれに該当します。

3. NIXランタイム

NIXは、純粋関数型パッケージマネージャーであり、再現性のあるビルドとデプロイメントを可能にするシステムです。NIXは、ソフトウェアのビルド環境を厳密に分離し、依存関係の衝突を避けることを目的としています。NIXランタイムとは、NIXによって管理される環境でプログラムが実行される際に使用されるライブラリやシステムコール、その他のコンポーネメントの集合を指します。

コミットメッセージにある「NIX conflict」は、Goコンパイラが生成するバイナリがNIX環境下で実行される際に、NIXランタイムが提供する cas という名前の関数と、Goコンパイラ内部の cas() 関数が名前衝突を起こす可能性があったことを示しています。これは、リンカがどちらの cas を参照すべきか判断できず、ビルドエラーや実行時エラーを引き起こす可能性があるため、Goコンパイラ側で名前を変更することで回避されました。

技術的詳細

このコミットの技術的な核心は、コンパイラ内部のシンボル(関数名)が、外部のシステムライブラリやランタイムのシンボルと衝突する可能性を排除することにあります。

Goコンパイラは、C言語で書かれた部分が多く、特に古いバージョンのGoでは、C言語の慣習に従って関数名が付けられていました。cas() という関数は、コンパイラが switch 文を処理する際に、新しい Case 構造体(switch 文の各 case ラベルに対応する内部表現)を作成するために使用されていました。これはコンパイラ内部の非常に特定の目的のための関数であり、外部に公開されるAPIではありませんでした。

しかし、cas という名前が「compare and swap」というアトミック操作のデファクトスタンダードな略語であるため、多くのシステムライブラリやOSのAPIでこの名前が使用されています。例えば、Linuxカーネルやglibc(GNU C Library)のような低レベルなライブラリでは、並行処理のための cas 関数が提供されていることがあります。

Goコンパイラが生成する実行ファイルは、最終的にOSのランタイム環境上で動作します。この際、もしコンパイラ内部の cas() 関数と、OSのランタイムが提供する cas 関数が同じシンボル名を持っていて、かつリンカがどちらを解決すべきか曖昧な場合、以下の問題が発生する可能性があります。

  1. リンカエラー: リンカが同じ名前のシンボルを複数見つけ、どちらをリンクすべきか判断できないため、ビルドが失敗する。
  2. 実行時エラー/不正な動作: リンカが誤ってOSのランタイムの cas 関数をリンクしてしまい、Goコンパイラが意図した cas() 関数(Case 構造体を作成する関数)とは異なる関数が呼び出される。これにより、コンパイラの動作が不正になったり、クラッシュしたりする。

特にNIXのような厳密なビルド環境では、依存関係の解決やシンボルの衝突がより顕著になることがあります。このコミットは、このような潜在的なシンボル衝突を未然に防ぐための予防的な措置であり、Goコンパイラの堅牢性とクロスプラットフォーム互換性を高めるための重要な変更と言えます。

cas() 関数がコンパイラ内部の3箇所でのみ使用されていたという記述は、変更の影響範囲が限定的であり、リファクタリングが比較的容易であったことを示しています。関数名を newcase() に変更することで、意味的な衝突を避けつつ、コンパイラの内部ロジックには影響を与えないようにしています。

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

このコミットでは、主にGoコンパイラのヘッダーファイルとCソースファイルにおいて、cas という関数名の宣言と呼び出しが newcase に変更されています。

具体的には以下のファイルが変更されています。

  • src/cmd/5c/gc.h
  • src/cmd/6c/gc.h
  • src/cmd/8c/gc.h
  • src/cmd/cc/pgen.c
  • src/cmd/cc/pswt.c

src/cmd/5c/gc.h, src/cmd/6c/gc.h, src/cmd/8c/gc.h の変更

これらのファイルは、各アーキテクチャ(Plan 9 ARM, AMD64, x86)向けのCコンパイラが使用する共通ヘッダーファイルです。ここで cas 関数のプロトタイプ宣言が newcase に変更されています。

--- a/src/cmd/5c/gc.h
+++ b/src/cmd/5c/gc.h
@@ -304,7 +304,7 @@ void	gpseudo(int, Sym*, Node*);
 int	swcmp(const void*, const void*);
 void	doswit(Node*);
 void	swit1(C1*, int, int32, Node*);
-void	cas(void);
+void	newcase(void);
 void	bitload(Node*, Node*, Node*, Node*, Node*);
 void	bitstore(Node*, Node*, Node*, Node*, Node*);
 int	mulcon(Node*, Node*);

同様の変更が src/cmd/6c/gc.hsrc/cmd/8c/gc.h にも適用されています。

src/cmd/cc/pgen.c の変更

pgen.c は、Goコンパイラのフロントエンドの一部で、パーサーが生成した構文木からコードを生成する役割を担っています。このファイル内で cas() 関数の呼び出しが newcase() に変更されています。

--- a/src/cmd/cc/pgen.c
+++ b/src/cmd/cc/pgen.c
@@ -266,7 +266,7 @@ loop:
 		if(cases == C)
 			diag(n, "case/default outside a switch");
 		if(l == Z) {
-			cas();
+			newcase();
 			cases->val = 0;
 			cases->def = 1;
 			cases->label = pc;
@@ -278,7 +278,7 @@ loop:
 		if(l->op == OCONST)
 		if(typeword[l->type->etype] && l->type->etype != TIND) {
-			cas();
+			newcase();
 			cases->val = l->vconst;
 			cases->def = 0;
 			cases->label = pc;
@@ -303,7 +303,7 @@ loop:
 
 		cn = cases;
 		cases = C;
-		cas();
+		newcase();
 
 		sbc = breakpc;
 		breakpc = pc;

このファイルでは、switch 文の処理に関連する箇所で cas() が3回呼び出されており、これらすべてが newcase() に変更されています。

src/cmd/cc/pswt.c の変更

pswt.c は、switch 文のコード生成に関連する部分です。このファイル内で cas 関数の定義が newcase に変更されています。

--- a/src/cmd/cc/pswt.c
+++ b/src/cmd/cc/pswt.c
@@ -92,7 +92,7 @@ doswit(Node *n)
 }
 
 void
-cas(void)
+newcase(void)
 {
 	Case *c;
 

このファイルでは、cas 関数の実際の定義(実装)が newcase に変更されています。

コアとなるコードの解説

このコミットの変更は、Goコンパイラの内部実装における関数名の変更という、一見すると単純なものです。しかし、その背後には、システムプログラミングにおけるシンボル解決と名前空間の衝突という重要な概念が隠されています。

gc.h の変更

gc.h ファイル群は、Goコンパイラの各アーキテクチャ固有のバックエンド(5c, 6c, 8c)と共通フロントエンド(cc)の間で共有されるヘッダーファイルです。これらのファイルで void cas(void); という関数プロトタイプが void newcase(void); に変更されたことは、コンパイラ全体で cas という名前の関数が newcase に置き換えられることを宣言しています。これは、コンパイラのビルド時にリンカが cas という名前の関数を探す際に、誤って外部のライブラリの cas を参照しないようにするための第一歩です。

pgen.c の変更

pgen.c は、Go言語のソースコードがパースされて生成された抽象構文木 (AST) を元に、中間コードや最終的な機械語を生成する過程で重要な役割を担っています。switch 文の処理において、各 case ラベルに対応する内部的なデータ構造(Case 構造体)を管理するために cas() 関数が呼び出されていました。

例えば、switch 文のデフォルトケースや、各 case の定数値を処理する際に cas() が呼び出され、新しい Case エントリが作成されていました。この cas() の呼び出しを newcase() に変更することで、コンパイラ内部のロジックはそのままに、シンボル名だけが変更されます。これにより、コンパイラが生成するバイナリや、コンパイラ自体の実行時に、外部の cas 関数との衝突を避けることができます。

pswt.c の変更

pswt.c は、switch 文のコード生成に関する具体的な実装が含まれています。このファイルで cas 関数の実際の定義が newcase に変更されたことは、gc.h で宣言されたプロトタイプと pgen.c で呼び出された関数が、実際に newcase という新しい名前で実装されることを意味します。

void cas(void) から void newcase(void) への変更は、関数のシグネチャ(引数と戻り値の型)はそのままに、名前だけを変更するリファクタリングです。これにより、この関数が内部で実行する処理(新しい Case 構造体の初期化など)は一切変わらず、単にその識別子が変わるだけです。

全体的な影響

この変更は、Goコンパイラの内部的な整合性を保ちつつ、外部環境(特にNIXランタイム)との潜在的なシンボル衝突を回避するためのものです。コンパイラ内部の関数名であるため、Go言語のユーザーが書くコードや、Go言語の標準ライブラリには直接的な影響はありません。しかし、Goコンパイラ自体のビルドや、NIX環境下でのGoプログラムのビルドの安定性向上に寄与します。

このような名前衝突の回避は、大規模なソフトウェアプロジェクトや、複数のコンポーネントが連携するシステムにおいて、安定性と互換性を維持するために不可欠なプラクティスです。

関連リンク

参考にした情報源リンク