Skip to main content

c/
string_fast.rs

1// ============================================================
2// String/memory fast paths
3// ============================================================
4//
5// These are hot enough that the generic byte loops in the rest of the libc
6// were measurably slower than musl.  The x86_64 versions use SSE2 (part of
7// the x86_64 baseline) and dispatch to AVX2 when the CPU and kernel support
8// it; other architectures use word-at-a-time loops with an unaligned first
9// block, which is what the musl C implementations compile to.
10
11#[cfg(target_arch = "x86_64")]
12mod fastmem_x86 {
13    use core::sync::atomic::{AtomicU8, Ordering};
14
15    const UNKNOWN: u8 = 0;
16    const NO: u8 = 1;
17    const YES: u8 = 2;
18
19    static AVX2: AtomicU8 = AtomicU8::new(UNKNOWN);
20    static ERMS: AtomicU8 = AtomicU8::new(UNKNOWN);
21
22    #[inline]
23    fn xgetbv0() -> u64 {
24        let lo: u32;
25        let hi: u32;
26        unsafe {
27            core::arch::asm!("xgetbv", in("ecx") 0u32, out("eax") lo, out("edx") hi,
28                             options(nostack, nomem));
29        }
30        ((hi as u64) << 32) | lo as u64
31    }
32
33    pub fn avx2() -> bool {
34        match AVX2.load(Ordering::Relaxed) {
35            YES => return true,
36            NO => return false,
37            _ => {}
38        }
39        let ok = {
40            let max = core::arch::x86_64::__cpuid(0).eax;
41            if max < 7 {
42                false
43            } else {
44                let c1 = core::arch::x86_64::__cpuid(1);
45                let osxsave = c1.ecx & (1 << 27) != 0;
46                let avx = c1.ecx & (1 << 28) != 0;
47                osxsave
48                    && avx
49                    && (xgetbv0() & 0x6) == 0x6
50                    && core::arch::x86_64::__cpuid_count(7, 0).ebx & (1 << 5) != 0
51            }
52        };
53        AVX2.store(if ok { YES } else { NO }, Ordering::Relaxed);
54        ok
55    }
56
57    pub fn erms() -> bool {
58        match ERMS.load(Ordering::Relaxed) {
59            YES => return true,
60            NO => return false,
61            _ => {}
62        }
63        let ok = {
64            let max = core::arch::x86_64::__cpuid(0).eax;
65            max >= 7 && core::arch::x86_64::__cpuid_count(7, 0).ebx & (1 << 9) != 0
66        };
67        ERMS.store(if ok { YES } else { NO }, Ordering::Relaxed);
68        ok
69    }
70}
71
72#[cfg(target_arch = "x86_64")]
73use core::arch::x86_64::*;
74
75// ------------------------------------------------------------
76// strlen
77// ------------------------------------------------------------
78
79#[cfg(target_arch = "x86_64")]
80unsafe fn strlen_sse2(s: *const u8) -> usize {
81    let start = s as usize;
82    let mut p = start & !15usize;
83    let zero = _mm_setzero_si128();
84    let first = _mm_load_si128(p as *const __m128i);
85    let mut mask = _mm_movemask_epi8(_mm_cmpeq_epi8(first, zero)) as u32;
86    mask &= !0u32 << (start - p);
87    if mask != 0 {
88        return p - start + mask.trailing_zeros() as usize;
89    }
90    p += 16;
91    loop {
92        let a = _mm_load_si128(p as *const __m128i);
93        let ma = _mm_movemask_epi8(_mm_cmpeq_epi8(a, zero)) as u32;
94        if ma != 0 {
95            return p + ma.trailing_zeros() as usize - start;
96        }
97        let b = _mm_load_si128((p + 16) as *const __m128i);
98        let mb = _mm_movemask_epi8(_mm_cmpeq_epi8(b, zero)) as u32;
99        if mb != 0 {
100            return p + 16 + mb.trailing_zeros() as usize - start;
101        }
102        p += 32;
103    }
104}
105
106#[cfg(target_arch = "x86_64")]
107#[target_feature(enable = "avx2")]
108unsafe fn strlen_avx2(s: *const u8) -> usize {
109    let start = s as usize;
110    let mut p = start & !31usize;
111    let zero = _mm256_setzero_si256();
112    let first = _mm256_load_si256(p as *const __m256i);
113    let mut mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(first, zero)) as u32;
114    mask &= !0u32 << (start - p);
115    if mask != 0 {
116        return p - start + mask.trailing_zeros() as usize;
117    }
118    p += 32;
119    loop {
120        let a = _mm256_load_si256(p as *const __m256i);
121        let ma = _mm256_movemask_epi8(_mm256_cmpeq_epi8(a, zero)) as u32;
122        if ma != 0 {
123            return p + ma.trailing_zeros() as usize - start;
124        }
125        let b = _mm256_load_si256((p + 32) as *const __m256i);
126        let mb = _mm256_movemask_epi8(_mm256_cmpeq_epi8(b, zero)) as u32;
127        if mb != 0 {
128            return p + 32 + mb.trailing_zeros() as usize - start;
129        }
130        p += 64;
131    }
132}
133
134#[cfg(not(target_arch = "x86_64"))]
135unsafe fn strlen_words(s: *const u8) -> usize {
136    let start = s as usize;
137    let mut p = s;
138    while (p as usize) & 7 != 0 {
139        if *p == 0 {
140            return p as usize - start;
141        }
142        p = p.add(1);
143    }
144    const ONES: u64 = 0x0101_0101_0101_0101;
145    const HIGHS: u64 = 0x8080_8080_8080_8080;
146    // One aligned word at a time. A 32-byte unroll can cross into an
147    // unmapped guard page when the NUL byte is the last byte before it.
148    loop {
149        let w = core::ptr::read(p as *const u64);
150        let z = w.wrapping_sub(ONES) & !w & HIGHS;
151        if z != 0 {
152            return p as usize - start + (z.trailing_zeros() as usize / 8);
153        }
154        p = p.add(8);
155    }
156}
157
158#[no_mangle]
159pub unsafe extern "C" fn strlen(s: *const c_char) -> usize {
160    #[cfg(target_arch = "x86_64")]
161    {
162        if fastmem_x86::avx2() {
163            return strlen_avx2(s as *const u8);
164        }
165        strlen_sse2(s as *const u8)
166    }
167    #[cfg(not(target_arch = "x86_64"))]
168    {
169        strlen_words(s as *const u8)
170    }
171}
172
173// ------------------------------------------------------------
174// memcpy
175// ------------------------------------------------------------
176
177#[cfg(target_arch = "x86_64")]
178#[inline]
179unsafe fn store16(dst: *mut u8, src: *const u8) {
180    let v = _mm_loadu_si128(src as *const __m128i);
181    _mm_storeu_si128(dst as *mut __m128i, v);
182}
183
184// RISC-V ABIs allow misaligned loads/stores, but LLVM lowers unaligned
185// `u64` accesses to eight byte operations because the psABI does not
186// guarantee them. Force a single `ld`/`sd`; the kernel emulates the rare
187// case where the hardware cannot do it in one cycle.
188#[cfg(target_arch = "riscv64")]
189#[inline(always)]
190unsafe fn load_u64(p: *const u8) -> u64 {
191    let v: u64;
192    core::arch::asm!("ld {}, 0({})", out(reg) v, in(reg) p, options(nostack));
193    v
194}
195
196#[cfg(not(target_arch = "riscv64"))]
197#[inline(always)]
198unsafe fn load_u64(p: *const u8) -> u64 {
199    core::ptr::read_unaligned(p as *const u64)
200}
201
202#[cfg(target_arch = "riscv64")]
203#[inline(always)]
204unsafe fn store_u64(p: *mut u8, v: u64) {
205    core::arch::asm!("sd {}, 0({})", in(reg) v, in(reg) p, options(nostack));
206}
207
208#[cfg(not(target_arch = "riscv64"))]
209#[inline(always)]
210unsafe fn store_u64(p: *mut u8, v: u64) {
211    core::ptr::write_unaligned(p as *mut u64, v);
212}
213
214#[cfg(target_arch = "riscv64")]
215#[inline(always)]
216unsafe fn copy32(d: *mut u8, s: *const u8) {
217    core::arch::asm!(
218        "ld t0, 0({s})", "ld t1, 8({s})", "ld t2, 16({s})", "ld t3, 24({s})",
219        "sd t0, 0({d})", "sd t1, 8({d})", "sd t2, 16({d})", "sd t3, 24({d})",
220        s = in(reg) s, d = in(reg) d,
221        out("t0") _, out("t1") _, out("t2") _, out("t3") _,
222        options(nostack),
223    );
224}
225
226#[cfg(target_arch = "riscv64")]
227#[inline(always)]
228unsafe fn store32(d: *mut u8, v: u64) {
229    core::arch::asm!(
230        "sd {v}, 0({d})", "sd {v}, 8({d})", "sd {v}, 16({d})", "sd {v}, 24({d})",
231        v = in(reg) v, d = in(reg) d,
232        options(nostack),
233    );
234}
235
236#[inline]
237unsafe fn copy8(dst: *mut u8, src: *const u8) {
238    store_u64(dst, load_u64(src));
239}
240
241#[cfg(target_arch = "x86_64")]
242unsafe fn memcpy_sse2(dst: *mut u8, src: *const u8, n: usize) {
243    if n >= 16 {
244        let mut i = 0usize;
245        while i + 16 <= n {
246            store16(dst.add(i), src.add(i));
247            i += 16;
248        }
249        if i < n {
250            store16(dst.add(n - 16), src.add(n - 16));
251        }
252    } else if n >= 8 {
253        copy8(dst, src);
254        copy8(dst.add(n - 8), src.add(n - 8));
255    } else if n >= 4 {
256        core::ptr::write_unaligned(
257            dst as *mut u32,
258            core::ptr::read_unaligned(src as *const u32),
259        );
260        core::ptr::write_unaligned(
261            dst.add(n - 4) as *mut u32,
262            core::ptr::read_unaligned(src.add(n - 4) as *const u32),
263        );
264    } else if n >= 2 {
265        core::ptr::write_unaligned(
266            dst as *mut u16,
267            core::ptr::read_unaligned(src as *const u16),
268        );
269        core::ptr::write_unaligned(
270            dst.add(n - 2) as *mut u16,
271            core::ptr::read_unaligned(src.add(n - 2) as *const u16),
272        );
273    } else if n == 1 {
274        *dst = *src;
275    }
276}
277
278#[cfg(target_arch = "x86_64")]
279#[target_feature(enable = "avx2")]
280unsafe fn memcpy_avx2(dst: *mut u8, src: *const u8, n: usize) {
281    let mut i = 0usize;
282    let head = (32 - (dst as usize & 31)) & 31;
283    while i < head {
284        *dst.add(i) = *src.add(i);
285        i += 1;
286    }
287    while i + 128 <= n {
288        let a = _mm256_loadu_si256(src.add(i) as *const __m256i);
289        let b = _mm256_loadu_si256(src.add(i + 32) as *const __m256i);
290        let c = _mm256_loadu_si256(src.add(i + 64) as *const __m256i);
291        let d = _mm256_loadu_si256(src.add(i + 96) as *const __m256i);
292        _mm256_store_si256(dst.add(i) as *mut __m256i, a);
293        _mm256_store_si256(dst.add(i + 32) as *mut __m256i, b);
294        _mm256_store_si256(dst.add(i + 64) as *mut __m256i, c);
295        _mm256_store_si256(dst.add(i + 96) as *mut __m256i, d);
296        i += 128;
297    }
298    while i + 32 <= n {
299        let a = _mm256_loadu_si256(src.add(i) as *const __m256i);
300        _mm256_store_si256(dst.add(i) as *mut __m256i, a);
301        i += 32;
302    }
303    if i < n {
304        let off = n - 32;
305        let a = _mm256_loadu_si256(src.add(off) as *const __m256i);
306        _mm256_storeu_si256(dst.add(off) as *mut __m256i, a);
307    }
308    _mm256_zeroupper();
309}
310
311#[cfg(target_arch = "x86_64")]
312unsafe fn memcpy_rep(dst: *mut u8, src: *const u8, n: usize) {
313    core::arch::asm!("rep movsb", inout("rdi") dst => _, inout("rsi") src => _,
314                     inout("rcx") n => _, options(nostack, preserves_flags));
315}
316
317#[cfg(not(target_arch = "x86_64"))]
318unsafe fn memcpy_words(dst: *mut u8, src: *const u8, n: usize) {
319    let mut d = dst;
320    let mut s = src;
321    let all = dst.add(n);
322    while d < all && (d as usize) & 7 != 0 {
323        *d = *s;
324        d = d.add(1);
325        s = s.add(1);
326    }
327    let mut end = d.add((all as usize - d as usize) & !31);
328    while d < end {
329        #[cfg(target_arch = "riscv64")]
330        copy32(d, s);
331        #[cfg(not(target_arch = "riscv64"))]
332        {
333            copy8(d, s);
334            copy8(d.add(8), s.add(8));
335            copy8(d.add(16), s.add(16));
336            copy8(d.add(24), s.add(24));
337        }
338        d = d.add(32);
339        s = s.add(32);
340    }
341    end = d.add((all as usize - d as usize) & !7);
342    while d < end {
343        copy8(d, s);
344        d = d.add(8);
345        s = s.add(8);
346    }
347    while d < all {
348        *d = *s;
349        d = d.add(1);
350        s = s.add(1);
351    }
352}
353
354#[no_mangle]
355pub unsafe extern "C" fn memcpy(dst: *mut c_void, src: *const c_void, n: usize) -> *mut c_void {
356    let d = dst as *mut u8;
357    let s = src as *const u8;
358    if n != 0 {
359        #[cfg(target_arch = "x86_64")]
360        {
361            if n >= 128 {
362                if fastmem_x86::avx2() {
363                    memcpy_avx2(d, s, n);
364                } else if fastmem_x86::erms() {
365                    memcpy_rep(d, s, n);
366                } else {
367                    memcpy_sse2(d, s, n);
368                }
369            } else {
370                memcpy_sse2(d, s, n);
371            }
372        }
373        #[cfg(not(target_arch = "x86_64"))]
374        memcpy_words(d, s, n);
375    }
376    dst
377}
378
379// ------------------------------------------------------------
380// memset
381// ------------------------------------------------------------
382
383#[cfg(target_arch = "x86_64")]
384unsafe fn memset_sse2(dst: *mut u8, c: u8, n: usize) {
385    let v16 = _mm_set1_epi8(c as i8);
386    if n >= 16 {
387        let mut i = 0usize;
388        while i + 16 <= n {
389            _mm_storeu_si128(dst.add(i) as *mut __m128i, v16);
390            i += 16;
391        }
392        if i < n {
393            _mm_storeu_si128(dst.add(n - 16) as *mut __m128i, v16);
394        }
395    } else if n >= 8 {
396        let v64 = 0x0101_0101_0101_0101u64 * c as u64;
397        core::ptr::write_unaligned(dst as *mut u64, v64);
398        core::ptr::write_unaligned(dst.add(n - 8) as *mut u64, v64);
399    } else if n >= 4 {
400        let v32 = 0x0101_0101u32 * c as u32;
401        core::ptr::write_unaligned(dst as *mut u32, v32);
402        core::ptr::write_unaligned(dst.add(n - 4) as *mut u32, v32);
403    } else if n >= 2 {
404        let v16 = 0x0101u16 * c as u16;
405        core::ptr::write_unaligned(dst as *mut u16, v16);
406        core::ptr::write_unaligned(dst.add(n - 2) as *mut u16, v16);
407    } else if n == 1 {
408        *dst = c;
409    }
410}
411
412#[cfg(target_arch = "x86_64")]
413#[target_feature(enable = "avx2")]
414unsafe fn memset_avx2(dst: *mut u8, c: u8, n: usize) {
415    let v = _mm256_set1_epi8(c as i8);
416    let mut i = 0usize;
417    let head = (32 - (dst as usize & 31)) & 31;
418    while i < head {
419        *dst.add(i) = c;
420        i += 1;
421    }
422    while i + 128 <= n {
423        _mm256_store_si256(dst.add(i) as *mut __m256i, v);
424        _mm256_store_si256(dst.add(i + 32) as *mut __m256i, v);
425        _mm256_store_si256(dst.add(i + 64) as *mut __m256i, v);
426        _mm256_store_si256(dst.add(i + 96) as *mut __m256i, v);
427        i += 128;
428    }
429    while i + 32 <= n {
430        _mm256_store_si256(dst.add(i) as *mut __m256i, v);
431        i += 32;
432    }
433    if i < n {
434        _mm256_storeu_si256(dst.add(n - 32) as *mut __m256i, v);
435    }
436    _mm256_zeroupper();
437}
438
439#[cfg(not(target_arch = "x86_64"))]
440unsafe fn memset_words(dst: *mut u8, c: u8, n: usize) {
441    let word = 0x0101_0101_0101_0101u64 * c as u64;
442    let all = dst.add(n);
443    let mut d = dst;
444    while d < all && (d as usize) & 7 != 0 {
445        *d = c;
446        d = d.add(1);
447    }
448    let mut end = d.add((all as usize - d as usize) & !31);
449    while d < end {
450        #[cfg(target_arch = "riscv64")]
451        store32(d, word);
452        #[cfg(not(target_arch = "riscv64"))]
453        {
454            store_u64(d, word);
455            store_u64(d.add(8), word);
456            store_u64(d.add(16), word);
457            store_u64(d.add(24), word);
458        }
459        d = d.add(32);
460    }
461    end = d.add((all as usize - d as usize) & !7);
462    while d < end {
463        store_u64(d, word);
464        d = d.add(8);
465    }
466    while d < all {
467        *d = c;
468        d = d.add(1);
469    }
470}
471
472#[no_mangle]
473pub unsafe extern "C" fn memset(s: *mut c_void, c: c_int, n: usize) -> *mut c_void {
474    let d = s as *mut u8;
475    let b = c as u8;
476    if n != 0 {
477        #[cfg(target_arch = "x86_64")]
478        {
479            if n >= 128 && fastmem_x86::avx2() {
480                memset_avx2(d, b, n);
481            } else {
482                memset_sse2(d, b, n);
483            }
484        }
485        #[cfg(not(target_arch = "x86_64"))]
486        memset_words(d, b, n);
487    }
488    s
489}
490
491// ------------------------------------------------------------
492// memmove
493// ------------------------------------------------------------
494
495#[no_mangle]
496pub unsafe extern "C" fn memmove(dst: *mut c_void, src: *const c_void, n: usize) -> *mut c_void {
497    let d = dst as *mut u8;
498    let s = src as *const u8;
499    let du = d as usize;
500    let su = s as usize;
501    if n == 0 || du == su {
502        return dst;
503    }
504    if du < su {
505        if du + n <= su {
506            return memcpy(dst, src, n);
507        }
508        // Overlapping, moving down: copy forwards. A chunk may only be as
509        // wide as the overlap, otherwise its store would clobber the source
510        // of the next chunk.
511        let delta = su - du;
512        let mut i = 0usize;
513        if delta >= 32 {
514            while i + 32 <= n {
515                copy8(d.add(i), s.add(i));
516                copy8(d.add(i + 8), s.add(i + 8));
517                copy8(d.add(i + 16), s.add(i + 16));
518                copy8(d.add(i + 24), s.add(i + 24));
519                i += 32;
520            }
521        }
522        if delta >= 8 {
523            while i + 8 <= n {
524                copy8(d.add(i), s.add(i));
525                i += 8;
526            }
527        }
528        while i < n {
529            *d.add(i) = *s.add(i);
530            i += 1;
531        }
532    } else {
533        if su + n <= du {
534            return memcpy(dst, src, n);
535        }
536        // Overlapping, moving up: copy backwards. The sub-chunks inside a
537        // block must also go high to low so a store cannot clobber bytes the
538        // same block has not read yet.
539        let mut i = n;
540        while i >= 32 {
541            i -= 32;
542            copy8(d.add(i + 24), s.add(i + 24));
543            copy8(d.add(i + 16), s.add(i + 16));
544            copy8(d.add(i + 8), s.add(i + 8));
545            copy8(d.add(i), s.add(i));
546        }
547        while i >= 8 {
548            i -= 8;
549            copy8(d.add(i), s.add(i));
550        }
551        while i > 0 {
552            i -= 1;
553            *d.add(i) = *s.add(i);
554        }
555    }
556    dst
557}
558
559// ------------------------------------------------------------
560// memcmp
561// ------------------------------------------------------------
562
563#[cfg(target_arch = "x86_64")]
564#[target_feature(enable = "avx2")]
565unsafe fn memcmp_avx2(a: *const u8, b: *const u8, n: usize) -> c_int {
566    let mut i = 0usize;
567    while i + 32 <= n {
568        let va = _mm256_loadu_si256(a.add(i) as *const __m256i);
569        let vb = _mm256_loadu_si256(b.add(i) as *const __m256i);
570        let mask = _mm256_movemask_epi8(_mm256_cmpeq_epi8(va, vb)) as u32;
571        if mask != u32::MAX {
572            let k = (!mask).trailing_zeros() as usize;
573            _mm256_zeroupper();
574            return *a.add(i + k) as c_int - *b.add(i + k) as c_int;
575        }
576        i += 32;
577    }
578    _mm256_zeroupper();
579    while i + 8 <= n {
580        let wa = load_u64(a.add(i));
581        let wb = load_u64(b.add(i));
582        if wa != wb {
583            break;
584        }
585        i += 8;
586    }
587    while i < n {
588        let x = *a.add(i);
589        let y = *b.add(i);
590        if x != y {
591            return x as c_int - y as c_int;
592        }
593        i += 1;
594    }
595    0
596}
597
598#[no_mangle]
599pub unsafe extern "C" fn memcmp(s1: *const c_void, s2: *const c_void, n: usize) -> c_int {
600    let a = s1 as *const u8;
601    let b = s2 as *const u8;
602    if n == 0 {
603        return 0;
604    }
605    // Most unequal comparisons differ early; resolve that without setting up
606    // a vector/word loop.
607    if *a != *b {
608        return *a as c_int - *b as c_int;
609    }
610    #[cfg(target_arch = "x86_64")]
611    {
612        if n >= 32 && fastmem_x86::avx2() {
613            return memcmp_avx2(a, b, n);
614        }
615    }
616    let mut i = 0usize;
617    while i + 8 <= n {
618        let wa = core::ptr::read_unaligned(a.add(i) as *const u64);
619        let wb = core::ptr::read_unaligned(b.add(i) as *const u64);
620        if wa != wb {
621            break;
622        }
623        i += 8;
624    }
625    while i < n {
626        let x = *a.add(i);
627        let y = *b.add(i);
628        if x != y {
629            return x as c_int - y as c_int;
630        }
631        i += 1;
632    }
633    0
634}
635
636// ------------------------------------------------------------
637// strcmp
638// ------------------------------------------------------------
639
640#[no_mangle]
641pub unsafe extern "C" fn strcmp(s1: *const u8, s2: *const u8) -> c_int {
642    let mut a = s1;
643    let mut b = s2;
644    const ONES: u64 = 0x0101_0101_0101_0101;
645    const HIGHS: u64 = 0x8080_8080_8080_8080;
646    loop {
647        // Walk to the next page end (or as far as the shorter string allows)
648        // with a tight word loop; the room computation runs once per page,
649        // not once per word.
650        let room_a = 4096 - ((a as usize) & 4095);
651        let room_b = 4096 - ((b as usize) & 4095);
652        let room = if room_a < room_b { room_a } else { room_b };
653        let words_end = a.add(room & !7usize);
654        while a < words_end {
655            let wa = load_u64(a);
656            let wb = load_u64(b);
657            if wa == wb {
658                if wa.wrapping_sub(ONES) & !wa & HIGHS != 0 {
659                    return 0;
660                }
661                a = a.add(8);
662                b = b.add(8);
663                continue;
664            }
665            let mut i = 0usize;
666            while i < 8 {
667                let x = *a.add(i);
668                let y = *b.add(i);
669                if x != y {
670                    return x as c_int - y as c_int;
671                }
672                if x == 0 {
673                    return 0;
674                }
675                i += 1;
676            }
677        }
678        let tail = room & 7;
679        let mut i = 0usize;
680        while i < tail {
681            let x = *a;
682            let y = *b;
683            if x != y {
684                return x as c_int - y as c_int;
685            }
686            if x == 0 {
687                return 0;
688            }
689            a = a.add(1);
690            b = b.add(1);
691            i += 1;
692        }
693    }
694}