新版FM音源プログラム (17)
armcc の「エンベデッド・アセンブラ」では、関数まるごとアセンブリ言語で記述し、関数冒頭のレジスタ・セーブや、関数最後のレジスタ・リストアやリターン命令まですべてを記述する必要がありました。
gcc の「インライン・アセンブラ」では、C 言語での記述に混ざって「asm 文」が存在し、関数のプロローグ/エピローグ・コードは asm 文内では記述する必要がなく、C 側で自動的に生成されます。
また、デフォルトでは最適化の対象になっています。 (最適化オフにも設定できます)
アセンブラ命令も「裸」で記述することはできず、文字列の中に「閉じ込め」られた形で記述します。
下に gcc のインライン・アセンブラ機能を使って書いたプログラム・リストを示します。 (2018 年 2 月 28 日追記: ラベルを「ローカル・ラベル」(数字のみで構成されるラベル) に変更しました)
(2018 年 3 月 2 日追記: パイプライン・ストール解消のためプログラムの一部を変更しました)
// PSoC Creator 4.0 SP1 (gcc 4.9.3) // 64 cycle / 2slot (CY8C4245 (CM0) @ 24 MHz, Flash wait=0) // 67 cycle / 2slot (CY8C4245 (CM0) @ 48 MHz, Flash wait=1) // 49 cycle / 2slot (CY8C5888 (CM3) @ 16 MHz, flash wait=0) // 55 cycle / 2slot (CY8C5888 (CM3) @ 80 MHz, flash wait=4) #define DEF_OFS(x) (offsetof(op_prop_t, x)) // // gcc inline assembly language function // void calc_slot(op_prop_t *o_p, int num_slot) { __asm__ __volatile__ ( // Use UAL (Unified Assembler Language) syntax " .syntax unified \n\t" "1: \n\t" // // series FM modulater slot // " ldr r2,[r0,%[ph_inc]] \n\t" " ldr r3,[r0,%[ph_acc]] \n\t" " ldr r4,[r0,%[mod_in]] \n\t" " ldr r5,[r0,%[stab_p]] \n\t" " adds r2, r2,r3 \n\t" " ldrh r3,[r0,%[ind_mask]] \n\t" " str r2,[r0,%[ph_acc]] \n\t" " adds r4, r4,r2 \n\t" #if defined(__thumb2__) // ARMv7-M (Cortex-M3/M4) " ands r3, r3,r4,lsr %[acc_sft] \n\t" #else // ARMv6-M (Cortex-M0) " lsrs r4, r4,%[acc_sft] \n\t" " ands r3, r3,r4 \n\t" #endif " ldr r2,[r0,%[ol_lin]] \n\t" " ldrsh r3,[r5,r3] \n\t" // // 2-tap FIR filter for feedback // #if defined(__thumb2__) // ARMv7-M (Cortex-M3/M4) " ldrsh r5,[r0,%[op_out]] \n\t" #else // ARMv6-M (Cortex-M0) " ldrh r5,[r0,%[op_out]] \n\t" " sxth r5, r5 \n\t" #endif " muls r2, r3,r2 \n\t" " asrs r4, r2,#15 \n\t" " ldrh r3,[r0,%[mod_mul]] \n\t" " strh r4,[r0,%[op_out]] \n\t" " adds r5, r5,r4 \n\t" " muls r3, r5,r3 \n\t" " str r3,[r0,%[mod_in]] \n\t" // // series FM carrier slot // " ldr r2,[r0,%[ph_inc]+%c[S]] \n\t" " ldr r3,[r0,%[ph_acc]+%c[S]] \n\t" " ldrh r5,[r0,%[mod_mul]+%c[S]] \n\t" " adds r2, r2,r3 \n\t" " str r2,[r0,%[ph_acc]+%c[S]] \n\t" #if defined(__thumb2__) // ARMv7-M (Cortex-M3/M4) " mla r4, r4, r5, r2 \n\t" #else // ARMv6-M (Cortex-M0) " muls r4, r5,r4 \n\t" " adds r4, r4,r2 \n\t" #endif " ldrh r3,[r0,%[ind_mask]+%c[S]] \n\t" " ldr r5,[r0,%[stab_p]+%c[S]] \n\t" #if defined(__thumb2__) // ARMv7-M (Cortex-M3/M4) " and r3, r3,r4,lsr %[acc_sft] \n\t" #else // ARMv6-M (Cortex-M0) " lsrs r4, r4,%[acc_sft] \n\t" " ands r3, r4,r3 \n\t" #endif " ldr r2,[r0,%[ol_lin]+%c[S]] \n\t" " ldrsh r3,[r5,r3] \n\t" " muls r2, r3,r2 \n\t" " asrs r2, r2,#15 \n\t" " strh r2,[r0,%[op_out]+%c[S]] \n\t" " adds r0, r0,%[S]*2 \n\t" " subs r1, r1,#2 \n\t" " bgt 1b \n\t" // output parameter list : "+r" (o_p), // [arg1] = r0 = (op_prop_t *) o_p "+r" (num_slot) // [arg2] = r1 = (int) num_slot : [acc_sft] "I" (PH_ACC_FRAC_BITS-1), // input parameter list ("I" for immediate constant) [ph_inc] "I" (DEF_OFS(ph_inc)), [ph_acc] "I" (DEF_OFS(ph_acc)), [mod_in] "I" (DEF_OFS(mod_in)), [stab_p] "I" (DEF_OFS(stab_p)), [ind_mask] "I" (DEF_OFS(ind_mask)), [op_out] "I" (DEF_OFS(op_out)), [ol_lin] "I" (DEF_OFS(ol_lin)), [mod_mul] "I" (DEF_OFS(mod_mul)), [S] "I" (sizeof(op_prop_t)) // clobber reg list : "r2", "r3", "r4", "r5", "cc", "memory" // clobber reg list );// __asm__ } // void calc_slot()
(拡張) インライン・アセンブラの構文は、
__asm__ __volatile__ ( アセンブラ命令ストリング : 出力パラメタ・リスト : 入力パラメタ・リスト : クロバー・リスト )
となっています。 (「__asm__」は「asm」でもよい)
デフォルトでは最適化の対象になりますが、「__volatile__」(あるいは「volatile」) を付けると最適化の対象から外れます。
「アセンブラ命令ストリング」はアセンブリ言語の命令を記述している文字列です。
これは「単一の文字列」の必要がありますが、「ホワイト・スペース」のみで区切られた複数の文字列を連結して単一の文字列にする C コンパイラの機能を利用して、
" ldr r2, [r0, #4] \n\t" " adds r3, r3, r2 \n\t"
のように、複数行に渡って書くこともできます。
「出力パラメタ・リスト」、「入力パラメタ・リスト」は、アセンブラのレジスタ名などと、C 言語での変数名などとの対応を指示するものです。
古いフォーマットでは、
制約文字列 (C の式), 制約文字列 (C の式), ...
というように記述します。
最初の要素に対するアクセスでは、アセンブラ記述としては「%0」として表します。 以下、同様に %1, %2, ... となります。
「制約文字列」は、そのパラメタの種別を表すもので、代表的には "r" が「汎用レジスタ」を表します。
「C の式」はそのパラメタに対応する C の式です。 出力パラメタがレジスタの場合には、「変数名」などの「左辺値」の必要があります。
「クロバー・リスト」は、入出力パラメタとしては扱われないけれど、アセンブラ・プログラム中で使用されるレジスタなどのリストを C コンパイラに示して、レジスタ割り付けやセーブ/リストア処理などの参考にするものです。
新しいフォーマットでは、
[シンボル名] 制約文字列 (C の式), [シンボル名] 制約文字列 (C の式), ...
と記述します。
「シンボル名」はアセンブラ記述として参照するときのシンボル名を定義するもので、アセンブラ記述中では %[シンボル名] としてアクセスします。
上のスロット計算関数では、呼び出し側から r0 に第一引数の (op_prop_t *) o_p 、r1 に第二引数の (int) num_slot がセットされて関数が呼ばれた状態のまま、関数側ではレジスタの移動などはありません。
一応、出力パラメタ・リストに制約文字列 "+r" として、入出力に使うレジスタとして %0, %1 を指定していますが、アセンブラ・プログラム中では r0, r1 に決め打ちで記述しており、 C 側としてもプロローグ/エピローグ・コードだけで、r0, r1 については触らないコードが出力されています。
制約文字列に "I" を指定すると、レジスタではなく、0 〜 255 の範囲のイミディエイト数値として扱われます。 この場合、対応する C の式はコンパイル時に定数値にまで還元可能な式の必要があります。
たとえば、
[ph_acc] "I" (offsetof(op_prop_t, ph_acc))
と指定すると、アセンブラ命令としては、
ldr r2, [r0, %[ph_acc]]
という記述で最終的には、
ldr r2, [r0, #4]
に変換されます。
(offsetof(op_prop_t, ... )) を並べるのは面倒なので、armcc のエンベデッド・アセンブラの場合と同様に、「DEF_OFS()」マクロ
#define DEF_OFS(x) (offsetof(op_prop_t, x))
を定義しています。 armcc の場合との違いは、「__cpp()」があるかないかです。
% の指定で、イミディエイト数値を表す "#" (ナンバー記号) が先頭に付加されて数値に置き換えられます。
%c と指定すると、"#" が付加されずに数値のみに変換されます。
op_prop_t のサイズを
[S] "I" (sizeof(op_prop_t))
として定義してあるので、op_prop[] ポインタを動かさずにオペレータ 1 側の構造体メンバにアクセスするのに
ldr r2, [r0, %[ph_acc] + %c[S]]
という記述で、最終的に
ldr r2, [r0, #4 + 28]
という形に変換されます。