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

[インデックス 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に変更されています。これは、amd64amd64p32の両方で同様のパフォーマンスが期待されるためです。

この変更は、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バイト)にアライメントされるとは限りません。この不一致が、ゼロ化処理における問題を引き起こしていました。

また、メモリ領域のマージに関する閾値の変更は、ゼロ化処理の効率化に関わります。連続したメモリ領域を一度にゼロ化することでパフォーマンスを向上させることができますが、そのマージの判断基準がamd64amd64p32で共通の最適値になるように調整されています。

前提知識の解説

  • 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): valalignの倍数に切り上げる(アライメントする)関数です。

技術的詳細

このコミットは、src/cmd/6g/ggen.cファイル内のdefframe関数におけるゼロ化処理のロジックを修正しています。defframe関数は、関数のスタックフレームを定義し、その中でゼロ化が必要な自動変数(PAUTOクラスの変数)を処理します。

主な変更点は以下の2つです。

  1. アライメントチェックの緩和: ゼロ化が必要な変数のサイズとオフセットが、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. メモリ領域マージの閾値の調整: 連続したメモリ領域を効率的にゼロ化するために、隣接する領域をマージする際の閾値を2*widthptrから2*widthregに変更しました。

    • 変更前: n->xoffset + n->type->width >= lo - 2*widthptr
    • 変更後: n->xoffset + n->type->width >= lo - 2*widthreg この変更は、amd64amd64p32の両方でゼロ化のパフォーマンスを最適化するためのものです。amd64p32でもレジスタ幅は64ビットであるため、ゼロ化処理の効率を考慮すると、widthregを基準とした閾値の方が適切であると判断されました。これにより、より大きな連続した領域を一度にゼロ化できる可能性が高まり、ゼロ化のオーバーヘッドが削減されます。
  3. マージ時の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関数における変更点を詳細に解説します。

  1. 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エラー(コンパイラが異常終了するエラー)が回避されます。
  2. 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に変更することで、amd64amd64p32の両方でゼロ化のパフォーマンスを最適化します。amd64p32でもレジスタ幅は64ビットであるため、ゼロ化処理の効率を考慮すると、widthregを基準とした閾値の方が適切であると判断されました。これにより、より大きな連続した領域を一度にゼロ化できる可能性が高まり、ゼロ化のオーバーヘッドが削減されます。
  3. lo = rnd(n->xoffset, widthreg);

    • 変更前: lo = n->xoffset;
    • この行は、現在の変数nの領域を既存のゼロ化領域にマージする際に、新しいゼロ化領域の開始オフセットloを計算しています。
    • 変更の理由: マージされた領域の開始オフセットがwidthregにアライメントされるように調整することで、ゼロ化処理が効率的に行われるようにするためです。rnd関数は、指定された値を指定されたアライメントの倍数に切り上げる役割を果たします。これにより、ゼロ化処理が常に最適なアライメントで行われることが保証されます。

これらの変更は、amd64p32という特殊な環境におけるGoコンパイラのゼロ化処理の正確性を確保しつつ、一般的なamd64環境とのパフォーマンスの整合性を維持するための重要な修正です。

関連リンク

参考にした情報源リンク