[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