こんにちは。NoMaYです。e2 studio v6.3.0がリリースされていたので、インストールして幾つかプロジェクトを作成して、いつものようにe2 studioのインストールフォルダを眺めていたら、CCRXmachine.hとCCRXmachine.cというファイルがあることに気付きました。中を見てみると、概ねファイル名から予想される通りのソースファイルでした。(今までのe2 studioのインストールフォルダを見直してみたところ、以前からあったことが分かりましたが、今まで気付きませんでした。) ただ、一部コメントアウトされているものがあったり、以前に別スレッド『GUNRX用プロジェクトのスマートコンフィグレータのBSPを見ていて気付いた変な移植コード』で話題にしたことと同じ元のコードの意図を理解していない書き換えがあったり、ちょっと惜しいような気もしました。e2 studioインストールフォルダ\internal\projectgen\rx\Generate\CCRXConversion\inc\CCRXmachine.he2 studioインストールフォルダ\internal\projectgen\rx\Generate\CCRXConversion\inc\CCRXmachine.c
こんにちは。NoMaYです。幾つか前の投稿で書いたCC-RXで積和演算を行うrmpab()関数(およびrmpaw()関数とrmpal()関数も)ですが、GNURXでの今回のCCRXmachine2.hの実装が何か汚くなって来たので、少し頭を冷やしてGoogle検索していたところ、GNURXのビルトイン関数に__builtin_rx_rmpa()という関数があることに気付きました。ところが、これがまた悩ましい仕様になっていました。
void __builtin_rx_rmpa (void) Generates the rmpa machine instruction which initiates a repeated multiply and accumulate sequence.
GNURXgcc-renesas.com/migration-guides/rx/index.html#Build_inGCC本家gcc.gnu.org/onlinedocs/gcc/RX-Built-in-Functions.html#index-_005f_005fbuiltin_005frx_005frmpa悩ましい点の1つ目は、積和演算関数は積和演算命令のデータのサイズ(B/W/L)に応じて3種類あるのですが、この__builtin_rx_rmpa()関数はバイトサイズ用のものであり、他のデータサイズ用のものが無い点です。2つ目は、積和演算関数には引数が4つあるのですが、この__builtin_rx_rmpa()関数には引数がありませんので、一体どうやって使えば良いのか分からない(ドキュメントに使い方の記述が無い)点です。加えて、恐らくこの様に使うに違いない、というのが分かっても試してみると、最適化無しでしか使えない点です。それで、まず使い方に関しては、RedHat社で組み込み向けGNUコンパイラを開発されているDJ Delorie氏が本家Rulzへ投稿したrmpa_gcc.zipのrmpa.cの以下のインラインアセンブラ部分をこの__builtin_rx_rmpa()関数で置き換える、ということになる筈だと思います。(ちなみに、今回のCCRXmachine2.hでも苦し紛れに同様な記述をしていたのですが、どうやらその記述で合っていそうだ、ということが分かって来ました。)Optimizing multiply-accumulate (RMPA) with GCCrenesasrulz.com/rx/f/rx---forum/2509/optimizing-multiply-accumulate-rmpa-with-gccrmpa.c (rmpa_gcc.zipに含まれているソース)
#include "data.h"extern __inline__ __attribute__((always_inline))long longrmpaw (long long init, unsigned long count, short *a, short *b){ register long long rv __asm__("r4") = init; register unsigned long count_out __asm__("r3") = count; register short *a_out __asm__("r1") = a; register short *b_out __asm__("r2") = b; __asm__ volatile ("rmpa.w" : "=r" (rv), "=r" (count_out) , "=r" (a_out), "=r" (b_out) : "0" (rv), "1" (count_out), "2" (a_out), "3" (b_out) : "r6"); return rv;}long long mac (short *a, short *b, int len){ return rmpaw (0, len, a, b);}
これをそのままGNURX 2018q1でコンパイルした場合
-O3⇒ コードはソースの意図通り (もともと 引数 a --> r1, 引数 b --> r2, 引数 len --> r3 で呼ばれる)⇒ ただし、そもそもソース上でr6を初期化しておらず、積和演算結果が意図通りにならない場合がある筈⇒ また、アセンブラ記述部がa[0]~a[count-1]とb[0]~b[count-1]を参照しているという情報が無いので、 上の例では意図通りのコードが生成されたものの、もっと複雑なソースにrmpaw()関数がインライン展開 された場合に誤ったコードが生成される可能性がある(と思われます、、、詳細は後述します、、、) 4 .global _mac 6 _mac: 11 0000 7E A6 push.l r6 17 0002 66 04 mov.L #0, r4 18 0004 EF 45 mov.L r4, r5 21 0006 7F 8D rmpa.w 27 0008 EF 41 mov.L r4, r1 28 000a EF 52 mov.L r5, r2 29 000c 3F 66 01 rtsd #4, r6-r6
-O0⇒ コンパイルエラーになってしまう>makerx-elf-gcc -g -O0 -MMD -Wa,-adln=rmpa.lst -c -o rmpa.o rmpa.crmpa.c: In function 'mac':rmpa.c:22:1: error: r6 cannot be used in asm here } ^
ところが、以下の通りインラインアセンブラ部分をこの__builtin_rx_rmpa()関数で置き換えてコンパイルしてみると、最適化有りでは正しくないコードが生成されてしまっている、ということに気付きます、、、
register long long rv __asm__("r4") = init; register unsigned long count_out __asm__("r3") = count; register short *a_out __asm__("r1") = a; register short *b_out __asm__("r2") = b; __builtin_rx_rmpa(); return rv;
GNURX 2018q1でコンパイルした場合
-O3 / -O2 / -O1⇒ コードが滅茶苦茶 (r5:r4にinitの初期値0が設定されておらず、逆に戻り値はinitの初期値0のまま)⇒ 補足すれば、最適化処理で__builtin_rx_rmpa();は入出力の全く無いNOP命令と同じ扱い!をされている 4 .global _mac 6 _mac: 11 0000 6E 67 pushm r6-r7 13 0002 EF 06 mov.L r0, r6 19 0004 7F 8E rmpa 24 0006 66 01 mov.L #0, r1 25 0008 EF 12 mov.L r1, r2 26 000a 3F 67 02 rtsd #8, r6-r7
-O0⇒ コードはソースの意図通り 4 .global _mac 6 _mac: 10 0000 7E A6 push.l r6 12 0002 71 06 E0 add #-32, r0, r6 14 0005 EF 60 mov.L r6, r0 16 0007 A1 69 mov.L r1, 20[r6] 引数 a 17 0009 A1 E2 mov.L r2, 24[r6] 引数 b 18 000b A1 EB mov.L r3, 28[r6] 引数 c 20 000d A9 ED mov.L 28[r6], r5 21 000f F8 66 00 mov.L #0, [r6] 0 --> init(下位32ビット) 22 0012 3E 61 00 mov.L #0, 4[r6] 0 --> init(上位32ビット) 23 0015 A0 E5 mov.L r5, 8[r6] 引数 c --> r5 --> count 24 0017 A9 6D mov.L 20[r6], r5 25 0019 A0 ED mov.L r5, 12[r6] 引数 a --> r5 --> a_out 26 001b A9 E5 mov.L 24[r6], r5 27 001d A1 65 mov.L r5, 16[r6] 引数 b --> r5 --> b_out 31 001f EC 64 mov.L [r6], r4 init(下位32ビット) --> r4 32 0021 A8 6D mov.L 4[r6], r5 init(上位32ビット) --> r5 34 0023 A8 E3 mov.L 8[r6], r3 count --> r3 36 0025 A8 E9 mov.L 12[r6], r1 a_out --> r1 38 0027 A9 62 mov.L 16[r6], r2 b_out --> r2 40 0029 7F 8E rmpa 42 002b EF 42 mov.L r4, r2 43 002d EF 53 mov.L r5, r3 44 002f EF 24 mov.L r2, r4 45 0031 EF 35 mov.L r3, r5 49 0033 EF 42 mov.L r4, r2 50 0035 EF 53 mov.L r5, r3 51 0037 EF 24 mov.L r2, r4 52 0039 EF 35 mov.L r3, r5 54 003b EF 41 mov.L r4, r1 r4 --> 戻り値(下位32ビット) 55 003d EF 52 mov.L r5, r2 r5 --> 戻り値(上位32ビット) 56 003f 3F 66 09 rtsd #36, r6-r6
このような具合であり、以前に投稿した__builtin_rx_xchg()関数で「複雑難解な最適化処理との兼ね合いでどちらに転ぶか分からない不安があって悩ましい」と感じたのと同様に、この__builtin_rx_rmpa()関数でも最適化処理との絡みが悩ましい、と感じました。そして、最適化処理との絡みという点で、DJ Delorie氏のインラインアセンブラ部分も怪しい気がします。実は、今回のCCRXmachine2.hの為に色々調べていたので気付いたのですが、ストリング操作命令では以下のような記述が必要であり、それはRMPA命令でも同じだと思われます。ところが、DJ Delorie氏のインラインアセンブラ部分に、それに相当する記述(アセンブラ記述部がa[0]~a[count-1]とb[0]~b[count-1]を参照しているという記述)がありませんので、複雑なソースにrmpaw()関数がインライン展開された場合に最適化処理により誤ったコードが生成される可能性があると思われます、、、6.45.2 Extended Asm - Assembler Instructions with C Expression Operandsgcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Clobbers-and-Scratch-RegistersIA-32インテル®アーキテクチャ ソフトウェア・デベロッパーズ・マニュアル 中巻B:命令セット・リファレンスN-Zwww.intel.com/content/dam/www/public/ijkk/jp/ja/documents/developer/IA32_Arh_Dev_Man_Vol2B_i.pdfそもそも、DJ Delorie氏のソース上でr6が初期化されていませんので、積和演算結果が意図通りにならない場合がある筈だと思われます、、、RXファミリ ユーザーズマニュアル ソフトウェア編www.renesas.com/ja-jp/doc/products/mpumcu/doc/rx_family/r01us0032jj0120_rxsm.pdfちなみに、今回のCCRXmachine2.hの手元の作業中のソースでは、rmpab()関数(およびrmpaw()関数とrmpal()関数も)は、ようやく以下の記述に至ったところです。
#ifndef rmpab#define rmpab(init, count, addr1, addr2) ccrx_machine_h_rmpab(init, count, addr1, addr2)#endifstatic __inline__ long long ccrx_machine_h_rmpab(long long init, unsigned long count, signed char *addr1, signed char *addr2) __attribute__((always_inline));static __inline__ long long ccrx_machine_h_rmpab(long long init, unsigned long count, signed char *addr1, signed char *addr2){/* CC-RX V2.03 ARG init = R2:R1 ARG count = R3 ARG addr1 = R4 ARG addr2 = 04H[R0](08H[R0] after push R6) RET R2:R1 PUSH.L R6 MOV.L R1, R14 MOV.L R2, R5 MOV.L 08H[R0], R2 SHAR #1FH, R5, R6 MOV.L R4, R1 MOV.L R14, R4 RMPA.B MOV.L R5, R2 MOV.L R4, R1 RTSD #04H, R6-R6*/ register long long _r5r4 __asm__("r4") = init; register signed char *_r1 __asm__("r1") = addr1; register signed char *_r2 __asm__("r2") = addr2; register unsigned long _r3 __asm__("r3") = count;#ifdef __OPTIMIZE__ /* In case of other than -O0, we assign r6. */ register signed long _rS __asm__("r6") = (signed long)(init >> 63);#else /* In case of -O0, we assign r8 because r6 and r7 are reserved for the frame pointer and something. */ register signed long _rS __asm__("r8") = (signed long)(init >> 63);#endif __asm__ volatile ( /* AssemblerTemplate */#ifdef __OPTIMIZE__ "RMPA.B" "\n\t"#else "XCHG R6, R8" "\n\t" "RMPA.B" "\n\t" "XCHG R6, R8" "\n\t"#endif : /* OutputOperands */ /**/ "+r" (_rS), /**/ "+r" (_r5r4), /**/ "+r" (_r3) : /* InputOperands */ /**/ "r" (_r1), /**/ "r" (_r2), /**/ "m" (*(signed char (*)[count]) addr1), /**/ "m" (*(signed char (*)[count]) addr2) : /* Clobbers */ /*"r1",*/ /* This causes a compile error: asm-specifier for variable '_r1' conflicts with asm clobber list */ /*"r2" */ /* This causes a compile error: asm-specifier for variable '_r2' conflicts with asm clobber list */ ); __asm__ volatile ( /* AssemblerTemplate */ ""/*"NOP"*/ "\n\t" : /* OutputOperands */ /* No outputs. */ : /* InputOperands */ /* No inputs. */ : /* Clobbers */ "r1", "r2" /* This asm statement can make the compiler to recognize that r1 and r2 are not equivalent to addr1 and addr2. */ /* For example, after replacing 'return _r5r4;' --> 'return *addr1 * *addr2;' tentatively, code are different. *//* GNURX 2018q1 -O2In case of WITHOUT this asm statement, it is recognized that r1 and r2 are equivalent to addr1 and addr2 mistakenly. 0019 7E A6 push.l r6 001b 60 40 sub #4, r0 001d EF 25 mov.L r2, r5 001f A8 8A mov.L 12[r0], r2 0021 FC 43 41 xchg r4, r1 0024 FD BF 56 shar #31, r5, r6 0027 7F 8C RMPA.B return *addr1 * *addr2; 0029 CC 15 mov.B [r1], r5 002b CC 21 mov.B [r2], r1 002d 4F 51 mul r5, r1 002f FD BF 12 shar #31, r1, r2 0032 3F 66 02 rtsd #8, r6-r6In case of WITH this asm statement, it is recognized that r1 and r2 are not equivalent to addr1 and addr2 correctly. 0019 7E A6 push.l r6 001b 60 40 sub #4, r0 001d EF 25 mov.L r2, r5 001f ED 0E 03 mov.L 12[r0], r14 0022 EF 4F mov.L r4, r15 0024 EF 14 mov.L r1, r4 0026 EF E2 mov.L r14, r2 0028 EF F1 mov.L r15, r1 002a FD BF 56 shar #31, r5, r6 002d 7F 8C RMPA.B return *addr1 * *addr2; 002f CC F1 mov.B [r15], r1 0031 06 0C E1 mul [r14].B, r1 0034 FD BF 12 shar #31, r1, r2 0037 3F 66 02 rtsd #8, r6-r6*/ ); return _r5r4;/* GNURX 2018q1 -O2 108 0019 7E A6 push.l r6 110 001b 60 40 sub #4, r0 113 001d EF 25 mov.L r2, r5 115 001f A8 8A mov.L 12[r0], r2 120 0021 FC 43 41 xchg r4, r1 122 0024 FD BF 56 shar #31, r5, r6 125 0027 7F 8C RMPA.B 134 0029 EF 41 mov.L r4, r1 135 002b EF 52 mov.L r5, r2 136 002d 3F 66 02 rtsd #8, r6-r6*//* GNURX 2018q1 -O0, (This caller is not good example, to be reconsidered...) 185 00ab 6E 6C pushm r6-r12 187 00ad 71 06 D8 add #-40, r0, r6 189 00b0 EF 60 mov.L r6, r0 191 00b2 75 45 48 mov.L #0x48, r5 192 00b5 4B 65 add r6, r5 193 00b7 A1 69 mov.L r1, 20[r6] 194 00b9 A1 E2 mov.L r2, 24[r6] 195 00bb A1 EB mov.L r3, 28[r6] 196 00bd A2 64 mov.L r4, 32[r6] 197 00bf A9 6C mov.L 20[r6], r4 198 00c1 E3 64 mov.L r4, [r6] 199 00c3 A9 E4 mov.L 24[r6], r4 200 00c5 A0 6C mov.L r4, 4[r6] 201 00c7 A9 EC mov.L 28[r6], r4 202 00c9 A0 E4 mov.L r4, 8[r6] 203 00cb AA 64 mov.L 32[r6], r4 204 00cd A0 EC mov.L r4, 12[r6] 205 00cf EC 55 mov.L [r5], r5 206 00d1 A1 65 mov.L r5, 16[r6] 210 00d3 EC 64 mov.L [r6], r4 211 00d5 A8 6D mov.L 4[r6], r5 213 00d7 A8 E9 mov.L 12[r6], r1 215 00d9 A9 62 mov.L 16[r6], r2 217 00db A8 E3 mov.L 8[r6], r3 219 00dd ED 6C 01 mov.L 4[r6], r12 220 00e0 FD BF CA shar #31, r12, r10 221 00e3 ED 6C 01 mov.L 4[r6], r12 222 00e6 FD BF CB shar #31, r12, r11 223 00e9 EF A8 mov.L r10, r8 225 00eb ED 6A 03 mov.L 12[r6], r10 226 00ee ED 6B 04 mov.L 16[r6], r11 228 00f1 FC 43 68 XCHG R6, R8 229 00f4 7F 8C RMPA.B 230 00f6 FC 43 68 XCHG R6, R8 239 00f9 EF 42 mov.L r4, r2 240 00fb EF 53 mov.L r5, r3 241 00fd EF 24 mov.L r2, r4 242 00ff EF 35 mov.L r3, r5 246 0101 EF 42 mov.L r4, r2 247 0103 EF 53 mov.L r5, r3 248 0105 EF 24 mov.L r2, r4 249 0107 EF 35 mov.L r3, r5 250 0109 EF 41 mov.L r4, r1 251 010b EF 52 mov.L r5, r2 252 010d 3F 6C 11 rtsd #68, r6-r12*/}