【ARMv8 SIMD和浮点指令编程】NEON 加法指令——加法都能玩出花

news2024/12/26 13:07:14

向量加法包括常见的普通加指令,还包括长加、宽加、半加、饱和加、按对加、按对加并累加、选择高半部分结果加、全部元素加等。如果你和我一开始以为的只有一种普通加,那就太小看设计者了!同时这么多加法指令的确会提升我们设计程序的效率,同样学习这些指令也需要花费不少精力。

1 ADD

加(向量),该指令将两个源 SIMD&FP 寄存器中的相应元素相加,将结果放入向量中,并将向量写入目标 SIMD&FP 寄存器。

标量

在这里插入图片描述

ADD <V><d>, <V><n>, <V><m>

向量

在这里插入图片描述

ADD <Vd>.<T>, <Vn>.<T>, <Vm>.<T>

<V> 是宽度说明符,以“size”编码:

size<V>
0xRESERVED
10RESERVED
11D

<d> 是 SIMD&FP 目标寄存器的编号,在“Rd”字段中。

<n> 是第一个 SIMD&FP 源寄存器的编号,编码在“Rn”字段中。

<m> 是第二个 SIMD&FP 源寄存器的编号,编码在“Rm”字段中。

<Vd> 是 SIMD&FP 目标寄存器的名称,在“Rd”字段中编码。

<T> 是排列说明符,编码为“size:Q”:

sizeQ<T>
0008B
00116B
0104H
0118H
1002S
1014S
110RESERVED
1112D

<Vn> 是第一个 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<Vm> 是第二个 SIMD&FP 源寄存器的名称,在“Rm”字段中编码。

下面是使用 ADD 指令的例子。

    auto *srcArr = new int[4];

    for (int i = 0; i < 4; i++) {
        srcArr[i] = 100 * (i + 1);
    }

    char *src = (char *) srcArr;
    LOGD("in srcArr: %d %d %d %d", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);

    asm volatile(
        "LD1 {v0.4S}, [%[src]]\n"
        "ADD v1.4S, v0.4S, v0.4S\n"
        "ST1 {v1.4S}, [%[src]]\n"
    :[src] "+r"(src)
    :
    : "cc", "memory", "v0");

    LOGD("-----------------------------");
    LOGD("out srcArr: %d %d %d %d", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);

    delete[] srcArr;

ADD v1.4S, v0.4S, v0.4S 将 v0 寄存器 S 通道的值加上 v0 寄存器 S 通道的值的结果写入 v1 的 S 通道。

运行结果:

2023-05-20 14:58:44.906 5306-5306/com.example.myapplication D/native-armv8a: in srcArr: 100 200 300 400
2023-05-20 14:58:44.906 5306-5306/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-20 14:58:44.906 5306-5306/com.example.myapplication D/native-armv8a: out srcArr: 200 400 600 800

2 UADDL/UADDL2

无符号长加(向量),该指令将第一个源 SIMD&FP 寄存器的下半部分或上半部分中的每个向量元素与第二个源 SIMD&FP 寄存器的相应向量元素相加,将结果放入向量中,并将向量写入目标 SIMD&FP 寄存器。目标向量元素的长度是源向量元素的两倍。该指令中的所有值都是无符号整数值。

UADDL 指令从每个源寄存器的下半部分提取每个源向量。UADDL2 指令从每个源寄存器的上半部分提取每个源向量。

在这里插入图片描述

UADDL{2} <Vd>.<Ta>, <Vn>.<Tb>, <Vm>.<Tb>

2 是第二个和上半部分说明符。如果存在,它会导致对保存较窄元素的寄存器的高 64 位执行操作,并以“Q”编码:

Q2
0[absent]
1[present]

<Vd> 是 SIMD&FP 目标寄存器的名称,在“Rd”字段中编码。

<Ta> 是排列说明符,以“size”编码:

size<Ta>
008H
014S
102D
11RESERVED

<Vn> 是第一个 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<Tb> 是排列说明符,编码为“size:Q”:

sizeQ<T>
0008B
00116B
0104H
0118H
1002S
1014S
11xRESERVED

<Vm> 是第二个 SIMD&FP 源寄存器的名称,在“Rm”字段中编码。

下面是使用 UADDL/UADDL2 指令的例子。

    auto *srcArr = new unsigned int[4];
    auto *outArr = new unsigned long long int[4]{0};

    for (int i = 0; i < 4; i++) {
        srcArr[i] = 100 * (i + 1);
    }

    char *src = (char *) srcArr;
    char *dst = (char *) outArr;
    LOGD("in srcArr: %u %u %u %u", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);

    asm volatile(
        "LD1 {v0.4S}, [%[src]]\n"
        "UADDL v1.2D, v0.2S, v0.2S\n"
        "UADDL2 v2.2D, v0.4S, v0.4S\n"
        "ST1 {v1.2D, v2.2D}, [%[dst]]\n"
    :[src] "+r"(src),
     [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1", "v2");

    LOGD("-----------------------------");
    LOGD("out outArr: %llu %llu %llu %llu", outArr[0], outArr[1], outArr[2], outArr[3]);

    delete[] srcArr;
    delete[] outArr;

UADDL v1.2D, v0.2S, v0.2S 将 v0 寄存器低半部分两个 S 通道的值加上 v0 寄存器低半部分两个 S 通道的值,并将结果写入 v1 寄存器的两个 D 通道。

UADDL2 v2.2D, v0.4S, v0.4S 将 v0 寄存器高半部分两个 S 通道的值加上 v0 寄存器高半部分两个 S 通道的值,并将结果写入 v2 寄存器的两个 D 通道。

运行结果:

2023-05-20 15:23:48.145 9806-9806/com.example.myapplication D/native-armv8a: in srcArr: 100 200 300 400
2023-05-20 15:23:48.145 9806-9806/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-20 15:23:48.145 9806-9806/com.example.myapplication D/native-armv8a: out outArr: 200 400 600 800

3 UADDW/UADDW2

无符号宽加,该指令将第一个源 SIMD&FP 寄存器的向量元素与第二个源 SIMD&FP 寄存器的下半部分或上半部分中相应的向量元素相加,将结果放入向量中,并将向量写入 SIMD&FP 目标寄存器。目标寄存器和第一个源寄存器的向量元素是第二个源寄存器的向量元素的两倍长。该指令中的所有值都是无符号整数值。

UADDW 指令从第二个源寄存器的下半部分提取向量元素。UADDW2 指令从第二个源寄存器的上半部分提取向量元素。

在这里插入图片描述

UADDW{2} <Vd>.<Ta>, <Vn>.<Ta>, <Vm>.<Tb>

2 是第二个和上半部分说明符。如果存在,它会导致对保存较窄元素的寄存器的高 64 位执行操作,并以“Q”编码:

Q2
0[absent]
1[present]

<Vd> 是 SIMD&FP 目标寄存器的名称,在“Rd”字段中编码。

<Ta> 是排列说明符,以“size”编码:

size<Ta>
008H
014S
102D
11RESERVED

<Vn> 是第一个 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<Vm> 是第二个 SIMD&FP 源寄存器的名称,在“Rm”字段中编码。

<Tb> 是排列说明符,编码为“size:Q”:

sizeQ<T>
0008B
00116B
0104H
0118H
1002S
1014S
11xRESERVED

下面是使用 UADDW/UADDW2 指令的例子。

    auto *srcArr = new unsigned int[4];
    auto *outArr = new unsigned long long int[4]{0};

    for (int i = 0; i < 4; i++) {
        srcArr[i] = 100 * (i + 1);
        outArr[i] = 10 * (i + 1);
    }

    char *src = (char *) srcArr;
    char *dst = (char *) outArr;
    LOGD("in srcArr: %u %u %u %u", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in outArr: %llu %llu %llu %llu", outArr[0], outArr[1], outArr[2], outArr[3]);

    asm volatile(
        "LD1 {v0.4S}, [%[src]]\n"
        "LD1 {v1.2D, v2.2D}, [%[dst]]\n"
        "UADDW v3.2D, v1.2D, v0.2S\n"
        "UADDW2 v4.2D, v2.2D, v0.4S\n"
        "ST1 {v3.2D, v4.2D}, [%[dst]]\n"
    :[src] "+r"(src),
     [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1", "v2", "v3", "v4");

    LOGD("-----------------------------");
    LOGD("out outArr: %llu %llu %llu %llu", outArr[0], outArr[1], outArr[2], outArr[3]);

    delete[] srcArr;
    delete[] outArr;

UADDW v3.2D, v1.2D, v0.2S 将 v0 寄存器低半部分两个 S 通道的值加上 v1 寄存器的两个 D 通道的值,并将结果写入 v3 寄存器的两个 D 通道。

UADDW2 v4.2D, v2.2D, v0.4S 将 v0 寄存器高半部分两个 S 通道的值加上 v2 寄存器的两个 D 通道的值,并将结果写入 v4 寄存器的两个 D 通道。

在这里插入图片描述

运行结果:

2023-05-20 15:56:42.446 1618-1618/com.example.myapplication D/native-armv8a: in srcArr: 100 200 300 400
2023-05-20 15:56:42.446 1618-1618/com.example.myapplication D/native-armv8a: in outArr: 10 20 30 40
2023-05-20 15:56:42.446 1618-1618/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-20 15:56:42.446 1618-1618/com.example.myapplication D/native-armv8a: out outArr: 110 220 330 440

4 UQADD

无符号饱和加法,该指令将两个源 SIMD&FP 寄存器的相应元素的值相加,将结果放入向量中,并将向量写入目标 SIMD&FP 寄存器。如果任何结果发生溢出,这些结果就会饱和。如果发生饱和,则设置累积饱和位 FPSR.QC。

标量

在这里插入图片描述

UQADD <V><d>, <V><n>, <V><m>

向量

在这里插入图片描述

UQADD <Vd>.<T>, <Vn>.<T>, <Vm>.<T>

<V> 是宽度说明符,以“size”编码:

size<V>
00B
01H
10S
11D

<d> 是 SIMD&FP 目标寄存器的编号,在“Rd”字段中。

<n> 是第一个 SIMD&FP 源寄存器的编号,编码在“Rn”字段中。

<m> 是第二个 SIMD&FP 源寄存器的编号,编码在“Rm”字段中。

<Vd> 是 SIMD&FP 目标寄存器的名称,在“Rd”字段中编码。

<T> 是排列说明符,编码为“size:Q”:

sizeQ<T>
0008B
00116B
0104H
0118H
1002S
1014S
110RESERVED
1112D

<Vn> 是第一个 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<Vm> 是第二个 SIMD&FP 源寄存器的名称,在“Rm”字段中编码。

下面是使用 UQADD 指令的例子。

    auto *srcArr = new unsigned int[4];
    auto *outArr = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        srcArr[i] = 0xFFFFFFFF;
        outArr[i] = i;
    }

    char *src = (char *) srcArr;
    char *dst = (char *) outArr;
    LOGD("in srcArr: %u %u %u %u", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in outArr: %u %u %u %u", outArr[0], outArr[1], outArr[2], outArr[3]);

    asm volatile(
        "LD1 {v0.4S}, [%[src]]\n"
        "LD1 {v1.4S}, [%[dst]]\n"
        "UQADD v2.4S, v0.4S, v1.4S\n"
        "ST1 {v2.4S}, [%[dst]]\n"
    :[src] "+r"(src),
     [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1", "v2");

    LOGD("-----------------------------");
    LOGD("out outArr: %u %u %u %u", outArr[0], outArr[1], outArr[2], outArr[3]);

    delete[] srcArr;
    delete[] outArr;

运行过程中加入断点可查到 FPSR.QC 已经被置为 1(从低到高第 27 位(低位从 0 开始))。

在这里插入图片描述

运行结果:

2023-05-20 16:57:15.332 8522-8522/com.example.myapplication D/native-armv8a: in srcArr: 4294967295 4294967295 4294967295 4294967295
2023-05-20 16:57:15.332 8522-8522/com.example.myapplication D/native-armv8a: in outArr: 0 1 2 3
2023-05-20 16:57:15.332 8522-8522/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-20 16:57:15.332 8522-8522/com.example.myapplication D/native-armv8a: out outArr: 4294967295 4294967295 4294967295 4294967295

5 ADDHN/ADDHN2

Add returning High Narrow,该指令将第一个源 SIMD&FP 寄存器中的每个向量元素加上第二个源 SIMD&FP 寄存器中的相应向量元素,将结果的最高有效一半放入向量中,并将向量写入目标 SIMD&FP 寄存器的下半部分或上半部分。结果被截断,有关四舍五入的结果,可使用 RADDHN、RADDHN2。

ADDHN 指令将向量写入目标寄存器的下半部分并清除上半部分,而 ADDHN2 指令将向量写入目标寄存器的上半部分而不影响寄存器的其它位。

在这里插入图片描述

ADDHN{2} <Vd>.<Tb>, <Vn>.<Ta>, <Vm>.<Ta>

2 是第二个和上半部分说明符。如果存在,它会导致对保存较窄元素的寄存器的高 64 位执行操作,并以“Q”编码:

Q2
0[absent]
1[present]

<Vd> 是 SIMD&FP 目标寄存器的名称,在“Rd”字段中编码。

<Ta> 是排列说明符,以“size”编码:

size<Ta>
008H
014S
102D
11RESERVED

<Vn> 是第一个 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<Vm> 是第二个 SIMD&FP 源寄存器的名称,在“Rm”字段中编码。

<Tb> 是排列说明符,编码为“size:Q”:

sizeQ<T>
0008B
00116B
0104H
0118H
1002S
1014S
11xRESERVED

下面是使用 ADDHN/ADDHN2 指令的例子。

    auto *srcArr = new unsigned long long int[4];
    auto *outArr = new unsigned int[4]{0};

    for (int i = 0; i < 4; i++) {
        srcArr[i] = 0x10010000000 * (i + 1);
    }

    char *src = (char *) srcArr;
    char *dst = (char *) outArr;
    LOGD("in srcArr: 0x%llx 0x%llx 0x%llx 0x%llx", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in outArr: 0x%x 0x%x 0x%x 0x%x", outArr[0], outArr[1], outArr[2], outArr[3]);

    asm volatile(
        "LD1 {v0.2D, v1.2D}, [%[src]]\n"
        "ADDHN v2.2S, v0.2D, v0.2D\n"
        "ADDHN2 v2.4S, v1.2D, v1.2D\n"
        "ST1 {v2.4S}, [%[dst]]\n"
    :[src] "+r"(src),
     [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1", "v2");

    LOGD("-----------------------------");
    LOGD("out outArr: 0x%x 0x%x 0x%x 0x%x", outArr[0], outArr[1], outArr[2], outArr[3]);

    delete[] srcArr;
    delete[] outArr;

ADDHN v2.2S, v0.2D, v0.2D 将 v0 的两个 D 通道的值加上 v0 的两个 D 通道的值,接着取结果的高位(也就是 D 通道的高 S 通道部分)写入 v2 的两个低 S 通道,v2 寄存器高 S 通道全部清零。

ADDHN2 v2.4S, v1.2D, v1.2D 将 v1 的两个 D 通道的值加上 v1 的两个 D 通道的值,接着取结果的高位(也就是 D 通道的高 S 通道部分)写入 v2 的两个高 S 通道,并保持 v2 其它位不变。

在这里插入图片描述

运行结果:

2023-05-21 11:16:56.576 6664-6664/com.example.myapplication D/native-armv8a: in srcArr: 0x10010000000 0x20020000000 0x30030000000 0x40040000000
2023-05-21 11:16:56.576 6664-6664/com.example.myapplication D/native-armv8a: in outArr: 0x0 0x0 0x0 0x0
2023-05-21 11:16:56.576 6664-6664/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-21 11:16:56.576 6664-6664/com.example.myapplication D/native-armv8a: out outArr: 0x200 0x400 0x600 0x800

6 RADDHN/RADDHN2

和 ADDHN/ADDHN2 的唯一区别在于对结果进行了四舍五入,而非截断。

下面是使用 RADDHN/RADDHN2 指令的例子。使用 ADDHN/ADDHN2 的例子将命令替换为 RADDHN/RADDHN2 即可。

    auto *srcArr = new unsigned long long int[4];
    auto *outArr = new unsigned int[4]{0};

    for (int i = 0; i < 4; i++) {
        srcArr[i] = 0x10010000000 * (i + 1);
    }

    char *src = (char *) srcArr;
    char *dst = (char *) outArr;
    LOGD("in srcArr: 0x%llx 0x%llx 0x%llx 0x%llx", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in outArr: 0x%x 0x%x 0x%x 0x%x", outArr[0], outArr[1], outArr[2], outArr[3]);

    asm volatile(
        "LD1 {v0.2D, v1.2D}, [%[src]]\n"
        "RADDHN v2.2S, v0.2D, v0.2D\n"
        "RADDHN2 v2.4S, v1.2D, v1.2D\n"
        "ST1 {v2.4S}, [%[dst]]\n"
    :[src] "+r"(src),
     [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1", "v2");

    LOGD("-----------------------------");
    LOGD("out outArr: 0x%x 0x%x 0x%x 0x%x", outArr[0], outArr[1], outArr[2], outArr[3]);

    delete[] srcArr;
    delete[] outArr;

从运行结果来看,最后出现了 0x801 这个值,这是因为 0x40040000000 + 0x40040000000 = 0x80080000000,提取高 4 个字节的时候同时四舍五入进位了。

运行结果:

2023-05-21 11:11:08.096 20999-20999/com.example.myapplication D/native-armv8a: in srcArr: 0x10010000000 0x20020000000 0x30030000000 0x40040000000
2023-05-21 11:11:08.096 20999-20999/com.example.myapplication D/native-armv8a: in outArr: 0x0 0x0 0x0 0x0
2023-05-21 11:11:08.096 20999-20999/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-21 11:11:08.096 20999-20999/com.example.myapplication D/native-armv8a: out outArr: 0x200 0x400 0x600 0x801

7 UHADD

无符号半加,该指令将两个源 SIMD&FP 寄存器中相应的无符号整数值相加,将每个结果右移一位,将结果放入向量中,并将向量写入目标 SIMD&FP 寄存器。结果被截断。有关四舍五入的结果,请使用 URHADD。

在这里插入图片描述

UHADD <Vd>.<T>, <Vn>.<T>, <Vm>.<T>

<Vd> 是 SIMD&FP 目标寄存器的名称,在“Rd”字段中编码。

<T> 是排列说明符,编码在“size:Q”字段中。它可以具有以下值:

sizeQ<T>
0008B
00116B
0104H
0118H
1002S
1014S
11xRESERVED

<Vn> 是第一个 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<Vm> 是第二个 SIMD&FP 源寄存器的名称,在“Rm”字段中编码。

下面是使用 UHADD 指令的例子。

    auto *srcArr = new unsigned int[4];
    auto *outArr = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        srcArr[i] = 0x01020304 * (i + 1);
        outArr[i] = 0x10203040 * (i + 1);
    }

    char *src = (char *) srcArr;
    char *dst = (char *) outArr;
    LOGD("in srcArr: 0x%x 0x%x 0x%x 0x%x", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in outArr: 0x%x 0x%x 0x%x 0x%x", outArr[0], outArr[1], outArr[2], outArr[3]);

    asm volatile(
        "LD1 {v0.4S}, [%[src]]\n"
        "LD1 {v1.4S}, [%[dst]]\n"
        "UHADD v2.4S, v0.4S, v1.4S\n"
        "ST1 {v2.4S}, [%[dst]]\n"
    :[src] "+r"(src),
     [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1", "v2");

    LOGD("-----------------------------");
    LOGD("out outArr: 0x%x 0x%x 0x%x 0x%x", outArr[0], outArr[1], outArr[2], outArr[3]);

    delete[] srcArr;
    delete[] outArr;

我们计算一组数值:0x1020304 + 0x10203040 = 0x11223344 = 0b00010001 00100010 00110011 01000100,右移一位后即 0b00001000 10010001 00011001 10100010 = 0x089119A2。

运行结果:

2023-05-21 14:59:44.900 13393-13393/com.example.myapplication D/native-armv8a: in srcArr: 0x1020304 0x2040608 0x306090c 0x4080c10
2023-05-21 14:59:44.900 13393-13393/com.example.myapplication D/native-armv8a: in outArr: 0x10203040 0x20406080 0x306090c0 0x4080c100
2023-05-21 14:59:44.900 13393-13393/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-21 14:59:44.900 13393-13393/com.example.myapplication D/native-armv8a: out outArr: 0x89119a2 0x11223344 0x19b34ce6 0x22446688

8 URHADD

和 UHADD 的唯一区别在于对结果进行了四舍五入,而非截断。

笔者实际没有琢磨出设计这条指令的意义,就比如说 S 通道相加,最大的无符号数是 0xFFFFFFFF,如果 0xFFFFFFFF + 0xFFFFFFFF,也就是两倍的 0xFFFFFFFF,对这个结果右移 1 位(相当于除以 2),还是 0xFFFFFFFF,根本就无法触及四舍五入的操作!

9 ADDP

标量

加一对元素,该指令将源 SIMD&FP 寄存器中的两个向量元素相加,并将结果写入目标 SIMD&FP 寄存器。

在这里插入图片描述

ADDP <V><d>, <Vn>.<T>

<V> 是宽度说明符,以“size”编码:

size<V>
0xRESERVED
10RESERVED
11D

<d> 是 SIMD&FP 目标寄存器的编号,在“Rd”字段中。

<Vn> 是 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<T> 是排列说明符,编码为“size”:

size<V>
0xRESERVED
10RESERVED
11D

向量

该指令通过将第一个源 SIMD&FP 寄存器的向量元素连接在第二个源 SIMD&FP 寄存器的向量元素之后创建一个向量,从连接的向量中读取每对相邻的向量元素,将每对值加在一起,将结果放入 向量,并将向量写入目标 SIMD&FP 寄存器。

在这里插入图片描述

ADDP <Vd>.<T>, <Vn>.<T>, <Vm>.<T>

<Vd> 是 SIMD&FP 目标寄存器的名称,在“Rd”字段中编码。

<T> 是排列说明符,编码为“size:Q”:

sizeQ<T>
0008B
00116B
0104H
0118H
1002S
1014S
110RESERVED
1112D

<Vn> 是第一个 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<Vm> 是第二个 SIMD&FP 源寄存器的名称,在“Rm”字段中编码。

下面是使用 ADDP 指令的例子。

    auto *srcArr = new unsigned int[4];
    auto *outArr = new unsigned int[4];

    for (int i = 0; i < 4; i++) {
        srcArr[i] = i + 1;
        outArr[i] = (i + 1) * 2;
    }

    char *src = (char *) srcArr;
    char *dst = (char *) outArr;
    LOGD("in srcArr: %u %u %u %u", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in outArr: %u %u %u %u", outArr[0], outArr[1], outArr[2], outArr[3]);

    asm volatile(
        "LD1 {v0.4S}, [%[src]]\n"
        "LD1 {v1.4S}, [%[dst]]\n"
        "ADDP v2.4S, v0.4S, v1.4S\n"
        "ST1 {v2.4S}, [%[dst]]\n"
    :[src] "+r"(src),
     [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1", "v2");

    LOGD("-----------------------------");
    LOGD("out outArr: %u %u %u %u", outArr[0], outArr[1], outArr[2], outArr[3]);

    delete[] srcArr;
    delete[] outArr;

ADDP v2.4S, v0.4S, v1.4S 将 v1 寄存器的 S 通道连接到 v0 寄存器之后形成一个新的向量,从这个新的向量中按对加,最后将结果写入 v2 的 S 通道。

运行结果:

2023-05-21 16:16:33.270 11613-11613/com.example.myapplication D/native-armv8a: in srcArr: 1 2 3 4
2023-05-21 16:16:33.270 11613-11613/com.example.myapplication D/native-armv8a: in outArr: 2 4 6 8
2023-05-21 16:16:33.270 11613-11613/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-21 16:16:33.270 11613-11613/com.example.myapplication D/native-armv8a: out outArr: 3 7 6 14

10 UADDLP

Unsigned Add Long Pairwise,该指令将源 SIMD&FP 寄存器中向量中的相邻无符号整数值对相加,将结果放入向量中,并将向量写入目标 SIMD&FP 寄存器。目标向量元素的长度是源向量元素的两倍。

在这里插入图片描述

UADDLP <Vd>.<Ta>, <Vn>.<Tb>

<Vd> 是 SIMD&FP 目标寄存器的名称,在“Rd”字段中编码。

<Ta> 是排列说明符,编码在“size:Q”字段中。 它可以具有以下值:

sizeQ<Ta>
0004H
0018H
0102S
0114S
1001D
1012D
11xRESERVED

<Vn> 是 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<Tb> 是排列说明符,编码在“size:Q”字段中。 它可以具有以下值:

sizeQ<Tb>
0008B
00116B
0104H
0118H
1002S
1014S
11xRESERVED

下面是使用 UADDLP 指令的例子。

    auto *srcArr = new unsigned int[4];
    auto *outArr = new unsigned long long int[2]{0};

    for (int i = 0; i < 4; i++) {
        srcArr[i] = i + 1;
    }

    char *src = (char *) srcArr;
    char *dst = (char *) outArr;
    LOGD("in srcArr: %u %u %u %u", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in outArr: %llu %llu", outArr[0], outArr[1]);

    asm volatile(
        "LD1 {v0.4S}, [%[src]]\n"
        "UADDLP v1.2D, v0.4S\n"
        "ST1 {v1.2D}, [%[dst]]\n"
    :[src] "+r"(src),
     [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1");

    LOGD("-----------------------------");
    LOGD("out outArr: %llu %llu", outArr[0], outArr[1]);

    delete[] srcArr;
    delete[] outArr;

UADDLP v1.2D, v0.4S 将 v0 寄存器的 S 通道按对加,最后将结果写入 v1 的两个 D 通道。

运行结果:

2023-05-21 16:34:07.706 1951-1951/com.example.myapplication D/native-armv8a: in srcArr: 1 2 3 4
2023-05-21 16:34:07.706 1951-1951/com.example.myapplication D/native-armv8a: in outArr: 0 0
2023-05-21 16:34:07.706 1951-1951/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-21 16:34:07.706 1951-1951/com.example.myapplication D/native-armv8a: out outArr: 3 7

11 UADALP

Unsigned Add and Accumulate Long Pairwise,该指令将源 SIMD&FP 寄存器中向量中的相邻无符号整数值对相加,并将结果与目标 SIMD&FP 寄存器的向量元素累加。目标向量元素的长度是源向量元素的两倍。

在这里插入图片描述

UADALP <Vd>.<Ta>, <Vn>.<Tb>

<Vd> 是 SIMD&FP 目标寄存器的名称,在“Rd”字段中编码。

<Ta> 是排列说明符,编码在“size:Q”字段中。 它可以具有以下值:

sizeQ<Ta>
0004H
0018H
0102S
0114S
1001D
1012D
11xRESERVED

<Vn> 是 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<Tb> 是排列说明符,编码在“size:Q”字段中。 它可以具有以下值:

sizeQ<Tb>
0008B
00116B
0104H
0118H
1002S
1014S
11xRESERVED

下面是使用 UADALP 指令的例子。

    auto *srcArr = new unsigned int[4];
    auto *outArr = new unsigned long long int[2]{0};

    for (int i = 0; i < 4; i++) {
        srcArr[i] = i + 1;
    }
    
    outArr[0] = 1;
    outArr[1] = 2;

    char *src = (char *) srcArr;
    char *dst = (char *) outArr;
    LOGD("in srcArr: %u %u %u %u", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in outArr: %llu %llu", outArr[0], outArr[1]);

    asm volatile(
        "LD1 {v0.4S}, [%[src]]\n"
        "LD1 {v1.2D}, [%[dst]]\n"
        "UADALP v1.2D, v0.4S\n"
        "ST1 {v1.2D}, [%[dst]]\n"
    :[src] "+r"(src),
     [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1");

    LOGD("-----------------------------");
    LOGD("out outArr: %llu %llu", outArr[0], outArr[1]);

    delete[] srcArr;
    delete[] outArr;

UADALP v1.2D, v0.4S 将 v0 寄存器的 S 通道按对加,得到按对加的结果后,将其分别累加到 v1 的两个 D 通道。

运行结果:

2023-05-21 16:45:00.687 4754-4754/com.example.myapplication D/native-armv8a: in srcArr: 1 2 3 4
2023-05-21 16:45:00.687 4754-4754/com.example.myapplication D/native-armv8a: in outArr: 1 2
2023-05-21 16:45:00.687 4754-4754/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-21 16:45:00.687 4754-4754/com.example.myapplication D/native-armv8a: out outArr: 4 9

12 ADDV

Add across Vector,该指令将源 SIMD&FP 寄存器中的每个向量元素相加,并将标量结果写入目标 SIMD&FP 寄存器。

在这里插入图片描述

ADDV <V><d>, <Vn>.<T>

<V> 是目标宽度说明符,在“size”字段中编码。它可以具有以下值:

Vsize
B00
H01
S10
RESERVED11

<d> 是 SIMD&FP 目标寄存器的编号,在“Rd”字段中编码。

<Vn> 是 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<T> 是排列说明符,编码在“size:Q”字段中。 它可以具有以下值:

sizeQT
0008B
00116B
0104H
0118H
100RESERVED
1014S
11xRESERVED

下面是使用 ADDV 指令的例子。

    auto *srcArr = new unsigned int[4];
    unsigned int dst = 0;

    for (int i = 0; i < 4; i++) {
        srcArr[i] = i + 1;
    }

    char *src = (char *) srcArr;
    LOGD("in srcArr: %u %u %u %u", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in dst: %u", dst);

    asm volatile(
    "LD1 {v0.4S}, [%[src]]\n"
    "ADDV S1, v0.4S\n"
    "MOV %w[dst], v1.S[0]\n"
    :[src] "+r"(src),
    [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1");

    LOGD("-----------------------------");
    LOGD("out dst: %u", dst);

    delete[] srcArr;

ADDV S1, v0.4S 将 v0 寄存器的 S 通道值全部相加,并将得到的结果写入 S1 寄存器。

运行结果:

2023-05-21 16:59:39.237 18281-18281/com.example.myapplication D/native-armv8a: in srcArr: 1 2 3 4
2023-05-21 16:59:39.237 18281-18281/com.example.myapplication D/native-armv8a: in dst: 0
2023-05-21 16:59:39.237 18281-18281/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-21 16:59:39.237 18281-18281/com.example.myapplication D/native-armv8a: out dst: 10

13 UADDLV

Unsigned sum Long across Vector,该指令将源 SIMD&FP 寄存器中的每个向量元素相加,并将标量结果写入目标 SIMD&FP 寄存器。目标标量是源向量元素的两倍长。该指令中的所有值都是无符号整数值。

在这里插入图片描述

UADDLV <V><d>, <Vn>.<T>

<V> 是目标宽度说明符,在“size”字段中编码。它可以具有以下值:

Vsize
H00
S01
D10
RESERVED11

<d> 是 SIMD&FP 目标寄存器的编号,在“Rd”字段中编码。

<Vn> 是 SIMD&FP 源寄存器的名称,在“Rn”字段中编码。

<T> 是排列说明符,编码在“size:Q”字段中。 它可以具有以下值:

sizeQT
0008B
00116B
0104H
0118H
100RESERVED
1014S
11xRESERVED

下面是使用 UADDLV 指令的例子。

    auto *srcArr = new unsigned int[4];
    unsigned long long int dst = 0;

    for (int i = 0; i < 4; i++) {
        srcArr[i] = i + 1;
    }

    char *src = (char *) srcArr;
    LOGD("in srcArr: %u %u %u %u", srcArr[0], srcArr[1], srcArr[2], srcArr[3]);
    LOGD("in dst: %llu", dst);

    asm volatile(
    "LD1 {v0.4S}, [%[src]]\n"
    "UADDLV D1, v0.4S\n"
    "MOV %[dst], v1.D[0]\n"
    :[src] "+r"(src),
    [dst] "+r"(dst)
    :
    : "cc", "memory", "v0", "v1");

    LOGD("-----------------------------");
    LOGD("out dst: %llu", dst);

    delete[] srcArr;

UADDLV D1, v0.4S 将 v0 寄存器的 S 通道值全部相加,并将得到的结果写入 D1 寄存器。

运行结果:

2023-05-21 17:06:17.782 6272-6272/com.example.myapplication D/native-armv8a: in srcArr: 1 2 3 4
2023-05-21 17:06:17.782 6272-6272/com.example.myapplication D/native-armv8a: in dst: 0
2023-05-21 17:06:17.782 6272-6272/com.example.myapplication D/native-armv8a: -----------------------------
2023-05-21 17:06:17.782 6272-6272/com.example.myapplication D/native-armv8a: out dst: 10

14 其它

上面很多加法指令都是基于无符号数的,实际上它们也有对应的有符号数版本,就不再一一展开了。

参考资料

1.《ARMv8-A-Programmer-Guide》
2.《Arm® A64 Instruction Set Architecture Armv8, for Armv8-A architecture profile》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/603904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

numpy包中的编码函数和解码函数numpy.char.encode() numpy.char.decode()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 numpy包中的编码函数和解码函数 numpy.char.encode() numpy.char.decode() [太阳]选择题 下列代码最后输出的结果是&#xff1f; import numpy as np x np.array([I, Love, Python]) print(…

Python jieba库

前言 Jieba库是优秀的中文分词第三方库&#xff0c;中文文本需要通过分词获得单个的词语。 Jieba库的分词原理&#xff1a;利用一个中文词库&#xff0c;确定汉字之间的关联概率&#xff0c;汉字间概率大的组成词组&#xff0c;形成分词结果。除了分词&#xff0c;用户还可以…

【数据库原理与应用 - 第八章】数据库的事务管理与并发控制

目录 一、事务管理 1、概念及特性 2、事务控制 &#xff08;1&#xff09;事务控制语句 显示事务举例 二、并发控制 1、问题引入 2、并发执行带来的问题 &#xff08;1&#xff09;丢失修改 &#xff08;2&#xff09;不可重复读 &#xff08;3&#xff09;读"…

[自学记录02|百人计划]纹理压缩

一、什么是纹理压缩 纹理压缩是为了解决内存、带宽问题&#xff0c;专为在计算机图形渲染系统中存储纹理而使用的图像压缩技术。 1.图片格式和纹理格式的区别 (1)图片格式 图片格式是图片文件的存储格式&#xff0c;通常在磁盘、内存中储存和传输文件时使用&#xff1b;例如…

单片机GD32F303RCT6 (Macos环境)开发 (三十三)—— 光照传感器 (BH1750)

GD32 光照传感器 BH1750的使用 1、GPIO模拟i2c配置 使用管脚为SCL PB10 SDA PB11&#xff0c;移植代码时可换自己的管脚。软件模拟i2c在十九章中讲过&#xff0c;与其不同的地方是&#xff0c;这里的us延时函数&#xff0c;换成了定时器3做us级的延时。 tim3的配置&#xf…

linux 找回root密码(CentOS7.6)

linux 找回root密码(CentOS7.6) 首先&#xff0c;启动系统&#xff0c;进入开机界面&#xff0c;在界面中按“e”进入编辑界面。如图 2. 进入编辑界面&#xff0c;使用键盘上的上下键把光标往下移动&#xff0c;找到以““Linux16”开头内容所在的行数”&#xff0c;在行的最后…

java-字符流和字节流(二)

java-字符流和字节流(二) 一、字节缓冲流 1.1字节缓冲流构造方法 字节缓冲流介绍 BufferOutputStream&#xff1a;该类实现缓冲输出流。 通过设置这样的输出流&#xff0c;应用程序可以向底层输出流写入字节&#xff0c;而不必为写入的每个字节导致底层系统的调用 BufferedIn…

chatgpt赋能python:Python动图如何优化SEO?

Python动图如何优化SEO&#xff1f; Python是一种高级编程语言&#xff0c;广泛应用于数据分析、人工智能和网站开发等领域。Python还支持创建动态图像&#xff0c;这些动态图像通常用于数据可视化、演示和教育目的。在本文中&#xff0c;我们将探讨如何使用Python创建动态图像…

chatgpt赋能python:Python加f之SEO的重要性

Python加f之SEO的重要性 随着互联网的不断发展和普及&#xff0c;越来越多的企业和个人纷纷进入到了网站建设&#xff0c;网络营销的大军之中。而SEO作为重要的一环&#xff0c;在各个领域内也变得愈加重要。而Python中的f字符串是近些年来引起广泛关注的一种新的字符串格式化…

chatgpt赋能python:Python动态内存分配:如何优化你的代码

Python动态内存分配&#xff1a;如何优化你的代码 在编写Python代码时&#xff0c;你可能已经注意到内存使用方面的一些问题。Python动态内存分配是一个重要的话题&#xff0c;它涉及到Python程序如何在运行时使用内存。本文将向您介绍Python动态内存分配的基本概念和如何优化…

chatgpt赋能python:Python动态代码在SEO中的重要性

Python动态代码在SEO中的重要性 Python是一种非常流行的编程语言&#xff0c;用于开发Web应用程序、数据分析、人工智能和机器学习。Python的动态代码能够动态生成HTML、CSS和JavaScript来创建动态网页。这种能力使Python在SEO中非常有用&#xff0c;因为它可以帮助网站排名更…

chatgpt赋能python:如何使用Python制作动画?

如何使用Python制作动画&#xff1f; Python是一种高级编程语言&#xff0c;被广泛应用于各种领域&#xff0c;包括动画制作。Python的简洁性和强大的功能使得它成为一个很好的选择来制作动画。在这篇文章中&#xff0c;我将向您介绍使用Python如何制作动画。 第一步&#xf…

Vue3 相关Composition Api 2

一&#xff0c;其他Composition Api shallowReactive 与 shallowRef shallowReactive:只处理对象最外层属性的响应式&#xff08;浅响应式&#xff09;。 shallowRef:只处理基本数据类型的响应式&#xff0c;不进行对象的响应式处理。 什么时候使用&#xff1f; 如果有一个对…

Hive

Hive 概览 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供类SQL查询功能。 本质是将SQL转换为MapReduce程序。 主要用途&#xff1a;用来做离线数据分析&#xff0c;比直接用MapReduce开发效率更高。 架构 数…

chatgpt赋能python:用Python制作动画,你不可错过的工具

用Python制作动画&#xff0c;你不可错过的工具 Python是一种高级编程语言&#xff0c;最初被设计用于编写自动化脚本和简化复杂任务。然而&#xff0c;如今它越来越多地被用于创意和艺术性的项目&#xff0c;甚至是动画制作。 Python在动画制作中的优势一直受到赞誉。它是一…

辅助驾驶功能开发-功能算法篇(2)-ACC-状态机跳转设计

1、ACC状态介绍 ALOD_MODE &#xff08;ACC状态&#xff09; 状态说明OFF ACC关闭状态&#xff0c;此时ACC图标不显示&#xff0c;且其他相关信号都发默认值。此状态下车辆完全由驾驶员控制。 PassiveACC已由驾驶员开启&#xff0c;但有抑制条件(如安全带、车门)满足&#xff0…

chatgpt赋能python:Python制作录屏软件,让你的屏幕动作不再错过

Python制作录屏软件&#xff0c;让你的屏幕动作不再错过 作为一名Python工程师&#xff0c;你可能有很多需要记录屏幕操作的场景&#xff0c;比如演示软件或者录制教学视频。那么&#xff0c;有没有一款Python制作的录屏软件来满足你的需求呢&#xff1f;答案是肯定的&#xf…

K-means聚类算法原理、步骤、评价指标和实现

1、聚类 聚类与分类不同&#xff0c;聚类分析分通过分析大量含有一定规律但杂乱数据&#xff0c;得到数据间内在的逻辑&#xff0c;将杂乱的数据按照所得的数据规律划分成不同的种类。K-measn、DBSCAN和层次是当前广泛使用的三种聚类方法。以下对三种方法进行分析&#xff0c;…

Mybatis 如何实现返回多个结果集——详测版

文章结构 本文介绍一个在 Mybatis 中不常见的操作&#xff0c;但是可能有些朋友刚好需要用到&#xff0c;Mybatis 如何实现返回多个结果集 什么情况会返回多个结果集&#xff1a; 存储过程多个 select 语句 具体过程如下&#xff08;作者实测&#xff1a;跟着观战就完事了&a…

chatgpt赋能python:Python:一个强大、适用广泛的编程语言

Python&#xff1a;一个强大、适用广泛的编程语言 作为一种高级编程语言&#xff0c;Python 可以轻松地完成许多计算机编程任务。它是一种协作和代码重用的语言&#xff0c;Python旨在提高生产力并减少缺陷。 对于那些想要学习编程语言的人来说&#xff0c;Python 是非常适合…