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; 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, + [ArgSpec, ArgSpec] + >, + FunctionSpec< + "setjmp", + RetValSpec, + [ArgSpec] + >, + FunctionSpec< + "longjmp", + RetValSpec, + [ArgSpec, ArgSpec] + >, + ] + >; + 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 + +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 + +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 + +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 + +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); +}