libcommunism
Userspace cooperative threading library
Common.cpp
Go to the documentation of this file.
1 
4 #include "Common.h"
5 #include "CothreadPrivate.h"
6 
7 #include "Fastcall.S"
8 
9 #include <algorithm>
10 #include <array>
11 #include <cstddef>
12 #include <exception>
13 #include <functional>
14 #include <memory>
15 #include <stdexcept>
16 #include <thread>
17 
18 using namespace libcommunism;
19 using namespace libcommunism::internal;
20 
26 thread_local std::array<uintptr_t, x86::kMainStackSize> x86::gMainStack;
27 
28 
29 
40 x86::x86(const Entry &entry, const size_t stackSize) : CothreadImpl(entry, stackSize) {
41  void *buf{nullptr};
42 
43  // round down stack size to ensure it's aligned before allocating it
44  auto allocSize = stackSize & ~(kStackAlignment - 1);
45  allocSize = allocSize ? allocSize : kDefaultStackSize;
46 
47  buf = AllocStack(allocSize);
48 
49  // create it as if we had provided the memory in the first place
50  this->stack = {reinterpret_cast<uintptr_t *>(buf), allocSize / sizeof(uintptr_t)};
51  this->ownsStack = true;
52 
53  Prepare(this, entry);
54 }
55 
64 x86::x86(const Entry &entry, std::span<uintptr_t> stack) : CothreadImpl(entry, stack) {
65  x86::ValidateStackSize(stack.size() * sizeof(uintptr_t));
66  Prepare(this, entry);
67 }
68 
73  if(this->ownsStack) {
74  x86::DeallocStack(this->stack.data());
75  }
76 }
77 
79  x86::Switch(static_cast<x86 *>(from), this);
80 }
81 
89 void x86::ValidateStackSize(const size_t size) {
90  if(!size) throw std::runtime_error("Size may not be nil");
91  if(size % kStackAlignment) throw std::runtime_error("Stack is misaligned");
92 }
93 
104 void* x86::AllocStack(const size_t bytes) {
105  void *buf{nullptr};
106 #ifdef _WIN32
107  buf = _aligned_malloc(bytes, kStackAlignment);
108 #else
109  int err{0};
110  err = posix_memalign(&buf, kStackAlignment, bytes);
111  if(err) {
112  throw std::runtime_error("posix_memalign() failed");
113  }
114 #endif
115  if(!buf) {
116  throw std::runtime_error("failed to allocate stack");
117  }
118  return buf;
119 }
120 
128 void x86::DeallocStack(void* stack) {
129 #ifdef _WIN32
130  _aligned_free(stack);
131 #else
132  free(stack);
133 #endif
134 }
135 
140 void x86::CothreadReturned() {
142 }
143 
149 void x86::DereferenceCallInfo(CallInfo *info) {
150  info->entry();
151  delete info;
152 
153  // invoke the return handler; this shouldn't return
154  CothreadReturned();
155  std::terminate();
156 }
157 
165 void x86::Prepare(x86 *wrap, const Entry &entry) {
166 #ifndef _MSC_VER
167 #pragma GCC diagnostic push
168 #pragma GCC diagnostic ignored "-Winvalid-offsetof"
169 #endif
170  static_assert(offsetof(x86, stackTop) == COTHREAD_OFF_CONTEXT_TOP, "cothread stack top is invalid");
171 #ifndef _MSC_VER
172 #pragma GCC diagnostic pop
173 #endif
174 
175  // build the context structure we pass to our entry point stub
176  auto info = new CallInfo{entry};
177  if(!info) throw std::runtime_error("Failed to allocate call info");
178 
179  // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast)
180  // prepare some space for a stack frame
181  auto &stackBuf = wrap->stack;
182  auto stackFrame = reinterpret_cast<std::byte *>(stackBuf.data());
183  stackFrame += ((stackBuf.size()*sizeof(uintptr_t)) & ~(0x10-1))
184  - (sizeof(uintptr_t) * (4 + kNumSavedRegisters));
185  auto stack = reinterpret_cast<uintptr_t *>(stackFrame);
186 
187  // if main returns (it shouldn't, we call std:;terminate) just crash
188  *--stack = 0;
189 
190  // and then jump to the stub that calls the entry point
191  *--stack = reinterpret_cast<uintptr_t>(info);
192  *--stack = reinterpret_cast<uintptr_t>(&x86::DereferenceCallInfo);
193  *--stack = reinterpret_cast<uintptr_t>(&x86::JumpToEntry);
194  // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast)
195 
196  // clear the region that registers are written into (so they're all zeroed)
197  for(size_t i = 0; i < kNumSavedRegisters; i++) {
198  *--stack = 0;
199  }
200 
201  // restore the stack pointer to the correct point
202  wrap->stackTop = stack;
203 }
204 
205 
213  return new x86(x86::gMainStack);
214 }
static Cothread * Current()
Definition: Cothread.cpp:117
Architecture specific methods for working with cothreads on x86 systems.
Definition: Common.h:29
static constexpr const size_t kDefaultStackSize
Definition: Common.h:103
void switchTo(CothreadImpl *from) override
Definition: Common.cpp:78
static const size_t kNumSavedRegisters
Definition: Common.h:82
static constexpr const size_t kStackAlignment
Definition: Common.h:97
x86(const Entry &entry, const size_t stackSize=0)
Definition: Common.cpp:40
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