[インデックス 19554] ファイルの概要
このコミットは、Goランタイムにおける文字列処理、特にワイド文字(UTF-16など)からGoの内部表現(UTF-8)への変換に関連する重要な変更です。runetochar
関数の再導入とgostringw
関数でのその利用を通じて、Windowsビルドの安定性向上を目指しています。
コミット
- コミットハッシュ:
0f4b53c1c2db3ae7f3ed25ba7e5149baf14fc46c
- 作者: Keith Randall khr@golang.org
- 日付: 2014年6月17日 火曜日 00:36:23 -0700
- レビュー担当者: bradfitz, golang-codereviews, alex.brainman
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0f4b53c1c2db3ae7f3ed25ba7e5149baf14fc46c
元コミット内容
runtime: reconstitute runetochar for use by gostringw.
Fixes windows builds (hopefully).
LGTM=bradfitz
R=golang-codereviews, bradfitz, alex.brainman
CC=golang-codereviews
https://golang.org/cl/103470045
変更の背景
このコミットの主な目的は、GoランタイムにおけるWindowsビルドの問題を解決することでした。具体的には、gostringw
関数がワイド文字(Windows APIで一般的に使用されるUTF-16エンコーディング)をGoの内部文字列表現(UTF-8)に変換する際に、runetochar
関数を再利用することで、その処理をより堅牢にすることを目指しています。
しかし、このコミット、またはこれに関連する一連の変更は、GoランタイムがWindows 7やWindows Server 2008 R2などの古いWindowsオペレーティングシステムでは利用できない新しいWindows APIを利用し始めるきっかけとなりました。その結果、Go 1.21.5以降のバージョンでビルドされたGoプログラムは、これらの古いOS上では実行できなくなり、「Exception 0xc0000005」のようなアクセス違反エラーが発生するようになりました。
したがって、この変更は当時のWindowsビルドの問題を「修正する」意図があったものの、結果として古いWindows環境のサポート終了という大きな影響をもたらしました。Goプロジェクトは、Go 1.20がWindows 7およびWindows Server 2008をサポートする最後のリリースであったと公式に表明しています。
前提知識の解説
Goランタイム (Go Runtime)
Goランタイムは、Goプログラムの実行を管理する環境です。これには、ガベージコレクション、ゴルーチンのスケジューリング、メモリ管理、システムコールとのインターフェースなどが含まれます。Goプログラムは、コンパイル時にランタイムが静的にリンクされるため、外部の依存関係なしに実行可能なバイナリが生成されます。
Unicode、UTF-8、UTF-16
- Unicode: 世界中の文字を統一的に扱うための文字コードの国際標準です。各文字には一意のコードポイント(数値)が割り当てられています。
- UTF-8: Unicodeのコードポイントをバイト列にエンコードするための可変長エンコーディング方式です。ASCII互換性があり、英語圏の文字は1バイト、日本語などの文字は3バイト、絵文字などは4バイトで表現されます。Goの文字列は内部的にUTF-8でエンコードされています。
- UTF-16: Unicodeのコードポイントを16ビット単位でエンコードする方式です。Windows APIでは、ワイド文字(
wchar_t
)としてUTF-16が広く使用されています。
Goにおけるrune
Go言語において、rune
はint32
のエイリアスであり、Unicodeのコードポイントを表します。Goの文字列はUTF-8バイト列ですが、for range
ループで文字列をイテレートすると、各要素はrune
(Unicodeコードポイント)として扱われます。
runetochar
関数
この関数は、Unicodeのコードポイント(rune
)をUTF-8バイト列に変換する役割を担います。Goの内部で文字列処理を行う上で非常に基本的な操作です。
gostringw
関数
この関数は、Goランタイム内部で、ワイド文字(通常はUTF-16)のバイト列をGoの標準的なUTF-8文字列に変換するために使用されます。Windows APIから返される文字列などをGoの文字列として扱う際に必要となります。
Windows API
Windowsオペレーティングシステムが提供するアプリケーションプログラミングインターフェース(API)の集合です。ファイル操作、ネットワーク通信、GUI表示など、Windows上で動作するアプリケーションがシステム機能を利用するための窓口となります。多くのWindows APIは、文字列をワイド文字(UTF-16)で扱います。
技術的詳細
このコミットの技術的な核心は、Goランタイムのsrc/pkg/runtime/string.c
ファイルにおけるrunetochar
関数の再導入と、そのgostringw
関数での利用方法の変更です。
-
runetochar
関数の再導入: 以前はruntime·runetochar
としてグローバルに利用されていた可能性のあるrunetochar
関数が、static int32 runetochar(byte *str, int32 rune)
としてstring.c
内にローカル関数として再実装されました。この関数は、与えられたrune
(Unicodeコードポイント)をUTF-8バイト列に変換し、そのバイト数を返します。- UTF-8エンコーディングロジック:
- 1バイト文字 (0x0000-0x007F):
0xxxxxxx
- 2バイト文字 (0x0080-0x07FF):
110xxxxx 10xxxxxx
- 3バイト文字 (0x0800-0xFFFF):
1110xxxx 10xxxxxx 10xxxxxx
- 4バイト文字 (0x10000-0x10FFFF):
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
このロジックは、Bit1
,Bitx
,Bit2
,Bit3
,Bit4
,Bit5
といったビットマスク定数と、Tx
,T2
,T3
,T4
といったUTF-8のプレフィックスバイト、そしてRune1
,Rune2
,Rune3
といったコードポイントの範囲定数を用いて実装されています。 また、無効なUnicodeコードポイント(Runemax
を超える値)やサロゲートペア(SurrogateMin
からSurrogateMax
の範囲)は、Runeerror
(0xFFFD) に変換されるようになっています。これは、UTF-8の仕様に準拠し、不正な入力に対する堅牢性を高めるための処理です。
- 1バイト文字 (0x0000-0x007F):
- UTF-8エンコーディングロジック:
-
gostringw
関数の変更:runtime·gostringw
関数は、uint16
の配列として渡されるワイド文字文字列をGoのString
型に変換します。この変換処理の中で、各ワイド文字(str[i]
)をUTF-8バイト列に変換するために、新しく再導入されたローカルなrunetochar
関数を呼び出すように変更されました。 以前はruntime·runetochar
というグローバルな関数を呼び出していた箇所が、単にrunetochar
と記述されることで、string.c
内で定義されたローカル関数が使用されるようになりました。これにより、関数のスコープが限定され、モジュール性が向上します。 -
テスト関連ファイルの変更:
src/pkg/runtime/export_test.go
:gostringW
関数がテストのためにエクスポートされるようになりました。これにより、ランタイムの内部関数であるgostringw
をGoのテストコードから呼び出して検証することが可能になります。src/pkg/runtime/string_test.go
:TestStringW
という新しいテスト関数が追加されました。このテストは、様々な文字列(ASCII、マルチバイト文字を含む)をワイド文字バイト列に変換し、runtime.GostringW
を呼び出してGo文字列に戻し、元の文字列と一致するかどうかを検証します。これにより、gostringw
とrunetochar
の組み合わせが正しく機能することを確認します。src/pkg/runtime/stubs.goc
:gostringW
というGoから呼び出し可能なスタブ関数が追加されました。これは、GoのテストコードからCで実装されたruntime·gostringw
を呼び出すためのブリッジとして機能します。
これらの変更は、GoランタイムがWindows環境で文字列をより正確かつ効率的に処理するための基盤を強化するものです。特に、ワイド文字とUTF-8間の変換ロジックの正確性は、Windowsアプリケーションとの相互運用性において極めて重要です。
コアとなるコードの変更箇所
主な変更は以下のファイルに集中しています。
src/pkg/runtime/string.c
:runetochar
関数の再導入とgostringw
での利用。src/pkg/runtime/export_test.go
:gostringW
のテスト用エクスポート。src/pkg/runtime/string_test.go
:gostringw
の動作を検証する新しいテストケースの追加。src/pkg/runtime/stubs.goc
:gostringW
のGo-Cブリッジスタブの追加。
特に、src/pkg/runtime/string.c
におけるrunetochar
関数の定義と、runtime·gostringw
関数内のruntime·runetochar
からrunetochar
への呼び出し箇所の変更がコアとなります。
--- a/src/pkg/runtime/string.c
+++ b/src/pkg/runtime/string.c
@@ -100,6 +100,94 @@ runtime·gostringnocopy(byte *str)
return s;
}
+// TODO: move this elsewhere
+enum
+{
+ Bit1 = 7,
+ Bitx = 6,
+ Bit2 = 5,
+ Bit3 = 4,
+ Bit4 = 3,
+ Bit5 = 2,
+
+ Tx = ((1<<(Bitx+1))-1) ^ 0xFF, /* 1000 0000 */
+ T2 = ((1<<(Bit2+1))-1) ^ 0xFF, /* 1100 0000 */
+ T3 = ((1<<(Bit3+1))-1) ^ 0xFF, /* 1110 0000 */
+ T4 = ((1<<(Bit4+1))-1) ^ 0xFF, /* 1111 0000 */
+
+ Rune1 = (1<<(Bit1+0*Bitx))-1, /* 0000 0000 0111 1111 */
+ Rune2 = (1<<(Bit2+1*Bitx))-1, /* 0000 0111 1111 1111 */
+ Rune3 = (1<<(Bit3+2*Bitx))-1, /* 1111 1111 1111 1111 */
+
+ Maskx = (1<<Bitx)-1, /* 0011 1111 */
+
+ Runeerror = 0xFFFD,
+
+ SurrogateMin = 0xD800,
+ SurrogateMax = 0xDFFF,
+
+ Runemax = 0x10FFFF, /* maximum rune value */
+};
+
+static int32
+runetochar(byte *str, int32 rune) /* note: in original, arg2 was pointer */
+{
+ /* Runes are signed, so convert to unsigned for range check. */
+ uint32 c;
+
+ /*
+ * one character sequence
+ * 00000-0007F => 00-7F
+ */
+ c = rune;
+ if(c <= Rune1) {
+ str[0] = c;
+ return 1;
+ }
+
+ /*
+ * two character sequence
+ * 0080-07FF => T2 Tx
+ */
+ if(c <= Rune2) {
+ str[0] = T2 | (c >> 1*Bitx);
+ str[1] = Tx | (c & Maskx);
+ return 2;
+ }
+
+ /*
+ * If the Rune is out of range or a surrogate half, convert it to the error rune.
+ * Do this test here because the error rune encodes to three bytes.
+ * Doing it earlier would duplicate work, since an out of range
+ * Rune wouldn't have fit in one or two bytes.
+ */
+ if (c > Runemax)
+ c = Runeerror;
+ if (SurrogateMin <= c && c <= SurrogateMax)
+ c = Runeerror;
+
+ /*
+ * three character sequence
+ * 0800-FFFF => T3 Tx Tx
+ */
+ if (c <= Rune3) {
+ str[0] = T3 | (c >> 2*Bitx);
+ str[1] = Tx | ((c >> 1*Bitx) & Maskx);
+ str[2] = Tx | (c & Maskx);
+ return 3;
+ }
+
+ /*
+ * four character sequence (21-bit value)
+ * 10000-1FFFFF => T4 Tx Tx Tx
+ */
+ str[0] = T4 | (c >> 3*Bitx);
+ str[1] = Tx | ((c >> 2*Bitx) & Maskx);
+ str[2] = Tx | ((c >> 1*Bitx) & Maskx);
+ str[3] = Tx | (c & Maskx);
+ return 4;
+}
+
String
runtime·gostringw(uint16 *str)
{
n1 = 0;
for(i=0; str[i]; i++)
- n1 += runtime·runetochar(buf, str[i]);
+ n1 += runetochar(buf, str[i]);
s = gostringsize(n1+4);
n2 = 0;
for(i=0; str[i]; i++) {
// check for race
if(n2 >= n1)
break;
- n2 += runtime·runetochar(s.str+n2, str[i]);
+ n2 += runetochar(s.str+n2, str[i]);
}
s.len = n2;
s.str[s.len] = 0;
コアとなるコードの解説
runetochar
関数の詳細
runetochar
関数は、Goのrune
(int32
)をUTF-8バイト列に変換し、そのバイト数を返します。
static int32
runetochar(byte *str, int32 rune)
{
uint32 c; // runeを符号なし整数として扱うための変数
c = rune; // runeの値をcにコピー
// 1バイト文字の処理 (0x0000-0x007F)
// ASCII文字は1バイトで表現される
if(c <= Rune1) { // Rune1 = 0x7F (127)
str[0] = c; // そのままバイトとして格納
return 1; // 1バイト消費
}
// 2バイト文字の処理 (0x0080-0x07FF)
if(c <= Rune2) { // Rune2 = 0x7FF (2047)
str[0] = T2 | (c >> 1*Bitx); // 最初のバイト: 110xxxxx (T2 = 0xC0) と上位ビット
str[1] = Tx | (c & Maskx); // 2番目のバイト: 10xxxxxx (Tx = 0x80) と下位ビット
return 2; // 2バイト消費
}
// 無効なRuneやサロゲートペアの処理
// Runemax (0x10FFFF) を超える値、またはサロゲートペアの範囲 (0xD800-0xDFFF) にある場合は、
// エラーRune (0xFFFD) に変換する。
// エラーRuneは3バイトでエンコードされるため、この段階でチェックすることで重複処理を避ける。
if (c > Runemax)
c = Runeerror; // Runemaxを超える場合はエラーRuneに
if (SurrogateMin <= c && c <= SurrogateMax)
c = Runeerror; // サロゲートペアの範囲の場合はエラーRuneに
// 3バイト文字の処理 (0x0800-0xFFFF)
if (c <= Rune3) { // Rune3 = 0xFFFF (65535)
str[0] = T3 | (c >> 2*Bitx); // 最初のバイト: 1110xxxx (T3 = 0xE0) と上位ビット
str[1] = Tx | ((c >> 1*Bitx) & Maskx); // 2番目のバイト: 10xxxxxx と中間ビット
str[2] = Tx | (c & Maskx); // 3番目のバイト: 10xxxxxx と下位ビット
return 3; // 3バイト消費
}
// 4バイト文字の処理 (0x10000-0x10FFFF)
// 21ビット値 (Unicodeの最大値)
str[0] = T4 | (c >> 3*Bitx); // 最初のバイト: 11110xxx (T4 = 0xF0) と上位ビット
str[1] = Tx | ((c >> 2*Bitx) & Maskx); // 2番目のバイト: 10xxxxxx と中間ビット
str[2] = Tx | ((c >> 1*Bitx) & Maskx); // 3番目のバイト: 10xxxxxx と中間ビット
str[3] = Tx | (c & Maskx); // 4番目のバイト: 10xxxxxx と下位ビット
return 4; // 4バイト消費
}
この関数は、入力されたrune
の値に基づいて、UTF-8のエンコーディング規則に従って1バイトから4バイトのバイト列をstr
ポインタが指すメモリに書き込み、書き込んだバイト数を返します。
runtime·gostringw
関数の変更点
runtime·gostringw
関数は、uint16
の配列として渡されるワイド文字文字列をGoのString
型に変換します。
String
runtime·gostringw(uint16 *str)
{
// ... (前略) ...
n1 = 0;
for(i=0; str[i]; i++)
n1 += runetochar(buf, str[i]); // ここでローカルなrunetocharを呼び出す
s = gostringsize(n1+4);
n2 = 0;
for(i=0; str[i]; i++) {
// check for race
if(n2 >= n1)
break;
n2 += runetochar(s.str+n2, str[i]); // ここでもローカルなrunetocharを呼び出す
}
s.len = n2;
s.str[s.len] = 0;
}
変更前はruntime·runetochar
というグローバルな関数を呼び出していた箇所が、このコミットによってrunetochar
というローカルな関数を呼び出すように修正されました。これにより、gostringw
はstring.c
内で定義されたUTF-8変換ロジックを直接利用するようになり、コードの自己完結性が高まりました。
この変更は、GoランタイムがWindows環境でワイド文字をGoの内部文字列に変換する際の正確性と効率性を向上させることを目的としています。
関連リンク
- Gerrit Change-ID: https://golang.org/cl/103470045
- GitHub Commit: https://github.com/golang/go/commit/0f4b53c1c2db3ae7f3ed25ba7e5149baf14fc46c
参考にした情報源リンク
- Go 1.21.5以降のWindows 7/2008 R2サポート終了に関する情報:
- https://go.dev/doc/go1.20#windows
- https://github.com/golang/go/issues/64800
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQEePlig2YsBE4faHkMesbvaajjXeCPHBbvPvSqX1TnYNh7lc460XnO_UgqmewC6sT9RWAuXlOy-Kp6lcFyNieIC5I1XHHXi2a_fZcHJi-sZjbajxZwA
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQGRXI00nNwtcN7qek8hmsUxuIO5hu6bbvBgcgdFpG7jKkt5m7shL96NgDR3-QGNPv1X07xjkACTDrNclX7SLt84f3CGQwqITsxrXZhcEZ5dq7mIhKLBPBRQc7eorEjlXSNDc_4r