Skip to main content

c/
string_fast.rs

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