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

[インデックス 13130] ファイルの概要

このコミットは、Go言語のランタイムにおけるARMアーキテクチャ向けのソフトウェア浮動小数点(softfloat)実装に、条件付き実行のサポートを追加するものです。これにより、ARMプロセッサの条件コードレジスタ(CPSR)の状態に基づいて浮動小数点命令の実行を制御できるようになり、より正確な命令のシミュレーションが可能になります。特に、Issue #3638の修正を目的としています。

コミット

commit fb3a1b6821d35e13ec9631ecbff0d013022f2374
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Wed May 23 02:00:40 2012 +0800

    runtime: support conditional execution in ARM softfloat
            Fixes #3638.
    
    R=golang-dev, dave, rsc
    CC=golang-dev
    https://golang.org/cl/6213057

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/fb3a1b6821d35e13ec9631ecbff0d013022f2374

元コミット内容

runtime: support conditional execution in ARM softfloat
        Fixes #3638.

R=golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6213057

変更の背景

この変更の背景には、Go言語がARMアーキテクチャ上で動作する際に、浮動小数点演算をソフトウェアでエミュレートする必要があったという事情があります。ARMプロセッサには、命令の実行を条件付きで行うためのメカニズム(条件付き実行)が備わっています。これは、直前の演算結果に基づいて、特定の命令を実行するかどうかを決定するものです。

GoのランタイムがARMの浮動小数点命令をソフトウェアで処理する際、この条件付き実行のセマンティクスを正確に再現する必要がありました。元の実装では、この条件付き実行が適切にサポートされていなかったため、特定の浮動小数点命令が期待通りに動作しない、あるいは誤った結果を生成する可能性がありました。Issue #3638は、この問題、すなわちARM softfloatにおける条件付き実行の欠如に起因するバグを報告しており、このコミットはその修正を目的としています。

前提知識の解説

ARMアーキテクチャと条件付き実行

ARMアーキテクチャは、RISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャであり、組み込みシステムやモバイルデバイスで広く利用されています。ARM命令セットの大きな特徴の一つに「条件付き実行(Conditional Execution)」があります。

  • 条件コード(Condition Codes): ARMプロセッサは、演算結果に応じて特定のフラグ(条件コード)をCPSR(Current Program Status Register)に設定します。主要なフラグには以下のものがあります。
    • N (Negative): 結果が負の場合にセット。
    • Z (Zero): 結果がゼロの場合にセット。
    • C (Carry): 演算でキャリーが発生した場合(加算)またはボローが発生しなかった場合(減算)にセット。
    • V (Overflow): 符号付き演算でオーバーフローが発生した場合にセット。
  • 条件コードフィールド: CPSRのビット28から31がこれらの条件コードフラグに対応します。
  • 条件付き命令: ほとんどのARM命令は、命令コードの最上位4ビットに条件コードを指定することで、CPSRの現在の状態に基づいて実行されるかどうかを決定できます。例えば、EQ(Equal)条件はZフラグがセットされている場合に命令を実行し、NE(Not Equal)条件はZフラグがクリアされている場合に命令を実行します。AL(Always)は常に実行されることを意味します。

ソフトウェア浮動小数点(Softfloat)

一部のARMプロセッサ、特に古いものや低コストの組み込みシステム向けには、専用の浮動小数点ユニット(FPU)が搭載されていない場合があります。このような環境で浮動小数点演算を行うためには、ソフトウェアで浮動小数点演算をエミュレートする必要があります。これを「ソフトウェア浮動小数点」または「softfloat」と呼びます。

Go言語のランタイムは、FPUを持たないARM環境でもGoプログラムが動作するように、このsoftfloat実装を提供しています。softfloatは、CPUの整数演算命令と論理演算命令を組み合わせて、浮動小数点数の加算、減算、乗算、除算などの操作をシミュレートします。

Goランタイムと命令のシミュレーション

Goランタイムは、特定のアーキテクチャや環境に依存する低レベルの処理(メモリ管理、スケジューリング、システムコールなど)を担当します。FPUを持たないARM環境では、Goのコンパイラが生成する浮動小数点命令は、直接ハードウェアで実行されるのではなく、ランタイム内のsoftfloatコードによって解釈・実行されます。

このコミットの文脈では、GoランタイムがARMの浮動小数点命令をシミュレートする際に、その命令が持つ条件付き実行のセマンティクスも正確に再現する必要がありました。つまり、命令の実行前にCPSRの状態をチェックし、その条件が満たされている場合にのみ命令の本体をシミュレートするというロジックが必要とされました。

技術的詳細

このコミットの技術的詳細の核心は、ARMの条件付き実行をGoランタイムのsoftfloat実装内で正確にエミュレートする方法にあります。

ARM命令は、その最上位4ビット(ビット28-31)に条件コード(condフィールド)を持ちます。このcondフィールドは、命令が実行されるべきCPSRの状態を示します。例えば、0000EQ(Z=1)、0001NE(Z=0)、1110AL(常に実行)などです。

コミットでは、stepflt関数(浮動小数点命令のステップ実行をシミュレートする関数)内で、以下のロジックが追加されています。

  1. 命令のデコード: 実行中の命令(i)から最上位4ビットを抽出し、opc(opcodeの略で、ここでは条件コードを指す)として取得します。
  2. CPSRの取得: 現在のCPSRレジスタの値から、条件コードに関連する上位4ビットを抽出し、cpsr変数に格納します。
  3. 条件の評価: opcの値に基づいて、命令が実行されるべき条件が満たされているかを評価します。
    • conditions配列は、一般的な条件コード(EQ, NE, CS, CC, MI, PL, VS, VC, HI, LS)に対応するCPSRフラグの組み合わせを定義しています。この配列は、各条件が満たすべきCPSRフラグの「必須セット」と「必須クリア」のビットマスクを格納しています。
    • opcがこれらの一般的な条件コードの範囲内(0-9)であれば、conditions配列を使用してCPSRの状態をチェックします。
      • ((cpsr & (conditions[opc/2] >> 4)) == (conditions[opc/2] >> 4)):これは、条件が「必須セット」とするフラグがCPSRで実際にセットされているかをチェックします。conditions[opc/2] >> 4は、必須セットのビットマスクです。
      • ((cpsr & (conditions[opc/2] & 0xf)) == 0):これは、条件が「必須クリア」とするフラグがCPSRで実際にクリアされているかをチェックします。conditions[opc/2] & 0xfは、必須クリアのビットマスクです。
      • これらのチェックが成功した場合、または条件が反転(opc & 1)している場合に、命令の実行を継続します。そうでなければ、命令はスキップされ、stepfltは1(命令が占めるワード数)を返して次の命令に進みます。
    • opcGE(Greater than or Equal, N==V)またはLT(Less than, N!=V)の場合、NフラグとVフラグの比較に基づいて条件を評価します。
    • opcGT(Greater than, N==V && Z==0)またはLE(Less than or Equal, N!=V || Z==1)の場合、Nフラグ、Vフラグ、Zフラグの組み合わせに基づいて条件を評価します。
    • opcAL(Always, 14)の場合、常に実行されます。
    • opc15(予約済みまたは不正な条件)の場合、命令は実行されず、stepfltは0を返します。
  4. 命令の書き換え(条件が満たされた場合): 条件が満たされ、命令が実行されることになった場合、元の命令の条件コードフィールド(最上位4ビット)をAL0xe)に書き換えます。これは、後続のexecuteラベルにジャンプした後、命令が常に実行されるようにするためです。これにより、条件チェックのロジックと実際の命令実行ロジックが分離され、コードが簡潔になります。

この変更により、Goランタイムのsoftfloatは、ARMプロセッサのネイティブな条件付き実行の振る舞いをより忠実にエミュレートできるようになり、浮動小数点演算の正確性と互換性が向上しました。

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

src/pkg/runtime/softfloat_arm.c ファイルにおいて、主に以下の変更が行われています。

  1. FLAGS_N, FLAGS_Z, FLAGS_C, FLAGS_V の定義変更:

    -#define FLAGS_N (1 << 31)
    -#define FLAGS_Z (1 << 30)
    -#define FLAGS_C (1 << 29)
    -#define FLAGS_V (1 << 28)
    +#define FLAGS_N (1U << 31)
    +#define FLAGS_Z (1U << 30)
    +#define FLAGS_C (1U << 29)
    +#define FLAGS_V (1U << 28)
    

    11Uに変更することで、符号なし整数としてシフト演算を行うことを明示しています。これにより、FLAGS_Nが負の値として解釈されることを防ぎ、ビット演算の意図を明確にしています。

  2. conditions配列の追加:

    // conditions array record the required CPSR cond field for the
    // first 5 pairs of conditional execution opcodes
    // higher 4 bits are must set, lower 4 bits are must clear
    static const uint8 conditions[10/2] = {
        [0/2] = (FLAGS_Z >> 24) | 0, // 0: EQ (Z set), 1: NE (Z clear)
        [2/2] = (FLAGS_C >> 24) | 0, // 2: CS/HS (C set), 3: CC/LO (C clear)
        [4/2] = (FLAGS_N >> 24) | 0, // 4: MI (N set), 5: PL (N clear)
        [6/2] = (FLAGS_V >> 24) | 0, // 6: VS (V set), 7: VC (V clear)
        [8/2] = (FLAGS_C >> 24) | 
                (FLAGS_Z >> 28),     // 8: HI (C set and Z clear), 9: LS (C clear and Z set)
    };
    

    ARMの条件コードに対応するCPSRフラグの組み合わせを定義する配列です。各要素は、上位4ビットが「必須セット」フラグ、下位4ビットが「必須クリア」フラグを示します。

  3. stepflt関数内の条件付き実行ロジックの追加:

    @@ -102,8 +114,49 @@ stepflt(uint32 *pc, uint32 *regs)
     \ti = *pc;
     
     \tif(trace)
    -\t\truntime·printf(\"stepflt %p %x\\n\", pc, i);\n
    +\t\truntime·printf(\"stepflt %p %x (cpsr %x)\\n\", pc, i, regs[CPSR] >> 28);\n
    +\n+\topc = i >> 28;\n
    +\tif(opc == 14) // common case first\n
    +\t\tgoto execute;\n
    +\tcpsr = regs[CPSR] >> 28;\n
    +\tswitch(opc) {\n
    +\tcase 0: case 1: case 2: case 3: case 4: \n
    +\tcase 5: case 6: case 7: case 8: case 9:\n
    +\t\tif(((cpsr & (conditions[opc/2] >> 4)) == (conditions[opc/2] >> 4)) &&\n
    +\t\t   ((cpsr & (conditions[opc/2] & 0xf)) == 0)) {\n
    +\t\t\tif(opc & 1) return 1;\n
    +\t\t} else {\n
    +\t\t\tif(!(opc & 1)) return 1;\n
    +\t\t}\n
    +\t\tbreak;\n
    +\tcase 10: // GE (N == V)\n
    +\tcase 11: // LT (N != V)\n
    +\t\tif((cpsr & (FLAGS_N >> 28)) == (cpsr & (FLAGS_V >> 28))) {\n
    +\t\t\tif(opc & 1) return 1;\n
    +\t\t} else {\n
    +\t\t\tif(!(opc & 1)) return 1;\n
    +\t\t}\n
    +\t\tbreak;\n
    +\tcase 12: // GT (N == V and Z == 0)\n
    +\tcase 13: // LE (N != V or Z == 1)\n
    +\t\tif((cpsr & (FLAGS_N >> 28)) == (cpsr & (FLAGS_V >> 28)) &&\n
    +\t\t   (cpsr & (FLAGS_Z >> 28)) == 0) {\n
    +\t\t\tif(opc & 1) return 1;\n
    +\t\t} else {\n
    +\t\t\tif(!(opc & 1)) return 1;\n
    +\t\t}\n
    +\t\tbreak;\n
    +\tcase 14: // AL\n
    +\t\tbreak;\n
    +\tcase 15: // shouldn\'t happen\n
    +\t\treturn 0;\n
    +\t}\n
    +\tif(trace)\n
    +\t\truntime·printf(\"conditional %x (cpsr %x) pass\\n\", opc, cpsr);\n
    +\ti = (0xeU << 28) | (i & 0xfffffff);\n
     \n+execute:\n
     \t// special cases\n
     \tif((i&0xfffff000) == 0xe59fb000) {
     \t\t// load r11 from pc-relative address.\n
    

    このセクションが、命令の条件コードを解析し、CPSRの状態に基づいて命令を実行するかどうかを決定する主要なロジックです。条件が満たされた場合、命令の条件コードをAL(常に実行)に書き換え、executeラベルにジャンプして実際の命令シミュレーションに進みます。

コアとなるコードの解説

追加されたコードは、ARMプロセッサの命令パイプラインにおける条件付き実行の振る舞いを、Goランタイムのソフトウェア浮動小数点エミュレータ内で再現するためのものです。

stepflt関数は、ARMの浮動小数点命令を1つずつ処理し、その命令が占めるワード数(通常は1ワード)を返します。この関数が呼び出されるたびに、現在のプログラムカウンタ(pc)が指す命令を読み込み、その命令が浮動小数点命令であるかどうかを判断します。

変更の核心は、命令が浮動小数点命令であると判断された後、その命令の実行条件をチェックする部分です。

  1. opc = i >> 28;: 読み込んだ命令iの最上位4ビットを抽出し、opc(条件コード)として取得します。
  2. if(opc == 14) goto execute;: 最も一般的なケースであるAL(Always)条件(opcが14)の場合、条件チェックをスキップして直接executeラベルにジャンプします。これは最適化であり、常に実行される命令に対して無駄なチェックを省きます。
  3. cpsr = regs[CPSR] >> 28;: 現在のCPSRレジスタから、条件コードフラグが格納されている上位4ビットを抽出します。
  4. switch(opc)ブロック: opcの値に基づいて、様々な条件コードを処理します。
    • 一般的な条件(0-9): conditions配列を使用して、CPSRのフラグが条件を満たしているかをチェックします。例えば、EQopc=0)はZフラグがセットされていることを要求し、NEopc=1)はZフラグがクリアされていることを要求します。opc & 1は、条件が反転しているかどうか(例: EQ vs NE)を判断するために使用されます。条件が満たされない場合、関数は1を返し、命令はスキップされます。
    • GE/LT(10/11): NフラグとVフラグの比較(N == VまたはN != V)に基づいて条件を評価します。
    • GT/LE(12/13): Nフラグ、Vフラグ、Zフラグの組み合わせに基づいて条件を評価します。
    • AL(14): 前述の通り、既に処理済みです。
    • 15: 不正な条件コードの場合、0を返して命令が浮動小数点命令ではないことを示します。
  5. i = (0xeU << 28) | (i & 0xfffffff);: 条件チェックを通過し、命令が実行されることになった場合、元の命令iの条件コードフィールドをAL0xe)に書き換えます。これにより、executeラベル以降の実際の命令シミュレーションロジックは、条件を考慮せずに命令を実行できるようになります。これは、条件チェックと命令実行のロジックを分離し、コードの複雑さを軽減するための一般的な手法です。

このロジックにより、GoランタイムはARMの浮動小数点命令を、その条件付き実行のセマンティクスを含めて正確にエミュレートできるようになり、ARM環境でのGoプログラムの安定性と互換性が向上しました。

関連リンク

参考にした情報源リンク