[libc-commits] [libc] bcc9a55 - [libc] add proc number parser and sysconf wrapper (#194159)

via libc-commits libc-commits at lists.llvm.org
Thu Apr 30 07:06:43 PDT 2026


Author: Schrodinger ZHU Yifan
Date: 2026-04-30T14:06:37Z
New Revision: bcc9a55bdb228661d98444f0d6c74b47ed0426bb

URL: https://github.com/llvm/llvm-project/commit/bcc9a55bdb228661d98444f0d6c74b47ed0426bb
DIFF: https://github.com/llvm/llvm-project/commit/bcc9a55bdb228661d98444f0d6c74b47ed0426bb.diff

LOG: [libc] add proc number parser and sysconf wrapper (#194159)

Add the functionality to detect number of processors with best effort.
Needed by STL to detect parallelism.

Assisted-by: Codex with gpt-5.4 high fast

Added: 
    libc/src/__support/OSUtil/linux/syscall_wrappers/sched_getaffinity.h
    libc/src/__support/OSUtil/linux/sysinfo.h
    libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp

Modified: 
    libc/include/llvm-libc-macros/linux/unistd-macros.h
    libc/include/unistd.yaml
    libc/src/__support/OSUtil/linux/CMakeLists.txt
    libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
    libc/src/unistd/linux/CMakeLists.txt
    libc/src/unistd/linux/sysconf.cpp
    libc/test/src/__support/OSUtil/linux/CMakeLists.txt
    libc/test/src/unistd/sysconf_test.cpp

Removed: 
    


################################################################################
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..b3c19fe638d6d 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
@@ -364,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/CMakeLists.txt b/libc/src/__support/OSUtil/linux/CMakeLists.txt
index b02fa141fa456..dc1865967a348 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.ctype_utils
+    libc.src.__support.macros.config
+    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
+)
+
 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..f793c7522fd1e
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/sysinfo.h
@@ -0,0 +1,191 @@
+//===------------- 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/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 char POSSIBLE_NPROC_PATH[] =
+    "/sys/devices/system/cpu/possible";
+LIBC_INLINE_VAR constexpr char 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(const char *path) {
+    ErrorOr<int> open_result =
+        linux_syscalls::open(path, 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)) {
+      // 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;
+      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:
+  // 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) {}
+
+  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(const char *path) {
+  return ProcParser(path).parse();
+}
+
+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;
+
+  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 (unsigned char byte : mask_buffer)
+    cpu_count += static_cast<size_t>(cpp::popcount(byte));
+
+  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..f979ba0669872 100644
--- a/libc/src/unistd/linux/CMakeLists.txt
+++ b/libc/src/unistd/linux/CMakeLists.txt
@@ -612,9 +612,11 @@ add_entrypoint_object(
   HDRS
     ../sysconf.h
   DEPENDS
-    libc.include.unistd
-    libc.include.sys_auxv
+    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 979e178cb45ee..0578d3a6c319c 100644
--- a/libc/src/unistd/linux/sysconf.cpp
+++ b/libc/src/unistd/linux/sysconf.cpp
@@ -10,28 +10,35 @@
 
 #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"
 #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..8e6958941f289 100644
--- a/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
+++ b/libc/test/src/__support/OSUtil/linux/CMakeLists.txt
@@ -2,6 +2,19 @@ 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.CPP.string_view
+    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..800f062343c2c
--- /dev/null
+++ b/libc/test/src/__support/OSUtil/linux/sysinfo_test.cpp
@@ -0,0 +1,93 @@
+//===-- 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"
+
+namespace LIBC_NAMESPACE_DECL {
+
+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;
+
+  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, PossibleCpuCountSmokeTest) {
+  cpp::optional<size_t> parsed =
+      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));
+}
+
+TEST(LlvmLibcOSUtilSysinfoTest, OnlineCpuCountSmokeTest) {
+  cpp::optional<size_t> parsed =
+      sysinfo::parse_nproc_from(sysinfo::ONLINE_NPROC_PATH);
+  ASSERT_TRUE(static_cast<bool>(parsed));
+  EXPECT_GT(*parsed, size_t(0));
+  EXPECT_GT(sysinfo::parse_nproc_with_fallback_from(sysinfo::ONLINE_NPROC_PATH),
+            size_t(0));
+}
+
+TEST(LlvmLibcOSUtilSysinfoTest, SyntheticCpuLists) {
+  constexpr char FILENAME[] =
+      APPEND_LIBC_TEST("sysinfo.synthetic_cpu_list.test");
+  CString test_file = libc_make_test_file_path(FILENAME);
+
+  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, 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), size_t(0));
+  }
+
+  ASSERT_EQ(LIBC_NAMESPACE::unlink(test_file), 0);
+}
+
+TEST(LlvmLibcOSUtilSysinfoTest, NonexistentPath) {
+  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)));
+  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 85bc1258a4863..e780e2c515585 100644
--- a/libc/test/src/unistd/sysconf_test.cpp
+++ b/libc/test/src/unistd/sysconf_test.cpp
@@ -15,3 +15,13 @@ TEST(LlvmLibcSysconfTest, PagesizeTest) {
   long pagesize = LIBC_NAMESPACE::sysconf(_SC_PAGESIZE);
   ASSERT_GT(pagesize, 0l);
 }
+
+TEST(LlvmLibcSysconfTest, NprocessorsConfTest) {
+  long sysconf_count = LIBC_NAMESPACE::sysconf(_SC_NPROCESSORS_CONF);
+  ASSERT_GT(sysconf_count, 0l);
+}
+
+TEST(LlvmLibcSysconfTest, NprocessorsOnlnTest) {
+  long sysconf_count = LIBC_NAMESPACE::sysconf(_SC_NPROCESSORS_ONLN);
+  ASSERT_GT(sysconf_count, 0l);
+}


        


More information about the libc-commits mailing list