ABI 管理

Android デバイスによって、使用している CPU が異なるため、サポートされる命令セットも異なります。そのため、CPU と命令セットの組み合わせごとに、それぞれ専用のアプリケーション バイナリ インターフェース(ABI)が用意されています。ABI は、アプリのマシンコードが実行時にシステムとやり取りする方法を詳細に定義したものです。アプリの動作対象となる CPU アーキテクチャに合わせて、ABI を指定する必要があります。

ABI には通常、次の情報が格納されています。

  • マシンコードが使用する CPU 命令セット。
  • 実行時のメモリストアとメモリロードのエンディアン。
  • 実行バイナリのフォーマット(プログラム、共有ライブラリ、サポートするコンテンツのタイプなど)。
  • コードとシステムとの間でデータを受け渡す際の各種規則。たとえば、アライメントに関する制約や、システムが関数の呼び出し時にスタックやレジスタを使用する方法などが該当します。
  • 実行時にマシンコードで使用できる関数シンボルのリスト(通常は、非常に限定的なライブラリ セットが対象になります)。

このページでは、NDK がサポートしている ABI と、各 ABI の仕組みについて説明します。32 ビットシステムで生じる ABI の問題の一覧については、32 ビット ABI のバグをご覧ください。

サポート対象 ABI

各 ABI は、1 つ以上の命令セットをサポートします。各 ABI がサポートしている命令セットの概要について、表 1 に示します。

表 1: ABI とサポート対象命令セット

ABI サポート対象命令セット 備考
armeabi
  • ARMV5TE 以降
  • Thumb-1
  • r16 でサポート終了。r17 で削除。ハードウェア支援型の浮動小数点演算なし。
    armeabi-v7a
  • armeabi
  • Thumb-2
  • VFPv3-D16
  • その他(省略可)
  • ARMv5、v6 デバイスと互換性なし。
    arm64-v8a
  • AArch64
  • x86
  • x86(IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
  • MOVBE や SSE4 に対するサポートなし。
    x86_64
  • x86-64
  • MMX
  • SSE/2/3
  • SSSE3
  • SSE4.1、4.2
  • POPCNT
  • 注: これまで NDK は 32 ビットおよび 64 ビットの MIPS をサポートしていましたが、NDK r17 でこのサポートは削除されました。

    各 ABI の詳細については、以下をご覧ください。

    armeabi

    注: この ABI は NDK r17 で削除されました。

    この ABI は、少なくとも ARMv5TE 命令セットをサポートしている ARM ベース CPU 向けです。詳細については、以下のドキュメントをご覧ください。

    類似する ABI を 1 つのファミリーとしてまとめる EABI が AAPCS 標準によって定義されています。また、Android は、リトル エンディアン ARM GNU / Linux ABI に準拠しています。

    この ABI は、ハードウェア支援型の浮動小数点演算をサポートしていません。浮動小数点演算はすべて、コンパイラの libgcc.a 静的ライブラリのソフトウェア ヘルパー関数を使用します。

    armeabi ABI は、ARM の Thumb(別名 Thumb-1)命令セットをサポートしています。Android.mk ファイル内で LOCAL_ARM_MODE 変数を使用して別の動作を指定しない限り、NDK はデフォルトで Thumb コードを生成します。

    armeabi-v7a

    この ABI は、armeabi を拡張したもので、さまざまな CPU 命令セット拡張機能が組み込まれています。この Android 固有の ABI がサポートしている命令セット拡張機能は次のとおりです。

    • Thumb-2 命令セット拡張機能。Thumb-1 と同等の簡潔さで、32 ビット ARM 命令と同等のパフォーマンスを実現します。
    • VFP ハードウェア FPU 命令。具体的には VFPv3-D16 で、このアーキテクチャは、ARM コアの 16 個の 32 ビットレジスタに加えて、16 個の専用 64 ビット浮動小数点レジスタを搭載しています。

    Advanced SIMD(別名 NEON)、VFPv3-D32、ThumbEE など、v7-a ARM 仕様に含まれている他の拡張機能は、この ABI ではオプションです。各拡張機能が搭載されているとは限らないため、システムは実行時に、対象の拡張機能を利用できるかチェックする必要があります。利用できない場合は、別のコードパスを使用する必要があります。このチェック動作は、システムが MMXSSE2、その他 x86 CPU 向けの専用命令セットをチェック、使用する際に通常実行する動作と同様です。

    このランタイム チェックの実行方法については、cpufeatures ライブラリをご覧ください。また、NEON 向けのマシンコードをビルドする際の NDK のサポートについては、NEON サポートをご覧ください。

    armeabi-v7a ABI は、-mfloat-abi=softfp スイッチを使用することで、コンパイラが関数を呼び出す際、専用の浮動小数点レジスタペアではなく、コアのレジスタペアにすべての倍精度値を渡すようにするルールを適用します。システムは、FP レジスタを使用して、すべての内部計算を実行することができます。これにより、計算速度が大幅に向上します。

    arm64-v8a

    この ABI は、AArch64 をサポートしている ARMv8 ベース CPU 向けです。NEON 命令セットと VFPv4 命令セットが追加されています。

    詳細については、ARMv8 テクノロジー プレビューをご覧ください。不明な点がある場合は、ARM までお問い合わせください。

    x86

    この ABI は、一般に「x86」や「IA-32」と呼ばれる命令セットをサポートしている CPU 向けです。この ABI の主な特性は次のとおりです。

    • 次のようなコンパイラ フラグを使用して GCC が命令を生成します。
          -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
          

      このフラグは、Pentium Pro 命令セットをターゲットにしています。また、MMXSSESSE2SSE3SSSE3 の命令セット拡張機能もターゲットにしています。生成されるコードは、上位の Intel 32 ビット CPU 全体でバランスを取った形で最適化されます。

      コンパイラ フラグの詳細、特にパフォーマンスの最適化に関するフラグについては、GCC x86 パフォーマンスのヒントをご覧ください。

    • SVR 向けの規則ではなく、標準 Linux x86 32 ビット呼び出し規則を使用します。詳細については、各種 C++ コンパイラおよびオペレーティング システム向けの呼び出し規則の「セクション 6: レジスタの使用」をご覧ください。

    この ABI には、次のような必須でない他の IA-32 命令セット拡張機能は含まれていません。

    • MOVBE
    • SSE4 のバリアント

    このような拡張機能を利用するには、ランタイム機能プロービングを使用して対象の拡張機能を有効にし、サポートしていないデバイス向けにフォールバックを提供する必要があります。

    NDK ツールチェーンは、関数を呼び出す際、16 バイトのスタック アライメントを想定しています。デフォルトのツールと設定の場合、このルールが自動的に適用されます。アセンブリ コードを記述している場合、スタック アライメントを維持する必要があります。また、他のコンパイラにもこのルールを遵守させる必要があります。

    詳細については、以下のドキュメントをご覧ください。

    x86_64

    この ABI は、一般に「x86-64」と呼ばれる命令セットをサポートしている CPU 向けです。次のようなコンパイラ フラグを使用して GCC が生成する命令をサポートします。

        -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel
        

    このフラグは、GCC のドキュメントにあるように、x86-64 命令セットをターゲットにしています。また、MMXSSESSE2SSE3SSSE3SSE4.1SSE4.2POPCNT の命令セット拡張機能もターゲットにしています。生成されるコードは、上位の Intel 64 ビット CPU 全体でバランスを取った形で最適化されます。

    コンパイラ フラグの詳細、特にパフォーマンスの最適化に関するフラグについては、GCC x86 パフォーマンスをご覧ください。

    この ABI には、次のような必須でない他の x86-64 命令セット拡張機能は含まれていません。

    • MOVBE
    • SHA
    • AVX
    • AVX2

    このような拡張機能を利用するには、ランタイム機能プロービングを使用して対象の拡張機能を有効にし、サポートしていないデバイス向けにフォールバックを提供する必要があります。

    詳細については、以下のドキュメントをご覧ください。

    特定の ABI 向けのコードを生成する

    デフォルトでは、NDK は、サポートが終了していないすべての ABI をターゲットにしています。Application.mk ファイル内で APP_ABI を設定すると、単一の ABI をターゲットにすることができます。APP_ABI の使用例を次のスニペットに示します。

        APP_ABI := arm64-v8a  # Target only arm64-v8a
        APP_ABI := all  # Target all ABIs, including those that are deprecated.
        APP_ABI := armeabi-v7a x86_64  # Target only armeabi-v7a and x86_64.
        

    APP_ABI で指定できる値については、Application.mk をご覧ください。

    ビルドシステムのデフォルト動作では、各 ABI のバイナリが単一の APK に組み込まれます。このような APK を「ファット APK」と呼びます。ファット APK は、単一の ABI のバイナリだけを含む APK と比べて、サイズがかなり大きくなります。互換性が拡大するというメリットがありますが、サイズが大きくなるのは大きなデメリットとなります。したがって、分割 APK を利用して、APK のサイズを縮小しつつ、デバイス互換性を最大限維持していく方法を強くおすすめします。

    インストール時、Package Manager は、ターゲット デバイスに最も適したマシンコードだけを解凍します。詳細については、インストール時のネイティブ コードの自動抽出をご覧ください。

    Android プラットフォーム上での ABI 管理

    このセクションでは、Android プラットフォームが APK 内のネイティブ コードを管理する方法について説明します。

    アプリ パッケージ内のネイティブ コード

    Play ストアも Package Manager も、次のパターンに合致する APK 内のファイルパスに基づいて、NDK が生成したライブラリを検索します。

        /lib/<abi>/lib<name>.so
        

    この例において、<abi> は、サポート対象 ABI のリストに含まれる 1 つの ABI 名を示し、<name> は、Android.mk ファイル内の LOCAL_MODULE 変数に対して定義したライブラリの名前を示します。APK ファイルは zip 形式のファイルであるため、簡単に開いて、共有ネイティブ ライブラリの場所を確認することができます。

    想定される場所で共有ネイティブ ライブラリを見つけられなかった場合、システムはライブラリを使用できません。その場合は、アプリ自体がライブラリをコピーして dlopen() を実行する必要があります。

    ファット APK の場合、各ライブラリは、対応する ABI に合致する名前のディレクトリに格納されます。たとえば、ファット APK 内で各ライブラリは次のように格納されます。

        /lib/armeabi/libfoo.so
        /lib/armeabi-v7a/libfoo.so
        /lib/arm64-v8a/libfoo.so
        /lib/x86/libfoo.so
        /lib/x86_64/libfoo.so
        

    注: Android 4.0.3 以前を搭載している ARMv7 ベースの Android デバイスは、armeabi ディレクトリと armeabi-v7a ディレクトリの両方が存在する場合、armeabi ディレクトリからネイティブ ライブラリをインストールします。これは、APK 内で /lib/armeabi//lib/armeabi-v7a/ の後に配置されるためです。この問題は 4.0.4 以降では修正されています。

    Android プラットフォーム ABI サポート

    Android システムは、以下のようなビルド固有のシステム プロパティ情報に基づいて、実行時にサポート対象の ABI を認識します。

    • 対象デバイスのプライマリ ABI。これは、システム イメージ自体で使用されているマシンコードを示します。
    • セカンダリ ABI(省略可)。これは、システム イメージがサポートしている他の ABI を示します。

    このメカニズムによって、システムはインストール時に、パッケージから最適なマシンコードを抽出します。

    最良のパフォーマンスを実現するには、プライマリ ABI だけを対象にコンパイルすることをおすすめします。たとえば、一般的な ARMv5TE ベースデバイスであれば、プライマリ ABI の armeabi だけを定義します。他方、一般的な ARMv7 ベースデバイスの場合は、プライマリ ABI を armeabi-v7a に設定し、セカンダリ ABI を armeabi に設定します(どちらの ABI 向けに生成されたアプリ ネイティブ バイナリでも実行できるため)。

    64 ビットデバイスは 32 ビット バリアントもサポートします。たとえば、arm64-v8a デバイスは、armeabi コードや armeabi-v7a コードも実行することができます。ただし、arm64-v8a をターゲットにしているアプリを 64 ビットデバイス上で実行する場合は、arm64-v8a バージョンの方が armeabi-v7a バージョンよりも優れたパフォーマンスを発揮します。

    x86 ベースデバイスも多くの場合、armeabi-v7a NDK バイナリと armeabi NDK バイナリを実行できます。このようなデバイスの場合、プライマリ ABI を x86 に設定し、セカンダリ ABI を armeabi-v7a に設定します。

    インストール時のネイティブ コードの自動抽出

    アプリのインストール時、Package Manager サービスは APK をスキャンし、次の形式の共有ライブラリを検索します。

        lib/<primary-abi>/lib<name>.so
        

    ライブラリが見つからず、かつ、セカンダリ ABI が定義されていた場合、このサービスは次の形式の共有ライブラリがないかスキャンします。

        lib/<secondary-abi>/lib<name>.so
        

    探しているライブラリが見つかると、Package Manager はそのライブラリをアプリの data ディレクトリ(data/data/<package_name>/lib/)の下にある /lib/lib<name>.so にコピーします。

    共有オブジェクト ファイルがまったく存在しない場合、アプリのビルドとインストールは行われますが、実行時にクラッシュします。