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

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

このコミットは、Goコンパイラ(cmd/5c、386アーキテクチャ向け)における構造体リテラル代入時の副作用の取り扱いに関するバグを修正するものです。具体的には、代入の左辺(LHS)に副作用を持つ式が含まれる場合に発生する問題を解決し、以前のバグ回避策として導入された変更(リビジョン a5b96b602690)を元に戻しています。これにより、Goプログラムのコンパイル時の正確性と安定性が向上します。

コミット

commit c13866db7feaa9bcd3398184f8722aca23b1a26f
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Sat Jan 12 09:16:50 2013 +0100

    cmd/5c: fix handling of side effects when assigning a struct literal.
    
    Also undo revision a5b96b602690 used to workaround the bug.
    
    Fixes #4643.
    
    R=rsc, golang-dev, dave, minux.ma, lucio.dere, bradfitz
    CC=golang-dev
    https://golang.org/cl/7090043

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

https://github.com/golang/go/commit/c13866db7feaa9bcd3398184f8722aca23b1a26f

元コミット内容

cmd/5c: fix handling of side effects when assigning a struct literal.

Also undo revision a5b96b602690 used to workaround the bug.

Fixes #4643.

R=rsc, golang-dev, dave, minux.ma, lucio.dere, bradfitz
CC=golang-dev
https://golang.org/cl/7090043

変更の背景

このコミットは、Goコンパイラにおける特定のバグ(Issue #4643)を修正するために行われました。このバグは、構造体リテラルを代入する際に、代入先の式(左辺値、LHS)に副作用が含まれている場合に、コンパイラが正しくコードを生成できないという問題でした。

具体的には、以下のようなコードが問題を引き起こしていました。

type S struct {
    x int
}

var s *S

func f() *S {
    println("f called")
    return &S{}
}

func main() {
    s = f() // ここで f() が呼び出され、副作用が発生
    *s = S{x: 1} // この代入が問題
}

この場合、*s = S{x: 1} の代入において、*sf() の結果に依存しており、f() は副作用(println)を持っています。コンパイラがこの副作用を適切に処理できず、誤ったコードを生成してしまう可能性がありました。

このバグを一時的に回避するために、以前のリビジョン a5b96b602690src/pkg/runtime/mgc0.c に変更が加えられていました。この回避策は、ガベージコレクタのポインタバッファ操作に関するものでしたが、根本的な問題解決には至っていませんでした。

今回のコミットでは、コンパイラ(cmd/5c)側でこの副作用の取り扱いを修正することで、根本的な解決を図っています。これにより、mgc0.c に導入されていた一時的な回避策が不要となり、その変更も元に戻されています。

前提知識の解説

Goコンパイラ (cmd/5c)

Go言語のコンパイラは、ターゲットアーキテクチャごとに異なるフロントエンドを持っています。cmd/5c は、Intel 386(32ビット)アーキテクチャ向けのGoコンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイラの内部では、ソースコードの構文解析、意味解析、中間コード生成、最適化、そして最終的な機械語コード生成といった段階を経て処理が行われます。

構造体リテラル (Struct Literal)

Go言語における構造体リテラルは、構造体の新しい値をその場で作成し、初期化するための構文です。例えば、S{x: 1, y: "hello"} のように記述します。これは、構造体のインスタンスを生成し、指定されたフィールドに値を設定する簡潔な方法です。

副作用 (Side Effect)

プログラミングにおける副作用とは、関数や式の評価が、その戻り値以外に、プログラムの状態に何らかの変更をもたらすことを指します。例えば、グローバル変数の変更、I/O操作(ファイルの読み書き、画面への出力など)、ネットワーク通信、あるいはポインタを介したメモリ内容の変更などが副作用に該当します。副作用を持つ式は、その評価順序がプログラムの挙動に影響を与えるため、コンパイラはこれらを慎重に扱う必要があります。

Goのガベージコレクタ (src/pkg/runtime/mgc0.c)

src/pkg/runtime/mgc0.c は、Goランタイムのガベージコレクタ(GC)の一部を実装しているC言語のファイルです。Goは自動メモリ管理を行う言語であり、不要になったメモリ領域をGCが自動的に解放します。mgc0.c のようなファイルは、GCがメモリ上のオブジェクトをスキャンし、到達可能なオブジェクトをマークし、到達不能なオブジェクトを解放するプロセスに関与しています。ポインタの追跡やバッファリングといった低レベルな操作が行われます。

Issue #4643

Go言語のGitHubリポジトリで管理されているバグトラッカーのイシュー番号です。この番号は、特定のバグ報告とその議論、解決までの経緯を追跡するために使用されます。

技術的詳細

このコミットの技術的な核心は、コンパイラが構造体リテラルの代入を処理する際に、代入先の左辺値(LHS)が副作用を持つかどうかを正確に判断し、それに応じてコード生成の戦略を変更することにあります。

src/cmd/5c/cgen.c は、Goコンパイラのコード生成部分、特にC言語のコード生成に関連するファイルです。sugen 関数は、構造体や配列の代入を処理する際に呼び出される可能性があります。

変更前は、sugen 関数内で構造体代入の左辺(nn)が「関数呼び出しを含む複雑な式」(nn->complex >= FNX)である場合にのみ、副作用の可能性を考慮してコードを書き換えていました。しかし、関数呼び出し以外にも副作用を持つ式は存在します(例:ポインタのデリファレンスなど)。

このコミットでは、この条件をより汎用的な side(nn) 関数呼び出しに置き換えています。side 関数は、与えられたノード(式)が副作用を持つかどうかを判断するGoコンパイラの内部関数です。これにより、コンパイラは関数呼び出しだけでなく、より広範な副作用を持つ左辺値に対しても、構造体リテラル代入を安全に処理できるようになります。具体的には、副作用を持つ左辺値の場合、コンパイラは一時変数を使用して代入を安全に行うようにコードを生成します。

一方、src/pkg/runtime/mgc0.c の変更は、以前のバグ回避策の巻き戻しです。リビジョン a5b96b602690 では、*bitbufpos = (BitTarget){obj, ti, bitp, shift}; bitbufpos++; のようなポインタバッファへの代入を、*bitbufpos++ = (BitTarget){obj, ti, bitp, shift}; のように単一の式にまとめていました。これは、コンパイラのバグによって、代入とインクリメントが別々のステートメントとして扱われると問題が発生する可能性があったための一時的な回避策でした。コンパイラ側のバグが修正されたため、この回避策は不要となり、より簡潔な元の形式に戻されています。これは、コンパイラの修正が正しく機能していることの証でもあります。

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

src/cmd/5c/cgen.c

--- a/src/cmd/5c/cgen.c
+++ b/src/cmd/5c/cgen.c
@@ -950,9 +950,9 @@ sugen(Node *n, Node *nn, int32 w)
 
 	case OSTRUCT:
 		/*
-		 * rewrite so lhs has no fn call
+		 * rewrite so lhs has no side effect.
 		 */
-		if(nn != Z && nn->complex >= FNX) {
+		if(nn != Z && side(nn)) {
 			nod1 = *n;
 			nod1.type = typ(TIND, n->type);
 			regret(&nod2, &nod1);

src/pkg/runtime/mgc0.c

--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -338,8 +338,7 @@ flushptrbuf(PtrTarget *ptrbuf, PtrTarget **ptrbufpos, Obj **_wp, Workbuf **_wbuf
 			if((bits & (bitAllocated|bitMarked)) != bitAllocated)
 				continue;
 
-			*bitbufpos = (BitTarget){obj, ti, bitp, shift};
-			bitbufpos++;
+			*bitbufpos++ = (BitTarget){obj, ti, bitp, shift};
 		}
 
 		runtime·lock(&lock);
@@ -542,8 +541,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
 			
 			// iface->tab
 			if((void*)iface->tab >= arena_start && (void*)iface->tab < arena_used) {
-				*ptrbufpos = (PtrTarget){iface->tab, (uintptr)itabtype->gc};
-				ptrbufpos++;
+				*ptrbufpos++ = (PtrTarget){iface->tab, (uintptr)itabtype->gc};
 				if(ptrbufpos == ptrbuf_end)
 					flushptrbuf(ptrbuf, &ptrbufpos, &wp, &wbuf, &nobj, bitbuf);
 			}
@@ -570,8 +568,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
 				stack_top.b += PtrSize;
 				obj = *(byte**)i;
 				if(obj >= arena_start && obj < arena_used) {
-					*ptrbufpos = (PtrTarget){obj, 0};
-					ptrbufpos++;
+					*ptrbufpos++ = (PtrTarget){obj, 0};
 					if(ptrbufpos == ptrbuf_end)
 						flushptrbuf(ptrbuf, &ptrbufpos, &wp, &wbuf, &nobj, bitbuf);
 				}
@@ -657,8 +654,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
 		}
 
 		if(obj >= arena_start && obj < arena_used) {
-			*ptrbufpos = (PtrTarget){obj, objti};
-			ptrbufpos++;
+			*ptrbufpos++ = (PtrTarget){obj, objti};
 			if(ptrbufpos == ptrbuf_end)
 				flushptrbuf(ptrbuf, &ptrbufpos, &wp, &wbuf, &nobj, bitbuf);
 		}

コアとなるコードの解説

src/cmd/5c/cgen.c の変更

  • 変更前: if(nn != Z && nn->complex >= FNX)
    • この条件は、代入の左辺(nn)がNULLでなく、かつその複雑度が FNX(関数呼び出しを含む複雑な式を示す定数)以上である場合に真となります。これは、左辺が関数呼び出しを含む場合にのみ副作用を考慮するという、限定的なアプローチでした。
  • 変更後: if(nn != Z && side(nn))
    • この変更により、条件が side(nn) 関数呼び出しに置き換えられました。side(nn) は、nn が副作用を持つ式であるかどうかをより包括的に判断するGoコンパイラの内部関数です。これにより、関数呼び出しだけでなく、ポインタのデリファレンスなど、他の種類の副作用を持つ左辺値に対しても、コンパイラが適切にコードを生成できるようになります。この修正が、Issue #4643 の根本的な解決策となります。

src/pkg/runtime/mgc0.c の変更

mgc0.c の変更は、すべて *ptrbufpos = ...; ptrbufpos++; という2行のコードを *ptrbufpos++ = ...; という1行のコードに戻すものです。

  • 変更前:
    *bitbufpos = (BitTarget){obj, ti, bitp, shift};
    bitbufpos++;
    
    これは、BitTarget 構造体を *bitbufpos に代入し、その後 bitbufpos をインクリメントするという、2つの独立した操作です。
  • 変更後:
    *bitbufpos++ = (BitTarget){obj, ti, bitp, shift};
    
    これは、*bitbufpos に代入を行った後、bitbufpos をインクリメントするという、単一の式で代入とインクリメントを同時に行うC言語のイディオムです。

この変更は、以前のコミット a5b96b602690 で導入された一時的な回避策を元に戻すものです。a5b96b602690 は、コンパイラのバグ(今回のコミットで修正されたもの)により、代入とインクリメントが別々のステートメントとして扱われると問題が発生する可能性があったため、これらを1つの式にまとめることで問題を回避していました。cmd/5c のバグが修正されたため、この回避策は不要となり、より自然で簡潔な元のコードに戻されました。これは、コンパイラの修正が正しく機能していることの確認でもあります。

関連リンク

参考にした情報源リンク

  • Go Issue #4643 の議論
  • Go CL 7090043 のレビューコメント
  • Go言語のコンパイラに関する一般的な情報
  • C言語のポインタ演算子に関する情報
  • プログラミングにおける副作用の概念に関する情報
  • Go言語のガベージコレクタに関する一般的な情報

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

このコミットは、Goコンパイラ(cmd/5c、386アーキテクチャ向け)における構造体リテラル代入時の副作用の取り扱いに関するバグを修正するものです。具体的には、代入の左辺(LHS)に副作用を持つ式が含まれる場合に発生する問題を解決し、以前のバグ回避策として導入された変更(リビジョン a5b96b602690)を元に戻しています。これにより、Goプログラムのコンパイル時の正確性と安定性が向上します。

コミット

commit c13866db7feaa9bcd3398184f8722aca23b1a26f
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date:   Sat Jan 12 09:16:50 2013 +0100

    cmd/5c: fix handling of side effects when assigning a struct literal.
    
    Also undo revision a5b96b602690 used to workaround the bug.
    
    Fixes #4643.
    
    R=rsc, golang-dev, dave, minux.ma, lucio.dere, bradfitz
    CC=golang-dev
    https://golang.org/cl/7090043

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

https://github.com/golang/go/commit/c13866db7feaa9bcd3398184f8722aca23b1a26f

元コミット内容

cmd/5c: fix handling of side effects when assigning a struct literal.

Also undo revision a5b96b602690 used to workaround the bug.

Fixes #4643.

R=rsc, golang-dev, dave, minux.ma, lucio.dere, bradfitz
CC=golang-dev
https://golang.org/cl/7090043

変更の背景

このコミットは、Goコンパイラにおける特定のバグ(Issue #4643)を修正するために行われました。このバグは、構造体リテラルを代入する際に、代入先の式(左辺値、LHS)に副作用が含まれている場合に、コンパイラが正しくコードを生成できないという問題でした。

具体的には、以下のようなコードが問題を引き起こしていました。

type S struct {
    x int
}

var s *S

func f() *S {
    println("f called")
    return &S{}
}

func main() {
    s = f() // ここで f() が呼び出され、副作用が発生
    *s = S{x: 1} // この代入が問題
}

この場合、*s = S{x: 1} の代入において、*sf() の結果に依存しており、f() は副作用(println)を持っています。コンパイラがこの副作用を適切に処理できず、誤ったコードを生成してしまう可能性がありました。

このバグを一時的に回避するために、以前のリビジョン a5b96b602690src/pkg/runtime/mgc0.c に変更が加えられていました。この回避策は、ガベージコレクタのポインタバッファ操作に関するものでしたが、根本的な問題解決には至っていませんでした。

今回のコミットでは、コンパイラ(cmd/5c)側でこの副作用の取り扱いを修正することで、根本的な解決を図っています。これにより、mgc0.c に導入されていた一時的な回避策が不要となり、その変更も元に戻されています。

前提知識の解説

Goコンパイラ (cmd/5c)

Go言語のコンパイラは、ターゲットアーキテクチャごとに異なるフロントエンドを持っています。cmd/5c は、Intel 386(32ビット)アーキテクチャ向けのGoコンパイラです。Goのソースコードを機械語に変換する役割を担っています。コンパイラの内部では、ソースコードの構文解析、意味解析、中間コード生成、最適化、そして最終的な機械語コード生成といった段階を経て処理が行われます。

構造体リテラル (Struct Literal)

Go言語における構造体リテラルは、構造体の新しい値をその場で作成し、初期化するための構文です。例えば、S{x: 1, y: "hello"} のように記述します。これは、構造体のインスタンスを生成し、指定されたフィールドに値を設定する簡潔な方法です。

副作用 (Side Effect)

プログラミングにおける副作用とは、関数や式の評価が、その戻り値以外に、プログラムの状態に何らかの変更をもたらすことを指します。例えば、グローバル変数の変更、I/O操作(ファイルの読み書き、画面への出力など)、ネットワーク通信、あるいはポインタを介したメモリ内容の変更などが副作用に該当します。副作用を持つ式は、その評価順序がプログラムの挙動に影響を与えるため、コンパイラはこれらを慎重に扱う必要があります。

Goのガベージコレクタ (src/pkg/runtime/mgc0.c)

src/pkg/runtime/mgc0.c は、Goランタイムのガベージコレクタ(GC)の一部を実装しているC言語のファイルです。Goは自動メモリ管理を行う言語であり、不要になったメモリ領域をGCが自動的に解放します。mgc0.c のようなファイルは、GCがメモリ上のオブジェクトをスキャンし、到達可能なオブジェクトをマークし、到達不能なオブジェクトを解放するプロセスに関与しています。ポインタの追跡やバッファリングといった低レベルな操作が行われます。

Issue #4643

Go言語のGitHubリポジトリで管理されているバグトラッカーのイシュー番号です。この番号は、特定のバグ報告とその議論、解決までの経緯を追跡するために使用されます。

技術的詳細

このコミットの技術的な核心は、コンパイラが構造体リテラルの代入を処理する際に、代入先の左辺値(LHS)が副作用を持つかどうかを正確に判断し、それに応じてコード生成の戦略を変更することにあります。

src/cmd/5c/cgen.c は、Goコンパイラのコード生成部分、特にC言語のコード生成に関連するファイルです。sugen 関数は、構造体や配列の代入を処理する際に呼び出される可能性があります。

変更前は、sugen 関数内で構造体代入の左辺(nn)が「関数呼び出しを含む複雑な式」(nn->complex >= FNX)である場合にのみ、副作用の可能性を考慮してコードを書き換えていました。しかし、関数呼び出し以外にも副作用を持つ式は存在します(例:ポインタのデリファレンスなど)。

このコミットでは、この条件をより汎用的な side(nn) 関数呼び出しに置き換えています。side 関数は、与えられたノード(式)が副作用を持つかどうかを判断するGoコンパイラの内部関数です。これにより、コンパイラは関数呼び出しだけでなく、より広範な副作用を持つ左辺値に対しても、構造体リテラル代入を安全に処理できるようになります。具体的には、副作用を持つ左辺値の場合、コンパイラは一時変数を使用して代入を安全に行うようにコードを生成します。

一方、src/pkg/runtime/mgc0.c の変更は、以前のバグ回避策の巻き戻しです。リビジョン a5b96b602690 では、*bitbufpos = (BitTarget){obj, ti, bitp, shift}; bitbufpos++; のようなポインタバッファへの代入を、*bitbufpos++ = (BitTarget){obj, ti, bitp, shift}; のように単一の式にまとめていました。これは、コンパイラのバグによって、代入とインクリメントが別々のステートメントとして扱われると問題が発生する可能性があったための一時的な回避策でした。コンパイラ側のバグが修正されたため、この回避策は不要となり、より簡潔な元の形式に戻されています。これは、コンパイラの修正が正しく機能していることの証でもあります。

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

src/cmd/5c/cgen.c

--- a/src/cmd/5c/cgen.c
+++ b/src/cmd/5c/cgen.c
@@ -950,9 +950,9 @@ sugen(Node *n, Node *nn, int32 w)
 
 	case OSTRUCT:
 		/*
-		 * rewrite so lhs has no fn call
+		 * rewrite so lhs has no side effect.
 		 */
-		if(nn != Z && nn->complex >= FNX) {
+		if(nn != Z && side(nn)) {
 			nod1 = *n;
 			nod1.type = typ(TIND, n->type);
 			regret(&nod2, &nod1);

src/pkg/runtime/mgc0.c

--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -338,8 +338,7 @@ flushptrbuf(PtrTarget *ptrbuf, PtrTarget **ptrbufpos, Obj **_wp, Workbuf **_wbuf
 			if((bits & (bitAllocated|bitMarked)) != bitAllocated)
 				continue;
 
-			*bitbufpos = (BitTarget){obj, ti, bitp, shift};
-			bitbufpos++;
+			*bitbufpos++ = (BitTarget){obj, ti, bitp, shift};
 		}
 
 		runtime·lock(&lock);
@@ -542,8 +541,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
 			
 			// iface->tab
 			if((void*)iface->tab >= arena_start && (void*)iface->tab < arena_used) {
-				*ptrbufpos = (PtrTarget){iface->tab, (uintptr)itabtype->gc};
-				ptrbufpos++;
+				*ptrbufpos++ = (PtrTarget){iface->tab, (uintptr)itabtype->gc};
 				if(ptrbufpos == ptrbuf_end)
 					flushptrbuf(ptrbuf, &ptrbufpos, &wp, &wbuf, &nobj, bitbuf);
 			}
@@ -570,8 +568,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
 				stack_top.b += PtrSize;
 				obj = *(byte**)i;
 				if(obj >= arena_start && obj < arena_used) {
-					*ptrbufpos = (PtrTarget){obj, 0};
-					ptrbufpos++;
+					*ptrbufpos++ = (PtrTarget){obj, 0};
 					if(ptrbufpos == ptrbuf_end)
 						flushptrbuf(ptrbuf, &ptrbufpos, &wp, &wbuf, &nobj, bitbuf);
 				}
@@ -657,8 +654,7 @@ scanblock(Workbuf *wbuf, Obj *wp, uintptr nobj, bool keepworking)
 		}
 
 		if(obj >= arena_start && obj < arena_used) {
-			*ptrbufpos = (PtrTarget){obj, objti};
-			ptrbufpos++;
+			*ptrbufpos++ = (PtrTarget){obj, objti};
 			if(ptrbufpos == ptrbuf_end)
 				flushptrbuf(ptrbuf, &ptrbufpos, &wp, &wbuf, &nobj, bitbuf);
 		}

コアとなるコードの解説

src/cmd/5c/cgen.c の変更

  • 変更前: if(nn != Z && nn->complex >= FNX)
    • この条件は、代入の左辺(nn)がNULLでなく、かつその複雑度が FNX(関数呼び出しを含む複雑な式を示す定数)以上である場合に真となります。これは、左辺が関数呼び出しを含む場合にのみ副作用を考慮するという、限定的なアプローチでした。
  • 変更後: if(nn != Z && side(nn))
    • この変更により、条件が side(nn) 関数呼び出しに置き換えられました。side(nn) は、nn が副作用を持つ式であるかどうかをより包括的に判断するGoコンパイラの内部関数です。これにより、関数呼び出しだけでなく、ポインタのデリファレンスなど、他の種類の副作用を持つ左辺値に対しても、コンパイラが適切にコードを生成できるようになります。この修正が、Issue #4643 の根本的な解決策となります。

src/pkg/runtime/mgc0.c の変更

mgc0.c の変更は、すべて *ptrbufpos = ...; ptrbufpos++; という2行のコードを *ptrbufpos++ = ...; という1行のコードに戻すものです。

  • 変更前:
    *bitbufpos = (BitTarget){obj, ti, bitp, shift};
    bitbufpos++;
    
    これは、BitTarget 構造体を *bitbufpos に代入し、その後 bitbufpos をインクリメントするという、2つの独立した操作です。
  • 変更後:
    *bitbufpos++ = (BitTarget){obj, ti, bitp, shift};
    
    これは、*bitbufpos に代入を行った後、bitbufpos をインクリメントするという、単一の式で代入とインクリメントを同時に行うC言語のイディオムです。

この変更は、以前のコミット a5b96b602690 で導入された一時的な回避策を元に戻すものです。a5b96b602690 は、コンパイラのバグ(今回のコミットで修正されたもの)により、代入とインクリメントが別々のステートメントとして扱われると問題が発生する可能性があったため、これらを1つの式にまとめることで問題を回避していました。cmd/5c のバグが修正されたため、この回避策は不要となり、より自然で簡潔な元のコードに戻されました。これは、コンパイラの修正が正しく機能していることの確認でもあります。

関連リンク

  • Go CL 7090043: https://golang.org/cl/7090043
  • 注記: コミットメッセージには Fixes #4643 と記載されていますが、現在のGoの公開Issueトラッカーではこの番号のIssueは見つかりませんでした。これは、Issueが非公開であったか、別のトラッキングシステムで管理されていた可能性、あるいは非常に古いIssueであるためアーカイブされている可能性が考えられます。

参考にした情報源リンク

  • Go CL 7090043 のレビューコメント
  • Go言語のコンパイラに関する一般的な情報
  • C言語のポインタ演算子に関する情報
  • プログラミングにおける副作用の概念に関する情報
  • Go言語のガベージコレクタに関する一般的な情報