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

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

このコミットは、Goコンパイラの型宣言と型チェックに関連する部分、具体的にはsrc/cmd/gc/dcl.cファイルに対する変更です。このファイルは、Go言語のコンパイラ(gc)の一部であり、宣言(declarations)の処理、特に型の解決や式の評価において重要な役割を担っています。

コミット

fix unsafe.Sizeof("abc")

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

https://github.com/golang/go/commit/3c0fc400fb1f8f43b855cd663d87e5f091d90bbf

元コミット内容

fix unsafe.Sizeof("abc")

R=rsc
OCL=25105
CL=25105

変更の背景

このコミットは、Go言語のunsafeパッケージに含まれるSizeof関数が、文字列リテラルに対して正しく動作しないというバグを修正するために行われました。

Go言語において、unsafe.Sizeofは任意の型の変数が占めるメモリサイズをバイト単位で返します。しかし、初期の実装では、unsafe.Sizeof("abc")のように文字列リテラルを引数として渡した場合、コンパイラがその文字列リテラルの型を正しく認識せず、誤ったサイズを返す可能性がありました。

具体的には、文字列リテラルはコンパイル時に定数として扱われますが、Sizeofが期待するのは特定の型の情報です。この不整合により、コンパイラが文字列リテラルを一般的なリテラルとして処理し、その結果、Sizeofが文字列型(string)の実際のサイズ(ポインタと長さの2ワード)ではなく、リテラル自体の内部表現のサイズを誤って計算してしまう問題が発生していました。

この修正は、unsafe.Sizeofunsafe.Offsetofunsafe.Alignofといったunsafeパッケージの関数が、文字列リテラルを引数として受け取った際に、それがGoの組み込み型であるstring型として扱われるように、コンパイラの型推論ロジックを調整することを目的としています。これにより、これらの関数が文字列リテラルに対しても期待通りの結果を返すようになります。

前提知識の解説

unsafeパッケージ

Go言語のunsafeパッケージは、Goの型安全性とメモリ安全性の制約を意図的に回避するための機能を提供します。これにより、低レベルのメモリ操作や、Goの型システムでは表現できないような操作が可能になります。しかし、その名の通り「unsafe(安全でない)」であり、誤用するとプログラムのクラッシュや未定義の動作を引き起こす可能性があります。主に、C言語との連携、特定のパフォーマンス最適化、またはGoのランタイム内部の操作など、特殊なケースでのみ使用されます。

unsafeパッケージには以下の主要な関数があります。

  • unsafe.Sizeof(x interface{}) uintptr: 引数xが占めるメモリのバイト数を返します。これは、xの静的な型に基づいて計算され、実行時の値には依存しません。
  • unsafe.Offsetof(x interface{}) uintptr: 構造体のフィールドxが、その構造体の先頭からどれだけオフセットしているか(バイト単位)を返します。
  • unsafe.Alignof(x interface{}) uintptr: 引数xの型が要求するアライメント(メモリ配置の制約)をバイト単位で返します。

Goの型システムと文字列の内部表現

Go言語は静的型付け言語であり、すべての変数にはコンパイル時に型が決定されます。string型はGoの組み込み型であり、不変のバイトシーケンスを表します。Goの文字列は内部的には、データへのポインタと文字列の長さ(バイト数)の2つの要素で構成される構造体として表現されます。したがって、64ビットシステムでは通常16バイト(ポインタ8バイト + 長さ8バイト)を占めます。

Goコンパイラ (gc) と dcl.c

Go言語の公式コンパイラはgcと呼ばれます。gcは、Goのソースコードを機械語に変換するプロセスにおいて、字句解析、構文解析、型チェック、最適化、コード生成などの複数のフェーズを実行します。

src/cmd/gc/dcl.cファイルは、gcコンパイラの「宣言(declarations)」フェーズに関連するコードを含んでいます。このフェーズでは、変数、関数、型などの宣言が処理され、それらのスコープ、型、およびその他の属性が解決されます。特に、unsafeパッケージの関数呼び出しのように、コンパイラが特殊な処理を行う必要がある組み込み関数やマジック関数(コンパイラが特別に扱う関数)の処理ロジックもこのファイルに含まれることがあります。

技術的詳細

このコミットの技術的な核心は、src/cmd/gc/dcl.cファイル内のunsafenmagic関数にあります。この関数は、unsafe.Sizeofunsafe.Offsetofunsafe.Alignofといったunsafeパッケージの「マジック関数」の呼び出しをコンパイル時に処理します。

変更前は、unsafe.Sizeofunsafe.Alignofに文字列リテラル(例: "abc")が渡された場合、コンパイラは引数rの型(r->type)を直接参照していました。しかし、文字列リテラルはコンパイル時にはOLITERAL(リテラルオペレーション)として扱われ、そのval.ctype(定数型)はCTSTR(文字列定数)となります。この時点でのr->typeは、必ずしもstring型を指しているとは限りませんでした。そのため、SizeofAlignofが文字列リテラルの「実際の型」であるstringのサイズやアライメントではなく、リテラル自体の内部的なコンパイラ表現のサイズを誤って計算してしまう問題がありました。

この修正では、trという新しいTypeポインタ変数を導入し、引数rの型を決定するロジックを改善しています。

  1. まず、tr = r->type;として、引数rの現在の型をtrに代入します。
  2. 次に、if(r->op == OLITERAL && r->val.ctype == CTSTR)という条件で、rが文字列リテラルであるかどうかをチェックします。
  3. もしrが文字列リテラルであれば、tr = types[TSTRING];として、trを明示的にGoの組み込みstring型(types[TSTRING]はコンパイラ内部でstring型を表す定数)に設定します。
  4. その後の処理(v = tr->width;など)では、このtr変数を使用することで、文字列リテラルが渡された場合でも、常にstring型の正しいサイズやアライメントが計算されるようになります。

これにより、unsafe.Sizeof("abc")は、文字列リテラルの内容に関わらず、Goのstring型が占めるメモリサイズ(通常16バイト)を正しく返すようになります。同様の修正がAlignofにも適用され、文字列リテラルのアライメントも正しく計算されるようになりました。

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

diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index 1f053b6114..fc977eba20 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1525,7 +1525,7 @@ unsafenmagic(Node *l, Node *r)
 {
 	Node *n;
 	Sym *s;
-	Type *t;\n+	Type *t, *tr;
 	long v;
 	Val val;
 
@@ -1541,9 +1541,12 @@ unsafenmagic(Node *l, Node *r)
 
 	if(strcmp(s->name, "Sizeof") == 0) {
 		walktype(r, Erv);
-		if(r->type == T)
+		tr = r->type;
+		if(r->op == OLITERAL && r->val.ctype == CTSTR)
+			tr = types[TSTRING];
+		if(tr == T)
 			goto no;
-		v = r->type->width;
+		v = tr->width;
 		goto yes;
 	}
 	if(strcmp(s->name, "Offsetof") == 0) {
@@ -1555,16 +1558,21 @@ unsafenmagic(Node *l, Node *r)
 	}
 	if(strcmp(s->name, "Alignof") == 0) {
 		walktype(r, Erv);
-		if (r->type == T)
+		tr = r->type;
+		if(r->op == OLITERAL && r->val.ctype == CTSTR)
+			tr = types[TSTRING];
+		if(tr == T)
 			goto no;
+\n 		// 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;
+		t->type->down->type = tr;
 		// compute struct widths
 		dowidth(t);
+\n 		// the offset of T is its required alignment
 		v = t->type->down->width;
 		goto yes;
 }

コアとなるコードの解説

変更の中心は、unsafenmagic関数内でSizeofAlignofの処理を行う部分です。

  1. Type *t, *tr;: trという新しいTypeポインタ変数が追加されました。これは、引数の型を一時的に保持し、必要に応じて調整するために使用されます。
  2. tr = r->type;: SizeofAlignofの処理の冒頭で、まず引数rの現在の型をtrに代入します。
  3. if(r->op == OLITERAL && r->val.ctype == CTSTR): ここが重要な変更点です。
    • r->op == OLITERAL: rがリテラル(定数)であることをチェックします。
    • r->val.ctype == CTSTR: そのリテラルが文字列定数であることをチェックします。
    • この条件が真の場合、つまり引数が文字列リテラルである場合、tr = types[TSTRING];が実行されます。これは、trをGoの組み込みstring型に強制的に設定することを意味します。これにより、文字列リテラルがunsafe.Sizeofunsafe.Alignofに渡された場合でも、コンパイラはそれをstring型として正しく扱います。
  4. if(tr == T): 型が不明な場合(Tはコンパイラ内部で「不明な型」を表すことが多い)はエラー処理(goto no;)に移行します。このチェックは、trが正しく設定された後に行われます。
  5. v = tr->width;: Sizeofの場合、trwidth(サイズ)が計算に使用されます。
  6. t->type->down->type = tr;: Alignofの場合、アライメント計算のために一時的に作成される構造体のフィールド型としてtrが使用されます。

この修正により、文字列リテラルがunsafe.Sizeofunsafe.Alignofに渡された際に、コンパイラがそのリテラルをstring型として正しく解釈し、期待されるサイズやアライメントを返すようになりました。

関連リンク

参考にした情報源リンク

  • Go言語 unsafe パッケージのドキュメント: https://pkg.go.dev/unsafe
  • Go言語の文字列に関する公式ブログ記事: https://go.dev/blog/strings
  • Goコンパイラのソースコード (GoのGitHubリポジトリ): https://github.com/golang/go
  • Go言語のコンパイラに関する一般的な情報 (必要に応じて検索): "Go compiler internals", "Go gc dcl.c" など
  • Go言語の型システムに関する一般的な情報 (必要に応じて検索): "Go type system"```markdown

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

このコミットは、Goコンパイラの型宣言と型チェックに関連する部分、具体的にはsrc/cmd/gc/dcl.cファイルに対する変更です。このファイルは、Go言語のコンパイラ(gc)の一部であり、宣言(declarations)の処理、特に型の解決や式の評価において重要な役割を担っています。

コミット

fix unsafe.Sizeof("abc")

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

https://github.com/golang/go/commit/3c0fc400fb1f8f43b855cd663d87e5f091d90bbf

元コミット内容

fix unsafe.Sizeof("abc")

R=rsc
OCL=25105
CL=25105

変更の背景

このコミットは、Go言語のunsafeパッケージに含まれるSizeof関数が、文字列リテラルに対して正しく動作しないというバグを修正するために行われました。

Go言語において、unsafe.Sizeofは任意の型の変数が占めるメモリサイズをバイト単位で返します。しかし、初期の実装では、unsafe.Sizeof("abc")のように文字列リテラルを引数として渡した場合、コンパイラがその文字列リテラルの型を正しく認識せず、誤ったサイズを返す可能性がありました。

具体的には、文字列リテラルはコンパイル時に定数として扱われますが、Sizeofが期待するのは特定の型の情報です。この不整合により、コンパイラが文字列リテラルを一般的なリテラルとして処理し、その結果、Sizeofが文字列型(string)の実際のサイズ(ポインタと長さの2ワード)ではなく、リテラル自体の内部表現のサイズを誤って計算してしまう問題が発生していました。

この修正は、unsafe.Sizeofunsafe.Offsetofunsafe.Alignofといったunsafeパッケージの関数が、文字列リテラルを引数として受け取った際に、それがGoの組み込み型であるstring型として扱われるように、コンパイラの型推論ロジックを調整することを目的としています。これにより、これらの関数が文字列リテラルに対しても期待通りの結果を返すようになります。

前提知識の解説

unsafeパッケージ

Go言語のunsafeパッケージは、Goの型安全性とメモリ安全性の制約を意図的に回避するための機能を提供します。これにより、低レベルのメモリ操作や、Goの型システムでは表現できないような操作が可能になります。しかし、その名の通り「unsafe(安全でない)」であり、誤用するとプログラムのクラッシュや未定義の動作を引き起こす可能性があります。主に、C言語との連携、特定のパフォーマンス最適化、またはGoのランタイム内部の操作など、特殊なケースでのみ使用されます。

unsafeパッケージには以下の主要な関数があります。

  • unsafe.Sizeof(x interface{}) uintptr: 引数xが占めるメモリのバイト数を返します。これは、xの静的な型に基づいて計算され、実行時の値には依存しません。
  • unsafe.Offsetof(x interface{}) uintptr: 構造体のフィールドxが、その構造体の先頭からどれだけオフセットしているか(バイト単位)を返します。
  • unsafe.Alignof(x interface{}) uintptr: 引数xの型が要求するアライメント(メモリ配置の制約)をバイト単位で返します。

Goの型システムと文字列の内部表現

Go言語は静的型付け言語であり、すべての変数にはコンパイル時に型が決定されます。string型はGoの組み込み型であり、不変のバイトシーケンスを表します。Goの文字列は内部的には、データへのポインタと文字列の長さ(バイト数)の2つの要素で構成される構造体として表現されます。したがって、64ビットシステムでは通常16バイト(ポインタ8バイト + 長さ8バイト)を占めます。

Goコンパイラ (gc) と dcl.c

Go言語の公式コンパイラはgcと呼ばれます。gcは、Goのソースコードを機械語に変換するプロセスにおいて、字句解析、構文解析、型チェック、最適化、コード生成などの複数のフェーズを実行します。

src/cmd/gc/dcl.cファイルは、gcコンパイラの「宣言(declarations)」フェーズに関連するコードを含んでいます。このフェーズでは、変数、関数、型などの宣言が処理され、それらのスコープ、型、およびその他の属性が解決されます。特に、unsafeパッケージの関数呼び出しのように、コンパイラが特殊な処理を行う必要がある組み込み関数やマジック関数(コンパイラが特別に扱う関数)の処理ロジックもこのファイルに含まれることがあります。

技術的詳細

このコミットの技術的な核心は、src/cmd/gc/dcl.cファイル内のunsafenmagic関数にあります。この関数は、unsafe.Sizeofunsafe.Offsetofunsafe.Alignofといったunsafeパッケージの「マジック関数」の呼び出しをコンパイル時に処理します。

変更前は、unsafe.Sizeofunsafe.Alignofに文字列リテラル(例: "abc")が渡された場合、コンパイラは引数rの型(r->type)を直接参照していました。しかし、文字列リテラルはコンパイル時にはOLITERAL(リテラルオペレーション)として扱われ、そのval.ctype(定数型)はCTSTR(文字列定数)となります。この時点でのr->typeは、必ずしもstring型を指しているとは限りませんでした。そのため、SizeofAlignofが文字列リテラルの「実際の型」であるstringのサイズやアライメントではなく、リテラル自体の内部的なコンパイラ表現のサイズを誤って計算してしまう問題がありました。

この修正では、trという新しいTypeポインタ変数を導入し、引数rの型を決定するロジックを改善しています。

  1. まず、tr = r->type;として、引数rの現在の型をtrに代入します。
  2. 次に、if(r->op == OLITERAL && r->val.ctype == CTSTR)という条件で、rが文字列リテラルであるかどうかをチェックします。
  3. もしrが文字列リテラルであれば、tr = types[TSTRING];として、trを明示的にGoの組み込みstring型(types[TSTRING]はコンパイラ内部でstring型を表す定数)に設定します。
  4. その後の処理(v = tr->width;など)では、このtr変数を使用することで、文字列リテラルが渡された場合でも、常にstring型の正しいサイズやアライメントが計算されるようになります。

これにより、unsafe.Sizeof("abc")は、文字列リテラルの内容に関わらず、Goのstring型が占めるメモリサイズ(通常16バイト)を正しく返すようになります。同様の修正がAlignofにも適用され、文字列リテラルのアライメントも正しく計算されるようになりました。

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

diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c
index 1f053b6114..fc977eba20 100644
--- a/src/cmd/gc/dcl.c
+++ b/src/cmd/gc/dcl.c
@@ -1525,7 +1525,7 @@ unsafenmagic(Node *l, Node *r)
 {
 	Node *n;
 	Sym *s;
-	Type *t;\n+	Type *t, *tr;
 	long v;
 	Val val;
 
@@ -1541,9 +1541,12 @@ unsafenmagic(Node *l, Node *r)
 
 	if(strcmp(s->name, "Sizeof") == 0) {
 		walktype(r, Erv);
-		if(r->type == T)
+		tr = r->type;
+		if(r->op == OLITERAL && r->val.ctype == CTSTR)
+			tr = types[TSTRING];
+		if(tr == T)
 			goto no;
-		v = r->type->width;
+		v = tr->width;
 		goto yes;
 	}
 	if(strcmp(s->name, "Offsetof") == 0) {
@@ -1555,16 +1558,21 @@ unsafenmagic(Node *l, Node *r)
 	}
 	if(strcmp(s->name, "Alignof") == 0) {
 		walktype(r, Erv);
-		if (r->type == T)
+		tr = r->type;
+		if(r->op == OLITERAL && r->val.ctype == CTSTR)
+			tr = types[TSTRING];
+		if(tr == T)
 			goto no;
+\n 		// 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;
+		t->type->down->type = tr;
 		// compute struct widths
 		dowidth(t);
+\n 		// the offset of T is its required alignment
 		v = t->type->down->width;
 		goto yes;
 }

コアとなるコードの解説

変更の中心は、unsafenmagic関数内でSizeofAlignofの処理を行う部分です。

  1. Type *t, *tr;: trという新しいTypeポインタ変数が追加されました。これは、引数の型を一時的に保持し、必要に応じて調整するために使用されます。
  2. tr = r->type;: SizeofAlignofの処理の冒頭で、まず引数rの現在の型をtrに代入します。
  3. if(r->op == OLITERAL && r->val.ctype == CTSTR): ここが重要な変更点です。
    • r->op == OLITERAL: rがリテラル(定数)であることをチェックします。
    • r->val.ctype == CTSTR: そのリテラルが文字列定数であることをチェックします。
    • この条件が真の場合、つまり引数が文字列リテラルである場合、tr = types[TSTRING];が実行されます。これは、trをGoの組み込みstring型に強制的に設定することを意味します。これにより、文字列リテラルがunsafe.Sizeofunsafe.Alignofに渡された場合でも、コンパイラはそれをstring型として正しく扱います。
  4. if(tr == T): 型が不明な場合(Tはコンパイラ内部で「不明な型」を表すことが多い)はエラー処理(goto no;)に移行します。このチェックは、trが正しく設定された後に行われます。
  5. v = tr->width;: Sizeofの場合、trwidth(サイズ)が計算に使用されます。
  6. t->type->down->type = tr;: Alignofの場合、アライメント計算のために一時的に作成される構造体のフィールド型としてtrが使用されます。

この修正により、文字列リテラルがunsafe.Sizeofunsafe.Alignofに渡された際に、コンパイラがそのリテラルをstring型として正しく解釈し、期待されるサイズやアライメントを返すようになりました。

関連リンク

参考にした情報源リンク

  • Go言語 unsafe パッケージのドキュメント: https://pkg.go.dev/unsafe
  • Go言語の文字列に関する公式ブログ記事: https://go.dev/blog/strings
  • Goコンパイラのソースコード (GoのGitHubリポジトリ): https://github.com/golang/go
  • Go言語のコンパイラに関する一般的な情報 (必要に応じて検索): "Go compiler internals", "Go gc dcl.c" など
  • Go言語の型システムに関する一般的な情報 (必要に応じて検索): "Go type system"