Palm Programmer's Laboratory
Palm OS Programmer's Companion Volume I/14-4
14-4 ARM ネイティブサブルーチンの作成
ARM ネイティブサブルーチンは、正しい形式のプロトタイプと必要な ARM コードが含まれている必要があります。サンプルの ARM ネイティブサブルーチンである armlet-simple.c は必要最小限のコードを示すために SDK に含まれています。
ARM ネイティブサブルーチンは Palm OS API 関数をコールすることもできますし、68K コードをコールバックすることもできます。414 ページの“ARM コードからの Palm OS API の呼び出し”を参照して下さい。SDK に含まれる armlet-oscall.c も、Palm OS 関数コールの例になっています。
以下のセクションでは、ARMネイティブサブルーチンの作成手順を説明しています。
- “68K アプリケーションのパフォーマンス上重要な部分を隔離する”
- “ARM ネイティブサブルーチンが引数を1つだけとるように修正する”
- “68K と ARM の技術的な違いを処理する”
- “ARM ネイティブサブルーチンをテストする”
- “ARM ネイティブサブルーチンをビルドする”
- “68KアプリケーションにARMコードを組み込む”
68K アプリケーションのパフォーマンス上重要な部分を隔離する
どのアルゴリズムを ARM ネイティブサブルーチンにすれば良いかを決めるには、まず 68Kアプリケーションのパフォーマンス分析から始める必要があります。68Kアプリケーションがパフォーマンステストの結果“十分に速く”動作しているのであれば、ARM ネイティブサブルーチンを作成する理由はありません。
- Palm OS シミュレータを使用して 68Kアプリケーションをテストします。Palm OS シミュレータはアプリケーションの Palm OS Garnet との互換性をテストするもっとも簡単かつベストな方法です。アプリケーションを Palm OS シミュレータで動作させることで、Palm OS Garnet 上でどのアルゴリズムに問題が出るかがわかります。暗号化や圧縮のように多量の計算を行なうアルゴリズムは、Palm OS シミュレータ上では動きが遅くなります。パフォーマンスの違いに気づいたら、ARM ネイティブサブルーチンとして書きなおすことで改善できそうなアルゴリズムを見つけます。
- プロファイルバージョンの Palm OS エミュレータを使用して 68K アプリケーションをテストします。プロファイルバージョンの Palm OS エミュレータは、アプリケーションの実行をモニタリングしてどのアルゴリズムに時間がかかっているかを示す統計情報を生成します。エミュレータは遅いアルゴリズムをピンポイントで示しますが、エミュレータ上のパフォーマンスは Palm OS Garnet 上でのパフォーマンスを示さない場合があります。エミュレータは Palm OS Garnet の PACE コンポーネントを含んでいませんが、シミュレータは含んでいます。
オペレーティングシステムを何度もコールする関数は通常、ARM ネイティブ化の候補には適しません。なぜなら、アプリケーションの“68K側”と“ARM側”の同時デバッグは容易ではなく、またパラメータのバイトスワップにオーバーヘッドがかかるからです。
ARM ネイティブサブルーチンが引数を1つだけとるように修正する
PceNativeCall 関数は 68Kアプリケーションから ARM ネイティブサブルーチンをコールするのに使用する関数です。この関数は引数を2つしかとりません。ARM ネイティブサブルーチンのポインタとデータブロックを指すポインタです。結果的に、サブルーチンが入力パラメータを1つしかとらない場合が一番書き易いことになります。
68K側から呼ばれる ARM 関数 ―― すなわち ARM コードのエントリとなる関数 ―― のプロトタイプは、以下のようになっている必要があります( PceNativeCall.h で宣言されています) 。
typedef unsigned long NativeFuncType (const void *emulStateP, void *userData68KP, Call68KFuncType *call68KFuncP)
この関数のパラメータは以下のように定義されます。
- → emulStateP
- PACE の(不透明な)エミュレーション状態を指すポインタ。このポインタは Palm OS 関数やアプリケーションコールバック関数をコールする際に使用します。詳細は“ARM コードからの Palm OS API の呼び出し”を参照して下さい。
- ←→ userData68KP
- PceNativeCall 関数に渡した userDataP 引数が、ARM コードで直接デリファレンスできるようにバイトスワップされて渡されます。このポインタが指すデータブロックにアクセスする際の注意点については、“68K と ARM の技術的な違いを処理する”を参照して下さい。
- → call68KFunc
- ARM コードから PACE がエミュレートする環境にコールバックするためのフックが指定されます。これは OS 関数とアプリケーションコールバックの両方で使用されます。詳細は“関数ポインタを使用した関数呼び出し”を参照して下さい。
ARM のエントリポイントとなる関数の名前は PNOMain でなければなりません。
- IMPORTANT
- ARM のエントリポイントとなる関数の名前が PNOMain でない場合、コンパイラは “***[ARMC1000.bin.elf]Error 1”というメッセージを出力します。Palm OS Developer Suite を使用して "Simple" テンプレートで "Managed Make 68K PNO C/C++ Project" を作成した場合、エントリポイント関数の名前を変更しないで下さい。
ARM のエントリポイント関数が返す値は呼出し元に戻されるため、68K側にとって重要な意味を持ちます。復帰値に意味がなければ0を返すようにして下さい。レジスタ A0 と D0 の両方にこの復帰値が設定されると、ポインタまたは即値の返却を想定するコードにとって意味があります。
※訳注:上の段落の最後の文が訳しきれません。原文は以下の通りです。 Both register A0 and D0 are set to this return value, making it meaningful to code that is expecting either a pointer or an immediate result.
68K と ARM の技術的な違いを処理する
ARM ネイティブサブルーチンをインプリメントする場合、68Kプロセッサと ARM プロセッサの違いを意識する必要があります。以下のセクションでは、ARM ネイティブサブルーチンを扱う上で必要ないくつかの技術的な留意点を説明します。
- “ビッグエンディアンとリトルエンディアン”
- “整数値のアライメント”
- “構造体のパッキング”
ビッグエンディアンとリトルエンディアン
68Kプロセッサの整数はビッグエンディアンであり、ARMプロセッサはリトルエンディアンです。ビッグとリトルは、マルチバイト整数が保存される際のバイト順を示しています。ビッグエンディアンでは、最上位バイトが最初に配置され、リトルエンディアンでは最上位バイトが最後に配置されます。
このことは、2 バイトおよび 4 バイト整数が逆のバイト順で保存され、結果として ARM と 68K のプロセッサ間でデータを交換する際にバイトスワップをしなければならないことを意味します。エンディアンは 2 バイトおよび 4 バイト整数(ポインタを含む)のコンテキストにのみ関係します。文字列など、その他のデータ型はバイトスワップの必要はありません。
PACE は PceNativeCall 関数の userData68KP パラメータを自動的にバイトスワップするため、ARM 関数内で変更することなく直ちに参照することができます。PACE は呼び出し元関数に戻される 4 バイトの復帰値についても自動的にバイトスワップを行ないます。
PACE は userData68KP パラメータが指しているデータについてはバイトスワップを行ないません。これは、どのような種類のデータが渡されているかについて PACE は何も知らないためです。(バイトスワップする必要があるのは 2 および 4 バイト整数だけであることと、userData68KP パラメータは任意のデータ構造を指す単なるポインタに過ぎないことを思い出して下さい。)
ARMネイティブサブルーチンで使用するバイトスワップマクロ
Endianutils.h には、ARM ネイティブサブルーチンで 2 および 4 バイト整数のバイトスワップをするための便利なマクロが含まれています。
- ByteSwap16(integer)
- 2 バイト(16ビット)整数値をバイトスワップします。
- ByteSwap32(integer)
- 4 バイト(32ビット)整数値をバイトスワップします。
ARM ネイティブサブルーチンはデータブロック内の整数値を必要に応じてバイトスワップしなければなりません。
整数値のアライメント
ARM プロセッサでは、4 バイト整数は 4 バイト境界に揃えられていることが要求されます。68K プロセッサでは、偶数アドレスに揃えられていることだけが要求されます。
整数データのアライメントの違いに対応するには、以下の2つの選択肢があります。
- MemPtrNew を使用してデータをアロケートし、整数型データのアライメントに注意してデータ構造を定義します。MemPtrNew は常に 4 バイト境界に揃えられたアドレスを返すので、データが 4 バイト境界から始まることを確実にできます。ただし、データ自体が正しく揃えられていることにも注意しなければなりません。データオブジェクトを並べる場合、68K と ARM では 4 バイトオブジェクトが異なる方法で揃えられることを知っておく必要があります。この様子を表 14.1 に示します。4 バイトデータが正しく揃えられていない場合、ARM プロセッサは 4 の倍数アドレスを使用してデータにアクセスしようとし、結果としてデータの破損が発生します。
- 4 バイト整数値を使用する前にローカル変数にコピーします。Endianutils.h には 4 バイト変数をバイトスワップしながら読み込んだり書き込んだりするのに便利なマクロが含まれています。Read68KUnaligned32(address) は指定されたアドレスから値を読み込み、Write68KUnaligned32(address, value) は指定されたアドレスに指定された値を書き込みます。
表 14.1 デフォルトのデータオブジェクトアライメント
データオブジェクトのサイズ | 68K プロセッサのアライメント | ARM プロセッサのアライメント |
---|---|---|
1 バイト | 任意のアドレス | 任意のアドレス |
2 バイト | 2 バイトアライメント(偶数アドレス) | 2 バイトアライメント(偶数アドレス) |
4 バイト | 2 バイトアライメント(偶数アドレス) | 4 バイトアライメント(4 の倍数アドレス) |
構造体のパッキング
異なるコンパイラは、互いに異なる方法で自動的に構造体のパッキングを行ないます。コンパイラによっては、指定されたオプションに従って構造体が与えられたバイト境界に揃うようにバイトパディングを行ないます。構造体を宣言する時、および構造体のローカルコピーを作成する場合は注意して下さい。
ARM ネイティブサブルーチンをテストする
ARM ネイティブサブルーチンは ARM ハードウェア上の Palm OS Garnet で動作します。しかし、Palm OS シミュレータでは ARM ネイティブコードは動作しません。そのかわり、シミュレータは Microsoft Windows 上で動作する Palm OS Garnet の実装を提供しています。その結果、シミュレータで ARM ネイティブサブルーチンをテストするには、対象のサブルーチンを Windows DLL としてビルドする必要があります。シミュレータの PACE 実装は、サブルーチンコールを DLL 関数呼び出しとして認識します。
SDK には、サンプル ARM ネイティブサブルーチン(同じく SDK に含まれています)と同じ関数のエントリポイントを含む DLL をビルドするサンプルアプリケーションが含まれています。
DLL をコールする場合、PceNativeCall に渡す最初の引数は DLL とその中の実行されるエントリポイント関数の名前を NULL 文字で区切った NULL 終端文字列を指すポインタになります(例えば、文字列 "test.dll\0EntryPoint" を指すポインタ)。
デフォルトでは、シミュレータは PalmSim.exe が動作しているディレクトリ内で DLL を検索します。別の場所に DLL を配置したい場合、ARM ネイティブサブルーチン DLL 名をフルパスで指定する必要があります(例えば、"c:\\projects\\armletdll\\test.dll\0EntryPoint" )。
68K アプリケーションはプロセッサの種類をチェックしなければなりません。
- プロセッサが ARM であれば、68K アプリケーションは ARM ネイティブサブルーチンをコールすべきです。
- プロセッサが Windows であれば、68K アプリケーションは Windows DLL をコールすべきです。
上記以外の場合、68K アプリケーションはサブルーチンの 68K バージョンをコールすべきです。この場合、アプリケーションは古いバージョンの Palm OS で動作していると想定されます。
ARM ネイティブサブルーチンをビルドする
ARM ネイティブサブルーチンをビルドするには、ARM コンパイラを使用する必要があります。PalmSource 社は ARM コンパイラのサポートを提供していませんが、ARM Developer Suite (ADS) や gcc など、いくつかのコンパイラが利用できます。
コンパイルされた ARM ネイティブサブルーチンのオブジェクトファイルは、そのまま( raw )のバイナリファイルとして 68K アプリケーションにリンクされる必要があります。アドレスオフセットの計算は、通常エントリポイント関数がバイナリファイルの先頭に配置されるため簡単です。
68KアプリケーションにARMコードを組み込む
ARMバイナリの生成に使用した仕組みとは無関係に、それを .prc ファイルに組み込むには2つの異なる方法があります。
- Palm OS Developer Suite を使用している場合、68K PNO C/C++ プロジェクトを作成します。Managed Make プロジェクトは ARM バイナリを自動的にアプリケーションに統合します。Standard Make プロジェクト同じようにセットアップすることができます。
- CodeWarrior や PilRC のようなツールを使用して ARM バイナリをリソースファイルとして配置し、そのリソースを 68K プロジェクトに取り込みます。
- 得られたバイナリデータを別のリソースファイルに hex データとしてコピーします。ARM バイナリファイルからリソースへの処理には hex dump utility を使用します。
- ARM コードをアプリケーションのソースコードに直接整数配列として取り込みます。ただし注意が必要なのは、この配列は 68K コンパイラによってビッグエンディアンとして解釈され、ARM プロセッサにはリトルエンディアンとして解釈されるということです。そのため、適切なオペコードが得られるように整数値をバイトスワップする必要があります。また、配列自体がソースコード内で 4 バイトアライメントになっている必要があるため、コンパイラを適切に設定する必要があります。