如何在GAS汇编中对128位SSE寄存器执行PSLLDQ逻辑移位?
GAS汇编中PSLLDQ寄存器操作数报错的解决方法及替代方案
错误原因
pslldq指令的x86指令集设计仅支持立即数imm8(0-15)作为移位长度操作数,不接受寄存器或内存操作数,这就是你用%xmm10作为操作数时出现operand type mismatch错误的原因。
解决方法一:跳转表处理所有可能的移位值
因为128位SSE寄存器最多左移15字节(移位16字节等同于清零),可以为每个可能的移位值(0-15)编写对应的pslldq指令,通过跳转表根据寄存器中的值跳转到对应指令执行。
示例代码:
# 假设移位长度存储在%rax中(需确保值在0-15范围内) cmpq $15, %rax ja invalid_shift # 处理超出0-15的非法移位值 # 构建跳转表,每个条目对应一个移位值的指令地址 lea shift_jump_table(%rip), %rcx jmp *(%rcx, %rax, 8) # 根据%rax的值跳转到对应指令 # 跳转表定义 shift_jump_table: .quad shift_by_0 .quad shift_by_1 .quad shift_by_2 .quad shift_by_3 .quad shift_by_4 .quad shift_by_5 .quad shift_by_6 .quad shift_by_7 .quad shift_by_8 .quad shift_by_9 .quad shift_by_10 .quad shift_by_11 .quad shift_by_12 .quad shift_by_13 .quad shift_by_14 .quad shift_by_15 # 各移位值对应的指令 shift_by_0: pslldq $0, %xmm14 jmp shift_done shift_by_1: pslldq $1, %xmm14 jmp shift_done shift_by_2: pslldq $2, %xmm14 jmp shift_done shift_by_3: pslldq $3, %xmm14 jmp shift_done shift_by_4: pslldq $4, %xmm14 jmp shift_done shift_by_5: pslldq $5, %xmm14 jmp shift_done shift_by_6: pslldq $6, %xmm14 jmp shift_done shift_by_7: pslldq $7, %xmm14 jmp shift_done shift_by_8: pslldq $8, %xmm14 jmp shift_done shift_by_9: pslldq $9, %xmm14 jmp shift_done shift_by_10: pslldq $10, %xmm14 jmp shift_done shift_by_11: pslldq $11, %xmm14 jmp shift_done shift_by_12: pslldq $12, %xmm14 jmp shift_done shift_by_13: pslldq $13, %xmm14 jmp shift_done shift_by_14: pslldq $14, %xmm14 jmp shift_done shift_by_15: pslldq $15, %xmm14 shift_done: # 移位完成后的后续代码 invalid_shift: # 处理非法移位值的逻辑(如报错、退出等)
解决方法二:用PSHUFB指令实现任意字节左移(更高效)
pshufb(字节乱序)指令支持用XMM寄存器作为控制掩码,可灵活实现任意字节的左移效果,无需跳转,效率更高。
掩码规则
pshufb的掩码寄存器每个字节对应目标寄存器的一个字节位置:
- 若掩码字节最高位为1,目标位置填充0
- 若最高位为0,目标位置取源寄存器中掩码字节低4位指定索引的字节
对于左移N字节的需求,掩码构造规则:
- 前N个字节的掩码值为
0x80(填充0) - 从第N个字节开始,每个位置的掩码值为
当前位置索引 - N(即取源寄存器中靠前的对应字节)
示例代码(假设移位长度N在%al中,范围0-15):
# 1. 将N广播到XMM寄存器的所有字节 pxor %xmm10, %xmm10 pinsrb $0, %al, %xmm10 # %xmm10[0] = N,其余字节为0 pshufb %xmm10, %xmm10 # 广播N到%xmm10的所有16个字节 # 2. 生成0-15的索引序列(每个字节对应一个位置索引) movq $0x0706050403020100, %rbx movq $0x0f0e0d0c0b0a0908, %rcx movq %rbx, %xmm11 movq %rcx, %xmm12 punpcklqdq %xmm12, %xmm11 # %xmm11 = [0,1,2,...,15](每个字节为对应索引) # 3. 计算索引与N的差值:索引 - N movdqa %xmm11, %xmm13 # 保存原索引用于后续比较 psubb %xmm10, %xmm11 # %xmm11 = 索引 - N # 4. 生成全0x80的寄存器(用于填充左移后的空位置) movq $0x8080808080808080, %rbx movq %rbx, %xmm12 movq %rbx, %xmm14 punpcklqdq %xmm14, %xmm12 # %xmm12 = 16个0x80字节 # 5. 生成选择掩码:区分索引 >= N和索引 < N的位置 pcmpgtb %xmm10, %xmm13 # %xmm13:索引 > N → 0xff,索引 <= N → 0x00(GAS语法:src在前,dest在后) pxor %xmm15, %xmm15 pcmpeqb %xmm15, %xmm15 # %xmm15 = 0xff...ff pxor %xmm13, %xmm15 # %xmm15:索引 < N → 0xff,索引 >= N → 0x00(取反%xmm13) # 6. 合并得到最终的PSHUFB掩码 pand %xmm15, %xmm12 # 保留索引 < N位置的0x80,其余置0 pandn %xmm15, %xmm11 # 保留索引 >= N位置的(索引-N),其余置0 por %xmm12, %xmm11 # %xmm11即为最终掩码 # 7. 执行PSHUFB实现左移N字节 movdqa %xmm14, %xmm16 # 保存原数据(避免覆盖) pshufb %xmm11, %xmm16 # %xmm16就是左移N字节后的结果 movdqa %xmm16, %xmm14 # 将结果写回%xmm14
内容的提问来源于stack exchange,提问作者RajibTheKing
相关产品推荐
相关产品推荐

