libcommunism
Userspace cooperative threading library
SetJmp.cpp
Go to the documentation of this file.
1 
6 #include "SetJmp.h"
7 #include "CothreadPrivate.h"
8 
9 #include <atomic>
10 #include <csetjmp>
11 #include <cstddef>
12 #include <cstdlib>
13 #include <cstring>
14 #include <mutex>
15 #include <stdexcept>
16 #include <system_error>
17 
18 #include <signal.h>
19 
20 using namespace libcommunism;
21 using namespace libcommunism::internal;
22 
23 static_assert(sizeof(sigjmp_buf) < (SetJmp::kMainStackSize * sizeof(uintptr_t)),
24  "main stack size is too small for sigjmp_buf!");
25 
26 thread_local std::array<uintptr_t, SetJmp::kMainStackSize> SetJmp::gMainStack;
27 
28 SetJmp::EntryContext *SetJmp::gCurrentlyPreparing{nullptr};
29 std::mutex SetJmp::gSignalLock;
30 
36 SetJmp::SetJmp(const Entry &entry, const size_t stackSize) : CothreadImpl(entry, stackSize) {
37  void *buf{nullptr};
38 
39  // round down stack size to ensure it's aligned before allocating it
40  auto allocSize = stackSize & ~(SetJmp::kStackAlignment - 1);
41  allocSize = allocSize ? allocSize : SetJmp::kDefaultStackSize;
42 
43  // then add space for ucontext
44  allocSize += sizeof(sigjmp_buf);
45  if(allocSize % SetJmp::kStackAlignment) {
46  allocSize += SetJmp::kStackAlignment - (allocSize % SetJmp::kStackAlignment);
47  }
48 
49  // and allocate it
50 #ifdef _WIN32
51  buf = _aligned_malloc(allocSize, SetJmp::kStackAlignment);
52 #else
53  int err{0};
54  err = posix_memalign(&buf, SetJmp::kStackAlignment, allocSize);
55  if(err) {
56  throw std::runtime_error("posix_memalign() failed");
57  }
58 #endif
59  if(!buf) {
60  throw std::runtime_error("failed to allocate stack");
61  }
62 
63  // create it as if we had provided the memory in the first place
64  this->stack = {reinterpret_cast<uintptr_t *>(buf), allocSize / sizeof(uintptr_t)};
65  this->ownsStack = true;
66 
67  Prepare(this, entry);
68 }
69 
73 SetJmp::SetJmp(const Entry &entry, std::span<uintptr_t> stack) : CothreadImpl(entry, stack) {
74  Prepare(this, entry);
75 }
76 
81  if(this->ownsStack) {
82 #ifdef _WIN32
83  _aligned_free(this->stack.data());
84 #else
85  free(this->stack.data());
86 #endif
87  }
88 }
89 
94  auto from = static_cast<SetJmp *>(_from);
95 
96  if(!sigsetjmp(*SetJmp::JmpBufFor(from), 0)) {
97  std::atomic_thread_fence(std::memory_order_release);
98  siglongjmp(*SetJmp::JmpBufFor(this), 1);
99  }
100 }
101 
102 
103 
111  return new SetJmp(SetJmp::gMainStack);
112 }
113 
120 void SetJmp::InvokeCothreadDidReturnHandler(Cothread *from) {
121  gReturnHandler(from);
122  std::terminate();
123 }
124 
137 void SetJmp::Prepare(SetJmp *thread, const Cothread::Entry &entry) {
138  int err{0};
139  struct sigaction handler, oldHandler;
140  stack_t stack{}, oldStack{};
141 
142  auto jbuf = JmpBufFor(thread);
143  memset(jbuf, 0, sizeof(*jbuf));
144 
145  // prepare the signal handling stack
146  auto offset = sizeof(sigjmp_buf);
147  if(offset % kStackAlignment) {
148  offset += kStackAlignment - (offset % kStackAlignment);
149  }
150 
151  stack.ss_sp = reinterpret_cast<std::byte *>(thread->stack.data()) + offset;
152  stack.ss_size = (thread->stack.size() * sizeof(uintptr_t)) - offset;
153 
154  auto info = new EntryContext( thread, entry);
155  if(!info) throw std::runtime_error("Failed to allocate context");
156 
157  // listen man you're just gonna have to trust me on this one
158  try {
159  std::lock_guard<std::mutex> lock(gSignalLock);
160 
161  err = sigaltstack(&stack, &oldStack);
162  if(err) {
163  throw std::system_error(errno, std::generic_category(), "sigaltstack");
164  }
165 
166  gCurrentlyPreparing = info;
167  std::atomic_thread_fence(std::memory_order_release);
168 
169  handler.sa_handler = SignalHandlerSetupThunk;
170  handler.sa_flags = SA_ONSTACK;
171  sigemptyset(&handler.sa_mask);
172 
173  err = sigaction(SIGUSR1, &handler, &oldHandler);
174  if(err) {
175  throw std::system_error(errno, std::generic_category(), "sigaction");
176  }
177  err = raise(SIGUSR1);
178  if(err) {
179  throw std::system_error(errno, std::generic_category(), "raise");
180  }
181  sigaltstack(&oldStack, nullptr);
182  sigaction(SIGUSR1, &oldHandler, nullptr);
183  } catch(const std::exception &) {
184  delete info;
185  throw;
186  }
187 }
188 
200 void SetJmp::SignalHandlerSetupThunk(int signal) {
201  (void) signal;
202 
203  auto ctx = gCurrentlyPreparing;
204  if(sigsetjmp(*JmpBufFor(ctx->impl), 0)) {
205  ctx->entry();
206  InvokeCothreadDidReturnHandler(Cothread::Current());
207  }
208 }
Instance of a single cooperative thread.
Definition: Cothread.h:24
std::function< void()> Entry
Type alias for an entry point of a cothread.
Definition: Cothread.h:27
static Cothread * Current()
Definition: Cothread.cpp:117
Context switching utilizing the C library setjmp() and longjmp() methods.
Definition: SetJmp.h:25
static constexpr const size_t kStackAlignment
Definition: SetJmp.h:82
SetJmp(const Entry &entry, const size_t stackSize=0)
Definition: SetJmp.cpp:36
void switchTo(CothreadImpl *from) override
Definition: SetJmp.cpp:93
static constexpr const size_t kDefaultStackSize
Definition: SetJmp.h:96
static constexpr const size_t kMainStackSize
Definition: SetJmp.h:90
Implementation details (including architecture/platform specific code) for the library.
Definition: Common.h:11
std::function< void(libcommunism::Cothread *)> gReturnHandler
Definition: Cothread.cpp:72
Main namespace for the libcommunism library.
Definition: Common.h:11
CothreadImpl * AllocKernelThreadWrapper()
Definition: Common.cpp:134
Abstract interface for a platform implementation of cothreads.
Definition: CothreadImpl.h:18
std::span< uintptr_t > stack
Stack used by this cothread, if any.
Definition: CothreadImpl.h:88