ARM-v8 NEON:是否有将单个普通寄存器拆分到 NEON 寄存器的多个通道的指令?
ARM-v8 NEON: is there an instruction to split a single normal register across multiple lanes of a NEON register?
我是 ARM-v8 (AArch64) 的新手,只在 ARM-v7 中进行了一点点 NEON 编码(但我对 A32 非常满意,对普通 A64 还可以(*))。
最终我要做的是计算一组(最多 15 个)32 位值中每个设置位 [31:0] 的频率。即在这15个值中,bit 0设置了多少次,bit 1设置了多少次等
所以,我想做的是将 32 位拆分为 128 位 NEON 寄存器中的 32 个半字节,然后累加 NEON 寄存器,如下所示:
// args(x0: ptr to array of 16 32-bit words) ret(v0: sum of set bits as 32 nibbles)
mov w2, 16 // w2: loop counter
mov v0, 0 // v0: accumulate count
1:
ldr w1, [x0], 4
split v1, w1 // here some magic occurs
add v0.16b, v0.16b, v1.16b
subs w2, w2, 1
bne 1b
我对 ARM 文档不太满意。 ARMv8-ARM 仅具有 354 条 NEON 指令的字母顺序列表(800 页伪代码)。 ARMv8-A 程序员指南只有 14 页的介绍和诱人的声明"New lane insert and extract instructions have been added to support the new register packing scheme." 而 NEON 程序员指南是关于 ARM-v7.
假设没有一条指令可以做到这一点,那么最有效的方法是什么? -- 不是在寻找完整的解决方案,但 NEON 能否提供帮助?如果我必须分别加载每条车道,那就没有多大意义了...
(*) 虽然不能说我 喜欢 A64。 :-(
我认为不能按半字节完成,但按字节应该可以。
加载一个向量,并在每个字节中设置相关的源位(您需要其中两个,因为我们可能只能按字节而不是按半字节执行此操作)。将单词的每个字节复制为两个向量中的每个 8 字节大小的元素。使用两个掩码执行 cmtst
(这将设置所有位,即将其设置为 -1,如果设置了相应的位,则在元素中),然后累加。
类似这样的东西,未经测试:
.section .rodata
mask: .byte 1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128
.text
mov w2, 16 // w2: loop counter
mov v0.16b, 0 // v0: accumulate count 1
mov v1.16b, 0 // v1: accumulate count 2
adrp w3, mask
add w3, :lo12:mask
ld1 {v2.16b}, [w3] // v2: mask with one bit set in each byte
1:
ld1r {v3.4s}, [x0], #4 // One vector with the full 32 bit word
subs w2, w2, 1
dup v4.8b, v3.b[0] // v4: vector containing the lowest byte of the word
dup v5.8b, v3.b[1] // v5: vector containing the second lowest byte of the word
dup v6.8b, v3.b[2]
dup v7.8b, v3.b[3]
ins v4.d[1], v5.d[0] // v4: elements 0-7: lowest byte, elements 8-15: second byte
ins v6.d[1], v7.d[0] // v6: elements 0-7: third byte, elements 8-15: fourth byte
cmtst v4.16b, v4.16b, v2.16b // v4: each byte -1 if the corresponding bit was set
cmtst v6.16b, v6.16b, v2.16b // v5: each byte -1 if the corresponding bit was set
sub v0.16b, v0.16b, v4.16b // accumulate: if bit was set, subtract -1 i.e. add +1
sub v1.16b, v1.16b, v6.16b
b.ne 1b
// Done, count of individual bits in byte sized elements in v0-v1
编辑:Jake 'Alquimista' LEE 建议的 ld4r
方法实际上比这里的加载要好; ld1r
后跟四个 dup
可以在这里替换为 ld4r {v4.8b, v5.8b, v6.8b, v7.8h}, [x0], #4
,保持逻辑相同。对于其余部分,无论是 cmtst
还是 ushl
+ and
最终速度更快,都必须进行测试和测量才能看到。同时处理两个 32 位字,如在他的解决方案中,可能比我这里的解决方案提供更好的吞吐量。
你应该跳出框框思考。源数据是 32 位宽并不意味着您应该按 32 位访问它们。
通过以 4x8 位的方式读取它们,问题就简单多了。下面是拆分和计算数组中的每个 32 位:
/*
* alqCountBits.S
*
* Created on: 2020. 5. 26.
* Author: Jake 'Alquimista' LEE
*/
.arch armv8-a
.global alqCountBits
.text
// extern void alqCountBits(uint32_t *pDst, uint32_t *pSrc, uint32_t nLength);
// assert(nLength % 2 == 0);
pDst .req x0
pSrc .req x1
length .req w2
.balign 64
.func
alqCountBits:
adr x3, .LShiftTable
movi v30.16b, #1
ld1r {v31.2d}, [x3]
movi v0.16b, #0
movi v1.16b, #0
movi v2.16b, #0
movi v3.16b, #0
movi v4.16b, #0
movi v5.16b, #0
movi v6.16b, #0
movi v7.16b, #0
.balign 64
1:
ld4r {v16.8b, v17.8b, v18.8b, v19.8b}, [pSrc], #4
ld4r {v20.8b, v21.8b, v22.8b, v23.8b}, [pSrc], #4
subs length, length, #2
trn1 v24.2d, v16.2d, v17.2d
trn1 v25.2d, v18.2d, v19.2d
trn1 v26.2d, v20.2d, v21.2d
trn1 v27.2d, v22.2d, v23.2d
ushl v16.16b, v24.16b, v31.16b
ushl v17.16b, v25.16b, v31.16b
ushl v18.16b, v26.16b, v31.16b
ushl v19.16b, v27.16b, v31.16b
and v16.16b, v16.16b, v30.16b
and v17.16b, v17.16b, v30.16b
and v18.16b, v18.16b, v30.16b
and v19.16b, v19.16b, v30.16b
uaddl v24.8h, v18.8b, v16.8b
uaddl2 v25.8h, v18.16b, v16.16b
uaddl v26.8h, v19.8b, v17.8b
uaddl2 v27.8h, v19.16b, v17.16b
uaddw v0.4s, v0.4s, v24.4h
uaddw2 v1.4s, v1.4s, v24.8h
uaddw v2.4s, v2.4s, v25.4h
uaddw2 v3.4s, v3.4s, v25.8h
uaddw v4.4s, v4.4s, v26.4h
uaddw2 v5.4s, v5.4s, v26.8h
uaddw v6.4s, v6.4s, v27.4h
uaddw2 v7.4s, v7.4s, v27.8h
b.gt 1b
.balign 8
stp q0, q1, [pDst, #0]
stp q2, q3, [pDst, #32]
stp q4, q5, [pDst, #64]
stp q6, q7, [pDst, #96]
ret
.endfunc
.balign 8
.LShiftTable:
.dc.b 0, -1, -2, -3, -4, -5, -6, -7
.end
我也不喜欢 aarch64
助记符。为了比较,我把 aarch32
版本放在下面:
/*
* alqCountBits.S
*
* Created on: 2020. 5. 26.
* Author: Jake 'Alquimista' LEE
*/
.syntax unified
.arm
.arch armv7-a
.fpu neon
.global alqCountBits
.text
// extern void alqCountBits(uint32_t *pDst, uint32_t *pSrc, uint32_t nLength);
// assert(nLength % 2 == 0);
pDst .req r0
pSrc .req r1
length .req r2
.balign 32
.func
alqCountBits:
adr r12, .LShiftTable
vpush {q4-q7}
vld1.64 {d30}, [r12]
vmov.i8 q14, #1
vmov.i8 q0, #0
vmov.i8 q1, #0
vmov.i8 q2, #0
vmov.i8 q3, #0
vmov.i8 q4, #0
vmov.i8 q5, #0
vmov.i8 q6, #0
vmov.i8 q7, #0
vmov d31, d30
.balign 32
1:
vld4.8 {d16[], d17[], d18[], d19[]}, [pSrc]!
vld4.8 {d20[], d21[], d22[], d23[]}, [pSrc]!
subs length, length, #2
vshl.u8 q8, q8, q15
vshl.u8 q9, q9, q15
vshl.u8 q10, q10, q15
vshl.u8 q11, q11, q15
vand q8, q8, q14
vand q9, q9, q14
vand q10, q10, q14
vand q11, q11, q14
vaddl.u8 q12, d20, d16
vaddl.u8 q13, d21, d17
vaddl.u8 q8, d22, d18
vaddl.u8 q10, d23, d19
vaddw.u16 q0, q0, d24
vaddw.u16 q1, q1, d25
vaddw.u16 q2, q2, d26
vaddw.u16 q3, q3, d27
vaddw.u16 q4, q4, d16
vaddw.u16 q5, q5, d17
vaddw.u16 q6, q6, d20
vaddw.u16 q7, q7, d21
bgt 1b
.balign 8
vst1.32 {q0, q1}, [pDst]!
vst1.32 {q2, q3}, [pDst]!
vst1.32 {q4, q5}, [pDst]!
vst1.32 {q6, q7}, [pDst]
vpop {q4-q7}
bx lr
.endfunc
.balign 8
.LShiftTable:
.dc.b 0, -1, -2, -3, -4, -5, -6, -7
.end
如您所见,aarch32
中根本不需要 trn1
等价
不过,由于寄存器的数量众多,我总体上更喜欢 aarch64
。
结合以上答案,修改我的要求;-)我想出了:
tst:
ldr x0, =test_data
ldr x1, =mask
ld1 {v2.2d}, [x1] // ld1.2d v2, [x1] // load 2 * 64 = 128 bits
movi v0.16b, 0
mov w2, 8
1:
ld1r {v1.8h}, [x0], 2 // ld1r.8h v1, [x0], 2 // repeat one 16-bit word across eight 16-bit lanes
cmtst v1.16b, v1.16b, v2.16b // cmtst.16b v1, v1, v2 // sets -1 in each 8bit word of 16 8-bit lanes if input matches mask
sub v0.16b, v0.16b, v1.16b // sub.16b v0, v0, v1 // sub -1 = add +1
subs w2, w2, 1
bne 1b
// v0 contains 16 bytes, mildly shuffled.
如果想要他们不洗牌:
mov v1.d[0], v0.d[1]
uzp1 v2.8b, v0.8b, v1.8b
uzp2 v3.8b, v0.8b, v1.8b
mov v2.d[1], v3.d[0]
// v2 contains 16 bytes, in order.
以下最多计算 15 个 32 位样本(累积为 32 个半字节):
tst2:
ldr x0, =test_data2
ldr x1, =mask2
ld1 {v2.4s, v3.4s, v4.4s, v5.4s}, [x1] // ld1.4s {v2, v3, v4, v5}, [x1]
movi v0.16b, 0
mov w2, 8
1:
ld1r {v1.4s}, [x0], 4 // ld1r.4s v1, [x0], 4 // repeat one 32-bit word across four 32-bit lanes
cmtst v6.16b, v1.16b, v2.16b // cmtst.16b v6, v1, v2 // upper nibbles
cmtst v1.16b, v1.16b, v3.16b // cmtst.16b v1, v1, v3 // lower nibbles
and v6.16b, v6.16b, v4.16b // and.16b v6, v6, v4 // upper inc 0001.0000 x 16
and v1.16b, v1.16b, v5.16b // and.16b v1, v1, v5 // lower inc 0000.0001 x 16
orr v1.16b, v1.16b, v6.16b // orr.16b v1, v1, v6
add v0.16b, v0.16b, v1.16b // add.16b v0, v0, v1 // accumulate
subs w2, w2, 1
bne 1b
// v0 contains 32 nibbles -- somewhat shuffled, but that's ok.
// fedcba98.76543210.fedcba98.76543210.fedcba98.76543210.fedcba98.76543210 fedcba98.76543210.fedcba98.76543210.fedcba98.76543210.fedcba98.76543210
// 10000000.10000000.01000000.01000000.00100000.00100000.00010000.00010000 00001000.00001000.00000100.00000100.00000010.00000010.00000001.00000001
// f 7 e 6 d 5 c 4 b 3 a 2 9 1 8 0
mask:
.quad 0x0808040402020101
.quad 0x8080404020201010
test_data:
.hword 0x0103
.hword 0x0302
.hword 0x0506
.hword 0x080A
.hword 0x1010
.hword 0x2020
.hword 0xc040
.hword 0x8080
// FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰.FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰.FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰.FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰
// 10001000 10001000 10001000 10001000 01000100 01000100 01000100 01000100 00100010 00100010 00100010 00100010 00010001 00010001 00010001 00010001
// F B 7 3 f b ⁷ ³ E A 6 2 e a ⁶ ² D 9 5 1 d ⁹ ⁵ ¹ C 8 4 0 c ⁸ ⁴ ⁰
mask2:
.quad 0x8080808040404040 // v2
.quad 0x2020202010101010
.quad 0x0808080804040404 // v3
.quad 0x0202020201010101
.quad 0x1010101010101010 // v4
.quad 0x1010101010101010
.quad 0x0101010101010101 // v5
.quad 0x0101010101010101
test_data2:
.word 0xff000103
.word 0xff000302
.word 0xff000506
.word 0xff00080A
.word 0xff001010
.word 0xff002020
.word 0xff00c040
.word 0xff008080
我是 ARM-v8 (AArch64) 的新手,只在 ARM-v7 中进行了一点点 NEON 编码(但我对 A32 非常满意,对普通 A64 还可以(*))。
最终我要做的是计算一组(最多 15 个)32 位值中每个设置位 [31:0] 的频率。即在这15个值中,bit 0设置了多少次,bit 1设置了多少次等
所以,我想做的是将 32 位拆分为 128 位 NEON 寄存器中的 32 个半字节,然后累加 NEON 寄存器,如下所示:
// args(x0: ptr to array of 16 32-bit words) ret(v0: sum of set bits as 32 nibbles)
mov w2, 16 // w2: loop counter
mov v0, 0 // v0: accumulate count
1:
ldr w1, [x0], 4
split v1, w1 // here some magic occurs
add v0.16b, v0.16b, v1.16b
subs w2, w2, 1
bne 1b
我对 ARM 文档不太满意。 ARMv8-ARM 仅具有 354 条 NEON 指令的字母顺序列表(800 页伪代码)。 ARMv8-A 程序员指南只有 14 页的介绍和诱人的声明"New lane insert and extract instructions have been added to support the new register packing scheme." 而 NEON 程序员指南是关于 ARM-v7.
假设没有一条指令可以做到这一点,那么最有效的方法是什么? -- 不是在寻找完整的解决方案,但 NEON 能否提供帮助?如果我必须分别加载每条车道,那就没有多大意义了...
(*) 虽然不能说我 喜欢 A64。 :-(
我认为不能按半字节完成,但按字节应该可以。
加载一个向量,并在每个字节中设置相关的源位(您需要其中两个,因为我们可能只能按字节而不是按半字节执行此操作)。将单词的每个字节复制为两个向量中的每个 8 字节大小的元素。使用两个掩码执行 cmtst
(这将设置所有位,即将其设置为 -1,如果设置了相应的位,则在元素中),然后累加。
类似这样的东西,未经测试:
.section .rodata
mask: .byte 1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128
.text
mov w2, 16 // w2: loop counter
mov v0.16b, 0 // v0: accumulate count 1
mov v1.16b, 0 // v1: accumulate count 2
adrp w3, mask
add w3, :lo12:mask
ld1 {v2.16b}, [w3] // v2: mask with one bit set in each byte
1:
ld1r {v3.4s}, [x0], #4 // One vector with the full 32 bit word
subs w2, w2, 1
dup v4.8b, v3.b[0] // v4: vector containing the lowest byte of the word
dup v5.8b, v3.b[1] // v5: vector containing the second lowest byte of the word
dup v6.8b, v3.b[2]
dup v7.8b, v3.b[3]
ins v4.d[1], v5.d[0] // v4: elements 0-7: lowest byte, elements 8-15: second byte
ins v6.d[1], v7.d[0] // v6: elements 0-7: third byte, elements 8-15: fourth byte
cmtst v4.16b, v4.16b, v2.16b // v4: each byte -1 if the corresponding bit was set
cmtst v6.16b, v6.16b, v2.16b // v5: each byte -1 if the corresponding bit was set
sub v0.16b, v0.16b, v4.16b // accumulate: if bit was set, subtract -1 i.e. add +1
sub v1.16b, v1.16b, v6.16b
b.ne 1b
// Done, count of individual bits in byte sized elements in v0-v1
编辑:Jake 'Alquimista' LEE 建议的 ld4r
方法实际上比这里的加载要好; ld1r
后跟四个 dup
可以在这里替换为 ld4r {v4.8b, v5.8b, v6.8b, v7.8h}, [x0], #4
,保持逻辑相同。对于其余部分,无论是 cmtst
还是 ushl
+ and
最终速度更快,都必须进行测试和测量才能看到。同时处理两个 32 位字,如在他的解决方案中,可能比我这里的解决方案提供更好的吞吐量。
你应该跳出框框思考。源数据是 32 位宽并不意味着您应该按 32 位访问它们。
通过以 4x8 位的方式读取它们,问题就简单多了。下面是拆分和计算数组中的每个 32 位:
/*
* alqCountBits.S
*
* Created on: 2020. 5. 26.
* Author: Jake 'Alquimista' LEE
*/
.arch armv8-a
.global alqCountBits
.text
// extern void alqCountBits(uint32_t *pDst, uint32_t *pSrc, uint32_t nLength);
// assert(nLength % 2 == 0);
pDst .req x0
pSrc .req x1
length .req w2
.balign 64
.func
alqCountBits:
adr x3, .LShiftTable
movi v30.16b, #1
ld1r {v31.2d}, [x3]
movi v0.16b, #0
movi v1.16b, #0
movi v2.16b, #0
movi v3.16b, #0
movi v4.16b, #0
movi v5.16b, #0
movi v6.16b, #0
movi v7.16b, #0
.balign 64
1:
ld4r {v16.8b, v17.8b, v18.8b, v19.8b}, [pSrc], #4
ld4r {v20.8b, v21.8b, v22.8b, v23.8b}, [pSrc], #4
subs length, length, #2
trn1 v24.2d, v16.2d, v17.2d
trn1 v25.2d, v18.2d, v19.2d
trn1 v26.2d, v20.2d, v21.2d
trn1 v27.2d, v22.2d, v23.2d
ushl v16.16b, v24.16b, v31.16b
ushl v17.16b, v25.16b, v31.16b
ushl v18.16b, v26.16b, v31.16b
ushl v19.16b, v27.16b, v31.16b
and v16.16b, v16.16b, v30.16b
and v17.16b, v17.16b, v30.16b
and v18.16b, v18.16b, v30.16b
and v19.16b, v19.16b, v30.16b
uaddl v24.8h, v18.8b, v16.8b
uaddl2 v25.8h, v18.16b, v16.16b
uaddl v26.8h, v19.8b, v17.8b
uaddl2 v27.8h, v19.16b, v17.16b
uaddw v0.4s, v0.4s, v24.4h
uaddw2 v1.4s, v1.4s, v24.8h
uaddw v2.4s, v2.4s, v25.4h
uaddw2 v3.4s, v3.4s, v25.8h
uaddw v4.4s, v4.4s, v26.4h
uaddw2 v5.4s, v5.4s, v26.8h
uaddw v6.4s, v6.4s, v27.4h
uaddw2 v7.4s, v7.4s, v27.8h
b.gt 1b
.balign 8
stp q0, q1, [pDst, #0]
stp q2, q3, [pDst, #32]
stp q4, q5, [pDst, #64]
stp q6, q7, [pDst, #96]
ret
.endfunc
.balign 8
.LShiftTable:
.dc.b 0, -1, -2, -3, -4, -5, -6, -7
.end
我也不喜欢 aarch64
助记符。为了比较,我把 aarch32
版本放在下面:
/*
* alqCountBits.S
*
* Created on: 2020. 5. 26.
* Author: Jake 'Alquimista' LEE
*/
.syntax unified
.arm
.arch armv7-a
.fpu neon
.global alqCountBits
.text
// extern void alqCountBits(uint32_t *pDst, uint32_t *pSrc, uint32_t nLength);
// assert(nLength % 2 == 0);
pDst .req r0
pSrc .req r1
length .req r2
.balign 32
.func
alqCountBits:
adr r12, .LShiftTable
vpush {q4-q7}
vld1.64 {d30}, [r12]
vmov.i8 q14, #1
vmov.i8 q0, #0
vmov.i8 q1, #0
vmov.i8 q2, #0
vmov.i8 q3, #0
vmov.i8 q4, #0
vmov.i8 q5, #0
vmov.i8 q6, #0
vmov.i8 q7, #0
vmov d31, d30
.balign 32
1:
vld4.8 {d16[], d17[], d18[], d19[]}, [pSrc]!
vld4.8 {d20[], d21[], d22[], d23[]}, [pSrc]!
subs length, length, #2
vshl.u8 q8, q8, q15
vshl.u8 q9, q9, q15
vshl.u8 q10, q10, q15
vshl.u8 q11, q11, q15
vand q8, q8, q14
vand q9, q9, q14
vand q10, q10, q14
vand q11, q11, q14
vaddl.u8 q12, d20, d16
vaddl.u8 q13, d21, d17
vaddl.u8 q8, d22, d18
vaddl.u8 q10, d23, d19
vaddw.u16 q0, q0, d24
vaddw.u16 q1, q1, d25
vaddw.u16 q2, q2, d26
vaddw.u16 q3, q3, d27
vaddw.u16 q4, q4, d16
vaddw.u16 q5, q5, d17
vaddw.u16 q6, q6, d20
vaddw.u16 q7, q7, d21
bgt 1b
.balign 8
vst1.32 {q0, q1}, [pDst]!
vst1.32 {q2, q3}, [pDst]!
vst1.32 {q4, q5}, [pDst]!
vst1.32 {q6, q7}, [pDst]
vpop {q4-q7}
bx lr
.endfunc
.balign 8
.LShiftTable:
.dc.b 0, -1, -2, -3, -4, -5, -6, -7
.end
如您所见,aarch32
trn1
等价
不过,由于寄存器的数量众多,我总体上更喜欢 aarch64
。
结合以上答案,修改我的要求;-)我想出了:
tst:
ldr x0, =test_data
ldr x1, =mask
ld1 {v2.2d}, [x1] // ld1.2d v2, [x1] // load 2 * 64 = 128 bits
movi v0.16b, 0
mov w2, 8
1:
ld1r {v1.8h}, [x0], 2 // ld1r.8h v1, [x0], 2 // repeat one 16-bit word across eight 16-bit lanes
cmtst v1.16b, v1.16b, v2.16b // cmtst.16b v1, v1, v2 // sets -1 in each 8bit word of 16 8-bit lanes if input matches mask
sub v0.16b, v0.16b, v1.16b // sub.16b v0, v0, v1 // sub -1 = add +1
subs w2, w2, 1
bne 1b
// v0 contains 16 bytes, mildly shuffled.
如果想要他们不洗牌:
mov v1.d[0], v0.d[1]
uzp1 v2.8b, v0.8b, v1.8b
uzp2 v3.8b, v0.8b, v1.8b
mov v2.d[1], v3.d[0]
// v2 contains 16 bytes, in order.
以下最多计算 15 个 32 位样本(累积为 32 个半字节):
tst2:
ldr x0, =test_data2
ldr x1, =mask2
ld1 {v2.4s, v3.4s, v4.4s, v5.4s}, [x1] // ld1.4s {v2, v3, v4, v5}, [x1]
movi v0.16b, 0
mov w2, 8
1:
ld1r {v1.4s}, [x0], 4 // ld1r.4s v1, [x0], 4 // repeat one 32-bit word across four 32-bit lanes
cmtst v6.16b, v1.16b, v2.16b // cmtst.16b v6, v1, v2 // upper nibbles
cmtst v1.16b, v1.16b, v3.16b // cmtst.16b v1, v1, v3 // lower nibbles
and v6.16b, v6.16b, v4.16b // and.16b v6, v6, v4 // upper inc 0001.0000 x 16
and v1.16b, v1.16b, v5.16b // and.16b v1, v1, v5 // lower inc 0000.0001 x 16
orr v1.16b, v1.16b, v6.16b // orr.16b v1, v1, v6
add v0.16b, v0.16b, v1.16b // add.16b v0, v0, v1 // accumulate
subs w2, w2, 1
bne 1b
// v0 contains 32 nibbles -- somewhat shuffled, but that's ok.
// fedcba98.76543210.fedcba98.76543210.fedcba98.76543210.fedcba98.76543210 fedcba98.76543210.fedcba98.76543210.fedcba98.76543210.fedcba98.76543210
// 10000000.10000000.01000000.01000000.00100000.00100000.00010000.00010000 00001000.00001000.00000100.00000100.00000010.00000010.00000001.00000001
// f 7 e 6 d 5 c 4 b 3 a 2 9 1 8 0
mask:
.quad 0x0808040402020101
.quad 0x8080404020201010
test_data:
.hword 0x0103
.hword 0x0302
.hword 0x0506
.hword 0x080A
.hword 0x1010
.hword 0x2020
.hword 0xc040
.hword 0x8080
// FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰.FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰.FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰.FEDCBA98.76543210.fedcba⁹⁸.⁷⁶⁵⁴³²¹⁰
// 10001000 10001000 10001000 10001000 01000100 01000100 01000100 01000100 00100010 00100010 00100010 00100010 00010001 00010001 00010001 00010001
// F B 7 3 f b ⁷ ³ E A 6 2 e a ⁶ ² D 9 5 1 d ⁹ ⁵ ¹ C 8 4 0 c ⁸ ⁴ ⁰
mask2:
.quad 0x8080808040404040 // v2
.quad 0x2020202010101010
.quad 0x0808080804040404 // v3
.quad 0x0202020201010101
.quad 0x1010101010101010 // v4
.quad 0x1010101010101010
.quad 0x0101010101010101 // v5
.quad 0x0101010101010101
test_data2:
.word 0xff000103
.word 0xff000302
.word 0xff000506
.word 0xff00080A
.word 0xff001010
.word 0xff002020
.word 0xff00c040
.word 0xff008080