[インデックス 18973] ファイルの概要
コミット
このコミットは、Goコンパイラ(cmd/gc
)におけるライブネス解析(liveness analysis)に関連する複数のバグ修正を目的としています。特に、ガベージコレクション(GC)の正確性と効率性、およびコンパイラが生成するコードの健全性を向上させるための変更が含まれています。スタック変数のゼロ初期化の改善、レジスタ最適化器(regopt
)のライブネス解析とGCのライブネス解析との整合性の確保、VARDEF
アノテーションの誤用修正、およびゼロ長配列のポインタ処理の修正が主な内容です。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6722d4563190d952334ff0642dc2e2664b173b57
元コミット内容
commit 6722d4563190d952334ff0642dc2e2664b173b57
Author: Russ Cox <rsc@golang.org>
Date: Thu Mar 27 14:05:57 2014 -0400
cmd/gc: liveness-related bug fixes
1. On entry to a function, only zero the ambiguously live stack variables.
Before, we were zeroing all stack variables containing pointers.
The zeroing is pretty inefficient right now (issue 7624), but there are also
too many stack variables detected as ambiguously live (issue 7345),
and that must be addressed before deciding how to improve the zeroing code.
(Changes in 5g/ggen.c, 6g/ggen.c, 8g/ggen.c, gc/pgen.c)
Fixes #7647.
2. Make the regopt word-based liveness analysis preserve the
whole-variable liveness property expected by the garbage collection
bitmap liveness analysis. That is, if the regopt liveness decides that
one word in a struct needs to be preserved, make sure it preserves
the entire struct. This is particularly important for multiword values
such as strings, slices, and interfaces, in which all the words need
to be present in order to understand the meaning.
(Changes in 5g/reg.c, 6g/reg.c, 8g/reg.c.)
Fixes #7591.
3. Make the regopt word-based liveness analysis treat a variable
as having its address taken - which makes it preserved across
all future calls - whenever n->addrtaken is set, for consistency
with the gc bitmap liveness analysis, even if there is no machine
instruction actually taking the address. In this case n->addrtaken
is incorrect (a nicer way to put it is overconservative), and ideally
there would be no such cases, but they can happen and the two
analyses need to agree.
(Changes in 5g/reg.c, 6g/reg.c, 8g/reg.c; test in bug484.go.)
Fixes crashes found by turning off "zero everything" in step 1.
4. Remove spurious VARDEF annotations. As the comment in
gc/pgen.c explains, the VARDEF must immediately precede
the initialization. It cannot be too early, and it cannot be too late.
In particular, if a function call sits between the VARDEF and the
actual machine instructions doing the initialization, the variable
will be treated as live during that function call even though it is
uninitialized, leading to problems.
(Changes in gc/gen.c; test in live.go.)
Fixes crashes found by turning off "zero everything" in step 1.
5. Do not treat loading the address of a wide value as a signal
that the value must be initialized. Instead depend on the existence
of a VARDEF or the first actual read/write of a word in the value.
If the load is in order to pass the address to a function that does
the actual initialization, treating the load as an implicit VARDEF
causes the same problems as described in step 4.
The alternative is to arrange to zero every such value before
passing it to the real initialization function, but this is a much
easier and more efficient change.
(Changes in gc/plive.c.)
Fixes crashes found by turning off "zero everything" in step 1.
6. Treat wide input parameters with their address taken as
initialized on entry to the function. Otherwise they look
"ambiguously live" and we will try to emit code to zero them.
(Changes in gc/plive.c.)
Fixes crashes found by turning off "zero everything" in step 1.
7. An array of length 0 has no pointers, even if the element type does.
Without this change, the zeroing code complains when asked to
clear a 0-length array.
(Changes in gc/reflect.c.)
LGTM=khr
R=khr
CC=golang-codereviews
https://golang.org/cl/80160044
変更の背景
このコミットは、Goコンパイラとランタイムにおけるライブネス解析の不正確さに起因する複数のバグ、特にガベージコレクションの誤動作やクラッシュを修正するために行われました。Goのガベージコレクタは、スタック上のポインタを正確に識別し、それらが指すオブジェクトをマークする必要があります。しかし、コンパイラのライブネス解析が不正確であると、以下の問題が発生します。
- 未初期化データの参照: 変数がまだ初期化されていないにもかかわらず、ライブ(使用中)であると誤って判断されると、ガベージコレクタが未初期化のメモリ領域をポインタとして解釈し、不正なメモリアドレスにアクセスしてクラッシュする可能性があります。
- 非効率なゼロ初期化: 不要なスタック変数がゼロ初期化されることで、実行時のパフォーマンスが低下します。特に、ポインタを含むすべてのスタック変数をゼロ初期化する従来の挙動は非効率的でした。
- コンパイラ最適化とGCの不整合: コンパイラのレジスタ最適化器が行うライブネス解析と、ガベージコレクタが使用するライブネス情報との間に不整合があると、最適化によってGCが必要とする情報が失われたり、誤解されたりする可能性があります。例えば、レジスタ最適化器が変数をデッド(不要)と判断して最適化しても、GCはそれをライブと見なす場合、GCが不正なメモリをスキャンする原因となります。
VARDEF
アノテーションの誤用:VARDEF
は変数が初期化されたことをコンパイラに伝えるための重要なアノテーションですが、これが不適切なタイミングで挿入されると、変数が初期化前にライブであると誤認され、問題を引き起こします。
これらの問題は、特に「ステップ1で「すべてをゼロにする」をオフにすることで見つかったクラッシュ」という記述から、スタック変数のゼロ初期化の挙動を変更した際に顕在化したようです。これは、以前の「すべてをゼロにする」という保守的なアプローチが、これらの潜在的な問題を覆い隠していたことを示唆しています。このコミットは、より正確で効率的なライブネス解析を実装し、Goプログラムの安定性とパフォーマンスを向上させることを目的としています。
前提知識の解説
このコミットの理解には、以下のGoコンパイラとランタイムの概念に関する知識が必要です。
ライブネス解析 (Liveness Analysis)
ライブネス解析は、コンパイラのデータフロー解析の一種で、プログラムの特定のポイントにおいて、ある変数の値が将来使用される可能性があるかどうか(「ライブ」であるか)を判断します。もし変数の値が将来使用されない場合、その変数は「デッド」と見なされ、そのメモリ領域は再利用可能になります。ガベージコレクタは、ライブなポインタのみを追跡し、到達可能なオブジェクトをマークするためにこの情報を使用します。
- 曖昧なライブネス (Ambiguously Live): 変数がライブであるかデッドであるかをコンパイラが確実に判断できない場合、その変数は「曖昧にライブ」と見なされます。安全のために、このような変数はライブであると仮定され、ガベージコレクタの対象となります。しかし、これが多すぎると非効率的になります。
ガベージコレクション (Garbage Collection, GC) とポインタ
Goはトレース型ガベージコレクタを使用しています。GCは、プログラムが動的に割り当てたメモリのうち、もはや到達不可能になった(どのライブなポインタからも参照されていない)オブジェクトを自動的に解放します。
- スタック上のポインタ: Goの関数呼び出しでは、ローカル変数や引数がスタック上に割り当てられます。これらの変数の中にポインタが含まれる場合、GCはスタックをスキャンしてそれらのポインタを識別し、ヒープ上のオブジェクトをマークする必要があります。
- GCビットマップ (GC Bitmap): Goランタイムは、スタックフレームやヒープオブジェクト内のどのオフセットにポインタが含まれているかを示すビットマップを生成します。これにより、GCはメモリを効率的にスキャンし、ポインタではないデータを誤ってポインタとして解釈するのを防ぎます。
- ゼロ初期化 (Zeroing): Goでは、新しい変数はデフォルトでゼロ値に初期化されます。ポインタ型の場合、これは
nil
になります。スタック変数のゼロ初期化は、GCが未初期化のメモリ領域を誤ってポインタとして解釈し、不正なアドレスにアクセスするのを防ぐための安全策です。
Goコンパイラの構造 (cmd/gc
)
Goコンパイラは、複数のステージと、異なるアーキテクチャ(5g: ARM, 6g: x86-64, 8g: x86)に対応するバックエンドで構成されています。
cmd/gc
: Goコンパイラの主要部分。ggen.c
: コード生成の汎用部分。スタックフレームのセットアップや、スタック変数のゼロ初期化に関連するコードを生成します。reg.c
: レジスタ最適化器(regopt
)のコード。命令ストリームを分析し、レジスタ割り当てやライブネス解析を行います。gen.c
: コード生成の一般的なユーティリティ。pgen.c
: プロローグ/エピローグ生成。関数のエントリ/エグジットコードを扱います。スタックフレームの割り当てや初期化に関連します。plive.c
: ポインタのライブネス解析。GCが使用するライブネス情報を生成します。reflect.c
: 型情報(リフレクション)に関連するユーティリティ。
VARDEF
アノテーション
VARDEF
は、Goコンパイラの内部で使われるアノテーション(注釈)で、特定の変数が定義され、初期化されたことをコンパイラのライブネス解析に伝えます。これにより、コンパイラはその変数をライブと見なし、ガベージコレクタがその変数をスキャン対象とすることができます。VARDEF
が不適切に配置されると、変数が未初期化の状態でライブであると誤認され、GCが不正なメモリをスキャンする原因となります。
n->addrtaken
Goコンパイラの内部表現(ASTノード)において、n->addrtaken
フラグは、変数のアドレスが取られた(ポインタとして使用された)ことを示します。これは、変数がヒープにエスケープする可能性があるか、あるいはGCがその変数を特別に扱う必要があるかを示す重要なヒントとなります。このフラグが設定されている変数は、通常、関数呼び出しを跨いでライブであると見なされます。
マルチワード値 (Multiword Values)
Goにおけるstring
、slice
、interface
などの型は、複数の「ワード」(通常はポインタと長さ/容量、または型情報とデータポインタ)で構成されます。これらの値が正しく解釈されるためには、すべての構成要素が整合性を持ってライブである必要があります。例えば、slice
はデータへのポインタ、長さ、容量の3つのワードで構成され、これらすべてがGCによって正しく認識される必要があります。
技術的詳細
このコミットは、Goコンパイラのライブネス解析とガベージコレクションの連携における複数のエッジケースと不整合を修正しています。
1. スタック変数のゼロ初期化の改善
問題点: 以前は、関数エントリ時にポインタを含むすべてのスタック変数がゼロ初期化されていました。これは安全策ではありますが、非効率的であり、特に多くのスタック変数がポインタを含む場合にパフォーマンスのオーバーヘッドが大きくなります。また、コンパイラが「曖昧にライブ」と判断するスタック変数が多すぎるという別の問題(issue 7345)も存在しました。
修正内容: このコミットでは、関数エントリ時にゼロ初期化される対象を、「曖昧にライブ」と判断されたスタック変数のみに限定しました。これにより、不要なゼロ初期化が削減され、パフォーマンスが向上します。stkzerosize
というグローバル変数が削除され、代わりに各スタック変数ノードのn->needzero
フラグに基づいて個別にゼロ初期化コードが生成されるようになりました。
影響ファイル: src/cmd/5g/ggen.c
, src/cmd/6g/ggen.c
, src/cmd/8g/ggen.c
, src/cmd/gc/pgen.c
2. regopt
とGCライブネス解析の整合性
問題点: レジスタ最適化器(regopt
)はワードベースのライブネス解析を行います。しかし、ガベージコレクタのビットマップベースのライブネス解析は、変数全体(例えば、struct
、string
、slice
、interface
などのマルチワード値)がライブであるという特性を期待します。regopt
がマルチワード値の一部(例えば、struct
内の1ワード)のみをライブと判断し、残りをデッドと最適化してしまうと、GCがその変数を正しく解釈できなくなり、クラッシュや誤ったGCを引き起こす可能性がありました。
修正内容: regopt
のライブネス解析において、あるGo変数を構成するいずれかのワードがライブであると判断された場合、そのGo変数を構成するすべてのワードをライブとしてマークするように変更されました。これは、Var
構造体にnextinnode
フィールドを追加し、同じGo変数に属するワードをリンクリストで繋ぐことで実現されています。これにより、GCが期待する「変数全体」のライブネスが保証され、マルチワード値の正確なGCが可能になります。
影響ファイル: src/cmd/5g/reg.c
, src/cmd/6g/reg.c
, src/cmd/8g/reg.c
3. n->addrtaken
フラグの整合性
問題点: n->addrtaken
フラグは、変数のアドレスが取られたことを示し、GCのライブネス解析ではその変数を関数呼び出しを跨いでライブであると見なします。しかし、regopt
のワードベースのライブネス解析は、実際にアドレスを取る機械語命令が生成されない場合、このフラグを考慮しないことがありました。これにより、GCとregopt
のライブネス解析の間で不整合が生じ、regopt
が変数の初期化を最適化で削除してしまい、GCが未初期化のデータを参照する原因となっていました。特に、クロージャ内で:=
を使って外部変数をシャドウするようなケースで発生しやすかったようです。
修正内容: regopt
のライブネス解析において、n->addrtaken
フラグが設定されている変数は、たとえ実際の機械語命令でアドレスが取られていなくても、関数呼び出しを跨いでライブであると見なされるように変更されました。これにより、GCのライブネス解析との整合性が保たれ、未初期化データの参照によるクラッシュが防止されます。
影響ファイル: src/cmd/5g/reg.c
, src/cmd/6g/reg.c
, src/cmd/8g/reg.c
, test/fixedbugs/bug484.go
(テストケース追加)
4. 不適切なVARDEF
アノテーションの削除
問題点: VARDEF
アノテーションは、変数が初期化された直前に配置されるべきものです。しかし、コンパイラが不適切なタイミングでVARDEF
を挿入してしまうことがありました。特に、VARDEF
と実際の初期化コードの間に別の関数呼び出しが挟まる場合、その関数呼び出し中に変数が未初期化のままライブであると誤って判断され、GCが不正なメモリをスキャンする原因となっていました。
修正内容: src/cmd/gc/gen.c
から、OAS
(代入)やcgen_dcl
(変数宣言のコード生成)の際に自動的にgvardef
を呼び出すコードが削除されました。これにより、VARDEF
アノテーションがより厳密に、変数の実際の初期化直前にのみ挿入されるようになります。
影響ファイル: src/cmd/gc/gen.c
, test/live.go
(テストケース追加)
5. ワイド値のアドレスロードの扱い
問題点: 以前は、ワイド値(大きな構造体など)のアドレスをロードする操作が、その値が初期化されるべきであるという暗黙のVARDEF
シグナルとして扱われることがありました。しかし、このアドレスロードが実際には別の関数に渡され、その関数内で初期化が行われる場合、ステップ4で述べたVARDEF
の誤配置と同じ問題を引き起こしていました。つまり、アドレスロードと実際の初期化の間にギャップが生じ、未初期化のままライブであると誤認される可能性がありました。
修正内容: ワイド値のアドレスロードを、その値が初期化されたというシグナルとして扱わないように変更されました。代わりに、明示的なVARDEF
アノテーションが存在するか、または値の最初の実際の読み書きが行われた場合にのみ、初期化されたと判断するように修正されました。これにより、不適切なライブネスの誤認が防止されます。
影響ファイル: src/cmd/gc/plive.c
6. addrtaken
を持つワイドな入力パラメータの扱い
問題点: addrtaken
フラグが設定されているワイドな入力パラメータ(関数への引数)は、関数エントリ時に「曖昧にライブ」であると判断され、不必要にゼロ初期化されるコードが生成される可能性がありました。これは、これらのパラメータが呼び出し元によって既に初期化されているはずであるにもかかわらず、コンパイラがその事実を認識できないために発生していました。
修正内容: src/cmd/gc/plive.c
において、addrtaken
フラグが設定されているワイドな入力パラメータは、関数エントリ時に既に初期化されているものとして扱うように変更されました。これにより、不必要なゼロ初期化が回避され、生成されるコードの効率が向上します。
影響ファイル: src/cmd/gc/plive.c
7. ゼロ長配列のポインタ処理
問題点: 要素型がポインタを含む型であっても、長さが0の配列は実際にはポインタを含みません。しかし、以前のゼロ初期化コードは、0長配列をクリアしようとした際に、ポインタの存在を誤って仮定し、エラーを報告することがありました。
修正内容: src/cmd/gc/reflect.c
のhaspointers
関数に、配列の長さが0の場合はポインタを含まないと明示的に判断するロジックが追加されました。これにより、ゼロ長配列に対する不適切なゼロ初期化の試みがなくなり、コンパイラの挙動がより正確になります。
影響ファイル: src/cmd/gc/reflect.c
コアとなるコードの変更箇所
このコミットは、Goコンパイラの複数のC言語ソースファイルにわたる広範な変更を含んでいます。主要な変更箇所は以下の通りです。
src/cmd/{5g,6g,8g}/ggen.c
:- 関数エントリ時のスタック変数のゼロ初期化ロジックが大幅に変更されました。以前の
stkzerosize
に基づく一括ゼロ初期化から、n->needzero
フラグを持つ個々の変数に対するゼロ初期化に切り替わっています。 stkzerosize
変数の使用が削除されました。
- 関数エントリ時のスタック変数のゼロ初期化ロジックが大幅に変更されました。以前の
src/cmd/{5g,6g,8g}/reg.c
:Var
構造体にnextinnode
フィールドが追加され、同じGo変数に属するワードをリンクリストで管理できるようになりました。prop
関数(ライブネス伝播)内で、cal.b
(ライブなビットマップ)が更新される際に、Go変数全体がライブであるようにビットをセットするロジックが追加されました。これにより、マルチワード値のライブネスが保証されます。mkvar
関数内で、node->addrtaken
が設定されている変数に対してsetaddrs
を呼び出し、関数呼び出しを跨いでライブであると見なすロジックが追加されました。
src/cmd/gc/gen.c
:OAS
(代入)やcgen_dcl
(変数宣言)の処理から、gvardef
の呼び出しが削除されました。これにより、VARDEF
アノテーションの自動挿入が抑制されます。
src/cmd/gc/go.h
:Var
構造体にnextinnode
フィールドが追加されました。stkzerosize
変数の宣言が削除されました。
src/cmd/gc/pgen.c
:allocauto
関数内で、stkzerosize
の初期化と更新ロジックが削除されました。precisestack_enabled
に基づくn->needzero
の強制設定ロジックが削除されました。
src/cmd/gc/plive.c
:progeffects
関数内で、addrtaken
を持つ入力パラメータを初期化済みとして扱うロジックが追加されました。progeffects
関数内で、addrtaken
を持つ変数のアドレスロードが、VARDEF
シグナルとして扱われないように修正されました。
src/cmd/gc/reflect.c
:haspointers
関数内で、長さが0の配列はポインタを持たないというチェックが追加されました。
test/fixedbugs/bug484.go
とtest/live.go
:- 新しいテストケースが追加され、修正されたライブネス解析の挙動が検証されています。特に
bug484.go
は、addrtaken
フラグの不整合によって引き起こされるクラッシュを再現し、修正を検証するためのものです。live.go
は、VARDEF
の誤配置に関連するライブネスエラーをテストしています。
- 新しいテストケースが追加され、修正されたライブネス解析の挙動が検証されています。特に
コアとなるコードの解説
src/cmd/{5g,6g,8g}/ggen.c
の変更点
以前のggen.c
では、stkzerosize
という変数を使って、スタック上のポインタを含む領域全体を一括でゼロ初期化していました。これは、appendpp
関数を使って、AMOVW
(ARMの場合)やAMOVQ
(x86-64の場合)などの命令でメモリをゼロクリアするループを生成していました。
// 変更前 (簡略化)
if(stkzerosize > 0) {
// ... stkzerosize の領域をゼロ初期化するコードを生成 ...
}
このコミットでは、この一括ゼロ初期化のロジックが削除され、代わりにcurfn->dcl
(現在の関数の宣言リスト)をイテレートし、各変数n
のn->needzero
フラグがセットされている場合にのみ、その変数を個別にゼロ初期化するコードを生成するように変更されました。
// 変更後 (簡略化)
for(l=curfn->dcl; l != nil; l = l->next) {
n = l->n;
if(!n->needzero)
continue;
// ... n の領域をゼロ初期化するコードを生成 ...
}
この変更により、ゼロ初期化の対象がより限定され、不要なゼロ初期化が削減されます。
src/cmd/{5g,6g,8g}/reg.c
の変更点
reg.c
はレジスタ最適化器のライブネス解析を担当します。
重要な変更はVar
構造体へのnextinnode
フィールドの追加と、prop
関数内のライブネス伝播ロジックの修正です。
// src/cmd/gc/go.h (変更点)
struct Var
{
vlong offset;
Node* node;
Var* nextinnode; // 新しく追加されたフィールド
int width;
char name;
char etype;
// ...
};
nextinnode
は、同じGo変数(例えば、struct
やslice
)に属する異なるワード(Var
インスタンス)をリンクリストで繋ぐために使用されます。これにより、ワードベースのライブネス解析が、Go変数全体としてのライブネスを考慮できるようになります。
prop
関数では、cal.b
(関数呼び出しを跨いでライブであると推定されるビットマップ)が更新される際に、以下のロジックが追加されました。
// src/cmd/{5g,6g,8g}/reg.c (prop関数内、簡略化)
// cal.b は関数呼び出しを跨いでライブである現在の近似値
// cal.b の各ビットは単一のスタックワードを表す
// 各ワードについて、同じGo変数内の他の追跡されたスタックワードをすべて見つけ、それらもライブとしてマークする
for(z=0; z<BITS; z++) {
if(cal.b[z] == 0)
continue;
for(i=0; i<32; i++) { // 各ビットをチェック
if(z*32+i >= nvar || ((cal.b[z]>>i)&1) == 0)
continue;
v = var+z*32+i; // ライブと判断されたワードに対応するVar
if(v->node->opt == nil) // 固定レジスタなど、Go変数ではない場合はスキップ
continue;
// v->node->opt は、Go変数 v->node に対応する追跡されたワードの Var のリンクリストの先頭
// リストを辿ってすべてのビットをセットする
// 二次的な挙動を避けるため、vがリストの先頭であるか、先頭のビットがまだセットされていない場合にのみ処理
v1 = v->node->opt;
j = v1 - var;
if(v == v1 || ((cal.b[j/32]>>(j&31))&1) == 0) {
for(; v1 != nil; v1 = v1->nextinnode) {
j = v1 - var;
cal.b[j/32] |= 1<<(j&31); // 関連するすべてのワードをライブとしてマーク
}
}
}
}
このコードは、cal.b
内のいずれかのビットがセットされた場合(つまり、Go変数の一部がライブであると判断された場合)、そのGo変数全体を構成するすべてのワードに対応するビットもcal.b
にセットするようにします。これにより、GCが期待する「変数全体」のライブネスが保証されます。
また、mkvar
関数では、node->addrtaken
フラグが設定されている場合にsetaddrs(bit)
を呼び出すロジックが追加されました。これは、GCのライブネス解析との整合性を保つためです。
// src/cmd/{5g,6g,8g}/reg.c (mkvar関数内、簡略化)
if(node->addrtaken)
setaddrs(bit); // addrtaken が設定されていれば、関連するビットをライブとしてマーク
src/cmd/gc/gen.c
の変更点
gen.c
では、OAS
(代入)やcgen_dcl
(変数宣言)の際に自動的にgvardef
を呼び出すコードが削除されました。
// 変更前 (src/cmd/gc/gen.c、OASケース内)
// if(n->colas && isfat(n->left->type) && n->left->op == ONAME)
// gvardef(n->left); // 削除された行
// 変更前 (src/cmd/gc/gen.c、cgen_dcl関数内)
// if(isfat(n->type))
// gvardef(n); // 削除された行
これらの行が削除されたことで、VARDEF
アノテーションの挿入がより明示的な制御下に置かれ、不適切なタイミングでの挿入が防止されます。
src/cmd/gc/plive.c
の変更点
plive.c
はポインタのライブネス解析を担当します。
progeffects
関数内で、addrtaken
を持つ入力パラメータを初期化済みとして扱うロジックが追加されました。
// src/cmd/gc/plive.c (progeffects関数内、PARAMケース)
case PPARAM:
if(node->addrtaken)
bvset(avarinit, i); // addrtaken の入力パラメータは初期化済みとマーク
bvset(varkill, i);
break;
また、addrtaken
を持つ変数のアドレスロードが、VARDEF
シグナルとして扱われないように修正されました。
// src/cmd/gc/plive.c (progeffects関数内、OADDRケース)
// 変更前: bvset(avarinit, pos);
// 変更後: if(info.flags & (LeftRead|LeftWrite)) bvset(avarinit, pos);
これは、アドレスロードが読み書きを伴う場合にのみavarinit
(初期化済み変数ビットマップ)をセットするように変更されたことを意味します。
src/cmd/gc/reflect.c
の変更点
reflect.c
のhaspointers
関数に、ゼロ長配列の特殊なケースが追加されました。
// src/cmd/gc/reflect.c (haspointers関数内、TARRAYケース)
case TARRAY:
if(t->bound == 0) { // empty array
ret = 0; // 長さ0の配列はポインタを持たない
break;
}
ret = haspointers(t->type);
break;
これにより、長さが0の配列は、その要素型がポインタを含む型であっても、ポインタを持たないと正しく判断されるようになります。
関連リンク
- Go issue 7647: cmd/gc: zeroing of stack variables is too broad
- Go issue 7591: cmd/gc: regopt liveness analysis needs to preserve whole-variable liveness
- Go issue 7624: cmd/gc: zeroing of stack variables is inefficient
- Go issue 7345: cmd/gc: too many stack variables detected as ambiguously live
- Go CL 80160044: cmd/gc: liveness-related bug fixes
参考にした情報源リンク
- Go言語のガベージコレクションについて (一般的な解説)
- Goコンパイラの内部構造 (一般的な解説)
- https://go.dev/src/cmd/compile/internal/gc/README (当時のGoコンパイラの構造とは異なる可能性がありますが、概念的な理解に役立ちます)
- ライブネス解析 (一般的なデータフロー解析の概念)