如何使用重复的字节值填充64位寄存器

时间:2015-01-14 09:58:31

标签: assembly x86 64-bit masm

我正在使用Visual C ++ 2010和masm进行一些x64程序集('快速调用'调用约定)。

所以,让我说我在C ++中有一个函数:

extern "C" void fillArray(unsigned char* byteArray, unsigned char value);

指向数组的指针将位于RCX中,char值将位于DL

如何使用DL为RAX填充值,以便在mov qword ptr [RCX], RAX和打印byteArray时,所有值都等于' char值'?

请注意,我没有尝试编写我的编译器代码,我只是在学习。

3 个答案:

答案 0 :(得分:8)

您可以乘以0x0101010101010101将最低字节复制到所有其他字节(假设其余字节都是零开始),这有点烦人,因为没有imul r64, r64, imm64但您可以这样做:

mov  rax, 0x0101010101010101 
imul rax, rdx                 ; at least as fast as  mul rdx  on all CPUs

如果rdx不是必需的格式(换句话说,如果设置了一些额外的位),只需添加一个 前面是movzx eax, dl,并将常量移动到RDX或其他寄存器中。 (movzx edx,dl无法从Intel CPU上的mov-elimination中受益。)

如果您不喜欢代码大小(mov r64, imm64本身已经是10个字节),只需在数据段中保留该常量即可。

答案 1 :(得分:5)

因为你打电话给你的程序' fillArray',我假设你想用一个字节值填充整个内存块。所以我对不同的方法进行了比较。它是32位masm代码,但结果在64位模式下应该类似。使用对齐和未对齐的缓冲区测试每种方法。结果如下:

Simple REP STOSB - aligned....: 192
Simple REP STOSB - not aligned: 192
Simple REP STOSD - aligned....: 191
Simple REP STOSD - not aligned: 222
Simple while loop - aligned....: 267
Simple while loop - not aligned: 261
Simple while loop with different addressing - aligned....: 271
Simple while loop with different addressing - not aligned: 262
Loop with 16-byte SSE write - aligned....: 192
Loop with 16-byte SSE write - not aligned: 205
Loop with 16-byte SSE write non-temporal hint - aligned....: 126 (EDIT)

使用以下代码的最天真的变体似乎在两种情况下都表现最佳,并且代码尺寸也最小:

cld
mov al, 44h   ; byte value
mov edi, lpDst
mov ecx, 256000*4  ; buf size
rep stosb

编辑:对齐数据不是最快的。添加了性能最佳的MOVNTDQ版本,见下文。

为了完整起见,以下是其他例程的摘录 - 假设值在以前扩展为EAX:

Rep Stosd:

mov edi, lpDst
mov ecx, 256000
rep stosd

简单:

mov edi, lpDst
mov ecx, 256000
.while ecx>0
    mov [edi],eax
    add edi,4
    dec ecx
.endw

不同的简单:

mov edi, lpDst
xor ecx, ecx
.while ecx<256000 
    mov [edi+ecx*4],eax
    inc ecx
.endw

SSE(两者):

movd xmm0,eax
punpckldq xmm0,xmm0    ; xxxxxxxxGGGGHHHH -> xxxxxxxxHHHHHHHH
punpcklqdq xmm0,xmm0   ; xxxxxxxxHHHHHHHH -> HHHHHHHHHHHHHHHH
mov ecx, 256000/4   ; 16 byte
mov edi, lpDst
.while ecx>0 
    movdqa xmmword ptr [edi],xmm0    ; movdqu for unaligned
    add edi,16
    dec ecx
.endw

SSE(NT,对齐,编辑):

movd xmm0,eax
punpckldq xmm0,xmm0    ; xxxxxxxxGGGGHHHH -> xxxxxxxxHHHHHHHH
punpcklqdq xmm0,xmm0   ; xxxxxxxxHHHHHHHH -> HHHHHHHHHHHHHHHH
mov ecx, 256000/4   ; 16 byte
mov edi, lpDst
.while ecx>0 
    movntdq xmmword ptr [edi],xmm0
    add edi,16
    dec ecx
.endw

我在这里上传了整个代码http://pastie.org/9831404 ---需要装配来自hutch的MASM包。


如果SSSE3可用,您可以使用pshufb向寄存器的所有位置广播一个字节,而不是punpck指令链。

movd    xmm0, edx
xorps   xmm1,xmm1      ; xmm1 = 0
pshufb  xmm0, xmm1     ; xmm0 = _mm_set1_epi8(dl)

答案 2 :(得分:3)

天真的方式

xor ebx, ebx
mov bl, dl   ; input in dl
mov bh, dl
mov eax, ebx
shl ebx, 16
or  ebx, eax
mov eax, ebx
shl rax, 32
or  rax, rbx ; output in rax

所以它可能比哈罗德的解决方案慢一些

您还可以查看以下代码的编译器程序集输出

uint64_t s;
s = (s << 8)  | s;
s = (s << 16) | s;
s = (s << 32) | s;

gcc 8.2生成the following output,结果为rax

movzx   edi, dil      # s, c
mov     rax, rdi  # _1, s
sal     rax, 8    # _1,
or      rdi, rax    # s, _1
mov     rax, rdi  # _2, s
sal     rax, 16   # _2,
or      rax, rdi    # s, s
mov     rdi, rax  # _3, s
sal     rdi, 32   # _3,
or      rax, rdi    # s, _3
ret 

正如您所看到的,由于编译器始终使用64位寄存器(即使不需要),因此效率不高。因此,为了使编译器更容易优化,只需像这样修改它

uint32_t s = (c << 8) | c;
s = (s << 16) | s;
return ((uint64_t)s << 32) | s;

请注意,这仅用于填充像RAX这样的单个寄存器,其值使用DL,如上所述。对于大数组,最好使用SIMD或其他专门指令,例如repstos,例如std::fillmemset的实现方式。 gcc和Clang都足够聪明,可以识别memset(&int64, bytevalue, sizeof int64)并将其转换为乘法,如上所述为0x0101010101010101