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

[インデックス 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における一時変数マージの修正。 mkvarLeftAddrケースを処理していたため、一時変数マージ最適化から実質的に隠蔽されていました。 これをprog.cに移動します。

変更の背景

Goコンパイラは、生成されるバイナリのサイズと実行速度を最適化するために、様々な最適化パスを持っています。その一つに「一時変数マージ (temp-merging)」と呼ばれる最適化があります。これは、寿命が重ならない一時変数を同じレジスタやメモリ位置に割り当てることで、リソースの再利用を促進し、コードサイズを削減し、キャッシュ効率を向上させるものです。

このコミットが行われた当時、GoコンパイラのARMアーキテクチャ向けバックエンドであるcmd/5gにおいて、この一時変数マージ最適化が特定の条件下で正しく機能しない問題がありました。問題の根源は、コンパイラのレジスタ割り当てと変数使用状況の追跡を行うreg.c内のmkvar関数にありました。

mkvar関数は、変数のアドレス(Adr構造体)を解析し、その変数がレジスタに割り当てられているか、メモリに割り当てられているか、あるいは定数であるかといった情報をビットマスクとして表現します。この情報に基づいて、コンパイラは変数の読み書きのパターンを追跡し、最適化の機会を探します。

しかし、mkvarD_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.cmkvar関数が、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];
}

そして、mkvarD_CONSTを処理する際に、LeftAddrフラグが設定されるべきではない状況で設定されてしまうことがありました。LeftAddrは、オペランドがアドレスとして使用されることを示すものであり、定数そのものがアドレスとして扱われることは稀です。

この誤ったLeftAddrフラグが設定されると、regopt内の別のロジックが、その変数を一時変数マージの対象から除外してしまいます。これは、LeftAddrが設定された変数は、そのアドレスが直接操作される可能性があり、単純な値の読み書きとは異なるため、より慎重な扱いが必要とされるためです。しかし、定数に対してこのフラグが誤って設定されると、不必要な制約が課せられ、最適化の機会が失われていました。

このコミットの解決策は、LeftAddrの判定ロジックをmkvarから切り離し、より適切な場所であるsrc/cmd/5g/prog.cproginfo関数に移動することです。

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の処理が削除され、LeftReadLeftAddrの処理が分離されました。

// 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のセマンティクスはproginforegoptの連携によって正しく処理されるようになりました。結果として、一時変数マージ最適化が、誤ったLeftAddrフラグによって阻害されることなく、より多くのケースで適用されるようになり、生成されるARMコードの効率が向上します。

コアとなるコードの変更箇所

このコミットでは、以下の2つのファイルが変更されています。

  1. src/cmd/5g/prog.c:

    • proginfo関数内に、D_CONSTタイプのアドレスがシンボルを持ち、かつLeftReadフラグが設定されている場合に、LeftReadをクリアし、LeftAddrを設定するロジックが追加されました。
  2. src/cmd/5g/reg.c:

    • regopt関数内で、LeftReadLeftAddrの処理が分離されました。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構造体infoflagsフィールドに設定します。

追加された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関数内の変更は、LeftReadLeftAddrの処理を明確に分離しています。

  • 修正前は、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)が呼び出されます。これは、変数がアドレスとして扱われる場合に、そのアドレスがレジスタ割り当てのコンテキストに追加されることを保証します。

この分離により、LeftReadLeftAddrのセマンティクスがより正確に反映され、レジスタ割り当ての決定が適切に行われるようになります。

mkvar関数内の変更は、D_CONSTタイプのアドレスを特別扱いするロジックを完全に削除しています。

  • 修正前は、case D_CONST:flag = 1; goto onereg;という処理を持っていました。これは、定数をレジスタのように扱おうとするものでしたが、定数は通常レジスタに割り当てられないため、このロジックは混乱を招き、LeftAddrの誤設定の原因となっていました。
  • 修正後は、このcase D_CONST:のブロックが完全に削除されました。これにより、mkvarD_CONSTを他のレジスタやシフト操作と同様に、単にそのアドレスがレジスタを持つかどうかをチェックする一般的なパスで処理するようになります。これにより、mkvarの役割がより明確になり、LeftAddrの誤設定が防止されます。

これらの変更は、Goコンパイラのレジスタ割り当てと最適化の正確性を向上させ、特にARMアーキテクチャにおける生成コードの効率を高めることに貢献しています。

関連リンク

参考にした情報源リンク

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];
}

そして、mkvarD_CONSTを処理する際に、LeftAddrフラグが設定されるべきではない状況で設定されてしまうことがありました。LeftAddrは、オペランドがアドレスとして使用されることを示すものであり、定数そのものがアドレスとして扱われることは稀です。

この誤ったLeftAddrフラグが設定されると、regopt内の別のロジックが、その変数を一時変数マージの対象から除外してしまいます。これは、LeftAddrが設定された変数は、そのアドレスが直接操作される可能性があり、単純な値の読み書きとは異なるため、より慎重な扱いが必要とされるためです。しかし、定数に対してこのフラグが誤って設定されると、不必要な制約が課せられ、最適化の機会が失われていました。

このコミットの解決策は、LeftAddrの判定ロジックをmkvarから切り離し、より適切な場所であるsrc/cmd/5g/prog.cproginfo関数に移動することです。

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の処理が削除され、LeftReadLeftAddrの処理が分離されました。

// 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のセマンティクスはproginforegoptの連携によって正しく処理されるようになりました。結果として、一時変数マージ最適化が、誤ったLeftAddrフラグによって阻害されることなく、より多くのケースで適用されるようになり、生成されるARMコードの効率が向上します。

コアとなるコードの変更箇所

このコミットでは、以下の2つのファイルが変更されています。

  1. src/cmd/5g/prog.c:

    • proginfo関数内に、D_CONSTタイプのアドレスがシンボルを持ち、かつLeftReadフラグが設定されている場合に、LeftReadをクリアし、LeftAddrを設定するロジックが追加されました。
  2. src/cmd/5g/reg.c:

    • regopt関数内で、LeftReadLeftAddrの処理が分離されました。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構造体infoflagsフィールドに設定します。

追加された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関数内の変更は、LeftReadLeftAddrの処理を明確に分離しています。

  • 修正前は、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)が呼び出されます。これは、変数がアドレスとして扱われる場合に、そのアドレスがレジスタ割り当てのコンテキストに追加されることを保証します。

この分離により、LeftReadLeftAddrのセマンティクスがより正確に反映され、レジスタ割り当ての決定が適切に行われるようになります。

mkvar関数内の変更は、D_CONSTタイプのアドレスを特別扱いするロジックを完全に削除しています。

  • 修正前は、case D_CONST:flag = 1; goto onereg;という処理を持っていました。これは、定数をレジスタのように扱おうとするものでしたが、定数は通常レジスタに割り当てられないため、このロジックは混乱を招き、LeftAddrの誤設定の原因となっていました。
  • 修正後は、このcase D_CONST:のブロックが完全に削除されました。これにより、mkvarD_CONSTを他のレジスタやシフト操作と同様に、単にそのアドレスがレジスタを持つかどうかをチェックする一般的なパスで処理するようになります。これにより、mkvarの役割がより明確になり、LeftAddrの誤設定が防止されます。

これらの変更は、Goコンパイラのレジスタ割り当てと最適化の正確性を向上させ、特にARMアーキテクチャにおける生成コードの効率を高めることに貢献しています。

関連リンク

参考にした情報源リンク