アセンブリ・ルーチンを呼ぶ

C言語で書いたプログラムからアセンブリ言語で書いたプログラムを利用する方法として、インライン・アセンブラがあります。これは高級言語のソースのなかに記述できるのが強みですが、いろいろと制約があり、必ずしもアセンブリ言語のよさを100%引き出せるわけではありません。

インライン・アセンブラと異なり、別のソースファイルにアセンブリ・ルーチンを置くならばルーチン全部を丸々アセンブリ言語で記述することができます。この場合、プログラマはプロセッサの能力をほぼ完全に引き出すことができます。

無論、ただの昼飯があるわけではなく、プログラマはCコンパイラが生成するコードの呼び出し規則に従ってプログラムを書かなければなりません。ここではC言語から呼ぶことのできるアセンブリ・ルーチンの書き方を説明します。なお、概略のみ説明しますので、完全な説明は以下の文献を参照してください。

関数名

C言語の関数名の頭にアンダースコアをつけたものがアセンブリ・ルーチンのラベルとなります。例えば次のようなC言語の関数を考えてください

// C言語の関数 voo 
void foo(void);

この場合、アセンブリ・ルーチンのラベルは次のように頭にアンダースコアをつけたものになります。

_foo:

C++言語の関数名は中間オブジェクトに落としたときにコンパイラが変更しています。そこで次のように宣言することでアセンブリ・ルーチンを呼ぶことができます。この機能は本来C++言語からC言語のコンパイル済みルーチンを呼ぶためのものですが、アセンブリ・ルーチンを呼ぶことにも使えます。

// C++言語の関数 foo
void "C" foo(void); 

関数エントリーとエクジット

Cコンパイラは関数呼び出し時に規則にのっとってスタック上に戻り情報などを退避します。アセンブリ・ルーチンを組むときもこの規則に沿ったほうが話が簡単になります。

構築するデータ構造を理解するのはやや面倒ですが、規則に沿うこと自体は簡単です。このためのマクロがasm_sprt.hに定義されているからです

#include <asm_sprt.h>    // アセンブリ言語サポート・ヘッダ
...

_foo:
    full_entry(0);
    ...
    // アセンブリ言語による関数記述
    ...
    full_exit;

full_entryがそのためのマクロです。このマクロはスタック・フレームを構築するものです。引数は0を与えていますが、この意味は後で説明します。full_entryは関数内部のデータ構造を整えますが、関数に渡される引数にかかわらずこのマクロを使うことができます。

full_exitはfull_entryに対応する脱出マクロです。このマクロは関数からの戻りも実行しますのでRTS命令は不要です。

一時領域

一時的に使う変数やレジスタの退避にはスタック上の一時領域を使います。領域の確保はfull_entryマクロの引数で指定します。nワードの領域を確保するにはfull_entryに引数nを渡します。このとき、呼ばれた関数内部で自由に使用できる領域はdm(i5-2)からdm(i5-n-1)までです。例えばnが4ならばdm(i5-2)からdm(i5-5)までを一時領域として使用できます。

レジスタの退避と復帰

asm_sprt.h には次の三つのマクロが定義されています。

pushs( reg )
regの値をスタックにプッシュします。
pops( reg )
スタックからポップしてregに復帰します。M6の値を破壊して1にします。
and_pops( reg )
popsかand_popsに続けて実行し、スタックからポップしてregに復帰します。M6への設定を行いませんのでpopsより高速ですがM6には必ず1が設定されていなければなりません。

引数参照

引数はスタック経由で渡します。呼ばれたルーチンからこれらの引数を参照するには、I5レジスタを基点としてアクセスします。このとき、第一引数はI5+1、第二引数はI5+2に位置します。例として次の関数プロトタイプを見てください。

int foo ( int param1, int * param2, int * param3(void), int param4 );

このプロトタイプを持つ関数内部では与えられた引数を次のようにアクセスすることができます。

引数 位置 サイズ 説明
param1 I5+1 1 int型
param2 I5+2 1 intへのポインタ型
param3 I5+3 2 関数へのポインタ型
param4 I5+5 1 int型

関数へのポインタ型だけ引数のサイズが2ワードであることに注意してください。VisualDSP++のCコンパイラは関数へのポインタを2ワードで表します。したがって関数は24ビットのアドレス空間のどこにでも置くことができます。反面、データへのポインタは1ワードですので、アドレス空間の最初の1ページにしかデータを配置できません。いずれにせよADSP-2191を遊びで使う分にはデータ空間が足りないということはないでしょう。むしろ関数へのポインタ型のサイズをうっかり1と計算しないように注意してください。

引数の取得に関しては次の二つのマクロが asm_sprt.h で定義されています。

get_arg( reg、offset )
I5+offset番地から値を読み出してregに格納します。M6レジスタを破壊するのが欠点です。
get_arg_dreg( dreg, offset )
I5+offset番地から値を読み出してdregに格納します。データレジスタにしか格納できませんが、他のレジスタを破壊しません。

戻り値

戻り値は、値のサイズによって返し方が違います。

戻り値が1ワード
intやshortのように1ワードで戻り値を表現できる場合には、AX1に戻り値を格納します
戻り値が2ワード
関数へのポインタや小さな構造体で2ワードを要するものは、SR1:SR0でレジスタペアを組んで値を返します。この場合、上位ワードはSR1に格納します。
戻り値が3ワード以上
大きなstructを返す場合には戻り値が3ワード以上になります。この場合、少し複雑な手続きを行います。まず関数を呼び出す前に戻り値を格納する領域を呼び出し側が確保します。次に関数呼び出し時に隠し引数として先ほど用意した領域のアドレスを渡します。この隠し引数はSR1に格納されます。呼び出された側はSR1の値をアドレスとする領域に戻り値を格納して返します。このあとはSR1を破壊してもかまいません。

レジスタの使用制限

Cコンパイラは一部のレジスタの使い方を厳しく決めています。そのためレジスタの使い方には制限があり、それを守らなければプログラムは正常に動作しません。以下でコンパイラがレジスタをどのように使うかという観点から種類わけを行います。

専用( Dedicated )
一部のレジスタはコンパイラが独占的に使います。これらのレジスタを変更してはいけません。変更した場合、正常動作は望めません。これらのレジスタの役割には割り込みハンドラも依存していますので、決して値を変更しないでください。
保護( Preserved )
I2, I3, I7, M0, M2,M4はコンパイラが使用します。呼ばれた側のルーチンは、戻り時に元の値に戻す場合に限り、これらのレジスタを自由に使えます。なお、マニュアルには明記されていませんが、
スクラッチ( Scratch )
好きなように利用してもいいレジスタです。戻り時に値を復帰する必要はありません。
Lx
L0からL7は保護レジスタのように働きますが、コンパイラは常に値を0とします。従って、これらのレジスタを元に復帰する場合、単に0で埋めるだけで結構です。
Bx
B0からB7はスクラッチ・レジスタとして使えます。コンパイラはこのレジスタを使いません。

以下の表にADSP-2191のレジスタをコンパイラの用法に従って色分けして示します。色はレジスタの使い方を示します。

レジスタ名
AX0 AX1 AY0 AY1 AR AF
MX0 MX1 MY0 MY1 MR2 MR1 MR0
SI SE SB SR2 SR1 SR0
I0 L0 B0 M0 DMPG1 ASTAT MSTAT
I1 L1 B1 M1 CNTR STACKA
I2 L2 B2 M2 ICNTL STACKP
I3 L3 B3 M3 IJPG CCODE
I4 L4 B4 M4 DMPG2 IMASK
I5 L5 B5 M5 IRPTL
I6 L6 B6 M6 LPSTACKA
I7 L7 B7 M7 LPSTACKP

-reserveスイッチ

コンパイラに対してI2, I3, M0レジスタを使わないように指示することができます。指示はコンパイラ・オプションを使い、構文は次のようになります。

-reserve I2,M0

この例ではI2とM0を使用しないように指定しています。コンパイラはL2、B2を使いませんからアセンブリ・プログラムはI2を使って永続的な循環バッファを作ることができます。永続的とは、アセンブリ・ルーチンの外でこのバッファが破壊されないことを意味します。

ただし、I2, I3, M0はそれぞれ保護レジスタですから、ライブラリ・ルーチンの中で使用されていることはありえます。したがって、-reserveオプションで永続的なバッファを組む場合、割り込みルーチン内ではそれを利用できないことになります。ライブラリの中で割り込みがかかるかもしれないからです。

2191空挺団 | プログラム | EZ-KIT | こぼれ話 | アーキテクチャー | 命令 | レジスタ | DSP掲示板 | FAQ |