[libc-commits] [libc] [libcxx] [llvm] libcxx build test (PR #193840)

Schrodinger ZHU Yifan via libc-commits libc-commits at lists.llvm.org
Wed Apr 29 08:07:10 PDT 2026


https://github.com/SchrodingerZhu updated https://github.com/llvm/llvm-project/pull/193840

>From 234efeae50abf072c1f86e2febb8cb5a31c2b153 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yifanzhu at rochester.edu>
Date: Fri, 24 Apr 2026 11:52:39 -0400
Subject: [PATCH 01/13] [libc][libcxx] enable libcxx build-and-test workflow

---
 libc/config/linux/aarch64/entrypoints.txt    | 311 ++++++++++++++---
 libc/config/linux/aarch64/headers.txt        |   3 +
 libc/config/linux/riscv/entrypoints.txt      |   1 +
 libc/config/linux/x86_64/entrypoints.txt     |   3 +
 libc/include/llvm-libc-types/__thread_type.h |   4 +-
 libc/src/link/CMakeLists.txt                 |   1 +
 libc/src/link/dl_iterate_phdr.cpp            | 131 ++++++-
 libc/src/pthread/pthread_self.cpp            |   3 +-
 libc/src/threads/thrd_current.cpp            |   3 +-
 libcxx/include/__mbstate_t.h                 |   2 +
 run.sh                                       | 340 +++++++++++++++++++
 runtimes/CMakeLists.txt                      |   9 +
 12 files changed, 762 insertions(+), 49 deletions(-)
 create mode 100755 run.sh

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index b15edc5e3e102..27d948aa89536 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -18,6 +18,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.ctype.toupper
 
     # dlfcn.h entrypoints
+    libc.src.dlfcn.dladdr
     libc.src.dlfcn.dlclose
     libc.src.dlfcn.dlerror
     libc.src.dlfcn.dlopen
@@ -36,6 +37,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.poll.poll
 
     # sched.h entrypoints
+    libc.src.sched.getcpu
     libc.src.sched.sched_get_priority_max
     libc.src.sched.sched_get_priority_min
     libc.src.sched.sched_getaffinity
@@ -108,6 +110,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.inttypes.strtoimax
     libc.src.inttypes.strtoumax
 
+    # link.h entrypoints
+    libc.src.link.dl_iterate_phdr
+
     # stdbit.h entrypoints
     libc.src.stdbit.stdc_bit_ceil_uc
     libc.src.stdbit.stdc_bit_ceil_ui
@@ -181,6 +186,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdbit.stdc_trailing_zeros_us
 
     # stdlib.h entrypoints
+    libc.src.stdlib.a64l
     libc.src.stdlib.abs
     libc.src.stdlib.atof
     libc.src.stdlib.atoi
@@ -188,6 +194,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdlib.atoll
     libc.src.stdlib.bsearch
     libc.src.stdlib.div
+    libc.src.stdlib.l64a
     libc.src.stdlib.labs
     libc.src.stdlib.ldiv
     libc.src.stdlib.llabs
@@ -199,8 +206,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.stdlib.srand
     libc.src.stdlib.strfromd
     libc.src.stdlib.strfromf
-    # TODO: long double support is buggy with clang-11. Re-enable when buildbots are upgraded.
-    # libc.src.stdlib.strfroml
+    libc.src.stdlib.strfroml
     libc.src.stdlib.strtod
     libc.src.stdlib.strtof
     libc.src.stdlib.strtol
@@ -253,6 +259,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     # sys/ioctl.h entrypoints
     libc.src.sys.ioctl.ioctl
 
+    # sys/ipc.h entrypoints
+    libc.src.sys.ipc.ftok
+
     # sys/mman.h entrypoints
     libc.src.sys.mman.madvise
     libc.src.sys.mman.mincore
@@ -266,6 +275,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.sys.mman.munlock
     libc.src.sys.mman.munlockall
     libc.src.sys.mman.munmap
+    libc.src.sys.mman.pkey_alloc
+    libc.src.sys.mman.pkey_free
+    libc.src.sys.mman.pkey_get
+    libc.src.sys.mman.pkey_mprotect
+    libc.src.sys.mman.pkey_set
     libc.src.sys.mman.remap_file_pages
     libc.src.sys.mman.posix_madvise
     libc.src.sys.mman.shm_open
@@ -278,6 +292,11 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.sys.resource.getrlimit
     libc.src.sys.resource.setrlimit
 
+    # sys/sem.h entrypoints
+    libc.src.sys.sem.semget
+    libc.src.sys.sem.semctl
+    libc.src.sys.sem.semop
+
     # sys/sendfile entrypoints
     libc.src.sys.sendfile.sendfile
 
@@ -296,6 +315,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.sys.statvfs.fstatvfs
     libc.src.sys.statvfs.statvfs
 
+    # sys/time.h entrypoints
+    libc.src.sys.time.utimes
+
     # sys/utsname.h entrypoints
     libc.src.sys.utsname.uname
 
@@ -326,6 +348,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     # unistd.h entrypoints
     libc.src.unistd.access
     libc.src.unistd.chdir
+    libc.src.unistd.chown
     libc.src.unistd.close
     libc.src.unistd.dup
     libc.src.unistd.dup2
@@ -346,6 +369,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.unistd.getppid
     libc.src.unistd.getsid
     libc.src.unistd.gettid
+    libc.src.unistd.getgid
     libc.src.unistd.getuid
     libc.src.unistd.isatty
     libc.src.unistd.link
@@ -370,8 +394,42 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.unistd.write
 
     # wchar.h entrypoints
+    libc.src.wchar.btowc
     libc.src.wchar.wcslen
+    libc.src.wchar.wcsnlen
     libc.src.wchar.wctob
+    libc.src.wchar.wmemmove
+    libc.src.wchar.wmemset
+    libc.src.wchar.wcschr
+    libc.src.wchar.wcsncmp
+    libc.src.wchar.wcsxfrm
+    libc.src.wchar.wcscmp
+    libc.src.wchar.wcspbrk
+    libc.src.wchar.wcsrchr
+    libc.src.wchar.wcsspn
+    libc.src.wchar.wcscspn
+    libc.src.wchar.wcsdup
+    libc.src.wchar.wmemcmp
+    libc.src.wchar.wmempcpy
+    libc.src.wchar.wmemcpy
+    libc.src.wchar.wcsncpy
+    libc.src.wchar.wcscat
+    libc.src.wchar.wcsstr
+    libc.src.wchar.wcsncat
+    libc.src.wchar.wcslcat
+    libc.src.wchar.wcscpy
+    libc.src.wchar.wcslcpy
+    libc.src.wchar.wmemchr
+    libc.src.wchar.wcpcpy
+    libc.src.wchar.wcpncpy
+    libc.src.wchar.wcstod
+    libc.src.wchar.wcstof
+    libc.src.wchar.wcstok
+    libc.src.wchar.wcstol
+    libc.src.wchar.wcstold
+    libc.src.wchar.wcstoll
+    libc.src.wchar.wcstoul
+    libc.src.wchar.wcstoull
 
     # wctype.h entrypoints
     libc.src.wctype.iswalpha
@@ -469,16 +527,17 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.cosf
     libc.src.math.coshf
     libc.src.math.cospif
-    libc.src.math.daddl
-    libc.src.math.ddivl
     libc.src.math.dfmal
     libc.src.math.dmull
     libc.src.math.dsqrtl
+    libc.src.math.daddl
+    libc.src.math.ddivl
     libc.src.math.dsubl
     libc.src.math.erff
     libc.src.math.exp
     libc.src.math.exp10
     libc.src.math.exp10f
+    libc.src.math.exp10m1f
     libc.src.math.exp2
     libc.src.math.exp2f
     libc.src.math.exp2m1f
@@ -494,6 +553,10 @@ set(TARGET_LIBM_ENTRYPOINTS
     libc.src.math.fdim
     libc.src.math.fdimf
     libc.src.math.fdiml
+    libc.src.math.fdiv
+    libc.src.math.fdivl
+    libc.src.math.ffma
+    libc.src.math.ffmal
     libc.src.math.floor
     libc.src.math.floorf
     libc.src.math.floorl
@@ -687,40 +750,50 @@ endif()
 if(LIBC_TYPES_HAS_FLOAT16)
   list(APPEND TARGET_LIBM_ENTRYPOINTS
     # math.h C23 _Float16 entrypoints
+    libc.src.math.acosf16
     libc.src.math.acoshf16
+    libc.src.math.acospif16
+    libc.src.math.asinf16
+    libc.src.math.asinhf16
     libc.src.math.asinpif16
+    libc.src.math.atanf16
     libc.src.math.atan2f16
+    libc.src.math.atanhf16
+    libc.src.math.atanpif16
     libc.src.math.canonicalizef16
     libc.src.math.ceilf16
     libc.src.math.copysignf16
+    libc.src.math.cosf16
+    libc.src.math.coshf16
     libc.src.math.cospif16
     libc.src.math.erff16
     libc.src.math.erfcf16
+    libc.src.math.exp10f16
+    libc.src.math.exp10m1f16
+    libc.src.math.exp2f16
+    libc.src.math.exp2m1f16
     libc.src.math.expf16
+    libc.src.math.expm1f16
     libc.src.math.f16add
     libc.src.math.f16addf
-    # libc.src.math.f16addl
+    libc.src.math.f16addl
     libc.src.math.f16div
     libc.src.math.f16divf
-    # libc.src.math.f16divl
+    libc.src.math.f16divl
     libc.src.math.f16fma
     libc.src.math.f16fmaf
-    # libc.src.math.f16fmal
+    libc.src.math.f16fmal
     libc.src.math.f16mul
     libc.src.math.f16mulf
-    # libc.src.math.f16mull
+    libc.src.math.f16mull
     libc.src.math.f16sqrt
     libc.src.math.f16sqrtf
-    # libc.src.math.f16sqrtl
+    libc.src.math.f16sqrtl
     libc.src.math.f16sub
     libc.src.math.f16subf
-    # libc.src.math.f16subl
+    libc.src.math.f16subl
     libc.src.math.fabsf16
     libc.src.math.fdimf16
-    libc.src.math.fdiv
-    libc.src.math.fdivl
-    libc.src.math.ffma
-    libc.src.math.ffmal
     libc.src.math.floorf16
     libc.src.math.fmaf16
     libc.src.math.fmaxf16
@@ -733,11 +806,12 @@ if(LIBC_TYPES_HAS_FLOAT16)
     libc.src.math.fminimum_magf16
     libc.src.math.fminimum_numf16
     libc.src.math.fminimumf16
-    # libc.src.math.fmodf16
+    libc.src.math.fmodf16
     libc.src.math.frexpf16
     libc.src.math.fromfpf16
     libc.src.math.fromfpxf16
     libc.src.math.getpayloadf16
+    libc.src.math.hypotf16
     libc.src.math.ilogbf16
     libc.src.math.iscanonicalf16
     libc.src.math.issignalingf16
@@ -745,20 +819,20 @@ if(LIBC_TYPES_HAS_FLOAT16)
     libc.src.math.llogbf16
     libc.src.math.llrintf16
     libc.src.math.llroundf16
+    libc.src.math.log10f16
     libc.src.math.log10p1f16
+    libc.src.math.log2f16
     libc.src.math.log2p1f16
     libc.src.math.logbf16
+    libc.src.math.logf16
     libc.src.math.lrintf16
     libc.src.math.lroundf16
-    # libc.src.math.modff16
+    libc.src.math.modff16
     libc.src.math.nanf16
     libc.src.math.nearbyintf16
     libc.src.math.nextafterf16
     libc.src.math.nextdownf16
-    # Temporarily disable nexttowardf16 on aarch64 because the conversion
-    # between _Float16 and long double will crash clang-11.  This is fixed in
-    # clang-12 and after: https://godbolt.org/z/8ceT9454c
-    # libc.src.math.nexttowardf16
+    libc.src.math.nexttowardf16
     libc.src.math.nextupf16
     libc.src.math.remainderf16
     libc.src.math.remquof16
@@ -771,8 +845,13 @@ if(LIBC_TYPES_HAS_FLOAT16)
     libc.src.math.scalbnf16
     libc.src.math.setpayloadf16
     libc.src.math.setpayloadsigf16
+    libc.src.math.sinf16
+    libc.src.math.sinhf16
     libc.src.math.sinpif16
     libc.src.math.sqrtf16
+    libc.src.math.tanf16
+    libc.src.math.tanhf16
+    libc.src.math.tanpif16
     libc.src.math.totalorderf16
     libc.src.math.totalordermagf16
     libc.src.math.truncf16
@@ -780,17 +859,17 @@ if(LIBC_TYPES_HAS_FLOAT16)
     libc.src.math.ufromfpxf16
   )
 
-  # if(LIBC_TYPES_HAS_FLOAT128)
-  #   list(APPEND TARGET_LIBM_ENTRYPOINTS
-  #     # math.h C23 mixed _Float16 and _Float128 entrypoints
-  #     libc.src.math.f16addf128
-  #     libc.src.math.f16divf128
-  #     libc.src.math.f16fmaf128
-  #     libc.src.math.f16mulf128
-  #     libc.src.math.f16sqrtf128
-  #     libc.src.math.f16subf128
-  #   )
-  # endif()
+  if(LIBC_TYPES_HAS_FLOAT128)
+    list(APPEND TARGET_LIBM_ENTRYPOINTS
+      # math.h C23 mixed _Float16 and _Float128 entrypoints
+      libc.src.math.f16addf128
+      libc.src.math.f16divf128
+      libc.src.math.f16fmaf128
+      libc.src.math.f16mulf128
+      libc.src.math.f16sqrtf128
+      libc.src.math.f16subf128
+    )
+  endif()
 endif()
 
 if(LIBC_TYPES_HAS_CFLOAT128)
@@ -807,6 +886,7 @@ if(LIBC_TYPES_HAS_FLOAT128)
   list(APPEND TARGET_LIBM_ENTRYPOINTS
     # math.h C23 _Float128 entrypoints
     libc.src.math.atan2f128
+    libc.src.math.atan2l
     libc.src.math.canonicalizef128
     libc.src.math.ceilf128
     libc.src.math.copysignf128
@@ -961,8 +1041,120 @@ if(LIBC_TYPES_HAS_FLOAT128)
   )
 endif()
 
+if(LIBC_COMPILER_HAS_FIXED_POINT)
+  list(APPEND TARGET_LIBM_ENTRYPOINTS
+    # stdfix.h _Fract and _Accum entrypoints
+    libc.src.stdfix.abshk
+    libc.src.stdfix.abshr
+    libc.src.stdfix.absk
+    libc.src.stdfix.abslk
+    libc.src.stdfix.abslr
+    libc.src.stdfix.absr
+    libc.src.stdfix.exphk
+    libc.src.stdfix.expk
+    libc.src.stdfix.roundhk
+    libc.src.stdfix.roundhr
+    libc.src.stdfix.roundk
+    libc.src.stdfix.roundlk
+    libc.src.stdfix.roundlr
+    libc.src.stdfix.roundr
+    libc.src.stdfix.rounduhk
+    libc.src.stdfix.rounduhr
+    libc.src.stdfix.rounduk
+    libc.src.stdfix.roundulk
+    libc.src.stdfix.roundulr
+    libc.src.stdfix.roundur
+    libc.src.stdfix.sqrtuhk
+    libc.src.stdfix.sqrtuhr
+    libc.src.stdfix.sqrtuk
+    libc.src.stdfix.sqrtur
+    # libc.src.stdfix.sqrtulk
+    libc.src.stdfix.sqrtulr
+    libc.src.stdfix.uhksqrtus
+    libc.src.stdfix.uksqrtui
+    libc.src.stdfix.hrbits
+    libc.src.stdfix.uhrbits
+    libc.src.stdfix.rbits
+    libc.src.stdfix.urbits
+    libc.src.stdfix.lrbits
+    libc.src.stdfix.ulrbits
+    libc.src.stdfix.hkbits
+    libc.src.stdfix.uhkbits
+    libc.src.stdfix.kbits
+    libc.src.stdfix.ukbits
+    libc.src.stdfix.lkbits
+    libc.src.stdfix.ulkbits
+    libc.src.stdfix.bitshr
+    libc.src.stdfix.bitsr
+    libc.src.stdfix.bitslr
+    libc.src.stdfix.bitshk
+    libc.src.stdfix.bitsk
+    libc.src.stdfix.bitslk
+    libc.src.stdfix.bitsuhr
+    libc.src.stdfix.bitsur
+    libc.src.stdfix.bitsulr
+    libc.src.stdfix.bitsuhk
+    libc.src.stdfix.bitsuk
+    libc.src.stdfix.bitsulk
+    libc.src.stdfix.countlshr
+    libc.src.stdfix.countlsr
+    libc.src.stdfix.countlslr
+    libc.src.stdfix.countlshk
+    libc.src.stdfix.countlsk
+    libc.src.stdfix.countlslk
+    libc.src.stdfix.countlsuhr
+    libc.src.stdfix.countlsur
+    libc.src.stdfix.countlsulr
+    libc.src.stdfix.countlsuhk
+    libc.src.stdfix.countlsuk
+    libc.src.stdfix.countlsulk
+    libc.src.stdfix.idivr
+    libc.src.stdfix.idivlr
+    libc.src.stdfix.idivk
+    libc.src.stdfix.idivlk
+    libc.src.stdfix.idivur
+    libc.src.stdfix.idivulr
+    libc.src.stdfix.idivuk
+    libc.src.stdfix.idivulk
+    libc.src.stdfix.rdivi
+  )
+endif()
+
 if(LLVM_LIBC_FULL_BUILD)
   list(APPEND TARGET_LIBC_ENTRYPOINTS
+    # ctype.h entrypoints
+    libc.src.ctype.isalnum_l
+    libc.src.ctype.isalpha_l
+    libc.src.ctype.isblank_l
+    libc.src.ctype.iscntrl_l
+    libc.src.ctype.isdigit_l
+    libc.src.ctype.isgraph_l
+    libc.src.ctype.islower_l
+    libc.src.ctype.isprint_l
+    libc.src.ctype.ispunct_l
+    libc.src.ctype.isspace_l
+    libc.src.ctype.isupper_l
+    libc.src.ctype.isxdigit_l
+    libc.src.ctype.tolower_l
+    libc.src.ctype.toupper_l
+
+    # stdlib.h entrypoints
+    libc.src.stdlib.strtod_l
+    libc.src.stdlib.strtof_l
+    libc.src.stdlib.strtol_l
+    libc.src.stdlib.strtold_l
+    libc.src.stdlib.strtoll_l
+    libc.src.stdlib.strtoul_l
+    libc.src.stdlib.strtoull_l
+
+    # string.h entrypoints
+    libc.src.string.strcoll_l
+    libc.src.string.strxfrm_l
+
+    # strings.h entrypoints
+    libc.src.strings.strcasecmp_l
+    libc.src.strings.strncasecmp_l
+
     # assert.h entrypoints
     libc.src.assert.__assert_fail
 
@@ -1017,6 +1209,9 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.pthread.pthread_join
     libc.src.pthread.pthread_key_create
     libc.src.pthread.pthread_key_delete
+    libc.src.pthread.pthread_barrier_init
+    libc.src.pthread.pthread_barrier_wait
+    libc.src.pthread.pthread_barrier_destroy
     libc.src.pthread.pthread_mutex_destroy
     libc.src.pthread.pthread_mutex_init
     libc.src.pthread.pthread_mutex_lock
@@ -1063,16 +1258,16 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.sched.__sched_setcpuset
     libc.src.sched.__sched_getcpuisset
 
-    # strings.h entrypoints
-    libc.src.strings.strcasecmp_l
-    libc.src.strings.strncasecmp_l
-
     # setjmp.h entrypoints
     libc.src.setjmp.longjmp
     libc.src.setjmp.setjmp
     libc.src.setjmp.siglongjmp
     libc.src.setjmp.sigsetjmp
 
+    # ucontext.h entrypoints
+    #libc.src.ucontext.getcontext
+    #libc.src.ucontext.setcontext
+
     # stdio.h entrypoints
     libc.src.stdio.clearerr
     libc.src.stdio.clearerr_unlocked
@@ -1124,7 +1319,11 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.stdlib.atexit
     libc.src.stdlib.exit
     libc.src.stdlib.getenv
+    libc.src.stdlib.mbstowcs
+    libc.src.stdlib.mbtowc
     libc.src.stdlib.quick_exit
+    libc.src.stdlib.wcstombs
+    libc.src.stdlib.wctomb
 
     # signal.h entrypoints
     libc.src.signal.kill
@@ -1198,11 +1397,24 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.time.gettimeofday
     libc.src.time.gmtime
     libc.src.time.gmtime_r
+    libc.src.time.localtime
+    libc.src.time.localtime_r
     libc.src.time.mktime
     libc.src.time.nanosleep
+    libc.src.time.strftime
+    libc.src.time.strftime_l
     libc.src.time.time
     libc.src.time.timespec_get
 
+    # locale.h entrypoints
+    libc.src.locale.localeconv
+    libc.src.locale.duplocale
+    libc.src.locale.freelocale
+    libc.src.locale.localeconv
+    libc.src.locale.newlocale
+    libc.src.locale.setlocale
+    libc.src.locale.uselocale
+
     # unistd.h entrypoints
     libc.src.unistd.__llvm_libc_syscall
     libc.src.unistd._exit
@@ -1222,20 +1434,43 @@ if(LLVM_LIBC_FULL_BUILD)
     # sys/socket.h entrypoints
     libc.src.sys.socket.accept
     libc.src.sys.socket.accept4
+    libc.src.sys.socket.socket
     libc.src.sys.socket.bind
     libc.src.sys.socket.connect
     libc.src.sys.socket.getsockopt
     libc.src.sys.socket.listen
-    libc.src.sys.socket.setsockopt
     libc.src.sys.socket.shutdown
-    libc.src.sys.socket.socket
+    libc.src.sys.socket.socketpair
+    libc.src.sys.socket.setsockopt
+    libc.src.sys.socket.send
+    libc.src.sys.socket.sendto
+    libc.src.sys.socket.sendmsg
+    libc.src.sys.socket.recv
+    libc.src.sys.socket.recvfrom
+    libc.src.sys.socket.recvmsg
+
+    # wchar.h entrypoints
+    libc.src.wchar.mblen
+    libc.src.wchar.mbrlen
+    libc.src.wchar.mbsinit
+    libc.src.wchar.mbrtowc
+    libc.src.wchar.mbsrtowcs
+    libc.src.wchar.mbsnrtowcs
+    libc.src.wchar.wcrtomb
+    libc.src.wchar.wcsrtombs
+    libc.src.wchar.wcsnrtombs
+
+    # nl_types.h entrypoints
+    libc.src.nl_types.catopen 
+    libc.src.nl_types.catclose
+    libc.src.nl_types.catgets
   )
 endif()
 
 set(TARGET_LIBMVEC_ENTRYPOINTS)
 
 if(LIBC_COMPILER_HAS_EXT_VECTOR_TYPE)
-  list(APPEND TARGET_LIBMVEC_ENTRYPOINTS
+list(APPEND TARGET_LIBMVEC_ENTRYPOINTS
     libc.src.mathvec.expf
   )
 endif()
diff --git a/libc/config/linux/aarch64/headers.txt b/libc/config/linux/aarch64/headers.txt
index 4784fc5d29ddc..0e5d5c753aed1 100644
--- a/libc/config/linux/aarch64/headers.txt
+++ b/libc/config/linux/aarch64/headers.txt
@@ -20,6 +20,7 @@ set(TARGET_PUBLIC_HEADERS
     libc.include.malloc
     libc.include.math
     libc.include.netinet_in
+    libc.include.nl_types
     libc.include.poll
     libc.include.pthread
     libc.include.sched
@@ -38,12 +39,14 @@ set(TARGET_PUBLIC_HEADERS
     libc.include.sys_auxv
     libc.include.sys_epoll
     libc.include.sys_ioctl
+    libc.include.sys_ipc
     libc.include.sys_mman
     libc.include.sys_prctl
     libc.include.sys_queue
     libc.include.sys_random
     libc.include.sys_resource
     libc.include.sys_select
+    libc.include.sys_sem
     libc.include.sys_socket
     libc.include.sys_stat
     libc.include.sys_statvfs
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 6478aed3b0391..ff82dd479665a 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -18,6 +18,7 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.ctype.toupper
 
     # dlfcn.h entrypoints
+    libc.src.dlfcn.dladdr
     libc.src.dlfcn.dlclose
     libc.src.dlfcn.dlerror
     libc.src.dlfcn.dlopen
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index d1c1d9496af67..2c9d1bf3a0f4b 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -110,6 +110,9 @@ set(TARGET_LIBC_ENTRYPOINTS
     libc.src.inttypes.strtoimax
     libc.src.inttypes.strtoumax
 
+    # link.h entrypoints
+    libc.src.link.dl_iterate_phdr
+
     # stdbit.h entrypoints
     libc.src.stdbit.stdc_bit_ceil_uc
     libc.src.stdbit.stdc_bit_ceil_ui
diff --git a/libc/include/llvm-libc-types/__thread_type.h b/libc/include/llvm-libc-types/__thread_type.h
index 645573f544a99..e2f4829e9fd7f 100644
--- a/libc/include/llvm-libc-types/__thread_type.h
+++ b/libc/include/llvm-libc-types/__thread_type.h
@@ -9,8 +9,6 @@
 #ifndef LLVM_LIBC_TYPES___THREAD_TYPE_H
 #define LLVM_LIBC_TYPES___THREAD_TYPE_H
 
-typedef struct {
-  void *__attrib;
-} __thread_type;
+typedef void *__thread_type;
 
 #endif // LLVM_LIBC_TYPES___THREAD_TYPE_H
diff --git a/libc/src/link/CMakeLists.txt b/libc/src/link/CMakeLists.txt
index 55f5edfab7d93..35fe9e87c74f2 100644
--- a/libc/src/link/CMakeLists.txt
+++ b/libc/src/link/CMakeLists.txt
@@ -5,5 +5,6 @@ add_entrypoint_object(
   HDRS
     dl_iterate_phdr.h
   DEPENDS
+    libc.src.__support.OSUtil.linux.auxv
     libc.hdr.stdint_proxy
 )
diff --git a/libc/src/link/dl_iterate_phdr.cpp b/libc/src/link/dl_iterate_phdr.cpp
index 7964411598d4a..7aa2be4f497f6 100644
--- a/libc/src/link/dl_iterate_phdr.cpp
+++ b/libc/src/link/dl_iterate_phdr.cpp
@@ -8,18 +8,141 @@
 
 #include "dl_iterate_phdr.h"
 
+#include "hdr/sys_auxv_macros.h"
+#include "src/__support/OSUtil/linux/auxv.h"
 #include "src/__support/common.h"
 #include "src/__support/macros/config.h"
 
+#include <elf.h>
+
+extern "C" {
+[[gnu::weak]] extern const char __ehdr_start;
+[[gnu::weak]] extern const char __executable_start;
+}
+
 namespace LIBC_NAMESPACE_DECL {
 
+namespace {
+
+LIBC_INLINE bool is_valid_elf(const ElfW(Ehdr) *ehdr) {
+  if (ehdr == nullptr)
+    return false;
+
+  return ehdr->e_ident[EI_MAG0] == ELFMAG0 &&
+         ehdr->e_ident[EI_MAG1] == ELFMAG1 &&
+         ehdr->e_ident[EI_MAG2] == ELFMAG2 &&
+         ehdr->e_ident[EI_MAG3] == ELFMAG3;
+}
+
+LIBC_INLINE const ElfW(Ehdr) *get_executable_ehdr() {
+  const ElfW(Ehdr) *ehdr = nullptr;
+
+  if (&__ehdr_start != nullptr)
+    ehdr = reinterpret_cast<const ElfW(Ehdr) *>(&__ehdr_start);
+  else if (&__executable_start != nullptr)
+    ehdr = reinterpret_cast<const ElfW(Ehdr) *>(&__executable_start);
+
+  return is_valid_elf(ehdr) ? ehdr : nullptr;
+}
+
+LIBC_INLINE ElfW(Addr) get_executable_load_bias(const ElfW(Ehdr) *ehdr,
+                                                const ElfW(Phdr) *phdr_table,
+                                                ElfW(Half) phnum) {
+  if (phdr_table != nullptr) {
+    const uintptr_t runtime_phdr = reinterpret_cast<uintptr_t>(phdr_table);
+    for (ElfW(Half) i = 0; i < phnum; ++i) {
+      if (phdr_table[i].p_type == PT_PHDR)
+        return static_cast<ElfW(Addr)>(runtime_phdr - phdr_table[i].p_vaddr);
+    }
+  }
+
+  // For PIE binaries the ELF header is mapped at the load bias. ET_EXEC uses
+  // absolute virtual addresses, so report zero there.
+  if (ehdr != nullptr && ehdr->e_type == ET_DYN)
+    return reinterpret_cast<ElfW(Addr)>(ehdr);
+
+  return 0;
+}
+
+LIBC_INLINE int call_with_executable(__dl_iterate_phdr_callback_t callback,
+                                     void *arg) {
+  const ElfW(Ehdr) *ehdr = get_executable_ehdr();
+  if (ehdr == nullptr)
+    return -1;
+
+  cpp::optional<unsigned long> aux_phdr_val = auxv::get(AT_PHDR);
+  cpp::optional<unsigned long> aux_phnum_val = auxv::get(AT_PHNUM);
+  auto *aux_phdr = reinterpret_cast<const ElfW(Phdr) *>(
+      aux_phdr_val ? *aux_phdr_val : 0);
+  const auto aux_phnum = static_cast<ElfW(Half)>(aux_phnum_val ? *aux_phnum_val
+                                                               : 0);
+
+  const ElfW(Phdr) *phdr =
+      aux_phdr != nullptr ? aux_phdr
+                          : reinterpret_cast<const ElfW(Phdr) *>(
+                                reinterpret_cast<uintptr_t>(ehdr) + ehdr->e_phoff);
+  const ElfW(Half) phnum = aux_phnum != 0 ? aux_phnum : ehdr->e_phnum;
+
+  dl_phdr_info exe_info = {};
+  exe_info.dlpi_addr = get_executable_load_bias(ehdr, phdr, phnum);
+  exe_info.dlpi_name = nullptr;
+  exe_info.dlpi_phdr = phdr;
+  exe_info.dlpi_phnum = phnum;
+  exe_info.dlpi_adds = 0;
+  exe_info.dlpi_subs = 0;
+  exe_info.dlpi_tls_modid = 0;
+  exe_info.dlpi_tls_data = nullptr;
+  return callback(&exe_info, sizeof(exe_info), arg);
+}
+
+LIBC_INLINE int call_with_vdso(__dl_iterate_phdr_callback_t callback,
+                               void *arg) {
+  cpp::optional<unsigned long> aux_vdso = auxv::get(AT_SYSINFO_EHDR);
+  auto *ehdr_vdso =
+      reinterpret_cast<const ElfW(Ehdr) *>(aux_vdso ? *aux_vdso : 0);
+  if (!is_valid_elf(ehdr_vdso))
+    return 0;
+
+  auto *phdr = reinterpret_cast<const ElfW(Phdr) *>(
+      reinterpret_cast<uintptr_t>(ehdr_vdso) + ehdr_vdso->e_phoff);
+
+  dl_phdr_info vdso_info = {};
+  vdso_info.dlpi_name = nullptr;
+  vdso_info.dlpi_phdr = phdr;
+  vdso_info.dlpi_phnum = ehdr_vdso->e_phnum;
+  vdso_info.dlpi_adds = 0;
+  vdso_info.dlpi_subs = 0;
+  vdso_info.dlpi_tls_modid = 0;
+  vdso_info.dlpi_tls_data = nullptr;
+
+  for (ElfW(Half) i = 0; i < ehdr_vdso->e_phnum; ++i) {
+    if (phdr[i].p_type == PT_LOAD) {
+      vdso_info.dlpi_addr = reinterpret_cast<ElfW(Addr)>(ehdr_vdso) -
+                            static_cast<ElfW(Addr)>(phdr[i].p_vaddr);
+      break;
+    }
+  }
+
+  return callback(&vdso_info, sizeof(vdso_info), arg);
+}
+
+} // namespace
+
 LLVM_LIBC_FUNCTION(int, dl_iterate_phdr,
                    (__dl_iterate_phdr_callback_t callback, void *arg)) {
-  // FIXME: For pure static linking, this can report just the executable with
-  // info from __ehdr_start or AT_{PHDR,PHNUM} decoding, and its PT_TLS; and it
-  // could report the vDSO.
-  (void)callback, (void)arg;
+#if defined(LIBC_TARGET_ARCH_IS_X86_64) || defined(LIBC_TARGET_ARCH_IS_AARCH64)
+  if (callback == nullptr)
+    return -1;
+
+  int rc = call_with_executable(callback, arg);
+  if (rc != 0)
+    return rc;
+  return call_with_vdso(callback, arg);
+#else
+  (void)callback;
+  (void)arg;
   return 0;
+#endif
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pthread/pthread_self.cpp b/libc/src/pthread/pthread_self.cpp
index c3169ec1ca5c4..e810b033170cb 100644
--- a/libc/src/pthread/pthread_self.cpp
+++ b/libc/src/pthread/pthread_self.cpp
@@ -20,8 +20,7 @@ static_assert(sizeof(pthread_t) == sizeof(LIBC_NAMESPACE::Thread),
               "Mismatch between pthread_t and internal Thread.");
 
 LLVM_LIBC_FUNCTION(pthread_t, pthread_self, ()) {
-  pthread_t th;
-  th.__attrib = self.attrib;
+  pthread_t th = self.attrib;
   return th;
 }
 
diff --git a/libc/src/threads/thrd_current.cpp b/libc/src/threads/thrd_current.cpp
index 634159712b629..29a0de13ef85e 100644
--- a/libc/src/threads/thrd_current.cpp
+++ b/libc/src/threads/thrd_current.cpp
@@ -19,8 +19,7 @@ static_assert(sizeof(thrd_t) == sizeof(LIBC_NAMESPACE::Thread),
               "Mismatch between thrd_t and internal Thread.");
 
 LLVM_LIBC_FUNCTION(thrd_t, thrd_current, ()) {
-  thrd_t th;
-  th.__attrib = self.attrib;
+  thrd_t th = self.attrib;
   return th;
 }
 
diff --git a/libcxx/include/__mbstate_t.h b/libcxx/include/__mbstate_t.h
index d331ba622d37f..d7ff5b8dd4adc 100644
--- a/libcxx/include/__mbstate_t.h
+++ b/libcxx/include/__mbstate_t.h
@@ -39,6 +39,8 @@
 #  define __NEED_mbstate_t
 #  include <bits/alltypes.h>
 #  undef __NEED_mbstate_t
+#elif __has_include(<llvm-libc-types/mbstate_t.h>)
+#  include <llvm-libc-types/mbstate_t.h>
 #elif __has_include(<bits/types/mbstate_t.h>)
 #  include <bits/types/mbstate_t.h> // works on most Unixes
 #elif __has_include(<sys/_types/_mbstate_t.h>)
diff --git a/run.sh b/run.sh
new file mode 100755
index 0000000000000..748565f0ac573
--- /dev/null
+++ b/run.sh
@@ -0,0 +1,340 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+repo="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+build="${BUILD_DIR:-$repo/build-libcxx-llvm}"
+base_c_compiler="${CC:-/usr/bin/clang}"
+base_compiler="${CXX:-/usr/bin/clang++}"
+lit_jobs="${LIT_JOBS:-4}"
+host_arch="$(uname -m)"
+multiarch_triple=""
+configure_c_flags="${CFLAGS:-}"
+configure_cxx_flags="${CXXFLAGS:-}"
+compiler_is_clang=0
+
+case "$host_arch" in
+  x86_64)
+    compiler_rt_arch=x86_64
+    ;;
+  aarch64|arm64)
+    compiler_rt_arch=aarch64
+    ;;
+  *)
+    compiler_rt_arch="$host_arch"
+    ;;
+esac
+
+if "$base_compiler" -dM -E -x c++ /dev/null 2>/dev/null | grep -q '__clang__'; then
+  compiler_is_clang=1
+fi
+
+cd "$repo"
+
+for candidate in \
+  "$(
+    case "$host_arch" in
+      x86_64) echo x86_64-linux-gnu ;;
+      aarch64|arm64) echo aarch64-linux-gnu ;;
+      riscv64) echo riscv64-linux-gnu ;;
+      *) ;;
+    esac
+  )" \
+  "$("$base_c_compiler" -print-multiarch 2>/dev/null || true)" \
+  "$("$base_compiler" -print-multiarch 2>/dev/null || true)" \
+  "$("$base_c_compiler" -dumpmachine 2>/dev/null || true)" \
+  "$("$base_compiler" -dumpmachine 2>/dev/null || true)"; do
+  if [[ -n "$candidate" && -d "/usr/include/$candidate" ]]; then
+    multiarch_triple="$candidate"
+    break
+  fi
+done
+
+if [[ -z "$multiarch_triple" ]]; then
+  multiarch_triple="$(find /usr/include -mindepth 1 -maxdepth 1 -type d -name '*-linux-gnu' -exec test -d '{}/asm' ';' -print -quit | xargs -r basename)"
+fi
+
+if [[ -n "$multiarch_triple" ]]; then
+  configure_c_flags="${configure_c_flags:+$configure_c_flags }-idirafter/usr/include/$multiarch_triple"
+  configure_cxx_flags="${configure_cxx_flags:+$configure_cxx_flags }-idirafter/usr/include/$multiarch_triple"
+fi
+
+if [[ ! -f "$build/CMakeCache.txt" ]]; then
+  mkdir -p "$build"
+  cmake \
+    -S "$repo/runtimes" \
+    -B "$build" \
+    -G Ninja \
+    -DCMAKE_BUILD_TYPE=Release \
+    -DCMAKE_C_COMPILER="$base_c_compiler" \
+    -DCMAKE_CXX_COMPILER="$base_compiler" \
+    -DCMAKE_C_FLAGS="$configure_c_flags" \
+    -DCMAKE_CXX_FLAGS="$configure_cxx_flags" \
+    -DCMAKE_C_COMPILER_LAUNCHER= \
+    -DCMAKE_CXX_COMPILER_LAUNCHER= \
+    -DLIBUNWIND_ENABLE_SHARED=OFF \
+    -DLIBUNWIND_ENABLE_STATIC=ON \
+    -DLIBCXXABI_ENABLE_SHARED=OFF \
+    -DLIBCXXABI_ENABLE_STATIC=ON \
+    -DLIBCXXABI_ENABLE_STATIC_UNWINDER=ON \
+    -DLIBCXX_ENABLE_SHARED=OFF \
+    -DLIBCXX_ENABLE_STATIC=ON \
+    -DLIBCXX_ENABLE_STATIC_ABI_LIBRARY=ON \
+    -DLIBCXX_ENABLE_FILESYSTEM=OFF \
+    -DLIBCXX_ENABLE_WIDE_CHARACTERS=OFF \
+    -DLLVM_LIBC_INCLUDE_SCUDO=ON \
+    -DCOMPILER_RT_BUILD_SCUDO_STANDALONE_WITH_LLVM_LIBC=ON \
+    -DCOMPILER_RT_BUILD_GWP_ASAN=OFF \
+    -DCOMPILER_RT_SCUDO_STANDALONE_BUILD_SHARED=OFF \
+    -DRUNTIMES_USE_LIBC=llvm-libc \
+    -DLLVM_ENABLE_RUNTIMES="libc;compiler-rt;libunwind;libcxxabi;libcxx" \
+    -DLIBCXX_INCLUDE_TESTS=ON \
+    -DLIBCXX_INCLUDE_BENCHMARKS=OFF
+fi
+
+builtins_target="clang_rt.builtins-$compiler_rt_arch"
+builtins_target_candidates="$(ninja -C "$build" -t targets all | sed -n 's/:.*//; /^clang_rt\.builtins-/p; /^libclang_rt\.builtins-.*\.a$/p')"
+if ! grep -qx "$builtins_target" <<<"$builtins_target_candidates"; then
+  alt_target="libclang_rt.builtins-$compiler_rt_arch.a"
+  if grep -qx "$alt_target" <<<"$builtins_target_candidates"; then
+    builtins_target="$alt_target"
+  else
+    builtins_target="$(head -n 1 <<<"$builtins_target_candidates")"
+    if [[ -z "$builtins_target" ]]; then
+      echo "unable to locate a compiler-rt builtins target in $build" >&2
+      exit 1
+    fi
+  fi
+fi
+
+root="$(mktemp -d "$build/run-sandbox.XXXXXX")"
+launcher_shims="$root/shims"
+sysroot="$root/sysroot"
+wrapper="$root/clang++-llvm-libc"
+lit_wrapper="$root/llvm-lit-fork"
+empty_include="$root/empty-usr-local-include"
+fortify_shim_c="$root/fortify-shim.c"
+fortify_shim_o="$root/fortify-shim.o"
+multiarch_include_dir=""
+asm_source_dir=""
+
+cleanup() {
+  rm -rf "$root"
+}
+trap cleanup EXIT
+
+mkdir -p "$launcher_shims"
+cat > "$launcher_shims/sccache" <<'EOF'
+#!/usr/bin/env bash
+exec "$@"
+EOF
+chmod +x "$launcher_shims/sccache"
+export PATH="$launcher_shims:$PATH"
+
+if (($# == 0)); then
+  targets=("$build/libcxx/test")
+else
+  targets=("$@")
+fi
+
+ninja -C "$build" \
+  libc/include/generate-libc-headers \
+  crt1.o \
+  libc.a \
+  libm.a \
+  lib/libunwind.a \
+  "$builtins_target" \
+  cxx-test-depends
+
+builtins="$build/compiler-rt/lib/linux/libclang_rt.builtins-$compiler_rt_arch.a"
+if [[ ! -f "$builtins" ]]; then
+  builtins="$(find "$build/compiler-rt/lib" -name "libclang_rt.builtins-$compiler_rt_arch.a" -print -quit)"
+fi
+if [[ -z "${builtins:-}" || ! -f "$builtins" ]]; then
+  builtins="$(find "$build/compiler-rt/lib" -name 'libclang_rt.builtins-*.a' -print -quit)"
+  if [[ -z "${builtins:-}" ]]; then
+    echo "unable to locate compiler-rt builtins archive under $build/compiler-rt/lib" >&2
+    exit 1
+  fi
+fi
+
+mkdir -p "$sysroot/usr/include" "$empty_include"
+
+# Preserve Linux UAPI headers, but do not copy libc++ into /usr/include.
+if [[ -d /usr/include/linux ]]; then
+  cp -a /usr/include/linux "$sysroot/usr/include/"
+fi
+if [[ -d /usr/include/asm-generic ]]; then
+  cp -a /usr/include/asm-generic "$sysroot/usr/include/"
+fi
+if [[ -n "$multiarch_triple" ]]; then
+  multiarch_include_dir="$sysroot/usr/include/$multiarch_triple"
+  mkdir -p "$multiarch_include_dir"
+fi
+if [[ -n "$multiarch_triple" && -d "/usr/include/$multiarch_triple/asm" ]]; then
+  asm_source_dir="$(readlink -f "/usr/include/$multiarch_triple/asm")"
+elif [[ -d /usr/include/asm ]]; then
+  asm_source_dir="$(readlink -f /usr/include/asm)"
+fi
+if [[ -n "$asm_source_dir" ]]; then
+  mkdir -p "${multiarch_include_dir:-$sysroot/usr/include}/asm"
+  cp -a "$asm_source_dir/." "${multiarch_include_dir:-$sysroot/usr/include}/asm/"
+fi
+cp -a "$build/libc/include/." "$sysroot/usr/include/"
+
+crtbegin="$("$base_compiler" --print-file-name=crtbeginT.o)"
+crtend="$("$base_compiler" --print-file-name=crtend.o)"
+
+cat > "$fortify_shim_c" <<'EOF'
+typedef __SIZE_TYPE__ size_t;
+
+__attribute__((noreturn)) static void __llvm_libcxx_test_chk_fail(void) {
+  __builtin_trap();
+}
+
+static void *__llvm_libcxx_test_memcpy(void *dst, const void *src, size_t len) {
+  unsigned char *out = (unsigned char *)dst;
+  const unsigned char *in = (const unsigned char *)src;
+  for (size_t i = 0; i < len; ++i)
+    out[i] = in[i];
+  return dst;
+}
+
+static void *__llvm_libcxx_test_memmove(void *dst, const void *src, size_t len) {
+  unsigned char *out = (unsigned char *)dst;
+  const unsigned char *in = (const unsigned char *)src;
+  if (out == in || len == 0)
+    return dst;
+  if (out < in || out >= in + len) {
+    for (size_t i = 0; i < len; ++i)
+      out[i] = in[i];
+  } else {
+    for (size_t i = len; i != 0; --i)
+      out[i - 1] = in[i - 1];
+  }
+  return dst;
+}
+
+static void *__llvm_libcxx_test_memset(void *dst, int value, size_t len) {
+  unsigned char *out = (unsigned char *)dst;
+  unsigned char byte = (unsigned char)value;
+  for (size_t i = 0; i < len; ++i)
+    out[i] = byte;
+  return dst;
+}
+
+void *__memcpy_chk(void *dst, const void *src, size_t len, size_t dstlen) {
+  if (len > dstlen)
+    __llvm_libcxx_test_chk_fail();
+  return __llvm_libcxx_test_memcpy(dst, src, len);
+}
+
+void *__memmove_chk(void *dst, const void *src, size_t len, size_t dstlen) {
+  if (len > dstlen)
+    __llvm_libcxx_test_chk_fail();
+  return __llvm_libcxx_test_memmove(dst, src, len);
+}
+
+void *__memset_chk(void *dst, int value, size_t len, size_t dstlen) {
+  if (len > dstlen)
+    __llvm_libcxx_test_chk_fail();
+  return __llvm_libcxx_test_memset(dst, value, len);
+}
+EOF
+
+"$base_compiler" \
+  -c \
+  -x c \
+  -fno-stack-protector \
+  -ffreestanding \
+  "$fortify_shim_c" \
+  -o "$fortify_shim_o"
+
+cat > "$wrapper" <<EOF
+#!/usr/bin/env bash
+set -euo pipefail
+
+base_compiler=$base_compiler
+sysroot=$sysroot
+repo=$repo
+build=$build
+crtbegin=$crtbegin
+crtend=$crtend
+builtins=$builtins
+fortify_shim_o=$fortify_shim_o
+multiarch_include_dir=$multiarch_include_dir
+compiler_is_clang=$compiler_is_clang
+
+linking=1
+for arg in "\$@"; do
+  case "\$arg" in
+    -c|-E|-S|-fsyntax-only|-emit-ast)
+      linking=0
+      ;;
+  esac
+done
+
+common_args=(
+  -nostdlibinc
+  -isystem "\$sysroot/usr/include"
+  -fno-stack-protector
+  -Wno-missing-braces
+)
+if (( compiler_is_clang )); then
+  # The sandboxed sysroot setup can trigger Clang's GCC-install-dir warning,
+  # and libc++'s config probes use -Werror while checking -std= flags.
+  common_args+=(-Wno-unknown-warning-option -Wno-gcc-install-dir-libstdcxx)
+fi
+if [[ -n "\$multiarch_include_dir" ]]; then
+  common_args+=(-isystem "\$multiarch_include_dir")
+fi
+
+if (( linking )); then
+  exec "\$base_compiler" \
+    "\$@" \
+    "\${common_args[@]}" \
+    -nodefaultlibs \
+    -nostartfiles \
+    -static \
+    "$build/libc/startup/linux/crt1.o" \
+    "\$crtbegin" \
+    -L "$build/libc/lib" \
+    -lunwind \
+    -lc \
+    -lm \
+    "\$fortify_shim_o" \
+    "\$builtins" \
+    "\$crtend"
+else
+  exec "\$base_compiler" "\$@" "\${common_args[@]}"
+fi
+EOF
+
+chmod +x "$wrapper"
+
+cat > "$lit_wrapper" <<EOF
+#!/usr/bin/env python3
+import multiprocessing as mp
+import runpy
+import sys
+
+mp.set_start_method("fork")
+sys.argv[0] = "$build/bin/llvm-lit"
+runpy.run_path("$build/bin/llvm-lit", run_name="__main__")
+EOF
+
+chmod +x "$lit_wrapper"
+
+bwrap \
+  --ro-bind / / \
+  --bind "$repo" "$repo" \
+  --dev-bind /dev /dev \
+  --proc /proc \
+  --tmpfs /tmp \
+  --chdir "$repo" \
+  --ro-bind "$sysroot/usr/include" /usr/include \
+  --ro-bind "$empty_include" /usr/local/include \
+  "$lit_wrapper" \
+  -sv \
+  "-j$lit_jobs" \
+  --param "compiler=$wrapper" \
+  "${targets[@]}"
diff --git a/runtimes/CMakeLists.txt b/runtimes/CMakeLists.txt
index 75551ba0c651a..61a43356694f3 100644
--- a/runtimes/CMakeLists.txt
+++ b/runtimes/CMakeLists.txt
@@ -59,6 +59,15 @@ foreach(proj ${LLVM_ENABLE_RUNTIMES})
   endif()
 endforeach()
 
+# The in-tree LLVM libc only provides a complete public C header set in
+# full-build mode. Consumers like libc++, libc++abi, and libunwind need those
+# headers to avoid falling back to the host libc in overlay mode.
+if(DEFINED RUNTIMES_USE_LIBC AND RUNTIMES_USE_LIBC STREQUAL "llvm-libc")
+  message(STATUS "Enabling LLVM_LIBC_FULL_BUILD because RUNTIMES_USE_LIBC=llvm-libc")
+  set(LLVM_LIBC_FULL_BUILD ON CACHE BOOL
+      "Build and test LLVM libc as if it is the full libc" FORCE)
+endif()
+
 function(runtime_register_component name)
   set_property(GLOBAL APPEND PROPERTY SUB_COMPONENTS ${name})
 endfunction()

>From 96ee0396c9a87ff6b376154d82d7250e68a4e296 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Wed, 29 Apr 2026 10:26:27 -0400
Subject: [PATCH 02/13] fix

---
 .../thread.thread.class/thread.thread.id/format.pass.cpp        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp
index 39a257592809d..69156777c2767 100644
--- a/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp
+++ b/libcxx/test/std/thread/thread.threads/thread.thread.class/thread.thread.id/format.pass.cpp
@@ -52,7 +52,7 @@ void test_format(StringViewT expected, std::thread::id arg) {
 
 template <class CharT>
 void test_fmt() {
-#if !defined(__APPLE__) && !defined(__FreeBSD__)
+#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined (__LLVM_LIBC__)
   test_format(SV("0"), std::thread::id());
 #else
   test_format(SV("0x0"), std::thread::id());

>From feb115d657d3a7c10d88ce2b2ab070cd2b39a268 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Wed, 29 Apr 2026 10:47:20 -0400
Subject: [PATCH 03/13] patch TLS

---
 libc/src/__support/threads/thread.cpp | 31 ++++++++++++++++++++++-----
 1 file changed, 26 insertions(+), 5 deletions(-)

diff --git a/libc/src/__support/threads/thread.cpp b/libc/src/__support/threads/thread.cpp
index ae179f068b88e..3e652172eace6 100644
--- a/libc/src/__support/threads/thread.cpp
+++ b/libc/src/__support/threads/thread.cpp
@@ -10,6 +10,7 @@
 #include "src/__support/macros/config.h"
 #include "src/__support/threads/mutex.h"
 
+#include "hdr/limits_macros.h"
 #include "src/__support/CPP/array.h"
 #include "src/__support/CPP/mutex.h" // lock_guard
 #include "src/__support/CPP/optional.h"
@@ -155,11 +156,31 @@ ThreadAtExitCallbackMgr *get_thread_atexit_callback_mgr() {
 
 void call_atexit_callbacks(ThreadAttributes *attrib) {
   attrib->atexit_callback_mgr->call();
-  for (size_t i = 0; i < TSS_KEY_COUNT; ++i) {
-    TSSValueUnit &unit = tss_values[i];
-    // Both dtor and value need to nonnull to call dtor
-    if (unit.dtor != nullptr && unit.payload != nullptr)
-      unit.dtor(unit.payload);
+
+  for (size_t iteration = 0; iteration < PTHREAD_DESTRUCTOR_ITERATIONS;
+       ++iteration) {
+    bool has_pending_tss_dtor = false;
+
+    for (size_t i = 0; i < TSS_KEY_COUNT; ++i) {
+      TSSValueUnit &unit = tss_values[i];
+      if (unit.dtor == nullptr || unit.payload == nullptr)
+        continue;
+
+      void *payload = unit.payload;
+      unit.payload = nullptr;
+      unit.dtor(payload);
+    }
+
+    for (size_t i = 0; i < TSS_KEY_COUNT; ++i) {
+      TSSValueUnit &unit = tss_values[i];
+      if (unit.dtor != nullptr && unit.payload != nullptr) {
+        has_pending_tss_dtor = true;
+        break;
+      }
+    }
+
+    if (!has_pending_tss_dtor)
+      break;
   }
 }
 

>From 70183294cf2936606ab52d7692629672a084c00f Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Sat, 25 Apr 2026 12:39:35 -0400
Subject: [PATCH 04/13] [libc] add proc number parser and sysconf wrapper

Assisted-by: Codex with gpt-5.4 high fast
---
 .../llvm-libc-macros/linux/unistd-macros.h    |   2 +
 libc/include/unistd.yaml                      |   8 +
 .../src/__support/OSUtil/linux/CMakeLists.txt |  17 ++
 .../linux/syscall_wrappers/CMakeLists.txt     |  14 ++
 .../syscall_wrappers/sched_getaffinity.h      |  36 ++++
 libc/src/__support/OSUtil/linux/sysinfo.h     | 188 ++++++++++++++++++
 libc/src/unistd/linux/CMakeLists.txt          |   1 +
 libc/src/unistd/linux/sysconf.cpp             |  20 +-
 .../src/__support/OSUtil/linux/CMakeLists.txt |  12 ++
 .../__support/OSUtil/linux/sysinfo_test.cpp   |  99 +++++++++
 libc/test/src/unistd/sysconf_test.cpp         |  19 ++
 11 files changed, 409 insertions(+), 7 deletions(-)
 create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h
 create mode 100644 libc/src/__support/OSUtil/linux/sysinfo.h
 create mode 100644 libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp

diff --git a/libc/include/llvm-libc-macros/linux/unistd-macros.h b/libc/include/llvm-libc-macros/linux/unistd-macros.h
index a4c8e3cd91f7e..ba1ae48f17b78 100644
--- a/libc/include/llvm-libc-macros/linux/unistd-macros.h
+++ b/libc/include/llvm-libc-macros/linux/unistd-macros.h
@@ -17,6 +17,8 @@
 
 #define _SC_PAGESIZE 1
 #define _SC_PAGE_SIZE _SC_PAGESIZE
+#define _SC_NPROCESSORS_CONF 83
+#define _SC_NPROCESSORS_ONLN 84
 
 #define _PC_FILESIZEBITS 0
 #define _PC_LINK_MAX 1
diff --git a/libc/include/unistd.yaml b/libc/include/unistd.yaml
index 5966ab9199890..4c6856ccca4bd 100644
--- a/libc/include/unistd.yaml
+++ b/libc/include/unistd.yaml
@@ -14,6 +14,14 @@ macros:
     macro_header: unistd-macros.h
   - macro_name: STDERR_FILENO
     macro_header: unistd-macros.h
+  - macro_name: _SC_PAGESIZE
+    macro_header: unistd-macros.h
+  - macro_name: _SC_PAGE_SIZE
+    macro_header: unistd-macros.h
+  - macro_name: _SC_NPROCESSORS_CONF
+    macro_header: unistd-macros.h
+  - macro_name: _SC_NPROCESSORS_ONLN
+    macro_header: unistd-macros.h
 types:
   - type_name: uid_t
   - type_name: gid_t
diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt
index b02fa141fa456..2f41d88ae3875 100644
--- a/libc/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt
@@ -38,6 +38,23 @@ add_header_library(
     libc.src.__support.threads.callonce
 )
 
+add_header_library(
+  sysinfo
+  HDRS
+    sysinfo.h
+  DEPENDS
+    libc.hdr.errno_macros
+    libc.src.__support.CPP.array
+    libc.src.__support.CPP.bit
+    libc.src.__support.CPP.optional
+    libc.src.__support.CPP.string_view
+    libc.src.__support.OSUtil.linux.syscall_wrappers.close
+    libc.src.__support.OSUtil.linux.syscall_wrappers.open
+    libc.src.__support.OSUtil.linux.syscall_wrappers.read
+    libc.src.__support.OSUtil.linux.syscall_wrappers.sched_getaffinity
+    libc.src.__support.ctype_utils
+)
+
 add_header_library(
   vdso_sym
   HDRS
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index 049eeac913780..ddabd0c39b858 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -11,6 +11,20 @@ add_header_library(
     libc.include.sys_syscall
 )
 
+add_header_library(
+  sched_getaffinity
+  HDRS
+    sched_getaffinity.h
+  DEPENDS
+    libc.hdr.types.pid_t
+    libc.src.__support.CPP.span
+    libc.src.__support.OSUtil.osutil
+    libc.src.__support.common
+    libc.src.__support.error_or
+    libc.src.__support.macros.config
+    libc.include.sys_syscall
+)
+
 add_header_library(
   close
   HDRS
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h
new file mode 100644
index 0000000000000..33a7bb41481de
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h
@@ -0,0 +1,36 @@
+//===-- Implementation header for sched_getaffinity -----------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_SCHED_GETAFFINITY_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_SCHED_GETAFFINITY_H
+
+#include "hdr/types/pid_t.h"
+#include "src/__support/CPP/span.h"
+#include "src/__support/OSUtil/linux/syscall.h" // syscall_impl
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/macros/config.h"
+
+#include <sys/syscall.h> // For syscall numbers
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+LIBC_INLINE ErrorOr<int> sched_getaffinity(pid_t tid,
+                                           cpp::span<unsigned char> mask) {
+  int ret =
+      syscall_impl<int>(SYS_sched_getaffinity, tid, mask.size(), mask.data());
+  if (ret < 0)
+    return Error(-ret);
+  return ret;
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_SYSCALL_WRAPPERS_SCHED_GETAFFINITY_H
diff --git a/libc/src/__support/OSUtil/linux/sysinfo.h b/libc/src/__support/OSUtil/linux/sysinfo.h
new file mode 100644
index 0000000000000..c1389fb4d6337
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/sysinfo.h
@@ -0,0 +1,188 @@
+//===------------- Linux sysinfo support -------------------------------------//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSINFO_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSINFO_H
+
+#include "hdr/errno_macros.h"
+#include "src/__support/CPP/array.h"
+#include "src/__support/CPP/bit.h"
+#include "src/__support/CPP/optional.h"
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/read.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h"
+#include "src/__support/ctype_utils.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace sysinfo {
+
+LIBC_INLINE_VAR constexpr cpp::string_view POSSIBLE_NPROC_PATH =
+    "/sys/devices/system/cpu/possible";
+LIBC_INLINE_VAR constexpr cpp::string_view ONLINE_NPROC_PATH =
+    "/sys/devices/system/cpu/online";
+
+// Parses Linux CPU-list syntax:
+//   list  := item (',' item)*
+//   item  := number | number '-' number
+//   number := [0-9]+
+class ProcParser {
+  enum class ProcParserState {
+    ParseUnstarted,
+    ParseNumber,
+    ParseRangeSeparator,
+    ParseRangeEnd
+  };
+
+  ProcParserState state;
+  cpp::array<char, 128> buffer;
+  int fd;
+  size_t cursor;
+  size_t buffer_end;
+  size_t cpu_count;
+  size_t current_number;
+  size_t range_start;
+  bool has_error;
+
+  LIBC_INLINE static int open_path(cpp::string_view path) {
+    ErrorOr<int> open_result =
+        linux_syscalls::open(path.data(), O_RDONLY | O_CLOEXEC, 0);
+    return open_result ? *open_result : -1;
+  }
+
+  LIBC_INLINE cpp::optional<char> next_char() {
+    if (fd < 0)
+      return cpp::nullopt;
+
+    while (cursor == buffer_end) {
+      ErrorOr<ssize_t> bytes_read =
+          linux_syscalls::read(fd, buffer.data(), buffer.size());
+      if (!bytes_read) {
+        if (bytes_read.error() == EINTR)
+          continue;
+        has_error = true;
+        return cpp::nullopt;
+      }
+
+      if (*bytes_read == 0)
+        return cpp::nullopt;
+
+      cursor = 0;
+      buffer_end = static_cast<size_t>(*bytes_read);
+    }
+
+    return buffer[cursor++];
+  }
+
+  LIBC_INLINE bool finish_group() {
+    if (state == ProcParserState::ParseUnstarted)
+      return true;
+    if (state == ProcParserState::ParseRangeSeparator)
+      return false;
+
+    if (state == ProcParserState::ParseRangeEnd) {
+      if (current_number < range_start)
+        return false;
+      cpu_count += current_number - range_start + 1;
+    } else {
+      ++cpu_count;
+    }
+
+    current_number = 0;
+    range_start = 0;
+    state = ProcParserState::ParseUnstarted;
+    return true;
+  }
+
+  LIBC_INLINE bool consume(char ch) {
+    if (internal::isdigit(ch)) {
+      current_number = current_number * 10 + static_cast<size_t>(ch - '0');
+      if (state == ProcParserState::ParseUnstarted)
+        state = ProcParserState::ParseNumber;
+      else if (state == ProcParserState::ParseRangeSeparator)
+        state = ProcParserState::ParseRangeEnd;
+      return true;
+    }
+
+    if (ch == '-') {
+      if (state != ProcParserState::ParseNumber)
+        return false;
+      range_start = current_number;
+      current_number = 0;
+      state = ProcParserState::ParseRangeSeparator;
+      return true;
+    }
+
+    if (ch == ',' || ch == '\n')
+      return finish_group();
+
+    if (ch == ' ' || ch == '\t' || ch == '\r')
+      return state == ProcParserState::ParseUnstarted ? true : finish_group();
+
+    return false;
+  }
+
+public:
+  LIBC_INLINE explicit ProcParser(cpp::string_view path)
+      : state(ProcParserState::ParseUnstarted), buffer{}, fd(open_path(path)),
+        cursor(buffer.size()), buffer_end(buffer.size()), cpu_count(0),
+        current_number(0), range_start(0), has_error(fd < 0) {}
+
+  LIBC_INLINE ~ProcParser() {
+    if (fd >= 0)
+      linux_syscalls::close(fd);
+  }
+
+  LIBC_INLINE cpp::optional<size_t> parse() {
+    if (fd < 0)
+      return cpp::nullopt;
+
+    while (cpp::optional<char> ch = next_char())
+      if (!consume(*ch))
+        return cpp::nullopt;
+
+    if (has_error)
+      return cpp::nullopt;
+
+    if (!finish_group())
+      return cpp::nullopt;
+
+    if (cpu_count == 0)
+      return cpp::nullopt;
+    return cpu_count;
+  }
+};
+
+LIBC_INLINE cpp::optional<size_t> parse_nproc_from(cpp::string_view path) {
+  return ProcParser(path).parse();
+}
+
+LIBC_INLINE size_t parse_nproc_with_fallback_from(cpp::string_view path) {
+  if (cpp::optional<size_t> cpu_count = parse_nproc_from(path))
+    return *cpu_count;
+
+  cpp::array<unsigned char, 128> mask_buffer = {};
+
+  ErrorOr<int> affinity_result =
+      linux_syscalls::sched_getaffinity(0, mask_buffer);
+  if (!affinity_result)
+    return 1;
+
+  size_t cpu_count = 0;
+  for (size_t i = 0; i < mask_buffer.size(); ++i)
+    cpu_count += static_cast<size_t>(cpp::popcount(mask_buffer[i]));
+
+  return cpu_count > 0 ? cpu_count : 1;
+}
+
+} // namespace sysinfo
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSINFO_H
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index c31fe24ac85cb..56a43d31d808b 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -614,6 +614,7 @@ add_entrypoint_object(
   DEPENDS
     libc.include.unistd
     libc.include.sys_auxv
+    libc.src.__support.OSUtil.linux.sysinfo
     libc.src.__support.libc_errno
     libc.src.__support.OSUtil.linux.auxv
 )
diff --git a/libc/src/unistd/linux/sysconf.cpp b/libc/src/unistd/linux/sysconf.cpp
index 979e178cb45ee..3243b7afa5596 100644
--- a/libc/src/unistd/linux/sysconf.cpp
+++ b/libc/src/unistd/linux/sysconf.cpp
@@ -12,26 +12,32 @@
 
 #include "hdr/unistd_macros.h"
 #include "src/__support/OSUtil/linux/auxv.h"
+#include "src/__support/OSUtil/linux/sysinfo.h"
 #include "src/__support/libc_errno.h"
 #include "src/__support/macros/config.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
 LLVM_LIBC_FUNCTION(long, sysconf, (int name)) {
-  long ret = 0;
   if (name == _SC_PAGESIZE) {
     cpp::optional<unsigned long> page_size = auxv::get(AT_PAGESZ);
     if (page_size)
       return static_cast<long>(*page_size);
-    ret = -1;
-  }
-
-  // TODO: Complete the rest of the sysconf options.
-  if (ret < 0) {
     libc_errno = EINVAL;
     return -1;
   }
-  return ret;
+
+  if (name == _SC_NPROCESSORS_CONF)
+    return static_cast<long>(
+        sysinfo::parse_nproc_with_fallback_from(sysinfo::POSSIBLE_NPROC_PATH));
+
+  if (name == _SC_NPROCESSORS_ONLN)
+    return static_cast<long>(
+        sysinfo::parse_nproc_with_fallback_from(sysinfo::ONLINE_NPROC_PATH));
+
+  // TODO: Complete the rest of the sysconf options.
+  libc_errno = EINVAL;
+  return -1;
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
index ff82616cc4a70..2549a8edf9d91 100644
--- a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
@@ -2,6 +2,18 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_ARCHITECTURE})
   add_subdirectory(${LIBC_TARGET_ARCHITECTURE})
 endif()
 
+add_libc_test(
+  sysinfo_test
+  SUITE libc-osutil-tests
+  SRCS sysinfo_test.cpp
+  DEPENDS
+    libc.src.__support.OSUtil.linux.sysinfo
+    libc.src.fcntl.open
+    libc.src.unistd.close
+    libc.src.unistd.unlink
+    libc.src.unistd.write
+)
+
 add_libc_test(
   vdso_test
   SUITE libc-osutil-tests
diff --git a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
new file mode 100644
index 0000000000000..e4e54ff12124b
--- /dev/null
+++ b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
@@ -0,0 +1,99 @@
+//===-- Unittests for Linux sysinfo support -------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/CPP/string_view.h"
+#include "src/__support/OSUtil/linux/sysinfo.h"
+#include "src/fcntl/open.h"
+#include "src/unistd/close.h"
+#include "src/unistd/unlink.h"
+#include "src/unistd/write.h"
+#include "test/UnitTest/Test.h"
+
+#include <sys/sysinfo.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+static int write_test_file(cpp::string_view path, cpp::string_view contents) {
+  int fd = LIBC_NAMESPACE::open(path.data(), O_WRONLY | O_CREAT | O_TRUNC, 0600);
+  if (fd < 0)
+    return fd;
+
+  if (LIBC_NAMESPACE::write(fd, contents.data(), contents.size()) !=
+      static_cast<ssize_t>(contents.size())) {
+    LIBC_NAMESPACE::close(fd);
+    return -1;
+  }
+
+  return LIBC_NAMESPACE::close(fd);
+}
+
+TEST(LlvmLibcOSUtilSysinfoTest, PossibleCpuCountMatchesHostSysconf) {
+  int cpu_count = ::get_nprocs_conf();
+  ASSERT_GT(cpu_count, 0);
+  cpp::optional<size_t> parsed =
+      sysinfo::parse_nproc_from(sysinfo::POSSIBLE_NPROC_PATH);
+  ASSERT_TRUE(static_cast<bool>(parsed));
+  EXPECT_EQ(*parsed, static_cast<size_t>(cpu_count));
+  EXPECT_EQ(sysinfo::parse_nproc_with_fallback_from(sysinfo::POSSIBLE_NPROC_PATH),
+            static_cast<size_t>(cpu_count));
+}
+
+TEST(LlvmLibcOSUtilSysinfoTest, OnlineCpuCountMatchesHostSysconf) {
+  int cpu_count = ::get_nprocs();
+  ASSERT_GT(cpu_count, 0);
+  cpp::optional<size_t> parsed =
+      sysinfo::parse_nproc_from(sysinfo::ONLINE_NPROC_PATH);
+  ASSERT_TRUE(static_cast<bool>(parsed));
+  EXPECT_EQ(*parsed, static_cast<size_t>(cpu_count));
+  EXPECT_EQ(sysinfo::parse_nproc_with_fallback_from(sysinfo::ONLINE_NPROC_PATH),
+            static_cast<size_t>(cpu_count));
+}
+
+TEST(LlvmLibcOSUtilSysinfoTest, SyntheticCpuLists) {
+  constexpr const char *FILENAME =
+      APPEND_LIBC_TEST("sysinfo.synthetic_cpu_list.test");
+  CString test_file = libc_make_test_file_path(FILENAME);
+  cpp::string_view test_file_path = static_cast<const char *>(test_file);
+
+  struct TestCase {
+    cpp::string_view contents;
+    cpp::optional<size_t> expected;
+  };
+
+  constexpr TestCase TEST_CASES[] = {
+      {"0\n", 1},
+      {"0-7\n", 8},
+      {"0-0,2,4-6\n", 5},
+      {"0-3,8-11\n", 8},
+      {"0-3,8-11,16\n", 9},
+      {"1,2,3,4-9,99\n", 10},
+      {"3-1\n", cpp::nullopt},
+      {"0-\n", cpp::nullopt},
+  };
+
+  for (const TestCase &test_case : TEST_CASES) {
+    ASSERT_EQ(write_test_file(test_file_path, test_case.contents), 0);
+    cpp::optional<size_t> parsed = sysinfo::parse_nproc_from(test_file_path);
+    EXPECT_EQ(static_cast<bool>(parsed), static_cast<bool>(test_case.expected));
+    if (parsed)
+      EXPECT_EQ(*parsed, *test_case.expected);
+    EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(test_file_path), size_t(0));
+  }
+
+  ASSERT_EQ(LIBC_NAMESPACE::unlink(test_file_path.data()), 0);
+}
+
+TEST(LlvmLibcOSUtilSysinfoTest, NonexistentPath) {
+  constexpr cpp::string_view test_file_path =
+      "/not-exist-at-all-path-for-libc-nproc-test";
+
+  EXPECT_FALSE(static_cast<bool>(sysinfo::parse_nproc_from(test_file_path)));
+  EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(test_file_path), size_t(0));
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/unistd/sysconf_test.cpp b/libc/test/src/unistd/sysconf_test.cpp
index 85bc1258a4863..d02c0a17391dc 100644
--- a/libc/test/src/unistd/sysconf_test.cpp
+++ b/libc/test/src/unistd/sysconf_test.cpp
@@ -9,9 +9,28 @@
 #include "src/unistd/sysconf.h"
 #include "test/UnitTest/Test.h"
 
+#include <sys/sysinfo.h>
 #include <unistd.h>
 
 TEST(LlvmLibcSysconfTest, PagesizeTest) {
   long pagesize = LIBC_NAMESPACE::sysconf(_SC_PAGESIZE);
   ASSERT_GT(pagesize, 0l);
 }
+
+TEST(LlvmLibcSysconfTest, NprocessorsConfTest) {
+  int cpu_count = ::get_nprocs_conf();
+  ASSERT_GT(cpu_count, 0);
+
+  long sysconf_count = LIBC_NAMESPACE::sysconf(_SC_NPROCESSORS_CONF);
+  ASSERT_GT(sysconf_count, 0l);
+  EXPECT_EQ(sysconf_count, static_cast<long>(cpu_count));
+}
+
+TEST(LlvmLibcSysconfTest, NprocessorsOnlnTest) {
+  int cpu_count = ::get_nprocs();
+  ASSERT_GT(cpu_count, 0);
+
+  long sysconf_count = LIBC_NAMESPACE::sysconf(_SC_NPROCESSORS_ONLN);
+  ASSERT_GT(sysconf_count, 0l);
+  EXPECT_EQ(sysconf_count, static_cast<long>(cpu_count));
+}

>From a5b533b301e6c7dcf1575229f8a93500e97cd512 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Sat, 25 Apr 2026 12:41:31 -0400
Subject: [PATCH 05/13] fix

---
 libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
index e4e54ff12124b..ff5d5dca12cf0 100644
--- a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
+++ b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
@@ -19,7 +19,8 @@
 namespace LIBC_NAMESPACE_DECL {
 
 static int write_test_file(cpp::string_view path, cpp::string_view contents) {
-  int fd = LIBC_NAMESPACE::open(path.data(), O_WRONLY | O_CREAT | O_TRUNC, 0600);
+  int fd =
+      LIBC_NAMESPACE::open(path.data(), O_WRONLY | O_CREAT | O_TRUNC, 0600);
   if (fd < 0)
     return fd;
 
@@ -39,8 +40,9 @@ TEST(LlvmLibcOSUtilSysinfoTest, PossibleCpuCountMatchesHostSysconf) {
       sysinfo::parse_nproc_from(sysinfo::POSSIBLE_NPROC_PATH);
   ASSERT_TRUE(static_cast<bool>(parsed));
   EXPECT_EQ(*parsed, static_cast<size_t>(cpu_count));
-  EXPECT_EQ(sysinfo::parse_nproc_with_fallback_from(sysinfo::POSSIBLE_NPROC_PATH),
-            static_cast<size_t>(cpu_count));
+  EXPECT_EQ(
+      sysinfo::parse_nproc_with_fallback_from(sysinfo::POSSIBLE_NPROC_PATH),
+      static_cast<size_t>(cpu_count));
 }
 
 TEST(LlvmLibcOSUtilSysinfoTest, OnlineCpuCountMatchesHostSysconf) {
@@ -82,7 +84,8 @@ TEST(LlvmLibcOSUtilSysinfoTest, SyntheticCpuLists) {
     EXPECT_EQ(static_cast<bool>(parsed), static_cast<bool>(test_case.expected));
     if (parsed)
       EXPECT_EQ(*parsed, *test_case.expected);
-    EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(test_file_path), size_t(0));
+    EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(test_file_path),
+              size_t(0));
   }
 
   ASSERT_EQ(LIBC_NAMESPACE::unlink(test_file_path.data()), 0);

>From fe9e1515165fa9718ee5a82c731c5d2ea43c3b8d Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Sat, 25 Apr 2026 12:42:11 -0400
Subject: [PATCH 06/13] test

---
 .../__support/OSUtil/linux/sysinfo_test.cpp   | 29 +++++++------------
 libc/test/src/unistd/sysconf_test.cpp         |  9 ------
 2 files changed, 11 insertions(+), 27 deletions(-)

diff --git a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
index ff5d5dca12cf0..752dc73ffea06 100644
--- a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
+++ b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
@@ -14,8 +14,6 @@
 #include "src/unistd/write.h"
 #include "test/UnitTest/Test.h"
 
-#include <sys/sysinfo.h>
-
 namespace LIBC_NAMESPACE_DECL {
 
 static int write_test_file(cpp::string_view path, cpp::string_view contents) {
@@ -33,27 +31,22 @@ static int write_test_file(cpp::string_view path, cpp::string_view contents) {
   return LIBC_NAMESPACE::close(fd);
 }
 
-TEST(LlvmLibcOSUtilSysinfoTest, PossibleCpuCountMatchesHostSysconf) {
-  int cpu_count = ::get_nprocs_conf();
-  ASSERT_GT(cpu_count, 0);
+TEST(LlvmLibcOSUtilSysinfoTest, PossibleCpuCountSmokeTest) {
   cpp::optional<size_t> parsed =
       sysinfo::parse_nproc_from(sysinfo::POSSIBLE_NPROC_PATH);
   ASSERT_TRUE(static_cast<bool>(parsed));
-  EXPECT_EQ(*parsed, static_cast<size_t>(cpu_count));
-  EXPECT_EQ(
-      sysinfo::parse_nproc_with_fallback_from(sysinfo::POSSIBLE_NPROC_PATH),
-      static_cast<size_t>(cpu_count));
+  EXPECT_GT(*parsed, size_t(0));
+  EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(sysinfo::POSSIBLE_NPROC_PATH),
+            size_t(0));
 }
 
-TEST(LlvmLibcOSUtilSysinfoTest, OnlineCpuCountMatchesHostSysconf) {
-  int cpu_count = ::get_nprocs();
-  ASSERT_GT(cpu_count, 0);
+TEST(LlvmLibcOSUtilSysinfoTest, OnlineCpuCountSmokeTest) {
   cpp::optional<size_t> parsed =
       sysinfo::parse_nproc_from(sysinfo::ONLINE_NPROC_PATH);
   ASSERT_TRUE(static_cast<bool>(parsed));
-  EXPECT_EQ(*parsed, static_cast<size_t>(cpu_count));
-  EXPECT_EQ(sysinfo::parse_nproc_with_fallback_from(sysinfo::ONLINE_NPROC_PATH),
-            static_cast<size_t>(cpu_count));
+  EXPECT_GT(*parsed, size_t(0));
+  EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(sysinfo::ONLINE_NPROC_PATH),
+            size_t(0));
 }
 
 TEST(LlvmLibcOSUtilSysinfoTest, SyntheticCpuLists) {
@@ -92,11 +85,11 @@ TEST(LlvmLibcOSUtilSysinfoTest, SyntheticCpuLists) {
 }
 
 TEST(LlvmLibcOSUtilSysinfoTest, NonexistentPath) {
-  constexpr cpp::string_view test_file_path =
+  constexpr cpp::string_view TEST_PATH =
       "/not-exist-at-all-path-for-libc-nproc-test";
 
-  EXPECT_FALSE(static_cast<bool>(sysinfo::parse_nproc_from(test_file_path)));
-  EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(test_file_path), size_t(0));
+  EXPECT_FALSE(static_cast<bool>(sysinfo::parse_nproc_from(TEST_PATH)));
+  EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(TEST_PATH), size_t(0));
 }
 
 } // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/unistd/sysconf_test.cpp b/libc/test/src/unistd/sysconf_test.cpp
index d02c0a17391dc..e780e2c515585 100644
--- a/libc/test/src/unistd/sysconf_test.cpp
+++ b/libc/test/src/unistd/sysconf_test.cpp
@@ -9,7 +9,6 @@
 #include "src/unistd/sysconf.h"
 #include "test/UnitTest/Test.h"
 
-#include <sys/sysinfo.h>
 #include <unistd.h>
 
 TEST(LlvmLibcSysconfTest, PagesizeTest) {
@@ -18,19 +17,11 @@ TEST(LlvmLibcSysconfTest, PagesizeTest) {
 }
 
 TEST(LlvmLibcSysconfTest, NprocessorsConfTest) {
-  int cpu_count = ::get_nprocs_conf();
-  ASSERT_GT(cpu_count, 0);
-
   long sysconf_count = LIBC_NAMESPACE::sysconf(_SC_NPROCESSORS_CONF);
   ASSERT_GT(sysconf_count, 0l);
-  EXPECT_EQ(sysconf_count, static_cast<long>(cpu_count));
 }
 
 TEST(LlvmLibcSysconfTest, NprocessorsOnlnTest) {
-  int cpu_count = ::get_nprocs();
-  ASSERT_GT(cpu_count, 0);
-
   long sysconf_count = LIBC_NAMESPACE::sysconf(_SC_NPROCESSORS_ONLN);
   ASSERT_GT(sysconf_count, 0l);
-  EXPECT_EQ(sysconf_count, static_cast<long>(cpu_count));
 }

>From b17717f07c818243b9a788f4f4e788ec9a8d0a98 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Sat, 25 Apr 2026 13:17:44 -0400
Subject: [PATCH 07/13] fix

---
 libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
index 752dc73ffea06..96350dd9d7c18 100644
--- a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
+++ b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
@@ -36,8 +36,9 @@ TEST(LlvmLibcOSUtilSysinfoTest, PossibleCpuCountSmokeTest) {
       sysinfo::parse_nproc_from(sysinfo::POSSIBLE_NPROC_PATH);
   ASSERT_TRUE(static_cast<bool>(parsed));
   EXPECT_GT(*parsed, size_t(0));
-  EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(sysinfo::POSSIBLE_NPROC_PATH),
-            size_t(0));
+  EXPECT_GT(
+      sysinfo::parse_nproc_with_fallback_from(sysinfo::POSSIBLE_NPROC_PATH),
+      size_t(0));
 }
 
 TEST(LlvmLibcOSUtilSysinfoTest, OnlineCpuCountSmokeTest) {

>From 9be381cfa59c1da6256e7406fb61e46cc4754e14 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Sun, 26 Apr 2026 09:41:17 -0400
Subject: [PATCH 08/13] ensure nul termination

---
 libc/src/__support/OSUtil/linux/CMakeLists.txt        | 1 +
 libc/src/__support/OSUtil/linux/sysinfo.h             | 6 +++++-
 libc/test/src/__support/OSUtil/linux/CMakeLists.txt   | 1 +
 libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp | 7 +++++--
 4 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt
index 2f41d88ae3875..1790a84cdebba 100644
--- a/libc/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt
@@ -53,6 +53,7 @@ add_header_library(
     libc.src.__support.OSUtil.linux.syscall_wrappers.read
     libc.src.__support.OSUtil.linux.syscall_wrappers.sched_getaffinity
     libc.src.__support.ctype_utils
+    libc.src.string.memory_utils.inline_memcpy
 )
 
 add_header_library(
diff --git a/libc/src/__support/OSUtil/linux/sysinfo.h b/libc/src/__support/OSUtil/linux/sysinfo.h
index c1389fb4d6337..ecf1240f7f507 100644
--- a/libc/src/__support/OSUtil/linux/sysinfo.h
+++ b/libc/src/__support/OSUtil/linux/sysinfo.h
@@ -20,6 +20,7 @@
 #include "src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h"
 #include "src/__support/ctype_utils.h"
 #include "src/__support/macros/config.h"
+#include "src/string/memory_utils/inline_memcpy.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace sysinfo {
@@ -52,8 +53,11 @@ class ProcParser {
   bool has_error;
 
   LIBC_INLINE static int open_path(cpp::string_view path) {
+    __extension__ char buf[path.size() + 1];
+    inline_memcpy(buf, path.data(), path.size());
+    buf[path.size()] = '\0';
     ErrorOr<int> open_result =
-        linux_syscalls::open(path.data(), O_RDONLY | O_CLOEXEC, 0);
+        linux_syscalls::open(buf, O_RDONLY | O_CLOEXEC, 0);
     return open_result ? *open_result : -1;
   }
 
diff --git a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
index 2549a8edf9d91..941646d5a9d4d 100644
--- a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
@@ -9,6 +9,7 @@ add_libc_test(
   DEPENDS
     libc.src.__support.OSUtil.linux.sysinfo
     libc.src.fcntl.open
+    libc.src.string.memory_utils.inline_memcpy
     libc.src.unistd.close
     libc.src.unistd.unlink
     libc.src.unistd.write
diff --git a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
index 96350dd9d7c18..73d42a8e6ed87 100644
--- a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
+++ b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
@@ -9,6 +9,7 @@
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/OSUtil/linux/sysinfo.h"
 #include "src/fcntl/open.h"
+#include "src/string/memory_utils/inline_memcpy.h"
 #include "src/unistd/close.h"
 #include "src/unistd/unlink.h"
 #include "src/unistd/write.h"
@@ -17,8 +18,10 @@
 namespace LIBC_NAMESPACE_DECL {
 
 static int write_test_file(cpp::string_view path, cpp::string_view contents) {
-  int fd =
-      LIBC_NAMESPACE::open(path.data(), O_WRONLY | O_CREAT | O_TRUNC, 0600);
+  __extension__ char buf[path.size() + 1];
+  inline_memcpy(buf, path.data(), path.size());
+  buf[path.size()] = '\0';
+  int fd = LIBC_NAMESPACE::open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0600);
   if (fd < 0)
     return fd;
 

>From 603b51c754d5297d92f2b6a227809b1e4a8557c8 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Sun, 26 Apr 2026 10:15:24 -0400
Subject: [PATCH 09/13] fix

---
 .../src/__support/OSUtil/linux/CMakeLists.txt |  2 --
 libc/src/__support/OSUtil/linux/sysinfo.h     | 21 ++++++++----------
 .../src/__support/OSUtil/linux/CMakeLists.txt |  2 +-
 .../__support/OSUtil/linux/sysinfo_test.cpp   | 22 +++++++------------
 4 files changed, 18 insertions(+), 29 deletions(-)

diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt
index 1790a84cdebba..355d052765514 100644
--- a/libc/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt
@@ -47,13 +47,11 @@ add_header_library(
     libc.src.__support.CPP.array
     libc.src.__support.CPP.bit
     libc.src.__support.CPP.optional
-    libc.src.__support.CPP.string_view
     libc.src.__support.OSUtil.linux.syscall_wrappers.close
     libc.src.__support.OSUtil.linux.syscall_wrappers.open
     libc.src.__support.OSUtil.linux.syscall_wrappers.read
     libc.src.__support.OSUtil.linux.syscall_wrappers.sched_getaffinity
     libc.src.__support.ctype_utils
-    libc.src.string.memory_utils.inline_memcpy
 )
 
 add_header_library(
diff --git a/libc/src/__support/OSUtil/linux/sysinfo.h b/libc/src/__support/OSUtil/linux/sysinfo.h
index ecf1240f7f507..a20707f8945c7 100644
--- a/libc/src/__support/OSUtil/linux/sysinfo.h
+++ b/libc/src/__support/OSUtil/linux/sysinfo.h
@@ -13,21 +13,19 @@
 #include "src/__support/CPP/array.h"
 #include "src/__support/CPP/bit.h"
 #include "src/__support/CPP/optional.h"
-#include "src/__support/CPP/string_view.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/read.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h"
 #include "src/__support/ctype_utils.h"
 #include "src/__support/macros/config.h"
-#include "src/string/memory_utils/inline_memcpy.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace sysinfo {
 
-LIBC_INLINE_VAR constexpr cpp::string_view POSSIBLE_NPROC_PATH =
+LIBC_INLINE_VAR constexpr char POSSIBLE_NPROC_PATH[] =
     "/sys/devices/system/cpu/possible";
-LIBC_INLINE_VAR constexpr cpp::string_view ONLINE_NPROC_PATH =
+LIBC_INLINE_VAR constexpr char ONLINE_NPROC_PATH[] =
     "/sys/devices/system/cpu/online";
 
 // Parses Linux CPU-list syntax:
@@ -52,12 +50,9 @@ class ProcParser {
   size_t range_start;
   bool has_error;
 
-  LIBC_INLINE static int open_path(cpp::string_view path) {
-    __extension__ char buf[path.size() + 1];
-    inline_memcpy(buf, path.data(), path.size());
-    buf[path.size()] = '\0';
+  LIBC_INLINE static int open_path(const char *path) {
     ErrorOr<int> open_result =
-        linux_syscalls::open(buf, O_RDONLY | O_CLOEXEC, 0);
+        linux_syscalls::open(path, O_RDONLY | O_CLOEXEC, 0);
     return open_result ? *open_result : -1;
   }
 
@@ -134,7 +129,9 @@ class ProcParser {
   }
 
 public:
-  LIBC_INLINE explicit ProcParser(cpp::string_view path)
+  // Using string view isn't exactly correct because we demands null-terminated
+  // strings.
+  LIBC_INLINE explicit ProcParser(const char *path)
       : state(ProcParserState::ParseUnstarted), buffer{}, fd(open_path(path)),
         cursor(buffer.size()), buffer_end(buffer.size()), cpu_count(0),
         current_number(0), range_start(0), has_error(fd < 0) {}
@@ -164,11 +161,11 @@ class ProcParser {
   }
 };
 
-LIBC_INLINE cpp::optional<size_t> parse_nproc_from(cpp::string_view path) {
+LIBC_INLINE cpp::optional<size_t> parse_nproc_from(const char *path) {
   return ProcParser(path).parse();
 }
 
-LIBC_INLINE size_t parse_nproc_with_fallback_from(cpp::string_view path) {
+LIBC_INLINE size_t parse_nproc_with_fallback_from(const char *path) {
   if (cpp::optional<size_t> cpu_count = parse_nproc_from(path))
     return *cpu_count;
 
diff --git a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
index 941646d5a9d4d..8e6958941f289 100644
--- a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
@@ -7,9 +7,9 @@ add_libc_test(
   SUITE libc-osutil-tests
   SRCS sysinfo_test.cpp
   DEPENDS
+    libc.src.__support.CPP.string_view
     libc.src.__support.OSUtil.linux.sysinfo
     libc.src.fcntl.open
-    libc.src.string.memory_utils.inline_memcpy
     libc.src.unistd.close
     libc.src.unistd.unlink
     libc.src.unistd.write
diff --git a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
index 73d42a8e6ed87..800f062343c2c 100644
--- a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
+++ b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
@@ -9,7 +9,6 @@
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/OSUtil/linux/sysinfo.h"
 #include "src/fcntl/open.h"
-#include "src/string/memory_utils/inline_memcpy.h"
 #include "src/unistd/close.h"
 #include "src/unistd/unlink.h"
 #include "src/unistd/write.h"
@@ -17,11 +16,8 @@
 
 namespace LIBC_NAMESPACE_DECL {
 
-static int write_test_file(cpp::string_view path, cpp::string_view contents) {
-  __extension__ char buf[path.size() + 1];
-  inline_memcpy(buf, path.data(), path.size());
-  buf[path.size()] = '\0';
-  int fd = LIBC_NAMESPACE::open(buf, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+static int write_test_file(const char *path, cpp::string_view contents) {
+  int fd = LIBC_NAMESPACE::open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
   if (fd < 0)
     return fd;
 
@@ -54,10 +50,9 @@ TEST(LlvmLibcOSUtilSysinfoTest, OnlineCpuCountSmokeTest) {
 }
 
 TEST(LlvmLibcOSUtilSysinfoTest, SyntheticCpuLists) {
-  constexpr const char *FILENAME =
+  constexpr char FILENAME[] =
       APPEND_LIBC_TEST("sysinfo.synthetic_cpu_list.test");
   CString test_file = libc_make_test_file_path(FILENAME);
-  cpp::string_view test_file_path = static_cast<const char *>(test_file);
 
   struct TestCase {
     cpp::string_view contents;
@@ -76,20 +71,19 @@ TEST(LlvmLibcOSUtilSysinfoTest, SyntheticCpuLists) {
   };
 
   for (const TestCase &test_case : TEST_CASES) {
-    ASSERT_EQ(write_test_file(test_file_path, test_case.contents), 0);
-    cpp::optional<size_t> parsed = sysinfo::parse_nproc_from(test_file_path);
+    ASSERT_EQ(write_test_file(test_file, test_case.contents), 0);
+    cpp::optional<size_t> parsed = sysinfo::parse_nproc_from(test_file);
     EXPECT_EQ(static_cast<bool>(parsed), static_cast<bool>(test_case.expected));
     if (parsed)
       EXPECT_EQ(*parsed, *test_case.expected);
-    EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(test_file_path),
-              size_t(0));
+    EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(test_file), size_t(0));
   }
 
-  ASSERT_EQ(LIBC_NAMESPACE::unlink(test_file_path.data()), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::unlink(test_file), 0);
 }
 
 TEST(LlvmLibcOSUtilSysinfoTest, NonexistentPath) {
-  constexpr cpp::string_view TEST_PATH =
+  constexpr const char *TEST_PATH =
       "/not-exist-at-all-path-for-libc-nproc-test";
 
   EXPECT_FALSE(static_cast<bool>(sysinfo::parse_nproc_from(TEST_PATH)));

>From 10db62ba47ea1de45c56297909cb38031e7dfc7c Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yifanzhu at rochester.edu>
Date: Mon, 27 Apr 2026 11:28:52 -0400
Subject: [PATCH 10/13] cleanup

---
 libc/include/unistd.yaml                        | 2 +-
 libc/src/__support/OSUtil/linux/sysinfo.h       | 4 ++--
 libc/src/unistd/linux/CMakeLists.txt            | 7 ++++---
 libc/src/unistd/linux/sysconf.cpp               | 1 +
 libc/test/src/sys/auxv/linux/CMakeLists.txt     | 4 ++--
 libc/test/src/sys/auxv/linux/getauxval_test.cpp | 8 +++++---
 6 files changed, 15 insertions(+), 11 deletions(-)

diff --git a/libc/include/unistd.yaml b/libc/include/unistd.yaml
index 4c6856ccca4bd..b3c19fe638d6d 100644
--- a/libc/include/unistd.yaml
+++ b/libc/include/unistd.yaml
@@ -372,7 +372,7 @@ functions:
   - name: sysconf
     standards:
       - POSIX
-    return_type: int
+    return_type: long
     arguments:
       - type: int
   - name: truncate
diff --git a/libc/src/__support/OSUtil/linux/sysinfo.h b/libc/src/__support/OSUtil/linux/sysinfo.h
index a20707f8945c7..2b041fd817117 100644
--- a/libc/src/__support/OSUtil/linux/sysinfo.h
+++ b/libc/src/__support/OSUtil/linux/sysinfo.h
@@ -177,8 +177,8 @@ LIBC_INLINE size_t parse_nproc_with_fallback_from(const char *path) {
     return 1;
 
   size_t cpu_count = 0;
-  for (size_t i = 0; i < mask_buffer.size(); ++i)
-    cpu_count += static_cast<size_t>(cpp::popcount(mask_buffer[i]));
+  for (unsigned char byte : mask_buffer)
+    cpu_count += static_cast<size_t>(cpp::popcount(byte));
 
   return cpu_count > 0 ? cpu_count : 1;
 }
diff --git a/libc/src/unistd/linux/CMakeLists.txt b/libc/src/unistd/linux/CMakeLists.txt
index 56a43d31d808b..f979ba0669872 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -612,10 +612,11 @@ add_entrypoint_object(
   HDRS
     ../sysconf.h
   DEPENDS
-    libc.include.unistd
-    libc.include.sys_auxv
-    libc.src.__support.OSUtil.linux.sysinfo
+    libc.hdr.unistd_macros
+    libc.hdr.sys_auxv_macros
     libc.src.__support.libc_errno
+    libc.src.__support.macros.config
+    libc.src.__support.OSUtil.linux.sysinfo
     libc.src.__support.OSUtil.linux.auxv
 )
 
diff --git a/libc/src/unistd/linux/sysconf.cpp b/libc/src/unistd/linux/sysconf.cpp
index 3243b7afa5596..0578d3a6c319c 100644
--- a/libc/src/unistd/linux/sysconf.cpp
+++ b/libc/src/unistd/linux/sysconf.cpp
@@ -10,6 +10,7 @@
 
 #include "src/__support/common.h"
 
+#include "hdr/sys_auxv_macros.h"
 #include "hdr/unistd_macros.h"
 #include "src/__support/OSUtil/linux/auxv.h"
 #include "src/__support/OSUtil/linux/sysinfo.h"
diff --git a/libc/test/src/sys/auxv/linux/CMakeLists.txt b/libc/test/src/sys/auxv/linux/CMakeLists.txt
index 66370118bf6d6..3eb1c092b2070 100644
--- a/libc/test/src/sys/auxv/linux/CMakeLists.txt
+++ b/libc/test/src/sys/auxv/linux/CMakeLists.txt
@@ -6,10 +6,10 @@ add_libc_unittest(
   SRCS
     getauxval_test.cpp
   DEPENDS
-    libc.include.sys_auxv
+    libc.hdr.sys_auxv_macros
     libc.src.errno.errno
+    libc.src.string.memory_utils.inline_strstr
     libc.src.sys.auxv.getauxval
     libc.test.UnitTest.ErrnoCheckingTest
     libc.test.UnitTest.ErrnoSetterMatcher
-    libc.src.string.strstr
 )
diff --git a/libc/test/src/sys/auxv/linux/getauxval_test.cpp b/libc/test/src/sys/auxv/linux/getauxval_test.cpp
index b8728b7ad775c..f266717aa7c9c 100644
--- a/libc/test/src/sys/auxv/linux/getauxval_test.cpp
+++ b/libc/test/src/sys/auxv/linux/getauxval_test.cpp
@@ -6,12 +6,12 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "hdr/sys_auxv_macros.h"
+#include "src/string/memory_utils/inline_strstr.h"
 #include "src/sys/auxv/getauxval.h"
 #include "test/UnitTest/ErrnoCheckingTest.h"
 #include "test/UnitTest/ErrnoSetterMatcher.h"
 #include "test/UnitTest/Test.h"
-#include <src/string/strstr.h>
-#include <sys/auxv.h>
 
 using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
 using LlvmLibcGetauxvalTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
@@ -26,5 +26,7 @@ TEST_F(LlvmLibcGetauxvalTest, Basic) {
     return value;
   };
   EXPECT_THAT(getfilename(), returns(NE(0ul)).with_errno(EQ(0)));
-  ASSERT_TRUE(LIBC_NAMESPACE::strstr(filename, "getauxval_test") != nullptr);
+  ASSERT_TRUE(LIBC_NAMESPACE::inline_strstr(
+                  filename, "getauxval_test",
+                  [](char a, char b) { return a - b; }) != nullptr);
 }

>From 45f2b7237055f830e8bc36ef3413ee7ae9283c47 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yifanzhu at rochester.edu>
Date: Mon, 27 Apr 2026 11:33:40 -0400
Subject: [PATCH 11/13] add comment

---
 libc/src/__support/OSUtil/linux/sysinfo.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/libc/src/__support/OSUtil/linux/sysinfo.h b/libc/src/__support/OSUtil/linux/sysinfo.h
index 2b041fd817117..f793c7522fd1e 100644
--- a/libc/src/__support/OSUtil/linux/sysinfo.h
+++ b/libc/src/__support/OSUtil/linux/sysinfo.h
@@ -102,6 +102,8 @@ class ProcParser {
 
   LIBC_INLINE bool consume(char ch) {
     if (internal::isdigit(ch)) {
+      // Not using internal::strtointeger here because a number can be across
+      // two reads in rare cases.
       current_number = current_number * 10 + static_cast<size_t>(ch - '0');
       if (state == ProcParserState::ParseUnstarted)
         state = ProcParserState::ParseNumber;

>From d023652daa23f7077a3a6c98318f68e914351652 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <yifanzhu at rochester.edu>
Date: Tue, 28 Apr 2026 11:53:54 -0400
Subject: [PATCH 12/13] fix

---
 .../src/__support/OSUtil/linux/CMakeLists.txt |  3 ++
 libc/src/__support/OSUtil/linux/sysinfo.h     | 29 +++++++++++++------
 .../__support/OSUtil/linux/sysinfo_test.cpp   |  9 +++---
 3 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/libc/src/__support/OSUtil/linux/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt
index 355d052765514..e872bb5e14189 100644
--- a/libc/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/CMakeLists.txt
@@ -47,11 +47,14 @@ add_header_library(
     libc.src.__support.CPP.array
     libc.src.__support.CPP.bit
     libc.src.__support.CPP.optional
+    libc.src.__support.CPP.string_view
+    libc.src.__support.libc_assert
     libc.src.__support.OSUtil.linux.syscall_wrappers.close
     libc.src.__support.OSUtil.linux.syscall_wrappers.open
     libc.src.__support.OSUtil.linux.syscall_wrappers.read
     libc.src.__support.OSUtil.linux.syscall_wrappers.sched_getaffinity
     libc.src.__support.ctype_utils
+    libc.src.string.memory_utils.inline_memcpy
 )
 
 add_header_library(
diff --git a/libc/src/__support/OSUtil/linux/sysinfo.h b/libc/src/__support/OSUtil/linux/sysinfo.h
index f793c7522fd1e..715ac3bc3d484 100644
--- a/libc/src/__support/OSUtil/linux/sysinfo.h
+++ b/libc/src/__support/OSUtil/linux/sysinfo.h
@@ -13,19 +13,22 @@
 #include "src/__support/CPP/array.h"
 #include "src/__support/CPP/bit.h"
 #include "src/__support/CPP/optional.h"
+#include "src/__support/CPP/string_view.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/read.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h"
 #include "src/__support/ctype_utils.h"
+#include "src/__support/libc_assert.h"
 #include "src/__support/macros/config.h"
+#include "src/string/memory_utils/inline_memcpy.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace sysinfo {
 
-LIBC_INLINE_VAR constexpr char POSSIBLE_NPROC_PATH[] =
+LIBC_INLINE_VAR constexpr cpp::string_view POSSIBLE_NPROC_PATH =
     "/sys/devices/system/cpu/possible";
-LIBC_INLINE_VAR constexpr char ONLINE_NPROC_PATH[] =
+LIBC_INLINE_VAR constexpr cpp::string_view ONLINE_NPROC_PATH =
     "/sys/devices/system/cpu/online";
 
 // Parses Linux CPU-list syntax:
@@ -50,9 +53,19 @@ class ProcParser {
   size_t range_start;
   bool has_error;
 
-  LIBC_INLINE static int open_path(const char *path) {
+  LIBC_INLINE static int open_path(cpp::string_view path) {
+    // Zero initialized
+    cpp::array<char, 64> buffer{};
+#ifdef NDEBUG
+    if (LIBC_UNLIKELY(path.size() <= buffer.size()))
+      __builtin_trap();
+#else
+    LIBC_ASSERT(path.size() < buffer.size() &&
+                "we don't support long paths in process parser");
+#endif
+    inline_memcpy(buffer.data(), path.data(), path.size());
     ErrorOr<int> open_result =
-        linux_syscalls::open(path, O_RDONLY | O_CLOEXEC, 0);
+        linux_syscalls::open(buffer.data(), O_RDONLY | O_CLOEXEC, 0);
     return open_result ? *open_result : -1;
   }
 
@@ -131,9 +144,7 @@ class ProcParser {
   }
 
 public:
-  // Using string view isn't exactly correct because we demands null-terminated
-  // strings.
-  LIBC_INLINE explicit ProcParser(const char *path)
+  LIBC_INLINE explicit ProcParser(cpp::string_view path)
       : state(ProcParserState::ParseUnstarted), buffer{}, fd(open_path(path)),
         cursor(buffer.size()), buffer_end(buffer.size()), cpu_count(0),
         current_number(0), range_start(0), has_error(fd < 0) {}
@@ -163,11 +174,11 @@ class ProcParser {
   }
 };
 
-LIBC_INLINE cpp::optional<size_t> parse_nproc_from(const char *path) {
+LIBC_INLINE cpp::optional<size_t> parse_nproc_from(cpp::string_view path) {
   return ProcParser(path).parse();
 }
 
-LIBC_INLINE size_t parse_nproc_with_fallback_from(const char *path) {
+LIBC_INLINE size_t parse_nproc_with_fallback_from(cpp::string_view path) {
   if (cpp::optional<size_t> cpu_count = parse_nproc_from(path))
     return *cpu_count;
 
diff --git a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
index 800f062343c2c..e891866cad021 100644
--- a/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
+++ b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
@@ -16,7 +16,7 @@
 
 namespace LIBC_NAMESPACE_DECL {
 
-static int write_test_file(const char *path, cpp::string_view contents) {
+static int write_test_file(const CString &path, cpp::string_view contents) {
   int fd = LIBC_NAMESPACE::open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);
   if (fd < 0)
     return fd;
@@ -53,7 +53,7 @@ TEST(LlvmLibcOSUtilSysinfoTest, SyntheticCpuLists) {
   constexpr char FILENAME[] =
       APPEND_LIBC_TEST("sysinfo.synthetic_cpu_list.test");
   CString test_file = libc_make_test_file_path(FILENAME);
-
+  cpp::string_view test_file_view{static_cast<const char *>(test_file)};
   struct TestCase {
     cpp::string_view contents;
     cpp::optional<size_t> expected;
@@ -72,11 +72,12 @@ TEST(LlvmLibcOSUtilSysinfoTest, SyntheticCpuLists) {
 
   for (const TestCase &test_case : TEST_CASES) {
     ASSERT_EQ(write_test_file(test_file, test_case.contents), 0);
-    cpp::optional<size_t> parsed = sysinfo::parse_nproc_from(test_file);
+    cpp::optional<size_t> parsed = sysinfo::parse_nproc_from(test_file_view);
     EXPECT_EQ(static_cast<bool>(parsed), static_cast<bool>(test_case.expected));
     if (parsed)
       EXPECT_EQ(*parsed, *test_case.expected);
-    EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(test_file), size_t(0));
+    EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(test_file_view),
+              size_t(0));
   }
 
   ASSERT_EQ(LIBC_NAMESPACE::unlink(test_file), 0);

>From e38d58beb44269add0df209e86a776ff1e0bdc49 Mon Sep 17 00:00:00 2001
From: Schrodinger ZHU Yifan <i at zhuyi.fan>
Date: Tue, 28 Apr 2026 22:22:13 -0400
Subject: [PATCH 13/13] fix

---
 libc/src/__support/OSUtil/linux/sysinfo.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libc/src/__support/OSUtil/linux/sysinfo.h b/libc/src/__support/OSUtil/linux/sysinfo.h
index 715ac3bc3d484..159dfa01f2bc3 100644
--- a/libc/src/__support/OSUtil/linux/sysinfo.h
+++ b/libc/src/__support/OSUtil/linux/sysinfo.h
@@ -57,7 +57,7 @@ class ProcParser {
     // Zero initialized
     cpp::array<char, 64> buffer{};
 #ifdef NDEBUG
-    if (LIBC_UNLIKELY(path.size() <= buffer.size()))
+    if (LIBC_UNLIKELY(path.size() >= buffer.size()))
       __builtin_trap();
 #else
     LIBC_ASSERT(path.size() < buffer.size() &&



More information about the libc-commits mailing list