[インデックス 19084] ファイルの概要
このコミットは、Goコンパイラ(cmd/6g
、amd64アーキテクチャ向け)における変数ゼロ化の制約を緩和するものです。特に、amd64p32
環境でのポインタのアライメントに関する問題を修正し、メモリ領域のマージに関する最適化の閾値を調整しています。これにより、amd64p32
環境でのゼロ化処理の正確性と効率性が向上します。
コミット
commit f400d9aafc961c18e3289a0e701e61b2482a342a
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Wed Apr 9 21:23:36 2014 +0200
cmd/6g: relax constraint on variables that need zeroing.
On amd64p32 pointers are 32-bit-aligned and cannot be assumed to
have an offset multiple of widthreg. Instead check that they are
withptr-aligned.
Also change the threshold for region merging to 2*widthreg
instead of 2*widthptr because performance on amd64 and amd64p32
is expected to be the same.
Fixes #7712.
LGTM=khr
R=rsc, dave, khr, brad, bradfitz
CC=golang-codereviews
https://golang.org/cl/84690044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/f400d9aafc961c18e3289a0e701e61b2482a342a
元コミット内容
このコミットは、Goコンパイラのcmd/6g
(amd64アーキテクチャ向けコンパイラ)において、ゼロ化が必要な変数に対する制約を緩和することを目的としています。
具体的には、amd64p32
環境において、ポインタが32ビットアライメントされているため、widthreg
(レジスタ幅)の倍数のオフセットを持つと仮定できないという問題に対処しています。代わりに、ポインタがwidthptr
(ポインタ幅)にアライメントされていることを確認するように変更されます。
また、メモリ領域のマージに関する閾値も2*widthptr
から2*widthreg
に変更されています。これは、amd64
とamd64p32
の両方で同様のパフォーマンスが期待されるためです。
この変更は、Issue #7712を修正するものです。
変更の背景
Go言語では、新しく確保されたメモリ領域や、特定のスコープに入る際に初期化されていない変数は、セキュリティと予測可能性のためにゼロ値で初期化される(ゼロ化される)必要があります。これはGoランタイムとコンパイラが連携して行います。
このコミットの背景には、amd64p32
という特定のアーキテクチャターゲットが存在します。amd64p32
は、64ビットの命令セット(amd64
)を使用しながら、ポインタのサイズが32ビットであるという特徴を持つ環境です。これは主にGoogle Native Client (GOOS=nacl
) と組み合わせて使用されていました。
通常のamd64
環境では、ポインタは64ビットであり、通常は8バイト(widthreg
に相当)のアライメントが期待されます。しかし、amd64p32
ではポインタが32ビット(4バイト)であるため、ポインタのアライメント要件が異なります。コンパイラが変数をゼロ化する際に、誤ったアライメントの仮定に基づいて処理を行うと、メモリの不正アクセスや、ゼロ化が正しく行われないといったバグにつながる可能性があります。
元のコードでは、ゼロ化が必要な変数のオフセットやサイズがwidthreg
の倍数であるという厳密な制約を課していました。しかし、amd64p32
環境では32ビットポインタが使用されるため、この制約が不適切でした。ポインタはwidthptr
(32ビット環境では4バイト)にアライメントされるべきであり、widthreg
(64ビット環境では8バイト)にアライメントされるとは限りません。この不一致が、ゼロ化処理における問題を引き起こしていました。
また、メモリ領域のマージに関する閾値の変更は、ゼロ化処理の効率化に関わります。連続したメモリ領域を一度にゼロ化することでパフォーマンスを向上させることができますが、そのマージの判断基準がamd64
とamd64p32
で共通の最適値になるように調整されています。
前提知識の解説
cmd/6g
: Goコンパイラの一部で、amd64アーキテクチャ向けのコンパイラフロントエンドを指します。Goのソースコードを機械語に変換する役割を担います。amd64p32
:amd64
(x86-64)命令セットを使用しながら、ポインタのサイズが32ビットであるという特殊なアーキテクチャターゲットです。Go 1.14以降ではサポートが終了しています。この環境では、レジスタは64ビット幅ですが、ポインタは32ビット幅です。- ゼロ化 (Zeroing): Go言語の重要な特性の一つで、新しく確保されたメモリ領域や、初期化されていない変数が自動的にゼロ値(数値型なら0、ブール型ならfalse、参照型ならnilなど)で初期化されることを指します。これにより、未初期化のメモリ内容によるバグやセキュリティリスクを防ぎます。
widthreg
: Goコンパイラ内部で使用される定数で、レジスタの幅(バイト単位)を表します。amd64
環境では通常8バイト(64ビット)です。widthptr
: Goコンパイラ内部で使用される定数で、ポインタの幅(バイト単位)を表します。amd64
環境では通常8バイト(64ビット)ですが、amd64p32
環境では4バイト(32ビット)です。- メモリのアライメント (Memory Alignment): データがメモリ上で特定のバイト境界に配置されることを指します。CPUが効率的にメモリにアクセスするためには、データがその型に応じた特定のアライメント要件を満たす必要があります。例えば、4バイトの整数は4バイト境界に、8バイトのポインタは8バイト境界に配置されるのが一般的です。アライメントが正しくないと、パフォーマンスの低下や、一部のアーキテクチャではクラッシュの原因となることがあります。
n->type->width
: 変数n
の型が持つ幅(サイズ)をバイト単位で表します。n->xoffset
: 変数n
がスタックフレーム内で持つオフセットをバイト単位で表します。rnd(val, align)
:val
をalign
の倍数に切り上げる(アライメントする)関数です。
技術的詳細
このコミットは、src/cmd/6g/ggen.c
ファイル内のdefframe
関数におけるゼロ化処理のロジックを修正しています。defframe
関数は、関数のスタックフレームを定義し、その中でゼロ化が必要な自動変数(PAUTOクラスの変数)を処理します。
主な変更点は以下の2つです。
-
アライメントチェックの緩和: ゼロ化が必要な変数のサイズとオフセットが、
widthreg
の倍数であるという厳密なチェックを、widthptr
の倍数であるかどうかのチェックに緩和しました。- 変更前:
n->type->width % widthreg != 0 || n->xoffset % widthreg != 0
- 変更後:
n->type->width % widthptr != 0 || n->xoffset % widthptr != 0
これは、amd64p32
環境ではポインタが32ビット(4バイト)であるため、変数のサイズやオフセットが必ずしも64ビット(8バイト)のwidthreg
の倍数になるとは限らないという事実に対応しています。ポインタのアライメントはwidthptr
に依存するため、この変更によりamd64p32
環境でのゼロ化処理が正しく行われるようになります。
- 変更前:
-
メモリ領域マージの閾値の調整: 連続したメモリ領域を効率的にゼロ化するために、隣接する領域をマージする際の閾値を
2*widthptr
から2*widthreg
に変更しました。- 変更前:
n->xoffset + n->type->width >= lo - 2*widthptr
- 変更後:
n->xoffset + n->type->width >= lo - 2*widthreg
この変更は、amd64
とamd64p32
の両方でゼロ化のパフォーマンスを最適化するためのものです。amd64p32
でもレジスタ幅は64ビットであるため、ゼロ化処理の効率を考慮すると、widthreg
を基準とした閾値の方が適切であると判断されました。これにより、より大きな連続した領域を一度にゼロ化できる可能性が高まり、ゼロ化のオーバーヘッドが削減されます。
- 変更前:
-
マージ時の
lo
の計算: 領域をマージする際に、lo
(ゼロ化する領域の開始オフセット)の計算方法も変更されました。- 変更前:
lo = n->xoffset;
- 変更後:
lo = rnd(n->xoffset, widthreg);
これは、マージされた領域の開始オフセットがwidthreg
にアライメントされるように調整することで、ゼロ化処理が効率的に行われるようにするためです。
- 変更前:
これらの変更により、amd64p32
環境におけるゼロ化の正確性が保証され、同時にamd64
環境とのパフォーマンスの整合性が図られています。
コアとなるコードの変更箇所
--- a/src/cmd/6g/ggen.c
+++ b/src/cmd/6g/ggen.c
@@ -42,12 +42,12 @@ defframe(Prog *ptxt)
continue;
if(n->class != PAUTO)
fatal("needzero class %d", n->class);
- if(n->type->width % widthreg != 0 || n->xoffset % widthreg != 0 || n->type->width == 0)
+ if(n->type->width % widthptr != 0 || n->xoffset % widthptr != 0 || n->type->width == 0)
fatal("var %lN has size %d offset %d", n, (int)n->type->width, (int)n->xoffset);
- if(lo != hi && n->xoffset + n->type->width >= lo - 2*widthptr) {
+ if(lo != hi && n->xoffset + n->type->width >= lo - 2*widthreg) {
// merge with range we already have
- lo = n->xoffset;
+ lo = rnd(n->xoffset, widthreg);
continue;
}
// zero old range
コアとなるコードの解説
src/cmd/6g/ggen.c
ファイル内のdefframe
関数における変更点を詳細に解説します。
-
if(n->type->width % widthptr != 0 || n->xoffset % widthptr != 0 || n->type->width == 0)
- 変更前:
if(n->type->width % widthreg != 0 || n->xoffset % widthreg != 0 || n->type->width == 0)
- この行は、ゼロ化が必要な自動変数(
PAUTO
クラスの変数)n
が、適切なアライメントとサイズを持っているかをチェックしています。 n->type->width % widthreg != 0
: 変数のサイズがwidthreg
(レジスタ幅、amd64では8バイト)の倍数でない場合。n->xoffset % widthreg != 0
: 変数のスタックオフセットがwidthreg
の倍数でない場合。n->type->width == 0
: 変数のサイズが0の場合(これは通常エラー)。- 変更の理由:
amd64p32
環境ではポインタが32ビット(4バイト)であるため、変数のサイズやオフセットが必ずしも64ビットのwidthreg
の倍数になるとは限りません。ポインタのアライメントはwidthptr
(ポインタ幅、amd64p32では4バイト)に依存するため、チェックをwidthptr
に変更することで、amd64p32
環境でのゼロ化処理が正しく行われるようになります。これにより、amd64p32
環境で発生していた不適切なfatal
エラー(コンパイラが異常終了するエラー)が回避されます。
- 変更前:
-
if(lo != hi && n->xoffset + n->type->width >= lo - 2*widthreg)
- 変更前:
if(lo != hi && n->xoffset + n->type->width >= lo - 2*widthptr)
- この行は、現在処理している変数
n
のメモリ領域が、既にゼロ化対象としてマークされている連続した領域(lo
からhi
まで)とマージできるかどうかを判断しています。 lo != hi
: 既にゼロ化対象の領域が存在する場合。n->xoffset + n->type->width
: 現在の変数のメモリ領域の終了アドレス。lo - 2*widthreg
: 既存のゼロ化領域の開始アドレスlo
から2*widthreg
だけ手前のオフセット。- 変更の理由: 連続したメモリ領域を一度にゼロ化することでパフォーマンスを向上させるための最適化です。閾値を
2*widthptr
から2*widthreg
に変更することで、amd64
とamd64p32
の両方でゼロ化のパフォーマンスを最適化します。amd64p32
でもレジスタ幅は64ビットであるため、ゼロ化処理の効率を考慮すると、widthreg
を基準とした閾値の方が適切であると判断されました。これにより、より大きな連続した領域を一度にゼロ化できる可能性が高まり、ゼロ化のオーバーヘッドが削減されます。
- 変更前:
-
lo = rnd(n->xoffset, widthreg);
- 変更前:
lo = n->xoffset;
- この行は、現在の変数
n
の領域を既存のゼロ化領域にマージする際に、新しいゼロ化領域の開始オフセットlo
を計算しています。 - 変更の理由: マージされた領域の開始オフセットが
widthreg
にアライメントされるように調整することで、ゼロ化処理が効率的に行われるようにするためです。rnd
関数は、指定された値を指定されたアライメントの倍数に切り上げる役割を果たします。これにより、ゼロ化処理が常に最適なアライメントで行われることが保証されます。
- 変更前:
これらの変更は、amd64p32
という特殊な環境におけるGoコンパイラのゼロ化処理の正確性を確保しつつ、一般的なamd64
環境とのパフォーマンスの整合性を維持するための重要な修正です。
関連リンク
- Go Issue #7712: https://code.google.com/p/go/issues/detail?id=7712 (現在はGitHubに移行しているため、直接アクセスできない可能性がありますが、GoのIssueトラッカーで検索可能です。)
- Go CL 84690044: https://golang.org/cl/84690044
参考にした情報源リンク
- amd64p32に関するStack Overflowの議論: https://stackoverflow.com/questions/20999630/what-is-amd64p32-in-go
- Go 1.14におけるamd64p32サポート終了に関するGitHubの議論: https://github.com/golang/go/issues/35753
- Go言語のメモリ管理とゼロ化に関する一般的な情報: Go公式ドキュメントやGoのソースコード(特に
runtime
パッケージ) - Goコンパイラの内部構造に関する情報: Goのソースコード(
src/cmd/compile
以下) - GoのIssueトラッカー: https://github.com/golang/go/issues
- Goのコードレビューシステム (Gerrit): https://go-review.googlesource.com/