ぐだぐだ低レベルプログラミング(160)A64 SIMD要素毎SQDMLAL(題訂正)

Joseph Halfmoon

前回に続き「SIMDレジスタの一方の全要素に他方の一要素を共通に掛け算」する命令の練習です。今回は「符号付整数の乗算結果を2倍した上で倍のビット幅のレジスタに積和もしくは積差を行った結果が溢れたらサチュレーションさせる」命令を練習してみます。まあ何度となくメンドクセー奴らを練習してきたのでこのくらいはなんてことない?

※タイトル訂正(2024年4月10日)旧タイトル「ぐだぐだ低レベルプログラミング(160)A64(AArach64)SIMD要素毎SQDMAL」のSQDMALは綴り間違いのため「ぐだぐだ低レベルプログラミング(160)A64 SIMD要素毎SQDMLAL(題訂正)」と訂正させていただきました。

※「ぐだぐだ低レベルプログラミング」投稿順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

sqdmlal、sqdmlal2、sqdmlsl、sqdmlsl2

ニーモニックの意味は以下のとおりです。

    • sqdmlal=Signed saturating doubling multiply-add long
    • sqdmlsl=Signed saturating doubling multiply-subtract long

Signed(符号付き整数)のmuliply(積)計算の結果をdoubling(2倍)した上でlong(倍のビット幅のレジスタ内容と) add(和)またはsubtract(差)をとり、最後の結果が符号付整数の表現可能な範囲を逸脱してしまったらsaturating(飽和)させるっと。

さらに言えば末尾に2がついてない命令の方は、第1オペランドをSIMDレジスタの下側半分から拾ってくるのに対して、末尾に2がついている命令の方は、第1オペランドをSIMDレジスタの上側半分から拾ってきます。

分かったような気がする?なお、末尾の「2」とlongの相乗効果により、アセンブリ言語表記のオペランドがパッと見、混乱の極みっす。こんな感じ。

sqdmlal2 v0.4S, v1.8H, v2.H[1]

4個なんだか8個なんだか、ワードなんだかハーフワードなんだかハッキリしろい、という表記ですが上記は正しくアセンブラに処理されます。

この程度のメンドさにたじろいでいたら、A64のSIMD命令など練習できまっせん(といいつつ、いい加減閉口してるが。)

実験につかったアセンブリ言語記述の被テスト関数

例によって手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下に。念のため、FPCRとFPSRにアクセスするための関数も用意いたしましたが、使いませなんだ。本当はサチュレーションした場合、フラグで確認することも可能デス。

.globl	sqdmlal4V, sqdmlal24V, sqdmlsl4V, sqdmlsl24V, readfpcr, readfpsr, writefpcr 
.text
.balign	4

sqdmlal4V:
    ld1  {V0.4S, v1.4S, v2.4S}, [x0]
    sqdmlal  v0.4S,  v1.4H, v2.H[1]
    st1  {v0.4S}, [x0]
    ret

sqdmlal24V:
    ld1  {V0.4S, v1.4S, v2.4S}, [x0]
    sqdmlal2  v0.4S,  v1.8H, v2.H[1]
    st1  {v0.4S}, [x0]
    ret

sqdmlsl4V:
    ld1  {V0.4S, v1.4S, v2.4S}, [x0]
    sqdmlsl  v0.4S,  v1.4H, v2.H[1]
    st1  {v0.4S}, [x0]
    ret

sqdmlsl24V:
    ld1  {V0.4S, v1.4S, v2.4S}, [x0]
    sqdmlsl2  v0.4S,  v1.8H, v2.H[1]
    st1  {v0.4S}, [x0]
    ret

readfpcr:
    mrs x0, fpcr
    ret

readfpsr:
    mrs x0, fpsr
    ret

writefpcr:
    msr fpcr, x0
    ret
C言語記述のmain関数

上記のアセンブリ言語関数を呼び出すmain関数が以下に。本来は符号付きの整数を処理する命令なのですが、「ビットパターンで見ないとダブリングとかサチュレーティングとかよく分からん」という手前勝手な理由で符号なし整数で与えてます。まあ、機械語命令に到達すればCレベルの符号の有り無しなど無用。

#include <stdio.h>
#include <stdint.h>
#include <math.h>

#define LOWARG(X)  (0xFFFF & X)
#define HIGHARG(X)  (0xFFFF & (X >> 16))

#define MAXMEM	(12)
uint32_t TargetMEM[MAXMEM];


extern void sqdmlal4V(uint32_t *);
extern void sqdmlal24V(uint32_t *);
extern void sqdmlsl4V(uint32_t *);
extern void sqdmlsl24V(uint32_t *);
extern uint32_t readfpcr(void);
extern uint32_t readfpsr(void);
extern void writefpcr(uint32_t);


void initTGT() {
    TargetMEM[0]   = 0x7FFFFFF0;
    TargetMEM[1]   = 0x7FFFFFF0;
    TargetMEM[2]   = 0x7FFFFFF0;
    TargetMEM[3]   = 0x7FFFFFF0;
    TargetMEM[4]   = 0x00020001;
    TargetMEM[5]   = 0x00040003;
    TargetMEM[6]   = 0x00050001;
    TargetMEM[7]   = 0x00050001;
    TargetMEM[8]   = 0x00020000;
    TargetMEM[9]   = 0x00000000;
    TargetMEM[10]  = 0x00000000;
    TargetMEM[11]  = 0x00000000;
}

void initTGTS() {
    TargetMEM[0]   = 0x8000000F;
    TargetMEM[1]   = 0x8000000F;
    TargetMEM[2]   = 0x8000000F;
    TargetMEM[3]   = 0x8000000F;
    TargetMEM[4]   = 0x00020001;
    TargetMEM[5]   = 0x00040003;
    TargetMEM[6]   = 0x00050001;
    TargetMEM[7]   = 0x00050001;
    TargetMEM[8]   = 0x00020000;
    TargetMEM[9]   = 0x00000000;
    TargetMEM[10]  = 0x00000000;
    TargetMEM[11]  = 0x00000000;
}

void dumpTGT(const char *arg) {
    printf("%s\n", arg);
    printf("%02d: %04x opr %04x -> %08x\n", 0, LOWARG(TargetMEM[4]), HIGHARG(TargetMEM[8]), TargetMEM[0]);
    printf("%02d: %04x opr %04x -> %08x\n", 1, HIGHARG(TargetMEM[4]), HIGHARG(TargetMEM[8]), TargetMEM[1]);
    printf("%02d: %04x opr %04x -> %08x\n", 2, LOWARG(TargetMEM[5]), HIGHARG(TargetMEM[8]), TargetMEM[2]);
    printf("%02d: %04x opr %04x -> %08x\n", 3, HIGHARG(TargetMEM[5]), HIGHARG(TargetMEM[8]), TargetMEM[3]);
}

void dumpTGT2(const char *arg) {
    printf("%s\n", arg);
    printf("%02d: %04x opr %04x -> %08x\n", 0, LOWARG(TargetMEM[6]), HIGHARG(TargetMEM[8]), TargetMEM[0]);
    printf("%02d: %04x opr %04x -> %08x\n", 1, HIGHARG(TargetMEM[6]), HIGHARG(TargetMEM[8]), TargetMEM[1]);
    printf("%02d: %04x opr %04x -> %08x\n", 2, LOWARG(TargetMEM[7]), HIGHARG(TargetMEM[8]), TargetMEM[2]);
    printf("%02d: %04x opr %04x -> %08x\n", 3, HIGHARG(TargetMEM[7]), HIGHARG(TargetMEM[8]), TargetMEM[3]);
}

int main(void) {
    initTGT();
    sqdmlal4V(TargetMEM);
    dumpTGT("sqdmlal v0.4S,  v1.4H, v2.H[1]");

    initTGT();
    sqdmlal24V(TargetMEM);
    dumpTGT2("sqdmlal2 v0.4S,  v1.8H, v2.H[1]");

    initTGTS();
    sqdmlsl4V(TargetMEM);
    dumpTGT("sqdmlsl v0.4S,  v1.4H, v2.H[1]");

    initTGTS();
    sqdmlsl24V(TargetMEM);
    dumpTGT2("sqdmlsl2 v0.4S,  v1.8H, v2.H[1]");

    return 0;
}
実機実行結果の確認

以下のようにしてビルドして実行しています。赤線引いてあるのがサチュレーションが発生している筈のところです。simd_elemSQDMLALresults

今回は4命令練習したな。でもまだまだあるのよ。。。

ぐだぐだ低レベルプログラミング(159)A64(AArach64)SIMD要素毎FMULX へ戻る

ぐだぐだ低レベルプログラミング(161)A64 SIMD要素毎SQDMLAHはARMv8.1 へ進む