[libc-commits] [libc] 3b82b4f - [libc] Add x86_64 implementation of setjmp and longjmp.

Siva Chandra Reddy via libc-commits libc-commits at lists.llvm.org
Tue Nov 1 15:58:43 PDT 2022


Author: Siva Chandra Reddy
Date: 2022-11-01T22:58:35Z
New Revision: 3b82b4fbd50ac15584ac88075f356f3ebf946515

URL: https://github.com/llvm/llvm-project/commit/3b82b4fbd50ac15584ac88075f356f3ebf946515
DIFF: https://github.com/llvm/llvm-project/commit/3b82b4fbd50ac15584ac88075f356f3ebf946515.diff

LOG: [libc] Add x86_64 implementation of setjmp and longjmp.

Reviewed By: michaelrj

Differential Revision: https://reviews.llvm.org/D137147

Added: 
    libc/include/llvm-libc-types/jmp_buf.h
    libc/include/setjmp.h.def
    libc/src/setjmp/CMakeLists.txt
    libc/src/setjmp/longjmp.cpp
    libc/src/setjmp/longjmp.h
    libc/src/setjmp/setjmp.cpp
    libc/src/setjmp/setjmp_impl.h
    libc/test/src/setjmp/CMakeLists.txt
    libc/test/src/setjmp/setjmp_test.cpp

Modified: 
    libc/config/linux/api.td
    libc/config/linux/x86_64/entrypoints.txt
    libc/config/linux/x86_64/headers.txt
    libc/include/CMakeLists.txt
    libc/include/llvm-libc-types/CMakeLists.txt
    libc/spec/stdc.td
    libc/src/CMakeLists.txt
    libc/test/src/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/config/linux/api.td b/libc/config/linux/api.td
index 25e19e83fbcf9..a22b3772b303e 100644
--- a/libc/config/linux/api.td
+++ b/libc/config/linux/api.td
@@ -314,3 +314,7 @@ def SpawnAPI : PublicAPI<"spawn.h"> {
 def TermiosAPI : PublicAPI<"termios.h"> {
   let Types = ["cc_t", "pid_t", "speed_t", "struct termios", "tcflag_t"];
 }
+
+def SetJmpAPI : PublicAPI<"setjmp.h"> {
+  let Types = ["jmp_buf"];
+}

diff  --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index b8d962dc7e2a2..7b0fb53451004 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -362,6 +362,10 @@ if(LLVM_LIBC_FULL_BUILD)
     # sched.h entrypoints
     libc.src.sched.__sched_getcpucount
 
+    # setjmp.h entrypoints
+    libc.src.setjmp.longjmp
+    libc.src.setjmp.setjmp
+
     # stdio.h entrypoints
     libc.src.stdio.clearerr
     libc.src.stdio.clearerr_unlocked

diff  --git a/libc/config/linux/x86_64/headers.txt b/libc/config/linux/x86_64/headers.txt
index 123fc7ef43286..a878e6d89e003 100644
--- a/libc/config/linux/x86_64/headers.txt
+++ b/libc/config/linux/x86_64/headers.txt
@@ -11,6 +11,7 @@ set(TARGET_PUBLIC_HEADERS
     libc.include.sched
     libc.include.signal
     libc.include.spawn
+    libc.include.setjmp
     libc.include.stdio
     libc.include.stdlib
     libc.include.string

diff  --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index 3e63695e12a2a..066c7783d82a8 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -76,6 +76,15 @@ add_gen_header(
     .llvm_libc_common_h
 )
 
+add_gen_header(
+  setjmp
+  DEF_FILE setjmp.h.def
+  GEN_HDR setjmp.h
+  DEPENDS
+    .llvm_libc_common_h
+    .llvm-libc-types.jmp_buf
+)
+
 add_gen_header(
   string
   DEF_FILE string.h.def

diff  --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index e4e9567d5fdc9..4bd69be95cfe3 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -36,6 +36,7 @@ add_header(gid_t HDR gid_t.h)
 add_header(uid_t HDR uid_t.h)
 add_header(imaxdiv_t HDR imaxdiv_t.h)
 add_header(ino_t HDR ino_t.h)
+add_header(jmp_buf HDR jmp_buf.h)
 add_header(mode_t HDR mode_t.h)
 add_header(mtx_t HDR mtx_t.h DEPENDS .__futex_word .__mutex_type)
 add_header(nlink_t HDR nlink_t.h)

diff  --git a/libc/include/llvm-libc-types/jmp_buf.h b/libc/include/llvm-libc-types/jmp_buf.h
new file mode 100644
index 0000000000000..e948a7f42248a
--- /dev/null
+++ b/libc/include/llvm-libc-types/jmp_buf.h
@@ -0,0 +1,29 @@
+//===-- Definition of type jmp_buf ----------------------------------------===//
+//
+// 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_JMP_BUF_H__
+#define __LLVM_LIBC_TYPES_JMP_BUF_H__
+
+typedef struct {
+#ifdef __x86_64__
+  __UINT64_TYPE__ rbx;
+  __UINT64_TYPE__ rbp;
+  __UINT64_TYPE__ r12;
+  __UINT64_TYPE__ r13;
+  __UINT64_TYPE__ r14;
+  __UINT64_TYPE__ r15;
+  __UINTPTR_TYPE__ rsp;
+  __UINTPTR_TYPE__ rip;
+#else
+#error "__jmp_buf not available for your target architecture."
+#endif
+} __jmp_buf;
+
+typedef __jmp_buf jmp_buf[1];
+
+#endif // __LLVM_LIBC_TYPES_JMP_BUF_H__

diff  --git a/libc/include/setjmp.h.def b/libc/include/setjmp.h.def
new file mode 100644
index 0000000000000..7447be2415bd5
--- /dev/null
+++ b/libc/include/setjmp.h.def
@@ -0,0 +1,16 @@
+//===-- C standard library header setjmp.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_SETJMP_H
+#define LLVM_LIBC_SETJMP_H
+
+#include <__llvm-libc-common.h>
+
+%%public_api()
+
+#endif // LLVM_LIBC_SETJMP_H

diff  --git a/libc/spec/stdc.td b/libc/spec/stdc.td
index 1c0d7c2dd8d99..964b3e7c27338 100644
--- a/libc/spec/stdc.td
+++ b/libc/spec/stdc.td
@@ -9,6 +9,8 @@ def StdC : StandardSpec<"stdc"> {
   NamedType LDivTType = NamedType<"ldiv_t">;
   NamedType LLDivTType = NamedType<"lldiv_t">;
 
+  NamedType JmpBuf = NamedType<"jmp_buf">;
+
   NamedType TssTType = NamedType<"tss_t">;
   PtrType TssTPtr = PtrType<TssTType>;
   NamedType TssDtorTType = NamedType<"tss_dtor_t">;
@@ -988,6 +990,30 @@ def StdC : StandardSpec<"stdc"> {
       ]
   >;
 
+  HeaderSpec SetJmp = HeaderSpec<
+      "setjmp.h",
+      [], // Macros
+      [JmpBuf],
+      [], // Enumerations
+      [
+          FunctionSpec<
+              "longjmp",
+              RetValSpec<NoReturn>,
+              [ArgSpec<JmpBuf>, ArgSpec<IntType>]
+          >,
+          FunctionSpec<
+              "setjmp",
+              RetValSpec<IntType>,
+              [ArgSpec<JmpBuf>]
+          >,
+          FunctionSpec<
+              "longjmp",
+              RetValSpec<VoidType>,
+              [ArgSpec<JmpBuf>, ArgSpec<IntType>]
+          >,
+      ]
+  >;
+
   let Headers = [
     Assert,
     CType,
@@ -998,6 +1024,7 @@ def StdC : StandardSpec<"stdc"> {
     StdIO,
     StdLib,
     IntTypes,
+    SetJmp,
     Signal,
     Threads,
     Time,

diff  --git a/libc/src/CMakeLists.txt b/libc/src/CMakeLists.txt
index 63a7258064dcc..eac68d3187ca0 100644
--- a/libc/src/CMakeLists.txt
+++ b/libc/src/CMakeLists.txt
@@ -26,6 +26,7 @@ endif()
 # The signal API is currently disabled as signal.h is incorrect.
 # since assert uses the signal API, we disable assert also.
 # add_subdirectory(assert)
+add_subdirectory(setjmp)
 add_subdirectory(signal)
 add_subdirectory(spawn)
 add_subdirectory(threads)

diff  --git a/libc/src/setjmp/CMakeLists.txt b/libc/src/setjmp/CMakeLists.txt
new file mode 100644
index 0000000000000..5b79155cb4f05
--- /dev/null
+++ b/libc/src/setjmp/CMakeLists.txt
@@ -0,0 +1,24 @@
+add_entrypoint_object(
+  setjmp
+  SRCS
+    setjmp.cpp
+  HDRS
+    setjmp_impl.h
+  COMPILE_OPTIONS
+    -O3 # We do not want any local variables in setjmp
+    -fno-omit-frame-pointer # The implementation assumes frame pointer on to the stack
+  DEPENDS
+    libc.include.setjmp
+)
+
+add_entrypoint_object(
+  longjmp
+  SRCS
+    longjmp.cpp
+  HDRS
+    longjmp.h
+  COMPILE_OPTIONS
+    -O3 # We do not want any local variables in longjmp
+  DEPENDS
+    libc.include.setjmp
+)

diff  --git a/libc/src/setjmp/longjmp.cpp b/libc/src/setjmp/longjmp.cpp
new file mode 100644
index 0000000000000..9d2d783a536fc
--- /dev/null
+++ b/libc/src/setjmp/longjmp.cpp
@@ -0,0 +1,46 @@
+//===-- Implementation of longjmp -----------------------------------------===//
+//
+// 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/setjmp/longjmp.h"
+#include "src/__support/architectures.h"
+#include "src/__support/common.h"
+
+#include <setjmp.h>
+
+namespace __llvm_libc {
+
+LLVM_LIBC_FUNCTION(void, longjmp, (__jmp_buf * buf, int val)) {
+#ifdef LLVM_LIBC_ARCH_X86_64
+  register __UINT64_TYPE__ rbx __asm__("rbx");
+  register __UINT64_TYPE__ rbp __asm__("rbp");
+  register __UINT64_TYPE__ r12 __asm__("r12");
+  register __UINT64_TYPE__ r13 __asm__("r13");
+  register __UINT64_TYPE__ r14 __asm__("r14");
+  register __UINT64_TYPE__ r15 __asm__("r15");
+  register __UINT64_TYPE__ rsp __asm__("rsp");
+  register __UINT64_TYPE__ rax __asm__("rax");
+
+  // ABI requires that the return value should be stored in rax. So, we store
+  // |val| in rax. Note that this has to happen before we restore the registers
+  // from values in |buf|. Otherwise, once rsp and rbp are updated, we cannot
+  // read |val|.
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=r"(rax) : "m"(val) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=r"(rbx) : "m"(buf->rbx) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=r"(rbp) : "m"(buf->rbp) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=r"(r12) : "m"(buf->r12) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=r"(r13) : "m"(buf->r13) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=r"(r14) : "m"(buf->r14) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=r"(r15) : "m"(buf->r15) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=r"(rsp) : "m"(buf->rsp) :);
+  LIBC_INLINE_ASM("jmp *%0\n\t" : : "m"(buf->rip));
+#else // LLVM_LIBC_ARCH_X86_64
+#error "longjmp implementation not available for the target architecture."
+#endif
+}
+
+} // namespace __llvm_libc

diff  --git a/libc/src/setjmp/longjmp.h b/libc/src/setjmp/longjmp.h
new file mode 100644
index 0000000000000..05d8baaa43913
--- /dev/null
+++ b/libc/src/setjmp/longjmp.h
@@ -0,0 +1,20 @@
+//===-- Implementation header for longjmp -----------------------*- 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_LONGJMP_LONGJMP_H
+#define LLVM_LIBC_SRC_LONGJMP_LONGJMP_H
+
+#include <setjmp.h>
+
+namespace __llvm_libc {
+
+void longjmp(__jmp_buf *buf, int val);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_LONGJMP_LONGJMP_H

diff  --git a/libc/src/setjmp/setjmp.cpp b/libc/src/setjmp/setjmp.cpp
new file mode 100644
index 0000000000000..11d2d7079ad54
--- /dev/null
+++ b/libc/src/setjmp/setjmp.cpp
@@ -0,0 +1,60 @@
+//===-- Implementation of setjmp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/architectures.h"
+#include "src/__support/common.h"
+#include "src/setjmp/setjmp_impl.h"
+
+#include <setjmp.h>
+
+namespace __llvm_libc {
+
+LLVM_LIBC_FUNCTION(int, setjmp, (__jmp_buf * buf)) {
+#ifdef LLVM_LIBC_ARCH_X86_64
+  register __UINT64_TYPE__ rbx __asm__("rbx");
+  register __UINT64_TYPE__ r12 __asm__("r12");
+  register __UINT64_TYPE__ r13 __asm__("r13");
+  register __UINT64_TYPE__ r14 __asm__("r14");
+  register __UINT64_TYPE__ r15 __asm__("r15");
+
+  // We want to store the register values as is. So, we will suppress the
+  // compiler warnings about the uninitialized variables declared above.
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wuninitialized"
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=m"(buf->rbx) : "r"(rbx) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=m"(buf->r12) : "r"(r12) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=m"(buf->r13) : "r"(r13) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=m"(buf->r14) : "r"(r14) :);
+  LIBC_INLINE_ASM("mov %1, %0\n\t" : "=m"(buf->r15) : "r"(r15) :);
+#pragma GCC diagnostic pop
+
+  // We want the rbp of the caller, which is what __builtin_frame_address(1)
+  // should return. But, compilers generate a warning that calling
+  // __builtin_frame_address with non-zero argument is unsafe. So, we use
+  // the knowledge of the x86_64 ABI to fetch the callers rbp. As per the ABI,
+  // the rbp of the caller is pushed on to the stack and then new top is saved
+  // in this function's rbp. So, we fetch it from location at which this
+  // functions's rbp is pointing.
+  buf->rbp = *reinterpret_cast<__UINTPTR_TYPE__ *>(__builtin_frame_address(0));
+
+  // The callers stack address is exactly 2 pointer widths ahead of the current
+  // frame pointer - between the current frame pointer and the rsp of the caller
+  // are the return address (pushed by the x86_64 call instruction) and the
+  // previous stack pointer as required by the x86_64 ABI.
+  // The stack pointer is ahead because the stack grows down on x86_64.
+  buf->rsp = reinterpret_cast<__UINTPTR_TYPE__>(__builtin_frame_address(0)) +
+             sizeof(__UINTPTR_TYPE__) * 2;
+  buf->rip = reinterpret_cast<__UINTPTR_TYPE__>(__builtin_return_address(0));
+#else // LLVM_LIBC_ARCH_X86_64
+#error "setjmp implementation not available for the target architecture."
+#endif
+
+  return 0;
+}
+
+} // namespace __llvm_libc

diff  --git a/libc/src/setjmp/setjmp_impl.h b/libc/src/setjmp/setjmp_impl.h
new file mode 100644
index 0000000000000..6b5115e112a56
--- /dev/null
+++ b/libc/src/setjmp/setjmp_impl.h
@@ -0,0 +1,22 @@
+//===-- Implementation header for setjmp ------------------------*- 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_SETJMP_SETJMP_H
+#define LLVM_LIBC_SRC_SETJMP_SETJMP_H
+
+// This header has the _impl prefix in its name to avoid conflict with the
+// public header setjmp.h which is also included. here.
+#include <setjmp.h>
+
+namespace __llvm_libc {
+
+int setjmp(__jmp_buf *buf);
+
+} // namespace __llvm_libc
+
+#endif // LLVM_LIBC_SRC_SETJMP_SETJMP_H

diff  --git a/libc/test/src/CMakeLists.txt b/libc/test/src/CMakeLists.txt
index 5e954e0d05352..bb5bbb1e5b34d 100644
--- a/libc/test/src/CMakeLists.txt
+++ b/libc/test/src/CMakeLists.txt
@@ -52,6 +52,7 @@ add_subdirectory(dirent)
 # The signal API is currently disabled as signal.h is incorrect.
 # since assert uses the signal API, we disable assert also.
 # add_subdirectory(assert)
+add_subdirectory(setjmp)
 add_subdirectory(signal)
 add_subdirectory(spawn)
 add_subdirectory(time)

diff  --git a/libc/test/src/setjmp/CMakeLists.txt b/libc/test/src/setjmp/CMakeLists.txt
new file mode 100644
index 0000000000000..b8f261d99856f
--- /dev/null
+++ b/libc/test/src/setjmp/CMakeLists.txt
@@ -0,0 +1,13 @@
+add_libc_testsuite(libc_setjmp_unittests)
+
+add_libc_unittest(
+  setjmp_test
+  SUITE
+    libc_setjmp_unittests
+  SRCS
+    setjmp_test.cpp
+  DEPENDS
+    libc.include.setjmp
+    libc.src.setjmp.longjmp
+    libc.src.setjmp.setjmp
+)

diff  --git a/libc/test/src/setjmp/setjmp_test.cpp b/libc/test/src/setjmp/setjmp_test.cpp
new file mode 100644
index 0000000000000..6dd1a22b8b2a9
--- /dev/null
+++ b/libc/test/src/setjmp/setjmp_test.cpp
@@ -0,0 +1,30 @@
+//===-- Unittests for setjmp and longjmp ----------------------------------===//
+//
+// 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/setjmp/longjmp.h"
+#include "src/setjmp/setjmp_impl.h"
+#include "utils/UnitTest/Test.h"
+
+jmp_buf buf;
+constexpr int MAX_LOOP = 123;
+
+void jump_back(int n) {
+  __llvm_libc::longjmp(buf, n); // Will return |n| out of setjmp
+}
+
+TEST(LlvmLibcSetJmpTest, SetAndJumpBack) {
+  // Local variables in setjmp scope should be declared volatile.
+  volatile int n = 0;
+  // The first time setjmp is called, it should return 0.
+  // Subsequent calls will return the value passed to jump_back below.
+  if (__llvm_libc::setjmp(buf) <= MAX_LOOP) {
+    ++n;
+    jump_back(n);
+  }
+  ASSERT_EQ(n, MAX_LOOP + 1);
+}


        


More information about the libc-commits mailing list