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

[インデックス 13569] ファイルの概要

このコミットは、Goコンパイラ(特にcmd/8g、x86アーキテクチャ向けコンパイラ)における、変数の幅(メモリサイズ)の誤計算に起因するコンパイルエラー(miscompilation)を修正するものです。具体的には、コンパイラが変数の幅を正しく認識せず、「設定されたが使用されていない」と誤って判断し、本来必要な代入処理を省略してしまう問題(Issue 3899)を解決します。これにより、変数が誤ったデータを持つ可能性がありました。

コミット

commit 823962c52186415d61f23a819af298dd45ed3a52
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Fri Aug 3 22:05:51 2012 +0200

    cmd/8g: fix miscompilation due to BADWIDTH.
    
    Fixes #3899.
    
    R=rsc
    CC=golang-dev, remy
    https://golang.org/cl/6453084

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/823962c52186415d61f23a819af298dd45ed3a52

元コミット内容

cmd/8g: fix miscompilation due to BADWIDTH.

Fixes #3899.

R=rsc
CC=golang-dev, remy
https://golang.org/cl/6453084

変更の背景

この変更は、Goコンパイラ(cmd/8g)が特定の状況下で変数のメモリ上の幅(サイズ)を誤って計算し、その結果として不適切な最適化を行ってしまうバグを修正するために導入されました。具体的には、コンパイラが変数を「設定されたが使用されていない」と誤認し、本来必要な代入処理を省略(elide)してしまうことで、変数が期待される値ではなく、古いデータや不定なデータを持ってしまうという問題(Go Issue 3899)が発生していました。この問題は、特にクロージャや関数ポインタが関わる複雑なコードパスで顕在化しやすかったと考えられます。

前提知識の解説

このコミットを理解するためには、以下のGoコンパイラの内部構造と概念に関する知識が必要です。

  • Goコンパイラ(cmd/8g: Go言語の初期のコンパイラは、ターゲットアーキテクチャごとに独立した実装を持っていました。8gはx86(32-bit)アーキテクチャ向けのコンパイラを指します。これらのコンパイラは、Goのソースコードを機械語に変換する主要な役割を担っていました。
  • Node構造体: Goコンパイラのフロントエンドでは、ソースコードが抽象構文木(AST: Abstract Syntax Tree)として表現されます。このASTの各要素はNode構造体で表されます。Nodeは変数、定数、関数呼び出し、演算子など、プログラムのあらゆる構成要素に対応します。
  • Type構造体: Go言語の型システムは、Type構造体によって内部的に表現されます。Type構造体には、その型に関する様々な情報(例えば、基本型、ポインタ型、構造体型、配列型など)が含まれます。
  • width: コンパイラにおけるwidthは、特定のデータ型がメモリ上で占めるバイト数を指します。例えば、int32型は4バイトの幅を持ちます。このwidth情報は、メモリの割り当て、アライメント、およびコード生成において極めて重要です。コンパイラは、変数のwidthに基づいて、その変数を格納するために必要なメモリ量を決定し、適切なロード/ストア命令を生成します。
  • dowidth関数: dowidthはGoコンパイラ内部の関数で、与えられたType構造体に対して、その型の正確なメモリ上の幅(width)を計算し、Type構造体のwidthフィールドに設定する役割を担います。構造体や配列のような複合型の場合、そのメンバーの型やアライメントルールに基づいて再帰的に幅を計算します。
  • BADWIDTH: これは、コンパイラが型の幅を正しく計算できなかったり、無効な幅が検出されたりした場合に発生する可能性のある内部エラー状態を示唆する用語です。widthが不正であると、メモリのオーバーラップ、データの破損、または不適切な最適化(今回のケースのように代入の省略)につながります。
  • naddr関数: src/cmd/8g/gsubr.cに存在するnaddr関数は、ASTノード(Node)から、アセンブリコード生成のためのアドレス情報(Addr)を構築する役割を担っています。この関数は、変数の型情報(n->type)を利用して、その変数のメモリ上のアドレスや幅を決定します。

技術的詳細

このバグは、naddr関数内で変数のアドレス情報を構築する際に、その変数の型(n->type)のwidthフィールドが適切に初期化されていない、または最新の状態に更新されていないことが原因で発生しました。

naddr関数は、Nodeが持つ型情報(n->type)から、その変数のメモリ上の幅(a->width = n->type->width;)を取得しようとします。しかし、このn->type->widthが、その時点までにdowidth関数によって正確に計算・設定されていない場合、誤った(例えばゼロや古い)値が使用されてしまいます。

コンパイラは、変数の幅がゼロであるか、または非常に小さいと判断した場合、その変数がメモリを占有しない、あるいは「使用されていない」と誤解釈する可能性があります。これにより、本来その変数への代入を行うべきコードが、最適化の一環として削除されてしまう(elideされる)という問題が発生しました。結果として、変数は初期化されないままか、古い値を持ったままになり、プログラムの動作が不正になるという「miscompilation」を引き起こしていました。

このコミットの修正は、naddr関数内でn->type->widthを使用する直前に、dowidth(n->type);を呼び出すことで、常に最新かつ正確な幅情報がn->type->widthに設定されるように保証します。これにより、コンパイラが変数のメモリレイアウトを正しく理解し、不適切な最適化を防ぐことができます。

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

変更はsrc/cmd/8g/gsubr.cファイルの一箇所です。

--- a/src/cmd/8g/gsubr.c
+++ b/src/cmd/8g/gsubr.c
@@ -1853,6 +1853,7 @@ naddr(Node *n, Addr *a, int canemitcode)\n 		a->width = 0;\n 		if(n->type != T) {\n 			a->etype = simtype[n->type->etype];\n+			dowidth(n->type);\n 			a->width = n->type->width;\n 			a->gotype = ngotype(n);\n 		}\

追加された行は以下の通りです。

dowidth(n->type);

また、この修正を検証するための新しいテストケースがtest/fixedbugs/bug450.goとして追加されました。

コアとなるコードの解説

src/cmd/8g/gsubr.cnaddr関数は、Goコンパイラのバックエンドの一部であり、抽象構文木(AST)のノードから、アセンブリコード生成に必要なアドレス情報(Addr構造体)を構築します。

変更前のコードでは、n->type->widthの値が、その型が持つべき正しいメモリ上の幅を反映していない可能性がありました。これは、n->typeが参照する型情報が、まだdowidth関数によって完全に処理され、そのwidthフィールドが計算・設定されていない場合に起こり得ます。

追加されたdowidth(n->type);という行は、n->type->widthを使用する直前に、n->typeが指す型の正確なメモリ上の幅を強制的に計算させ、n->type->widthフィールドを更新します。これにより、その直後のa->width = n->type->width;という代入が、常に正しい幅情報に基づいて行われるようになります。

この修正によって、コンパイラは変数のメモリレイアウトを正確に把握できるようになり、変数が「設定されたが使用されていない」と誤認して代入を省略するような不適切な最適化を防ぐことができます。結果として、変数が正しいデータを持つことが保証され、コンパイルされたプログラムの正確性が向上します。

test/fixedbugs/bug450.goは、このバグが顕在化する特定のコードパターン(クロージャの利用)を再現し、修正が正しく適用されたことを検証するための回帰テストです。

関連リンク

  • Go Issue 3899: このコミットが修正したバグのトラッキングイシュー。
  • Gerrit Change-ID: https://golang.org/cl/6453084 - このコミットに対応するGoのコードレビューシステム(Gerrit)上の変更セット。

参考にした情報源リンク

  • コミットメッセージ (./commit_data/13569.txt)
  • 変更されたソースコード (src/cmd/8g/gsubr.c)
  • 追加されたテストコード (test/fixedbugs/bug450.go)
  • Go言語のコンパイラ設計に関する一般的な知識
  • Go言語のIssueトラッカー (Go Issue 3899)