【发布时间】:2019-12-27 04:48:53
【问题描述】:
我希望用尽可能少的汇编代码来反转字符串。
由于缺乏 Unicorn 支持,我只能使用 SSSE3 扩展或更少。我试过访问 ymm & zmm 指令,但每次都会中断。
尽管 SSSE3 指令更简洁,但用于字节反转 128 位 XMM 寄存器的 16 字节 pshufb 控制向量仍然占用 16 字节并使其更长。我愿意接受任何想法,但以下是我的最佳尝试。
我需要 32 个字节或更少,越小越好。到目前为止,我得到的最好的是 42,但那是我假设 rdx 中字符串的大小(或者 ecx,如果使用 x86) 是 30。
理想情况下,它可以通过检查空终止符来动态获取大小。
字符串地址位于 rdx 内部(如果使用 x86,则为 ecx)。
附加限制:不使用堆栈空间。此代码块必须在没有 RSP 指向可用堆栈内存的情况下运行。
标准 x86 / 64 - 42 字节
; get values in registers
mov rax, [rdx]
mov rbx, [rdx + 8]
mov rcx, [rdx + 16]
mov r8, [rdx + 24]
; swap bytes around
bswap rax
bswap rbx
bswap rcx
bswap r8
; shift it right by 2 because of the nulls
sar r8, 16
; put it back
mov [rdx], r8
mov [rdx + 0x6], rcx
mov [rdx + 0xE], rbx
mov [rdx + 0x16], rax
SSE3 - 62字节(因为是字节数组,否则是46)
movdqu xmm3, [rip + 0x27]
movdqu xmm0, [rdx]
movdqu xmm1, [rdx] + 0x10
pshufb xmm0,xmm3
pshufb xmm1,xmm3
movdqu [rdx], xmm1
movdqu xmm1, [rdx+0x2]
movdqu [rdx], xmm1
movdqu [rdx+0xE], xmm0
hlt
; this would be tacked on to the end of the assembly as the rip + 0x27 value
\x00\x0F\x0E\x0D\x0C\x0B\x0A\x09\x08\x07\x06\x05\x04\x03\x02\x01
【问题讨论】:
-
如果您优化最小代码大小而不考虑性能,标量字节循环几乎肯定是最好的(可能使用
lodsb和stosb用于前向加载/存储)。如果codegolf.stackexchange.com 上已经有一个有效的 x86 机器代码答案,我不会感到惊讶,您可以将其用于字符串反转问题。另见Tips for golfing in x86/x64 machine code。但是,考虑到您可以选择使用 64 位操作数大小的完全展开或 SSSE3,您可能正在寻找大小和性能的平衡点。 -
这是一个 32 字节的 x86-64
void strrev(char* p),它适用于任何字符串长度 except 0 并遵守 System V ABI。如果这是 x86,它将使用更少的字节。也许您可以弄清楚如何以某种方式在其中塞入空字符串测试。 -
@IwillnotexistIdonotexist:这看起来像是一个答案;继续并发布它IMO。提示:2x
dec reg仅适用于具有 1 字节dec的 32 位代码,而不是具有 3 字节dec r64的 64 位代码。sub rdi, 2会更好,长度与std/scasw/cld相同。通过节省 2 个字节,您将有空间让JRCXZ跳过循环。此外,您可以删除not ecx/shr ecx,1上的 REX 前缀以假设字符串长度小于 4GiB。 -
@PeterCordes 我最后没有使用
jrcxz,而是通过避免计算~strlen() >> 1节省了几个字节,并用指针比较src <= dst替换了循环测试。 -
@IwillnotexistIdonotexist:你可以用
push/pop而不是mov复制一个2字节的64位寄存器。 push/pop 的默认操作数大小为 64 位,与 REX 前缀无关。
标签: assembly x86-64 micro-optimization code-size