/* strncpy with AVX2 Copyright (C) 2022-2023 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 . */ #include #if ISA_SHOULD_BUILD (3) # include # ifndef VEC_SIZE # include "x86-avx-vecs.h" # endif # ifndef STRNCPY # define STRNCPY __strncpy_avx2 # endif # ifdef USE_AS_WCSCPY # define VPCMPEQ vpcmpeqd # define VPMIN vpminud # define CHAR_SIZE 4 # else # define VPCMPEQ vpcmpeqb # define VPMIN vpminub # define CHAR_SIZE 1 # endif # include "strncpy-or-cat-overflow-def.h" # define PAGE_SIZE 4096 # define VZERO VMM(7) # define VZERO_128 VMM_128(7) .section SECTION(.text), "ax", @progbits ENTRY(STRNCPY) # ifdef __ILP32__ /* Clear the upper 32 bits. */ movl %edx, %edx # endif /* Filter zero length strings and very long strings. Zero length strings just return, very long strings are handled by just running rep stos{b|l} to zero set (which will almost certainly segfault), if that succeeds then just calling OVERFLOW_STRCPY (strcpy, stpcpy, wcscpy, wcpcpy). */ # ifdef USE_AS_WCSCPY decq %rdx movq %rdx, %rax /* 56 is end of max supported address space. */ shr $56, %rax jnz L(zero_len) salq $2, %rdx # else decq %rdx /* `dec` can macrofuse with `jl`. If the flag needs to become `jb` replace `dec` with `sub`. */ jl L(zero_len) # endif vpxor %VZERO_128, %VZERO_128, %VZERO_128 movl %esi, %eax andl $(PAGE_SIZE - 1), %eax cmpl $(PAGE_SIZE - VEC_SIZE), %eax ja L(page_cross) L(page_cross_continue): VMOVU (%rsi), %VMM(0) VPCMPEQ %VMM(0), %VZERO, %VMM(6) vpmovmskb %VMM(6), %ecx /* If no STPCPY just save end ahead of time. */ # ifndef USE_AS_STPCPY movq %rdi, %rax # elif defined USE_AS_WCSCPY /* Clear dependency as nearly all return code for wcpncpy uses `setc %al`. */ xorl %eax, %eax # endif cmpq $(VEC_SIZE - CHAR_SIZE), %rdx /* `jb` because length rdx is now length - CHAR_SIZE. */ jbe L(less_1x_vec) /* This may overset but thats fine because we still need to zero fill. */ VMOVU %VMM(0), (%rdi) testl %ecx, %ecx jnz L(zfill) /* Align. */ addq %rsi, %rdx subq %rsi, %rdi orq $(VEC_SIZE - 1), %rsi incq %rsi L(last_4x_vec): addq %rsi, %rdi L(loop_last_4x_vec): subq %rsi, %rdx VMOVA 0(%rsi), %VMM(1) VPCMPEQ %VMM(1), %VZERO, %VMM(6) vpmovmskb %VMM(6), %ecx cmpq $(VEC_SIZE * 2), %rdx jae L(more_2x_vec) cmpl $(VEC_SIZE), %edx jb L(ret_vec_x1_len) testl %ecx, %ecx jnz L(ret_vec_x1) VPCMPEQ VEC_SIZE(%rsi), %VZERO, %VMM(6) VMOVU %VMM(1), (%rdi) vpmovmskb %VMM(6), %ecx shlq $VEC_SIZE, %rcx L(ret_vec_x1_len): tzcntq %rcx, %rcx cmpl %ecx, %edx jbe L(ret_vec_x1_len_no_zfill) /* Fall through (expectation) is copy len < buffer len. */ VMOVU %VZERO, ((0)-(VEC_SIZE - CHAR_SIZE))(%rdi, %rdx) L(ret_vec_x1_len_no_zfill_mov): movl %ecx, %edx # ifdef USE_AS_STPCPY /* clear flags. */ xorl %ecx, %ecx # endif L(ret_vec_x1_len_no_zfill): VMOVU ((0)-(VEC_SIZE - CHAR_SIZE))(%rsi, %rdx), %VMM(1) VMOVU %VMM(1), ((0)-(VEC_SIZE - CHAR_SIZE))(%rdi, %rdx) # ifdef USE_AS_STPCPY # ifdef USE_AS_WCSCPY setc %al addq %rdx, %rdi leaq (%rdi, %rax, CHAR_SIZE), %rax # else movl %edx, %eax adcq %rdi, %rax # endif # endif L(return_vzeroupper): ZERO_UPPER_VEC_REGISTERS_RETURN .p2align 4,, 6 L(ret_vec_x1): bsfl %ecx, %ecx VMOVU %VZERO, ((0)-(VEC_SIZE - CHAR_SIZE))(%rdi, %rdx) subl %ecx, %edx /* Check if we need to reload/store. */ cmpl $VEC_SIZE, %edx jb L(ret_vec_x1_len_no_zfill_mov) /* Otherwise safe to just store directly. */ VMOVU %VMM(1), (%rdi) VMOVU %VZERO, (%rdi, %rcx) # ifdef USE_AS_STPCPY leaq (%rdi, %rcx), %rax # endif VZEROUPPER_RETURN .p2align 4,, 12 L(more_2x_vec): VMOVU %VMM(1), (%rdi) testl %ecx, %ecx /* Must fill at least 2x VEC. */ jnz L(zfill_vec1) VMOVA VEC_SIZE(%rsi), %VMM(2) VMOVU %VMM(2), VEC_SIZE(%rdi) VPCMPEQ %VMM(2), %VZERO, %VMM(6) vpmovmskb %VMM(6), %ecx testl %ecx, %ecx /* Must fill at least 1x VEC. */ jnz L(zfill_vec2) VMOVA (VEC_SIZE * 2)(%rsi), %VMM(3) VPCMPEQ %VMM(3), %VZERO, %VMM(6) vpmovmskb %VMM(6), %ecx /* Check if len is more 4x VEC. -CHAR_SIZE because rdx is len - CHAR_SIZE. */ cmpq $(VEC_SIZE * 4 - CHAR_SIZE), %rdx ja L(more_4x_vec) subl $(VEC_SIZE * 3), %edx jb L(ret_vec_x3_len) testl %ecx, %ecx jnz L(ret_vec_x3) VPCMPEQ (VEC_SIZE * 3)(%rsi), %VZERO, %VMM(6) VMOVU %VMM(3), (VEC_SIZE * 2)(%rdi) vpmovmskb %VMM(6), %ecx tzcntl %ecx, %ecx cmpl %ecx, %edx jbe L(ret_vec_x4_len_no_zfill) /* Fall through (expectation) is copy len < buffer len. */ VMOVU %VZERO, ((VEC_SIZE * 3)-(VEC_SIZE - CHAR_SIZE))(%rdi, %rdx) movl %ecx, %edx L(ret_vec_x4_len_no_zfill): VMOVU ((VEC_SIZE * 3)-(VEC_SIZE - CHAR_SIZE))(%rsi, %rdx), %VMM(1) VMOVU %VMM(1), ((VEC_SIZE * 3)-(VEC_SIZE - CHAR_SIZE))(%rdi, %rdx) # ifdef USE_AS_STPCPY # ifdef USE_AS_WCSCPY setc %al addq %rdx, %rdi leaq (VEC_SIZE * 3)(%rdi, %rax, CHAR_SIZE), %rax # else leal (VEC_SIZE * 3 + 0)(%edx), %eax adcq %rdi, %rax # endif # endif VZEROUPPER_RETURN L(ret_vec_x3_len): addl $(VEC_SIZE * 1), %edx tzcntl %ecx, %ecx cmpl %ecx, %edx jbe L(ret_vec_x3_len_no_zfill) /* Fall through (expectation) is copy len < buffer len. */ VMOVU %VZERO, ((VEC_SIZE * 2)-(VEC_SIZE - CHAR_SIZE))(%rdi, %rdx) L(ret_vec_x3_len_no_zfill_mov): movl %ecx, %edx # ifdef USE_AS_STPCPY /* clear flags. */ xorl %ecx, %ecx # endif .p2align 4,, 4 L(ret_vec_x3_len_no_zfill): VMOVU ((VEC_SIZE * 2)-(VEC_SIZE - CHAR_SIZE))(%rsi, %rdx), %VMM(1) VMOVU %VMM(1), ((VEC_SIZE * 2)-(VEC_SIZE - CHAR_SIZE))(%rdi, %rdx) # ifdef USE_AS_STPCPY # ifdef USE_AS_WCSCPY setc %al addq %rdx, %rdi leaq (VEC_SIZE * 2)(%rdi, %rax, CHAR_SIZE), %rax # else leal (VEC_SIZE * 2 + 0)(%rdx), %eax adcq %rdi, %rax # endif # endif VZEROUPPER_RETURN .p2align 4,, 8 L(ret_vec_x3): bsfl %ecx, %ecx VMOVU %VZERO, (VEC_SIZE * 3 +(-(VEC_SIZE - CHAR_SIZE)))(%rdi, %rdx) subl %ecx, %edx jl L(ret_vec_x3_len_no_zfill_mov) VMOVU %VMM(3), (VEC_SIZE * 2)(%rdi) VMOVU %VZERO, (VEC_SIZE * 2)(%rdi, %rcx) # ifdef USE_AS_STPCPY leaq (VEC_SIZE * 2)(%rdi, %rcx), %rax # endif VZEROUPPER_RETURN .p2align 4,, 8 L(more_4x_vec): VMOVU %VMM(3), (VEC_SIZE * 2)(%rdi) testl %ecx, %ecx jnz L(zfill_vec3) VMOVA (VEC_SIZE * 3)(%rsi), %VMM(4) VMOVU %VMM(4), (VEC_SIZE * 3)(%rdi) VPCMPEQ %VMM(4), %VZERO, %VMM(6) vpmovmskb %VMM(6), %ecx testl %ecx, %ecx jnz L(zfill_vec4) movq %rdx, %rcx addq %rsi, %rdx subq %rsi, %rdi subq $-(VEC_SIZE * 4), %rsi /* Recheck length before aligning. */ cmpq $(VEC_SIZE * 8 - CHAR_SIZE), %rcx jbe L(last_4x_vec) andq $(VEC_SIZE * -4), %rsi /* Do first half of loop ahead of time so loop can just start by storing. */ VMOVA (VEC_SIZE * 0 + 0)(%rsi), %VMM(0) VMOVA (VEC_SIZE * 1 + 0)(%rsi), %VMM(1) VMOVA (VEC_SIZE * 2 + 0)(%rsi), %VMM(2) VMOVA (VEC_SIZE * 3 + 0)(%rsi), %VMM(3) VPMIN %VMM(0), %VMM(1), %VMM(4) VPMIN %VMM(2), %VMM(3), %VMM(6) VPMIN %VMM(4), %VMM(6), %VMM(6) VPCMPEQ %VMM(6), %VZERO, %VMM(6) vpmovmskb %VMM(6), %r8d addq %rsi, %rdi testl %r8d, %r8d jnz L(loop_4x_done) /* Use r9 as end register. */ leaq -(VEC_SIZE * 4 - CHAR_SIZE)(%rdx), %r9 .p2align 4,, 11 L(loop_4x_vec): VMOVU %VMM(0), (VEC_SIZE * 0 + 0)(%rdi) VMOVU %VMM(1), (VEC_SIZE * 1 + 0)(%rdi) subq $(VEC_SIZE * -4), %rsi VMOVU %VMM(2), (VEC_SIZE * 2 + 0)(%rdi) VMOVU %VMM(3), (VEC_SIZE * 3 + 0)(%rdi) subq $(VEC_SIZE * -4), %rdi cmpq %rsi, %r9 jbe L(loop_last_4x_vec) VMOVA (VEC_SIZE * 0 + 0)(%rsi), %VMM(0) VMOVA (VEC_SIZE * 1 + 0)(%rsi), %VMM(1) VMOVA (VEC_SIZE * 2 + 0)(%rsi), %VMM(2) VMOVA (VEC_SIZE * 3 + 0)(%rsi), %VMM(3) VPMIN %VMM(0), %VMM(1), %VMM(4) VPMIN %VMM(2), %VMM(3), %VMM(6) VPMIN %VMM(4), %VMM(6), %VMM(6) VPCMPEQ %VMM(6), %VZERO, %VMM(6) vpmovmskb %VMM(6), %r8d testl %r8d, %r8d jz L(loop_4x_vec) L(loop_4x_done): subq %rsi, %rdx VMOVU %VMM(0), (VEC_SIZE * 0 + 0)(%rdi) VPCMPEQ %VMM(0), %VZERO, %VMM(6) vpmovmskb %VMM(6), %ecx testl %ecx, %ecx jnz L(zfill_vec1) VMOVU %VMM(1), (VEC_SIZE * 1 + 0)(%rdi) VPCMPEQ %VMM(1), %VZERO, %VMM(6) vpmovmskb %VMM(6), %ecx testl %ecx, %ecx jnz L(zfill_vec2) VMOVU %VMM(2), (VEC_SIZE * 2 + 0)(%rdi) VPCMPEQ %VMM(2), %VZERO, %VMM(6) vpmovmskb %VMM(6), %ecx testl %ecx, %ecx jnz L(zfill_vec3) VMOVU %VMM(3), (VEC_SIZE * 3 + 0)(%rdi) movl %r8d, %ecx // Zfill more.... .p2align 4,, 4 L(zfill_vec4): addq $(VEC_SIZE * 2), %rdi subq $(VEC_SIZE * 2), %rdx L(zfill_vec2): shlq $VEC_SIZE, %rcx L(zfill): bsfq %rcx, %rcx subq %rcx, %rdx addq %rcx, %rdi # ifdef USE_AS_STPCPY movq %rdi, %rax # endif L(zfill_from_page_cross): cmpq $VEC_SIZE, %rdx jb L(zfill_less_vec_vzeroupper) L(zfill_more_1x_vec): VMOVU %VZERO, CHAR_SIZE(%rdi) VMOVU %VZERO, (CHAR_SIZE - VEC_SIZE)(%rdi, %rdx) cmpq $(VEC_SIZE * 2), %rdx jae L(zfill_more_2x_vec) L(zfill_done0): VZEROUPPER_RETURN .p2align 4,, 8 L(zfill_vec3): addq $(VEC_SIZE * 2), %rdi subq $(VEC_SIZE * 2), %rdx .p2align 4,, 2 L(zfill_vec1): bsfl %ecx, %ecx addq %rcx, %rdi subq %rcx, %rdx # ifdef USE_AS_STPCPY movq %rdi, %rax # endif /* zfill from vec1/vec3 must have to set at least 2x VECS. */ VMOVU %VZERO, CHAR_SIZE(%rdi) VMOVU %VZERO, (CHAR_SIZE - VEC_SIZE)(%rdi, %rdx) cmpq $(VEC_SIZE * 2), %rdx jb L(zfill_done0) L(zfill_more_2x_vec): VMOVU %VZERO, (CHAR_SIZE - VEC_SIZE * 2)(%rdi, %rdx) VMOVU %VZERO, (VEC_SIZE + CHAR_SIZE)(%rdi) subq $(VEC_SIZE * 4 - CHAR_SIZE), %rdx jbe L(zfill_done) addq %rdi, %rdx VMOVU %VZERO, (VEC_SIZE * 2 + CHAR_SIZE)(%rdi) VMOVU %VZERO, (VEC_SIZE * 3 + CHAR_SIZE)(%rdi) VMOVU %VZERO, (VEC_SIZE * 0 + 0)(%rdx) VMOVU %VZERO, (VEC_SIZE * 1 + 0)(%rdx) subq $-(VEC_SIZE * 4 + CHAR_SIZE), %rdi cmpq %rdi, %rdx jbe L(zfill_done) andq $-(VEC_SIZE), %rdi .p2align 4,, 12 L(zfill_loop_4x_vec): VMOVA %VZERO, (VEC_SIZE * 0)(%rdi) VMOVA %VZERO, (VEC_SIZE * 1)(%rdi) VMOVA %VZERO, (VEC_SIZE * 2)(%rdi) VMOVA %VZERO, (VEC_SIZE * 3)(%rdi) subq $-(VEC_SIZE * 4), %rdi cmpq %rdi, %rdx ja L(zfill_loop_4x_vec) L(zfill_done): VZEROUPPER_RETURN .p2align 4,, 8 L(copy_1x): VMOVU %VMM(0), (%rdi) testl %ecx, %ecx jz L(ret_32_32) L(zfill_less_vec): bsfl %ecx, %ecx L(zfill_less_vec_no_bsf): subq %rcx, %rdx addq %rcx, %rdi # ifdef USE_AS_STPCPY movq %rdi, %rax # endif L(zfill_less_vec_vzeroupper): COND_VZEROUPPER /* We are taking advantage of the fact that to be here we must be writing null-term as (%rdi, %rcx) we have a byte of lee- way for overwriting. */ cmpl $16, %edx jb L(zfill_less_16) VMOVU %VZERO_128, (%rdi) VMOVU %VZERO_128, -(16 - CHAR_SIZE)(%rdi, %rdx) ret # ifdef USE_AS_STPCPY L(ret_32_32): leaq CHAR_SIZE(%rdi, %rdx), %rax VZEROUPPER_RETURN # endif .p2align 4,, 4 L(copy_16_31): /* Overfill to avoid branches. */ vmovdqu -(16 - CHAR_SIZE)(%rsi, %rdx), %xmm1 vmovdqu %xmm0, (%rdi) vmovdqu %xmm1, -(16 - CHAR_SIZE)(%rdi, %rdx) cmpl %ecx, %edx ja L(zfill_less_vec_no_bsf) # ifndef USE_AS_STPCPY L(ret_32_32): # else # ifdef USE_AS_WCSCPY setc %al addq %rdx, %rdi leaq (%rdi, %rax, CHAR_SIZE), %rax # else movl %edx, %eax adcq %rdi, %rax # endif # endif VZEROUPPER_RETURN .p2align 4,, 4 L(copy_8_15): /* Overfill to avoid branches. */ movq -(8 - CHAR_SIZE)(%rsi, %rdx), %rsi vmovq %xmm0, (%rdi) movq %rsi, -(8 - CHAR_SIZE)(%rdi, %rdx) cmpl %ecx, %edx jbe L(ret_8_15) subq %rcx, %rdx addq %rcx, %rdi # ifdef USE_AS_STPCPY movq %rdi, %rax # endif .p2align 4,, 8 L(zfill_less_16): xorl %ecx, %ecx cmpl $8, %edx jb L(zfill_less_8) movq %rcx, (%rdi) movq %rcx, -(8 - CHAR_SIZE)(%rdi, %rdx) # ifndef USE_AS_STPCPY L(ret_8_15): # endif ret .p2align 4,, 8 L(less_1x_vec): /* Reuse flag from `cmp $VEC_SIZE, %rdx`. The idea is many buffer sizes are aligned conventially. */ je L(copy_1x) tzcntl %ecx, %ecx cmpl $16, %edx jae L(copy_16_31) COND_VZEROUPPER cmpl $8, %edx jae L(copy_8_15) # ifdef USE_AS_WCSCPY testl %ecx, %ecx jz L(zfill_less_8_set_ret) movl (%rsi, %rdx), %esi vmovd %xmm0, (%rdi) movl %esi, (%rdi, %rdx) # ifdef USE_AS_STPCPY cmpl %ecx, %edx L(ret_8_15): setc %al addq %rdx, %rdi leaq (%rdi, %rax, CHAR_SIZE), %rax # endif ret L(zfill_less_8_set_ret): xorl %ecx, %ecx # ifdef USE_AS_STPCPY movq %rdi, %rax # endif L(zfill_less_8): movl %ecx, (%rdi) movl %ecx, (%rdi, %rdx) ret # else cmpl $3, %edx jb L(copy_0_3) /* Overfill to avoid branches. */ movl -3(%rsi, %rdx), %esi vmovd %xmm0, (%rdi) movl %esi, -3(%rdi, %rdx) cmpl %ecx, %edx jbe L(ret_4_7) subq %rcx, %rdx addq %rcx, %rdi # ifdef USE_AS_STPCPY movq %rdi, %rax # endif xorl %ecx, %ecx .p2align 4,, 8 L(zfill_less_8): cmpl $3, %edx jb L(zfill_less_3) movl %ecx, (%rdi) movl %ecx, -3(%rdi, %rdx) # ifdef USE_AS_STPCPY ret # endif L(ret_4_7): # ifdef USE_AS_STPCPY L(ret_8_15): movl %edx, %eax adcq %rdi, %rax # endif ret .p2align 4,, 4 L(zfill_less_3): testl %edx, %edx jz L(zfill_1) movw %cx, (%rdi) L(zfill_1): movb %cl, (%rdi, %rdx) ret .p2align 4,, 8 L(copy_0_3): vmovd %xmm0, %r8d testl %edx, %edx jz L(copy_1) movw %r8w, (%rdi) cmpl %ecx, %edx ja L(zfill_from_1) movzbl (%rsi, %rdx), %r8d # ifdef USE_AS_STPCPY movl %edx, %eax adcq %rdi, %rax movb %r8b, (%rdi, %rdx) ret # endif L(copy_1): # ifdef USE_AS_STPCPY movl %edx, %eax cmpl %ecx, %edx adcq %rdi, %rax # endif # ifdef USE_AS_WCSCPY vmovd %xmm0, (%rdi) # else movb %r8b, (%rdi, %rdx) # endif ret # endif .p2align 4,, 2 L(zero_len): movq %rdi, %rax ret # ifndef USE_AS_WCSCPY .p2align 4,, 8 L(zfill_from_1): # ifdef USE_AS_STPCPY leaq (%rdi, %rcx), %rax # endif movw $0, -1(%rdi, %rdx) ret # endif .p2align 4,, 4 .p2align 6,, 8 L(page_cross): movq %rsi, %rax andq $(VEC_SIZE * -1), %rax VPCMPEQ (%rax), %VZERO, %VMM(6) vpmovmskb %VMM(6), %ecx shrxl %esi, %ecx, %ecx subl %esi, %eax andl $(VEC_SIZE - 1), %eax cmpq %rax, %rdx jb L(page_cross_small) /* Optimizing more aggressively for space as this is very cold code. This saves 2x cache lines. */ /* If rcx is non-zero then continue. */ shl $CHAR_SIZE, %ecx jz L(page_cross_continue) bsf %ecx, %ecx subq %rcx, %rdx # ifdef USE_AS_STPCPY leaq -CHAR_SIZE(%rdi, %rcx), %rax # else movq %rdi, %rax # endif rep movsb # ifdef USE_AS_WCSCPY movl $0, (%rdi) # else movb $0, (%rdi) # endif jmp L(zfill_from_page_cross) L(page_cross_small): tzcntl %ecx, %ecx xorl %eax, %eax cmpl %ecx, %edx jbe L(page_cross_copy_only) /* Do a zfill of the tail before copying. */ movq %rdi, %r9 movl %ecx, %r8d subl %ecx, %edx leaq CHAR_SIZE(%rdi, %rcx), %rdi movl %edx, %ecx rep stosb movq %r9, %rdi movl %r8d, %edx L(page_cross_copy_only): leal CHAR_SIZE(%rdx), %ecx # ifdef USE_AS_STPCPY # ifdef USE_AS_WCSCPY setc %al addq %rdi, %rdx leaq (%rdx, %rax, CHAR_SIZE), %rax # else movl %edx, %eax adcq %rdi, %rax # endif # else movq %rdi, %rax # endif rep movsb ret L(best_effort_strncpy): movq %rdx, %rcx xorl %eax, %eax movq %rdi, %r8 /* The length is >= 2^63. We very much so expect to segfault at rep stos. If that doesn't happen then just strcpy to finish. */ # ifdef USE_AS_WCSCPY rep stosl # else rep stosb # endif movq %r8, %rdi jmp OVERFLOW_STRCPY END(STRNCPY) #endif