1/*
2 * Copyright (c) 2013 ARM Ltd
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. The name of the company may not be used to endorse or promote
14 *    products derived from this software without specific prior written
15 *    permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY ARM LTD ``AS IS'' AND ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL ARM LTD BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
22 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include <private/bionic_asm.h>
30
31#ifdef __ARMEB__
32#define S2LOMEM lsl
33#define S2LOMEMEQ lsleq
34#define S2HIMEM lsr
35#define MSB 0x000000ff
36#define LSB 0xff000000
37#define BYTE0_OFFSET 24
38#define BYTE1_OFFSET 16
39#define BYTE2_OFFSET 8
40#define BYTE3_OFFSET 0
41#else /* not  __ARMEB__ */
42#define S2LOMEM lsr
43#define S2LOMEMEQ lsreq
44#define S2HIMEM lsl
45#define BYTE0_OFFSET 0
46#define BYTE1_OFFSET 8
47#define BYTE2_OFFSET 16
48#define BYTE3_OFFSET 24
49#define MSB 0xff000000
50#define LSB 0x000000ff
51#endif /* not  __ARMEB__ */
52
53.syntax         unified
54
55#if defined (__thumb__)
56        .thumb
57        .thumb_func
58#endif
59
60        // To avoid warning about deprecated instructions, add an explicit
61        // arch. The code generated is exactly the same.
62        .arch armv7-a
63
64ENTRY(strcmp)
65      /* Use LDRD whenever possible.  */
66
67/* The main thing to look out for when comparing large blocks is that
68   the loads do not cross a page boundary when loading past the index
69   of the byte with the first difference or the first string-terminator.
70
71   For example, if the strings are identical and the string-terminator
72   is at index k, byte by byte comparison will not load beyond address
73   s1+k and s2+k; word by word comparison may load up to 3 bytes beyond
74   k; double word - up to 7 bytes.  If the load of these bytes crosses
75   a page boundary, it might cause a memory fault (if the page is not mapped)
76   that would not have happened in byte by byte comparison.
77
78   If an address is (double) word aligned, then a load of a (double) word
79   from that address will not cross a page boundary.
80   Therefore, the algorithm below considers word and double-word alignment
81   of strings separately.  */
82
83/* High-level description of the algorithm.
84
85   * The fast path: if both strings are double-word aligned,
86     use LDRD to load two words from each string in every loop iteration.
87   * If the strings have the same offset from a word boundary,
88     use LDRB to load and compare byte by byte until
89     the first string is aligned to a word boundary (at most 3 bytes).
90     This is optimized for quick return on short unaligned strings.
91   * If the strings have the same offset from a double-word boundary,
92     use LDRD to load two words from each string in every loop iteration, as in the fast path.
93   * If the strings do not have the same offset from a double-word boundary,
94     load a word from the second string before the loop to initialize the queue.
95     Use LDRD to load two words from every string in every loop iteration.
96     Inside the loop, load the second word from the second string only after comparing
97     the first word, using the queued value, to guarantee safety across page boundaries.
98   * If the strings do not have the same offset from a word boundary,
99     use LDR and a shift queue. Order of loads and comparisons matters,
100     similarly to the previous case.
101
102   * Use UADD8 and SEL to compare words, and use REV and CLZ to compute the return value.
103   * The only difference between ARM and Thumb modes is the use of CBZ instruction.
104   * The only difference between big and little endian is the use of REV in little endian
105     to compute the return value, instead of MOV.
106*/
107
108        .macro m_cbz reg label
109#ifdef __thumb2__
110        cbz     \reg, \label
111#else   /* not defined __thumb2__ */
112        cmp     \reg, #0
113        beq     \label
114#endif /* not defined __thumb2__ */
115        .endm /* m_cbz */
116
117        .macro m_cbnz reg label
118#ifdef __thumb2__
119        cbnz    \reg, \label
120#else   /* not defined __thumb2__ */
121        cmp     \reg, #0
122        bne     \label
123#endif /* not defined __thumb2__ */
124        .endm /* m_cbnz */
125
126        .macro  init
127        /* Macro to save temporary registers and prepare magic values.  */
128        subs    sp, sp, #16
129        .cfi_def_cfa_offset 16
130        strd    r4, r5, [sp, #8]
131        .cfi_rel_offset r4, 0
132        .cfi_rel_offset r5, 4
133        strd    r6, r7, [sp]
134        .cfi_rel_offset r6, 8
135        .cfi_rel_offset r7, 12
136        mvn     r6, #0  /* all F */
137        mov     r7, #0  /* all 0 */
138        .endm   /* init */
139
140        .macro  magic_compare_and_branch w1 w2 label
141        /* Macro to compare registers w1 and w2 and conditionally branch to label.  */
142        cmp     \w1, \w2        /* Are w1 and w2 the same?  */
143        magic_find_zero_bytes \w1
144        it      eq
145        cmpeq   ip, #0          /* Is there a zero byte in w1?  */
146        bne     \label
147        .endm /* magic_compare_and_branch */
148
149        .macro  magic_find_zero_bytes w1
150        /* Macro to find all-zero bytes in w1, result is in ip.  */
151        uadd8   ip, \w1, r6
152        sel     ip, r7, r6
153        .endm /* magic_find_zero_bytes */
154
155        .macro  setup_return w1 w2
156#ifdef __ARMEB__
157        mov     r1, \w1
158        mov     r2, \w2
159#else /* not  __ARMEB__ */
160        rev     r1, \w1
161        rev     r2, \w2
162#endif /* not  __ARMEB__ */
163        .endm /* setup_return */
164
165        pld [r0, #0]
166        pld [r1, #0]
167
168        /* Are both strings double-word aligned?  */
169        orr     ip, r0, r1
170        tst     ip, #7
171        bne     .L_do_align
172
173        /* Fast path.  */
174        init
175
176.L_doubleword_aligned:
177
178        /* Get here when the strings to compare are double-word aligned.  */
179        /* Compare two words in every iteration.  */
180        .p2align        2
1812:
182        pld [r0, #16]
183        pld [r1, #16]
184
185        /* Load the next double-word from each string.  */
186        ldrd    r2, r3, [r0], #8
187        ldrd    r4, r5, [r1], #8
188
189        magic_compare_and_branch w1=r2, w2=r4, label=.L_return_24
190        magic_compare_and_branch w1=r3, w2=r5, label=.L_return_35
191        b       2b
192
193.L_do_align:
194        /* Is the first string word-aligned?  */
195        ands    ip, r0, #3
196        beq     .L_word_aligned_r0
197
198        /* Fast compare byte by byte until the first string is word-aligned.  */
199        /* The offset of r0 from a word boundary is in ip. Thus, the number of bytes
200        to read until the next word boundary is 4-ip.  */
201        bic     r0, r0, #3
202        ldr     r2, [r0], #4
203        lsls    ip, ip, #31
204        beq     .L_byte2
205        bcs     .L_byte3
206
207.L_byte1:
208        ldrb    ip, [r1], #1
209        uxtb    r3, r2, ror #BYTE1_OFFSET
210        subs    ip, r3, ip
211        bne     .L_fast_return
212        m_cbz   reg=r3, label=.L_fast_return
213
214.L_byte2:
215        ldrb    ip, [r1], #1
216        uxtb    r3, r2, ror #BYTE2_OFFSET
217        subs    ip, r3, ip
218        bne     .L_fast_return
219        m_cbz   reg=r3, label=.L_fast_return
220
221.L_byte3:
222        ldrb    ip, [r1], #1
223        uxtb    r3, r2, ror #BYTE3_OFFSET
224        subs    ip, r3, ip
225        bne     .L_fast_return
226        m_cbnz  reg=r3, label=.L_word_aligned_r0
227
228.L_fast_return:
229        mov     r0, ip
230        bx      lr
231
232.L_word_aligned_r0:
233        init
234        /* The first string is word-aligned.  */
235        /* Is the second string word-aligned?  */
236        ands    ip, r1, #3
237        bne     .L_strcmp_unaligned
238
239.L_word_aligned:
240        /* The strings are word-aligned. */
241        /* Is the first string double-word aligned?  */
242        tst     r0, #4
243        beq     .L_doubleword_aligned_r0
244
245        /* If r0 is not double-word aligned yet, align it by loading
246        and comparing the next word from each string.  */
247        ldr     r2, [r0], #4
248        ldr     r4, [r1], #4
249        magic_compare_and_branch w1=r2 w2=r4 label=.L_return_24
250
251.L_doubleword_aligned_r0:
252        /* Get here when r0 is double-word aligned.  */
253        /* Is r1 doubleword_aligned?  */
254        tst     r1, #4
255        beq     .L_doubleword_aligned
256
257        /* Get here when the strings to compare are word-aligned,
258        r0 is double-word aligned, but r1 is not double-word aligned.  */
259
260        /* Initialize the queue.  */
261        ldr     r5, [r1], #4
262
263        /* Compare two words in every iteration.  */
264        .p2align        2
2653:
266        pld [r0, #16]
267        pld [r1, #16]
268
269        /* Load the next double-word from each string and compare.  */
270        ldrd    r2, r3, [r0], #8
271        magic_compare_and_branch w1=r2 w2=r5 label=.L_return_25
272        ldrd    r4, r5, [r1], #8
273        magic_compare_and_branch w1=r3 w2=r4 label=.L_return_34
274        b       3b
275
276        .macro miscmp_word offsetlo offsethi
277        /* Macro to compare misaligned strings.  */
278        /* r0, r1 are word-aligned, and at least one of the strings
279        is not double-word aligned.  */
280        /* Compare one word in every loop iteration.  */
281        /* OFFSETLO is the original bit-offset of r1 from a word-boundary,
282        OFFSETHI is 32 - OFFSETLO (i.e., offset from the next word).  */
283
284        /* Initialize the shift queue.  */
285        ldr     r5, [r1], #4
286
287        /* Compare one word from each string in every loop iteration.  */
288        .p2align        2
2897:
290        ldr     r3, [r0], #4
291        S2LOMEM r5, r5, #\offsetlo
292        magic_find_zero_bytes w1=r3
293        cmp     r7, ip, S2HIMEM #\offsetlo
294        and     r2, r3, r6, S2LOMEM #\offsetlo
295        it      eq
296        cmpeq   r2, r5
297        bne     .L_return_25
298        ldr     r5, [r1], #4
299        cmp     ip, #0
300        eor r3, r2, r3
301        S2HIMEM r2, r5, #\offsethi
302        it      eq
303        cmpeq   r3, r2
304        bne     .L_return_32
305        b       7b
306        .endm /* miscmp_word */
307
308.L_strcmp_unaligned:
309        /* r0 is word-aligned, r1 is at offset ip from a word.  */
310        /* Align r1 to the (previous) word-boundary.  */
311        bic     r1, r1, #3
312
313        /* Unaligned comparison word by word using LDRs. */
314        cmp     ip, #2
315        beq     .L_miscmp_word_16                 /* If ip == 2.  */
316        bge     .L_miscmp_word_24                 /* If ip == 3.  */
317        miscmp_word offsetlo=8 offsethi=24        /* If ip == 1.  */
318.L_miscmp_word_16:  miscmp_word offsetlo=16 offsethi=16
319.L_miscmp_word_24:  miscmp_word offsetlo=24 offsethi=8
320
321
322.L_return_32:
323        setup_return w1=r3, w2=r2
324        b       .L_do_return
325.L_return_34:
326        setup_return w1=r3, w2=r4
327        b       .L_do_return
328.L_return_25:
329        setup_return w1=r2, w2=r5
330        b       .L_do_return
331.L_return_35:
332        setup_return w1=r3, w2=r5
333        b       .L_do_return
334.L_return_24:
335        setup_return w1=r2, w2=r4
336
337.L_do_return:
338
339#ifdef __ARMEB__
340        mov     r0, ip
341#else /* not  __ARMEB__ */
342        rev     r0, ip
343#endif /* not  __ARMEB__ */
344
345        /* Restore temporaries early, before computing the return value.  */
346        ldrd    r6, r7, [sp]
347        ldrd    r4, r5, [sp, #8]
348        adds    sp, sp, #16
349        .cfi_def_cfa_offset 0
350        .cfi_restore r4
351        .cfi_restore r5
352        .cfi_restore r6
353        .cfi_restore r7
354
355        /* There is a zero or a different byte between r1 and r2.  */
356        /* r0 contains a mask of all-zero bytes in r1.  */
357        /* Using r0 and not ip here because cbz requires low register.  */
358        m_cbz   reg=r0, label=.L_compute_return_value
359        clz     r0, r0
360        /* r0 contains the number of bits on the left of the first all-zero byte in r1.  */
361        rsb     r0, r0, #24
362        /* Here, r0 contains the number of bits on the right of the first all-zero byte in r1.  */
363        lsr     r1, r1, r0
364        lsr     r2, r2, r0
365
366.L_compute_return_value:
367        movs    r0, #1
368        cmp     r1, r2
369        /* The return value is computed as follows.
370        If r1>r2 then (C==1 and Z==0) and LS doesn't hold and r0 is #1 at return.
371        If r1<r2 then (C==0 and Z==0) and we execute SBC with carry_in=0,
372        which means r0:=r0-r0-1 and r0 is #-1 at return.
373        If r1=r2 then (C==1 and Z==1) and we execute SBC with carry_in=1,
374        which means r0:=r0-r0 and r0 is #0 at return.
375        (C==0 and Z==1) cannot happen because the carry bit is "not borrow".  */
376        it      ls
377        sbcls   r0, r0, r0
378        bx      lr
379END(strcmp)
380