]> sourceware.org Git - glibc.git/commitdiff
aarch64: MTE compatible strncmp
authorAlex Butler <Alex.Butler@arm.com>
Tue, 16 Jun 2020 12:44:24 +0000 (12:44 +0000)
committerSzabolcs Nagy <szabolcs.nagy@arm.com>
Tue, 23 Jun 2020 16:55:39 +0000 (17:55 +0100)
Add support for MTE to strncmp. Regression tested with xcheck and benchmarked
with glibc's benchtests on the Cortex-A53, Cortex-A72, and Neoverse N1.

The existing implementation assumes that any access to the pages in which the
string resides is safe. This assumption is not true when MTE is enabled. This
patch updates the algorithm to ensure that accesses remain within the bounds
of an MTE tag (16-byte chunks) and improves overall performance.

Co-authored-by: Branislav Rankov <branislav.rankov@arm.com>
Co-authored-by: Wilco Dijkstra <wilco.dijkstra@arm.com>
sysdeps/aarch64/strncmp.S

index c5141fab8ae230fc2e10b19ed836d181cdeab1dc..ba2563490e0c22a82bc5673d3f25f8262e290afc 100644 (file)
@@ -25,7 +25,6 @@
 
 #define REP8_01 0x0101010101010101
 #define REP8_7f 0x7f7f7f7f7f7f7f7f
-#define REP8_80 0x8080808080808080
 
 /* Parameters and result.  */
 #define src1           x0
 #define tmp3           x10
 #define zeroones       x11
 #define pos            x12
-#define limit_wd       x13
-#define mask           x14
-#define endloop                x15
+#define mask           x13
+#define endloop                x14
 #define count          mask
+#define offset         pos
+#define neg_offset     x15
 
-ENTRY_ALIGN_AND_PAD (strncmp, 6, 7)
-       DELOUSE (0)
-       DELOUSE (1)
-       DELOUSE (2)
+/* Define endian dependent shift operations.
+   On big-endian early bytes are at MSB and on little-endian LSB.
+   LS_FW means shifting towards early bytes.
+   LS_BK means shifting towards later bytes.
+   */
+#ifdef __AARCH64EB__
+#define LS_FW lsl
+#define LS_BK lsr
+#else
+#define LS_FW lsr
+#define LS_BK lsl
+#endif
+
+       .text
+       .p2align 6
+       .rep 9
+       nop     /* Pad so that the loop below fits a cache line.  */
+       .endr
+ENTRY_ALIGN (strncmp, 0)
        cbz     limit, L(ret0)
        eor     tmp1, src1, src2
        mov     zeroones, #REP8_01
@@ -62,9 +77,6 @@ ENTRY_ALIGN_AND_PAD (strncmp, 6, 7)
        and     count, src1, #7
        b.ne    L(misaligned8)
        cbnz    count, L(mutual_align)
-       /* Calculate the number of full and partial words -1.  */
-       sub     limit_wd, limit, #1     /* limit != 0, so no underflow.  */
-       lsr     limit_wd, limit_wd, #3  /* Convert to Dwords.  */
 
        /* NUL detection works on the principle that (X - 1) & (~X) & 0x80
           (=> (X - 1) & ~(X | 0x7f)) is non-zero iff a byte is zero, and
@@ -74,56 +86,52 @@ L(loop_aligned):
        ldr     data1, [src1], #8
        ldr     data2, [src2], #8
 L(start_realigned):
-       subs    limit_wd, limit_wd, #1
+       subs    limit, limit, #8
        sub     tmp1, data1, zeroones
        orr     tmp2, data1, #REP8_7f
        eor     diff, data1, data2      /* Non-zero if differences found.  */
-       csinv   endloop, diff, xzr, pl  /* Last Dword or differences.  */
+       csinv   endloop, diff, xzr, hi  /* Last Dword or differences.  */
        bics    has_nul, tmp1, tmp2     /* Non-zero if NUL terminator.  */
        ccmp    endloop, #0, #0, eq
        b.eq    L(loop_aligned)
        /* End of performance-critical section  -- one 64B cache line.  */
 
-       /* Not reached the limit, must have found the end or a diff.  */
-       tbz     limit_wd, #63, L(not_limit)
-
-       /* Limit % 8 == 0 => all bytes significant.  */
-       ands    limit, limit, #7
-       b.eq    L(not_limit)
-
-       lsl     limit, limit, #3        /* Bits -> bytes.  */
-       mov     mask, #~0
-#ifdef __AARCH64EB__
-       lsr     mask, mask, limit
-#else
-       lsl     mask, mask, limit
-#endif
-       bic     data1, data1, mask
-       bic     data2, data2, mask
-
-       /* Make sure that the NUL byte is marked in the syndrome.  */
-       orr     has_nul, has_nul, mask
-
-L(not_limit):
+L(full_check):
+#ifndef __AARCH64EB__
        orr     syndrome, diff, has_nul
-
-#ifndef        __AARCH64EB__
+       add     limit, limit, 8 /* Rewind limit to before last subs. */
+L(syndrome_check):
+       /* Limit was reached. Check if the NUL byte or the difference
+          is before the limit. */
        rev     syndrome, syndrome
        rev     data1, data1
-       /* The MS-non-zero bit of the syndrome marks either the first bit
-          that is different, or the top bit of the first zero byte.
-          Shifting left now will bring the critical information into the
-          top bits.  */
        clz     pos, syndrome
        rev     data2, data2
        lsl     data1, data1, pos
+       cmp     limit, pos, lsr #3
        lsl     data2, data2, pos
        /* But we need to zero-extend (char is unsigned) the value and then
           perform a signed 32-bit subtraction.  */
        lsr     data1, data1, #56
        sub     result, data1, data2, lsr #56
-       RET
+       csel result, result, xzr, hi
+       ret
 #else
+       /* Not reached the limit, must have found the end or a diff.  */
+       tbz     limit, #63, L(not_limit)
+       add     tmp1, limit, 8
+       cbz     limit, L(not_limit)
+
+       lsl     limit, tmp1, #3 /* Bits -> bytes.  */
+       mov     mask, #~0
+       lsr     mask, mask, limit
+       bic     data1, data1, mask
+       bic     data2, data2, mask
+
+       /* Make sure that the NUL byte is marked in the syndrome.  */
+       orr     has_nul, has_nul, mask
+
+L(not_limit):
        /* For big-endian we cannot use the trick with the syndrome value
           as carry-propagation can corrupt the upper bits if the trailing
           bytes in the string contain 0x01.  */
@@ -134,7 +142,7 @@ L(not_limit):
        cmp     data1, data2
        cset    result, ne
        cneg    result, result, lo
-       RET
+       ret
 1:
        /* Re-compute the NUL-byte detection, using a byte-reversed value.  */
        rev     tmp3, data1
@@ -144,17 +152,18 @@ L(not_limit):
        rev     has_nul, has_nul
        orr     syndrome, diff, has_nul
        clz     pos, syndrome
-       /* The MS-non-zero bit of the syndrome marks either the first bit
-          that is different, or the top bit of the first zero byte.
+       /* The most-significant-non-zero bit of the syndrome marks either the
+          first bit that is different, or the top bit of the first zero byte.
           Shifting left now will bring the critical information into the
           top bits.  */
+L(end_quick):
        lsl     data1, data1, pos
        lsl     data2, data2, pos
        /* But we need to zero-extend (char is unsigned) the value and then
           perform a signed 32-bit subtraction.  */
        lsr     data1, data1, #56
        sub     result, data1, data2, lsr #56
-       RET
+       ret
 #endif
 
 L(mutual_align):
@@ -169,22 +178,12 @@ L(mutual_align):
        neg     tmp3, count, lsl #3     /* 64 - bits(bytes beyond align). */
        ldr     data2, [src2], #8
        mov     tmp2, #~0
-       sub     limit_wd, limit, #1     /* limit != 0, so no underflow.  */
-#ifdef __AARCH64EB__
-       /* Big-endian.  Early bytes are at MSB.  */
-       lsl     tmp2, tmp2, tmp3        /* Shift (count & 63).  */
-#else
-       /* Little-endian.  Early bytes are at LSB.  */
-       lsr     tmp2, tmp2, tmp3        /* Shift (count & 63).  */
-#endif
-       and     tmp3, limit_wd, #7
-       lsr     limit_wd, limit_wd, #3
-       /* Adjust the limit. Only low 3 bits used, so overflow irrelevant.  */
-       add     limit, limit, count
-       add     tmp3, tmp3, count
+       LS_FW   tmp2, tmp2, tmp3        /* Shift (count & 63).  */
+       /* Adjust the limit and ensure it doesn't overflow.  */
+       adds    limit, limit, count
+       csinv   limit, limit, xzr, lo
        orr     data1, data1, tmp2
        orr     data2, data2, tmp2
-       add     limit_wd, limit_wd, tmp3, lsr #3
        b       L(start_realigned)
 
        .p2align 6
@@ -203,18 +202,15 @@ L(byte_loop):
        b.eq    L(byte_loop)
 L(done):
        sub     result, data1, data2
-       RET
-
+       ret
        /* Align the SRC1 to a dword by doing a bytewise compare and then do
           the dword loop.  */
 L(try_misaligned_words):
-       lsr     limit_wd, limit, #3
-       cbz     count, L(do_misaligned)
+       cbz     count, L(src1_aligned)
 
        neg     count, count
        and     count, count, #7
        sub     limit, limit, count
-       lsr     limit_wd, limit, #3
 
 L(page_end_loop):
        ldrb    data1w, [src1], #1
@@ -225,48 +221,98 @@ L(page_end_loop):
        subs    count, count, #1
        b.hi    L(page_end_loop)
 
-L(do_misaligned):
-       /* Prepare ourselves for the next page crossing.  Unlike the aligned
-          loop, we fetch 1 less dword because we risk crossing bounds on
-          SRC2.  */
-       mov     count, #8
-       subs    limit_wd, limit_wd, #1
-       b.lo    L(done_loop)
+       /* The following diagram explains the comparison of misaligned strings.
+          The bytes are shown in natural order. For little-endian, it is
+          reversed in the registers. The "x" bytes are before the string.
+          The "|" separates data that is loaded at one time.
+          src1     | a a a a a a a a | b b b c c c c c | . . .
+          src2     | x x x x x a a a   a a a a a b b b | c c c c c . . .
+          After shifting in each step, the data looks like this:
+                       STEP_A              STEP_B              STEP_C
+          data1    a a a a a a a a     b b b c c c c c     b b b c c c c c
+          data2    a a a a a a a a     b b b 0 0 0 0 0     0 0 0 c c c c c
+          The bytes with "0" are eliminated from the syndrome via mask.
+          Align SRC2 down to 16 bytes. This way we can read 16 bytes at a
+          time from SRC2. The comparison happens in 3 steps. After each step
+          the loop can exit, or read from SRC1 or SRC2. */
+L(src1_aligned):
+       /* Calculate offset from 8 byte alignment to string start in bits. No
+          need to mask offset since shifts are ignoring upper bits. */
+       lsl     offset, src2, #3
+       bic     src2, src2, #0xf
+       mov     mask, -1
+       neg     neg_offset, offset
+       ldr     data1, [src1], #8
+       ldp     tmp1, tmp2, [src2], #16
+       LS_BK   mask, mask, neg_offset
+       and     neg_offset, neg_offset, #63     /* Need actual value for cmp later. */
+       /* Skip the first compare if data in tmp1 is irrelevant. */
+       tbnz    offset, 6, L(misaligned_mid_loop)
+
 L(loop_misaligned):
-       and     tmp2, src2, #0xff8
-       eor     tmp2, tmp2, #0xff8
-       cbz     tmp2, L(page_end_loop)
+       /* STEP_A: Compare full 8 bytes when there is enough data from SRC2.*/
+       LS_FW   data2, tmp1, offset
+       LS_BK   tmp1, tmp2, neg_offset
+       subs    limit, limit, #8
+       orr     data2, data2, tmp1      /* 8 bytes from SRC2 combined from two regs.*/
+       sub     has_nul, data1, zeroones
+       eor     diff, data1, data2      /* Non-zero if differences found.  */
+       orr     tmp3, data1, #REP8_7f
+       csinv   endloop, diff, xzr, hi  /* If limit, set to all ones. */
+       bic     has_nul, has_nul, tmp3  /* Non-zero if NUL byte found in SRC1. */
+       orr     tmp3, endloop, has_nul
+       cbnz    tmp3, L(full_check)
 
        ldr     data1, [src1], #8
-       ldr     data2, [src2], #8
-       sub     tmp1, data1, zeroones
-       orr     tmp2, data1, #REP8_7f
-       eor     diff, data1, data2      /* Non-zero if differences found.  */
-       bics    has_nul, tmp1, tmp2     /* Non-zero if NUL terminator.  */
-       ccmp    diff, #0, #0, eq
-       b.ne    L(not_limit)
-       subs    limit_wd, limit_wd, #1
-       b.pl    L(loop_misaligned)
+L(misaligned_mid_loop):
+       /* STEP_B: Compare first part of data1 to second part of tmp2. */
+       LS_FW   data2, tmp2, offset
+#ifdef __AARCH64EB__
+       /* For big-endian we do a byte reverse to avoid carry-propagation
+       problem described above. This way we can reuse the has_nul in the
+       next step and also use syndrome value trick at the end. */
+       rev     tmp3, data1
+       #define data1_fixed tmp3
+#else
+       #define data1_fixed data1
+#endif
+       sub     has_nul, data1_fixed, zeroones
+       orr     tmp3, data1_fixed, #REP8_7f
+       eor     diff, data2, data1      /* Non-zero if differences found.  */
+       bic     has_nul, has_nul, tmp3  /* Non-zero if NUL terminator.  */
+#ifdef __AARCH64EB__
+       rev     has_nul, has_nul
+#endif
+       cmp     limit, neg_offset, lsr #3
+       orr     syndrome, diff, has_nul
+       bic     syndrome, syndrome, mask        /* Ignore later bytes. */
+       csinv   tmp3, syndrome, xzr, hi /* If limit, set to all ones. */
+       cbnz    tmp3, L(syndrome_check)
 
-L(done_loop):
-       /* We found a difference or a NULL before the limit was reached.  */
-       and     limit, limit, #7
-       cbz     limit, L(not_limit)
-       /* Read the last word.  */
-       sub     src1, src1, 8
-       sub     src2, src2, 8
-       ldr     data1, [src1, limit]
-       ldr     data2, [src2, limit]
-       sub     tmp1, data1, zeroones
-       orr     tmp2, data1, #REP8_7f
-       eor     diff, data1, data2      /* Non-zero if differences found.  */
-       bics    has_nul, tmp1, tmp2     /* Non-zero if NUL terminator.  */
-       ccmp    diff, #0, #0, eq
-       b.ne    L(not_limit)
+       /* STEP_C: Compare second part of data1 to first part of tmp1. */
+       ldp     tmp1, tmp2, [src2], #16
+       cmp     limit, #8
+       LS_BK   data2, tmp1, neg_offset
+       eor     diff, data2, data1      /* Non-zero if differences found.  */
+       orr     syndrome, diff, has_nul
+       and     syndrome, syndrome, mask        /* Ignore earlier bytes. */
+       csinv   tmp3, syndrome, xzr, hi /* If limit, set to all ones. */
+       cbnz    tmp3, L(syndrome_check)
+
+       ldr     data1, [src1], #8
+       sub     limit, limit, #8
+       b       L(loop_misaligned)
+
+#ifdef __AARCH64EB__
+L(syndrome_check):
+       clz     pos, syndrome
+       cmp     pos, limit, lsl #3
+       b.lo    L(end_quick)
+#endif
 
 L(ret0):
        mov     result, #0
-       RET
+       ret
 
 END (strncmp)
 libc_hidden_builtin_def (strncmp)
This page took 0.051324 seconds and 5 git commands to generate.