[インデックス 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