[インデックス 18490] ファイルの概要
このコミットは、Goコンパイラのcmd/6c (ARM 64-bit), cmd/8c (x86 32-bit), および cmd/8g (x86 64-bit) におけるデバッグ出力の修正と、cmd/8g内のループの誤りを修正するものです。具体的には、プログラムカウンタ (PC) の値がvlong型であるにもかかわらず、print関数で正しく表示されていなかった問題と、cmd/8g/reg.c内のdumpit関数におけるforループのイテレーション変数の誤用が修正されています。
コミット
commit ca6186aa269eebf62d8a89d846a0eed4108e3b66
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu Feb 13 03:09:03 2014 -0500
cmd/6c, cmd/8c, cmd/8g: fix print of pc (which is vlong).
While we're at it, fix a wrong for statement in cmd/8g.
LGTM=rsc
R=rsc, golang-codereviews
CC=golang-codereviews
https://golang.org/cl/62700044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ca6186aa269eebf62d8a89d846a0eed4108e3b66
元コミット内容
Goコンパイラのcmd/6c、cmd/8c、cmd/8gにおいて、プログラムカウンタ(PC)の表示に関するバグを修正します。PCはvlong型であるにもかかわらず、print関数での出力時に適切な型変換が行われていませんでした。また、cmd/8g内の誤ったfor文も同時に修正します。
変更の背景
このコミットの背景には、Goコンパイラのデバッグ出力の正確性を向上させるという目的があります。特に、コンパイラのレジスタ割り当てやジャンプ最適化の段階で出力されるプログラムカウンタの値が、内部的にはvlong(符号なし64ビット整数)として扱われているにもかかわらず、デバッグ出力時にはその型が正しく考慮されていなかったため、表示が不正確になる可能性がありました。
Goコンパイラは、異なるアーキテクチャ(例: 6cはARM 64-bit、8cはx86 32-bit、8gはx86 64-bit)向けにコードを生成する際に、それぞれ専用のコンパイラフロントエンドを持っています。これらのコンパイラは、コード生成の過程で中間表現を操作し、最適化を行います。reg.cファイルは、これらのコンパイラにおけるレジスタ割り当てや命令の並べ替え、ジャンプ命令の最適化など、バックエンドの重要な部分を担っています。
デバッグ時にプログラムカウンタの正確な値を確認することは、コンパイラの動作を理解し、バグを特定する上で不可欠です。vlong型は、特に64ビットアーキテクチャにおいて、より大きなアドレス空間を表現するために使用されます。この値が正しく表示されないと、デバッグ作業が困難になるため、この修正はコンパイラの開発とデバッグの効率化に寄与します。
また、cmd/8g/reg.cにおけるforループの誤りは、コードの正確な動作を妨げる可能性があり、これも同時に修正されました。これは、コンパイラの内部ロジックの堅牢性を高めるための一般的なコード品質改善の一環と考えられます。
前提知識の解説
Goコンパイラの構造 (cmd/6c, cmd/8c, cmd/8g)
Goコンパイラは、伝統的にPlan 9 Cコンパイラの設計思想を受け継いでおり、異なるアーキテクチャごとに独立したコンパイラバイナリが存在します。
cmd/6c: ARM 64-bit (ARMv8-A) アーキテクチャ向けのCコンパイラ。Goのソースコードを中間表現に変換し、最終的にARM 64-bitのアセンブリコードを生成する過程で使用されます。cmd/8c: x86 32-bit (IA-32) アーキテクチャ向けのCコンパイラ。Goのソースコードを中間表現に変換し、x86 32-bitのアセンブリコードを生成する過程で使用されます。cmd/8g: x86 64-bit (x86-64) アーキテクチャ向けのGoコンパイラ。Goのソースコードを中間表現に変換し、x86 64-bitのアセンブリコードを生成する過程で使用されます。Goコンパイラは、C言語で書かれた部分とGo言語で書かれた部分が混在しており、これらのcmd/*cやcmd/*gは、そのC言語で書かれた部分のコンパイラを指します。
これらのコンパイラは、ソースコードの解析、型チェック、中間表現の生成、最適化、コード生成といった一連のフェーズを実行します。reg.cファイルは、主にバックエンドの最適化フェーズ、特にレジスタ割り当てとジャンプ命令の最適化に関連するコードを含んでいます。
プログラムカウンタ (PC)
プログラムカウンタ(Program Counter, PC)は、CPUが次に実行する命令のアドレスを保持するレジスタです。コンパイラの文脈では、生成されるアセンブリコード内の特定の命令のアドレスや、中間表現における命令の相対的な位置を示すために使用されます。デバッグ時には、PCの値を見ることで、プログラムが現在どの命令を実行しているかを追跡できます。
vlong型
vlongは、Goコンパイラの内部で使われるデータ型の一つで、符号なし64ビット整数(unsigned long longに相当)を表します。これは、特に64ビットアーキテクチャにおいて、メモリのアドレスや大きな数値、プログラムカウンタの値などを表現するために使用されます。C言語のprintf系関数でlong longを扱う場合、通常は%lldや%lluのようなフォーマット指定子を使用しますが、Goコンパイラの内部print関数では、vlong型をint型にキャストして出力しようとしていたため、値が切り捨てられたり、正しく表示されなかったりする問題が発生していました。
print関数とデバッグ出力
Goコンパイラのソースコード内には、デバッグ情報を出力するための独自のprint関数が定義されています。これは標準Cライブラリのprintfとは異なり、コンパイラの内部構造や特定のデバッグフラグ(例: debug['R'], debug['v'])に基づいて動作します。このprint関数が、vlong型のPC値を正しくフォーマットして出力できるように修正することが、このコミットの主要な目的です。
Reg構造体とFlow構造体
Goコンパイラのバックエンド、特にレジスタ割り当てやコード最適化のフェーズでは、プログラムの制御フローを表現するために様々なデータ構造が使用されます。
Reg構造体:reg.cファイルで頻繁に登場するReg構造体は、レジスタ割り当てグラフのノードや、命令のシーケンスを表すために使用される可能性があります。各Regインスタンスは、特定の命令(Prog構造体で表される)や、その命令に関連するレジスタ情報、制御フロー情報(前後の命令へのリンクなど)を保持します。r->pcはこのReg構造体の一部であり、関連する命令のプログラムカウンタを示します。Flow構造体:cmd/8g/reg.cのdumpit関数で言及されているFlow構造体は、制御フローグラフ(Control Flow Graph, CFG)のノードを表すために使用される可能性があります。CFGは、プログラムの実行パスをグラフとして表現したもので、最適化の際に重要な役割を果たします。r1->prog->pcは、Flow構造体に関連付けられた命令のプログラムカウンタを示します。
技術的詳細
このコミットの技術的詳細は、主にC言語の型変換とポインタ操作、そしてコンパイラのデバッグ出力メカニズムに焦点を当てています。
pcの型変換の修正
変更の核心は、r->pcおよびr1->prog->pcの値をprint関数に渡す際に、明示的に(int)にキャストしていた部分を修正することです。pcはvlong型(符号なし64ビット整数)であるため、これをint型(通常32ビット整数)にキャストすると、値がintの最大値を超える場合に情報が失われ、不正確な出力となります。
修正前:
print("%04d %P\\n", r->pc, p);
修正後:
print("%04d %P\\n", (int)r->pc, p); // これは元のコードの誤り。正しくは %lld または %llu を使うべきだが、Goコンパイラのprint関数は独自のフォーマットを持つため、ここでは(int)キャストが意図された修正。
注釈: 実際のGoコンパイラのprint関数は、標準Cライブラリのprintfとは異なり、独自のフォーマット指定子と引数の解釈を持っています。このコミットの文脈では、%04dがint型の引数を期待しているため、vlong型のr->pcをintにキャストすることで、print関数が期待する型に合わせていると解釈できます。これは、print関数自体がvlongを直接扱えるように設計されていないか、あるいは%04dが特定の範囲のPC値のみを想定しているため、明示的なキャストが必要とされた可能性があります。もしvlong全体を正確に表示する必要がある場合は、print関数自体を拡張するか、別のフォーマット指定子を使用する必要があります。この修正は、既存のprint関数の制約内で、PCの表示を「修正」するためのものです。
cmd/8g/reg.cにおけるforループの修正
src/cmd/8g/reg.cのdumpit関数内のforループには、イテレーション変数の誤用がありました。
修正前:
for(; r1 != nil; r1 = r->p2link)
このコードでは、ループの更新部分でr->p2linkを使用しています。rはループの外側で定義された変数であり、r1が指す現在の要素とは関係ありません。これにより、r1が正しく次の要素に進まず、無限ループに陥るか、予期せぬ動作を引き起こす可能性がありました。
修正後:
for(; r1 != nil; r1 = r1->p2link)
修正後は、ループの更新部分でr1->p2linkを使用しています。これにより、r1が現在処理しているFlow構造体の次の要素へのリンクを正しくたどり、ループが期待通りに機能するようになります。p2linkは、Flow構造体内のポインタであり、制御フローグラフにおける先行ブロック(predecessor)のリストを連結するために使用されます。
fixjmp関数とdumpit関数
fixjmp関数:src/cmd/6c/reg.cとsrc/cmd/8c/reg.cの両方に存在するfixjmp関数は、ジャンプ命令の最適化を担当します。具体的には、不要なジャンプ命令(例:JMP L1; L1: ...のような冗長なジャンプや、JMP L1の後にL1が続くようなケース)を削除したり、ジャンプのターゲットを直接的なものに修正したりします。この関数内でデバッグ出力としてPCが表示されるため、その表示の正確性が重要になります。dumpit関数:src/cmd/8g/reg.cに存在するdumpit関数は、制御フローグラフやレジスタ割り当ての状態をデバッグ目的でダンプ(出力)するために使用されます。この関数内で先行ブロックのPC値が出力される際に、forループの誤りがあったため、その修正が行われました。
これらの修正は、Goコンパイラのバックエンドにおけるデバッグの信頼性を高め、コンパイラ開発者がより効率的に問題を診断できるようにすることを目的としています。
コアとなるコードの変更箇所
src/cmd/6c/reg.c
--- a/src/cmd/6c/reg.c
+++ b/src/cmd/6c/reg.c
@@ -1461,7 +1461,7 @@ fixjmp(Reg *firstr)
for(r=firstr; r; r=r->link) {
p = r->prog;
if(debug['R'] && debug['v'])
- print("%04d %P\\n", r->pc, p);
+ print("%04d %P\\n", (int)r->pc, p);
if(p->as != ACALL && p->to.type == D_BRANCH && r->s2 && r->s2->prog->as == AJMP) {
r->s2 = chasejmp(r->s2, &jmploop);
p->to.offset = r->s2->pc;
@@ -1486,7 +1486,7 @@ fixjmp(Reg *firstr)
// Let it stay.
} else {
if(debug['R'] && debug['v'])
- print("del %04d %P\\n", r->pc, p);
+ print("del %04d %P\\n", (int)r->pc, p);
p->as = ANOP;
}
}
@@ -1499,7 +1499,7 @@ fixjmp(Reg *firstr)
p = r->prog;
if(p->as == AJMP && p->to.type == D_BRANCH && r->s2 == r->link) {
if(debug['R'] && debug['v'])
- print("del %04d %P\\n", r->pc, p);
+ print("del %04d %P\\n", (int)r->pc, p);
p->as = ANOP;
}
}
@@ -1520,7 +1520,7 @@ fixjmp(Reg *firstr)
if(debug['R'] && debug['v']) {
print("\n");
for(r=firstr; r; r=r->link)
- print("%04d %P\\n", r->pc, r->prog);
+ print("%04d %P\\n", (int)r->pc, r->prog);
print("\n");
}
}
src/cmd/8c/reg.c
--- a/src/cmd/8c/reg.c
+++ b/src/cmd/8c/reg.c
@@ -558,7 +558,7 @@ brk:
if(debug['R'] && debug['v']) {
print("after pass 7 (peep)\n");
for(r=firstr; r; r=r->link)
- print("%04d %P\\n", r->pc, r->prog);
+ print("%04d %P\\n", (int)r->pc, r->prog);
print("\n");
}
@@ -1375,7 +1375,7 @@ fixjmp(Reg *firstr)
for(r=firstr; r; r=r->link) {
p = r->prog;
if(debug['R'] && debug['v'])
- print("%04d %P\\n", r->pc, p);
+ print("%04d %P\\n", (int)r->pc, p);
if(p->as != ACALL && p->to.type == D_BRANCH && r->s2 && r->s2->prog->as == AJMP) {
r->s2 = chasejmp(r->s2, &jmploop);
p->to.offset = r->s2->pc;
@@ -1400,7 +1400,7 @@ fixjmp(Reg *firstr)
// Let it stay.
} else {
if(debug['R'] && debug['v'])
- print("del %04d %P\\n", r->pc, p);
+ print("del %04d %P\\n", (int)r->pc, p);
p->as = ANOP;
}
}
@@ -1413,7 +1413,7 @@ fixjmp(Reg *firstr)
p = r->prog;
if(p->as == AJMP && p->to.type == D_BRANCH && r->s2 == r->link) {
if(debug['R'] && debug['v'])
- print("del %04d %P\\n", r->pc, p);
+ print("del %04d %P\\n", (int)r->pc, p);
p->as = ANOP;
}
}
@@ -1434,7 +1434,7 @@ fixjmp(Reg *firstr)
if(debug['R'] && debug['v']) {
print("\n");
for(r=firstr; r; r=r->link)
- print("%04d %P\\n", r->pc, r->prog);
+ print("%04d %P\\n", (int)r->pc, r->prog);
print("\n");
}
}
src/cmd/8g/reg.c
--- a/src/cmd/8g/reg.c
+++ b/src/cmd/8g/reg.c
@@ -1138,7 +1138,7 @@ dumpit(char *str, Flow *r0, int isreg)
r1 = r->p2;
if(r1 != nil) {
print("\tpred:");
- for(; r1 != nil; r1 = r->p2link)
+ for(; r1 != nil; r1 = r1->p2link)
print(" %.4ud", (int)r1->prog->pc);
print("\n");
}
コアとなるコードの解説
src/cmd/6c/reg.c および src/cmd/8c/reg.c の変更
これらのファイルでは、fixjmp関数内のデバッグ出力行で、r->pcの値をprint関数に渡す際に、明示的に(int)にキャストする変更が加えられています。
例:
- print("%04d %P\\n", r->pc, p);
+ print("%04d %P\\n", (int)r->pc, p);
r->pcはvlong型(符号なし64ビット整数)であり、%04dフォーマット指定子は通常int型(32ビット整数)を期待します。この変更は、print関数がvlongを直接処理できない、または%04dがint型として解釈されることを前提としているため、明示的なキャストによってコンパイラの警告を回避し、かつprint関数が期待する引数の型に合わせることを目的としています。これにより、PCの値がintの範囲内であれば正しく表示されるようになります。もしPCの値がintの範囲を超える場合、このキャストによって値が切り捨てられる可能性がありますが、この修正は当時のprint関数の実装とデバッグの要件に合わせたものと考えられます。
src/cmd/8g/reg.c の変更
このファイルでは、dumpit関数内のforループの条件が修正されています。
例:
- for(; r1 != nil; r1 = r->p2link)
+ for(; r1 != nil; r1 = r1->p2link)
元のコードでは、ループのイテレーション変数r1を更新する際に、r->p2linkを使用していました。ここでrはループの外側で定義されたFlow構造体へのポインタであり、r1が指す現在のFlow構造体とは異なります。これは論理的な誤りであり、r1が正しく次の要素に進まないため、無限ループや不正なメモリアクセスを引き起こす可能性がありました。
修正後は、r1 = r1->p2linkとすることで、現在処理しているFlow構造体r1のp2linkメンバーを次のイテレーションのr1に代入しています。p2linkは、Flow構造体のリストを連結するためのポインタであり、これによりループが正しく先行ブロックのリストをたどることができるようになります。この修正は、dumpit関数が制御フローグラフの先行ブロックを正確にダンプするために不可欠です。
これらの変更は、Goコンパイラのデバッグ出力の正確性と、内部ロジックの堅牢性を向上させるための重要な修正です。
関連リンク
- Goコンパイラのソースコード: https://github.com/golang/go
- GoのChange List (CL) について: https://go.dev/doc/contribute#_change_lists
参考にした情報源リンク
- Go CL 62700044: https://golang.org/cl/62700044
- Goコンパイラの内部構造に関する一般的な情報 (Goのドキュメントやブログ記事など)
- A Quick Guide to Go's New Compiler: https://go.dev/blog/go1.5compiler (このコミットはGo 1.5以前のものですが、コンパイラの基本的な構造理解に役立ちます)
- The Go Programming Language Specification: https://go.dev/ref/spec
- C言語の型キャストと
printfフォーマット指定子に関する一般的な情報- C Standard Library
printf: https://en.cppreference.com/w/c/io/fprintf
- C Standard Library
- プログラムカウンタ (PC) の概念: https://en.wikipedia.org/wiki/Program_counter
- 制御フローグラフ (CFG) の概念: https://en.wikipedia.org/wiki/Control_flow_graph
- レジスタ割り当て (Register Allocation) の概念: https://en.wikipedia.org/wiki/Register_allocation
[インデックス 18490] ファイルの概要
このコミットは、Goコンパイラのcmd/6c (ARM 64-bit), cmd/8c (x86 32-bit), および cmd/8g (x86 64-bit) におけるデバッグ出力の修正と、cmd/8g内のループの誤りを修正するものです。具体的には、プログラムカウンタ (PC) の値がvlong型であるにもかかわらず、print関数で正しく表示されていなかった問題と、cmd/8g/reg.c内のdumpit関数におけるforループのイテレーション変数の誤用が修正されています。
コミット
commit ca6186aa269eebf62d8a89d846a0eed4108e3b66
Author: Shenghou Ma <minux.ma@gmail.com>
Date: Thu Feb 13 03:09:03 2014 -0500
cmd/6c, cmd/8c, cmd/8g: fix print of pc (which is vlong).
While we're at it, fix a wrong for statement in cmd/8g.
LGTM=rsc
R=rsc, golang-codereviews
CC=golang-codereviews
https://golang.org/cl/62700044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/ca6186aa269eebf62d8a89d846a0eed4108e3b66
元コミット内容
Goコンパイラのcmd/6c、cmd/8c、cmd/8gにおいて、プログラムカウンタ(PC)の表示に関するバグを修正します。PCはvlong型であるにもかかわらず、print関数での出力時に適切な型変換が行われていませんでした。また、cmd/8g内の誤ったfor文も同時に修正します。
変更の背景
このコミットの背景には、Goコンパイラのデバッグ出力の正確性を向上させるという目的があります。特に、コンパイラのレジスタ割り当てやジャンプ最適化の段階で出力されるプログラムカウンタの値が、内部的にはvlong(符号なし64ビット整数)として扱われているにもかかわらず、デバッグ出力時にはその型が正しく考慮されていなかったため、表示が不正確になる可能性がありました。
Goコンパイラは、異なるアーキテクチャ(例: 6cはARM 64-bit、8cはx86 32-bit、8gはx86 64-bit)向けにコードを生成する際に、それぞれ専用のコンパイラフロントエンドを持っています。これらのコンパイラは、コード生成の過程で中間表現を操作し、最適化を行います。reg.cファイルは、これらのコンパイラにおけるレジスタ割り当てや命令の並べ替え、ジャンプ命令の最適化など、バックエンドの重要な部分を担っています。
デバッグ時にプログラムカウンタの正確な値を確認することは、コンパイラの動作を理解し、バグを特定する上で不可欠です。vlong型は、特に64ビットアーキテクチャにおいて、より大きなアドレス空間を表現するために使用されます。この値が正しく表示されないと、デバッグ作業が困難になるため、この修正はコンパイラの開発とデバッグの効率化に寄与します。
また、cmd/8g/reg.cにおけるforループの誤りは、コードの正確な動作を妨げる可能性があり、これも同時に修正されました。これは、コンパイラの内部ロジックの堅牢性を高めるための一般的なコード品質改善の一環と考えられます。
前提知識の解説
Goコンパイラの構造 (cmd/6c, cmd/8c, cmd/8g)
Goコンパイラは、伝統的にPlan 9 Cコンパイラの設計思想を受け継いでおり、異なるアーキテクチャごとに独立したコンパイラバイナリが存在します。
cmd/6c: かつてGoプロジェクトで使用されていた、amd64アーキテクチャ(6は64-bit x86を指す)向けのCコンパイラです。Goのソースコードを中間表現に変換し、最終的にamd64のアセンブリコードを生成する過程で使用されていました。cmd/8c: x86 32-bit (IA-32) アーキテクチャ向けのCコンパイラ。Goのソースコードを中間表現に変換し、x86 32-bitのアセンブリコードを生成する過程で使用されます。cmd/8g: x86 64-bit (x86-64) アーキテクチャ向けのGoコンパイラ。Goのソースコードを中間表現に変換し、x86 64-bitのアセンブリコードを生成する過程で使用されます。Goコンパイラは、C言語で書かれた部分とGo言語で書かれた部分が混在しており、これらのcmd/*cやcmd/*gは、そのC言語で書かれた部分のコンパイラを指します。
これらのコンパイラは、ソースコードの解析、型チェック、中間表現の生成、最適化、コード生成といった一連のフェーズを実行します。reg.cファイルは、主にバックエンドの最適化フェーズ、特にレジスタ割り当てとジャンプ命令の最適化に関連するコードを含んでいます。
プログラムカウンタ (PC)
プログラムカウンタ(Program Counter, PC)は、CPUが次に実行する命令のアドレスを保持するレジスタです。コンパイラの文脈では、生成されるアセンブリコード内の特定の命令のアドレスや、中間表現における命令の相対的な位置を示すために使用されます。デバッグ時には、PCの値を見ることで、プログラムが現在どの命令を実行しているかを追跡できます。
vlong型
vlongは、Goコンパイラの内部で使われるデータ型の一つで、符号なし64ビット整数(unsigned long longに相当)を表します。これは、特に64ビットアーキテクチャにおいて、メモリのアドレスや大きな数値、プログラムカウンタの値などを表現するために使用されます。C言語のprintf系関数でlong longを扱う場合、通常は%lldや%lluのようなフォーマット指定子を使用しますが、Goコンパイラの内部print関数では、vlong型をint型にキャストして出力しようとしていたため、値が切り捨てられたり、正しく表示されなかったりする問題が発生していました。
print関数とデバッグ出力
Goコンパイラのソースコード内には、デバッグ情報を出力するための独自のprint関数が定義されています。これは標準Cライブラリのprintfとは異なり、コンパイラの内部構造や特定のデバッグフラグ(例: debug['R'], debug['v'])に基づいて動作します。このprint関数が、vlong型のPC値を正しくフォーマットして出力できるように修正することが、このコミットの主要な目的です。
Reg構造体とFlow構造体
Goコンパイラのバックエンド、特にレジスタ割り当てやコード最適化のフェーズでは、プログラムの制御フローを表現するために様々なデータ構造が使用されます。
Reg構造体:reg.cファイルで頻繁に登場するReg構造体は、レジスタ割り当てグラフのノードや、命令のシーケンスを表すために使用される可能性があります。各Regインスタンスは、特定の命令(Prog構造体で表される)や、その命令に関連するレジスタ情報、制御フロー情報(前後の命令へのリンクなど)を保持します。r->pcはこのReg構造体の一部であり、関連する命令のプログラムカウンタを示します。Flow構造体:cmd/8g/reg.cのdumpit関数で言及されているFlow構造体は、制御フローグラフ(Control Flow Graph, CFG)のノードを表すために使用される可能性があります。CFGは、プログラムの実行パスをグラフとして表現したもので、最適化の際に重要な役割を果たします。r1->prog->pcは、Flow構造体に関連付けられた命令のプログラムカウンタを示します。
技術的詳細
このコミットの技術的詳細は、主にC言語の型変換とポインタ操作、そしてコンパイラのデバッグ出力メカニズムに焦点を当てています。
pcの型変換の修正
変更の核心は、r->pcおよびr1->prog->pcの値をprint関数に渡す際に、明示的に(int)にキャストしていた部分を修正することです。pcはvlong型(符号なし64ビット整数)であるため、これをint型(通常32ビット整数)にキャストすると、値がintの最大値を超える場合に情報が失われ、不正確な出力となります。
修正前:
print("%04d %P\\n", r->pc, p);
修正後:
print("%04d %P\\n", (int)r->pc, p); // これは元のコードの誤り。正しくは %lld または %llu を使うべきだが、Goコンパイラのprint関数は独自のフォーマットを持つため、ここでは(int)キャストが意図された修正。
注釈: 実際のGoコンパイラのprint関数は、標準Cライブラリのprintfとは異なり、独自のフォーマット指定子と引数の解釈を持っています。このコミットの文脈では、%04dがint型の引数を期待しているため、vlong型のr->pcをintにキャストすることで、print関数が期待する型に合わせていると解釈できます。これは、print関数自体がvlongを直接扱えるように設計されていないか、あるいは%04dが特定の範囲のPC値のみを想定しているため、明示的なキャストが必要とされた可能性があります。もしvlong全体を正確に表示する必要がある場合は、print関数自体を拡張するか、別のフォーマット指定子を使用する必要があります。この修正は、既存のprint関数の制約内で、PCの表示を「修正」するためのものです。
cmd/8g/reg.cにおけるforループの修正
src/cmd/8g/reg.cのdumpit関数内のforループには、イテレーション変数の誤用がありました。
修正前:
for(; r1 != nil; r1 = r->p2link)
このコードでは、ループの更新部分でr->p2linkを使用しています。rはループの外側で定義された変数であり、r1が指す現在の要素とは関係ありません。これにより、r1が正しく次の要素に進まず、無限ループに陥るか、予期せぬ動作を引き起こす可能性がありました。
修正後:
for(; r1 != nil; r1 = r1->p2link)
修正後は、ループの更新部分でr1->p2linkを使用しています。これにより、r1が現在処理しているFlow構造体の次の要素へのリンクを正しくたどり、ループが期待通りに機能するようになります。p2linkは、Flow構造体内のポインタであり、制御フローグラフにおける先行ブロック(predecessor)のリストを連結するために使用されます。
fixjmp関数とdumpit関数
fixjmp関数:src/cmd/6c/reg.cとsrc/cmd/8c/reg.cの両方に存在するfixjmp関数は、ジャンプ命令の最適化を担当します。具体的には、不要なジャンプ命令(例:JMP L1; L1: ...のような冗長なジャンプや、JMP L1の後にL1が続くようなケース)を削除したり、ジャンプのターゲットを直接的なものに修正したりします。この関数内でデバッグ出力としてPCが表示されるため、その表示の正確性が重要になります。dumpit関数:src/cmd/8g/reg.cに存在するdumpit関数は、制御フローグラフやレジスタ割り当ての状態をデバッグ目的でダンプ(出力)するために使用されます。この関数内で先行ブロックのPC値が出力される際に、forループの誤りがあったため、その修正が行われました。
これらの修正は、Goコンパイラのバックエンドにおけるデバッグの信頼性を高め、コンパイラ開発者がより効率的に問題を診断できるようにすることを目的としています。
コアとなるコードの変更箇所
src/cmd/6c/reg.c
--- a/src/cmd/6c/reg.c
+++ b/src/cmd/6c/reg.c
@@ -1461,7 +1461,7 @@ fixjmp(Reg *firstr)
for(r=firstr; r; r=r->link) {
p = r->prog;
if(debug['R'] && debug['v'])
- print("%04d %P\\n", r->pc, p);
+ print("%04d %P\\n", (int)r->pc, p);
if(p->as != ACALL && p->to.type == D_BRANCH && r->s2 && r->s2->prog->as == AJMP) {
r->s2 = chasejmp(r->s2, &jmploop);
p->to.offset = r->s2->pc;
@@ -1486,7 +1486,7 @@ fixjmp(Reg *firstr)
// Let it stay.
} else {
if(debug['R'] && debug['v'])
- print("del %04d %P\\n", r->pc, p);
+ print("del %04d %P\\n", (int)r->pc, p);
p->as = ANOP;
}
}
@@ -1499,7 +1499,7 @@ fixjmp(Reg *firstr)
p = r->prog;
if(p->as == AJMP && p->to.type == D_BRANCH && r->s2 == r->link) {
if(debug['R'] && debug['v'])
- print("del %04d %P\\n", r->pc, p);
+ print("del %04d %P\\n", (int)r->pc, p);
p->as = ANOP;
}
}
@@ -1520,7 +1520,7 @@ fixjmp(Reg *firstr)
if(debug['R'] && debug['v']) {
print("\n");
for(r=firstr; r; r=r->link)
- print("%04d %P\\n", r->pc, r->prog);
+ print("%04d %P\\n", (int)r->pc, r->prog);
print("\n");
}
}
src/cmd/8c/reg.c
--- a/src/cmd/8c/reg.c
+++ b/src/cmd/8c/reg.c
@@ -558,7 +558,7 @@ brk:
if(debug['R'] && debug['v']) {
print("after pass 7 (peep)\n");
for(r=firstr; r; r=r->link)
- print("%04d %P\\n", r->pc, r->prog);
+ print("%04d %P\\n", (int)r->pc, r->prog);
print("\n");
}
@@ -1375,7 +1375,7 @@ fixjmp(Reg *firstr)
for(r=firstr; r; r=r->link) {
p = r->prog;
if(debug['R'] && debug['v'])
- print("%04d %P\\n", r->pc, p);
+ print("%04d %P\\n", (int)r->pc, p);
if(p->as != ACALL && p->to.type == D_BRANCH && r->s2 && r->s2->prog->as == AJMP) {
r->s2 = chasejmp(r->s2, &jmploop);
p->to.offset = r->s2->pc;
@@ -1400,7 +1400,7 @@ fixjmp(Reg *firstr)
// Let it stay.
} else {
if(debug['R'] && debug['v'])
- print("del %04d %P\\n", r->pc, p);
+ print("del %04d %P\\n", (int)r->pc, p);
p->as = ANOP;
}
}
@@ -1413,7 +1413,7 @@ fixjmp(Reg *firstr)
p = r->prog;
if(p->as == AJMP && p->to.type == D_BRANCH && r->s2 == r->link) {
if(debug['R'] && debug['v'])
- print("del %04d %P\\n", r->pc, p);
+ print("del %04d %P\\n", (int)r->pc, p);
p->as = ANOP;
}
}
@@ -1434,7 +1434,7 @@ fixjmp(Reg *firstr)
if(debug['R'] && debug['v']) {
print("\n");
for(r=firstr; r; r=r->link)
- print("%04d %P\\n", r->pc, r->prog);
+ print("%04d %P\\n", (int)r->pc, r->prog);
print("\n");
}
}
src/cmd/8g/reg.c
--- a/src/cmd/8g/reg.c
+++ b/src/cmd/8g/reg.c
@@ -1138,7 +1138,7 @@ dumpit(char *str, Flow *r0, int isreg)
r1 = r->p2;
if(r1 != nil) {
print("\tpred:");
- for(; r1 != nil; r1 = r->p2link)
+ for(; r1 != nil; r1 = r1->p2link)
print(" %.4ud", (int)r1->prog->pc);
print("\n");
}
コアとなるコードの解説
src/cmd/6c/reg.c および src/cmd/8c/reg.c の変更
これらのファイルでは、fixjmp関数内のデバッグ出力行で、r->pcの値をprint関数に渡す際に、明示的に(int)にキャストする変更が加えられています。
例:
- print("%04d %P\\n", r->pc, p);
+ print("%04d %P\\n", (int)r->pc, p);
r->pcはvlong型(符号なし64ビット整数)であり、%04dフォーマット指定子は通常int型(32ビット整数)を期待します。この変更は、print関数がvlongを直接処理できない、または%04dがint型として解釈されることを前提としているため、明示的なキャストによってコンパイラの警告を回避し、かつprint関数が期待する引数の型に合わせることを目的としています。これにより、PCの値がintの範囲内であれば正しく表示されるようになります。もしPCの値がintの範囲を超える場合、このキャストによって値が切り捨てられる可能性がありますが、この修正は当時のprint関数の実装とデバッグの要件に合わせたものと考えられます。
src/cmd/8g/reg.c の変更
このファイルでは、dumpit関数内のforループの条件が修正されています。
例:
- for(; r1 != nil; r1 = r->p2link)
+ for(; r1 != nil; r1 = r1->p2link)
元のコードでは、ループのイテレーション変数r1を更新する際に、r->p2linkを使用していました。ここでrはループの外側で定義されたFlow構造体へのポインタであり、r1が指す現在のFlow構造体とは異なります。これは論理的な誤りであり、r1が正しく次の要素に進まないため、無限ループや不正なメモリアクセスを引き起こす可能性がありました。
修正後は、r1 = r1->p2linkとすることで、現在処理しているFlow構造体r1のp2linkメンバーを次のイテレーションのr1に代入しています。p2linkは、Flow構造体のリストを連結するためのポインタであり、これによりループが正しく先行ブロックのリストをたどることができるようになります。この修正は、dumpit関数が制御フローグラフの先行ブロックを正確にダンプするために不可欠です。
これらの変更は、Goコンパイラのデバッグ出力の正確性と、内部ロジックの堅牢性を向上させるための重要な修正です。
関連リンク
- Goコンパイラのソースコード: https://github.com/golang/go
- GoのChange List (CL) について: https://go.dev/doc/contribute#_change_lists
参考にした情報源リンク
- Go CL 62700044: https://golang.org/cl/62700044
- Goコンパイラの内部構造に関する一般的な情報 (Goのドキュメントやブログ記事など)
- A Quick Guide to Go's New Compiler: https://go.dev/blog/go1.5compiler (このコミットはGo 1.5以前のものですが、コンパイラの基本的な構造理解に役立ちます)
- The Go Programming Language Specification: https://go.dev/ref/spec
- C言語の型キャストと
printfフォーマット指定子に関する一般的な情報- C Standard Library
printf: https://en.cppreference.com/w/c/io/fprintf
- C Standard Library
- プログラムカウンタ (PC) の概念: https://en.wikipedia.org/wiki/Program_counter
- 制御フローグラフ (CFG) の概念: https://en.wikipedia.org/wiki/Control_flow_graph
- レジスタ割り当て (Register Allocation) の概念: https://en.wikipedia.org/wiki/Register_allocation