欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【NEON 和 VFP 编程】NEON通用数据处理指令

程序员文章站 2022-04-19 17:16:24
...

本节包括以下小节:

• VCVT

向量在定点数或整数与浮点数之间转换。

• VDUP

将标量复制到向量的所有向量线。

• VEXT

提取。

• VMOV、VMVN(立即数)

移动和求反移动(立即数)。

• VMOVL、V{Q}MOVN、VQMOVUN

移动(寄存器)。

• VREV

反转向量中的元素。

• VSWP

交换向量。

• VTBL、VTBX

向量表查找。

• VTRN

向量转置。

• VUZP、VZIP

向量交叉存取和反向交叉存取。

一、VCVT

VCVT(向量转换)按下列方式之一转换一个向量中的每个元素,并将结果存放到另一向量中:

• 浮点数到整数

• 整数到浮点数

• 浮点数到定点数

• 定点数到浮点数。

语法

VCVT{cond}.type Qd, Qm {, #fbits}

VCVT{cond}.type Dd, Dm {, #fbits}

其中:

cond 是一个可选的条件代码。

type 为向量的元素指定数据类型。 必须是下列值之一:

S32.F32 浮点数到有符号整数或定点数

U32.F32 浮点数到无符号整数或定点数

F32.S32 有符号整数或定点数到浮点数

F32.U32 无符号整数或定点数到浮点数

Qd、Qm 为四字运算指定目标向量和操作数向量。

Dd、Dm 为双字运算指定目标向量和操作数向量。

fbits 如果存在,则指定定点数中的小数位数。 否则,将在浮点数和整数之间转换。fbits 必须在范围 0 到 32 内。 如果省略 fbits,则小数位数为 0。

舍入

整数或定点数到浮点数的转换使用向最接近的数舍入。

浮点数到整数或定点数的转换使用向零舍入。

VCVT指令在上一节中已经接触过了,它的作用就是在整数和浮点数之间进行转化。

    float *data = new float[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0;
        data1[i] = (unsigned int) i;
        LOGI("1 data[%d]=%f data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VCVT.F32.U32 q1,q0\t\n"
            "VSTM %0,{q1}\t\n"
    :"+r"(data) //%0
    :"r"(data1) //%1
    : "memory", "q0", "q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%f data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

10-30 09:08:55.210 3287-3287/ndk.example.com.ndkexample I/Native: 1 data[0]=0.000000 data1[0]=0
    1 data[1]=0.000000 data1[1]=0X1
    1 data[2]=0.000000 data1[2]=0X2
    1 data[3]=0.000000 data1[3]=0X3
    2 data[0]=0.000000 data1[0]=0
    2 data[1]=1.000000 data1[1]=0X1
    2 data[2]=2.000000 data1[2]=0X2
    2 data[3]=3.000000 data1[3]=0X3

二、VDUP

向量复制将标量复制到目标向量的每一元素。 源可以是 NEON 标量或 ARM 寄存器。

语法

VDUP{cond}.size Qd, Dm[x]

VDUP{cond}.size Dd, Dm[x]

VDUP{cond}.size Qd, Rm

VDUP{cond}.size Dd, Rm

其中:

cond 是一个可选的条件代码。

size 必须为 8、16 或 32。

Qd 为四字运算指定目标寄存器。

Dd 为双字运算指定目标寄存器。

Dm[x] 指定 NEON 标量。

Rm 指定 ARM 寄存器。Rm 不得为 pc。

下面看使用VDUP的例子。首先将数组内容加载到q0寄存器中,然后将d1[1]中的值复制到q1寄存器的每一个元素。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0;
        data1[i] = (unsigned int) i;
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VDUP.32 q1,d1[1]\t\n"
            "VSTM %0,{q1}\t\n"
    :"+r"(data) //%0
    :"r"(data1) //%1
    : "memory", "q0", "q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

10-31 07:50:34.390 3005-3005/ndk.example.com.ndkexample I/Native: 1 data[0]=0 data1[0]=0
    1 data[1]=0 data1[1]=0X1
    1 data[2]=0 data1[2]=0X2
    1 data[3]=0 data1[3]=0X3
    2 data[0]=0X3 data1[0]=0
    2 data[1]=0X3 data1[1]=0X1
    2 data[2]=0X3 data1[2]=0X2
    2 data[3]=0X3 data1[3]=0X3

当然另外一种使用方式是将arm寄存器的值复制到所有元素。

三、VEXT

向量提取从第二个操作数向量的低位和第一个操作数的高位提取 8 位元素,将这些元素连接起来,并将结果存放到目标向量中。

【NEON 和 VFP 编程】NEON通用数据处理指令

语法

VEXT{cond}.8 {Qd}, Qn, Qm, #imm

VEXT{cond}.8 {Dd}, Dn, Dm, #imm

其中:

cond 是一个可选的条件代码。

Qd、Qn、Qm 为四字运算指定目标寄存器、第一个操作数寄存器和第二个操作数寄存器。

Dd、Dn、Dm 为双字运算指定目标寄存器、第一个操作数寄存器和第二个操作数寄存器。

imm 是要从第二个操作数向量的低位提取的 8 位元素的数目;对于双字运算,位于范围 0 到 7 内;对于四字运算,位于范围 0 到 15 内。

VEXT 伪指令

可以将数据类型指定为 16、32 或 64,而不是 8。 在这种情况下,#imm 指的是半字、字或双字而不是字节,并且允许的范围也会相应的减小。

下面的示例根据上图可以很容易理解。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x11111111;
        data1[i] = 0xFFFFFFFF;
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VLDM %0,{q1}\t\n"
            "VEXT.8 q2,q0,q1,#7\t\n"
            "VSTM %0,{q2}\t\n"
    :"+r"(data) //%0
    :"r"(data1) //%1
    : "memory", "q0", "q1", "q2"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

10-31 08:05:06.760 3199-3199/? I/Native: 1 data[0]=0X11111111 data1[0]=0XFFFFFFFF
    1 data[1]=0X11111111 data1[1]=0XFFFFFFFF
    1 data[2]=0X11111111 data1[2]=0XFFFFFFFF
    1 data[3]=0X11111111 data1[3]=0XFFFFFFFF
    2 data[0]=0XFFFFFFFF data1[0]=0XFFFFFFFF
    2 data[1]=0XFFFFFFFF data1[1]=0XFFFFFFFF
    2 data[2]=0X111111FF data1[2]=0XFFFFFFFF
    2 data[3]=0X11111111 data1[3]=0XFFFFFFFF

四、VMOV、VMVN(立即数)

向量移动和向量求反移动(立即数)生成一个立即数,并将结果存放到目标寄存器。

语法

Vop{cond}.datatype Qd, #imm

Vop{cond}.datatype Dd, #imm

其中:

op 必须为 MOV 或 MVN。

cond 是一个可选的条件代码。

datatype 必须为 I8、I16、I32、I64 或 F32 之一。

Qd 或 Dd 是用于存放结果的 NEON 寄存器。

imm 是 datatype 所指定类型的常数。 将复制此常数来填充目标寄存器。

datatype VMOV VMVN
I8 0xXY -
I16 0x00XY、0xXY00 0xFFXY、0xXYFF
I32 0x000000XY、0x0000XY00、0x00XY0000、0xXY000000、0x0000XYFF、0x00XYFFFF 0xFFFFFFXY、0xFFFFXYFF、0xFFXYFFFF、0xXYFFFFFF、0xFFFFXY00、0xFFXY0000
I64 字节掩码 0xGGHHJJKKLLMMNNPP -
F32 浮点数 -

a. 0xGG、0xHH、0xJJ、0xKK、0xLL、0xMM、0xNN 和 0xPP 都必须为 0x00 或 0xFF。

b. 任何浮点数均可以 +/-n * 2- 形式表示,其中 n 和 r 是整数,16 <= n <= 31,0 <= r <= 7。

前面的内容已经遇到过使用VMOV指令移动一个立即数。下面接着看示例。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x11111111;
        data1[i] = 0xFFFFFFFF;
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VMOV.I32 q0,#0x00000012\t\n"
            "VSTM %0,{q0}\t\n"
    :"+r"(data) //%0
    :"r"(data1) //%1
    : "memory", "q0"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

10-31 08:35:38.000 3325-3325/ndk.example.com.ndkexample I/Native: 1 data[0]=0X11111111 data1[0]=0XFFFFFFFF
    1 data[1]=0X11111111 data1[1]=0XFFFFFFFF
    1 data[2]=0X11111111 data1[2]=0XFFFFFFFF
    1 data[3]=0X11111111 data1[3]=0XFFFFFFFF
    2 data[0]=0X12 data1[0]=0XFFFFFFFF
    2 data[1]=0X12 data1[1]=0XFFFFFFFF
    2 data[2]=0X12 data1[2]=0XFFFFFFFF
    2 data[3]=0X12 data1[3]=0XFFFFFFFF

五、VMOVL、V{Q}MOVN、VQMOVUN

VMOVL(向量长移)获取双字向量中的每个元素,用符号或零将其扩展到原长度的两倍,并将结果存放到四字向量中。

VMOVN(向量窄移)将四字向量中每个元素的最低有效半部复制到双字向量的相应元素中。

VQMOVN(向量饱和窄移)将操作数向量中的每个元素复制到目标向量的相应元素中。 结果元素是操作数元素宽度的一半,并且会将值饱和到结果宽度。

VQMOVUN(向量饱和窄移,有符号操作数和无符号结果)将操作数向量的每个元素复制到目标向量的相应元素中。 结果元素是操作数元素宽度的一半,并且会将值饱和到结果宽度。

语法

VMOVL{cond}.datatype Qd, Dm

V{Q}MOVN{cond}.datatype Dd, Qm

VQMOVUN{cond}.datatype Dd, Qm

其中:

Q 如果存在,则指定对结果进行饱和。

cond 是一个可选的条件代码。

datatype 必须是下列值之一:

S8、S16、S32 对于 VMOVL

U8、U16、U62 对于 VMOVL

I16, I32, I64 对于 VMOVN

S16、S32、S64 对于 VQMOVN 或 VQMOVUN

U16、U32、U64 对于 VQMOVN。

Qd、Dm 为 VMOVL 指定目标向量和操作数向量。

Dd、Qm 为 V{Q}MOV{U}N 指定目标向量和操作数向量。

关于MOVL的使用下面是一个用例。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x0;
        data1[i] = 0x00FF00FF;
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VMOVL.U16 q1,d0\t\n"
            "VSTM %0,{q1}\t\n"
    :"+r"(data) //%0
    :"r"(data1) //%1
    : "memory", "q0","q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

10-31 18:14:57.840 5994-5994/ndk.example.com.ndkexample I/Native: 1 data[0]=0 data1[0]=0XFF00FF
    1 data[1]=0 data1[1]=0XFF00FF
    1 data[2]=0 data1[2]=0XFF00FF
    1 data[3]=0 data1[3]=0XFF00FF
    2 data[0]=0XFF data1[0]=0XFF00FF
    2 data[1]=0XFF data1[1]=0XFF00FF
    2 data[2]=0XFF data1[2]=0XFF00FF
    2 data[3]=0XFF data1[3]=0XFF00FF

再来看VQMOVN指令

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x0;
        data1[i] = 0x12345678;
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VBIC q1,q1\t\n"
            "VQMOVN.U32 d2,q0\t\n"
            "VSTM %0,{q1}\t\n"
    :"+r"(data) //%0
    :"r"(data1) //%1
    : "memory", "q0","q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

10-31 18:23:52.620 6118-6118/ndk.example.com.ndkexample I/Native: 1 data[0]=0 data1[0]=0X12345678
    1 data[1]=0 data1[1]=0X12345678
    1 data[2]=0 data1[2]=0X12345678
    1 data[3]=0 data1[3]=0X12345678
    2 data[0]=0XFFFFFFFF data1[0]=0X12345678
    2 data[1]=0XFFFFFFFF data1[1]=0X12345678
    2 data[2]=0 data1[2]=0X12345678
    2 data[3]=0 data1[3]=0X12345678

修改data1中每个元素的值为0x5678后再试,会发现结果不再饱和。

10-31 18:26:23.470 6170-6170/ndk.example.com.ndkexample I/Native: 1 data[0]=0 data1[0]=0X5678
    1 data[1]=0 data1[1]=0X5678
    1 data[2]=0 data1[2]=0X5678
    1 data[3]=0 data1[3]=0X5678
    2 data[0]=0X56785678 data1[0]=0X5678
    2 data[1]=0X56785678 data1[1]=0X5678
    2 data[2]=0 data1[2]=0X5678
    2 data[3]=0 data1[3]=0X5678

六、VREV

VREV16(向量在半字中反转)反转向量每个半字中的 8 位元素的顺序,并将结果存放到对应的目标向量中。

VREV32(向量在字中反转)反转向量每个字中的 8 位或 16 位元素的顺序,并将结果存放到对应的目标向量中。

VREV64(向量在双字中反转)反转向量每个双字中的 8 位、16 位或 32 位元素的顺序,并将结果存放到对应的目标向量中。

语法

VREVn{cond}.size Qd, Qm

VREVn{cond}.size Dd, Dm

其中:

n 必须为 16、32 或 64 之一。

cond 是一个可选的条件代码。

size 必须为 8、16 或 32 之一,并且必须小于 n。

Qd、Qm 为四字运算指定目标向量和操作数向量。

Dd、Dm 为双字运算指定目标向量和操作数向量。

下面是一段示例代码。我们将q0寄存器中每个字中的8位顺序反转,也就是0x12345678转为了0x78563412。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x0;
        data1[i] = 0x12345678;
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VREV32.8 q1,q0\t\n"
            "VSTM %0,{q1}\t\n"
    :"+r"(data) //%0
    :"r"(data1) //%1
    : "memory", "q0","q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

11-01 07:50:55.210 3160-3160/ndk.example.com.ndkexample I/Native: 1 data[0]=0 data1[0]=0X12345678
    1 data[1]=0 data1[1]=0X12345678
    1 data[2]=0 data1[2]=0X12345678
    1 data[3]=0 data1[3]=0X12345678
    2 data[0]=0X78563412 data1[0]=0X12345678
    2 data[1]=0X78563412 data1[1]=0X12345678
    2 data[2]=0X78563412 data1[2]=0X12345678
    2 data[3]=0X78563412 data1[3]=0X12345678

七、VSWP

VSWP(向量交换)交换两个向量的内容。 这两个向量可以是双字或四字向量。 它们使用相同的数据类型。

语法

VSWP{cond}{.datatype} Qd, Qm

VSWP{cond}{.datatype} Dd, Dm

其中:

cond 是一个可选的条件代码。

datatype 是一个可选的数据类型。 汇编程序将忽略 datatype。

Qd、Qm 为四字运算指定向量。

Dd、Dm 为双字运算指定向量。

接下来看一个使用VSWP指令的示例。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0xFFFFFFFF;
        data1[i] = 0x12345678;
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VLDM %0,{q1}\t\n"
            "VSWP q1,q0\t\n"
            "VSTM %0,{q1}\t\n"
            "VSTM %1,{q0}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0","q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

11-01 07:59:33.630 3214-3214/ndk.example.com.ndkexample I/Native: 1 data[0]=0XFFFFFFFF data1[0]=0X12345678
    1 data[1]=0XFFFFFFFF data1[1]=0X12345678
    1 data[2]=0XFFFFFFFF data1[2]=0X12345678
    1 data[3]=0XFFFFFFFF data1[3]=0X12345678
    2 data[0]=0X12345678 data1[0]=0XFFFFFFFF
    2 data[1]=0X12345678 data1[1]=0XFFFFFFFF
    2 data[2]=0X12345678 data1[2]=0XFFFFFFFF
    2 data[3]=0X12345678 data1[3]=0XFFFFFFFF

从结果来看两个寄存器的值已经交换了。

八、VTBL、VTBX

VTBL(向量表查找)使用控制向量中的字节索引在表中查找字节值,并生成一个新的向量。 如果索引超出范围,则返回 0。

VTBX(向量表扩展)的用法与上一指令相同,但索引超出范围时目标元素将保持不变。

语法

Vop{cond}.8 Dd, list, Dm

其中:

op 必须为 TBL 或 TBX。

cond 是一个可选的条件代码。

Dd 指定目标向量。

list 指定包含表的向量。 必须是下列值之一:

• {Dn}

• {Dn,D(n+1)}

• {Dn,D(n+1),D(n+2)}

• {Dn,D(n+1),D(n+2),D(n+3)}。

list 中的所有寄存器都必须位于范围 D0 到 D31 内。

Dm 指定索引向量。

下面是使用VTBL的一个例子。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = 0x87654321;
        data1[i] = 0x12345678;
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VLDM %0,{q1}\t\n"
            "VMOV.I32 q2,#0x00000010\t\n"
            "VMOV.I32 q3,#0x00000200\t\n"
            "VORR q2,q2,q3\t\n"
            "VTBL.8 d6,{d0,d1,d2,d3},d4\t\n"
            "VSTM %0,{q3}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0","q1","q2","q3"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

11-01 08:32:28.430 3359-3359/ndk.example.com.ndkexample I/Native: 1 data[0]=0X87654321 data1[0]=0X12345678
    1 data[1]=0X87654321 data1[1]=0X12345678
    1 data[2]=0X87654321 data1[2]=0X12345678
    1 data[3]=0X87654321 data1[3]=0X12345678
    2 data[0]=0X78783421 data1[0]=0X12345678
    2 data[1]=0X78783421 data1[1]=0X12345678
    2 data[2]=0X200 data1[2]=0X12345678
    2 data[3]=0X200 data1[3]=0X12345678

这里要注意在表中查找的顺序。

九、VTRN

VTRN(向量转置)将其操作数向量的元素视为 2 x 2 矩阵的元素,并对此类矩阵进行转置。

【NEON 和 VFP 编程】NEON通用数据处理指令

语法

VTRN{cond}.size Qd, Qm

VTRN{cond}.size Dd, Dm

其中:

cond 是一个可选的条件代码。

size 必须为 8、16 或 32 之一。

Qd、Qm 为四字运算指定向量。

Dd、Dm 为双字运算指定向量。

首先回顾一下转置矩阵的概念。将矩阵的行列互换得到的新矩阵称为转置矩阵,转置矩阵的行列式不变。

m×nm \times n 矩阵

A=[a11a12a1na21a22a2nam1am2amn]A= \begin{bmatrix} {a_{11}}&{a_{12}}&{\cdots}&{a_{1n}}\\ {a_{21}}&{a_{22}}&{\cdots}&{a_{2n}}\\ {\vdots}&{\vdots}&{\ddots}&{\vdots}\\ {a_{m1}}&{a_{m2}}&{\cdots}&{a_{mn}}\\ \end{bmatrix}

的行列互换之后得到的矩阵,称为 AA 的转置矩阵,记作 ATA^T ,即:

AT=[a11a21am1a12a22am2a1na2namn]A^T= \begin{bmatrix} {a_{11}}&{a_{21}}&{\cdots}&{a_{m1}}\\ {a_{12}}&{a_{22}}&{\cdots}&{a_{m2}}\\ {\vdots}&{\vdots}&{\ddots}&{\vdots}\\ {a_{1n}}&{a_{2n}}&{\cdots}&{a_{mn}}\\ \end{bmatrix}

由定义可知,AAm×nm \times n 矩阵,则 ATA^Tn×mn \times m 矩阵。

示例代码:

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = (unsigned int) (i + 1);
        data1[i] = (unsigned int) (i + 5);
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VLDM %0,{q1}\t\n"
            "VTRN.32 q0,q1\t\n"
            "VSTM %0,{q1}\t\n"
            "VSTM %1,{q0}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0","q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

11-01 16:33:34.830 7256-7256/ndk.example.com.ndkexample I/Native: 1 data[0]=0X1 data1[0]=0X5
    1 data[1]=0X2 data1[1]=0X6
    1 data[2]=0X3 data1[2]=0X7
    1 data[3]=0X4 data1[3]=0X8
    2 data[0]=0X6 data1[0]=0X5
    2 data[1]=0X2 data1[1]=0X1
    2 data[2]=0X8 data1[2]=0X7
    2 data[3]=0X4 data1[3]=0X3

根据运行结果可以看出q0和q1寄存器的值已经改变了,它们组成了两个 2×22 \times 2 矩阵,然后进行了转置。详细地:

[1256]\begin{bmatrix} {1}&{2}\\ {5}&{6}\\ \end{bmatrix}

转置为

[1526]\begin{bmatrix} {1}&{5}\\ {2}&{6}\\ \end{bmatrix}

另一个矩阵

[3478]\begin{bmatrix} {3}&{4}\\ {7}&{8}\\ \end{bmatrix}

转置为

[3748]\begin{bmatrix} {3}&{7}\\ {4}&{8}\\ \end{bmatrix}

十、VUZP、VZIP

VZIP(向量压缩)交叉存取两个向量的元素。

VUZP(向量解压缩)反向交叉存取两个向量的元素。

语法

Vop{cond}.size Qd, Qm

Vop{cond}.size Dd, Dm

其中:

op 必须为 UZP 或 ZIP。

cond 是一个可选的条件代码。

size 必须为 8、16 或 32 之一。

Qd、Qm 为四字运算指定向量。

Dd、Dm 为双字运算指定向量。

注意

下列指令均是等效的:

VZIP.32 Dd, Dm

VUZP.32 Dd, Dm

VTRN.32 Dd, Dm

上述指令将反汇编为 VTRN.32 Dd, Dm。

下面来看一个使用VZIP交叉存取的例子。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = (unsigned int) (i + 1);
        data1[i] = (unsigned int) (i + 5);
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VLDM %0,{q1}\t\n"
            "VZIP.32 q0,q1\t\n"
            "VSTM %0,{q1}\t\n"
            "VSTM %1,{q0}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0","q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

11-01 17:02:40.180 7380-7380/ndk.example.com.ndkexample I/Native: 1 data[0]=0X1 data1[0]=0X5
    1 data[1]=0X2 data1[1]=0X6
    1 data[2]=0X3 data1[2]=0X7
    1 data[3]=0X4 data1[3]=0X8
    2 data[0]=0X7 data1[0]=0X5
    2 data[1]=0X3 data1[1]=0X1
    2 data[2]=0X8 data1[2]=0X6
    2 data[3]=0X4 data1[3]=0X2

详细地说,VZIP将两个输入vector的元素通过交叉生成一个有两个vector的矩阵。A1A_1={1,2,3,4} A2A_2={5,6,7,8},先取5然后取1,接着取6再取2,最后总的数组就是{5,1,6,2,7,3,8,4}。

再来看VUZP指令。

    unsigned int *data = new unsigned int[4];
    unsigned int *data1 = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        data[i] = (unsigned int) (i + 1);
        data1[i] = (unsigned int) (i + 5);
        LOGI("1 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

    asm volatile(
            "VLDM %1,{q0}\t\n"
            "VLDM %0,{q1}\t\n"
            "VUZP.32 q0,q1\t\n"
            "VSTM %0,{q1}\t\n"
            "VSTM %1,{q0}\t\n"
    :"+r"(data),//%0
    "+r"(data1) //%1
    :
    : "memory", "q0","q1"
    );

    for (int i = 0; i < 4; i++) {
        LOGI("2 data[%d]=%0#X data1[%d]=%0#X", i, data[i], i, data1[i]);
    }

运行结果:

11-01 17:31:54.780 7500-7500/? I/Native: 1 data[0]=0X1 data1[0]=0X5
    1 data[1]=0X2 data1[1]=0X6
    1 data[2]=0X3 data1[2]=0X7
    1 data[3]=0X4 data1[3]=0X8
    2 data[0]=0X6 data1[0]=0X5
    2 data[1]=0X8 data1[1]=0X7
    2 data[2]=0X2 data1[2]=0X1
    2 data[3]=0X4 data1[3]=0X3

详细地说,VUZP将两个输入vector的元素通过反交叉生成一个有两个vector的矩阵。A1A_1={1,2,3,4} A2A_2={5,6,7,8},先取5然后取7,接着取1再取3,最后总的数组就是{5,7,1,3,6,8,2,4}。