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

[インデックス 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つのプリプロセッサマクロを使用していました。

コミットメッセージの解説によると、_64BITREGGOARCH=amd64GOARCH=amd64p32の両方で常に定義され、これにより64ビット幅のレジスタが使用されることを保証していました。しかし、_64BITマクロはamd64ターゲットでのコンパイル時にのみ定義され、amd64p32環境では定義が省略されていました。

この_64BITの定義の欠如が問題を引き起こしました。Goランタイムの内部型定義、特にintuint、およびその他のアーキテクチャ固有の型は、通常_64BITの有無によって32ビットまたは64ビットに切り替わります。amd64p32環境で_64BITが定義されないため、これらの型が意図せず32ビットとして扱われてしまい、特にレジスタ幅が64ビットであるにもかかわらず、ポインタやサイズを表すuintreg型が32ビットとして定義されるという不整合が生じていました。

この不整合が、runtime.hにおけるuintregの誤った定義と、stack.cにおける型に関する警告の原因となっていました。このコミットは、これらの問題を修正し、Goランタイムがamd64p32環境で正しく、かつ警告なしにコンパイル・動作するようにすることを目的としています。

前提知識の解説

このコミットを理解するためには、以下の前提知識が必要です。

  1. Google Native Client (NaCl):

    • Googleが開発した、ウェブブラウザ内でネイティブコード(C/C++など)を安全に実行するための技術。サンドボックス化された環境で、CPUアーキテクチャに依存しないポータブルな実行形式(PNaCl)や、特定のアーキテクチャに特化した実行形式(NaCl)をサポートします。
    • Go言語は、かつてNaClをターゲットとしてサポートしており、GoプログラムをNaClモジュールとしてコンパイルし、ウェブブラウザで実行することが可能でした。
    • NaClは、2017年に非推奨となり、WebAssemblyに置き換えられました。しかし、このコミットが作成された2014年当時は活発に開発・利用されていました。
  2. GoのクロスコンパイルとGOARCH:

    • Goは強力なクロスコンパイル機能を持ち、異なるOSやアーキテクチャ向けのバイナリを簡単に生成できます。
    • GOARCH環境変数は、ターゲットとするCPUアーキテクチャを指定します(例: amd64, 386, arm, amd64p32など)。
  3. amd64p32アーキテクチャ:

    • これは、Google Native Clientのために特別に定義されたGoのアーキテクチャターゲットです。
    • 特徴は、64ビットのレジスタを使用する一方で、アドレス空間が32ビット(4GB)に制限されるという点です。
    • 通常のamd64(64ビットレジスタ、64ビットアドレス空間)とは異なり、ポインタのサイズが32ビットになります。これは、メモリ効率やサンドボックスの制約に対応するための設計でした。
    • コミットメッセージにある「アドレス空間は4GBウィンドウに制限される(ウィンドウはプロセスの48ビット仮想アドレス空間内のランダムな位置に配置される)」という記述は、NaClのサンドボックスが、実際の64ビット仮想アドレス空間の一部を切り出して32ビットのアドレス空間として提供していたことを示唆しています。
  4. 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ビットレジスタを持つ場合に定義されます。
  5. uintreg:

    • Goランタイム内部で使用される型で、レジスタの幅を表すために使われます。レジスタはCPUがデータを一時的に保持する場所であり、その幅はCPUアーキテクチャに依存します。
    • amd64amd64p32のように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->cretargsizeを明示的に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->cretuintptrにキャストすることで、runtime·printfの可変引数リストにおける型推論が正しく行われ、警告が解消されます。これは、%pフォーマット指定子がポインタ型を期待するため、uintptrへの明示的なキャストが安全性を高めるためです。

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

このコミットにおけるコアとなるコードの変更箇所は以下の2ファイルです。

  1. 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
     
     /*
    
  2. src/pkg/runtime/stack.c:

    • runtime·printf関数呼び出しにおいて、m->cretargsizeの引数に明示的な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が定義されないため、uintreguint32として扱われていました。これは、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->cretargsize%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アーキテクチャでより堅牢に動作するための重要な修正であり、型システムの正確性を保つ上で不可欠でした。

関連リンク

参考にした情報源リンク

  • コミットメッセージ自体 (commit_data/18821.txt)
  • Goのソースコード (src/pkg/runtime/runtime.h, src/pkg/runtime/stack.c)
  • Goのコンパイラとランタイムに関する一般的な知識
  • Google Native Clientに関する公開情報
  • amd64p32アーキテクチャに関する情報 (主にGoのコンテキスト内での言及)
  • C言語のプリプロセッサと型システムに関する一般的な知識
  • printfフォーマット指定子に関する情報