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

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

このコミットは、Go言語の初期開発段階における、組み込み関数printが配列型を扱えるようにするための変更と、それに伴うランタイム内部の配列表示関数の引数渡し方法の変更(ポインタから値渡しへ)を扱っています。

コミット

commit 9786f69f74a5fa290476774e07fb10ce8da84123
Author: Ken Thompson <ken@golang.org>
Date:   Thu Dec 18 22:17:05 2008 -0800

    print(array)
    
    R=r
    OCL=21570
    CL=21570

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

https://github.com/golang/go/commit/9786f69f74a5fa290476774e07fb10ce8da84123

元コミット内容

print(array)

変更の背景

このコミットは、Go言語の非常に初期の段階(2008年12月)に行われたもので、Go言語の組み込み関数であるprintが配列型を直接引数として受け取り、その内容を適切に表示できるようにするための機能追加が主な目的です。

当時のGo言語では、print関数は整数、文字列、ポインタ、インターフェース型などの基本的な型をサポートしていましたが、配列型(特に動的な配列、後のスライスに相当)の直接的な表示はサポートされていなかったと考えられます。デバッグや開発の利便性を向上させるため、配列の内容を簡単に確認できる機能が求められていました。

また、この変更は、Goランタイム内部で配列を扱う際の引数渡し規約の調整も含んでいます。具体的には、配列の情報を表示する内部関数sys·printarrayが、それまで配列構造体へのポインタを受け取っていたものを、配列構造体そのものを値として受け取るように変更されています。これは、Array構造体が比較的小さく、値渡しの方がコードの簡潔性やパフォーマンスの観点から有利であると判断されたため、あるいは初期のGo言語の設計思想における値渡しセマンティクスの一貫性を保つためと考えられます。

前提知識の解説

このコミットを理解するためには、以下のGo言語および関連技術の初期の概念に関する知識が必要です。

  • Go言語の組み込み関数 print: printはGo言語に最初から存在する組み込み関数で、デバッグ目的で変数の値を標準出力に出力するために使用されます。fmtパッケージが登場する以前は、これが主要なデバッグ出力手段でした。
  • Go言語の配列とスライス(初期の概念): Go言語には固定長配列と、より柔軟な可変長シーケンスであるスライスがあります。このコミットが行われた2008年当時、「動的な配列(dynamic array)」という表現が使われており、これは現在のスライスに相当する概念を指している可能性が高いです。スライスは内部的に、基盤となる配列へのポインタ、長さ(len)、容量(cap)を持つ構造体として実装されています。
  • Goコンパイラ (gc): Go言語の公式コンパイラはgcと呼ばれ、Goソースコードを機械語に変換します。コンパイラは、ソースコードの構文解析(parsing)、抽象構文木(AST)の構築、型チェック、最適化、コード生成などの段階を経て処理を行います。
  • AST (Abstract Syntax Tree): ソースコードの構造を木構造で表現したものです。コンパイラはASTを走査(walk)しながら、様々な処理を行います。
  • Goランタイム: Goプログラムの実行をサポートする低レベルのライブラリです。ガベージコレクション、スケジューリング、プリミティブなI/O操作、メモリ管理など、Goプログラムが動作するために必要な多くの機能を提供します。ランタイムはC言語やアセンブリ言語で書かれている部分が多くあります。
  • システム関数 (sys functions): Goコンパイラやランタイムが内部的に使用する、Go言語のプリミティブな操作に対応する低レベル関数群です。これらは通常、Go言語のソースコードからは直接呼び出されず、コンパイラによって生成されたコードから呼び出されます。sys.printarrayもその一つです。
  • C言語のポインタと構造体: Goランタイムの一部はC言語で書かれているため、C言語におけるポインタ(*)と構造体(struct)の概念、およびそれらのメンバーへのアクセス方法(ポインタの場合は->、値の場合は.)の理解が重要です。

技術的詳細

このコミットの技術的な変更は、GoコンパイラとGoランタイムの両方にまたがっています。

  1. コンパイラ側の変更 (src/cmd/gc/sys.go, src/cmd/gc/sysimport.c, src/cmd/gc/walk.c):

    • src/cmd/gc/sys.gosrc/cmd/gc/sysimport.cでは、Goコンパイラが認識すべきシステム関数としてprintarrayが追加されています。これは、print組み込み関数が配列型を引数として受け取った場合に、内部的にsys.printarrayを呼び出すための準備です。export func printarray(any);という宣言は、printarrayが任意の型(any)の引数を受け取れることを示唆しており、これはGoの型システムにおけるポリモーフィズムの初期の形と見なせます。
    • src/cmd/gc/walk.cは、コンパイラのASTウォーク処理を担当するファイルです。print組み込み関数の呼び出しを処理する部分に、引数が動的な配列(isdarray(l->type))である場合の新しいロジックが追加されました。このロジックは、syslook("printarray", 1)を使ってsys.printarray関数への参照を取得し、その関数に配列型の引数(argtype(on, l->type))を渡すようにコードを生成します。これにより、Goソースコードでprint(myArray)と書かれた場合に、コンパイラが適切にランタイムのsys.printarrayを呼び出すバイナリを生成できるようになります。
  2. ランタイム側の変更 (src/runtime/array.c, src/runtime/runtime.h):

    • src/runtime/runtime.hでは、sys·printarray関数のプロトタイプ宣言がvoid sys·printarray(Array*);からvoid sys·printarray(Array);に変更されています。これは、関数がArray構造体へのポインタではなく、Array構造体そのものを値として受け取ることを意味します。
    • src/runtime/array.cは、Goランタイムにおける配列関連の処理を実装しているファイルです。
      • sys·printarray関数の定義が、引数をArray *aからArray aに変更されています。
      • それに伴い、関数内部でArray構造体のメンバー(nel, cap, array)にアクセスする際の記法が、ポインタアクセス演算子->からドット演算子.に変更されています(例: a->nela.nelに)。
      • sys·newarray, sys·arraysliced, sys·arrays2dといった他のランタイム関数内でsys·printarrayを呼び出している箇所も、引数がポインタ(&ret, &old)から値(ret, old)に修正されています。

この変更は、Array構造体が比較的小さなサイズであり、値渡しによるコピーのオーバーヘッドが無視できる、あるいはポインタのデリファレンス(間接参照)を避けることでコードの簡潔性や実行効率が向上すると判断された結果であると考えられます。Go言語の設計哲学では、小さな構造体は値渡しが推奨される傾向があります。

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

src/runtime/array.csys·printarray 関数のシグネチャと内部実装の変更

--- a/src/runtime/array.c
+++ b/src/runtime/array.c
@@ -158,18 +158,18 @@ sys·arrays2d(byte* old, uint32 nel, Array ret)
 	\tprints("sys·arrays2d: old=");
 	\tsys·printpointer(old);
 	\tprints("; ret=");
-\t\tsys·printarray(&ret);
+\t\tsys·printarray(ret);
 	\tprints("\n");
 	}
 }
 
 void
-sys·printarray(Array *a)
+sys·printarray(Array a)
 {
 	prints("[");
-\tsys·printint(a->nel);
+\tsys·printint(a.nel);
 	prints("/");
-\tsys·printint(a->cap);
+\tsys·printint(a.cap);
 	prints("]");
-\tsys·printpointer(a->array);
+\tsys·printpointer(a.array);
 }

src/cmd/gc/walk.c における print 組み込み関数への配列型サポートの追加

--- a/src/cmd/gc/walk.c
+++ b/src/cmd/gc/walk.c
@@ -1936,6 +1936,11 @@ loop:
 	\t\t\targtype(on, l->type->type);\t// any-1
 	\t\t\tbreak;
 	\t\t}
+\t\tif(isdarray(l->type)) {
+\t\t\ton = syslook("printarray", 1);
+\t\t\targtype(on, l->type);\t// any-1
+\t\t\tbreak;
+\t\t}
 	\t\tbadtype(n->op, l->type, T);
 	\t\tl = listnext(&save);
 	\t\tgoto loop;

コアとなるコードの解説

  1. sys·printarray の引数変更:

    • src/runtime/array.csrc/runtime/runtime.h における sys·printarray 関数の変更は、Goランタイムが配列情報をどのように受け渡すかという、低レベルな規約の変更を示しています。
    • 以前は Array *a (配列構造体へのポインタ) を受け取っていましたが、これを Array a (配列構造体そのもの) に変更しました。
    • これにより、関数内部でのメンバーアクセスも a->nel から a.nel へと変更されています。
    • この変更は、Array構造体が小さいため、ポインタを介した間接参照よりも値渡しの方が効率的である、あるいはコードがより直接的で読みやすくなるという判断に基づいている可能性が高いです。Go言語では、小さな構造体やプリミティブ型は値渡しが一般的です。
  2. コンパイラによる print 組み込み関数への配列サポート:

    • src/cmd/gc/sys.gosrc/cmd/gc/sysimport.cprintarray がシステム関数として宣言されたことで、コンパイラがこの関数を認識できるようになりました。
    • src/cmd/gc/walk.c の変更は、Goコンパイラがprint組み込み関数の呼び出しを処理する際に、その引数が動的な配列(isdarray(l->type))であるかどうかをチェックするロジックを追加しています。
    • もし引数が動的な配列であれば、コンパイラはsyslook("printarray", 1)を使ってランタイムのsys·printarray関数への参照を取得し、その配列を引数として渡すようにコードを生成します。
    • これにより、Goのソースコードでprint(mySlice)(当時の「動的な配列」)と書くと、コンパイラが自動的にランタイムの適切な配列表示関数を呼び出すようになり、開発者は配列の内容を簡単にデバッグ出力できるようになりました。

これらの変更は、Go言語の初期段階において、基本的なデバッグ機能の強化と、ランタイム内部でのデータ構造の効率的な取り扱いを両立させるための重要なステップでした。

関連リンク

参考にした情報源リンク

  • Go言語のコミット履歴 (GitHub): https://github.com/golang/go/commits/master
  • Go言語の初期の設計に関する議論やドキュメント (当時の情報源は現在では見つけにくい可能性がありますが、Goのブログやメーリングリストのアーカイブに存在する可能性があります)
  • C言語のポインタと構造体に関する一般的なプログラミング知識。
  • コンパイラの基本的な動作原理(AST、コード生成など)に関する一般的な知識。