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

[インデックス 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のガベージコレクタは、スタック上のポインタを正確に識別し、それらが指すオブジェクトをマークする必要があります。しかし、コンパイラのライブネス解析が不正確であると、以下の問題が発生します。

  1. 未初期化データの参照: 変数がまだ初期化されていないにもかかわらず、ライブ(使用中)であると誤って判断されると、ガベージコレクタが未初期化のメモリ領域をポインタとして解釈し、不正なメモリアドレスにアクセスしてクラッシュする可能性があります。
  2. 非効率なゼロ初期化: 不要なスタック変数がゼロ初期化されることで、実行時のパフォーマンスが低下します。特に、ポインタを含むすべてのスタック変数をゼロ初期化する従来の挙動は非効率的でした。
  3. コンパイラ最適化とGCの不整合: コンパイラのレジスタ最適化器が行うライブネス解析と、ガベージコレクタが使用するライブネス情報との間に不整合があると、最適化によってGCが必要とする情報が失われたり、誤解されたりする可能性があります。例えば、レジスタ最適化器が変数をデッド(不要)と判断して最適化しても、GCはそれをライブと見なす場合、GCが不正なメモリをスキャンする原因となります。
  4. 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におけるstringsliceinterfaceなどの型は、複数の「ワード」(通常はポインタと長さ/容量、または型情報とデータポインタ)で構成されます。これらの値が正しく解釈されるためには、すべての構成要素が整合性を持ってライブである必要があります。例えば、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)はワードベースのライブネス解析を行います。しかし、ガベージコレクタのビットマップベースのライブネス解析は、変数全体(例えば、structstringsliceinterfaceなどのマルチワード値)がライブであるという特性を期待します。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.chaspointers関数に、配列の長さが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.gotest/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(現在の関数の宣言リスト)をイテレートし、各変数nn->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変数(例えば、structslice)に属する異なるワード(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.chaspointers関数に、ゼロ長配列の特殊なケースが追加されました。

// 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の配列は、その要素型がポインタを含む型であっても、ポインタを持たないと正しく判断されるようになります。

関連リンク

参考にした情報源リンク