libcommunism
Userspace cooperative threading library
UContext.cpp
Go to the documentation of this file.
1 
6 #include "UContext.h"
7 #include "CothreadPrivate.h"
8 
9 #include <cstddef>
10 #include <cstdlib>
11 #include <mutex>
12 #include <stdexcept>
13 
14 // required to get the "deprecated" ucontext sources
15 #define _XOPEN_SOURCE
16 #include <ucontext.h>
17 
18 using namespace libcommunism;
19 using namespace libcommunism::internal;
20 
21 // Validate an ucontext fits in the buffer
22 static_assert(sizeof(ucontext_t) < (UContext::kMainStackSize * sizeof(uintptr_t)),
23  "main stack size is too small for ucontext!");
24 
25 thread_local std::array<uintptr_t, UContext::kMainStackSize> UContext::gMainStack;
26 
27 std::unordered_map<int, std::unique_ptr<UContext::Context>> UContext::gContextInfo;
28 std::mutex UContext::gContextInfoLock;
29 int UContext::gContextNextId{0};
30 
31 
37 UContext::UContext(const Entry &entry, const size_t stackSize) : CothreadImpl(entry, stackSize) {
38  void *buf{nullptr};
39 
40  // round down stack size to ensure it's aligned before allocating it
41  auto allocSize = stackSize & ~(UContext::kStackAlignment - 1);
42  allocSize = allocSize ? allocSize : UContext::kDefaultStackSize;
43 
44  // then add space for ucontext
45  allocSize += sizeof(ucontext_t);
46  if(allocSize % kStackAlignment) {
47  allocSize += kStackAlignment - (allocSize % kStackAlignment);
48  }
49 
50  // and allocate it
51 #ifdef _WIN32
52  buf = _aligned_malloc(allocSize, UContext::kStackAlignment);
53 #else
54  int err{0};
55  err = posix_memalign(&buf, UContext::kStackAlignment, allocSize);
56  if(err) {
57  throw std::runtime_error("posix_memalign() failed");
58  }
59 #endif
60  if(!buf) {
61  throw std::runtime_error("failed to allocate stack");
62  }
63 
64  // create it as if we had provided the memory in the first place
65  this->stack = {reinterpret_cast<uintptr_t *>(buf), allocSize / sizeof(uintptr_t)};
66  this->ownsStack = true;
67 
68  Prepare(this, entry);
69 }
70 
74 UContext::UContext(const Entry &entry, std::span<uintptr_t> stack) : CothreadImpl(entry, stack) {
75  Prepare(this, entry);
76 }
77 
82  if(this->ownsStack) {
83 #ifdef _WIN32
84  _aligned_free(this->stack.data());
85 #else
86  free(this->stack.data());
87 #endif
88  }
89 }
90 
91 
92 
93 // XXX: We need to disable deprecation warnings for getcontext() and friends on macOS, BSD
94 #pragma clang diagnostic push
95 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
96 
105 void UContext::Prepare(UContext *thread, const UContext::Entry &entry) {
106  // build the context structure we pass to our "fake" entry point
107  auto info = std::make_unique<Context>(entry);
108  if(!info) throw std::runtime_error("Failed to allocate context");
109 
110  // get its ucontext_t and prepare it
111  auto uctx = ContextFor(thread);
112  memset(uctx, 0, sizeof(*uctx));
113 
114  if(getcontext(uctx)) {
115  throw std::runtime_error("getcontext() failed");
116  }
117 
118  // set its stack
119  auto offset = sizeof(ucontext_t);
120  if(offset % kStackAlignment) {
121  offset += kStackAlignment - (offset % kStackAlignment);
122  }
123 
124  uctx->uc_stack.ss_sp = reinterpret_cast<std::byte *>(thread->stack.data()) + offset;
125  uctx->uc_stack.ss_size = (thread->stack.size() * sizeof(uintptr_t)) - offset;
126 
127  // store the context in the spicy map
128  int id{0};
129  {
130  std::lock_guard<std::mutex> lg(gContextInfoLock);
131  do{
132  id = ++gContextNextId;
133  } while(!id);
134  gContextInfo.emplace(id, std::move(info));
135  }
136 
137  // fill in the context to invoke the helper method
138  // this is disgusting but it's C. lol
139  makecontext(uctx, reinterpret_cast<void(*)()>(&EntryStub), 1, id);
140 }
141 
148 void UContext::InvokeCothreadDidReturnHandler(Cothread *from) {
149  gReturnHandler(from);
150  std::terminate();
151 }
152 
158 void UContext::EntryStub(int id) {
159  // extract info
160  std::unique_ptr<Context> info;
161  {
162  std::lock_guard<std::mutex> lg(gContextInfoLock);
163  info = std::move(gContextInfo.at(id));
164  gContextInfo.erase(id);
165  }
166 
167  // invoke
168  info->entry();
169 
170  // call the return handler
171  info.reset();
172  UContext::InvokeCothreadDidReturnHandler(Cothread::Current());
173 }
174 
181  swapcontext(UContext::ContextFor(from), UContext::ContextFor(this));
182 }
183 
184 #pragma clang diagnostic pop
185 
193  return new UContext(UContext::gMainStack);
194 }
195 
Instance of a single cooperative thread.
Definition: Cothread.h:24
static Cothread * Current()
Definition: Cothread.cpp:117
Implementation of context switching that uses the C library's setcontext() methods.
Definition: UContext.h:33
UContext(const Entry &entry, const size_t stackSize=0)
Definition: UContext.cpp:37
static constexpr const size_t kStackAlignment
Definition: UContext.h:86
void switchTo(CothreadImpl *from) override
Definition: UContext.cpp:180
static constexpr const size_t kDefaultStackSize
Definition: UContext.h:100
static constexpr const size_t kMainStackSize
Definition: UContext.h:94
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