[インデックス 1657] ファイルの概要
このコミットは、Go言語の unsafe
パッケージに Alignof
関数を実装するものです。unsafe.Alignof
は、Goの型がメモリ上でどのようにアラインされるか(メモリ境界に沿って配置されるか)を決定するために使用されます。これは、特にC言語との相互運用や、メモリレイアウトを厳密に制御する必要がある低レベルプログラミングにおいて重要な機能です。
コミット
commit 651972b31f7da9a1d522fc427a6d6693070f1cb5
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue Feb 10 11:46:26 2009 -0800
Implement unsafe.Alignof.
R=ken
DELTA=20 (19 added, 0 deleted, 1 changed)
OCL=24719
CL=24771
---
src/cmd/gc/dcl.c | 19 ++++++++++++++++++-\n src/cmd/gc/sysimport.c | 1 +\n src/cmd/gc/unsafe.go | 1 +\n 3 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index d5d3a9bf4d..35d1a8e62b 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1495,6 +1495,7 @@ unsafenmagic(Node *l, Node *r)
{\n \tNode *n;\n \tSym *s;\n+\tType *t;\n \tlong v;\n \tVal val;\n \n@@ -1519,7 +1520,23 @@ unsafenmagic(Node *l, Node *r)
\t\tif(r->op != ODOT && r->op != ODOTPTR)\n \t\t\tgoto no;\n \t\twalktype(r, Erv);\n-\t\tv = n->xoffset;\n+\t\tv = r->xoffset;\n+\t\tgoto yes;\n+\t}\n+\tif(strcmp(s->name, \"Alignof\") == 0) {\n+\t\twalktype(r, Erv);\n+\t\tif (r->type == T)\n+\t\t\tgoto no;\n+\t\t// make struct { byte; T; }\n+\t\tt = typ(TSTRUCT);\n+\t\tt->type = typ(TFIELD);\n+\t\tt->type->type = types[TUINT8];\n+\t\tt->type->down = typ(TFIELD);\n+\t\tt->type->down->type = r->type;\n+\t\t// compute struct widths\n+\t\tdowidth(t);\n+\t\t// the offset of T is its required alignment\n+\t\tv = t->type->down->width;\n \t\tgoto yes;\n \t}\n \ndiff --git a/src/cmd/gc/sysimport.c b/src/cmd/gc/sysimport.c
index 4d682d675d..ccc38343d7 100644
--- a/src/cmd/gc/sysimport.c
+++ b/src/cmd/gc/sysimport.c
@@ -67,5 +67,6 @@ char *unsafeimport =
\t\"type unsafe.Pointer *any\\n\"\n \t\"func unsafe.Offsetof (? any) (? int)\\n\"\n \t\"func unsafe.Sizeof (? any) (? int)\\n\"\n+\t\"func unsafe.Alignof (? any) (? int)\\n\"\n \t\"\\n\"\n \t\"$$\\n\";\ndiff --git a/src/cmd/gc/unsafe.go b/src/cmd/gc/unsafe.go
index 47703f6e0f..d1dcee02a8 100644
--- a/src/cmd/gc/unsafe.go
+++ b/src/cmd/gc/unsafe.go
@@ -8,3 +8,4 @@ package PACKAGE
type\tPointer\t*any;\n func\tOffsetof(any) int;\n func\tSizeof(any) int;\n+func\tAlignof(any) int;\n```
## GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/651972b31f7da9a1d522fc427a6d6693070f1cb5
## 元コミット内容
`unsafe.Alignof` を実装する。
## 変更の背景
Go言語は、C言語のような低レベルな操作を可能にするために `unsafe` パッケージを提供しています。このパッケージには、メモリのサイズを取得する `Sizeof` や、構造体内のフィールドのオフセットを取得する `Offsetof` といった関数が既に存在していました。しかし、特定の型のメモリ上でのアラインメント(配置されるメモリ境界)を取得する機能は提供されていませんでした。
メモリのアラインメントは、パフォーマンスの最適化や、異なる言語(特にC言語)で書かれたライブラリとの相互運用において非常に重要です。例えば、C言語の構造体をGoで正確に表現し、その構造体をCの関数に渡す場合、各フィールドのアラインメントが一致している必要があります。`Alignof` がない場合、開発者は手動でアラインメントを推測するか、プラットフォーム固有の知識に頼る必要があり、これはエラーの原因となりがちでした。
このコミットは、このような低レベルなメモリ操作のニーズに応えるため、`unsafe.Alignof` 関数をGoコンパイラに組み込むことを目的としています。これにより、Goプログラムが型のメモリレイアウトに関する情報をより正確に取得できるようになり、より堅牢な低レベルコードの記述が可能になります。
## 前提知識の解説
### メモリのアラインメント (Memory Alignment)
メモリのアラインメントとは、コンピュータのメモリ上でデータが特定のバイトアドレスの倍数に配置されることを指します。例えば、4バイトのアラインメントを持つデータは、アドレスが4の倍数であるメモリ位置に配置されます。
* **なぜ必要か?**:
* **パフォーマンス**: 多くのCPUは、アラインされたデータにアクセスする方が、アラインされていないデータにアクセスするよりも高速です。アラインされていないアクセスは、追加のCPUサイクルを必要としたり、場合によってはハードウェア例外を引き起こしたりすることがあります。
* **ハードウェア要件**: 一部のハードウェア(特にDMAコントローラや特定の命令セット)は、データが特定のアラインメントを持つことを要求します。
* **ポータビリティ**: 異なるアーキテクチャ間でのバイナリ互換性を確保するために、アラインメントの規則を理解することが重要です。
* **パディング (Padding)**: 構造体内のフィールドが特定のアラインメント要件を満たすように、コンパイラはフィールド間に未使用のバイト(パディング)を挿入することがあります。これにより、構造体の合計サイズが、個々のフィールドのサイズの合計よりも大きくなることがあります。
### Go言語の `unsafe` パッケージ
Go言語の `unsafe` パッケージは、Goの型システムが通常課す安全性の制約を回避するための機能を提供します。このパッケージは、Goのメモリモデルを直接操作することを可能にし、C言語のような低レベルプログラミングを可能にします。
* **`unsafe.Pointer`**: 任意の型のポインタを保持できる特殊なポインタ型です。これにより、異なる型のポインタ間で変換を行うことができます。
* **`unsafe.Sizeof(x)`**: 式 `x` が占めるメモリのバイト数を返します。これは、`x` の静的な型に基づいて計算されます。
* **`unsafe.Offsetof(x.f)`**: 構造体 `x` のフィールド `f` のオフセット(構造体の先頭からのバイト数)を返します。
`unsafe` パッケージの関数は、コンパイラによって特別に扱われる組み込み関数であり、通常のGoの関数呼び出しとは異なります。これらは、コンパイル時にメモリレイアウトに関する情報を取得するために使用されます。
### Goコンパイラ (`gc`) の構造
Goコンパイラ (`gc`) は、Goのソースコードを機械語に変換する役割を担っています。このコミットで変更されている `src/cmd/gc/dcl.c` は、Goコンパイラの宣言(declaration)処理に関連する部分です。
* **`dcl.c`**: 宣言の解析、型の解決、変数のスコープ管理など、コンパイラのフロントエンドに近い部分を担当します。`unsafe` パッケージの組み込み関数は、この段階で特別に処理されます。
* **`sysimport.c`**: Goの組み込みパッケージ(`unsafe` など)の宣言をコンパイラにインポートするための情報を含んでいます。
* **`unsafe.go`**: `unsafe` パッケージのGo言語側の定義ファイルです。これは、コンパイラが `unsafe` パッケージの関数を認識するために使用されます。
## 技術的詳細
`unsafe.Alignof` の実装は、Goコンパイラの `src/cmd/gc/dcl.c` ファイル内の `unsafenmagic` 関数に新しいロジックを追加することで行われています。この関数は、`unsafe` パッケージの組み込み関数(`Sizeof`, `Offsetof` など)の呼び出しを処理します。
`Alignof` のアラインメント値を計算するために、コンパイラは巧妙なトリックを使用します。それは、対象の型 `T` を含むダミーの構造体を一時的に作成し、その構造体内の `T` のオフセットを計算するというものです。
具体的には、`Alignof(T)` の呼び出しがあった場合、コンパイラは以下のような匿名構造体を仮想的に作成します。
```go
struct {
byte; // 1バイトのパディング用フィールド
T; // アラインメントを知りたい型
}
この構造体を作成した後、コンパイラは dowidth
関数を呼び出して、この構造体のメモリレイアウトを計算します。dowidth
は、構造体の各フィールドのオフセットと、構造体全体のサイズを決定します。
ここで重要なのは、byte
フィールド(types[TUINT8]
で表現される1バイトの型)の直後に T
が配置されることです。Goのコンパイラは、構造体のフィールドを配置する際に、そのフィールドの型が要求するアラインメントを満たすように配置します。したがって、byte
フィールドの直後に T
を配置しようとすると、T
が要求するアラインメントが、byte
フィールドの直後のアドレスから計算された T
のオフセットとして現れます。
例えば、T
が4バイトのアラインメントを要求する場合、byte
フィールドがアドレス X
に配置されたとすると、T
はアドレス X+1
から配置されることになります。しかし、T
は4バイトアラインメントを要求するため、コンパイラは byte
フィールドと T
の間に3バイトのパディングを挿入し、T
をアドレス X+4
に配置します。この X+4
から X
を引いた値、つまり 4
が T
のアラインメント値となります。
このロジックにより、t->type->down->width
(ここで t->type->down
は仮想構造体内の T
型のフィールドを表す)が、T
のアラインメント値として取得されます。
コアとなるコードの変更箇所
このコミットでは、主に以下の3つのファイルが変更されています。
-
src/cmd/gc/dcl.c
:unsafenmagic
関数内にAlignof
の処理が追加されました。strcmp(s->name, "Alignof") == 0
でAlignof
呼び出しを識別します。- ダミーの構造体
{ byte; T; }
を作成し、その中のT
のオフセットを計算することでアラインメントを取得します。 - 具体的には、
typ(TSTRUCT)
で構造体型を、typ(TFIELD)
でフィールド型を、types[TUINT8]
で1バイトの型をそれぞれ作成し、構造体を構築しています。 dowidth(t)
を呼び出して、構築した構造体の幅(メモリレイアウト)を計算させます。v = t->type->down->width;
で、T
型のフィールドのオフセット(これがアラインメント値となる)を取得しています。
-
src/cmd/gc/sysimport.c
:unsafeimport
文字列にfunc unsafe.Alignof (? any) (? int)\n
が追加されました。- これは、Goコンパイラが
unsafe.Alignof
関数を組み込み関数として認識し、そのシグネチャ(引数と戻り値の型)を理解するために必要です。
-
src/cmd/gc/unsafe.go
:func Alignof(any) int;
の宣言が追加されました。- これは、Go言語のソースコードレベルで
unsafe
パッケージにAlignof
関数が存在することを宣言するためのものです。実際の機能はコンパイラ内部で実装されます。
コアとなるコードの解説
src/cmd/gc/dcl.c
の変更箇所に焦点を当てて解説します。
if(strcmp(s->name, "Alignof") == 0) {
walktype(r, Erv);
if (r->type == T)
goto no;
// make struct { byte; T; }
t = typ(TSTRUCT);
t->type = typ(TFIELD);
t->type->type = types[TUINT8];
t->type->down = typ(TFIELD);
t->type->down->type = r->type;
// compute struct widths
dowidth(t);
// the offset of T is its required alignment
v = t->type->down->width;
goto yes;
}
-
if(strcmp(s->name, "Alignof") == 0)
:- これは、現在のシンボル
s
の名前が "Alignof" であるかどうかをチェックしています。つまり、Goコード内でunsafe.Alignof(...)
のような呼び出しがあった場合にこのブロックが実行されます。
- これは、現在のシンボル
-
walktype(r, Erv);
:r
はAlignof
に渡された引数(アラインメントを知りたい型)を表すノードです。walktype
は、その型の情報を解決し、必要に応じてエラーチェックを行います。
-
if (r->type == T) goto no;
:T
はGoコンパイラ内部で「不明な型」や「エラー型」を示す特別な型です。もし引数の型が解決できなかったり、無効な型であったりした場合は、処理をスキップします。
-
// make struct { byte; T; }
:- ここからがアラインメント計算の核心です。
t = typ(TSTRUCT);
: 新しい構造体型t
を作成します。t->type = typ(TFIELD);
: 構造体の最初のフィールドを作成します。t->type->type = types[TUINT8];
: 最初のフィールドの型をuint8
(1バイト) に設定します。これはパディングの基準となるダミーフィールドです。t->type->down = typ(TFIELD);
: 構造体の2番目のフィールドを作成します。t->type->down->type = r->type;
: 2番目のフィールドの型を、Alignof
に渡された引数r
の型に設定します。これがアラインメントを知りたい実際の型です。
-
// compute struct widths
:dowidth(t);
: 作成したダミー構造体t
のメモリレイアウト(各フィールドのオフセットと構造体全体のサイズ)を計算します。この関数が、Goのメモリレイアウト規則に従ってパディングを挿入し、オフセットを決定します。
-
// the offset of T is its required alignment
:v = t->type->down->width;
: ここで、2番目のフィールド(r->type
、つまりアラインメントを知りたい型)のオフセットを取得します。dowidth
関数が、このフィールドをその型が要求するアラインメント境界に配置するため、このオフセット値がそのままその型のアラインメント値となります。例えば、uint32
のアラインメントが4バイトであれば、byte
の直後にuint32
を配置すると、uint32
のオフセットは4になります。
-
goto yes;
:- 計算が成功したことを示し、処理を続行します。
この実装は、Goコンパイラが内部的に型のメモリレイアウトをどのように計算しているかという知識を巧みに利用しています。ダミー構造体を作成し、その中で対象の型がどのように配置されるかをシミュレートすることで、その型が要求するアラインメントを効率的に導き出しています。
関連リンク
- Go言語
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe - Go言語のメモリレイアウトに関する公式ブログ記事 (Go 1.19以降の変更も含むが、基本的な概念は共通): https://go.dev/blog/go1.19-mem
参考にした情報源リンク
- Go言語の
unsafe
パッケージに関する一般的な情報 - メモリのアラインメントに関するコンピュータサイエンスの一般的な知識
- Goコンパイラのソースコード (
src/cmd/gc/
) の構造と機能に関する一般的な理解 - Go言語のコミット履歴と関連する議論 (Goの初期開発に関する情報)
- Go言語の
unsafe.Alignof
の動作に関するStack Overflowやブログ記事などの解説