[インデックス 18821] ファイルの概要
このコミットは、GoランタイムにおけるGoogle Native Client (NaCl) 関連の修正を扱っています。具体的には、runtime.h
におけるuintreg
型の定義の修正と、stack.c
におけるuintreg
の型がamd64p32
環境で32ビットになることによる警告への対処が主な内容です。
コミット
commit 6431be3fe414a73c84da6ef0777e04b4afadede0
Author: Dave Cheney <dave@cheney.net>
Date: Tue Mar 11 14:43:10 2014 +1100
runtime: more Native Client fixes
Thanks to Ian for spotting these.
runtime.h: define uintreg correctly.
stack.c: address warning caused by the type of uintreg being 32 bits on amd64p32.
Commentary (mainly for my own use)
nacl/amd64p32 defines a machine with 64bit registers, but address space is limited to a 4gb window (the window is placed randomly inside the full 48 bit virtual address space of a process). To cope with this 6c defines _64BIT and _64BITREG.
_64BITREG is always defined by 6c, so both GOARCH=amd64 and GOARCH=amd64p32 use 64bit wide registers.
However _64BIT itself is only defined when 6c is compiling for amd64 targets. The definition is elided for amd64p32 environments causing int, uint and other arch specific types to revert to their 32bit definitions.
LGTM=iant
R=iant, rsc, remyoudompheng
CC=golang-codereviews
https://golang.org/cl/72860046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6431be3fe414a73c84da6ef0777e04b4afadede0
元コミット内容
このコミットは、GoランタイムがGoogle Native Client (NaCl) 環境で正しく動作するための追加修正です。具体的には、runtime.h
ファイル内のuintreg
型の定義が、amd64p32
アーキテクチャにおいて誤っていた点を修正し、それに伴いstack.c
ファイルで発生していた型に関する警告を解消しています。
変更の背景
この変更の背景には、GoランタイムがGoogle Native Client (NaCl) 環境、特にamd64p32
という特殊なアーキテクチャで動作する際の型定義の不整合がありました。
Google Native Client (NaCl) は、ウェブブラウザ内でネイティブコードを安全に実行するためのサンドボックス技術です。Go言語は、このNaCl環境をターゲットとしてサポートしていました。
問題は、amd64p32
というアーキテクチャの特性に起因します。これは、64ビットのレジスタを使用するものの、アドレス空間が32ビット(4GB)に制限されるという、通常のamd64
とは異なるハイブリッドな環境です。Goコンパイラ(6c
)は、このamd64p32
環境を扱う際に、_64BIT
と_64BITREG
という2つのプリプロセッサマクロを使用していました。
コミットメッセージの解説によると、_64BITREG
はGOARCH=amd64
とGOARCH=amd64p32
の両方で常に定義され、これにより64ビット幅のレジスタが使用されることを保証していました。しかし、_64BIT
マクロはamd64
ターゲットでのコンパイル時にのみ定義され、amd64p32
環境では定義が省略されていました。
この_64BIT
の定義の欠如が問題を引き起こしました。Goランタイムの内部型定義、特にint
、uint
、およびその他のアーキテクチャ固有の型は、通常_64BIT
の有無によって32ビットまたは64ビットに切り替わります。amd64p32
環境で_64BIT
が定義されないため、これらの型が意図せず32ビットとして扱われてしまい、特にレジスタ幅が64ビットであるにもかかわらず、ポインタやサイズを表すuintreg
型が32ビットとして定義されるという不整合が生じていました。
この不整合が、runtime.h
におけるuintreg
の誤った定義と、stack.c
における型に関する警告の原因となっていました。このコミットは、これらの問題を修正し、Goランタイムがamd64p32
環境で正しく、かつ警告なしにコンパイル・動作するようにすることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
-
Google Native Client (NaCl):
- Googleが開発した、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するための技術。サンドボックス化された環境で、CPUアーキテクチャに依存しないポータブルな実行形式(PNaCl)や、特定のアーキテクチャに特化した実行形式(NaCl)をサポートします。
- Go言語は、かつてNaClをターゲットとしてサポートしており、GoプログラムをNaClモジュールとしてコンパイルし、ウェブブラウザで実行することが可能でした。
- NaClは、2017年に非推奨となり、WebAssemblyに置き換えられました。しかし、このコミットが作成された2014年当時は活発に開発・利用されていました。
-
Goのクロスコンパイルと
GOARCH
:- Goは強力なクロスコンパイル機能を持ち、異なるOSやアーキテクチャ向けのバイナリを簡単に生成できます。
GOARCH
環境変数は、ターゲットとするCPUアーキテクチャを指定します(例:amd64
,386
,arm
,amd64p32
など)。
-
amd64p32
アーキテクチャ:- これは、Google Native Clientのために特別に定義されたGoのアーキテクチャターゲットです。
- 特徴は、64ビットのレジスタを使用する一方で、アドレス空間が32ビット(4GB)に制限されるという点です。
- 通常の
amd64
(64ビットレジスタ、64ビットアドレス空間)とは異なり、ポインタのサイズが32ビットになります。これは、メモリ効率やサンドボックスの制約に対応するための設計でした。 - コミットメッセージにある「アドレス空間は4GBウィンドウに制限される(ウィンドウはプロセスの48ビット仮想アドレス空間内のランダムな位置に配置される)」という記述は、NaClのサンドボックスが、実際の64ビット仮想アドレス空間の一部を切り出して32ビットのアドレス空間として提供していたことを示唆しています。
-
Goコンパイラ(
6c
)とプリプロセッサマクロ:- Goの初期のコンパイラは、
gc
(Go Compiler)と呼ばれ、各アーキテクチャごとに6c
(amd64)、8c
(386)、5c
(arm)などの名前が付けられていました。このコミットでは6c
が言及されています。 - C言語のプリプロセッサと同様に、Goのコンパイラも条件付きコンパイルのために特定のプリプロセッサマクロ(
#ifdef
など)を使用します。 _64BIT
と_64BITREG
は、GoランタイムのCコード(GoのランタイムはCとGoのハイブリッドで書かれています)で、ターゲットアーキテクチャの特性に応じて型定義を切り替えるために使用されるマクロです。_64BIT
: 通常、ターゲットが64ビットアドレス空間を持つ場合に定義されます。これにより、int
,uint
,uintptr
などの型が64ビット幅になります。_64BITREG
: ターゲットが64ビットレジスタを持つ場合に定義されます。
- Goの初期のコンパイラは、
-
uintreg
型:- Goランタイム内部で使用される型で、レジスタの幅を表すために使われます。レジスタはCPUがデータを一時的に保持する場所であり、その幅はCPUアーキテクチャに依存します。
amd64
やamd64p32
のように64ビットレジスタを持つアーキテクチャでは、uintreg
は64ビットであるべきです。しかし、amd64p32
ではアドレス空間が32ビットであるため、uintptr
(ポインタの整数表現)は32ビットになります。この違いが混乱の原因となっていました。
これらの知識を総合すると、このコミットは、amd64p32
という特殊な環境下で、Goコンパイラが生成するCコードの型定義が、レジスタ幅とアドレス幅の差異によって引き起こされる不整合を修正していることが理解できます。
技術的詳細
このコミットの技術的詳細は、GoランタイムのCコードにおける型定義の条件付きコンパイルのロジックと、amd64p32
アーキテクチャの特殊性に基づいています。
GoランタイムのCコード(src/pkg/runtime/
以下)では、異なるアーキテクチャに対応するために、プリプロセッサディレクティブ(#ifdef
, #else
, #endif
)を多用して型定義を切り替えています。
変更前のruntime.h
では、以下のようなロジックでuintptr
, intptr
, intgo
, uintgo
, uintreg
が定義されていました。
#ifdef _64BIT
typedef uint64 uintptr;
typedef int64 intptr;
typedef int64 intgo; // Go's int
typedef uint64 uintgo; // Go's uint
typedef uint64 uintreg; // <-- ここが問題
#else
typedef uint32 uintptr;
typedef int32 intptr;
typedef int32 intgo; // Go's int
typedef uint32 uintgo; // Go's uint
typedef uint32 uintreg; // <-- ここが問題
#endif
#ifdef _64BITREG
//typedef uint64 uintreg; // <-- コメントアウトされていた
#else
//typedef uint32 uintreg; // <-- コメントアウトされていた
#endif
コミットメッセージの解説にあるように、amd64p32
環境では_64BITREG
は定義されるものの、_64BIT
は定義されませんでした。
このため、#ifdef _64BIT
のブロックでは#else
側の32ビット定義が選択されてしまい、uintreg
も32ビットとして定義されていました。
しかし、amd64p32
は64ビットレジスタを持つため、uintreg
は64ビットであるべきです。この矛盾が問題でした。
このコミットでは、uintreg
の定義を_64BIT
の条件付きコンパイルブロックから外し、_64BITREG
の条件付きコンパイルブロック内に移動させることで、この問題を解決しています。
変更後のruntime.h
の関連部分:
#ifdef _64BIT
typedef uint64 uintptr;
typedef int64 intptr;
typedef int64 intgo; // Go's int
typedef uint64 uintgo; // Go's uint
// typedef uint64 uintreg; // <-- 削除された
#else
typedef uint32 uintptr;
typedef int32 intptr;
typedef int32 intgo; // Go's int
typedef uint32 uintgo; // Go's uint
// typedef uint32 uintreg; // <-- 削除された
#endif
#ifdef _64BITREG
typedef uint64 uintreg; // <-- ここに移動し、コメントアウトが解除された
#else
typedef uint32 uintreg; // <-- ここに移動し、コメントアウトが解除された
#endif
この変更により、uintreg
の型は_64BITREG
の定義にのみ依存するようになります。amd64p32
では_64BITREG
が定義されるため、uintreg
は正しくuint64
として定義されるようになります。
また、stack.c
の変更は、このuintreg
の型定義の修正に伴うものです。runtime·printf
のフォーマット文字列と引数の型が一致しないことによる警告を解消するために、m->cret
とargsize
を明示的にuintptr
にキャストしています。これは、uintreg
が32ビットと誤って解釈されていたために発生していた警告であり、uintreg
が正しく64ビットとして定義されるようになったことで、uintptr
へのキャストがより適切になったことを示しています。
具体的には、stack.c
の以下の行が変更されました。
// 変更前
// runtime·printf("runtime: oldstack gobuf={pc:%p sp:%p lr:%p} cret=%p argsize=%p\n",
// top->gobuf.pc, top->gobuf.sp, top->gobuf.lr, m->cret, (uintptr)argsize);
// 変更後
runtime·printf("runtime: oldstack gobuf={pc:%p sp:%p lr:%p} cret=%p argsize=%p\n",
top->gobuf.pc, top->gobuf.sp, top->gobuf.lr, (uintptr)m->cret, (uintptr)argsize);
m->cret
もuintptr
にキャストすることで、runtime·printf
の可変引数リストにおける型推論が正しく行われ、警告が解消されます。これは、%p
フォーマット指定子がポインタ型を期待するため、uintptr
への明示的なキャストが安全性を高めるためです。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は以下の2ファイルです。
-
src/pkg/runtime/runtime.h
:uintreg
型の定義方法が変更されました。_64BIT
の条件付きコンパイルブロックからuintreg
の定義が削除されました。_64BITREG
の条件付きコンパイルブロック内のuintreg
の定義がコメントアウトから解除され、有効化されました。
--- a/src/pkg/runtime/runtime.h +++ b/src/pkg/runtime/runtime.h @@ -21,19 +21,17 @@ typedef uint64 uintptr; typedef int64 intptr; typedef int64 intgo; // Go's int typedef uint64 uintgo; // Go's uint -typedef uint64 uintreg; #else typedef uint32 uintptr; typedef int32 intptr; typedef int32 intgo; // Go's int typedef uint32 uintgo; // Go's uint -typedef uint32 uintreg; #endif #ifdef _64BITREG -//typedef uint64 uintreg; +typedef uint64 uintreg; #else -//typedef uint32 uintreg; +typedef uint32 uintreg; #endif /*
-
src/pkg/runtime/stack.c
:runtime·printf
関数呼び出しにおいて、m->cret
とargsize
の引数に明示的なuintptr
へのキャストが追加されました。
--- a/src/pkg/runtime/stack.c +++ b/src/pkg/runtime/stack.c @@ -187,7 +187,7 @@ runtime·oldstack(void) if(StackDebug >= 1) { runtime·printf("runtime: oldstack gobuf={pc:%p sp:%p lr:%p} cret=%p argsize=%p\n", - top->gobuf.pc, top->gobuf.sp, top->gobuf.lr, m->cret, (uintptr)argsize); + top->gobuf.pc, top->gobuf.sp, top->gobuf.lr, (uintptr)m->cret, (uintptr)argsize); } // gp->status is usually Grunning, but it could be Gsyscall if a stack split
コアとなるコードの解説
src/pkg/runtime/runtime.h
の変更
この変更の核心は、uintreg
型の定義ロジックを修正し、amd64p32
のような特殊なアーキテクチャで正しくレジスタ幅を反映させることです。
-
変更前:
uintreg
は、_64BIT
マクロの有無によって定義されていました。#ifdef _64BIT
ブロックではuint64
、#else
ブロックではuint32
と定義されていました。 しかし、amd64p32
では_64BIT
が定義されないため、uintreg
はuint32
として扱われていました。これは、amd64p32
が64ビットレジスタを持つという事実と矛盾していました。 -
変更後:
uintreg
の定義が_64BIT
の条件付きコンパイルから切り離され、_64BITREG
の条件付きコンパイルブロック内に移動しました。#ifdef _64BITREG
ブロックではuint64
、#else
ブロックではuint32
と定義されるようになりました。amd64p32
は64ビットレジスタを持つため、Goコンパイラ(6c
)は_64BITREG
を定義します。これにより、uintreg
は正しくuint64
として定義されるようになり、レジスタの実際の幅と型定義が一致するようになりました。
この修正は、Goランタイムがレジスタの値を扱う際に、正しいサイズの型を使用することを保証し、特にamd64p32
のようなハイブリッドアーキテクチャでの潜在的なデータ切り捨てやアライメントの問題を防ぎます。
src/pkg/runtime/stack.c
の変更
この変更は、runtime.h
におけるuintreg
の型定義の修正に直接関連しています。
-
変更前:
runtime·printf
関数呼び出しにおいて、m->cret
とargsize
が%p
フォーマット指定子に対応する引数として渡されていました。%p
はポインタ型(通常はvoid*
)を期待します。argsize
は元々uintptr
にキャストされていましたが、m->cret
はキャストなしで渡されていました。 もしm->cret
の基になる型が、コンパイラが%p
と期待する型と異なる場合(例えば、uintreg
が32ビットと誤って解釈されていた場合)、コンパイラは警告を発する可能性があります。 -
変更後:
m->cret
にも明示的に(uintptr)
へのキャストが追加されました。 これにより、runtime·printf
に渡される引数の型が、%p
フォーマット指定子が期待するポインタ型(またはその整数表現であるuintptr
)と確実に一致するようになります。これは、型安全性を高め、コンパイラの警告を解消するための標準的なプラクティスです。 この変更は、uintreg
の定義が正しくなったことで、uintptr
へのキャストがより意味を持つようになったことを示唆しています。uintptr
はポインタの整数表現であり、アドレス空間の幅に依存します。amd64p32
ではuintptr
は32ビットですが、uintreg
は64ビットです。この区別を明確にし、printf
の引数として適切な型を渡すことが重要です。
これらの変更は、GoランタイムがNaCl環境、特にamd64p32
アーキテクチャでより堅牢に動作するための重要な修正であり、型システムの正確性を保つ上で不可欠でした。
関連リンク
- Go言語の公式リポジトリ: https://github.com/golang/go
- Goのクロスコンパイルに関するドキュメント (Go 1.5以降の
GOARCH
とGOOS
): https://go.dev/doc/install/source#environment - Google Native Client (Wikipedia): https://ja.wikipedia.org/wiki/Google_Native_Client
- WebAssembly (NaClの後継技術): https://webassembly.org/
参考にした情報源リンク
- コミットメッセージ自体 (
commit_data/18821.txt
) - Goのソースコード (
src/pkg/runtime/runtime.h
,src/pkg/runtime/stack.c
) - Goのコンパイラとランタイムに関する一般的な知識
- Google Native Clientに関する公開情報
amd64p32
アーキテクチャに関する情報 (主にGoのコンテキスト内での言及)- C言語のプリプロセッサと型システムに関する一般的な知識
printf
フォーマット指定子に関する情報