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

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

このコミットは、GoランタイムのCgo(C言語との相互運用)部分において、ARMアーキテクチャでの間接呼び出しの方法を「旧来のスタイル」に変更するものです。具体的には、bl命令による直接的な関数呼び出しから、mov lr, pcmov pc, rXを組み合わせた間接的なジャンプ命令に切り替えることで、特定の状況下での挙動を調整しています。これは、GoとCのコード間でスタックやレジスタの状態を適切に管理し、安定した相互運用を保証するための低レベルな最適化または修正と考えられます。

コミット

commit 2560f8fe223a35aa5a6b203a3bfd922cb4bd819e
Author: Russ Cox <rsc@golang.org>
Date:   Wed Aug 14 14:54:08 2013 -0400

    runtime/cgo: use old-style indirect call on arm
    
    TBR=elias.naur
    CC=golang-dev
    https://golang.org/cl/12943043
---
 src/pkg/runtime/cgo/gcc_arm.S | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/pkg/runtime/cgo/gcc_arm.S b/src/pkg/runtime/cgo/gcc_arm.S
index 7cf91f9ffa..be50408825 100644
--- a/src/pkg/runtime/cgo/gcc_arm.S
+++ b/src/pkg/runtime/cgo/gcc_arm.S
@@ -25,8 +25,12 @@ EXT(crosscall_arm2):\n \tmov r5, r1\n \tmov r0, r2\n \tmov r1, r3\n-\tbl r5 // setmg(m, g)\n-\tbl r4 // fn()\n+\t// setmg(m, g)\n+\tmov lr, pc\n+\tmov pc, r5\n+\t// fn()\n+\tmov lr, pc\n+\tmov pc, r4\n \tpop {r4, r5, r6, r7, r8, r9, r10, r11, ip, pc}\n \n .globl EXT(__stack_chk_fail_local)\n```

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

[https://github.com/golang/go/commit/2560f8fe223a35aa5a6b203a3bfd922cb4bd819e](https://github.com/golang/go/commit/2560f8fe223a35aa5a6b203a3bfd922cb4bd819e)

## 元コミット内容

このコミットの目的は、「ARMアーキテクチャにおいて、Cgoの間接呼び出しに旧来のスタイルを使用する」ことです。これは、GoランタイムがC言語の関数を呼び出す際、またはC言語からGoの関数を呼び出す際に、特定の呼び出し規約やレジスタの使用方法を調整する必要があることを示唆しています。特にARMのような組み込みシステムで広く使われるアーキテクチャでは、低レベルなアセンブリコードでの正確な制御が重要になります。

## 変更の背景

Go言語のCgoは、GoプログラムがCライブラリと連携するための重要な機能です。しかし、Goのランタイム(特にガベージコレクタ)はメモリを移動させる可能性があるため、CコードがGoのメモリを直接参照する際には注意が必要です。Go 1.6以前は、GoポインタをCに渡す際のルールが緩やかで、これが原因でダングリングポインタ(無効なメモリを指すポインタ)やデータ破損といったデバッグが困難な問題を引き起こす可能性がありました。

このコミットが行われた2013年8月は、Go 1.6で厳格なポインタ渡しルールが導入される前の時期にあたります。したがって、この「旧来のスタイル」への変更は、当時のGoランタイムがARMアーキテクチャ上でCgoの呼び出しをより安全かつ安定して行うための、特定の低レベルな回避策または最適化であったと考えられます。

具体的には、`bl` (Branch with Link) 命令は関数呼び出しに使用され、呼び出し元のアドレスを`lr` (Link Register) に保存してからジャンプします。しかし、Cgoのようなクロス言語呼び出しのコンテキストでは、GoとCの間でスタックやレジスタの状態が複雑に変化するため、標準的な`bl`命令の挙動が常に適切とは限りません。

このコミットは、`bl`命令の代わりに`mov lr, pc`と`mov pc, rX`という2つの命令を組み合わせることで、より明示的にリターンアドレスを`lr`に保存し、指定されたレジスタ`rX`(この場合は`r5`または`r4`)に格納されたアドレスへジャンプしています。これは、GoランタイムがCgo呼び出しの前後でレジスタの状態をより細かく制御し、GoとCの呼び出し規約の違いを吸収するためのアプローチであったと推測されます。

## 前提知識の解説

1.  **Cgo**: Go言語の外部関数インターフェース (Foreign Function Interface)。GoプログラムからC言語の関数を呼び出したり、C言語からGoの関数を呼び出したりすることを可能にします。これにより、既存のCライブラリの利用や、システムレベルのプログラミングが可能になります。
2.  **ARMアーキテクチャ**: Advanced RISC Machineの略で、モバイルデバイス、組み込みシステム、サーバーなどで広く使用されているCPUアーキテクチャです。RISC (Reduced Instruction Set Computer) の原則に基づき、シンプルで高速な命令セットが特徴です。
3.  **間接呼び出し (Indirect Call)**: 呼び出す関数のアドレスがコンパイル時には確定せず、実行時にレジスタやメモリから取得される呼び出し方法です。関数ポインタを介した呼び出しなどがこれに該当します。
4.  **ARMアセンブリ命令**:
    *   `mov Rd, Rn`: レジスタ`Rn`の値をレジスタ`Rd`にコピーします。
    *   `bl label`: `label`へジャンプし、現在のプログラムカウンタ (PC) の次の命令のアドレスをリンクレジスタ (`lr`) に保存します。関数呼び出しに使用されます。
    *   `mov lr, pc`: 現在のプログラムカウンタ (`pc`) の値をリンクレジスタ (`lr`) にコピーします。`pc`は通常、現在の命令の次の命令のアドレスを指します。
    *   `mov pc, Rn`: レジスタ`Rn`の値をプログラムカウンタ (`pc`) にコピーします。これにより、`Rn`に格納されたアドレスへジャンプします。
    *   `pc` (Program Counter): 次に実行される命令のアドレスを保持するレジスタ。
    *   `lr` (Link Register): 関数呼び出し時に、呼び出し元に戻るためのアドレス(リターンアドレス)を保持するレジスタ。
5.  **Goのポインタ渡しルールとCgoの進化**:
    *   **Go 1.6以前**: GoのガベージコレクタはGoオブジェクトをメモリ内で移動させることがあります。Go 1.6以前は、GoポインタをCに直接渡すことが可能でしたが、GCがGoオブジェクトを移動させた場合、Cコードが古いアドレスを参照し続け、ダングリングポインタの問題を引き起こす可能性がありました。
    *   **Go 1.6以降**: Go 1.6では、GoポインタをCに渡す際の厳格なルールが導入されました。Goポインタは、それが指すGoメモリが他のGoポインタを含まない場合にのみCに渡すことができます。これにより、GCがメモリを移動させても安全性が保たれるようになりました。
    *   **`runtime/cgo.Handle` (Go 1.17以降)**: Go 1.17では、より複雑なGoの値をCに安全に渡すための`runtime/cgo.Handle`が導入されました。これは、Goの値を直接渡す代わりに、整数型の「ハンドル」をCに渡し、CがこのハンドルをGoに渡すことで元のGoの値を再取得するというメカニズムです。これにより、Goランタイムがメモリの制御を維持し、ダングリングポインタを防ぎます。

このコミットはGo 1.6以前の挙動に関連しており、当時のCgoの制約やARMアーキテクチャの特性を考慮した低レベルな調整であったと理解できます。

## 技術的詳細

このコミットの核心は、ARMアセンブリにおける関数呼び出しのメカニズムの変更にあります。

ARMアーキテクチャでは、関数呼び出しは通常、`bl` (Branch with Link) 命令を使用して行われます。`bl`命令は、呼び出し先の関数へジャンプする前に、現在のプログラムカウンタ (PC) の次の命令のアドレスをリンクレジスタ (`lr`) に保存します。これにより、関数が終了した後に呼び出し元に戻ることができます。

しかし、Cgoのコンテキストでは、GoとCの間で実行コンテキストが切り替わるため、単純な`bl`命令では不十分な場合があります。Goランタイムは、Cgo呼び出しの際に、Goのスケジューラ、スタック、ガベージコレクタの挙動を考慮に入れる必要があります。

このコミットでは、`bl rX`という命令を、以下の2つの命令の組み合わせに置き換えています。

1.  `mov lr, pc`: 現在のプログラムカウンタ (`pc`) の値をリンクレジスタ (`lr`) に明示的にコピーします。これは、`bl`命令が暗黙的に行うリターンアドレスの保存と同じ目的ですが、より直接的な制御を可能にします。
2.  `mov pc, rX`: レジスタ`rX`(この場合は`r5`または`r4`)に格納されているアドレスへジャンプします。これにより、`rX`に格納された関数ポインタを介して間接的に関数を呼び出すことができます。

この変更は、「旧来のスタイル」と表現されていますが、これは当時のGoランタイムがARM上でCgoの呼び出しを処理するための、より堅牢または互換性のある方法であったことを示唆しています。考えられる理由はいくつかあります。

*   **呼び出し規約の厳密な遵守**: GoとCの呼び出し規約が異なる場合、`bl`のような高レベルな命令では、Goランタイムが期待するレジスタの状態やスタックフレームの配置を正確に保証できない可能性があります。`mov lr, pc; mov pc, rX`の組み合わせは、リターンアドレスの保存とジャンプをより細かく制御し、GoランタイムがCgo呼び出しの前後で必要な状態を確立するのに役立った可能性があります。
*   **デバッグの容易性**: 特定のデバッグツールや環境において、`bl`命令よりも`mov lr, pc; mov pc, rX`の組み合わせの方が、呼び出しスタックのトレースやレジスタの状態の確認が容易であった可能性があります。
*   **特定のARMプロセッサの挙動**: ARMプロセッサの特定のモデルやバージョンにおいて、`bl`命令の挙動に微妙な違いがあったり、特定の最適化がCgoのコンテキストで問題を引き起こしたりする可能性があったかもしれません。この「旧来のスタイル」は、より広範なARMプロセッサでの互換性を確保するためのものであった可能性も考えられます。

この変更は、`setmg(m, g)`と`fn()`という2つの関数呼び出しに対して適用されています。`setmg(m, g)`はGoのM(Machine)とG(Goroutine)の状態を設定する関数、`fn()`は実際にCgoを介して呼び出されるGoまたはCの関数を指していると考えられます。これらの低レベルな呼び出しにおいて、Goランタイムが正確な制御を必要としていたことが伺えます。

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

変更は`src/pkg/runtime/cgo/gcc_arm.S`ファイルにあります。

```diff
--- a/src/pkg/runtime/cgo/gcc_arm.S
+++ b/src/pkg/runtime/cgo/gcc_arm.S
@@ -25,8 +25,12 @@ EXT(crosscall_arm2):\n \tmov r5, r1\n \tmov r0, r2\n \tmov r1, r3\n-\tbl r5 // setmg(m, g)\n-\tbl r4 // fn()\n+\t// setmg(m, g)\n+\tmov lr, pc\n+\tmov pc, r5\n+\t// fn()\n+\tmov lr, pc\n+\tmov pc, r4\n \tpop {r4, r5, r6, r7, r8, r9, r10, r11, ip, pc}\n \n .globl EXT(__stack_chk_fail_local)\n```

具体的には、`crosscall_arm2`というラベル内の2つの`bl`命令が変更されています。

*   `bl r5 // setmg(m, g)` が `mov lr, pc; mov pc, r5` に変更。
*   `bl r4 // fn()` が `mov lr, pc; mov pc, r4` に変更。

## コアとなるコードの解説

変更されたアセンブリコードは、GoランタイムがCgo呼び出しを行う際の低レベルな制御を示しています。

元のコードでは、`bl r5`と`bl r4`という命令が使用されていました。
*   `bl r5`: レジスタ`r5`に格納されているアドレスへジャンプし、リターンアドレスを`lr`に保存します。これは、`setmg(m, g)`関数を呼び出すためのものです。
*   `bl r4`: レジスタ`r4`に格納されているアドレスへジャンプし、リターンアドレスを`lr`に保存します。これは、`fn()`関数(実際のCgoコールバック関数)を呼び出すためのものです。

変更後のコードでは、これらの`bl`命令が以下のように置き換えられています。

```assembly
// setmg(m, g)
mov lr, pc
mov pc, r5
// fn()
mov lr, pc
mov pc, r4

この変更のポイントは以下の通りです。

  1. 明示的なリターンアドレスの保存: mov lr, pc命令は、現在のプログラムカウンタ (pc) の値をリンクレジスタ (lr) に明示的にコピーします。pcは通常、現在の命令の次の命令のアドレスを指すため、これはbl命令が暗黙的に行うリターンアドレスの保存と同じ効果を持ちます。しかし、明示的に行うことで、Goランタイムがリターンアドレスの管理をより細かく制御できる可能性があります。
  2. 間接的なジャンプ: mov pc, rX命令は、レジスタrXr5またはr4)に格納されているアドレスへプログラムカウンタを直接移動させます。これにより、rXに格納された関数ポインタを介して間接的に関数を呼び出すことができます。bl命令も間接呼び出しを行いますが、mov pc, rXはより直接的なジャンプであり、呼び出し規約やスタックフレームの管理において、Goランタイムがより柔軟な制御を必要としていた可能性を示唆しています。

この「旧来のスタイル」への変更は、当時のGoランタイムがARMアーキテクチャ上でCgoの呼び出しをより安定させるための、特定の低レベルな調整であったと考えられます。これは、GoとCの間のコンテキスト切り替え、スタックの切り替え、レジスタの状態の保存と復元といった複雑な処理を正確に行うために必要だった可能性があります。特に、Goのガベージコレクタがメモリを移動させる可能性があるため、Cgo呼び出しの前後でレジスタやメモリの状態を厳密に管理することが重要になります。

関連リンク

参考にした情報源リンク