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

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

このコミットは、Go言語のビルドシステム(cmd/dist)において、NetBSD/ARMアーキテクチャのサポートを改善するためのものです。具体的には、NetBSD/ARM環境で発生していた3つの主要な問題に対処しています。

  1. SIGILLの不適切な挙動: サポートされていないVFP命令が実行された際に、NetBSDカーネルがSIGILL(不正命令シグナル)を適切に報告せず、無限ループに陥る問題。これを検出するためにalarm(2)システムコールが導入されました。
  2. VFP11サポートの不完全性: NetBSD/ARMにおけるVFP11(Vector Floating Point)のサポートが不完全であるため、一時的にVFPv2(GOARM=6)の使用を無効化し、より安全なVFPv1(GOARM=5)に制限しています。
  3. GCCの最適化問題: NetBSD-currentに同梱されているGCCが-O2最適化レベルでGoのコードを誤って最適化してしまう問題。これを回避するため、NetBSD/ARM環境では最適化レベルを-O1に引き下げています。

コミット

commit 3d50aaf4832e1b1f8143f15cee9c425fd09e94e5
Author: Shenghou Ma <minux.ma@gmail.com>
Date:   Sun Mar 3 06:50:17 2013 +0800

    cmd/dist: support for NetBSD/ARM
    1. when executing a unsupported VFP instruction, the NetBSD kernel somehow
    doesn't report SIGILL, and instead just spin and spin, we add a alarm(2)
    to detect this case (albeit this is a kernel bug).
    2. NetBSD/ARM's VFP11 support is not complete, so temporarily disable it.
    3. The default gcc shipped with NetBSD-current mis-optimizes our code
    at -O2, so lower the optimization level to -O1 on NetBSD/ARM.
    
    R=dave, rsc
    CC=golang-dev
    https://golang.org/cl/7286044

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

https://github.com/golang/go/commit/3d50aaf4832e1b1f8143f15cee9c425fd09e94e5

元コミット内容

cmd/dist: support for NetBSD/ARM
1. when executing a unsupported VFP instruction, the NetBSD kernel somehow
doesn't report SIGILL, and instead just spin and spin, we add a alarm(2)
to detect this case (albeit this is a kernel bug).
2. NetBSD/ARM's VFP11 support is not complete, so temporarily disable it.
3. The default gcc shipped with NetBSD-current mis-optimizes our code
at -O2, so lower the optimization level to -O1 on NetBSD/ARM.

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

変更の背景

このコミットは、Go言語をNetBSD/ARMプラットフォームでより安定して動作させるために行われました。当時のNetBSD/ARM環境には、Goの実行に影響を与えるいくつかの問題が存在していました。

  1. VFP命令の不正な処理: ARMアーキテクチャには浮動小数点演算を高速化するためのVFP(Vector Floating Point)ユニットがあります。Goのコンパイラやランタイムは、利用可能なVFPのバージョン(VFPv1, VFPv2, VFPv3など)を検出して最適なコードを生成しようとします。しかし、NetBSDカーネルがサポートされていないVFP命令に遭遇した際に、通常発生すべきSIGILLシグナルを適切に発行せず、CPUが無限ループに陥るというバグがありました。これは、GoのビルドプロセスにおけるVFP機能の検出ロジックを妨げ、ビルドがハングアップする原因となっていました。
  2. VFP11の不完全なサポート: 特にRaspberry Piのようなデバイスで利用されるARM11プロセッサ(VFP11を搭載)において、NetBSDのVFPサポートが不完全であり、浮動小数点演算の正確性(不正確な結果、非正規化数、NaNの扱い)に問題があることが判明しました。これにより、Goの数学テストの一部が失敗する状況でした。また、GOARM=6(VFPv2相当)でcgoを使用すると、カーネルのアサーション失敗を引き起こし、システムがクラッシュする可能性もありました。
  3. GCCの最適化バグ: NetBSD-currentに同梱されていたGCCのバージョン(特にGCC 4.5.4)が、Goの特定のコード(例: gc/mparith3.c)を-O2(最適化レベル2)でコンパイルする際に誤った最適化を行い、不正なコードを生成してしまうバグがありました。これはGoのビルドの失敗や、生成されたバイナリの誤動作につながる可能性がありました。

これらの問題に対処し、NetBSD/ARM上でのGoのビルドと実行の信頼性を向上させることが、このコミットの主な目的でした。

前提知識の解説

NetBSD

NetBSDは、BSD系UNIXオペレーティングシステムの一つで、特にその移植性の高さで知られています。非常に多くの異なるハードウェアアーキテクチャで動作するように設計されており、ARMもその一つです。

ARMアーキテクチャ

ARM(Advanced RISC Machine)は、モバイルデバイスや組み込みシステムで広く使用されているRISC(Reduced Instruction Set Computer)ベースのプロセッサアーキテクチャです。低消費電力と高性能を両立させています。

VFP (Vector Floating Point)

VFPは、ARMアーキテクチャにおける浮動小数点演算ユニット(FPU)の拡張機能です。単精度および倍精度の浮動小数点演算をハードウェアで高速に実行するために使用されます。VFPには複数のバージョンがあり、それぞれ異なる機能セットと性能を持ちます。

  • VFPv1: 初期バージョン。
  • VFPv2: VFPv1の改良版で、ARMv5TEJおよびARMv6アーキテクチャで利用可能。GoのGOARM=6は通常VFPv2を想定します。
  • VFPv3: VFPv2の改良版で、ARMv7アーキテクチャで利用可能。GoのGOARM=7は通常VFPv3を想定します。

Goのビルドシステムは、実行環境のARMプロセッサがどのVFPバージョンをサポートしているかを検出し、それに応じてGOARM環境変数を設定しようとします。

SIGILL (Illegal Instruction Signal)

SIGILLは、プロセスが不正な命令(CPUが認識できない、または現在のモードで実行が許可されていない命令)を実行しようとしたときに、オペレーティングシステムによってプロセスに送信されるシグナルです。通常、プログラムのバグや、互換性のないCPU命令セットを使用しようとした場合に発生します。

SIGALRM (Alarm Clock Signal)

SIGALRMは、alarm(2)システムコールによって設定されたタイマーが満了したときに、オペレーティングシステムによってプロセスに送信されるシグナルです。このシグナルは、特定の操作がタイムアウトしたことを検出するために使用できます。

alarm(2)システムコール

alarm(2)は、指定された秒数後にSIGALRMシグナルを現在のプロセスに送信するようにタイマーを設定するUNIX系システムコールです。以前に設定されたアラームがある場合、それはキャンセルされ、新しいアラームが設定されます。引数に0を指定すると、現在のアラームがキャンセルされます。

GCCの最適化レベル (-O1, -O2)

GCC(GNU Compiler Collection)は、C、C++、Goなど多くのプログラミング言語をサポートするコンパイラです。コンパイル時にコードの最適化を行うための様々なオプションを提供します。

  • -O1: 基本的な最適化を有効にします。コンパイル時間と生成されるコードサイズのバランスを取りながら、ある程度の性能向上を目指します。
  • -O2: より多くの最適化を有効にします。-O1よりも積極的な最適化を行い、通常はより高速なコードを生成しますが、コンパイル時間は長くなる可能性があります。一部の複雑な最適化は、コンパイラのバグや特定のコードパターンとの相互作用により、予期せぬ問題(誤ったコード生成)を引き起こすことがあります。

技術的詳細

このコミットは、NetBSD/ARM環境におけるGoのビルドと実行の安定性を確保するために、以下の3つの主要な技術的変更を導入しています。

  1. SIGILLの検出強化とalarm(2)の導入:

    • 問題: NetBSDカーネルが、サポートされていないVFP命令の実行時にSIGILLを適切に発行せず、代わりにCPUが無限ループに陥るというカーネルバグが存在しました。Goのビルドシステムは、xtryexecfunc関数を使用して特定のVFP命令を実行し、SIGILLの発生を監視することで、その命令がサポートされているかどうかを検出します。このカーネルバグにより、検出プロセスがハングアップしていました。
    • 解決策: xtryexecfunc関数にalarm(2)システムコールを追加しました。具体的には、VFP命令の実行を試みる前に1秒のタイマーを設定し、SIGALRMシグナルハンドラもsigillhandSIGILLと同じハンドラ)に設定します。もし1秒以内にSIGILLが発生しない場合(つまり、カーネルがハングアップしている場合)、SIGALRMがトリガーされ、sigillhandが呼び出されてsigsetjmpによって設定されたジャンプポイントに戻ります。これにより、無限ループを回避し、VFP命令の実行が失敗したと判断できるようになります。これはカーネルバグのワークアラウンドであり、根本的な解決ではありませんが、Goのビルドプロセスが進行できるようになります。
  2. NetBSD/ARMにおけるVFPv2(GOARM=6)の無効化:

    • 問題: NetBSD/ARM、特にVFP11を搭載したシステム(例: Raspberry Pi)では、VFPv2のサポートが不完全であり、浮動小数点演算の正確性に問題がありました。これにより、Goの数学テストが失敗したり、cgoと組み合わせた場合にカーネルクラッシュを引き起こす可能性がありました。
    • 解決策: src/cmd/dist/arm.c内のxgetgoarm関数において、__NetBSD__または__FreeBSD__が定義されている場合、無条件にGOARM="5"(VFPv1相当)を返すように変更しました。これにより、より新しいVFPバージョン(VFPv2やVFPv3)の検出と使用を強制的に無効化し、既知のバグを回避します。ユーザーは手動でGOARM環境変数を設定することでこの制限を上書きできますが、デフォルトでは安全な設定が適用されます。
  3. NetBSD/ARMにおけるGCC最適化レベルの引き下げ:

    • 問題: NetBSD-currentに同梱されていたGCC 4.5.4が、ARMアーキテクチャでGoの特定のコードを-O2最適化レベルでコンパイルする際に誤ったコードを生成するバグがありました。このバグは、http://patchwork.ozlabs.org/patch/64562/で修正が利用可能でしたが、当時のNetBSDのデフォルトGCCには適用されていませんでした。
    • 解決策: src/cmd/dist/build.c内のproto_gccargs配列において、__NetBSD____arm__が両方定義されている場合、GCCの最適化レベルを-O2から-O1に引き下げるように変更しました。これにより、誤った最適化によるコンパイルエラーやランタイムエラーを回避し、Goのビルドの成功率を高めます。-O1-O2よりも積極的でない最適化を行うため、このバグの影響を受けにくいと考えられます。

これらの変更は、NetBSD/ARMプラットフォームでのGoの安定性と信頼性を大幅に向上させることを目的としています。

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

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

  1. src/cmd/dist/arm.c:

    • xgetgoarm関数に、__NetBSD__または__FreeBSD__が定義されている場合にGOARM="5"を返す条件付きコンパイルディレクティブを追加。
    --- a/src/cmd/dist/arm.c
    +++ b/src/cmd/dist/arm.c
    @@ -17,6 +17,18 @@ static void useVFPv1(void);\
     char *
     xgetgoarm(void)
     {
    +#if defined(__NetBSD__) || defined(__FreeBSD__)
    +// NetBSD has buggy support for VFPv2 (incorrect inexact, 
    +// denormial, and NaN handling). When GOARM=6, some of our
    +// math tests fails on Raspberry Pi.
    +// Thus we return "5" here for safety, the user is free
    +// to override.
    +// Note: using GOARM=6 with cgo can trigger a kernel assertion
    +// failure and crash NetBSD/evbarm kernel.
    +// FreeBSD also have broken VFP support, so disable VFP also
    +// on FreeBSD.
    +return "5";
    +#endif
     if(xtryexecfunc(useVFPv3))
     return "7";
     else if(xtryexecfunc(useVFPv1))
    
  2. src/cmd/dist/build.c:

    • proto_gccargs配列内のGCC最適化レベルの設定に、__NetBSD____arm__が両方定義されている場合に-O1を使用する条件付きコンパイルディレクティブを追加。それ以外の場合は-O2を使用。
    --- a/src/cmd/dist/build.c
    +++ b/src/cmd/dist/build.c
    @@ -411,7 +411,13 @@ static char *proto_gccargs[] = {\
     "-fno-common",
     "-ggdb",
     "-pipe",
    +#if defined(__NetBSD__) && defined(__arm__)
    +// GCC 4.5.4 (NetBSD nb1 20120916) on ARM is known to mis-optimize gc/mparith3.c
    +// Fix available at http://patchwork.ozlabs.org/patch/64562/.
    +"-O1",
    +#else
     "-O2",
    +#endif
     };
     
     static Vec gccargs;
    
  3. src/cmd/dist/unix.c:

    • xtryexecfunc関数にSIGALRMシグナルハンドラの設定とalarm(1)の呼び出しを追加。また、関数の終了時にalarm(0)でタイマーをキャンセルし、SIGALRMハンドラをデフォルトに戻す処理を追加。
    --- a/src/cmd/dist/unix.c
    +++ b/src/cmd/dist/unix.c
    @@ -745,17 +745,24 @@ static void sigillhand(int);\
     // xtryexecfunc tries to execute function f, if any illegal instruction
     // signal received in the course of executing that function, it will
     // return 0, otherwise it will return 1.
    +// Some systems (notably NetBSD) will spin and spin when executing VFPv3
    +// instructions on VFPv2 system (e.g. Raspberry Pi) without ever triggering
    +// SIGILL, so we set a 1-second alarm to catch that case.
     int
     xtryexecfunc(void (*f)(void))
     {
     int r;
     r = 0;
     signal(SIGILL, sigillhand);
    +signal(SIGALRM, sigillhand);
    +alarm(1);
     if(sigsetjmp(sigill_jmpbuf, 1) == 0) {
     f();
     r = 1;
     }
     signal(SIGILL, SIG_DFL);
    +alarm(0);
    +signal(SIGALRM, SIG_DFL);
     return r;
     }
     
    

コアとなるコードの解説

src/cmd/dist/arm.cの変更

xgetgoarm関数は、GoのビルドシステムがARMプロセッサのVFPサポートレベルを検出するために使用されます。この変更により、NetBSDまたはFreeBSD環境では、VFPv2(GOARM=6)以上のバージョンが利用可能であっても、強制的にGOARM="5"(VFPv1相当)を返すようになります。

  • #if defined(__NetBSD__) || defined(__FreeBSD__): このプリプロセッサディレクティブは、コンパイル対象がNetBSDまたはFreeBSDの場合に続くコードブロックを有効にします。
  • return "5";: これにより、VFPv2のバグがある環境で、より安全なVFPv1の使用が強制されます。コメントには、NetBSDのVFPv2サポートのバグ(不正確な結果、非正規化数、NaNの扱い)と、cgo使用時のカーネルクラッシュの可能性が明記されています。FreeBSDも同様の問題を抱えているため、対象に含まれています。

この変更は、NetBSD/ARMおよびFreeBSD/ARM環境でのGoの数学テストの失敗やシステムクラッシュを防ぐための予防措置です。

src/cmd/dist/build.cの変更

proto_gccargs配列は、GoのビルドプロセスでGCCを呼び出す際に渡されるデフォルトのコンパイルオプションを定義しています。

  • #if defined(__NetBSD__) && defined(__arm__): この条件は、コンパイル対象がNetBSDかつARMアーキテクチャの場合に真となります。
  • "-O1": この条件が真の場合、GCCの最適化レベルが-O1に設定されます。
  • #else / "-O2": それ以外の場合(NetBSD/ARMではない場合)は、引き続きデフォルトの-O2最適化レベルが使用されます。

この変更は、NetBSD/ARM環境の特定のGCCバージョンが-O2でGoのコードを誤って最適化するバグを回避するためのものです。最適化レベルを-O1に下げることで、このバグの影響を受けにくくし、Goのビルドの信頼性を向上させます。

src/cmd/dist/unix.cの変更

xtryexecfunc関数は、特定の関数(この場合はVFP命令を実行する関数)を試行的に実行し、その際にSIGILLシグナルが発生するかどうかを検出するために使用されます。

  • signal(SIGALRM, sigillhand);: SIGALRMシグナルが発生した際に、SIGILLと同じシグナルハンドラsigillhandが呼び出されるように設定します。
  • alarm(1);: 1秒のタイマーを設定し、1秒後にSIGALRMが送信されるようにします。
  • if(sigsetjmp(sigill_jmpbuf, 1) == 0): これは、シグナルハンドラから戻るためのジャンプポイントを設定します。sigillhandが呼び出されると、このジャンプポイントに戻ります。
  • f();: 試行する関数(VFP命令を実行する関数)を呼び出します。
  • alarm(0);: 試行が完了した後、設定されたアラームをキャンセルします。
  • signal(SIGALRM, SIG_DFL);: SIGALRMシグナルハンドラをデフォルトの動作に戻します。

この変更により、NetBSDカーネルがSIGILLを適切に発行せず、無限ループに陥るバグに対するワークアラウンドが提供されます。1秒のタイムアウトを設定することで、ハングアップ状態を検出し、xtryexecfuncがタイムアウトによって失敗を返すことができるようになります。これにより、GoのビルドプロセスがVFP機能の検出で停止することを防ぎます。

関連リンク

参考にした情報源リンク