[libc-commits] [libc] [libc] Implement clone(2) and use it in thread spawning (PR #224257)
Pavel Labath via libc-commits
libc-commits at lists.llvm.org
Thu Sep 17 03:32:20 PDT 2026
https://github.com/labath updated https://github.com/llvm/llvm-project/pull/224257
>From 4d7f684b5552c46039f18c92b95495c8f49aa6d3 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Wed, 16 Sep 2026 14:00:03 +0000
Subject: [PATCH] [libc] Implement clone(2) and use it in thread spawning
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 | 21 ++
.../linux/syscall_wrappers/aarch64/clone.h | 58 ++++++
.../OSUtil/linux/syscall_wrappers/clone.h | 69 +++++++
.../linux/syscall_wrappers/riscv/clone.h | 67 ++++++
.../linux/syscall_wrappers/x86_64/clone.h | 57 ++++++
.../__support/threads/linux/CMakeLists.txt | 7 +-
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 | 190 ++++++++++++++++++
20 files changed, 678 insertions(+), 104 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 a63d6ad6282c1..f4c39f6dec4e9 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -57,6 +57,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.pwd.setpwent
# sched.h entrypoints
+ libc.src.sched.clone
libc.src.sched.sched_get_priority_max
libc.src.sched.sched_get_priority_min
libc.src.sched.sched_getaffinity
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index e8180cbb0c70e..f5e754e1a9ce2 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -76,6 +76,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.pwd.setpwent
# sched.h entrypoints
+ libc.src.sched.clone
libc.src.sched.getcpu
libc.src.sched.sched_get_priority_max
libc.src.sched.sched_get_priority_min
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 3f4e563e54359..f16d1aaa8e1e2 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -76,6 +76,7 @@ set(TARGET_LIBC_ENTRYPOINTS
libc.src.pwd.setpwent
# sched.h entrypoints
+ libc.src.sched.clone
libc.src.sched.getcpu
libc.src.sched.sched_get_priority_max
libc.src.sched.sched_get_priority_min
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 17665f87dfcd8..c25eedcd42de8 100644
--- a/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/CMakeLists.txt
@@ -1113,6 +1113,27 @@ 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.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..e60e639040338
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/aarch64/clone.h
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 {
+namespace detail {
+
+constexpr uintptr_t STACK_ADJUSTMENT = 16;
+
+LIBC_INLINE long clone_impl(int flags, void *child_stack, pid_t *parent_tid,
+ void *tls, pid_t *child_tid) {
+ long ret;
+ 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"
+ "svc #0\n\t"
+ "brk #0\n\t"
+ "1:\n\t" : "=r"(ret) : "r"(x8),
+ "0"(x0), "r"(x1), "r"(x2), "r"(x3),
+ "r"(x4), [sys_exit] "i"(SYS_exit) : "memory", "cc");
+ return ret;
+}
+
+} // 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..83fccc6c6cd09
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/clone.h
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/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) {
+ // 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.
+ uintptr_t sp = reinterpret_cast<uintptr_t>(stack);
+ sp -= detail::STACK_ADJUSTMENT;
+ static_assert(sizeof(ChildStackFrame) <= detail::STACK_ADJUSTMENT);
+ 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..be484fe09eabc
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/riscv/clone.h
@@ -0,0 +1,67 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 {
+namespace detail {
+
+constexpr uintptr_t STACK_ADJUSTMENT = 16;
+
+LIBC_INLINE long clone_impl(int flags, void *child_stack, pid_t *parent_tid,
+ void *tls, pid_t *child_tid) {
+ long ret;
+ 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"
+ "ecall\n\t"
+ "unimp\n\t"
+ "1:\n\t" : "=r"(ret) : "r"(a7),
+ "0"(a0), "r"(a1), "r"(a2), "r"(a3),
+ "r"(a4), [sys_exit] "i"(SYS_exit),
+ [stack_adj] "i"(STACK_ADJUSTMENT) : "memory");
+ return ret;
+}
+
+} // 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..d791457ad0f3e
--- /dev/null
+++ b/libc/src/__support/OSUtil/linux/syscall_wrappers/x86_64/clone.h
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 {
+namespace detail {
+
+constexpr uintptr_t STACK_ADJUSTMENT = 16;
+
+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"
+ "syscall\n\t"
+ "hlt\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..363252a953eef 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
@@ -73,12 +74,6 @@ add_object_library(
libc.src.__support.OSUtil.linux.syscall_wrappers.write
libc.src.__support.threads.thread_common
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 3dcb6fd6da6ac..e3d114253c4d4 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"
@@ -31,19 +32,15 @@
#include "src/__support/threads/linux/futex_utils.h" // For FutexWordType
#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) {
@@ -196,6 +160,7 @@ cleanup_thread_resources(ThreadAttributes *attrib) {
thread_exit(ThreadReturnValue(attrib->retval.stdc_retval),
ThreadStyle::STDC);
}
+ return 0;
}
int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
@@ -244,14 +209,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);
@@ -302,48 +264,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..59eff4336591e
--- /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 &= ~15;
+ 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..9a0430a971841
--- /dev/null
+++ b/libc/test/src/sched/linux/clone_test.cpp
@@ -0,0 +1,190 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, 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, 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);
+}
+
+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);
+}
More information about the libc-commits
mailing list