ぐだぐだ低レベルプログラミング(131)ARM64(AArach64)DUP(ベクトル)

Joseph Halfmoon

前回は「実はA64のSIMD(ベクトル)命令にMOVなんてない」の回でした。今回はMOVじゃないけどMOVする命令があるの回です。INSとDUP。INSは前回の復習になりますが、DUPはSIMD計算するときにゃゼッテー欲しくなる操作です。ただ同じエレメントを並べるだけなんだけれども。無かったらどうして良いか分からない?

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

DUP(ベクトル・エレメント)命令

ベクトルや配列などを操作しているときに、全ての要素に同じ数を乗じたい(スカラー倍)とか、同じ数を足したい(並行移動)など「同じ数」が期待されるケースがままあります。SIMD(ベクトル)命令は複数の要素を1命令で演算できるだけに、ベクトルの片割れは「同じ要素を詰めたベクトル」となります。そんなときに活躍するのがDUP(ベクトル・エレメント)命令です。

以下は後の処理例での操作を図にしたものです。ベクトルの1要素をDUPlicateして並べているのが分かるかと思います。DUP_Diagram

上記は単精度浮動小数なので「要素のMOV」4回を1命令で「始末」してますが、これがバイト型であれば16回に相当する操作が1命令でできることになります。MOVの一種なんだけれども時短ってやつ。タイパが良いのよ。

INS(ベクトル・エレメント)命令

上記の「要素のMOV」にあたる1要素を他の要素にチマチマとMOVする操作がINS(ベクトル・エレメント)命令です。実は、前回のベクトル要素のMOVのところで既に実習済です。MOVはINSのエイリアスだったのねえ。折角なので、今回も実習してます。ただし、ニーモニックはINSを使って。

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

当然、DUPにしろ、INSにしろバイトからダブルワード(倍精度浮動小数)にいたる4種類のビット幅の要素に対応しているのですが、例によって手抜きで単精度浮動小数のみの実習であります。また、SIMDのオペランドのレジスタ幅としても64ビット幅もとれるのですが、大は小を兼ねる(ホントか?)ということで128ビット幅の時だけです。

これまた例によって手抜きの関数プロローグ、エピローグ無の被テスト関数のソースが以下に。

.globl	insv, dupv
.text
.balign	4

dupv:
    ld1  {v1.4S}, [x0], #16
    ld1  {v0.4S}, [x0], #16
    dup  v0.4S, v1.S[1]
    st1  {v0.4S}, [x0]
    ret

insv:
    ld1  {v1.4S}, [x0], #16
    ld1  {v0.4S}, [x0], #16
    ins  v0.S[1], v1.S[2]
    st1  {v0.4S}, [x0]
    ret

SIMDのオペランド、意味の違いで微妙に書き方が変わるのでちょいちょい間違えてアセンブラに怒られます。

C言語記述のmain関数

上記のアセンブリ言語関数を呼び出すmain関数が以下に。例のとおりで前回のソースの「チョイ変」っす。メモリにテキトーな値を詰め込んでおいて、レジスタにLOAD、LOAD、DUPしてSTORE、続いて同じことをINSで繰り返すっと。芸がないな。

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

#define MAXMEM	(24)
float TargetMEM[MAXMEM];

extern void dupv(float *);
extern void insv(float *);

void initTGT(float c) {
    for (int i=0; i < MAXMEM; i++) {
        TargetMEM[i] = c * (i+1);
    }
}

void dumpTGT(const char *arg) {
    printf("%s\n", arg);
    for (int i=0; i < MAXMEM; i++) {
        printf("%d: %f\n", i, TargetMEM[i]);
    }
}

int main(void) {
    initTGT(1.000f);

    dumpTGT("Before dup vector");
    dupv(TargetMEM);
    dumpTGT("After  dup vector");
    insv(&TargetMEM[12]);
    dumpTGT("After  ins vector");

    return 0;
}
実験結果

以下のようにしてビルドして実行しています。

$ gcc -g -O0 insdup.c insdup.s
$ ./a.out

標準出力に「ダラダラ」現れる結果を折りたたんで、見やすいように関係個所を枠で囲って紐づけてみましたです。insdupResults

DUPできてるみたい。でもまだまだMOV一族はいるのよ。

ぐだぐだ低レベルプログラミング(130)ARM64(AArach64)MOV(ベクトル)へ戻る

ぐだぐだ低レベルプログラミング(132)ARM64(AArach64)整数/SIMD間転送 へ進む