[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 12:25:31 PDT 2026


labath wrote:

> could you add a bit more detail on why this implementation can't use `syscall_impl`?

(adding here, LMK if you want to add some of this to the comment or commit msg as well)

The core issue is really simple. The clone syscall returns twice -- once in the parent and once in the child. Howere, the child returns with a different stack pointer value. The stack starts out empty -- no stack frames, locals or arguments. However, the execution still proceeds with the instruction immediately following the syscall insn. That is way outside of what is expressible in C(++).

Now, you say "can't". That's not entirely true because the previous implementation (in thread.cpp) kind of did that. But it could only do that by piling a bunch of workarounds to prevent the compiler from doing something that would crash.

First, it absolutely needs to `always_inline` the syscall function. The child has an empty stack so a return would immediately explode. This part at least has an attribute and well-defined semantics. The next part is trickier because it also needs to prevent the compiler from accessing the stack -- local variables, or spilled values. The previous version did that by forcing the locals into registers (this kind of works, but it is already architecture specific). It also force-enabled compiler optimizations, which makes it less likely the compiler will introduce spurious stack accesses, but there's no way to completely forbid it from doing that.

Next, you need some way to pass arguments to the child, the previous implementation did that by writing them to the new stack and then reading them from C code. The code solved that by forcing -fno-omit-frame-pointer, using __builtin_return_address (in an architecture-specific way), and forcing another compiler option to silence the warning about it's (potentially) unsafe use. This meant we couldn't zero out the frame pointer, which is a customary way to mark the end of the stack, and also remove the arguments from the stack once they were no longer needed. And it still required inline asm. A fully asm-based solution does not have this problem because it can pop the arguments off the stack, clear the frame pointer, and then call the user provided function -- while guaranteeing there will be no spurious stack accesses.

So, while putting more of this code into C would be possible, this wouldn't be standard C, but a very strangely restricted subset of it -- without a guarantee it will keep working. I don't think that's a good tradeoff for removing a couple of lines of inline asm.  An additional complication is that, if we want to make this syscall wrapper header-only (like our other wrappers), then these compiler options would need to be applied to the .cpp file actually using this function.

https://github.com/llvm/llvm-project/pull/224257


More information about the libc-commits mailing list