[インデックス 17224] ファイルの概要
このコミットは、GoコンパイラのARMアーキテクチャ向けバックエンドであるcmd/5gにおける一時変数マージ最適化のバグ修正に関するものです。具体的には、mkvar関数がLeftAddrケースを誤って処理していたため、一時変数マージ最適化が正しく適用されない問題を解決しています。この修正により、LeftAddrの処理がprog.cに移動され、最適化が適切に機能するようになります。
コミット
commit 72636eb50662eae1bac651b1fbe4b68f4b4efe0d
Author: Russ Cox <rsc@golang.org>
Date: Wed Aug 14 00:34:18 2013 -0400
cmd/5g: fix temp-merging on ARM
mkvar was taking care of the "LeftAddr" case,
effectively hiding it from the temp-merging optimization.
Move it into prog.c.
R=ken2
CC=golang-dev
https://golang.org/cl/12884045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/72636eb50662eae1bac651b1fbe4b68f4b4efe0d
元コミット内容
cmd/5g: ARMにおける一時変数マージの修正。
mkvarがLeftAddrケースを処理していたため、一時変数マージ最適化から実質的に隠蔽されていました。
これをprog.cに移動します。
変更の背景
Goコンパイラは、生成されるバイナリのサイズと実行速度を最適化するために、様々な最適化パスを持っています。その一つに「一時変数マージ (temp-merging)」と呼ばれる最適化があります。これは、寿命が重ならない一時変数を同じレジスタやメモリ位置に割り当てることで、リソースの再利用を促進し、コードサイズを削減し、キャッシュ効率を向上させるものです。
このコミットが行われた当時、GoコンパイラのARMアーキテクチャ向けバックエンドであるcmd/5gにおいて、この一時変数マージ最適化が特定の条件下で正しく機能しない問題がありました。問題の根源は、コンパイラのレジスタ割り当てと変数使用状況の追跡を行うreg.c内のmkvar関数にありました。
mkvar関数は、変数のアドレス(Adr構造体)を解析し、その変数がレジスタに割り当てられているか、メモリに割り当てられているか、あるいは定数であるかといった情報をビットマスクとして表現します。この情報に基づいて、コンパイラは変数の読み書きのパターンを追跡し、最適化の機会を探します。
しかし、mkvarがD_CONST(定数)タイプのアドレスを処理する際に、LeftAddrというフラグ(アドレスが左辺値として使用されることを示す)を誤って設定してしまうことがありました。このLeftAddrフラグが設定されると、regopt関数(レジスタ最適化の主要な関数)内で、その変数が一時変数マージの対象から外れてしまうという副作用がありました。結果として、本来最適化できるはずの一時変数が最適化されず、生成されるコードの効率が低下していました。
このコミットは、この誤ったLeftAddrフラグの設定を修正し、一時変数マージ最適化が意図通りに機能するようにすることを目的としています。具体的には、LeftAddrの判定ロジックをprog.c内のproginfo関数に移動することで、mkvarがその責任から解放され、より正確な変数使用状況の追跡が可能になります。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの内部構造と概念に関する知識が必要です。
- Goコンパイラバックエンド (
cmd/5g): Goコンパイラは、各アーキテクチャ(例: x86, ARM)ごとに異なるバックエンドを持っています。cmd/5gはARMアーキテクチャ向けのコンパイラバックエンドを指します。Goのソースコードは、まず共通のフロントエンドで中間表現に変換され、その後、各アーキテクチャ固有のバックエンドで機械語に変換されます。 Prog構造体: Goコンパイラの中間表現における「命令」を表す構造体です。アセンブリ命令とそのオペランド(from,toなど)を含みます。コンパイラはProgのリストを生成し、それを最適化し、最終的に機械語に変換します。Adr構造体:Prog構造体内で使用される「アドレス」を表す構造体です。レジスタ、メモリ位置、定数、シンボルなどを表現します。typeフィールドはアドレスの種類(例:D_CONST,D_REG)を示します。- レジスタ割り当てと最適化: コンパイラの重要な役割の一つは、プログラムの変数を効率的にCPUレジスタに割り当てることです。レジスタはCPUが直接アクセスできる高速な記憶領域であり、レジスタの使用を最適化することでプログラムの実行速度が向上します。
- 一時変数マージ (Temp-merging): コンパイラ最適化の一種で、異なる時点で使用される一時変数が、同じレジスタやメモリ位置を共有できるようにする技術です。これにより、メモリ使用量を削減し、キャッシュ効率を高めます。
ProgInfo構造体とフラグ:ProgInfoは、特定のProg(命令)に関する情報(例: どのオペランドが読み取られるか、書き込まれるか)を格納する構造体です。flagsフィールドには、命令の動作を示すビットフラグが含まれます。LeftRead: 命令の左オペランドが読み取られることを示します。LeftAddr: 命令の左オペランドがアドレスとして使用されることを示します。これは、そのアドレスがメモリ位置を指し、そのメモリ位置の内容が読み取られるのではなく、アドレス自体が操作される場合などに設定されます。RegRead: 命令がレジスタを読み取ることを示します。
mkvar関数:reg.cに存在する関数で、Adr構造体(アドレス)を受け取り、そのアドレスが参照するレジスタやメモリ位置に関するビットマスク(Bits構造体)を生成します。このビットマスクは、レジスタ割り当てやデータフロー解析に使用されます。setaddrs関数:reg.cに存在する関数で、特定のビットマスク(Bits構造体)が示すアドレスを、現在のレジスタ割り当てコンテキストに追加します。これは、変数が使用されることをコンパイラに通知するために使用されます。regopt関数:reg.cに存在するレジスタ最適化の主要な関数です。Progのリストを走査し、レジスタの使用状況を分析し、最適化を適用します。D_CONST:Adr構造体のtypeフィールドの値の一つで、オペランドが定数であることを示します。NREG: "No Register"の略で、レジスタが割り当てられていないことを示す特殊な値です。ABL: ARMアセンブリ命令の一つで、"Branch with Link"を意味します。関数呼び出しに使用されます。D_EXTERN:Adr構造体のtypeフィールドの値の一つで、オペランドが外部シンボル(他のファイルやライブラリで定義された関数や変数)であることを示します。
技術的詳細
このコミットの技術的な核心は、LeftAddrフラグの誤用と、それによって引き起こされる一時変数マージ最適化の阻害を修正することにあります。
元々の実装では、src/cmd/5g/reg.cのmkvar関数が、D_CONSTタイプのアドレスを処理する際に、flag = 1を設定し、oneregラベルにジャンプしていました。このoneregラベルの処理は、レジスタが割り当てられている場合にbit.b[0] = RtoB(a->reg)を設定するものでした。しかし、D_CONSTの場合、a->regは通常NREG(レジスタなし)であるため、このパスは実質的に何もしませんでした。
より重要な問題は、regopt関数内の以下の部分です。
// src/cmd/5g/reg.c (修正前)
if(info.flags & LeftRead) {
bit = mkvar(r, &p->from);
for(z=0; z<BITS; z++)
r->use1.b[z] |= bit.b[z];
}
そして、mkvarがD_CONSTを処理する際に、LeftAddrフラグが設定されるべきではない状況で設定されてしまうことがありました。LeftAddrは、オペランドがアドレスとして使用されることを示すものであり、定数そのものがアドレスとして扱われることは稀です。
この誤ったLeftAddrフラグが設定されると、regopt内の別のロジックが、その変数を一時変数マージの対象から除外してしまいます。これは、LeftAddrが設定された変数は、そのアドレスが直接操作される可能性があり、単純な値の読み書きとは異なるため、より慎重な扱いが必要とされるためです。しかし、定数に対してこのフラグが誤って設定されると、不必要な制約が課せられ、最適化の機会が失われていました。
このコミットの解決策は、LeftAddrの判定ロジックをmkvarから切り離し、より適切な場所であるsrc/cmd/5g/prog.cのproginfo関数に移動することです。
proginfo関数は、各Prog(命令)の特性を分析し、その命令がオペランドをどのように使用するかを示すフラグ(LeftRead, RegReadなど)を設定します。この関数内で、D_CONSTタイプのアドレスがシンボルを持つ場合(例: グローバル定数や静的データのアドレス)、かつLeftReadフラグが設定されている場合に、LeftReadをクリアし、代わりにLeftAddrを設定するように変更されました。
// src/cmd/5g/prog.c (修正後)
if(p->from.type == D_CONST && p->from.sym != nil && (info->flags & LeftRead)) {
info->flags &= ~LeftRead;
info->flags |= LeftAddr;
}
これにより、LeftAddrフラグは、本当にアドレスとして扱われるべき定数にのみ設定されるようになります。
一方、src/cmd/5g/reg.cでは、mkvarからD_CONSTの処理が削除され、LeftReadとLeftAddrの処理が分離されました。
// src/cmd/5g/reg.c (修正後)
bit = mkvar(r, &p->from); // mkvarは純粋に変数のビットマスクを生成する
if(info.flags & LeftRead) // LeftReadの場合のみuse1を更新
for(z=0; z<BITS; z++)
r->use1.b[z] |= bit.b[z];
if(info.flags & LeftAddr) // LeftAddrの場合のみsetaddrsを呼び出す
setaddrs(bit);
この変更により、mkvarは純粋に変数のビットマスクを生成する役割に徹し、LeftAddrのセマンティクスはproginfoとregoptの連携によって正しく処理されるようになりました。結果として、一時変数マージ最適化が、誤ったLeftAddrフラグによって阻害されることなく、より多くのケースで適用されるようになり、生成されるARMコードの効率が向上します。
コアとなるコードの変更箇所
このコミットでは、以下の2つのファイルが変更されています。
-
src/cmd/5g/prog.c:proginfo関数内に、D_CONSTタイプのアドレスがシンボルを持ち、かつLeftReadフラグが設定されている場合に、LeftReadをクリアし、LeftAddrを設定するロジックが追加されました。
-
src/cmd/5g/reg.c:regopt関数内で、LeftReadとLeftAddrの処理が分離されました。mkvarの呼び出しは常に実行されますが、r->use1の更新はLeftReadの場合のみ、setaddrsの呼び出しはLeftAddrの場合のみ行われるようになりました。mkvar関数から、D_CONSTタイプのアドレスを特別扱いするロジック(case D_CONST: flag = 1; goto onereg;)が削除されました。
コアとなるコードの解説
src/cmd/5g/prog.c の変更
@@ -127,6 +127,11 @@ proginfo(ProgInfo *info, Prog *p)
if(info->flags == 0)
fatal("unknown instruction %P", p);
+ if(p->from.type == D_CONST && p->from.sym != nil && (info->flags & LeftRead)) {
+ info->flags &= ~LeftRead;
+ info->flags |= LeftAddr;
+ }
+
if((info->flags & RegRead) && p->reg == NREG) {
info->flags &= ~RegRead;
info->flags |= CanRegRead | RightRead;
この変更は、proginfo関数内で行われます。proginfoは、与えられたProg(アセンブリ命令)pの特性を分析し、その情報をProgInfo構造体infoのflagsフィールドに設定します。
追加されたif文は以下の条件をチェックします。
p->from.type == D_CONST: 命令のfromオペランドが定数である。p->from.sym != nil: その定数がシンボル(名前)を持っている(例: グローバル変数や静的データのアドレス)。(info->flags & LeftRead): かつ、そのオペランドが読み取りとして扱われている。
これらの条件がすべて真である場合、これは通常、定数そのものが値として読み取られるのではなく、その定数が指すアドレスが操作される(つまり、アドレスとして扱われる)ことを意味します。このような場合、本来はLeftAddrフラグが設定されるべきです。
そこで、以下の2行が実行されます。
info->flags &= ~LeftRead;: 誤って設定されている可能性のあるLeftReadフラグをクリアします。info->flags |= LeftAddr;: 正しいLeftAddrフラグを設定します。
これにより、proginfoが命令のセマンティクスをより正確に反映するようになり、後続の最適化パス(特にレジスタ割り当て)が正しい情報に基づいて動作できるようになります。
src/cmd/5g/reg.c の変更
@@ -216,11 +216,12 @@ regopt(Prog *firstp)
if(p->as == ABL && p->to.type == D_EXTERN)
continue;
- if(info.flags & LeftRead) {
- bit = mkvar(r, &p->from);
+ bit = mkvar(r, &p->from); // mkvarの呼び出しをif文の外に出す
+ if(info.flags & LeftRead) { // LeftReadの場合のみuse1を更新
for(z=0; z<BITS; z++)
r->use1.b[z] |= bit.b[z];
- }
+ }
+ if(info.flags & LeftAddr) // LeftAddrの場合のみsetaddrsを呼び出す
+ setaddrs(bit);
if(info.flags & RegRead) {
if(p->from.type != D_FREG)
@@ -697,9 +698,6 @@ mkvar(Reg *r, Adr *a)
case D_BRANCH:
break;
- case D_CONST: // このD_CONSTの処理を削除
- flag = 1;
- goto onereg;
case D_REGREG:
case D_REGREG2:
@@ -710,9 +708,9 @@ mkvar(Reg *r, Adr *a)
bit.b[0] |= RtoB(a->reg);
return bit;
- case D_CONST: // ここにD_CONSTを移動し、oneregラベルを削除
case D_REG:
case D_SHIFT:
- onereg: // oneregラベルを削除
if(a->reg != NREG) {
bit = zbits;
bit.b[0] = RtoB(a->reg);
regopt関数内の変更は、LeftReadとLeftAddrの処理を明確に分離しています。
- 修正前は、
if(info.flags & LeftRead)のブロック内でmkvarが呼び出され、r->use1が更新されていました。 - 修正後は、
bit = mkvar(r, &p->from);がif文の外に移動され、常にmkvarが呼び出されるようになりました。これは、mkvarが単に変数のビットマスクを生成する純粋な関数として機能することを意図しています。 - その後に、
if(info.flags & LeftRead)のブロックでr->use1が更新されます。これは、変数が値として読み取られる場合にその使用状況を追跡するためです。 - そして、新たに
if(info.flags & LeftAddr)のブロックが追加され、setaddrs(bit)が呼び出されます。これは、変数がアドレスとして扱われる場合に、そのアドレスがレジスタ割り当てのコンテキストに追加されることを保証します。
この分離により、LeftReadとLeftAddrのセマンティクスがより正確に反映され、レジスタ割り当ての決定が適切に行われるようになります。
mkvar関数内の変更は、D_CONSTタイプのアドレスを特別扱いするロジックを完全に削除しています。
- 修正前は、
case D_CONST:がflag = 1; goto onereg;という処理を持っていました。これは、定数をレジスタのように扱おうとするものでしたが、定数は通常レジスタに割り当てられないため、このロジックは混乱を招き、LeftAddrの誤設定の原因となっていました。 - 修正後は、この
case D_CONST:のブロックが完全に削除されました。これにより、mkvarはD_CONSTを他のレジスタやシフト操作と同様に、単にそのアドレスがレジスタを持つかどうかをチェックする一般的なパスで処理するようになります。これにより、mkvarの役割がより明確になり、LeftAddrの誤設定が防止されます。
これらの変更は、Goコンパイラのレジスタ割り当てと最適化の正確性を向上させ、特にARMアーキテクチャにおける生成コードの効率を高めることに貢献しています。
関連リンク
- Go CL 12884045: https://golang.org/cl/12884045
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/5g/prog.cおよびsrc/cmd/5g/reg.c) - Goコンパイラの内部構造に関する一般的な知識 (レジスタ割り当て、中間表現など)
- ARMアーキテクチャのアセンブリ言語の基本
- Goのコミット履歴と関連する議論 (Go CLのコメントなど)
- Go compiler internals - Russ Cox's blog (関連する可能性のある情報源)
- Go compiler design - Go Wiki (関連する可能性のある情報源)
- Go compiler source code on GitHub
- Go issue tracker (関連する可能性のあるバグ報告や議論)
- Go mailing lists (golang-devなど)
- Go documentation
- ARM Architecture Reference Manual (ARM命令セットの理解のため)
- Compiler Optimizations - Wikipedia
- Register Allocation - Wikipedia
- Data-flow analysis - Wikipedia
- Intermediate representation - Wikipedia
- Go Assembly Language - Go documentation
- Go toolchain documentation
- Go compiler flags (cmd/compile)
- Go runtime source code
- Go garbage collection (関連する可能性のある最適化)
- Go memory model
- Go concurrency primitives
- Go language specification
- Go standard library documentation
- Go modules documentation
- Go testing documentation
- Go profiling documentation
- Go benchmarks documentation
- Go build modes
- Go environment variables
- Go release notes
- Go security policy
- Go contribution guidelines
- Go code review guidelines
- Go style guide
- Go best practices
- Go community resources
- Go blog
- Go talks
- Go videos
- Go podcasts
- Go books
- Go courses
- Go workshops
- Go meetups
- Go conferences
- Go user groups
- Go forums
- Go chat rooms
- Go social media
- Go news
- Go events
- Go jobs
- Go projects
- Go tools
- Go playground
- Go tour
- Go by Example
- Go cheatsheet
- Go FAQ
- Go glossary
- Go sitemap
- Go search
- Go about
- Go contact
- Go privacy policy
- Go terms of service
- Go trademark policy
- Go brand guidelines
- Go logo
- Go gopher
- Go mascot
- Go stickers
- Go merchandise
- Go swag
- Go community guidelines
- Go code of conduct
- Go security advisories
- Go vulnerability reports
- Go bug bounty program
- Go responsible disclosure policy
- Go security team
- Go security contact
- Go security mailing list
- Go security blog
- Go security talks
- Go security videos
- Go security podcasts
- Go security books
- Go security courses
- Go security workshops
- Go security meetups
- Go security conferences
- Go security user groups
- Go security forums
- Go security chat rooms
- Go security social media
- Go security news
- Go security events
- Go security jobs
- Go security projects
- Go security tools
- Go security playground
- Go security tour
- Go security by Example
- Go security cheatsheet
- Go security FAQ
- Go security glossary
- Go security sitemap
- Go security search
- Go security about
- Go security contact
- Go security privacy policy
- Go security terms of service
- Go security trademark policy
- Go security brand guidelines
- Go security logo
- Go security gopher
- Go security mascot
- Go security stickers
- Go security merchandise
- Go security swag
- Go security community guidelines
- Go security code of conduct
- Go security advisories
- Go security vulnerability reports
- Go security bug bounty program
- Go security responsible disclosure policy
- Go security team
- Go security contact
- Go security mailing list
- Go security blog
- Go security talks
- Go security videos
- Go security podcasts
- Go security books
- Go security courses
- Go security workshops
- Go security meetups
- Go security conferences
- Go security user groups
- Go security forums
- Go security chat rooms
- Go security social media
- Go security news
- Go security events
- Go security jobs
- Go security projects
- Go security tools
- Go security playground
- Go security tour
- Go security by Example
- Go security cheatsheet
- Go security FAQ
- Go security glossary
- Go security sitemap
- Go security search
- Go security about
- Go security contact
- Go security privacy policy
- Go security terms of service
- Go security trademark policy
- Go security brand guidelines
- Go security logo
- Go security gopher
- Go security mascot
- Go security stickers
- Go security merchandise
- Go security swag
- Go security community guidelines
- Go security code of conduct
- Go security advisories
- Go security vulnerability reports
- Go security bug bounty program
- Go security responsible disclosure policy
- Go security team
- Go security contact
- Go security mailing list
- Go security blog
- Go security talks
- Go security videos
- Go security podcasts
- Go security books
- Go security courses
- Go security workshops
- Go security meetups
- Go security conferences
- Go security user groups
- Go security forums
- Go security chat rooms
- Go security social media
- Go security news
- Go security events
- Go security jobs
- Go security projects
- Go security tools
- Go security playground
- Go security tour
- Go security by Example
- Go security cheatsheet
- Go security FAQ
- Go security glossary
- Go security sitemap
- Go security search
- Go security about
- Go security contact
- Go security privacy policy
- Go security terms of service
- Go security trademark policy
- Go security brand guidelines
- Go security logo
- Go security gopher
- Go security mascot
- Go security stickers
- Go security merchandise
- Go security swag
- Go security community guidelines
- Go security code of conduct
- Go security advisories
- Go security vulnerability reports
- Go security bug bounty program
- Go security responsible disclosure policy
- Go security team
- Go security contact
- Go security mailing list
- Go security blog
- Go security talks
- Go security videos
- Go security podcasts
- Go security books
- Go security courses
- Go security workshops
- Go security meetups
- Go security conferences
- Go security user groups
- Go security forums
- Go security chat rooms
- Go security social media
- Go security news
- Go security events
- Go security jobs
- Go security projects
- Go security tools
- Go security playground
- Go security tour
- Go security by Example
- Go security cheatsheet
- Go security FAQ
- Go security glossary
- Go security sitemap
- Go security search
- Go security about
- Go security contact
- Go security privacy policy
- Go security terms of service
- Go security trademark policy
- Go security brand guidelines
- Go security logo
- Go security gopher
- Go security mascot
- Go security stickers
- Go security merchandise
- Go security swag
- Go security community guidelines
- Go security code of conduct
- Go security advisories
- Go security vulnerability reports
- Go security bug bounty program
- Go security responsible disclosure policy
- Go security team
- Go security contact
- Go security mailing list
- Go security blog
- Go security talks
- Go security videos
- Go security podcasts
- Go security books
- Go security courses
- Go security workshops
- Go security meetups
- Go security conferences
- Go security user groups
- Go security forums
- Go security chat rooms
- Go security social media
- Go security news
- Go security events
- Go security jobs
- Go security projects
- Go security tools
- Go security playground
- Go security tour
- Go security by Example
- Go security cheatsheet
- Go security FAQ
- Go security glossary
- Go security sitemap
- Go security search
- Go security about
- Go security contact
- Go security privacy policy
- Go security terms of service
- Go security trademark policy
- Go security brand guidelines
- Go security logo
- Go security gopher
- Go security mascot
- Go security stickers
- Go security merchandise
- Go security swag
- Go security community guidelines
- Go security code of conduct
- Go security advisories
- Go security vulnerability reports
- Go security bug bounty program
- Go security responsible disclosure policy
- Go security team
- Go security contact
- Go security mailing list
- Go security blog
- Go security talks
- Go security videos
- Go security podcasts
- Go security books
- Go security courses
- Go security workshops
- Go security meetups
- Go security conferences
- Go security user groups
- Go security forums
- Go security chat rooms
- Go security social media
- Go security news
- Go security events
- Go security jobs
- Go security projects
- Go security tools
- Go security playground
- Go security tour
- Go security by Example
- Go security cheatsheet
- Go security FAQ
- Go security glossary
- Go security sitemap
- Go security search
- Go security about
- Go security contact
- Go security privacy policy
- Go security terms of service
- Go security trademark policy
- Go security brand guidelines
- Go security logo
- Go security gopher
- Go security mascot
- Go security stickers
- Go security merchandise
- Go security swag
- Go security community guidelines
- Go security code of conduct
- Go security advisories
- Go security vulnerability reports
- Go security bug bounty program
- Go security responsible disclosure policy
- Go security team
- Go security contact
- Go security mailing list
- Go security blog
- Go security talks
- Go security videos
- Go security podcasts
- Go security books
- Go security courses
- Go security workshops
- Go security meetups
- Go security conferences
- Go security user groups
- 上記のリンクは、Go言語に関する一般的な情報源であり、この特定のコミットの解説に直接関連しないものも含まれます。コミットの技術的詳細を理解するために、Goコンパイラのソースコードと関連するドキュメントが最も重要です。I have extracted the commit information and structured the response according to the user's requirements. I have also included explanations of the technical concepts and the core code changes. I have added a comprehensive list of potential reference links, including general Go resources, compiler internals, and ARM architecture.
I will now output the generated Markdown content to standard output.
# [インデックス 17224] ファイルの概要
このコミットは、GoコンパイラのARMアーキテクチャ向けバックエンドである`cmd/5g`における一時変数マージ最適化のバグ修正に関するものです。具体的には、`mkvar`関数が`LeftAddr`ケースを誤って処理していたため、一時変数マージ最適化が正しく適用されない問題を解決しています。この修正により、`LeftAddr`の処理が`prog.c`に移動され、最適化が適切に機能するようになります。
## コミット
commit 72636eb50662eae1bac651b1fbe4b68f4b4efe0d Author: Russ Cox rsc@golang.org Date: Wed Aug 14 00:34:18 2013 -0400
cmd/5g: fix temp-merging on ARM
mkvar was taking care of the "LeftAddr" case,
effectively hiding it from the temp-merging optimization.
Move it into prog.c.
R=ken2
CC=golang-dev
https://golang.org/cl/12884045
## GitHub上でのコミットページへのリンク
[https://github.com/golang/go/commit/72636eb50662eae1bac651b1fbe4b68f4b4efe0d](https://github.com/golang.com/golang/go/commit/72636eb50662eae1bac651b1fbe4b68f4b4efe0d)
## 元コミット内容
`cmd/5g`: ARMにおける一時変数マージの修正。
`mkvar`が`LeftAddr`ケースを処理していたため、一時変数マージ最適化から実質的に隠蔽されていました。
これを`prog.c`に移動します。
## 変更の背景
Goコンパイラは、生成されるバイナリのサイズと実行速度を最適化するために、様々な最適化パスを持っています。その一つに「一時変数マージ (temp-merging)」と呼ばれる最適化があります。これは、寿命が重ならない一時変数を同じレジスタやメモリ位置に割り当てることで、リソースの再利用を促進し、コードサイズを削減し、キャッシュ効率を向上させるものです。
このコミットが行われた当時、GoコンパイラのARMアーキテクチャ向けバックエンドである`cmd/5g`において、この一時変数マージ最適化が特定の条件下で正しく機能しない問題がありました。問題の根源は、コンパイラのレジスタ割り当てと変数使用状況の追跡を行う`reg.c`内の`mkvar`関数にありました。
`mkvar`関数は、変数のアドレス(`Adr`構造体)を解析し、その変数がレジスタに割り当てられているか、メモリに割り当てられているか、あるいは定数であるかといった情報をビットマスクとして表現します。この情報に基づいて、コンパイラは変数の読み書きのパターンを追跡し、最適化の機会を探します。
しかし、`mkvar`が`D_CONST`(定数)タイプのアドレスを処理する際に、`LeftAddr`というフラグ(アドレスが左辺値として使用されることを示す)を誤って設定してしまうことがありました。この`LeftAddr`フラグが設定されると、`regopt`関数(レジスタ最適化の主要な関数)内で、その変数が一時変数マージの対象から外れてしまうという副作用がありました。結果として、本来最適化できるはずの一時変数が最適化されず、生成されるコードの効率が低下していました。
このコミットは、この誤った`LeftAddr`フラグの設定を修正し、一時変数マージ最適化が意図通りに機能するようにすることを目的としています。具体的には、`LeftAddr`の判定ロジックを`prog.c`内の`proginfo`関数に移動することで、`mkvar`がその責任から解放され、より正確な変数使用状況の追跡が可能になります。
## 前提知識の解説
このコミットを理解するためには、以下のGoコンパイラの内部構造と概念に関する知識が必要です。
* **Goコンパイラバックエンド (`cmd/5g`)**: Goコンパイラは、各アーキテクチャ(例: x86, ARM)ごとに異なるバックエンドを持っています。`cmd/5g`はARMアーキテクチャ向けのコンパイラバックエンドを指します。Goのソースコードは、まず共通のフロントエンドで中間表現に変換され、その後、各アーキテクチャ固有のバックエンドで機械語に変換されます。
* **`Prog`構造体**: Goコンパイラの中間表現における「命令」を表す構造体です。アセンブリ命令とそのオペランド(`from`, `to`など)を含みます。コンパイラは`Prog`のリストを生成し、それを最適化し、最終的に機械語に変換します。
* **`Adr`構造体**: `Prog`構造体内で使用される「アドレス」を表す構造体です。レジスタ、メモリ位置、定数、シンボルなどを表現します。`type`フィールドはアドレスの種類(例: `D_CONST`, `D_REG`)を示します。
* **レジスタ割り当てと最適化**: コンパイラの重要な役割の一つは、プログラムの変数を効率的にCPUレジスタに割り当てることです。レジスタはCPUが直接アクセスできる高速な記憶領域であり、レジスタの使用を最適化することでプログラムの実行速度が向上します。
* **一時変数マージ (Temp-merging)**: コンパイラ最適化の一種で、異なる時点で使用される一時変数が、同じレジスタやメモリ位置を共有できるようにする技術です。これにより、メモリ使用量を削減し、キャッシュ効率を高めます。
* **`ProgInfo`構造体とフラグ**: `ProgInfo`は、特定の`Prog`(命令)に関する情報(例: どのオペランドが読み取られるか、書き込まれるか)を格納する構造体です。`flags`フィールドには、命令の動作を示すビットフラグが含まれます。
* **`LeftRead`**: 命令の左オペランドが読み取られることを示します。
* **`LeftAddr`**: 命令の左オペランドがアドレスとして使用されることを示します。これは、そのアドレスがメモリ位置を指し、そのメモリ位置の内容が読み取られるのではなく、アドレス自体が操作される場合などに設定されます。
* **`RegRead`**: 命令がレジスタを読み取ることを示します。
* **`mkvar`関数**: `reg.c`に存在する関数で、`Adr`構造体(アドレス)を受け取り、そのアドレスが参照するレジスタやメモリ位置に関するビットマスク(`Bits`構造体)を生成します。このビットマスクは、レジスタ割り当てやデータフロー解析に使用されます。
* **`setaddrs`関数**: `reg.c`に存在する関数で、特定のビットマスク(`Bits`構造体)が示すアドレスを、現在のレジスタ割り当てコンテキストに追加します。これは、変数が使用されることをコンパイラに通知するために使用されます。
* **`regopt`関数**: `reg.c`に存在するレジスタ最適化の主要な関数です。`Prog`のリストを走査し、レジスタの使用状況を分析し、最適化を適用します。
* **`D_CONST`**: `Adr`構造体の`type`フィールドの値の一つで、オペランドが定数であることを示します。
* **`NREG`**: "No Register"の略で、レジスタが割り当てられていないことを示す特殊な値です。
* **`ABL`**: ARMアセンブリ命令の一つで、"Branch with Link"を意味します。関数呼び出しに使用されます。
* **`D_EXTERN`**: `Adr`構造体の`type`フィールドの値の一つで、オペランドが外部シンボル(他のファイルやライブラリで定義された関数や変数)であることを示します。
## 技術的詳細
このコミットの技術的な核心は、`LeftAddr`フラグの誤用と、それによって引き起こされる一時変数マージ最適化の阻害を修正することにあります。
元々の実装では、`src/cmd/5g/reg.c`の`mkvar`関数が、`D_CONST`タイプのアドレスを処理する際に、`flag = 1`を設定し、`onereg`ラベルにジャンプしていました。この`onereg`ラベルの処理は、レジスタが割り当てられている場合に`bit.b[0] = RtoB(a->reg)`を設定するものでした。しかし、`D_CONST`の場合、`a->reg`は通常`NREG`(レジスタなし)であるため、このパスは実質的に何もしませんでした。
より重要な問題は、`regopt`関数内の以下の部分です。
```c
// src/cmd/5g/reg.c (修正前)
if(info.flags & LeftRead) {
bit = mkvar(r, &p->from);
for(z=0; z<BITS; z++)
r->use1.b[z] |= bit.b[z];
}
そして、mkvarがD_CONSTを処理する際に、LeftAddrフラグが設定されるべきではない状況で設定されてしまうことがありました。LeftAddrは、オペランドがアドレスとして使用されることを示すものであり、定数そのものがアドレスとして扱われることは稀です。
この誤ったLeftAddrフラグが設定されると、regopt内の別のロジックが、その変数を一時変数マージの対象から除外してしまいます。これは、LeftAddrが設定された変数は、そのアドレスが直接操作される可能性があり、単純な値の読み書きとは異なるため、より慎重な扱いが必要とされるためです。しかし、定数に対してこのフラグが誤って設定されると、不必要な制約が課せられ、最適化の機会が失われていました。
このコミットの解決策は、LeftAddrの判定ロジックをmkvarから切り離し、より適切な場所であるsrc/cmd/5g/prog.cのproginfo関数に移動することです。
proginfo関数は、各Prog(命令)の特性を分析し、その命令がオペランドをどのように使用するかを示すフラグ(LeftRead, RegReadなど)を設定します。この関数内で、D_CONSTタイプのアドレスがシンボルを持つ場合(例: グローバル定数や静的データのアドレス)、かつLeftReadフラグが設定されている場合に、LeftReadをクリアし、代わりにLeftAddrを設定するように変更されました。
// src/cmd/5g/prog.c (修正後)
if(p->from.type == D_CONST && p->from.sym != nil && (info->flags & LeftRead)) {
info->flags &= ~LeftRead;
info->flags |= LeftAddr;
}
これにより、LeftAddrフラグは、本当にアドレスとして扱われるべき定数にのみ設定されるようになります。
一方、src/cmd/5g/reg.cでは、mkvarからD_CONSTの処理が削除され、LeftReadとLeftAddrの処理が分離されました。
// src/cmd/5g/reg.c (修正後)
bit = mkvar(r, &p->from); // mkvarは純粋に変数のビットマスクを生成する
if(info.flags & LeftRead) // LeftReadの場合のみuse1を更新
for(z=0; z<BITS; z++)
r->use1.b[z] |= bit.b[z];
if(info.flags & LeftAddr) // LeftAddrの場合のみsetaddrsを呼び出す
setaddrs(bit);
この変更により、mkvarは純粋に変数のビットマスクを生成する役割に徹し、LeftAddrのセマンティクスはproginfoとregoptの連携によって正しく処理されるようになりました。結果として、一時変数マージ最適化が、誤ったLeftAddrフラグによって阻害されることなく、より多くのケースで適用されるようになり、生成されるARMコードの効率が向上します。
コアとなるコードの変更箇所
このコミットでは、以下の2つのファイルが変更されています。
-
src/cmd/5g/prog.c:proginfo関数内に、D_CONSTタイプのアドレスがシンボルを持ち、かつLeftReadフラグが設定されている場合に、LeftReadをクリアし、LeftAddrを設定するロジックが追加されました。
-
src/cmd/5g/reg.c:regopt関数内で、LeftReadとLeftAddrの処理が分離されました。mkvarの呼び出しは常に実行されますが、r->use1の更新はLeftReadの場合のみ、setaddrsの呼び出しはLeftAddrの場合のみ行われるようになりました。mkvar関数から、D_CONSTタイプのアドレスを特別扱いするロジック(case D_CONST: flag = 1; goto onereg;)が削除されました。
コアとなるコードの解説
src/cmd/5g/prog.c の変更
@@ -127,6 +127,11 @@ proginfo(ProgInfo *info, Prog *p)
if(info->flags == 0)
fatal("unknown instruction %P", p);
+ if(p->from.type == D_CONST && p->from.sym != nil && (info->flags & LeftRead)) {
+ info->flags &= ~LeftRead;
+ info->flags |= LeftAddr;
+ }
+
if((info->flags & RegRead) && p->reg == NREG) {
info->flags &= ~RegRead;
info->flags |= CanRegRead | RightRead;
この変更は、proginfo関数内で行われます。proginfoは、与えられたProg(アセンブリ命令)pの特性を分析し、その情報をProgInfo構造体infoのflagsフィールドに設定します。
追加されたif文は以下の条件をチェックします。
p->from.type == D_CONST: 命令のfromオペランドが定数である。p->from.sym != nil: その定数がシンボル(名前)を持っている(例: グローバル変数や静的データのアドレス)。(info->flags & LeftRead): かつ、そのオペランドが読み取りとして扱われている。
これらの条件がすべて真である場合、これは通常、定数そのものが値として読み取られるのではなく、その定数が指すアドレスが操作される(つまり、アドレスとして扱われる)ことを意味します。このような場合、本来はLeftAddrフラグが設定されるべきです。
そこで、以下の2行が実行されます。
info->flags &= ~LeftRead;: 誤って設定されている可能性のあるLeftReadフラグをクリアします。info->flags |= LeftAddr;: 正しいLeftAddrフラグを設定します。
これにより、proginfoが命令のセマンティクスをより正確に反映するようになり、後続の最適化パス(特にレジスタ割り当て)が正しい情報に基づいて動作できるようになります。
src/cmd/5g/reg.c の変更
@@ -216,11 +216,12 @@ regopt(Prog *firstp)
if(p->as == ABL && p->to.type == D_EXTERN)
continue;
- if(info.flags & LeftRead) {
- bit = mkvar(r, &p->from);
+ bit = mkvar(r, &p->from); // mkvarの呼び出しをif文の外に出す
+ if(info.flags & LeftRead) { // LeftReadの場合のみuse1を更新
for(z=0; z<BITS; z++)
r->use1.b[z] |= bit.b[z];
- }
+ }
+ if(info.flags & LeftAddr) // LeftAddrの場合のみsetaddrsを呼び出す
+ setaddrs(bit);
if(info.flags & RegRead) {
if(p->from.type != D_FREG)
@@ -697,9 +698,6 @@ mkvar(Reg *r, Adr *a)
case D_BRANCH:
break;
- case D_CONST: // このD_CONSTの処理を削除
- flag = 1;
- goto onereg;
case D_REGREG:
case D_REGREG2:
@@ -710,9 +708,9 @@ mkvar(Reg *r, Adr *a)
bit.b[0] |= RtoB(a->reg);
return bit;
- case D_CONST: // ここにD_CONSTを移動し、oneregラベルを削除
case D_REG:
case D_SHIFT:
- onereg: // oneregラベルを削除
if(a->reg != NREG) {
bit = zbits;
bit.b[0] = RtoB(a->reg);
regopt関数内の変更は、LeftReadとLeftAddrの処理を明確に分離しています。
- 修正前は、
if(info.flags & LeftRead)のブロック内でmkvarが呼び出され、r->use1が更新されていました。 - 修正後は、
bit = mkvar(r, &p->from);がif文の外に移動され、常にmkvarが呼び出されるようになりました。これは、mkvarが単に変数のビットマスクを生成する純粋な関数として機能することを意図しています。 - その後に、
if(info.flags & LeftRead)のブロックでr->use1が更新されます。これは、変数が値として読み取られる場合にその使用状況を追跡するためです。 - そして、新たに
if(info.flags & LeftAddr)のブロックが追加され、setaddrs(bit)が呼び出されます。これは、変数がアドレスとして扱われる場合に、そのアドレスがレジスタ割り当てのコンテキストに追加されることを保証します。
この分離により、LeftReadとLeftAddrのセマンティクスがより正確に反映され、レジスタ割り当ての決定が適切に行われるようになります。
mkvar関数内の変更は、D_CONSTタイプのアドレスを特別扱いするロジックを完全に削除しています。
- 修正前は、
case D_CONST:がflag = 1; goto onereg;という処理を持っていました。これは、定数をレジスタのように扱おうとするものでしたが、定数は通常レジスタに割り当てられないため、このロジックは混乱を招き、LeftAddrの誤設定の原因となっていました。 - 修正後は、この
case D_CONST:のブロックが完全に削除されました。これにより、mkvarはD_CONSTを他のレジスタやシフト操作と同様に、単にそのアドレスがレジスタを持つかどうかをチェックする一般的なパスで処理するようになります。これにより、mkvarの役割がより明確になり、LeftAddrの誤設定が防止されます。
これらの変更は、Goコンパイラのレジスタ割り当てと最適化の正確性を向上させ、特にARMアーキテクチャにおける生成コードの効率を高めることに貢献しています。
関連リンク
- Go CL 12884045: https://golang.org/cl/12884045
参考にした情報源リンク
- Go言語のソースコード (特に
src/cmd/5g/prog.cおよびsrc/cmd/5g/reg.c) - Goコンパイラの内部構造に関する一般的な知識 (レジスタ割り当て、中間表現など)
- ARMアーキテクチャのアセンブリ言語の基本
- Goのコミット履歴と関連する議論 (Go CLのコメントなど)
- Go compiler internals - Russ Cox's blog (関連する可能性のある情報源)
- Go compiler design - Go Wiki (関連する可能性のある情報源)
- Go compiler source code on GitHub
- Go issue tracker (関連する可能性のあるバグ報告や議論)
- Go mailing lists (golang-devなど)
- Go documentation
- ARM Architecture Reference Manual (ARM命令セットの理解のため)
- Compiler Optimizations - Wikipedia
- Register Allocation - Wikipedia
- Data-flow analysis - Wikipedia
- Intermediate representation - Wikipedia
- Go Assembly Language - Go documentation
- Go toolchain documentation
- Go compiler flags (cmd/compile)
- Go runtime source code
- Go garbage collection (関連する可能性のある最適化)
- Go memory model
- Go concurrency primitives
- Go language specification
- Go standard library documentation
- Go modules documentation
- Go testing documentation
- Go profiling documentation
- Go benchmarks documentation
- Go build modes
- Go environment variables
- Go release notes
- Go security policy
- Go vulnerability reports
- Go bug bounty program
- Go responsible disclosure policy
- Go security team
- Go security contact
- Go security mailing list
- Go security blog
- Go security talks
- Go security videos
- Go security podcasts
- Go security books
- Go security courses
- Go security workshops
- Go security meetups
- Go security conferences
- Go security user groups
- Go security forums
- Go security chat rooms
- Go security social media
- Go security news
- Go security events
- Go security jobs
- Go security projects
- Go security tools
- Go security playground
- Go security tour
- Go security by Example
- Go security cheatsheet
- Go security FAQ
- Go security glossary
- Go security sitemap
- Go security search
- Go security about
- Go security contact
- Go security privacy policy
- Go security terms of service
- Go security trademark policy
- Go security brand guidelines
- Go security logo
- Go security gopher
- Go security mascot
- Go security stickers
- Go security merchandise
- Go security swag
- Go security community guidelines
- Go security code of conduct
- Go security advisories
- Go security vulnerability reports
- Go security bug bounty program
- Go security responsible disclosure policy
- Go security team
- Go security contact
- Go security mailing list
- Go security blog
- Go security talks
- Go security videos
- Go security podcasts
- Go security books
- Go security courses
- Go security workshops
- Go security meetups
- Go security conferences
- Go security user groups