glibc/sysdeps/x86_64/memrchr.S
Noah Goldstein 731feee386 x86: Optimize memrchr-sse2.S
The new code:
    1. prioritizes smaller lengths more.
    2. optimizes target placement more carefully.
    3. reuses logic more.
    4. fixes up various inefficiencies in the logic.

The total code size saving is: 394 bytes
Geometric Mean of all benchmarks New / Old: 0.874

Regressions:
    1. The page cross case is now colder, especially re-entry from the
       page cross case if a match is not found in the first VEC
       (roughly 50%). My general opinion with this patch is this is
       acceptable given the "coldness" of this case (less than 4%) and
       generally performance improvement in the other far more common
       cases.

    2. There are some regressions 5-15% for medium/large user-arg
       lengths that have a match in the first VEC. This is because the
       logic was rewritten to optimize finds in the first VEC if the
       user-arg length is shorter (where we see roughly 20-50%
       performance improvements). It is not always the case this is a
       regression. My intuition is some frontend quirk is partially
       explaining the data although I haven't been able to find the
       root cause.

Full xcheck passes on x86_64.
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
2022-06-07 13:09:36 -07:00

351 lines
7.5 KiB
ArmAsm

/* fast SSE2 memrchr with 64 byte loop and pmaxub instruction using
Copyright (C) 2011-2022 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <sysdep.h>
#define VEC_SIZE 16
#define PAGE_SIZE 4096
.text
ENTRY_P2ALIGN(__memrchr, 6)
#ifdef __ILP32__
/* Clear upper bits. */
mov %RDX_LP, %RDX_LP
#endif
movd %esi, %xmm0
/* Get end pointer. */
leaq (%rdx, %rdi), %rcx
punpcklbw %xmm0, %xmm0
punpcklwd %xmm0, %xmm0
pshufd $0, %xmm0, %xmm0
/* Check if we can load 1x VEC without cross a page. */
testl $(PAGE_SIZE - VEC_SIZE), %ecx
jz L(page_cross)
/* NB: This load happens regardless of whether rdx (len) is zero. Since
it doesn't cross a page and the standard gurantees any pointer have
at least one-valid byte this load must be safe. For the entire
history of the x86 memrchr implementation this has been possible so
no code "should" be relying on a zero-length check before this load.
The zero-length check is moved to the page cross case because it is
1) pretty cold and including it pushes the hot case len <= VEC_SIZE
into 2-cache lines. */
movups -(VEC_SIZE)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %eax
subq $VEC_SIZE, %rdx
ja L(more_1x_vec)
L(ret_vec_x0_test):
/* Zero-flag set if eax (src) is zero. Destination unchanged if src is
zero. */
bsrl %eax, %eax
jz L(ret_0)
/* Check if the CHAR match is in bounds. Need to truly zero `eax` here
if out of bounds. */
addl %edx, %eax
jl L(zero_0)
/* Since we subtracted VEC_SIZE from rdx earlier we can just add to base
ptr. */
addq %rdi, %rax
L(ret_0):
ret
.p2align 4,, 5
L(ret_vec_x0):
bsrl %eax, %eax
leaq -(VEC_SIZE)(%rcx, %rax), %rax
ret
.p2align 4,, 2
L(zero_0):
xorl %eax, %eax
ret
.p2align 4,, 8
L(more_1x_vec):
testl %eax, %eax
jnz L(ret_vec_x0)
/* Align rcx (pointer to string). */
decq %rcx
andq $-VEC_SIZE, %rcx
movq %rcx, %rdx
/* NB: We could consistenyl save 1-byte in this pattern with `movaps
%xmm0, %xmm1; pcmpeq IMM8(r), %xmm1; ...`. The reason against it is
it adds more frontend uops (even if the moves can be eliminated) and
some percentage of the time actual backend uops. */
movaps -(VEC_SIZE)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
subq %rdi, %rdx
pmovmskb %xmm1, %eax
cmpq $(VEC_SIZE * 2), %rdx
ja L(more_2x_vec)
L(last_2x_vec):
subl $VEC_SIZE, %edx
jbe L(ret_vec_x0_test)
testl %eax, %eax
jnz L(ret_vec_x0)
movaps -(VEC_SIZE * 2)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %eax
subl $VEC_SIZE, %edx
bsrl %eax, %eax
jz L(ret_1)
addl %edx, %eax
jl L(zero_0)
addq %rdi, %rax
L(ret_1):
ret
/* Don't align. Otherwise lose 2-byte encoding in jump to L(page_cross)
causes the hot pause (length <= VEC_SIZE) to span multiple cache
lines. Naturally aligned % 16 to 8-bytes. */
L(page_cross):
/* Zero length check. */
testq %rdx, %rdx
jz L(zero_0)
leaq -1(%rcx), %r8
andq $-(VEC_SIZE), %r8
movaps (%r8), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %esi
/* Shift out negative alignment (because we are starting from endptr and
working backwards). */
negl %ecx
/* 32-bit shift but VEC_SIZE=16 so need to mask the shift count
explicitly. */
andl $(VEC_SIZE - 1), %ecx
shl %cl, %esi
movzwl %si, %eax
leaq (%rdi, %rdx), %rcx
cmpq %rdi, %r8
ja L(more_1x_vec)
subl $VEC_SIZE, %edx
bsrl %eax, %eax
jz L(ret_2)
addl %edx, %eax
jl L(zero_1)
addq %rdi, %rax
L(ret_2):
ret
/* Fits in aliging bytes. */
L(zero_1):
xorl %eax, %eax
ret
.p2align 4,, 5
L(ret_vec_x1):
bsrl %eax, %eax
leaq -(VEC_SIZE * 2)(%rcx, %rax), %rax
ret
.p2align 4,, 8
L(more_2x_vec):
testl %eax, %eax
jnz L(ret_vec_x0)
movaps -(VEC_SIZE * 2)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %eax
testl %eax, %eax
jnz L(ret_vec_x1)
movaps -(VEC_SIZE * 3)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %eax
subq $(VEC_SIZE * 4), %rdx
ja L(more_4x_vec)
addl $(VEC_SIZE), %edx
jle L(ret_vec_x2_test)
L(last_vec):
testl %eax, %eax
jnz L(ret_vec_x2)
movaps -(VEC_SIZE * 4)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %eax
subl $(VEC_SIZE), %edx
bsrl %eax, %eax
jz L(ret_3)
addl %edx, %eax
jl L(zero_2)
addq %rdi, %rax
L(ret_3):
ret
.p2align 4,, 6
L(ret_vec_x2_test):
bsrl %eax, %eax
jz L(zero_2)
addl %edx, %eax
jl L(zero_2)
addq %rdi, %rax
ret
L(zero_2):
xorl %eax, %eax
ret
.p2align 4,, 5
L(ret_vec_x2):
bsrl %eax, %eax
leaq -(VEC_SIZE * 3)(%rcx, %rax), %rax
ret
.p2align 4,, 5
L(ret_vec_x3):
bsrl %eax, %eax
leaq -(VEC_SIZE * 4)(%rcx, %rax), %rax
ret
.p2align 4,, 8
L(more_4x_vec):
testl %eax, %eax
jnz L(ret_vec_x2)
movaps -(VEC_SIZE * 4)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %eax
testl %eax, %eax
jnz L(ret_vec_x3)
addq $-(VEC_SIZE * 4), %rcx
cmpq $(VEC_SIZE * 4), %rdx
jbe L(last_4x_vec)
/* Offset everything by 4x VEC_SIZE here to save a few bytes at the end
keeping the code from spilling to the next cache line. */
addq $(VEC_SIZE * 4 - 1), %rcx
andq $-(VEC_SIZE * 4), %rcx
leaq (VEC_SIZE * 4)(%rdi), %rdx
andq $-(VEC_SIZE * 4), %rdx
.p2align 4,, 11
L(loop_4x_vec):
movaps (VEC_SIZE * -1)(%rcx), %xmm1
movaps (VEC_SIZE * -2)(%rcx), %xmm2
movaps (VEC_SIZE * -3)(%rcx), %xmm3
movaps (VEC_SIZE * -4)(%rcx), %xmm4
pcmpeqb %xmm0, %xmm1
pcmpeqb %xmm0, %xmm2
pcmpeqb %xmm0, %xmm3
pcmpeqb %xmm0, %xmm4
por %xmm1, %xmm2
por %xmm3, %xmm4
por %xmm2, %xmm4
pmovmskb %xmm4, %esi
testl %esi, %esi
jnz L(loop_end)
addq $-(VEC_SIZE * 4), %rcx
cmpq %rdx, %rcx
jne L(loop_4x_vec)
subl %edi, %edx
/* Ends up being 1-byte nop. */
.p2align 4,, 2
L(last_4x_vec):
movaps -(VEC_SIZE)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %eax
cmpl $(VEC_SIZE * 2), %edx
jbe L(last_2x_vec)
testl %eax, %eax
jnz L(ret_vec_x0)
movaps -(VEC_SIZE * 2)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %eax
testl %eax, %eax
jnz L(ret_vec_end)
movaps -(VEC_SIZE * 3)(%rcx), %xmm1
pcmpeqb %xmm0, %xmm1
pmovmskb %xmm1, %eax
subl $(VEC_SIZE * 3), %edx
ja L(last_vec)
bsrl %eax, %eax
jz L(ret_4)
addl %edx, %eax
jl L(zero_3)
addq %rdi, %rax
L(ret_4):
ret
/* Ends up being 1-byte nop. */
.p2align 4,, 3
L(loop_end):
pmovmskb %xmm1, %eax
sall $16, %eax
jnz L(ret_vec_end)
pmovmskb %xmm2, %eax
testl %eax, %eax
jnz L(ret_vec_end)
pmovmskb %xmm3, %eax
/* Combine last 2 VEC matches. If ecx (VEC3) is zero (no CHAR in VEC3)
then it won't affect the result in esi (VEC4). If ecx is non-zero
then CHAR in VEC3 and bsrq will use that position. */
sall $16, %eax
orl %esi, %eax
bsrl %eax, %eax
leaq -(VEC_SIZE * 4)(%rcx, %rax), %rax
ret
L(ret_vec_end):
bsrl %eax, %eax
leaq (VEC_SIZE * -2)(%rax, %rcx), %rax
ret
/* Use in L(last_4x_vec). In the same cache line. This is just a spare
aligning bytes. */
L(zero_3):
xorl %eax, %eax
ret
/* 2-bytes from next cache line. */
END(__memrchr)
weak_alias (__memrchr, memrchr)