[libc-commits] [libc] [libc] Implement ucontext functions for x86_64 (PR #187712)

Jeff Bailey via libc-commits libc-commits at lists.llvm.org
Fri Mar 20 07:45:20 PDT 2026


https://github.com/kaladron created https://github.com/llvm/llvm-project/pull/187712

Implemented getcontext, setcontext, makecontext, and swapcontext for x86_64:

- getcontext: Captures general-purpose registers and captures FP state (fxsave).
- setcontext: Restores general-purpose registers and FP state (fxrstor).
- swapcontext: Saves current context into oucp and switches to ucp.
- makecontext: Sets up a new stack and trampoline for context transition.

Updated ucontext_t with 16-byte alignment needed for fxsave/fxrstor. Added unit tests covering context switching, argument passing, and uc_link. Added TODOs for future signal mask support using rt_sigprocmask.

>From 37304c525e63a355ef7e029f7fd0050a168d29c7 Mon Sep 17 00:00:00 2001
From: Jeff Bailey <jbailey at raspberryginger.com>
Date: Fri, 20 Mar 2026 14:11:51 +0000
Subject: [PATCH] [libc] Implement ucontext functions for x86_64

Implemented getcontext, setcontext, makecontext, and swapcontext for x86_64:

- getcontext: Captures general-purpose registers and captures FP state (fxsave).
- setcontext: Restores general-purpose registers and FP state (fxrstor).
- swapcontext: Saves current context into oucp and switches to ucp.
- makecontext: Sets up a new stack and trampoline for context transition.

Updated ucontext_t with 16-byte alignment needed for fxsave/fxrstor.
Added unit tests covering context switching, argument passing, and uc_link.
Added TODOs for future signal mask support using rt_sigprocmask.
---
 libc/config/linux/x86_64/entrypoints.txt    |   6 ++
 libc/include/CMakeLists.txt                 |  12 +++
 libc/include/llvm-libc-types/CMakeLists.txt |   2 +
 libc/include/llvm-libc-types/mcontext_t.h   |  78 ++++++++++++++
 libc/include/llvm-libc-types/ucontext_t.h   |  32 ++++++
 libc/include/ucontext.h.def                 |  21 ++++
 libc/include/ucontext.yaml                  |  37 +++++++
 libc/src/CMakeLists.txt                     |   1 +
 libc/src/ucontext/CMakeLists.txt            |  29 ++++++
 libc/src/ucontext/getcontext.h              |  21 ++++
 libc/src/ucontext/makecontext.h             |  21 ++++
 libc/src/ucontext/setcontext.h              |  21 ++++
 libc/src/ucontext/swapcontext.h             |  21 ++++
 libc/src/ucontext/x86_64/CMakeLists.txt     |  60 +++++++++++
 libc/src/ucontext/x86_64/getcontext.cpp     |  94 +++++++++++++++++
 libc/src/ucontext/x86_64/makecontext.cpp    |  84 +++++++++++++++
 libc/src/ucontext/x86_64/setcontext.cpp     |  90 ++++++++++++++++
 libc/src/ucontext/x86_64/swapcontext.cpp    | 107 +++++++++++++++++++
 libc/test/src/CMakeLists.txt                |   1 +
 libc/test/src/ucontext/CMakeLists.txt       |  14 +++
 libc/test/src/ucontext/ucontext_test.cpp    | 109 ++++++++++++++++++++
 21 files changed, 861 insertions(+)
 create mode 100644 libc/include/llvm-libc-types/mcontext_t.h
 create mode 100644 libc/include/llvm-libc-types/ucontext_t.h
 create mode 100644 libc/include/ucontext.h.def
 create mode 100644 libc/include/ucontext.yaml
 create mode 100644 libc/src/ucontext/CMakeLists.txt
 create mode 100644 libc/src/ucontext/getcontext.h
 create mode 100644 libc/src/ucontext/makecontext.h
 create mode 100644 libc/src/ucontext/setcontext.h
 create mode 100644 libc/src/ucontext/swapcontext.h
 create mode 100644 libc/src/ucontext/x86_64/CMakeLists.txt
 create mode 100644 libc/src/ucontext/x86_64/getcontext.cpp
 create mode 100644 libc/src/ucontext/x86_64/makecontext.cpp
 create mode 100644 libc/src/ucontext/x86_64/setcontext.cpp
 create mode 100644 libc/src/ucontext/x86_64/swapcontext.cpp
 create mode 100644 libc/test/src/ucontext/CMakeLists.txt
 create mode 100644 libc/test/src/ucontext/ucontext_test.cpp

diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 0602955dc1cb1..e6342e792dd64 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -435,6 +435,12 @@ set(TARGET_LIBC_ENTRYPOINTS
     # sys/time.h entrypoints
     libc.src.sys.time.setitimer
     libc.src.sys.time.getitimer
+
+    # ucontext.h entrypoints
+    libc.src.ucontext.getcontext
+    libc.src.ucontext.setcontext
+    libc.src.ucontext.makecontext
+    libc.src.ucontext.swapcontext
 )
 
 if(LLVM_LIBC_INCLUDE_SCUDO)
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index b2ebd035872aa..ba669a9db84e5 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -432,6 +432,18 @@ add_header_macro(
     .llvm_libc_common_h
 )
 
+add_header_macro(
+  ucontext
+  ../libc/include/ucontext.yaml
+  ucontext.h
+  DEPENDS
+    .llvm_libc_common_h
+    .llvm-libc-types.mcontext_t
+    .llvm-libc-types.ucontext_t
+    .llvm-libc-types.sigset_t
+    .llvm-libc-types.stack_t
+)
+
 add_header_macro(
   sched
   ../libc/include/sched.yaml
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index 5d82660fd6762..0e2e43aa296c6 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -99,6 +99,8 @@ add_header(union_sigval HDR union_sigval.h)
 add_header(siginfo_t HDR siginfo_t.h DEPENDS .union_sigval .pid_t .uid_t .clock_t)
 add_header(sig_atomic_t HDR sig_atomic_t.h)
 add_header(sigset_t HDR sigset_t.h DEPENDS libc.include.llvm-libc-macros.signal_macros)
+add_header(mcontext_t HDR mcontext_t.h)
+add_header(ucontext_t HDR ucontext_t.h DEPENDS .mcontext_t .sigset_t .stack_t)
 add_header(__jmp_buf HDR __jmp_buf.h DEPENDS .sigset_t)
 add_header(jmp_buf HDR jmp_buf.h DEPENDS .__jmp_buf)
 add_header(sigjmp_buf HDR sigjmp_buf.h DEPENDS .__jmp_buf)
diff --git a/libc/include/llvm-libc-types/mcontext_t.h b/libc/include/llvm-libc-types/mcontext_t.h
new file mode 100644
index 0000000000000..c45040fc66243
--- /dev/null
+++ b/libc/include/llvm-libc-types/mcontext_t.h
@@ -0,0 +1,78 @@
+//===-- Definition of type mcontext_t -------------------------------------===//
+//
+// 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_TYPES_MCONTEXT_T_H
+#define LLVM_LIBC_TYPES_MCONTEXT_T_H
+
+#if defined(__x86_64__)
+typedef long long int greg_t;
+typedef greg_t gregset_t[23];
+
+enum {
+  REG_R8 = 0,
+  REG_R9,
+  REG_R10,
+  REG_R11,
+  REG_R12,
+  REG_R13,
+  REG_R14,
+  REG_R15,
+  REG_RDI,
+  REG_RSI,
+  REG_RBP,
+  REG_RBX,
+  REG_RDX,
+  REG_RAX,
+  REG_RCX,
+  REG_RSP,
+  REG_RIP,
+  REG_EFL,
+  REG_CSGSFS,
+  REG_ERR,
+  REG_TRAPNO,
+  REG_OLDMASK,
+  REG_CR2
+};
+
+struct _libc_fpxreg {
+  unsigned short significand[4];
+  unsigned short exponent;
+  unsigned short padding[3];
+};
+
+struct _libc_xmmreg {
+  unsigned int element[4];
+};
+
+struct _libc_fpstate {
+  unsigned short cwd;
+  unsigned short swd;
+  unsigned short ftw;
+  unsigned short fop;
+  unsigned long long rip;
+  unsigned long long rdp;
+  unsigned int mxcsr;
+  unsigned int mxcr_mask;
+  struct _libc_fpxreg _st[8];
+  struct _libc_xmmreg _xmm[16];
+  unsigned int padding[24];
+};
+
+typedef struct _libc_fpstate *fpregset_t;
+
+typedef struct {
+  gregset_t gregs;
+  fpregset_t fpregs;
+  unsigned long long __reserved1[8];
+} mcontext_t;
+
+#else
+#error "mcontext_t not available for your target architecture."
+#endif
+
+#endif // LLVM_LIBC_TYPES_MCONTEXT_T_H
diff --git a/libc/include/llvm-libc-types/ucontext_t.h b/libc/include/llvm-libc-types/ucontext_t.h
new file mode 100644
index 0000000000000..068c257bc470e
--- /dev/null
+++ b/libc/include/llvm-libc-types/ucontext_t.h
@@ -0,0 +1,32 @@
+//===-- Definition of type ucontext_t -------------------------------------===//
+//
+// 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_TYPES_UCONTEXT_T_H
+#define LLVM_LIBC_TYPES_UCONTEXT_T_H
+
+#include "mcontext_t.h"
+#include "sigset_t.h"
+#include "stack_t.h"
+
+#if defined(__x86_64__)
+
+typedef struct alignas(16) ucontext_t {
+  unsigned long uc_flags;
+  struct ucontext_t *uc_link;
+  stack_t uc_stack;
+  mcontext_t uc_mcontext;
+  sigset_t uc_sigmask;
+  struct _libc_fpstate __fpregs_mem;
+  unsigned long long __ssp[4];
+} ucontext_t;
+
+#else
+#error "ucontext_t not available for your target architecture."
+#endif
+
+#endif // LLVM_LIBC_TYPES_UCONTEXT_T_H
diff --git a/libc/include/ucontext.h.def b/libc/include/ucontext.h.def
new file mode 100644
index 0000000000000..37113bcac6776
--- /dev/null
+++ b/libc/include/ucontext.h.def
@@ -0,0 +1,21 @@
+//===-- C standard library header ucontext.h --------------------------------===//
+//
+// 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_UCONTEXT_H
+#define LLVM_LIBC_UCONTEXT_H
+
+#include "__llvm-libc-common.h"
+
+#include "llvm-libc-types/mcontext_t.h"
+#include "llvm-libc-types/ucontext_t.h"
+#include "llvm-libc-types/sigset_t.h"
+#include "llvm-libc-types/stack_t.h"
+
+%%public_api()
+
+#endif // LLVM_LIBC_UCONTEXT_H
diff --git a/libc/include/ucontext.yaml b/libc/include/ucontext.yaml
new file mode 100644
index 0000000000000..05ae5e9d38602
--- /dev/null
+++ b/libc/include/ucontext.yaml
@@ -0,0 +1,37 @@
+header: ucontext.h
+standards:
+  - posix
+types:
+  - type_name: mcontext_t
+  - type_name: ucontext_t
+  - type_name: sigset_t
+  - type_name: stack_t
+functions:
+  - name: getcontext
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: ucontext_t *
+  - name: setcontext
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: const ucontext_t *
+  - name: makecontext
+    standards:
+      - posix
+    return_type: void
+    arguments:
+      - type: ucontext_t *
+      - type: void(*)(void)
+      - type: int
+      - type: ...
+  - name: swapcontext
+    standards:
+      - posix
+    return_type: int
+    arguments:
+      - type: ucontext_t *__restrict
+      - type: const ucontext_t *__restrict
diff --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt
index 8a0acccaed708..3021e65c3edc4 100644
--- a/libc/src/CMakeLists.txt
+++ b/libc/src/CMakeLists.txt
@@ -44,6 +44,7 @@ add_subdirectory(locale)
 add_subdirectory(nl_types)
 add_subdirectory(search)
 add_subdirectory(setjmp)
+add_subdirectory(ucontext)
 add_subdirectory(signal)
 add_subdirectory(spawn)
 add_subdirectory(threads)
diff --git a/libc/src/ucontext/CMakeLists.txt b/libc/src/ucontext/CMakeLists.txt
new file mode 100644
index 0000000000000..b163eac0e339d
--- /dev/null
+++ b/libc/src/ucontext/CMakeLists.txt
@@ -0,0 +1,29 @@
+add_subdirectory(${LIBC_TARGET_ARCHITECTURE})
+
+add_entrypoint_object(
+  getcontext
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_ARCHITECTURE}.getcontext
+)
+
+add_entrypoint_object(
+  setcontext
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_ARCHITECTURE}.setcontext
+)
+
+add_entrypoint_object(
+  makecontext
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_ARCHITECTURE}.makecontext
+)
+
+add_entrypoint_object(
+  swapcontext
+  ALIAS
+  DEPENDS
+    .${LIBC_TARGET_ARCHITECTURE}.swapcontext
+)
diff --git a/libc/src/ucontext/getcontext.h b/libc/src/ucontext/getcontext.h
new file mode 100644
index 0000000000000..b8f1ef46e3829
--- /dev/null
+++ b/libc/src/ucontext/getcontext.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for getcontext ----------------------*- C++ -*-===//
+//
+// 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_UCONTEXT_GETCONTEXT_H
+#define LLVM_LIBC_SRC_UCONTEXT_GETCONTEXT_H
+
+#include "src/__support/macros/config.h"
+#include "include/llvm-libc-types/ucontext_t.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int getcontext(ucontext_t *ucp);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UCONTEXT_GETCONTEXT_H
diff --git a/libc/src/ucontext/makecontext.h b/libc/src/ucontext/makecontext.h
new file mode 100644
index 0000000000000..78e7999831738
--- /dev/null
+++ b/libc/src/ucontext/makecontext.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for makecontext ---------------------*- C++ -*-===//
+//
+// 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_UCONTEXT_MAKECONTEXT_H
+#define LLVM_LIBC_SRC_UCONTEXT_MAKECONTEXT_H
+
+#include "src/__support/macros/config.h"
+#include "include/llvm-libc-types/ucontext_t.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+void makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UCONTEXT_MAKECONTEXT_H
diff --git a/libc/src/ucontext/setcontext.h b/libc/src/ucontext/setcontext.h
new file mode 100644
index 0000000000000..e5fbac6da482f
--- /dev/null
+++ b/libc/src/ucontext/setcontext.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for setcontext ----------------------*- C++ -*-===//
+//
+// 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_UCONTEXT_SETCONTEXT_H
+#define LLVM_LIBC_SRC_UCONTEXT_SETCONTEXT_H
+
+#include "src/__support/macros/config.h"
+#include "include/llvm-libc-types/ucontext_t.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int setcontext(const ucontext_t *ucp);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UCONTEXT_SETCONTEXT_H
diff --git a/libc/src/ucontext/swapcontext.h b/libc/src/ucontext/swapcontext.h
new file mode 100644
index 0000000000000..4412c862a427a
--- /dev/null
+++ b/libc/src/ucontext/swapcontext.h
@@ -0,0 +1,21 @@
+//===-- Implementation header for swapcontext ---------------------*- C++ -*-===//
+//
+// 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_UCONTEXT_SWAPCONTEXT_H
+#define LLVM_LIBC_SRC_UCONTEXT_SWAPCONTEXT_H
+
+#include "src/__support/macros/config.h"
+#include "include/llvm-libc-types/ucontext_t.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+int swapcontext(ucontext_t *__restrict oucp, const ucontext_t *__restrict ucp);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_UCONTEXT_SWAPCONTEXT_H
diff --git a/libc/src/ucontext/x86_64/CMakeLists.txt b/libc/src/ucontext/x86_64/CMakeLists.txt
new file mode 100644
index 0000000000000..014ef3d095572
--- /dev/null
+++ b/libc/src/ucontext/x86_64/CMakeLists.txt
@@ -0,0 +1,60 @@
+add_entrypoint_object(
+  getcontext
+  SRCS
+    getcontext.cpp
+  HDRS
+    ../getcontext.h
+  COMPILE_OPTIONS
+    -O3
+    -fno-omit-frame-pointer
+  DEPENDS
+    libc.include.llvm-libc-types.ucontext_t
+    libc.src.__support.common
+    libc.src.__support.macros.config
+)
+
+add_entrypoint_object(
+  setcontext
+  SRCS
+    setcontext.cpp
+  HDRS
+    ../setcontext.h
+  COMPILE_OPTIONS
+    -O3
+    -fno-omit-frame-pointer
+  DEPENDS
+    libc.include.llvm-libc-types.ucontext_t
+    libc.src.__support.common
+    libc.src.__support.macros.config
+)
+
+add_entrypoint_object(
+  makecontext
+  SRCS
+    makecontext.cpp
+  HDRS
+    ../makecontext.h
+  COMPILE_OPTIONS
+    -O3
+    -fno-omit-frame-pointer
+  DEPENDS
+    libc.include.llvm-libc-types.ucontext_t
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.OSUtil.osutil
+)
+
+add_entrypoint_object(
+  swapcontext
+  SRCS
+    swapcontext.cpp
+  HDRS
+    ../swapcontext.h
+  COMPILE_OPTIONS
+    -O3
+    -fno-omit-frame-pointer
+  DEPENDS
+    libc.include.llvm-libc-types.ucontext_t
+    libc.src.__support.common
+    libc.src.__support.macros.config
+)
diff --git a/libc/src/ucontext/x86_64/getcontext.cpp b/libc/src/ucontext/x86_64/getcontext.cpp
new file mode 100644
index 0000000000000..f499873202a33
--- /dev/null
+++ b/libc/src/ucontext/x86_64/getcontext.cpp
@@ -0,0 +1,94 @@
+//===-- Implementation of getcontext for x86_64 ---------------------------===//
+//
+// 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/ucontext/getcontext.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+#include "include/llvm-libc-types/ucontext_t.h"
+#include <stddef.h>
+
+// We define these locally so we don't depend on system headers.
+// These are the standard sysV x86_64 ABI indices for gregset_t.
+
+namespace LIBC_NAMESPACE_DECL {
+
+// We use naked because we need to capture the exact register state
+// at the moment of the function call, avoiding any compiler prologue/epilogue.
+[[gnu::naked]]
+LLVM_LIBC_FUNCTION(int, getcontext, (ucontext_t *ucp)) {
+  asm(R"(
+      // ucp is in rdi
+      
+      // Save general purpose registers
+      mov %%r8, %c[r8](%%rdi)
+      mov %%r9, %c[r9](%%rdi)
+      mov %%r10, %c[r10](%%rdi)
+      mov %%r11, %c[r11](%%rdi)
+      mov %%r12, %c[r12](%%rdi)
+      mov %%r13, %c[r13](%%rdi)
+      mov %%r14, %c[r14](%%rdi)
+      mov %%r15, %c[r15](%%rdi)
+      mov %%rdi, %c[rdi](%%rdi)
+      mov %%rsi, %c[rsi](%%rdi)
+      mov %%rbp, %c[rbp](%%rdi)
+      mov %%rbx, %c[rbx](%%rdi)
+      mov %%rdx, %c[rdx](%%rdi)
+      // getcontext should return 0 when resumed by setcontext.
+      // So we save 0 into the RAX register of the context.
+      movq $0, %c[rax](%%rdi)
+      mov %%rcx, %c[rcx](%%rdi)
+
+      // The stack pointer before the call is rsp + sizeof(void*).
+      // The return address was pushed when this function was called.
+      // Save instruction pointer and stack pointer
+      mov (%%rsp), %%rax
+      mov %%rax, %c[rip](%%rdi)
+      lea %c[ret_size](%%rsp), %%rax
+      mov %%rax, %c[rsp](%%rdi)
+
+      // Save floating point state
+      fxsaveq %c[fpregs_mem](%%rdi)
+      // Point mcontext.fpregs to our internal FP storage
+      lea %c[fpregs_mem](%%rdi), %%rax
+      mov %%rax, %c[fpregs_ptr](%%rdi)
+
+      // Reset return value to 0
+      xor %%eax, %%eax
+
+      // TODO: Capture the signal mask using rt_sigprocmask syscall.
+      // Signal support is currently minimal in LLVM-libc.
+
+      retq
+      )" 
+      ::
+      [ret_size] "i"(sizeof(void*)),
+      [r8] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R8])),
+      [r9] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R9])),
+      [r10] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R10])),
+      [r11] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R11])),
+      [r12] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R12])),
+      [r13] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R13])),
+      [r14] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R14])),
+      [r15] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R15])),
+      [rdi] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RDI])),
+      [rsi] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RSI])),
+      [rbp] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RBP])),
+      [rbx] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RBX])),
+      [rdx] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RDX])),
+      [rax] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RAX])),
+      [rcx] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RCX])),
+      [rsp] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RSP])),
+      [rip] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RIP])),
+      [fpregs_mem] "i"(offsetof(ucontext_t, __fpregs_mem)),
+      [fpregs_ptr] "i"(offsetof(ucontext_t, uc_mcontext.fpregs))
+      : "memory"
+  );
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/ucontext/x86_64/makecontext.cpp b/libc/src/ucontext/x86_64/makecontext.cpp
new file mode 100644
index 0000000000000..8907dab4f1d18
--- /dev/null
+++ b/libc/src/ucontext/x86_64/makecontext.cpp
@@ -0,0 +1,84 @@
+//===-- Implementation of makecontext for x86_64 --------------------------===//
+//
+// 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/ucontext/makecontext.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+#include "include/llvm-libc-types/ucontext_t.h"
+#include <stddef.h>
+#include <stdint.h>
+#include <stdarg.h>
+
+#include "src/__support/OSUtil/exit.h"
+#include "src/ucontext/setcontext.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+extern "C" void __makecontext_trampoline_c(ucontext_t *uc_link) {
+  if (uc_link)
+    setcontext(uc_link);
+  
+  internal::exit(0);
+}
+
+[[gnu::naked]] void __makecontext_trampoline() {
+  asm(R"(
+      mov %rbx, %rdi
+      call __makecontext_trampoline_c
+      hlt
+  )");
+}
+
+LLVM_LIBC_FUNCTION(void, makecontext, (ucontext_t *ucp, void (*func)(void), int argc, ...)) {
+  if (!ucp || !func)
+    return;
+
+  // System V AMD64 ABI requirements.
+  constexpr uintptr_t STACK_ALIGN_BYTES = 16;
+  constexpr uintptr_t STACK_ALIGN_MASK = ~(STACK_ALIGN_BYTES - 1);
+  constexpr int REGISTER_ARGS_COUNT = 6;
+  constexpr uintptr_t ARG_SIZE = sizeof(greg_t);
+
+  uintptr_t stack_top = reinterpret_cast<uintptr_t>(ucp->uc_stack.ss_sp) + ucp->uc_stack.ss_size;
+  stack_top &= STACK_ALIGN_MASK;
+  
+  int stack_args = argc > REGISTER_ARGS_COUNT ? argc - REGISTER_ARGS_COUNT : 0;
+  
+  uintptr_t new_rsp = stack_top - stack_args * ARG_SIZE;
+  new_rsp &= STACK_ALIGN_MASK;
+  
+  // The System V ABI requires the stack to be 16-byte aligned before the 'call' instruction.
+  // When a function is entered, the return address has been pushed, making the stack misaligned by 8.
+  // We simulate this state by subtracting 8, storing the trampoline address at the top of the stack.
+  new_rsp -= ARG_SIZE;
+
+  greg_t *stack_area = reinterpret_cast<greg_t *>(new_rsp);
+  stack_area[0] = reinterpret_cast<greg_t>(&__makecontext_trampoline);
+
+  va_list ap;
+  va_start(ap, argc);
+  if (argc > 0) ucp->uc_mcontext.gregs[REG_RDI] = va_arg(ap, greg_t);
+  if (argc > 1) ucp->uc_mcontext.gregs[REG_RSI] = va_arg(ap, greg_t);
+  if (argc > 2) ucp->uc_mcontext.gregs[REG_RDX] = va_arg(ap, greg_t);
+  if (argc > 3) ucp->uc_mcontext.gregs[REG_RCX] = va_arg(ap, greg_t);
+  if (argc > 4) ucp->uc_mcontext.gregs[REG_R8]  = va_arg(ap, greg_t);
+  if (argc > 5) ucp->uc_mcontext.gregs[REG_R9]  = va_arg(ap, greg_t);
+  
+  for (int i = 0; i < stack_args; ++i) {
+    stack_area[i + 1] = va_arg(ap, greg_t);
+  }
+  
+  va_end(ap);
+
+  ucp->uc_mcontext.gregs[REG_RIP] = reinterpret_cast<greg_t>(func);
+  ucp->uc_mcontext.gregs[REG_RSP] = new_rsp;
+  ucp->uc_mcontext.gregs[REG_RBX] = reinterpret_cast<greg_t>(ucp->uc_link);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/ucontext/x86_64/setcontext.cpp b/libc/src/ucontext/x86_64/setcontext.cpp
new file mode 100644
index 0000000000000..43aa45ba2e0f7
--- /dev/null
+++ b/libc/src/ucontext/x86_64/setcontext.cpp
@@ -0,0 +1,90 @@
+//===-- Implementation of setcontext for x86_64 ---------------------------===//
+//
+// 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/ucontext/setcontext.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+#include "include/llvm-libc-types/ucontext_t.h"
+#include <stddef.h>
+
+namespace LIBC_NAMESPACE_DECL {
+
+[[gnu::naked]]
+LLVM_LIBC_FUNCTION(int, setcontext, (const ucontext_t *ucp)) {
+  asm(R"(
+      // ucp is in rdi
+      
+      // TODO: Restore the signal mask using rt_sigprocmask syscall.
+
+      // Restore floating point state
+      fxrstorq %c[fpregs_mem](%%rdi)
+
+      // Store the desired target rip in a temporary safe register 
+      // so we can jump there at the end easily.
+      mov %c[rip](%%rdi), %%rcx
+
+      // Restore stack pointer first
+      mov %c[rsp](%%rdi), %%rsp
+
+      // We simulate returning to the instruction pointer by pushing it onto
+      // our new stack so that a simple `ret` at the end can jump to it.
+      pushq %%rcx
+
+      // Restore other general purpose registers
+      mov %c[r8](%%rdi), %%r8
+      mov %c[r9](%%rdi), %%r9
+      mov %c[r10](%%rdi), %%r10
+      mov %c[r11](%%rdi), %%r11
+      mov %c[r12](%%rdi), %%r12
+      mov %c[r13](%%rdi), %%r13
+      mov %c[r14](%%rdi), %%r14
+      mov %c[r15](%%rdi), %%r15
+      
+      // We restore RSI and others
+      mov %c[rsi](%%rdi), %%rsi
+      mov %c[rbp](%%rdi), %%rbp
+      mov %c[rbx](%%rdi), %%rbx
+      mov %c[rdx](%%rdi), %%rdx
+      mov %c[rax](%%rdi), %%rax
+      
+      // Notice we are relying on RDI currently to access ucontext_t,
+      // so we restore it very last. RDI holds the address of the ucontext.
+      // Wait, we need to restore RCX, but we used it for RIP earlier -- wait, 
+      // we pushed it, so we can restore RCX properly too.
+      mov %c[rcx](%%rdi), %%rcx
+      mov %c[rdi](%%rdi), %%rdi
+
+      // Now we have the correct RSP, the RIP is on the top of the stack.
+      // General registers are restored.
+      // Jump and continue!
+      retq
+      )"
+      ::
+      [r8] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R8])),
+      [r9] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R9])),
+      [r10] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R10])),
+      [r11] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R11])),
+      [r12] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R12])),
+      [r13] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R13])),
+      [r14] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R14])),
+      [r15] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R15])),
+      [rdi] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RDI])),
+      [rsi] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RSI])),
+      [rbp] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RBP])),
+      [rbx] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RBX])),
+      [rdx] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RDX])),
+      [rax] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RAX])),
+      [rcx] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RCX])),
+      [rsp] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RSP])),
+      [rip] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RIP])),
+      [fpregs_mem] "i"(offsetof(ucontext_t, __fpregs_mem))
+  );
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/ucontext/x86_64/swapcontext.cpp b/libc/src/ucontext/x86_64/swapcontext.cpp
new file mode 100644
index 0000000000000..ba50df6694942
--- /dev/null
+++ b/libc/src/ucontext/x86_64/swapcontext.cpp
@@ -0,0 +1,107 @@
+//===-- Implementation of swapcontext for x86_64 --------------------------===//
+//
+// 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/ucontext/swapcontext.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+
+#include "include/llvm-libc-types/ucontext_t.h"
+#include <stddef.h>
+namespace LIBC_NAMESPACE_DECL {
+
+[[gnu::naked]]
+LLVM_LIBC_FUNCTION(int, swapcontext, (ucontext_t *__restrict oucp, const ucontext_t *__restrict ucp)) {
+  asm(R"(
+      // oucp is in rdi, ucp is in rsi
+      
+      // SAVE TO oucp
+      mov %%r8, %c[r8](%%rdi)
+      mov %%r9, %c[r9](%%rdi)
+      mov %%r10, %c[r10](%%rdi)
+      mov %%r11, %c[r11](%%rdi)
+      mov %%r12, %c[r12](%%rdi)
+      mov %%r13, %c[r13](%%rdi)
+      mov %%r14, %c[r14](%%rdi)
+      mov %%r15, %c[r15](%%rdi)
+      mov %%rdi, %c[rdi](%%rdi)
+      mov %%rsi, %c[rsi](%%rdi)
+      mov %%rbp, %c[rbp](%%rdi)
+      mov %%rbx, %c[rbx](%%rdi)
+      mov %%rdx, %c[rdx](%%rdi)
+      movq $0, %c[rax](%%rdi)
+      mov %%rcx, %c[rcx](%%rdi)
+
+      // Save instruction pointer and stack pointer
+      mov (%%rsp), %%rax
+      mov %%rax, %c[rip](%%rdi)
+      lea %c[ret_size](%%rsp), %%rax
+      mov %%rax, %c[rsp](%%rdi)
+
+      // Save FP state
+      fxsaveq %c[fpregs_mem](%%rdi)
+      // Point mcontext.fpregs to our internal FP storage
+      lea %c[fpregs_mem](%%rdi), %%rax
+      mov %%rax, %c[fpregs_ptr](%%rdi)
+
+      // TODO: Capture oucp signal mask and restore ucp signal mask
+
+      // RESTORE FROM ucp (rsi)
+      // Restore floating point state
+      fxrstorq %c[fpregs_mem](%%rsi)
+
+      // The pushq instruction restores the return address onto the stack.
+      mov %c[rip](%%rsi), %%rcx
+      mov %c[rsp](%%rsi), %%rsp
+      pushq %%rcx
+
+      mov %c[r8](%%rsi), %%r8
+      mov %c[r9](%%rsi), %%r9
+      mov %c[r10](%%rsi), %%r10
+      mov %c[r11](%%rsi), %%r11
+      mov %c[r12](%%rsi), %%r12
+      mov %c[r13](%%rsi), %%r13
+      mov %c[r14](%%rsi), %%r14
+      mov %c[r15](%%rsi), %%r15
+      
+      mov %c[rbp](%%rsi), %%rbp
+      mov %c[rbx](%%rsi), %%rbx
+      mov %c[rdx](%%rsi), %%rdx
+      mov %c[rax](%%rsi), %%rax
+      mov %c[rcx](%%rsi), %%rcx
+
+      mov %c[rdi](%%rsi), %%rdi
+      mov %c[rsi](%%rsi), %%rsi
+
+      retq
+      )"
+      ::
+      [ret_size] "i"(sizeof(void*)),
+      [r8] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R8])),
+      [r9] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R9])),
+      [r10] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R10])),
+      [r11] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R11])),
+      [r12] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R12])),
+      [r13] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R13])),
+      [r14] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R14])),
+      [r15] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_R15])),
+      [rdi] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RDI])),
+      [rsi] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RSI])),
+      [rbp] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RBP])),
+      [rbx] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RBX])),
+      [rdx] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RDX])),
+      [rax] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RAX])),
+      [rcx] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RCX])),
+      [rsp] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RSP])),
+      [rip] "i"(offsetof(ucontext_t, uc_mcontext.gregs[REG_RIP])),
+      [fpregs_mem] "i"(offsetof(ucontext_t, __fpregs_mem)),
+      [fpregs_ptr] "i"(offsetof(ucontext_t, uc_mcontext.fpregs))
+      : "memory"
+  );
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt
index 5e61c739c066a..df0e2e9c915b3 100644
--- a/libc/test/src/CMakeLists.txt
+++ b/libc/test/src/CMakeLists.txt
@@ -65,6 +65,7 @@ add_subdirectory(fenv)
 add_subdirectory(math)
 add_subdirectory(search)
 add_subdirectory(setjmp)
+add_subdirectory(ucontext)
 add_subdirectory(stdbit)
 add_subdirectory(stdfix)
 add_subdirectory(stdio)
diff --git a/libc/test/src/ucontext/CMakeLists.txt b/libc/test/src/ucontext/CMakeLists.txt
new file mode 100644
index 0000000000000..8846267855e86
--- /dev/null
+++ b/libc/test/src/ucontext/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_custom_target(libc_ucontext_unittests)
+
+add_libc_unittest(
+  ucontext_test
+  SUITE
+    libc_ucontext_unittests
+  SRCS
+    ucontext_test.cpp
+  DEPENDS
+    libc.src.ucontext.getcontext
+    libc.src.ucontext.setcontext
+    libc.src.ucontext.makecontext
+    libc.src.ucontext.swapcontext
+)
diff --git a/libc/test/src/ucontext/ucontext_test.cpp b/libc/test/src/ucontext/ucontext_test.cpp
new file mode 100644
index 0000000000000..c74f0a8c3a1d0
--- /dev/null
+++ b/libc/test/src/ucontext/ucontext_test.cpp
@@ -0,0 +1,109 @@
+//===-- Unittests for ucontext routines -----------------------------------===//
+//
+// 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/ucontext/getcontext.h"
+#include "src/ucontext/setcontext.h"
+#include "src/ucontext/makecontext.h"
+#include "src/ucontext/swapcontext.h"
+
+#include "test/UnitTest/Test.h"
+
+#include <ucontext.h>
+
+volatile int jumped = 0;
+TEST(LlvmLibcUcontextTest, BasicStubTest) {
+  ucontext_t ctx;
+  ASSERT_EQ(LIBC_NAMESPACE::getcontext(&ctx), 0);
+  if (!jumped) {
+    jumped = 1;
+    LIBC_NAMESPACE::setcontext(&ctx);
+    ASSERT_TRUE(false && "setcontext should not return on success");
+  }
+}
+
+ucontext_t old_ctx, new_ctx;
+volatile int swap_called = 0;
+
+void swap_func() {
+  swap_called = 1;
+  LIBC_NAMESPACE::setcontext(&old_ctx);
+}
+
+TEST(LlvmLibcUcontextTest, SwapcontextTest) {
+  LIBC_NAMESPACE::getcontext(&new_ctx);
+  constexpr size_t STACK_SIZE = 8192;
+  char stack[STACK_SIZE];
+  new_ctx.uc_stack.ss_sp = stack;
+  new_ctx.uc_stack.ss_size = sizeof(stack);
+  LIBC_NAMESPACE::makecontext(&new_ctx, swap_func, 0);
+
+  LIBC_NAMESPACE::swapcontext(&old_ctx, &new_ctx);
+  
+  ASSERT_EQ(swap_called, 1);
+}
+
+ucontext_t old_ctx_args, new_ctx_args;
+volatile int makecontext_args_called = 0;
+volatile int arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8;
+
+void args_func(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) {
+  makecontext_args_called = 1;
+  arg1 = a1; arg2 = a2; arg3 = a3; arg4 = a4;
+  arg5 = a5; arg6 = a6; arg7 = a7; arg8 = a8;
+  LIBC_NAMESPACE::setcontext(&old_ctx_args);
+}
+
+TEST(LlvmLibcUcontextTest, MakecontextArgsTest) {
+  LIBC_NAMESPACE::getcontext(&new_ctx_args);
+  constexpr size_t STACK_SIZE = 8192;
+  char stack[STACK_SIZE];
+  new_ctx_args.uc_stack.ss_sp = stack;
+  new_ctx_args.uc_stack.ss_size = sizeof(stack);
+  
+  // Cast function pointer to void(*)(void) as required by makecontext
+  using func_t = void (*)(void);
+  auto func = reinterpret_cast<func_t>(args_func);
+  
+  LIBC_NAMESPACE::makecontext(&new_ctx_args, func, 8, 11, 22, 33, 44, 55, 66, 77, 88);
+
+  LIBC_NAMESPACE::swapcontext(&old_ctx_args, &new_ctx_args);
+  
+  ASSERT_EQ(makecontext_args_called, 1);
+  ASSERT_EQ(arg1, 11);
+  ASSERT_EQ(arg2, 22);
+  ASSERT_EQ(arg3, 33);
+  ASSERT_EQ(arg4, 44);
+  ASSERT_EQ(arg5, 55);
+  ASSERT_EQ(arg6, 66);
+  ASSERT_EQ(arg8, 88);
+}
+
+ucontext_t old_ctx_return, new_ctx_return;
+volatile int makecontext_return_called = 0;
+
+void return_func() {
+  makecontext_return_called = 1;
+}
+
+TEST(LlvmLibcUcontextTest, MakecontextReturnTest) {
+  LIBC_NAMESPACE::getcontext(&new_ctx_return);
+  constexpr size_t STACK_SIZE = 8192;
+  char stack[STACK_SIZE];
+  new_ctx_return.uc_stack.ss_sp = stack;
+  new_ctx_return.uc_stack.ss_size = sizeof(stack);
+  new_ctx_return.uc_link = &old_ctx_return;
+  
+  using func_t = void (*)(void);
+  auto func = reinterpret_cast<func_t>(return_func);
+  
+  LIBC_NAMESPACE::makecontext(&new_ctx_return, func, 0);
+
+  LIBC_NAMESPACE::swapcontext(&old_ctx_return, &new_ctx_return);
+  
+  ASSERT_EQ(makecontext_return_called, 1);
+}



More information about the libc-commits mailing list