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

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

このコミットは、Go言語のリンカ(liblink)におけるARMアーキテクチャ向けのコード生成に関するバグ修正です。具体的には、浮動小数点レジスタの移動命令(MOVFDおよびMOVDF)において、レジスタ番号が7を超える場合に誤った機械語が生成される問題を解決します。

コミット

commit 4e5f31a760be4321777a57d1fa991a05c2d6a233
Author: Josh Bleecher Snyder <josharian@gmail.com>
Date:   Tue Mar 11 14:04:44 2014 -0400

    liblink: fix bad code generated for MOVFD/MOVDF when reg > 7
    
    The byte that r is or'd into is already 0x7, so the failure to zero r only
    impacts the generated machine code if the register is > 7.
    
    Fixes #7044.
    
    LGTM=dave, minux.ma, rsc
    R=dave, minux.ma, bradfitz, rsc
    CC=golang-codereviews
    https://golang.org/cl/73730043

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

https://github.com/golang/go/commit/4e5f31a760be4321777a57d1fa991a05c2d6a233

元コミット内容

liblink: fix bad code generated for MOVFD/MOVDF when reg > 7

The byte that r is or'd into is already 0x7, so the failure to zero r only
impacts the generated machine code if the register is > 7.

Fixes #7044.

LGTM=dave, minux.ma, rsc
R=dave, minux.ma, bradfitz, rsc
CC=golang-codereviews
https://golang.org/cl/73730043

変更の背景

この変更は、Go言語のリンカがARMアーキテクチャ向けに浮動小数点レジスタの移動命令(MOVFDおよびMOVDF)の機械語を生成する際に発生していたバグ(Issue 7044)を修正するために行われました。

問題の根源は、レジスタ番号が7を超える場合に、生成される機械語が不正になるというものでした。ARMアーキテクチャの浮動小数点レジスタは通常s0からs31(単精度)またはd0からd15(倍精度)のように番号付けされており、Goのリンカがこれらのレジスタを扱う際に、特定のバイトが適切にゼロクリアされないままレジスタ番号がOR演算されていたため、レジスタ番号が8以上になると誤ったビットが設定され、結果として不正な命令が生成されていました。

このバグは、特に多くの浮動小数点レジスタを使用するような計算集約的なGoプログラムがARMデバイス上で実行される際に、クラッシュや不正な結果を引き起こす可能性がありました。この修正は、GoプログラムがARM環境で正しく動作するための重要な安定性向上です。

前提知識の解説

  • Go言語のリンカ (liblink): Go言語のコンパイルプロセスにおいて、コンパイラが生成したオブジェクトファイルを結合し、実行可能なバイナリを生成する役割を担うツールです。liblinkは、様々なアーキテクチャ(x86, ARMなど)向けの機械語生成ロジックを含んでいます。
  • ARMアーキテクチャ: スマートフォン、タブレット、組み込みシステムなどで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。ARMプロセッサは、その電力効率の高さからモバイルデバイスで特に普及しています。
  • 浮動小数点レジスタ: プロセッサ内部に存在する、浮動小数点数(実数)の計算を高速に行うための専用レジスタです。ARMアーキテクチャには、単精度(32ビット)のSレジスタと倍精度(64ビット)のDレジスタがあります。
  • MOVFD / MOVDF 命令:
    • MOVFD (Move Float Double): 単精度浮動小数点レジスタ(Sレジスタ)から倍精度浮動小数点レジスタ(Dレジスタ)へ値を移動(変換)する命令。
    • MOVDF (Move Double Float): 倍精度浮動小数点レジスタ(Dレジスタ)から単精度浮動小数点レジスタ(Sレジスタ)へ値を移動(変換)する命令。 これらの命令は、異なる精度の浮動小数点数間で値をやり取りする際に使用されます。
  • 機械語生成: コンパイラやリンカが、プログラミング言語のコードをプロセッサが直接実行できるバイナリ形式の命令(機械語)に変換するプロセスです。このプロセスでは、レジスタの割り当て、命令のエンコーディングなどが行われます。
  • レジスタ番号とエンコーディング: ARM命令セットでは、命令の一部としてレジスタ番号がエンコードされます。特定の命令形式では、レジスタ番号を格納するためのビットフィールドが限られている場合があります。このコミットの文脈では、レジスタ番号が7を超える場合に問題が発生したことから、おそらく3ビット(0-7)でレジスタを表現する部分に起因する問題であったと推測されます。

技術的詳細

このバグは、src/liblink/asm5.cファイル内のARMアーキテクチャ向けアセンブリコード生成ロジックに存在していました。具体的には、if(0 /*debug['G']*/) print("%ux: %s: arm %d\\n", (uint32)(p->pc), p->from.sym->naで始まるコードブロック内で、浮動小数点レジスタを扱う際の処理に問題がありました。

元のコードでは、rという変数がレジスタ番号を保持していましたが、特定の条件下(p->asAMOVFAMOVDなどの命令の場合)にrがゼロに設定されていました。しかし、MOVFDMOVDFといった浮動小数点変換命令がこの条件に含まれていなかったため、これらの命令でレジスタ番号が8以上の場合に、rの値が適切にゼロクリアされず、その後のOR演算によって不正な機械語が生成されていました。

ARM命令のエンコーディングでは、レジスタ番号が命令の特定のビットフィールドに埋め込まれます。このバグは、レジスタ番号が8以上(つまり、3ビットで表現できない範囲)になったときに、本来ゼロであるべきビットがゼロにならず、結果として命令のオペランドが誤って解釈されることで発生しました。

修正は、MOVFDMOVDF命令も、AMOVFAMOVDなどと同様にrをゼロにする条件に追加することで、この問題を解決しています。これにより、レジスタ番号が8以上の場合でも、正しい機械語が生成されるようになりました。

この修正を検証するために、test/fixedbugs/issue7044.goという新しいテストファイルが追加されました。このテストは、float32からfloat64へ、そしてfloat64からfloat32への変換を、16個の異なる浮動小数点レジスタ(f0からf15d0からd15g0からg15など)を網羅的に使用して行います。これにより、レジスタ番号が7を超えるケース(特にf8以降のレジスタ)でバグが確実に再現され、修正が正しく適用されていることを確認できます。

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

--- a/src/liblink/asm5.c
+++ b/src/liblink/asm5.c
@@ -1635,7 +1635,8 @@ if(0 /*debug['G']*/) print("%ux: %s: arm %d\\n", (uint32)(p->pc), p->from.sym->na
 		r = p->reg;
 		if(r == NREG) {
 			r = rt;
-			if(p->as == AMOVF || p->as == AMOVD || p->as == ASQRTF || p->as == ASQRTD || p->as == AABSF || p->as == AABSD)
+			if(p->as == AMOVF || p->as == AMOVD || p->as == AMOVFD || p->as == AMOVDF ||\
+				p->as == ASQRTF || p->as == ASQRTD || p->as == AABSF || p->as == AABSD)
 				r = 0;
 		}
 		o1 |= rf | (r<<16) | (rt<<12);
--- /dev/null
+++ b/test/fixedbugs/issue7044.go
@@ -0,0 +1,43 @@
+// run
+
+// Copyright 2014 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Issue 7044: bad AMOVFD and AMOVDF assembly generation on
+// arm for registers above 7.
+
+package main
+
+import (
+	"fmt"
+	"reflect"
+)
+
+func f() [16]float32 {
+	f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15 :=
+		float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1), float32(1)
+	// Use all 16 registers to do float32 --> float64 conversion.
+	d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15 :=
+		float64(f0), float64(f1), float64(f2), float64(f3), float64(f4), float64(f5), float64(f6), float64(f7), float64(f8), float64(f9), float64(f10), float64(f11), float64(f12), float64(f13), float64(f14), float64(f15)
+	// Use all 16 registers to do float64 --> float32 conversion.
+	g0, g1, g2, g3, g4, g5, g6, g7, g8, g9, g10, g11, g12, g13, g14, g15 :=
+		float32(d0), float32(d1), float32(d2), float32(d3), float32(d4), float32(d5), float32(d6), float32(d7), float32(d8), float32(d9), float32(d10), float32(d11), float32(d12), float32(d13), float32(d14), float32(d15)
+	// Force another conversion, so that the previous conversion doesn't
+	// get optimized away into constructing the returned array. With current
+	// optimizations, constructing the returned array uses only
+	// a single register.
+	e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15 :=
+		float64(g0), float64(g1), float64(g2), float64(g3), float64(g4), float64(g5), float64(g6), float64(g7), float64(g8), float64(g9), float64(g10), float64(g11), float64(g12), float64(g13), float64(g14), float64(g15)
+	return [16]float32{
+		float32(e0), float32(e1), float32(e2), float32(e3), float32(e4), float32(e5), float32(e6), float32(e7), float32(e8), float32(e9), float32(e10), float32(e11), float32(e12), float32(e13), float32(e14), float32(e15),
+	}
+}
+
+func main() {
+	want := [16]float32{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
+	got := f()
+	if !reflect.DeepEqual(got, want) {
+		fmt.Printf("f() = %#v; want %#v\n", got, want)
+	}
+}

コアとなるコードの解説

src/liblink/asm5.cの変更は非常に小さいですが、その影響は大きいです。

変更前のコードでは、p->as(現在の命令の種類を示す)がAMOVF(単精度浮動小数点移動)やAMOVD(倍精度浮動小数点移動)などの命令である場合にのみ、レジスタ変数r0に設定されていました。しかし、AMOVFD(単精度から倍精度への変換)やAMOVDF(倍精度から単精度への変換)といった命令は、この条件に含まれていませんでした。

このため、AMOVFDAMOVDF命令が処理され、かつ使用されるレジスタ番号が7を超える場合(例えば、d8s16など)、rが適切にゼロクリアされず、その結果、命令のエンコーディング時に誤ったビットが設定され、不正な機械語が生成されていました。

修正では、if文の条件にp->as == AMOVFD || p->as == AMOVDF ||が追加されました。これにより、AMOVFDAMOVDF命令が処理される際にも、rが適切にゼロに設定されるようになり、レジスタ番号が8以上の場合でも正しい機械語が生成されるようになりました。

test/fixedbugs/issue7044.goは、この修正の有効性を確認するためのテストケースです。

  • このテストは、// runディレクティブで始まり、実行可能なテストであることを示しています。
  • f()関数内で、16個のfloat32変数を初期化し、それらをfloat64に変換(float32 --> float64)し、さらにその結果をfloat32に戻す(float64 --> float32)という一連の浮動小数点変換を行います。
  • この変換プロセスでは、ARMアーキテクチャの浮動小数点レジスタを広範囲にわたって使用するように設計されています。特に、レジスタ番号が7を超えるf8からf15d8からd15などのレジスタが使用されることで、修正前のバグが再現される状況を作り出します。
  • 最後の変換結果を[16]float32の配列として返し、main()関数で期待される結果(すべて1.0の配列)と比較します。reflect.DeepEqualを使用して、配列の内容が完全に一致するかどうかを検証します。
  • もし結果が期待と異なる場合、エラーメッセージを出力してテストが失敗したことを示します。

このテストは、リンカがMOVFDMOVDF命令を正しく機械語に変換できるようになったことを、実際のGoコードの実行を通じて検証するものです。

関連リンク

  • Go CL 73730043: https://golang.org/cl/73730043
  • Go Issue 7044: (直接のGitHubリンクは見つかりませんでしたが、CLのコミットメッセージで参照されています)

参考にした情報源リンク

  • Go CL 73730043: https://golang.org/cl/73730043
  • Go言語のリンカに関する一般的な情報 (Goのドキュメントやソースコード)
  • ARMアーキテクチャの浮動小数点命令セットに関する情報 (ARMの公式ドキュメントなど)
  • Go言語のIssueトラッカー (Issue 7044の詳細は、Goの内部バグトラッカーに存在する可能性があります)