[libc-commits] [libc] Clone (PR #225347)
Pavel Labath via libc-commits
libc-commits at lists.llvm.org
Tue Sep 22 02:50:48 PDT 2026
https://github.com/labath created https://github.com/llvm/llvm-project/pull/225347
None
>From 7f39ec5a57f5c65eeb2723a578d9fe9d86397811 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Mon, 21 Sep 2026 17:05:34 +0200
Subject: [PATCH 1/2] Reland "[libc] Implement clone(2) and use it in thread
spawning (#224257)"
This commit reverts 35006688eb78eb5b601e282d21701f430d737888/#225111,
reapplying 9028ff14b18ea678683a4960306051044160bb67/#224257. It fixes an
error in risc-v assembly (forgot to adjust for constant renaming).
The original commit message was:
This patch implements an internal clone syscall wrapper and uses it both
to implement the public clone(2) entry point and to spawn new threads in
libc's thread implementation.
Previously, thread creation in thread.cpp invoked the raw SYS_clone
syscall directly, requiring target-specific inline assembly or register
variables and subtle tricks with __builtin_frame_address to pass
arguments to start_thread in the newly spawned thread.
By introducing an inline assembly clone wrapper that sets up func and
arg on the child stack and jumps to the entry function upon clone
returning in the child, we can simplify start_thread to a normal
function taking a single void * argument and eliminate the frame pointer
hacks as well as the need to compile thread.cpp with
-fno-omit-frame-pointer or optimizations.
The public clone(2) entrypoint delegates to this wrapper after unpacking
its varargs and validating pointers (returning EINVAL like glibc).
A particularly tricky aspect of this patch is the invalidation of the
cached thread IDs. As with fork(), we do this in the parent, but we
cannot do this safely for clone() in all situations. The problematic
case is where the user does not set a custom TLS block (which means the
child uses the parent's block), does not clone the address space (no
copy-on-write), and does not suspend the parent (vfork semantics). In
this case, we just give up and don't touch the thread ID. For this
particular flag, most of the operations in the child are not safe, so
we're assuming the user is prepared for such a restricted environment.
Support is implemented for x86_64, aarch64, and riscv. The
architecture-specific assembly is split out into detail::clone_impl
inside per-architecture headers.
Assisted-by: Gemini
---
libc/config/linux/aarch64/entrypoints.txt | 1 +
libc/config/linux/riscv/entrypoints.txt | 1 +
libc/config/linux/x86_64/entrypoints.txt | 1 +
libc/include/sched.yaml | 11 +
.../linux/syscall_wrappers/CMakeLists.txt | 22 ++
.../linux/syscall_wrappers/aarch64/clone.h | 60 ++++++
.../OSUtil/linux/syscall_wrappers/clone.h | 72 +++++++
.../linux/syscall_wrappers/riscv/clone.h | 68 ++++++
.../linux/syscall_wrappers/x86_64/clone.h | 60 ++++++
.../__support/threads/linux/CMakeLists.txt | 5 +-
libc/src/__support/threads/linux/thread.cpp | 111 ++--------
libc/src/pthread/CMakeLists.txt | 3 -
libc/src/sched/CMakeLists.txt | 7 +
libc/src/sched/clone.h | 42 ++++
libc/src/sched/linux/CMakeLists.txt | 18 ++
libc/src/sched/linux/clone.cpp | 88 ++++++++
libc/src/threads/CMakeLists.txt | 4 -
libc/test/src/sched/CMakeLists.txt | 4 +
libc/test/src/sched/linux/CMakeLists.txt | 22 ++
libc/test/src/sched/linux/clone_test.cpp | 194 ++++++++++++++++++
20 files changed, 692 insertions(+), 102 deletions(-)
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/aarch64/clone.h
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/clone.h
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/riscv/clone.h
create mode 100644 libc/src/__support/OSUtil/linux/syscall_wrappers/x86_64/clone.h
create mode 100644 libc/src/sched/clone.h
create mode 100644 libc/src/sched/linux/clone.cpp
create mode 100644 libc/test/src/sched/linux/CMakeLists.txt
create mode 100644 libc/test/src/sched/linux/clone_test.cpp
diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index c6e8dc4e69fb4..683d4223998bc 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1219,6 +1219,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.pthread.pthread_setspecific
# sched.h entrypoints
+ libc.src.sched.clone
libc.src.sched.__sched_andcpuset
libc.src.sched.__sched_clrcpuset
libc.src.sched.__sched_cpualloc
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index c56fbf7974ade..3b92c3ad77233 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1410,6 +1410,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.pthread.pthread_setspecific
# sched.h entrypoints
+ libc.src.sched.clone
libc.src.sched.__sched_andcpuset
libc.src.sched.__sched_clrcpuset
libc.src.sched.__sched_cpualloc
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index b7c05bc6bc6c5..96c9e0c8d9264 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1426,6 +1426,7 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.pthread.pthread_setspecific
# sched.h entrypoints
+ libc.src.sched.clone
libc.src.sched.__sched_andcpuset
libc.src.sched.__sched_clrcpuset
libc.src.sched.__sched_cpualloc
diff --git a/libc/include/sched.yaml b/libc/include/sched.yaml
index 812f528bcaaf6..6997753144a8f 100644
--- a/libc/include/sched.yaml
+++ b/libc/include/sched.yaml
@@ -1,6 +1,7 @@
header: sched.h
standards:
- posix
+ - linux
macros:
- macro_name: SCHED_OTHER
macro_header: sched-macros.h
@@ -18,6 +19,16 @@ types:
enums: []
objects: []
functions:
+ - name: clone
+ standards:
+ - linux
+ return_type: int
+ arguments:
+ - type: int (*)(void *)
+ - type: void *
+ - type: int
+ - type: void *
+ - type: '...'
- name: __sched_andcpuset
standards:
- llvm_libc_ext
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
index f5f28f51f49f6..d1fbfa37715a9 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -1150,6 +1150,28 @@ add_header_library(
libc.include.sys_syscall
)
+set(clone_arch_header "")
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_ARCHITECTURE}/clone.h)
+ set(clone_arch_header ${LIBC_TARGET_ARCHITECTURE}/clone.h)
+endif()
+
+add_header_library(
+ clone
+ HDRS
+ clone.h
+ ${clone_arch_header}
+ DEPENDS
+ libc.hdr.stdint_proxy
+ libc.hdr.types.pid_t
+ libc.include.sys_syscall
+ libc.src.__support.common
+ libc.src.__support.error_or
+ libc.src.__support.libc_assert
+ libc.src.__support.macros.attributes
+ libc.src.__support.macros.config
+ libc.src.__support.macros.properties.architectures
+)
+
add_header_library(
clone3
HDRS
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/aarch64/clone.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/aarch64/clone.h
new file mode 100644
index 0000000000000..527cd4d4cf8d7
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/aarch64/clone.h
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of aarch64 clone.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_AARCH64_CLONE_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_AARCH64_CLONE_H
+
+#include "hdr/types/pid_t.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For SYS_clone, SYS_exit
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+constexpr uintptr_t CLONE_STACK_ALIGNMENT = 16;
+
+namespace detail {
+
+LIBC_INLINE long clone_impl(int flags, void *child_stack, pid_t *parent_tid,
+ void *tls, pid_t *child_tid) {
+ register long x8 asm("x8") = SYS_clone;
+ register long x0 asm("x0") = flags;
+ register void *x1 asm("x1") = child_stack;
+ register pid_t *x2 asm("x2") = parent_tid;
+ register void *x3 asm("x3") = tls;
+ register pid_t *x4 asm("x4") = child_tid;
+
+ LIBC_INLINE_ASM("svc #0\n\t"
+ "cbnz x0, 1f\n\t"
+ // Child execution:
+ // Zero frame pointer to terminate call stack unwinding.
+ "mov x29, #0\n\t"
+ "ldp x1, x0, [sp], #16\n\t"
+ "blr x1\n\t"
+ "mov x8, %[sys_exit]\n\t"
+ // Does not return.
+ "svc #0\n\t"
+ // Just in case it does..
+ "brk #0\n\t"
+ "1:\n\t" : "+r"(x0) : "r"(x8),
+ "r"(x1), "r"(x2), "r"(x3),
+ "r"(x4), [sys_exit] "i"(SYS_exit) : "memory", "cc");
+ return x0;
+}
+
+} // namespace detail
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_AARCH64_CLONE_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/clone.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/clone.h
new file mode 100644
index 0000000000000..c2b13f77e7aaa
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/clone.h
@@ -0,0 +1,72 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for clone.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_CLONE_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_CLONE_H
+
+#include "hdr/stdint_proxy.h"
+#include "hdr/types/pid_t.h"
+#include "src/__support/common.h"
+#include "src/__support/error_or.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/properties/architectures.h"
+
+#if defined(LIBC_TARGET_ARCH_IS_X86_64)
+#include "src/__support/OSUtil/linux/syscall_wrappers/x86_64/clone.h"
+#elif defined(LIBC_TARGET_ARCH_IS_AARCH64)
+#include "src/__support/OSUtil/linux/syscall_wrappers/aarch64/clone.h"
+#elif defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
+#include "src/__support/OSUtil/linux/syscall_wrappers/riscv/clone.h"
+#else
+#error "clone is not supported for this architecture"
+#endif
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+struct ChildStackFrame {
+ int (*func)(void *);
+ void *arg;
+};
+
+LIBC_INLINE ErrorOr<pid_t> clone(int (*func)(void *), void *stack, int flags,
+ void *arg, pid_t *parent_tid = nullptr,
+ void *tls = nullptr,
+ pid_t *child_tid = nullptr) {
+ uintptr_t sp = reinterpret_cast<uintptr_t>(stack);
+ LIBC_ASSERT(sp % CLONE_STACK_ALIGNMENT == 0);
+
+ // Set up func and arg at the top of the child stack in C++ so the assembly
+ // only needs to pass the standard system call arguments.
+ static_assert(sizeof(ChildStackFrame) <= CLONE_STACK_ALIGNMENT);
+ sp -= CLONE_STACK_ALIGNMENT;
+ auto *child_stack = reinterpret_cast<ChildStackFrame *>(sp);
+ child_stack->func = func;
+ child_stack->arg = arg;
+
+ // We don't use the syscall function because the child thread will "return" to
+ // a different stack. Any attempt to access local variables or spilled values
+ // (which can be implicitly added by the compiler) will crash or cause other
+ // sorts of problems.
+ long ret = detail::clone_impl(flags, child_stack, parent_tid, tls, child_tid);
+
+ if (ret < 0)
+ return Error(static_cast<int>(-ret));
+ return static_cast<pid_t>(ret);
+}
+
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_CLONE_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/riscv/clone.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/riscv/clone.h
new file mode 100644
index 0000000000000..34bfdfc947503
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/riscv/clone.h
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of riscv clone.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_RISCV_CLONE_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_RISCV_CLONE_H
+
+#include "hdr/stdint_proxy.h"
+#include "hdr/types/pid_t.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For SYS_clone, SYS_exit
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+constexpr uintptr_t CLONE_STACK_ALIGNMENT = 16;
+
+namespace detail {
+
+LIBC_INLINE long clone_impl(int flags, void *child_stack, pid_t *parent_tid,
+ void *tls, pid_t *child_tid) {
+ register long a7 asm("a7") = SYS_clone;
+ register long a0 asm("a0") = flags;
+ register void *a1 asm("a1") = child_stack;
+ register pid_t *a2 asm("a2") = parent_tid;
+ register void *a3 asm("a3") = tls;
+ register pid_t *a4 asm("a4") = child_tid;
+
+ LIBC_INLINE_ASM("ecall\n\t"
+ "bnez a0, 1f\n\t"
+ // Child execution:
+ // Zero frame pointer to terminate call stack unwinding.
+ "mv s0, zero\n\t"
+#if __riscv_xlen == 64
+ "ld a1, 0(sp)\n\t"
+ "ld a0, 8(sp)\n\t"
+#else
+ "lw a1, 0(sp)\n\t"
+ "lw a0, 4(sp)\n\t"
+#endif
+ "addi sp, sp, %c[stack_adj]\n\t"
+ "jalr a1\n\t"
+ "li a7, %[sys_exit]\n\t"
+ // Does not return.
+ "ecall\n\t"
+ // Just in case it does..
+ "unimp\n\t"
+ "1:\n\t" : "+r"(a0) : "r"(a7),
+ "r"(a1), "r"(a2), "r"(a3), "r"(a4), [sys_exit] "i"(SYS_exit),
+ [stack_adj] "i"(CLONE_STACK_ALIGNMENT) : "memory");
+ return a0;
+}
+
+} // namespace detail
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_RISCV_CLONE_H
diff --git a/libc/src/__support/OSUtil/linux/syscall_wrappers/x86_64/clone.h b/libc/src/__support/OSUtil/linux/syscall_wrappers/x86_64/clone.h
new file mode 100644
index 0000000000000..229cc7a7e78ba
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/x86_64/clone.h
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of x86_64 clone.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_X86_64_CLONE_H
+#define LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_X86_64_CLONE_H
+
+#include "hdr/types/pid_t.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include <sys/syscall.h> // For SYS_clone, SYS_exit
+
+namespace LIBC_NAMESPACE_DECL {
+namespace linux_syscalls {
+
+constexpr uintptr_t CLONE_STACK_ALIGNMENT = 16;
+
+namespace detail {
+
+LIBC_INLINE long clone_impl(int flags, void *child_stack, pid_t *parent_tid,
+ void *tls, pid_t *child_tid) {
+ long ret;
+ register pid_t *r10 __asm__("r10") = child_tid;
+ register void *r8 __asm__("r8") = tls;
+
+ LIBC_INLINE_ASM("syscall\n\t"
+ "testq %%rax, %%rax\n\t"
+ "jnz 1f\n\t"
+ // Child execution:
+ // Zero frame pointer to terminate call stack unwinding.
+ "xorl %%ebp, %%ebp\n\t"
+ "popq %%rax\n\t"
+ "popq %%rdi\n\t"
+ "call *%%rax\n\t"
+ "movl %%eax, %%edi\n\t"
+ "movl %[sys_exit], %%eax\n\t"
+ // Does not return.
+ "syscall\n\t"
+ // Just in case it does..
+ "ud2\n\t"
+ "1:\n\t" : "=a"(ret) : "0"(SYS_clone),
+ "D"(flags), "S"(child_stack), "d"(parent_tid), "r"(r10),
+ "r"(r8), [sys_exit] "i"(SYS_exit) : "rcx", "r11", "memory");
+ return ret;
+}
+
+} // namespace detail
+} // namespace linux_syscalls
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_OSUTIL_LINUX_SYSCALL_WRAPPERS_X86_64_CLONE_H
diff --git a/libc/src/__support/threads/linux/CMakeLists.txt b/libc/src/__support/threads/linux/CMakeLists.txt
index ae449c1af799d..a2196024d0c08 100644
--- a/libc/src/__support/threads/linux/CMakeLists.txt
+++ b/libc/src/__support/threads/linux/CMakeLists.txt
@@ -59,6 +59,7 @@ add_object_library(
libc.src.__support.CPP.string_view
libc.src.__support.common
libc.src.__support.error_or
+ libc.src.__support.OSUtil.linux.syscall_wrappers.clone
libc.src.__support.OSUtil.linux.syscall_wrappers.close
libc.src.__support.OSUtil.linux.syscall_wrappers.getpid
libc.src.__support.OSUtil.linux.syscall_wrappers.mmap
@@ -75,10 +76,6 @@ add_object_library(
libc.src.__support.threads.thread_attributes
COMPILE_OPTIONS
${libc_opt_high_flag}
- -fno-omit-frame-pointer # This allows us to sniff out the thread args from
- # the new thread's stack reliably.
- -Wno-frame-address # Yes, calling __builtin_return_address with a
- # value other than 0 is dangerous. We know.
)
add_header_library(
diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp
index efbdc4a45d373..df294e2e4b4fb 100644
--- a/libc/src/__support/threads/linux/thread.cpp
+++ b/libc/src/__support/threads/linux/thread.cpp
@@ -11,6 +11,7 @@
#include "src/__support/CPP/atomic.h"
#include "src/__support/CPP/string_view.h"
#include "src/__support/CPP/stringstream.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/clone.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/getpid.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/mmap.h"
@@ -32,19 +33,15 @@
#include "src/__support/threads/linux/futex_word.h"
#include "src/__support/threads/thread_attributes.h"
-#ifdef LIBC_TARGET_ARCH_IS_AARCH64
-#include <arm_acle.h>
-#endif
-
#include "hdr/errno_macros.h"
#include "hdr/fcntl_macros.h"
#include "hdr/sched_macros.h" // For CLONE_* flags.
#include "hdr/signal_macros.h"
#include "hdr/stdint_proxy.h"
#include "hdr/sys_mman_macros.h" // For PROT_* and MAP_* definitions.
-#include <linux/param.h> // For EXEC_PAGESIZE.
-#include <linux/prctl.h> // For PR_SET_NAME
-#include <sys/syscall.h> // For syscall numbers.
+#include <linux/param.h> // For EXEC_PAGESIZE.
+#include <linux/prctl.h> // For PR_SET_NAME
+#include <sys/syscall.h> // For syscall numbers.
namespace LIBC_NAMESPACE_DECL {
@@ -62,16 +59,6 @@ static constexpr unsigned CLONE_SYSCALL_FLAGS =
// wake the joining thread.
| CLONE_SETTLS; // Setup the thread pointer of the new thread.
-#ifdef LIBC_TARGET_ARCH_IS_AARCH64
-#define CLONE_RESULT_REGISTER "x0"
-#elif defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
-#define CLONE_RESULT_REGISTER "t0"
-#elif defined(LIBC_TARGET_ARCH_IS_X86_64)
-#define CLONE_RESULT_REGISTER "rax"
-#else
-#error "CLONE_RESULT_REGISTER not defined for your target architecture"
-#endif
-
static constexpr ErrorOr<size_t> add_no_overflow(size_t lhs, size_t rhs) {
if (lhs > SIZE_MAX - rhs)
return Error{EINVAL};
@@ -158,31 +145,8 @@ cleanup_thread_resources(ThreadAttributes *attrib) {
free_stack(attrib->stack, attrib->stacksize, attrib->guardsize);
}
-[[gnu::always_inline]] LIBC_INLINE uintptr_t get_start_args_addr() {
-// NOTE: For __builtin_frame_address to work reliably across compilers,
-// architectures and various optimization levels, the TU including this file
-// should be compiled with -fno-omit-frame-pointer.
-#ifdef LIBC_TARGET_ARCH_IS_X86_64
- return reinterpret_cast<uintptr_t>(__builtin_frame_address(0))
- // The x86_64 call instruction pushes resume address on to the stack.
- // Next, The x86_64 SysV ABI requires that the frame pointer be pushed
- // on to the stack. So, we have to step past two 64-bit values to get
- // to the start args.
- + sizeof(uintptr_t) * 2;
-#elif defined(LIBC_TARGET_ARCH_IS_AARCH64)
- // The frame pointer after cloning the new thread in the Thread::run method
- // is set to the stack pointer where start args are stored. So, we fetch
- // from there.
- return reinterpret_cast<uintptr_t>(__builtin_frame_address(1));
-#elif defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
- // The current frame pointer is the previous stack pointer where the start
- // args are stored.
- return reinterpret_cast<uintptr_t>(__builtin_frame_address(0));
-#endif
-}
-
-[[gnu::noinline]] void start_thread() {
- auto *start_args = reinterpret_cast<StartArgs *>(get_start_args_addr());
+static int start_thread(void *arg) {
+ auto *start_args = reinterpret_cast<StartArgs *>(arg);
auto *attrib = start_args->thread_attrib;
if (attrib->style == ThreadStyle::POSIX) {
@@ -192,6 +156,7 @@ cleanup_thread_resources(ThreadAttributes *attrib) {
ThreadReturnValue retval = start_args->runner.stdc_runner(start_args->arg);
thread_exit(retval, ThreadStyle::STDC);
}
+ return 0;
}
int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
@@ -240,14 +205,11 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
init_tls(tls);
// When the new thread is spawned by the kernel, the new thread gets the
- // stack we pass to the clone syscall. However, this stack is empty and does
- // not have any local vars present in this function. Hence, one cannot
- // pass arguments to the thread start function, or use any local vars from
- // here. So, we pack them into the new stack from where the thread can sniff
- // them out.
- //
- // Likewise, the actual thread state information is also stored on the
- // stack memory.
+ // stack we pass to the clone syscall. Thread initialization structures
+ // (StartArgs, ThreadAttributes, and the clear_tid Futex) are allocated on
+ // the new thread's stack memory so their lifetime is managed with the stack
+ // without requiring heap allocation. A pointer to StartArgs is passed
+ // directly to start_thread via the clone syscall wrapper's argument.
static constexpr size_t INTERNAL_STACK_DATA_SIZE =
sizeof(StartArgs) + sizeof(ThreadAttributes) + sizeof(Futex);
@@ -298,48 +260,15 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
get_tcb(tls.tp)->attrib = attrib;
- // The clone syscall takes arguments in an architecture specific order.
- // Also, we want the result of the syscall to be in a register as the child
- // thread gets a completely different stack after it is created. The stack
- // variables from this function will not be availalbe to the child thread.
-#if defined(LIBC_TARGET_ARCH_IS_X86_64)
- long register clone_result asm(CLONE_RESULT_REGISTER);
- clone_result = LIBC_NAMESPACE::syscall_impl<long>(
- SYS_clone, CLONE_SYSCALL_FLAGS, adjusted_stack,
- &attrib->tid, // The address where the child tid is written
- &clear_tid->val, // The futex where the child thread status is signalled
- tls.tp // The thread pointer value for the new thread.
- );
-#elif defined(LIBC_TARGET_ARCH_IS_AARCH64) || \
- defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
- long register clone_result asm(CLONE_RESULT_REGISTER);
- clone_result = LIBC_NAMESPACE::syscall_impl<long>(
- SYS_clone, CLONE_SYSCALL_FLAGS, adjusted_stack,
- &attrib->tid, // The address where the child tid is written
- tls.tp, // The thread pointer value for the new thread.
- &clear_tid->val // The futex where the child thread status is signalled
- );
-#else
-#error "Unsupported architecture for the clone syscall."
-#endif
-
- if (clone_result == 0) {
-#ifdef LIBC_TARGET_ARCH_IS_AARCH64
- // We set the frame pointer to be the same as the "sp" so that start args
- // can be sniffed out from start_thread.
-#ifdef __clang__
- // GCC does not currently implement __arm_wsr64/__arm_rsr64.
- __arm_wsr64("x29", __arm_rsr64("sp"));
-#else
- asm volatile("mov x29, sp");
-#endif
-#elif defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
- asm volatile("mv fp, sp");
-#endif
- start_thread();
- } else if (clone_result < 0) {
+ auto clone_result = linux_syscalls::clone(
+ start_thread, reinterpret_cast<void *>(adjusted_stack),
+ CLONE_SYSCALL_FLAGS, start_args, &attrib->tid,
+ reinterpret_cast<void *>(tls.tp),
+ reinterpret_cast<pid_t *>(&clear_tid->val));
+
+ if (!clone_result.has_value()) {
cleanup_thread_resources(attrib);
- return static_cast<int>(-clone_result);
+ return clone_result.error();
}
return 0;
diff --git a/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt
index cfbcb152ecc48..1e007218dfa97 100644
--- a/libc/src/pthread/CMakeLists.txt
+++ b/libc/src/pthread/CMakeLists.txt
@@ -607,9 +607,6 @@ add_entrypoint_object(
libc.src.pthread.pthread_attr_getguardsize
libc.src.pthread.pthread_attr_getstack
libc.src.errno.errno
- COMPILE_OPTIONS
- ${libc_opt_high_flag}
- -fno-omit-frame-pointer
)
add_entrypoint_object(
diff --git a/libc/src/sched/CMakeLists.txt b/libc/src/sched/CMakeLists.txt
index 9eb37b7a3bf89..4f8ac4f4d521d 100644
--- a/libc/src/sched/CMakeLists.txt
+++ b/libc/src/sched/CMakeLists.txt
@@ -2,6 +2,13 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
endif()
+add_entrypoint_object(
+ clone
+ ALIAS
+ DEPENDS
+ .${LIBC_TARGET_OS}.clone
+)
+
add_entrypoint_object(
getcpu
ALIAS
diff --git a/libc/src/sched/clone.h b/libc/src/sched/clone.h
new file mode 100644
index 0000000000000..02750626837a1
--- /dev/null
+++ b/libc/src/sched/clone.h
@@ -0,0 +1,42 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for clone.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_SCHED_CLONE_H
+#define LLVM_LIBC_SRC_SCHED_CLONE_H
+
+#include "hdr/types/pid_t.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+/// Creates a new child process with execution starting at \p func(\p arg).
+///
+/// \param func The child process entry function. Must not be null.
+/// \param stack Pointer to the top of the memory allocated for the child stack.
+/// Must not be null.
+/// \param flags Sharing flags (CLONE_*) and exit signal for the parent.
+/// \param arg Argument passed to \p func.
+/// \param ... Positional arguments depending on \p flags, in order:
+/// - pid_t *parent_tid if CLONE_PARENT_SETTID or CLONE_PIDFD is set.
+/// - void *tls if CLONE_SETTLS is set.
+/// - pid_t *child_tid if CLONE_CHILD_SETTID or CLONE_CHILD_CLEARTID
+/// is set.
+/// Preceding arguments must be provided if a subsequent argument is
+/// needed.
+/// \return On success, returns the process ID of the child. On failure, returns
+/// -1 and sets errno.
+int clone(int (*func)(void *), void *stack, int flags, void *arg, ...);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_SCHED_CLONE_H
diff --git a/libc/src/sched/linux/CMakeLists.txt b/libc/src/sched/linux/CMakeLists.txt
index 85d298f311d38..008437954d4e9 100644
--- a/libc/src/sched/linux/CMakeLists.txt
+++ b/libc/src/sched/linux/CMakeLists.txt
@@ -1,3 +1,21 @@
+add_entrypoint_object(
+ clone
+ SRCS
+ clone.cpp
+ HDRS
+ ../clone.h
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.sched_macros
+ libc.hdr.types.pid_t
+ libc.src.__support.OSUtil.linux.syscall_wrappers.clone
+ libc.src.__support.common
+ libc.src.__support.libc_errno
+ libc.src.__support.macros.config
+ libc.src.__support.threads.identifier
+ libc.src.errno.errno
+)
+
add_entrypoint_object(
getcpu
SRCS
diff --git a/libc/src/sched/linux/clone.cpp b/libc/src/sched/linux/clone.cpp
new file mode 100644
index 0000000000000..ed6cf81c9322d
--- /dev/null
+++ b/libc/src/sched/linux/clone.cpp
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Linux implementation of clone.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/sched/clone.h"
+#include "hdr/errno_macros.h"
+#include "hdr/sched_macros.h"
+#include "hdr/types/pid_t.h"
+#include "src/__support/OSUtil/linux/syscall_wrappers/clone.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_errno.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/identifier.h"
+#include <stdarg.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(int, clone,
+ (int (*func)(void *), void *stack, int flags, void *arg,
+ ...)) {
+ if (func == nullptr || stack == nullptr) {
+ libc_errno = EINVAL;
+ return -1;
+ }
+
+ pid_t *parent_tid = nullptr;
+ void *tls = nullptr;
+ pid_t *child_tid = nullptr;
+
+ if (flags & (CLONE_PARENT_SETTID | CLONE_SETTLS | CLONE_CHILD_SETTID |
+ CLONE_CHILD_CLEARTID | CLONE_PIDFD)) {
+ va_list args;
+ va_start(args, arg);
+ parent_tid = va_arg(args, pid_t *);
+ if (flags & (CLONE_SETTLS | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID))
+ tls = va_arg(args, void *);
+ if (flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID))
+ child_tid = va_arg(args, pid_t *);
+ va_end(args);
+ }
+
+ uintptr_t stack_as_integer = reinterpret_cast<uintptr_t>(stack);
+ stack_as_integer &= ~(linux_syscalls::CLONE_STACK_ALIGNMENT - 1);
+ stack = reinterpret_cast<void *>(stack_as_integer);
+
+ // In some situations, we need to invalidate parent's tid cache. We cannot do
+ // this in the child because a signal handler may observe the wrong tid before
+ // we get a chance to set it. We need to clear the tid if:
+ // - the child is sharing the TLS block with us (!clone_tls); and
+ // - the child is NOT sharing the address space with us (!clone_vm) or the
+ // parent-child execution is serialized (clone_vfork).
+ // We cannot safely do this in the clone_vm && !clone_vfork case, as the
+ // parent and child would race to overwrite each other's values, so we leave
+ // this case unsupported.
+ bool clone_settls = flags & CLONE_SETTLS;
+ bool clone_vm = flags & CLONE_VM;
+ bool clone_vfork = flags & CLONE_VFORK;
+ bool should_clear_tid = !clone_settls && (!clone_vm || clone_vfork);
+
+ pid_t tid_of_parent;
+ if (should_clear_tid) {
+ tid_of_parent = internal::gettid();
+ internal::force_set_tid(0);
+ }
+
+ auto ret = linux_syscalls::clone(func, stack, flags, arg, parent_tid, tls,
+ child_tid);
+
+ if (should_clear_tid)
+ internal::force_set_tid(tid_of_parent);
+
+ if (!ret.has_value()) {
+ libc_errno = ret.error();
+ return -1;
+ }
+ return ret.value();
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/threads/CMakeLists.txt b/libc/src/threads/CMakeLists.txt
index d881d8aa74d3f..56247204c30f9 100644
--- a/libc/src/threads/CMakeLists.txt
+++ b/libc/src/threads/CMakeLists.txt
@@ -23,10 +23,6 @@ add_entrypoint_object(
libc.src.__support.threads.thread
libc.include.threads
libc.src.errno.errno
- COMPILE_OPTIONS
- ${libc_opt_high_flag}
- -fno-omit-frame-pointer # This allows us to sniff out the thread args from
- # the new thread's stack reliably.
)
add_entrypoint_object(
diff --git a/libc/test/src/sched/CMakeLists.txt b/libc/test/src/sched/CMakeLists.txt
index c8bf9fea96383..0e585900aa756 100644
--- a/libc/test/src/sched/CMakeLists.txt
+++ b/libc/test/src/sched/CMakeLists.txt
@@ -1,5 +1,9 @@
add_custom_target(libc_sched_unittests)
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
+ add_subdirectory(${LIBC_TARGET_OS})
+endif()
+
add_libc_test(
affinity_test
SUITE
diff --git a/libc/test/src/sched/linux/CMakeLists.txt b/libc/test/src/sched/linux/CMakeLists.txt
new file mode 100644
index 0000000000000..de9e2aaafd823
--- /dev/null
+++ b/libc/test/src/sched/linux/CMakeLists.txt
@@ -0,0 +1,22 @@
+add_libc_test(
+ clone_test
+ SUITE
+ libc_sched_unittests
+ SRCS
+ clone_test.cpp
+ DEPENDS
+ libc.hdr.errno_macros
+ libc.hdr.sched_macros
+ libc.hdr.signal_macros
+ libc.hdr.stdint_proxy
+ libc.hdr.sys_wait_macros
+ libc.hdr.types.pid_t
+ libc.hdr.types.size_t
+ libc.include.sys_syscall
+ libc.src.__support.OSUtil.osutil
+ libc.src.sched.clone
+ libc.src.sys.wait.waitpid
+ libc.src.unistd.gettid
+ libc.test.UnitTest.ErrnoCheckingTest
+ libc.test.UnitTest.ErrnoSetterMatcher
+)
diff --git a/libc/test/src/sched/linux/clone_test.cpp b/libc/test/src/sched/linux/clone_test.cpp
new file mode 100644
index 0000000000000..9201ed641e929
--- /dev/null
+++ b/libc/test/src/sched/linux/clone_test.cpp
@@ -0,0 +1,194 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Unittests for clone.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/errno_macros.h"
+#include "hdr/sched_macros.h"
+#include "hdr/signal_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "hdr/sys_wait_macros.h"
+#include "hdr/types/pid_t.h"
+#include "hdr/types/size_t.h"
+#include "src/__support/OSUtil/syscall.h"
+#include "src/sched/clone.h"
+#include "src/sys/wait/waitpid.h"
+#include "src/unistd/gettid.h"
+#include "test/UnitTest/ErrnoCheckingTest.h"
+#include "test/UnitTest/ErrnoSetterMatcher.h"
+#include "test/UnitTest/Test.h"
+
+#include <sys/syscall.h>
+
+using namespace LIBC_NAMESPACE::testing::ErrnoSetterMatcher;
+using LlvmLibcSchedCloneTest = LIBC_NAMESPACE::testing::ErrnoCheckingTest;
+
+static constexpr size_t STACK_SIZE = 64 * 1024;
+alignas(16) static uint8_t child_stack[STACK_SIZE];
+
+static void *get_child_stack_top() { return child_stack + STACK_SIZE; }
+
+static int simple_child(void *arg) {
+ auto *val = reinterpret_cast<uintptr_t *>(arg);
+ if (val)
+ *val = 42;
+ return 123;
+}
+
+struct TidArgs {
+ pid_t parent_tid = 0;
+ pid_t cached_tid = 0;
+ pid_t sys_tid = 0;
+};
+
+static int check_tid_child(void *arg) {
+ auto *args = reinterpret_cast<TidArgs *>(arg);
+ pid_t cached = LIBC_NAMESPACE::gettid();
+ pid_t sys = LIBC_NAMESPACE::syscall_impl<pid_t>(SYS_gettid);
+ if (args) {
+ args->cached_tid = cached;
+ args->sys_tid = sys;
+ if (cached == args->parent_tid)
+ return 1;
+ }
+ if (cached != sys)
+ return 2;
+ return 0;
+}
+
+TEST_F(LlvmLibcSchedCloneTest, BasicProcess) {
+ uintptr_t val = 0;
+ void *stack_top = get_child_stack_top();
+ pid_t pid = LIBC_NAMESPACE::clone(simple_child, stack_top, SIGCHLD, &val);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_GT(pid, 0);
+
+ int status = 0;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 123);
+ // Address space was copy-on-write, so parent's val is unchanged.
+ ASSERT_EQ(val, static_cast<uintptr_t>(0));
+}
+
+TEST_F(LlvmLibcSchedCloneTest, InvalidFlags) {
+ void *stack_top = get_child_stack_top();
+ // CLONE_THREAD requires CLONE_SIGHAND. Specifying CLONE_THREAD alone fails
+ // with EINVAL.
+ EXPECT_THAT(
+ LIBC_NAMESPACE::clone(simple_child, stack_top, CLONE_THREAD, nullptr),
+ Fails(EINVAL));
+}
+
+TEST_F(LlvmLibcSchedCloneTest, NullFuncOrStack) {
+ void *stack_top = get_child_stack_top();
+ EXPECT_THAT(LIBC_NAMESPACE::clone(nullptr, stack_top, SIGCHLD, nullptr),
+ Fails(EINVAL));
+ EXPECT_THAT(LIBC_NAMESPACE::clone(simple_child, nullptr, SIGCHLD, nullptr),
+ Fails(EINVAL));
+}
+
+TEST_F(LlvmLibcSchedCloneTest, ChildTidSeparateVm) {
+ TidArgs args;
+ args.parent_tid = LIBC_NAMESPACE::gettid();
+ void *stack_top = get_child_stack_top();
+ pid_t pid = LIBC_NAMESPACE::clone(check_tid_child, stack_top, SIGCHLD, &args);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_GT(pid, 0);
+
+ int status = 0;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::gettid(), args.parent_tid);
+}
+
+// Emulators struggle with exotic flag combinations (e.g., CLONE_VM or
+// CLONE_PARENT_SETTID without CLONE_THREAD).
+#ifndef LIBC_TEST_UNDER_EMULATOR
+TEST_F(LlvmLibcSchedCloneTest, BasicVmShared) {
+ uintptr_t val = 0;
+ void *stack_top = get_child_stack_top();
+ pid_t pid = LIBC_NAMESPACE::clone(simple_child, stack_top,
+ CLONE_VM | CLONE_VFORK | SIGCHLD, &val);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_GT(pid, 0);
+
+ int status = 0;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 123);
+ // With CLONE_VM, child's modification is visible in the parent.
+ ASSERT_EQ(val, static_cast<uintptr_t>(42));
+}
+
+TEST_F(LlvmLibcSchedCloneTest, ParentSetTid) {
+ void *stack_top = get_child_stack_top();
+ pid_t ptid = 0;
+ pid_t pid = LIBC_NAMESPACE::clone(
+ simple_child, stack_top, CLONE_PARENT_SETTID | SIGCHLD, nullptr, &ptid);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_GT(pid, 0);
+ ASSERT_EQ(ptid, pid);
+
+ int status = 0;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 123);
+}
+
+TEST_F(LlvmLibcSchedCloneTest, ChildSetTid) {
+ void *stack_top = get_child_stack_top();
+ pid_t ctid = 0;
+ pid_t pid = LIBC_NAMESPACE::clone(
+ simple_child, stack_top,
+ CLONE_CHILD_SETTID | CLONE_VM | CLONE_VFORK | SIGCHLD, nullptr,
+ /*parent_tid=*/nullptr, /*tls=*/nullptr, &ctid);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_GT(pid, 0);
+ ASSERT_EQ(ctid, pid);
+
+ int status = 0;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 123);
+}
+
+TEST_F(LlvmLibcSchedCloneTest, ChildTidSharedVm) {
+ TidArgs args;
+ args.parent_tid = LIBC_NAMESPACE::gettid();
+ void *stack_top = get_child_stack_top();
+ pid_t pid = LIBC_NAMESPACE::clone(check_tid_child, stack_top,
+ CLONE_VM | CLONE_VFORK | SIGCHLD, &args);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_GT(pid, 0);
+
+ int status = 0;
+ pid_t cpid = LIBC_NAMESPACE::waitpid(pid, &status, 0);
+ ASSERT_ERRNO_SUCCESS();
+ ASSERT_EQ(cpid, pid);
+ ASSERT_TRUE(WIFEXITED(status));
+ ASSERT_EQ(WEXITSTATUS(status), 0);
+ ASSERT_EQ(args.cached_tid, args.sys_tid);
+ ASSERT_NE(args.cached_tid, args.parent_tid);
+ ASSERT_EQ(LIBC_NAMESPACE::gettid(), args.parent_tid);
+}
+#endif // LIBC_TEST_UNDER_EMULATOR
>From cb99c492b70f3a2a96f981f444bf17632249e934 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Tue, 22 Sep 2026 09:49:44 +0000
Subject: [PATCH 2/2] enable in overlay for testing
---
libc/config/linux/riscv/entrypoints.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 3b92c3ad77233..9b608349ddecb 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1,4 +1,5 @@
set(TARGET_LIBC_ENTRYPOINTS
+ libc.src.sched.clone
# arpa/inet.h entrypoints
libc.src.arpa.inet.htonl
libc.src.arpa.inet.htons
@@ -1410,7 +1411,6 @@ if(LLVM_LIBC_FULL_BUILD)
libc.src.pthread.pthread_setspecific
# sched.h entrypoints
- libc.src.sched.clone
libc.src.sched.__sched_andcpuset
libc.src.sched.__sched_clrcpuset
libc.src.sched.__sched_cpualloc
More information about the libc-commits
mailing list