[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
This commit is contained in:
Schrodinger ZHU Yifan
2026-04-30 10:06:37 -04:00
committed by GitHub
parent 6f1e6e47bd
commit bcc9a55bdb
11 changed files with 403 additions and 10 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}