似たような命令を何度も練習しているのも、A64の命令が多すぎるからと同じ文句を垂れてます。今回は浮動小数/SIMD(スカラー扱い)レジスタの「ペア」を一度にロード、ストアするLDP命令とSTP命令です。この命令とは別に複数レジスタを一度にロードできるベクトルロード、ストアもあるのだけれども、また後で。
※「ぐだぐだ低レベルプログラミング」投稿順indexはこちら
※実機動作確認には以下を使用しております。
-
- Raspberry Pi 4 model B、Cortex-A72コア(ARMv8-A)
- Raspberry Pi OS (64bit) bullseye
- gcc (Debian 10.2.1-6) 10.2.1 20210110
ARMv8もいろいろレベルがあり、Arm Cortex-A72はARMv8の中でもベーシックな(命令数の少ない)ARMv8p0です。
※A64の最新のマニュアルは以下でダウンロード可能です。
Arm Architecture Reference Manual for A-profile architecture
LDP命令とSTP命令
LDPは転送先のレジスタを2個、STPは転送元のレジスタを2個指定することができるロード、ストア命令です。2個はどのレジスタの組み合わせでもOKです。ロード元、ストア先のメモリアドレスはレジスタ2個分、連続でなければなりませぬ。使用可能なアドレシングモードは3種です。
-
- ベース+符合付7ビットオフセット(スケールド)
- プリインデックスド符合付7ビットオフセット(スケールド)
- ポストインデックスド符合付7ビットオフセット(スケールド)
今回実験のアセンブリ言語関数
「いつものように手抜きな」関数プロローグ無、エピローグ無の被テスト関数群が以下に。オペランドのサイズは簡単のため単精度浮動小数1種類に限ってます。さらに都合が良い(手抜きに)ので、以下のようにしています。
-
- LDP命令で連続したメモリから2個のデータをロード
- STP命令で上記とは異なるアドレスの連続したメモリに上記データをストア
- 関数はメモリアクセスに使ったポインタを返す
メモリの内容を吟味すれば1,2のステップで正しく転送ができていることが確認できます。また、3の戻り値を見れば、プリインデックスド、ポストインデックスドされていることも確認できるっと。
.globl fldpstpSpre, fldpstpSpost, fldpstpSso .text .balign 4 fldpstpSso: ldp s0, s1, [x0, #16] stp s0, s1, [x0, #24] ret fldpstpSpost: ldp s0, s1, [x0], #8 stp s0, s1, [x0], #8 ret fldpstpSpre: ldp s0, s1, [x0, #32]! stp s0, s1, [x0, #8]! ret
C言語記述のmain関数
「通り一遍さわるだけの手抜きな」C言語記述のテスト駆動部です。被テスト関数には引数として単精度配列(の先頭アドレス)を与えてます。
テスト開始前には配列には順番を示す値が格納されておりそれをダンプ。終了後には被テスト関数で転送済の値が格納されているので再びダンプしてます。
#include <stdio.h> #include <stdint.h> #define MAXMEM (12) float TargetMEM[MAXMEM]; extern uint64_t fldpstpSpre(float *); extern uint64_t fldpstpSpost(float *); extern uint64_t fldpstpSso(float *); void initTGT() { for (int i=0; i < MAXMEM; i++) { TargetMEM[i] = 1.1111f * i; } } void dumpTGT() { for (int i=0; i < MAXMEM; i++) { printf("%d: %f\n", i, TargetMEM[i]); } } int main(void) { uint64_t result; initTGT(); dumpTGT(); result = fldpstpSso(TargetMEM); printf ( "TargetMEM : %lx\n", TargetMEM); printf ( "fldpstpSso(NO-CHANGE) : %lx\n", result); result = fldpstpSpost(TargetMEM); printf ( "TargetMEM : %lx\n", TargetMEM); printf ( "fldpstpSpost(+0x10) : %lx\n", result); result = fldpstpSpre(TargetMEM); printf ( "TargetMEM : %lx\n", TargetMEM); printf ( "fldpstpSpre(+0x28) : %lx\n", result); dumpTGT(); return 0; }
ビルドして実行
予定どおりの結果が得られました。よかった。