[インデックス 1147] ファイルの概要
このコミットは、Go言語の初期のコードベースにおける src/cmd/cov/main.c
ファイルに対する変更です。src/cmd/cov
は、Goプログラムのコードカバレッジを測定するためのツール (cov
コマンド) のソースコードが含まれるディレクトリです。main.c
はそのツールの主要なC言語ソースファイルであり、Goプログラムのバイナリを解析し、実行されたコードパスを特定するロジックを含んでいます。
コミット
- コミットハッシュ:
4d1d5e8a88673d8866b80942575c2532048626f3
- 作者: Russ Cox rsc@golang.org
- コミット日時: Mon Nov 17 17:16:50 2008 -0800
- 変更ファイル:
src/cmd/cov/main.c
- 変更行数: 23行追加, 0行削除, 0行変更 (合計23行の追加)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4d1d5e8a88673d8866b80942575c2532048626f3
元コミット内容
work around more commonly-unreachable 6g code sequences.
R=r
DELTA=23 (23 added, 0 deleted, 0 changed)
OCL=19405
CL=19420
変更の背景
このコミットは、Go言語の初期のコンパイラである 6g
(64-bit x86アーキテクチャ向けのGoコンパイラ) が生成する特定のコードシーケンスが、コードカバレッジツール (cov
) によって「到達不能 (unreachable)」と誤認識される問題を回避するためのものです。
コードカバレッジツールは、プログラムの実行中にどのコードが実行されたかを追跡することで機能します。通常、これはバイナリに計測コードを挿入するか、実行時の命令ポインタの動きを監視することで実現されます。しかし、コンパイラが特定の最適化を行うと、生成されるアセンブリコードがカバレッジツールの期待するパターンと異なり、実際には到達可能なコードパスを誤って到達不能と判断してしまうことがあります。
具体的には、x86アーキテクチャにおける大きなシフト操作の実装において、コンパイラが XOR
命令とシフト命令 (SHL
/SHR
) を組み合わせたシーケンスを生成することがありました。このシーケンスは、カバレッジツールが通常検出するような単純な分岐や関数呼び出しとは異なるため、ツールがそのコードブロックをスキップしてしまう可能性がありました。このコミットは、cov
ツールがこのような特定の 6g
生成コードパターンを正しく認識し、カバレッジ計算から除外することで、誤った「到達不能」の報告を避けることを目的としています。
OCL
(Old Change List) と CL
(Change List) は、Google内部のコードレビューシステムで使用されていた変更セットの識別子です。このコミットは、Goがオープンソース化される前のGoogle内部での開発プロセスの一部であったことを示しています。
前提知識の解説
1. 6g
コンパイラ
6g
は、Go言語の初期に存在したコンパイラの一つで、主に64ビットx86アーキテクチャ (AMD64/x86-64) をターゲットとしていました。Go言語のコンパイラは、ターゲットアーキテクチャごとに異なる名前が付けられていました(例: 8g
は32ビットx86、5g
はARMなど)。現在では、これらのコンパイラは統合され、go build
コマンドによって自動的に適切なコンパイラが選択されるようになっています。このコミットが作成された2008年当時は、6g
がGo開発において重要な役割を担っていました。
2. コードカバレッジツール (cov
)
コードカバレッジは、ソフトウェアテストの品質を評価するための指標の一つで、テストスイートがソースコードのどの部分を実行したかを示します。cov
は、Go言語の初期に提供されていたコードカバレッジ測定ツールです。
コードカバレッジツールは通常、以下のいずれかの方法で機能します。
- インストゥルメンテーション: コンパイル時に、コードの各ブロックの開始点にカウンタを増やすような命令を挿入します。プログラム実行後、これらのカウンタの値を分析して、どのブロックが実行されたかを判断します。
- 実行時プロファイリング: プログラムの実行中に命令ポインタの動きを監視し、実行された命令のアドレスを記録します。
このコミットの文脈では、cov
ツールがGoプログラムのコンパイル済みバイナリを解析し、実行パスを追跡しようとしていたことが示唆されます。
3. x86アセンブリ命令
コミットメッセージとコードスニペットには、x86アセンブリ命令がいくつか登場します。
XORL AX, AX
/XORQ AX, AX
:XOR
(Exclusive OR) 命令は、オペランド間でビットごとの排他的論理和を計算します。XORL
は32ビットレジスタ (L
for Long),XORQ
は64ビットレジスタ (Q
for Quad) を対象とします。XORL AX, AX
のように同じレジスタに対してXOR
を行うと、そのレジスタの値は0
になります。これは、レジスタをゼロクリアするための一般的な最適化手法です。SHLL CL, AX
/SHLQ CL, AX
:SHL
(Shift Left) 命令は、指定されたビット数だけオペランドのビットを左にシフトします。SHLL
は32ビット、SHLQ
は64ビットを対象とします。CL
レジスタは、シフトするビット数を指定するためによく使用されます。CMPL CX, $20
/CMPL CX, $40
:CMP
(Compare) 命令は、2つのオペランドを比較し、フラグレジスタを設定します。これは通常、その後の条件分岐命令 (JCS
など) と組み合わせて使用されます。CMPL
は32ビットオペランドを比較します。JCS f+97(SB)
/JCS f+d0(SB)
:JCS
(Jump if Carry Set) 命令は、キャリーフラグ (CF) がセットされている場合にジャンプします。シフト操作などでオーバーフローが発生するとキャリーフラグがセットされることがあります。この文脈では、シフト操作の前に条件分岐が行われ、特定の条件でシフトがスキップされる可能性があることを示唆しています。
4. 到達不能なコード (Unreachable Code)
プログラミングにおいて「到達不能なコード」とは、プログラムの実行フローにおいて、どのような入力や条件の下でも決して実行されないコードブロックを指します。これは通常、バグ、論理エラー、またはコンパイラの最適化によって発生します。コードカバレッジツールが到達不能なコードを報告する場合、それはテストが不十分であるか、あるいはツールがコードの実行パスを誤解しているかのいずれかを示します。このコミットのケースでは、後者の「ツールがコードの実行パスを誤解している」が問題でした。
技術的詳細
このコミットの技術的詳細は、6g
コンパイラが生成する特定のアセンブリコードパターンと、cov
ツールがそれをどのように解釈するかという問題にあります。
Go言語の初期のコンパイラである 6g
は、特に大きなシフト操作(例えば、32ビットや64ビットの値を非常に大きなビット数だけシフトする場合)を最適化する際に、直接的なシフト命令だけでなく、レジスタをゼロクリアする XOR
命令を組み合わせたシーケンスを生成することがありました。これは、シフト操作の前にレジスタを確実にゼロにすることで、シフト結果の正確性を保証したり、特定のハードウェアの挙動に対応したりするためです。
コミットメッセージ内のコメントにあるアセンブリコードの例を見てみましょう。
// f+90 0x00002c9f CMPL CX,$20
// f+93 0x00002ca2 JCS f+97(SB)
// f+95 0x00002ca4 XORL AX,AX <<<
// f+97 0x00002ca6 SHLL CL,AX
// f+99 0x00002ca8 MOVL $1,CX
このシーケンスでは、まず CMPL
と JCS
で条件分岐が行われ、特定の条件(CX
が 20
と比較され、キャリーフラグがセットされる場合)で f+97(SB)
へジャンプします。このジャンプ先は XORL AX,AX
命令をスキップし、直接 SHLL CL,AX
へ進みます。
つまり、XORL AX,AX
は常に実行されるわけではなく、特定の条件でのみ実行されるか、あるいは全く実行されないパスが存在する可能性があります。
同様に、64ビット版の例もあります。
// f+c8 0x00002cd7 CMPL CX,$40
// f+cb 0x00002cda JCS f+d0(SB)
// f+cd 0x00002cdc XORQ AX,AX <<<
// f+d0 0x00002cdf SHLQ CL,AX
// f+d3 0x00002ce2 MOVQ $1,CX
ここでも XORQ AX,AX
が条件付きでスキップされる可能性があります。
cov
ツールは、プログラムの実行パスを追跡する際に、命令の連続性を分析します。しかし、上記のような XOR
命令が条件分岐によってスキップされる可能性がある場合、cov
ツールは XOR
命令が続くシフト命令の「前処理」として常に実行されると誤解し、その XOR
命令を含むブロックを「到達不能」と判断してしまうことがありました。これは、cov
ツールがアセンブリレベルでの複雑な分岐ロジックを完全に理解していなかったためと考えられます。
このコミットは、cov
ツールがこのような特定のパターン(XOR
命令の直後にシフト命令が続く、かつ命令長が2バイトまたは3バイトの短いシーケンス)を検出した場合に、それをカバレッジ計算から除外する(つまり、誤って到達不能と報告しない)ように修正しています。これにより、cov
ツールがより正確なカバレッジレポートを生成できるようになります。
コアとなるコードの変更箇所
--- a/src/cmd/cov/main.c
+++ b/src/cmd/cov/main.c
@@ -160,6 +160,29 @@ missing(uvlong pc, uvlong epc)
return;
}
+ if(epc - pc == 2 || epc -pc == 3) {
+ // check for XORL inside shift.
+ // (on x86 have to implement large shift with explicit zeroing).
+ // f+90 0x00002c9f CMPL CX,$20
+ // f+93 0x00002ca2 JCS f+97(SB)
+ // f+95 0x00002ca4 XORL AX,AX <<<
+ // f+97 0x00002ca6 SHLL CL,AX
+ // f+99 0x00002ca8 MOVL $1,CX
+ //
+ // f+c8 0x00002cd7 CMPL CX,$40
+ // f+cb 0x00002cda JCS f+d0(SB)
+ // f+cd 0x00002cdc XORQ AX,AX <<<
+ // f+d0 0x00002cdf SHLQ CL,AX
+ // f+d3 0x00002ce2 MOVQ $1,CX
+ buf[0] = 0;
+ machdata->das(text, pc, 0, buf, sizeof buf);
+ if(strncmp(buf, "XOR", 3) == 0) {
+ machdata->das(text, epc, 0, buf, sizeof buf);
+ if(strncmp(buf, "SHL", 3) == 0 || strncmp(buf, "SHR", 3) == 0)
+ return;
+ }
+ }
+
// show first instruction to make clear where we were.
machdata->das(text, pc, 0, buf, sizeof buf);
コアとなるコードの解説
追加されたコードは、missing
関数内にあります。この関数は、コードカバレッジの観点から「欠落している」と判断されたコードセグメントを処理する役割を担っていると考えられます。
追加された if
ブロックのロジックは以下の通りです。
-
if(epc - pc == 2 || epc -pc == 3)
:pc
は現在の命令ポインタ (Program Counter)、epc
は次の命令ポインタまたは現在のコードブロックの終了アドレスを示していると推測されます。- この条件は、現在の命令と次の命令の間のバイト長が2バイトまたは3バイトである場合に真となります。これは、
XORL AX,AX
(2バイト) やXORQ AX,AX
(3バイト) のような短い命令シーケンスを検出するためのヒューリスティックです。
-
コメントブロック:
- このコメントは、
6g
コンパイラがx86アーキテクチャで大きなシフト操作を実装する際に、明示的なゼロクリア (XOR
) を使用する必要があることを説明しています。 - 具体的なアセンブリコードの例が示されており、
XORL AX,AX
やXORQ AX,AX
が条件分岐 (JCS
) によってスキップされる可能性があることが視覚的に示されています。
- このコメントは、
-
buf[0] = 0; machdata->das(text, pc, 0, buf, sizeof buf);
:buf
は命令の逆アセンブル結果を格納するためのバッファです。machdata->das
は、指定されたアドレス (pc
) の命令を逆アセンブルし、そのテキスト表現をbuf
に書き込む関数です。das
は "disassemble" の略である可能性が高いです。
-
if(strncmp(buf, "XOR", 3) == 0)
:- 逆アセンブルされた命令の先頭3文字が "XOR" であるかをチェックします。これにより、現在の命令が
XORL
またはXORQ
であることを確認します。
- 逆アセンブルされた命令の先頭3文字が "XOR" であるかをチェックします。これにより、現在の命令が
-
machdata->das(text, epc, 0, buf, sizeof buf);
:- 現在の命令が
XOR
であった場合、次の命令 (epc
で示されるアドレス) を逆アセンブルします。
- 現在の命令が
-
if(strncmp(buf, "SHL", 3) == 0 || strncmp(buf, "SHR", 3) == 0)
:- 次の命令の先頭3文字が "SHL" (Shift Left) または "SHR" (Shift Right) であるかをチェックします。
-
return;
:- 上記のすべての条件(命令長が2または3バイト、現在の命令が
XOR
、次の命令がSHL
またはSHR
)が満たされた場合、missing
関数からreturn
します。 - これは、この特定のコードシーケンスが「到達不能」であると誤って報告されるのを防ぐためのものです。つまり、
cov
ツールはこのパターンを検出した場合、それをカバレッジの欠落とは見なさず、処理を終了します。
- 上記のすべての条件(命令長が2または3バイト、現在の命令が
この修正は、アセンブリレベルでの特定のコンパイラ最適化パターンを認識し、カバレッジツールの誤検出を回避するための、非常に低レベルかつ具体的なワークアラウンドです。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Go言語の初期の歴史に関する情報 (Goのブログなど): https://go.dev/blog/
参考にした情報源リンク
- Go言語の初期のコンパイラに関する情報 (例:
6g
):- https://go.dev/doc/install/source (古いGoのドキュメントにはコンパイラ名に関する言及がある場合があります)
- https://go.dev/blog/go-compiler-internals (コンパイラの進化に関するブログ記事)
- x86アセンブリ命令 (
XOR
,SHL
,SHR
,CMP
,JCS
):- Intel 64 and IA-32 Architectures Software Developer's Manuals (公式ドキュメント)
- オンラインのアセンブリ命令リファレンス (例: https://www.felixcloutier.com/x86/)
- コードカバレッジの概念:
- ソフトウェアテストに関する一般的な情報源
- Go言語の
go test -cover
コマンドに関するドキュメント: https://go.dev/blog/cover (これは後のカバレッジツールですが、概念は共通です)
- OCL/CL (Google内部の変更管理システム):
- Go言語の初期のコミットメッセージによく見られるもので、Google内部のコードレビュープロセスを示唆しています。具体的な公開ドキュメントは少ないですが、Goの歴史に関する記事で言及されることがあります。
[インデックス 1147] ファイルの概要
このコミットは、Go言語の初期のコードベースにおける src/cmd/cov/main.c
ファイルに対する変更です。src/cmd/cov
は、Goプログラムのコードカバレッジを測定するためのツール (cov
コマンド) のソースコードが含まれるディレクトリです。main.c
はそのツールの主要なC言語ソースファイルであり、Goプログラムのバイナリを解析し、実行されたコードパスを特定するロジックを含んでいます。
コミット
- コミットハッシュ:
4d1d5e8a88673d8866b80942575c2532048626f3
- 作者: Russ Cox rsc@golang.org
- コミット日時: Mon Nov 17 17:16:50 2008 -0800
- コミットメッセージ:
work around more commonly-unreachable 6g code sequences. R=r DELTA=23 (23 added, 0 deleted, 0 changed) OCL=19405 CL=19420
- 変更ファイル:
src/cmd/cov/main.c
- 変更行数: 23行追加, 0行削除, 0行変更 (合計23行の追加)
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4d1d5e8a88673d8866b80942575c2532048626f3
元コミット内容
work around more commonly-unreachable 6g code sequences.
R=r
DELTA=23 (23 added, 0 deleted, 0 changed)
OCL=19405
CL=19420
変更の背景
このコミットは、Go言語の初期のコンパイラである 6g
(64-bit x86アーキテクチャ向けのGoコンパイラ) が生成する特定のコードシーケンスが、コードカバレッジツール (cov
) によって「到達不能 (unreachable)」と誤認識される問題を回避するためのものです。
コードカバレッジツールは、プログラムの実行中にどのコードが実行されたかを追跡することで機能します。通常、これはバイナリに計測コードを挿入するか、実行時の命令ポインタの動きを監視することで実現されます。しかし、コンパイラが特定の最適化を行うと、生成されるアセンブリコードがカバレッジツールの期待するパターンと異なり、実際には到達可能なコードパスを誤って到達不能と判断してしまうことがあります。
具体的には、x86アーキテクチャにおける大きなシフト操作の実装において、コンパイラが XOR
命令とシフト命令 (SHL
/SHR
) を組み合わせたシーケンスを生成することがありました。このシーケンスは、カバレッジツールが通常検出するような単純な分岐や関数呼び出しとは異なるため、ツールがそのコードブロックをスキップしてしまう可能性がありました。このコミットは、cov
ツールがこのような特定の 6g
生成コードパターンを正しく認識し、カバレッジ計算から除外することで、誤った「到達不能」の報告を避けることを目的としています。
OCL
(Old Change List) と CL
(Change List) は、Google内部のコードレビューシステムで使用されていた変更セットの識別子です。このコミットは、Goがオープンソース化される前のGoogle内部での開発プロセスの一部であったことを示しています。
前提知識の解説
1. 6g
コンパイラ
6g
は、Go言語の初期に存在したコンパイラの一つで、主に64ビットx86アーキテクチャ (AMD64/x86-64) をターゲットとしていました。Go言語のコンパイラは、ターゲットアーキテクチャごとに異なる名前が付けられていました(例: 8g
は32ビットx86、5g
はARMなど)。
6g
は、Go 1.5でコンパイラがGo自身で書き直され、単一の go tool compile
バイナリに統合されるまで、Go開発において重要な役割を担っていました。この変更により、6g
のような特定のアーキテクチャ名を冠したコンパイラは直接使用されなくなりましたが、このコミットが作成された2008年当時は、Goプログラムをビルドするための主要なツールでした。gc
ツールチェーンの一部であり、高速なコンパイル時間を優先する設計でした。
2. コードカバレッジツール (cov
)
コードカバレッジは、ソフトウェアテストの品質を評価するための指標の一つで、テストスイートがソースコードのどの部分を実行したかを示します。cov
は、Go言語の初期に提供されていたコードカバレッジ測定ツールです。
コードカバレッジツールは通常、以下のいずれかの方法で機能します。
- インストゥルメンテーション: コンパイル時に、コードの各ブロックの開始点にカウンタを増やすような命令を挿入します。プログラム実行後、これらのカウンタの値を分析して、どのブロックが実行されたかを判断します。
- 実行時プロファイリング: プログラムの実行中に命令ポインタの動きを監視し、実行された命令のアドレスを記録します。
このコミットの文脈では、cov
ツールがGoプログラムのコンパイル済みバイナリを解析し、実行パスを追跡しようとしていたことが示唆されます。現代のGoでは、go test -cover
コマンドが標準的なコードカバレッジ測定ツールとして提供されています。
3. x86アセンブリ命令
コミットメッセージとコードスニペットには、x86アセンブリ命令がいくつか登場します。
XOR
(Exclusive OR): ビットごとの排他的論理和を計算します。XORL
は32ビットレジスタ、XORQ
は64ビットレジスタを対象とします。XOR AX, AX
のように同じレジスタに対してXOR
を行うと、そのレジスタの値は0
になります。これは、レジスタをゼロクリアするための一般的な最適化手法であり、MOV reg, 0
よりも効率的な場合があります。SHL
(Shift Left): 指定されたビット数だけオペランドのビットを左にシフトします。SHLL
は32ビット、SHLQ
は64ビットを対象とします。シフトされたビットは最上位ビット (MSB) から失われ、最下位ビット (LSB) にはゼロがシフトインされます。最後のシフトアウトされたビットはキャリーフラグ (CF) に入ります。これは、2のべき乗による乗算によく使用されます。SHR
(Shift Right): 指定されたビット数だけオペランドのビットを右にシフトします。SHR
は論理右シフトであり、最上位ビットにはゼロがシフトインされます。これは、符号なし数の2のべき乗による除算によく使用されます。CMP
(Compare): 2つのオペランドを比較し、結果を破棄しますが、CPUのフラグレジスタ (ゼロフラグ、キャリーフラグ、サインフラグ、オーバーフローフラグなど) を設定します。これは通常、その後の条件分岐命令と組み合わせて使用されます。JCS
(Jump if Carry Set): キャリーフラグ (CF) がセットされている場合 (CF = 1) に、指定されたラベルに制御を転送する条件付きジャンプ命令です。シフト操作などでオーバーフローが発生するとキャリーフラグがセットされることがあります。この文脈では、シフト操作の前に条件分岐が行われ、特定の条件でシフトがスキップされる可能性があることを示唆しています。
4. 到達不能なコード (Unreachable Code)
プログラミングにおいて「到達不能なコード」とは、プログラムの実行フローにおいて、どのような入力や条件の下でも決して実行されないコードブロックを指します。これは通常、バグ、論理エラー、またはコンパイラの最適化によって発生します。コードカバレッジツールが到達不能なコードを報告する場合、それはテストが不十分であるか、あるいはツールがコードの実行パスを誤解しているかのいずれかを示します。このコミットのケースでは、後者の「ツールがコードの実行パスを誤解している」が問題でした。
技術的詳細
このコミットの技術的詳細は、6g
コンパイラが生成する特定のアセンブリコードパターンと、cov
ツールがそれをどのように解釈するかという問題にあります。
Go言語の初期のコンパイラである 6g
は、特に大きなシフト操作(例えば、32ビットや64ビットの値を非常に大きなビット数だけシフトする場合)を最適化する際に、直接的なシフト命令だけでなく、レジスタをゼロクリアする XOR
命令を組み合わせたシーケンスを生成することがありました。これは、シフト操作の前にレジスタを確実にゼロにすることで、シフト結果の正確性を保証したり、特定のハードウェアの挙動に対応したりするためです。
コミットメッセージ内のコメントにあるアセンブリコードの例を見てみましょう。
// f+90 0x00002c9f CMPL CX,$20
// f+93 0x00002ca2 JCS f+97(SB)
// f+95 0x00002ca4 XORL AX,AX <<<
// f+97 0x00002ca6 SHLL CL,AX
// f+99 0x00002ca8 MOVL $1,CX
このシーケンスでは、まず CMPL
と JCS
で条件分岐が行われ、特定の条件(CX
が 20
と比較され、キャリーフラグがセットされる場合)で f+97(SB)
へジャンプします。このジャンプ先は XORL AX,AX
命令をスキップし、直接 SHLL CL,AX
へ進みます。
つまり、XORL AX,AX
は常に実行されるわけではなく、特定の条件でのみ実行されるか、あるいは全く実行されないパスが存在する可能性があります。
同様に、64ビット版の例もあります。
// f+c8 0x00002cd7 CMPL CX,$40
// f+cb 0x00002cda JCS f+d0(SB)
// f+cd 0x00002cdc XORQ AX,AX <<<
// f+d0 0x00002cdf SHLQ CL,AX
// f+d3 0x00002ce2 MOVQ $1,CX
ここでも XORQ AX,AX
が条件付きでスキップされる可能性があります。
cov
ツールは、プログラムの実行パスを追跡する際に、命令の連続性を分析します。しかし、上記のような XOR
命令が条件分岐によってスキップされる可能性がある場合、cov
ツールは XOR
命令が続くシフト命令の「前処理」として常に実行されると誤解し、その XOR
命令を含むブロックを「到達不能」と判断してしまうことがありました。これは、cov
ツールがアセンブリレベルでの複雑な分岐ロジックを完全に理解していなかったためと考えられます。
このコミットは、cov
ツールがこのような特定のパターン(XOR
命令の直後にシフト命令が続く、かつ命令長が2バイトまたは3バイトの短いシーケンス)を検出した場合に、それをカバレッジ計算から除外する(つまり、誤って到達不能と報告しない)ように修正しています。これにより、cov
ツールがより正確なカバレッジレポートを生成できるようになります。
コアとなるコードの変更箇所
--- a/src/cmd/cov/main.c
+++ b/src/cmd/cov/main.c
@@ -160,6 +160,29 @@ missing(uvlong pc, uvlong epc)
return;
}
+ if(epc - pc == 2 || epc -pc == 3) {
+ // check for XORL inside shift.
+ // (on x86 have to implement large shift with explicit zeroing).
+ // f+90 0x00002c9f CMPL CX,$20
+ // f+93 0x00002ca2 JCS f+97(SB)
+ // f+95 0x00002ca4 XORL AX,AX <<<
+ // f+97 0x00002ca6 SHLL CL,AX
+ // f+99 0x00002ca8 MOVL $1,CX
+ //
+ // f+c8 0x00002cd7 CMPL CX,$40
+ // f+cb 0x00002cda JCS f+d0(SB)
+ // f+cd 0x00002cdc XORQ AX,AX <<<
+ // f+d0 0x00002cdf SHLQ CL,AX
+ // f+d3 0x00002ce2 MOVQ $1,CX
+ buf[0] = 0;
+ machdata->das(text, pc, 0, buf, sizeof buf);
+ if(strncmp(buf, "XOR", 3) == 0) {
+ machdata->das(text, epc, 0, buf, sizeof buf);
+ if(strncmp(buf, "SHL", 3) == 0 || strncmp(buf, "SHR", 3) == 0)
+ return;
+ }
+ }
+
// show first instruction to make clear where we were.
machdata->das(text, pc, 0, buf, sizeof buf);
コアとなるコードの解説
追加されたコードは、missing
関数内にあります。この関数は、コードカバレッジの観点から「欠落している」と判断されたコードセグメントを処理する役割を担っていると考えられます。
追加された if
ブロックのロジックは以下の通りです。
-
if(epc - pc == 2 || epc -pc == 3)
:pc
は現在の命令ポインタ (Program Counter)、epc
は次の命令ポインタまたは現在のコードブロックの終了アドレスを示していると推測されます。- この条件は、現在の命令と次の命令の間のバイト長が2バイトまたは3バイトである場合に真となります。これは、
XORL AX,AX
(2バイト) やXORQ AX,AX
(3バイト) のような短い命令シーケンスを検出するためのヒューリスティックです。
-
コメントブロック:
- このコメントは、
6g
コンパイラがx86アーキテクチャで大きなシフト操作を実装する際に、明示的なゼロクリア (XOR
) を使用する必要があることを説明しています。 - 具体的なアセンブリコードの例が示されており、
XORL AX,AX
やXORQ AX,AX
が条件分岐 (JCS
) によってスキップされる可能性があることが視覚的に示されています。
- このコメントは、
-
buf[0] = 0; machdata->das(text, pc, 0, buf, sizeof buf);
:buf
は命令の逆アセンブル結果を格納するためのバッファです。machdata->das
は、指定されたアドレス (pc
) の命令を逆アセンブルし、そのテキスト表現をbuf
に書き込む関数です。das
は "disassemble" の略である可能性が高いです。
-
if(strncmp(buf, "XOR", 3) == 0)
:- 逆アセンブルされた命令の先頭3文字が "XOR" であるかをチェックします。これにより、現在の命令が
XORL
またはXORQ
であることを確認します。
- 逆アセンブルされた命令の先頭3文字が "XOR" であるかをチェックします。これにより、現在の命令が
-
machdata->das(text, epc, 0, buf, sizeof buf);
:- 現在の命令が
XOR
であった場合、次の命令 (epc
で示されるアドレス) を逆アセンブルします。
- 現在の命令が
-
if(strncmp(buf, "SHL", 3) == 0 || strncmp(buf, "SHR", 3) == 0)
:- 次の命令の先頭3文字が "SHL" (Shift Left) または "SHR" (Shift Right) であるかをチェックします。
-
return;
:- 上記のすべての条件(命令長が2または3バイト、現在の命令が
XOR
、次の命令がSHL
またはSHR
)が満たされた場合、missing
関数からreturn
します。 - これは、この特定のコードシーケンスが「到達不能」であると誤って報告されるのを防ぐためのものです。つまり、
cov
ツールはこのパターンを検出した場合、それをカバレッジの欠落とは見なさず、処理を終了します。
- 上記のすべての条件(命令長が2または3バイト、現在の命令が
この修正は、アセンブリレベルでの特定のコンパイラ最適化パターンを認識し、カバレッジツールの誤検出を回避するための、非常に低レベルかつ具体的なワークアラウンドです。
関連リンク
- Go言語の公式ウェブサイト: https://go.dev/
- Go言語のブログ (Goの歴史や技術的な詳細に関する記事): https://go.dev/blog/
参考にした情報源リンク
- Go言語の初期のコンパイラ (
6g
など) に関する情報源 (Goの公式ドキュメント、ブログ、関連する技術記事など) - x86アセンブリ命令 (
XOR
,SHL
,SHR
,CMP
,JCS
) に関する情報源 (Intelの公式ドキュメント、オンラインのアセンブリ命令リファレンスなど) - コードカバレッジの概念とGo言語におけるカバレッジツールに関する情報源 (ソフトウェアテストの一般的な概念、Goの
go test -cover
コマンドに関するドキュメントなど) - Google内部の変更管理システム (OCL/CL) に関する情報源 (Goの初期のコミットメッセージの文脈から推測される情報)