use libco on non-Win32 systems for guest OSThread scheduling

This commit is contained in:
theofficialgman
2026-08-25 20:38:41 -04:00
parent f63d0c84b5
commit a0b54b874b
19 changed files with 9040 additions and 27 deletions
+11
View File
@@ -103,6 +103,17 @@ Source: <https://github.com/ToruNiina/toml11/tree/v4.4.0>. Full license text:
Copyright (c) Antoine Aubry and contributors.
Referenced by `translator/src/Translator.Core`. Source: <https://github.com/aaubry/YamlDotNet>
### libco - ISC (valgrind.h: BSD-style)
Copyright byuu and the higan team.
Non-Windows builds use libco's symmetric stackful coroutines in place of Win32 Fibers for guest
OSThread scheduling (`runtime/src/fiber_manager.cpp`). Vendored in full (all non-Windows
CPU-architecture backends - amd64, x86, arm, aarch64, ppc, ppc64v2, plus the portable sjlj
fallback - though this project's x86_64-only target only ever compiles amd64.c) in
`runtime/third_party/libco` from commit `e18e09d634d612a01781168ad4d76be10a7e3bad`.
Source: <https://github.com/higan-emu/libco>. Full license text:
`runtime/third_party/libco/LICENSE`.
---
## Fetched at build time and redistributed in release builds
+17
View File
@@ -49,6 +49,23 @@ target_include_directories(mkw_pugixml PUBLIC third_party/pugixml)
target_compile_features(mkw_pugixml PUBLIC cxx_std_17)
set_target_properties(mkw_pugixml PROPERTIES UNITY_BUILD OFF)
# Non-Windows guest-fiber scheduling (runtime/src/fiber_manager.cpp) needs a symmetric
# stackful-coroutine primitive to stand in for Win32 Fibers. libco's co_switch() transfers
# directly to any other created coroutine, matching SwitchToFiber's semantics exactly (unlike
# asymmetric resume/yield coroutine libraries, which would need every call site restructured).
# Vendored from upstream (higan-emu/libco @ e18e09d, 2019-10-16, ISC license; valgrind.h is
# separately BSD-style licensed, see third_party/libco/LICENSE) - all of libco's non-Windows
# CPU-architecture backends are kept, even though libco.c's own preprocessor dispatch
# (__amd64__/__i386__/__arm__/__aarch64__/etc.) only ever selects amd64.c for this project's
# x86_64-only target (see the platform/arch check above). Windows keeps using native Fibers
# untouched, so this target is never built there.
if(NOT WIN32)
add_library(mkw_libco STATIC third_party/libco/libco.c)
add_library(mkw::libco ALIAS mkw_libco)
target_include_directories(mkw_libco PUBLIC third_party/libco)
set_target_properties(mkw_libco PROPERTIES UNITY_BUILD OFF)
endif()
# Runtime configuration is real TOML, parsed by toml11 rather than a project-
# specific line parser. Keep it header-only and vendored so disconnected release
# builds have exactly the same parser as developer builds.
+2
View File
@@ -79,6 +79,8 @@ target_link_libraries(mkw_runtime_common PRIVATE
target_link_libraries(mkw_runtime_common PRIVATE mkw::pugixml mkw::toml11 mkw::cryptopp)
if(WIN32)
target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp)
else()
target_link_libraries(mkw_runtime_common PRIVATE mkw::libco)
endif()
if(MKW_CPPWINRT_INCLUDE_DIR)
if(NOT EXISTS "${MKW_CPPWINRT_INCLUDE_DIR}/winrt/base.h")
+13 -1
View File
@@ -102,12 +102,24 @@ private:
static void CALLBACK FiberProc(void* param);
#else
static void FiberProc(void* param);
// libco's co_create() entry points take no argument (unlike CreateFiber's FiberProc(void*)),
// so this trampoline reads the guest thread address staged by CreateGuestFiber() and forwards
// into the (platform-neutral-bodied) FiberProc above. See fiber_manager.cpp.
static void FiberProcTrampoline();
#endif
// Switch from whichever fiber is currently active straight to the scheduler fiber, without
// the SwitchToThread bookkeeping (CPU context save/restore, s_currentGuestThread). Used for
// in-fiber yields that aren't a real guest thread switch: waiting out the EGG::Thread::start
// deferral loop, and returning control on natural thread exit.
static void SwitchToScheduler();
// Internal state
static std::mutex s_mutex;
static std::unordered_map<uint32_t, GuestFiber> s_fibers;
static std::vector<void*> s_fibersPendingDelete;
// The scheduler's own "fiber": a Windows HFIBER, or (non-Windows) libco's cothread_t for
// whichever native call stack first called GuestFiberManager::Initialize() - both are
// plain void* handles, so one field serves both platforms.
static void* s_schedulerFiber;
static uint32_t s_currentGuestThread;
static bool s_initialized;
+106 -26
View File
@@ -13,8 +13,24 @@
#include <iomanip>
#include <sstream>
#if !defined(_WIN32)
#include "libco.h"
#endif
namespace Fiber {
#if !defined(_WIN32)
namespace {
// libco's co_create() entry points take no argument, unlike CreateFiber(size, FiberProc, param).
// CreateGuestFiber() stages the guest thread address here immediately before the first co_switch
// into a freshly created cothread; FiberProcTrampoline reads it exactly once, at the top of the
// fiber's very first activation. Safe because guest fibers are strictly cooperative on a single
// OS thread: nothing else can run (and so nothing else can overwrite this) between the staging
// write and the trampoline's read of it.
thread_local uint32_t s_pendingFiberArg = 0;
} // namespace
#endif
std::mutex GuestFiberManager::s_mutex;
std::unordered_map<uint32_t, GuestFiber> GuestFiberManager::s_fibers;
std::vector<void*> GuestFiberManager::s_fibersPendingDelete;
@@ -24,18 +40,25 @@ bool GuestFiberManager::s_initialized = false;
thread_local CpuContext* GuestFiberManager::s_cpuContext = nullptr;
void GuestFiberManager::PurgePendingFibers() {
#if defined(_WIN32)
std::vector<void*> toDelete;
{
std::lock_guard<std::mutex> lock(s_mutex);
toDelete.swap(s_fibersPendingDelete);
}
#if defined(_WIN32)
const void* current = GetCurrentFiber();
for (void* f : toDelete) {
if (f && f != current) {
DeleteFiber(f);
}
}
#else
const void* current = co_active();
for (void* f : toDelete) {
if (f && f != current) {
co_delete(static_cast<cothread_t>(f));
}
}
#endif
}
@@ -204,10 +227,12 @@ void GuestFiberManager::Initialize() {
}
#else
RT_LOG(RT_TAG_OS) << "WARNING: Fiber support not available on this platform!" << std::endl;
s_schedulerFiber = nullptr;
// co_active() returns a handle for whichever native stack is currently running, creating one
// on first call if needed - the libco analogue of ConvertThreadToFiber(nullptr): it converts
// this call's own stack into a switchable target without altering control flow.
s_schedulerFiber = co_active();
#endif
s_currentGuestThread = 0;
s_initialized = true;
}
@@ -223,14 +248,25 @@ void GuestFiberManager::Shutdown() {
}
}
s_fibers.clear();
// Convert scheduler fiber back to thread
if (s_schedulerFiber) {
ConvertFiberToThread();
s_schedulerFiber = nullptr;
}
#else
for (auto& [addr, fiber] : s_fibers) {
if (fiber.fiber && !fiber.isSchedulerFiber) {
co_delete(static_cast<cothread_t>(fiber.fiber));
fiber.fiber = nullptr;
}
}
s_fibers.clear();
// Unlike ConvertFiberToThread, libco has no "undo" for co_active(): the scheduler's own
// stack was never separately allocated, so there is nothing to release here.
s_schedulerFiber = nullptr;
#endif
s_initialized = false;
}
@@ -250,12 +286,14 @@ bool GuestFiberManager::CreateGuestFiber(uint32_t guestThreadAddr, uint32_t entr
// Check if fiber already exists for this thread - if so, reset it
auto existingIt = s_fibers.find(guestThreadAddr);
if (existingIt != s_fibers.end()) {
#if defined(_WIN32)
// Delete the old fiber if it exists and is not the scheduler fiber
if (existingIt->second.fiber && !existingIt->second.isSchedulerFiber) {
#if defined(_WIN32)
DeleteFiber(existingIt->second.fiber);
}
#else
co_delete(static_cast<cothread_t>(existingIt->second.fiber));
#endif
}
s_fibers.erase(existingIt);
}
@@ -288,9 +326,18 @@ bool GuestFiberManager::CreateGuestFiber(uint32_t guestThreadAddr, uint32_t entr
return false;
}
#else
gf.fiber = nullptr;
// libco's co_create() entry point takes no argument; SwitchToThread() stages guestThreadAddr
// into s_pendingFiberArg immediately before the co_switch that first activates this handle.
constexpr unsigned int kHostStackSize = 64 * 1024;
gf.fiber = co_create(kHostStackSize, &FiberProcTrampoline);
if (!gf.fiber) {
RT_LOG(RT_TAG_OS) << "co_create failed for thread 0x"
<< std::hex << guestThreadAddr << std::dec << std::endl;
return false;
}
#endif
s_fibers[guestThreadAddr] = gf;
@@ -342,17 +389,27 @@ void GuestFiberManager::ExitGuestThread(uint32_t guestThreadAddr, ThreadState fi
s_currentGuestThread = 0;
}
#if defined(_WIN32)
if (it->second.fiber && !it->second.isSchedulerFiber) {
#if defined(_WIN32)
const void* current = GetCurrentFiber();
if (it->second.fiber == current) {
s_fibersPendingDelete.push_back(it->second.fiber);
} else {
DeleteFiber(it->second.fiber);
}
#else
const void* current = co_active();
if (it->second.fiber == current) {
// Deleting the coroutine we're currently executing on would free the very stack
// this call is running on; defer it (PurgePendingFibers) until some other fiber is
// active, exactly like the Windows branch above.
s_fibersPendingDelete.push_back(it->second.fiber);
} else {
co_delete(static_cast<cothread_t>(it->second.fiber));
}
#endif
it->second.fiber = nullptr;
}
#endif
}
void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu) {
@@ -415,10 +472,13 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu
// Store CPU context pointer for the target fiber to use
s_cpuContext = cpu;
#if defined(_WIN32)
// Check if we're already on the target fiber (e.g., switching to main thread
// when we're already on the scheduler fiber)
#if defined(_WIN32)
void* currentFiber = GetCurrentFiber();
#else
void* currentFiber = co_active();
#endif
if (currentFiber == fiberHandle) {
// Already executing on the target host fiber. This is common for the
// default guest thread, which also owns the scheduler fiber. Keep the
@@ -426,7 +486,7 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu
// from before the guest thread slept.
return;
}
if (cpu && haveTargetContext) {
*cpu = targetContext;
// FPSCR travels with the guest-thread context, and its NI bit is
@@ -436,8 +496,16 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu
}
// Switch to the target fiber (the target fiber will load its own context)
#if defined(_WIN32)
SwitchToFiber(fiberHandle);
#else
// Staged for FiberProcTrampoline's first (and only) read; a no-op for a fiber that has
// already started, since resuming it re-enters mid-function rather than through the
// trampoline's entry point.
s_pendingFiberArg = guestThreadAddr;
co_switch(static_cast<cothread_t>(fiberHandle));
#endif
// When we return here, the fiber that issued SwitchToThread has resumed.
// That does not automatically mean the previous guest thread became runnable
// again; a different thread may simply have yielded back to the scheduler.
@@ -476,7 +544,6 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu
s_currentGuestThread = 0;
}
}
#endif
}
uint32_t GuestFiberManager::GetCurrentGuestThread() {
@@ -551,6 +618,14 @@ void GuestFiberManager::ProcessTimerEvents(CpuContext* cpu) {
}
}
void GuestFiberManager::SwitchToScheduler() {
#if defined(_WIN32)
SwitchToFiber(s_schedulerFiber);
#else
co_switch(static_cast<cothread_t>(s_schedulerFiber));
#endif
}
#if defined(_WIN32)
void CALLBACK GuestFiberManager::FiberProc(void* param)
#else
@@ -558,9 +633,7 @@ void GuestFiberManager::FiberProc(void* param)
#endif
{
uint32_t guestThreadAddr = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(param));
#if defined(_WIN32)
// Get our fiber info
GuestFiber* fiber = nullptr;
uint32_t entryPoint = 0;
@@ -571,7 +644,7 @@ void GuestFiberManager::FiberProc(void* param)
auto it = s_fibers.find(guestThreadAddr);
if (it == s_fibers.end()) {
RT_LOG(RT_TAG_OS) << "FiberProc: fiber not found!" << std::endl;
SwitchToFiber(s_schedulerFiber);
SwitchToScheduler();
return;
}
fiber = &it->second;
@@ -634,7 +707,7 @@ void GuestFiberManager::FiberProc(void* param)
<< ", fn=0x" << startFn << ") after retries; continuing anyway." << std::dec << std::endl;
break;
}
SwitchToFiber(s_schedulerFiber);
SwitchToScheduler();
}
// The deferral loop above yields to the scheduler and therefore can resume
@@ -691,11 +764,18 @@ void GuestFiberManager::FiberProc(void* param)
}
// Return to scheduler
SwitchToFiber(s_schedulerFiber);
#else
(void)guestThreadAddr;
RT_LOG(RT_TAG_OS) << "Fibers not supported on this platform!" << std::endl;
#endif
SwitchToScheduler();
}
#if !defined(_WIN32)
void GuestFiberManager::FiberProcTrampoline() {
const uint32_t guestThreadAddr = s_pendingFiberArg;
FiberProc(reinterpret_cast<void*>(static_cast<uintptr_t>(guestThreadAddr)));
// FiberProc always calls SwitchToScheduler() on every exit path and never falls off its own
// end; this is only a safety net in case that ever changes; falling off co_create's entry
// function is otherwise undefined behavior (libco's own crash() fallback aborts instead).
SwitchToScheduler();
}
#endif
} // namespace Fiber
+9
View File
@@ -0,0 +1,9 @@
ISC License (ISC)
Copyright byuu and the higan team
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
The above applies to all files in this project except valgrind.h which is licensed under a BSD-style license. See the license text and copyright notice contained within that file.
+112
View File
@@ -0,0 +1,112 @@
#define LIBCO_C
#include "libco.h"
#include "settings.h"
#include <stdint.h>
#ifdef LIBCO_MPROTECT
#include <unistd.h>
#include <sys/mman.h>
#endif
#include "valgrind.h"
#ifdef __cplusplus
extern "C" {
#endif
static thread_local unsigned long co_active_buffer[64];
static thread_local cothread_t co_active_handle = 0;
static void (*co_swap)(cothread_t, cothread_t) = 0;
#ifdef LIBCO_MPROTECT
alignas(4096)
#else
section(text)
#endif
static const uint32_t co_swap_function[1024] = {
0x910003f0, /* mov x16,sp */
0xa9007830, /* stp x16,x30,[x1] */
0xa9407810, /* ldp x16,x30,[x0] */
0x9100021f, /* mov sp,x16 */
0xa9015033, /* stp x19,x20,[x1, 16] */
0xa9415013, /* ldp x19,x20,[x0, 16] */
0xa9025835, /* stp x21,x22,[x1, 32] */
0xa9425815, /* ldp x21,x22,[x0, 32] */
0xa9036037, /* stp x23,x24,[x1, 48] */
0xa9436017, /* ldp x23,x24,[x0, 48] */
0xa9046839, /* stp x25,x26,[x1, 64] */
0xa9446819, /* ldp x25,x26,[x0, 64] */
0xa905703b, /* stp x27,x28,[x1, 80] */
0xa945701b, /* ldp x27,x28,[x0, 80] */
0xf900303d, /* str x29, [x1, 96] */
0xf940301d, /* ldr x29, [x0, 96] */
0x6d072428, /* stp d8, d9, [x1,112] */
0x6d472408, /* ldp d8, d9, [x0,112] */
0x6d082c2a, /* stp d10,d11,[x1,128] */
0x6d482c0a, /* ldp d10,d11,[x0,128] */
0x6d09342c, /* stp d12,d13,[x1,144] */
0x6d49340c, /* ldp d12,d13,[x0,144] */
0x6d0a3c2e, /* stp d14,d15,[x1,160] */
0x6d4a3c0e, /* ldp d14,d15,[x0,160] */
0xd61f03c0, /* br x30 */
};
static void co_init(void) {
#ifdef LIBCO_MPROTECT
unsigned long addr = (unsigned long)co_swap_function;
unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE));
unsigned long size = (addr - base) + sizeof co_swap_function;
mprotect((void*)base, size, PROT_READ | PROT_EXEC);
#endif
}
cothread_t co_active(void) {
if(!co_active_handle) co_active_handle = &co_active_buffer;
return co_active_handle;
}
cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) {
unsigned long* handle;
if(!co_swap) {
co_init();
co_swap = (void (*)(cothread_t, cothread_t))co_swap_function;
}
if(!co_active_handle) co_active_handle = &co_active_buffer;
VALGRIND_STACK_REGISTER(memory, memory + size);
if((handle = (unsigned long*)memory)) {
unsigned long stack_top = (unsigned long)handle + size;
unsigned long *p;
stack_top &= ~((unsigned long) 15);
p = (unsigned long*)(stack_top);
handle[0] = (unsigned long)p; /* x16 (stack pointer) */
handle[1] = (unsigned long)entrypoint; /* x30 (link register) */
handle[12] = (unsigned long)p; /* x29 (frame pointer) */
}
return handle;
}
cothread_t co_create(unsigned int size, void (*entrypoint)(void)) {
void* memory = LIBCO_MALLOC(size);
if(!memory) return (cothread_t)0;
return co_derive(memory, size, entrypoint);
}
void co_delete(cothread_t handle) {
LIBCO_FREE(handle);
}
void co_switch(cothread_t handle) {
cothread_t co_previous_handle = co_active_handle;
co_swap(co_active_handle = handle, co_previous_handle);
}
int co_serializable(void) {
return 1;
}
#ifdef __cplusplus
}
#endif
+175
View File
@@ -0,0 +1,175 @@
#define LIBCO_C
#include "libco.h"
#include "settings.h"
#ifdef __cplusplus
extern "C" {
#endif
static thread_local long long co_active_buffer[64];
static thread_local cothread_t co_active_handle = 0;
static void (*co_swap)(cothread_t, cothread_t) = 0;
#ifdef LIBCO_MPROTECT
alignas(4096)
#else
section(text)
#endif
#ifdef _WIN32
/* ABI: Win64 */
static const unsigned char co_swap_function[4096] = {
0x48, 0x89, 0x22, /* mov [rdx],rsp */
0x48, 0x8b, 0x21, /* mov rsp,[rcx] */
0x58, /* pop rax */
0x48, 0x89, 0x6a, 0x08, /* mov [rdx+ 8],rbp */
0x48, 0x89, 0x72, 0x10, /* mov [rdx+16],rsi */
0x48, 0x89, 0x7a, 0x18, /* mov [rdx+24],rdi */
0x48, 0x89, 0x5a, 0x20, /* mov [rdx+32],rbx */
0x4c, 0x89, 0x62, 0x28, /* mov [rdx+40],r12 */
0x4c, 0x89, 0x6a, 0x30, /* mov [rdx+48],r13 */
0x4c, 0x89, 0x72, 0x38, /* mov [rdx+56],r14 */
0x4c, 0x89, 0x7a, 0x40, /* mov [rdx+64],r15 */
#if !defined(LIBCO_NO_SSE)
0x0f, 0x29, 0x72, 0x50, /* movaps [rdx+ 80],xmm6 */
0x0f, 0x29, 0x7a, 0x60, /* movaps [rdx+ 96],xmm7 */
0x44, 0x0f, 0x29, 0x42, 0x70, /* movaps [rdx+112],xmm8 */
0x48, 0x83, 0xc2, 0x70, /* add rdx,112 */
0x44, 0x0f, 0x29, 0x4a, 0x10, /* movaps [rdx+ 16],xmm9 */
0x44, 0x0f, 0x29, 0x52, 0x20, /* movaps [rdx+ 32],xmm10 */
0x44, 0x0f, 0x29, 0x5a, 0x30, /* movaps [rdx+ 48],xmm11 */
0x44, 0x0f, 0x29, 0x62, 0x40, /* movaps [rdx+ 64],xmm12 */
0x44, 0x0f, 0x29, 0x6a, 0x50, /* movaps [rdx+ 80],xmm13 */
0x44, 0x0f, 0x29, 0x72, 0x60, /* movaps [rdx+ 96],xmm14 */
0x44, 0x0f, 0x29, 0x7a, 0x70, /* movaps [rdx+112],xmm15 */
#endif
0x48, 0x8b, 0x69, 0x08, /* mov rbp,[rcx+ 8] */
0x48, 0x8b, 0x71, 0x10, /* mov rsi,[rcx+16] */
0x48, 0x8b, 0x79, 0x18, /* mov rdi,[rcx+24] */
0x48, 0x8b, 0x59, 0x20, /* mov rbx,[rcx+32] */
0x4c, 0x8b, 0x61, 0x28, /* mov r12,[rcx+40] */
0x4c, 0x8b, 0x69, 0x30, /* mov r13,[rcx+48] */
0x4c, 0x8b, 0x71, 0x38, /* mov r14,[rcx+56] */
0x4c, 0x8b, 0x79, 0x40, /* mov r15,[rcx+64] */
#if !defined(LIBCO_NO_SSE)
0x0f, 0x28, 0x71, 0x50, /* movaps xmm6, [rcx+ 80] */
0x0f, 0x28, 0x79, 0x60, /* movaps xmm7, [rcx+ 96] */
0x44, 0x0f, 0x28, 0x41, 0x70, /* movaps xmm8, [rcx+112] */
0x48, 0x83, 0xc1, 0x70, /* add rcx,112 */
0x44, 0x0f, 0x28, 0x49, 0x10, /* movaps xmm9, [rcx+ 16] */
0x44, 0x0f, 0x28, 0x51, 0x20, /* movaps xmm10,[rcx+ 32] */
0x44, 0x0f, 0x28, 0x59, 0x30, /* movaps xmm11,[rcx+ 48] */
0x44, 0x0f, 0x28, 0x61, 0x40, /* movaps xmm12,[rcx+ 64] */
0x44, 0x0f, 0x28, 0x69, 0x50, /* movaps xmm13,[rcx+ 80] */
0x44, 0x0f, 0x28, 0x71, 0x60, /* movaps xmm14,[rcx+ 96] */
0x44, 0x0f, 0x28, 0x79, 0x70, /* movaps xmm15,[rcx+112] */
#endif
0xff, 0xe0, /* jmp rax */
};
/* Valgrind is available on MINGW but not on MSVC. */
#if defined(__GNUC__)
#include "valgrind.h"
#endif
#include <windows.h>
static void co_init(void) {
#ifdef LIBCO_MPROTECT
DWORD old_privileges;
VirtualProtect((void*)co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READ, &old_privileges);
#endif
}
#else
/* ABI: SystemV */
static const unsigned char co_swap_function[4096] = {
0x48, 0x89, 0x26, /* mov [rsi],rsp */
0x48, 0x8b, 0x27, /* mov rsp,[rdi] */
0x58, /* pop rax */
0x48, 0x89, 0x6e, 0x08, /* mov [rsi+ 8],rbp */
0x48, 0x89, 0x5e, 0x10, /* mov [rsi+16],rbx */
0x4c, 0x89, 0x66, 0x18, /* mov [rsi+24],r12 */
0x4c, 0x89, 0x6e, 0x20, /* mov [rsi+32],r13 */
0x4c, 0x89, 0x76, 0x28, /* mov [rsi+40],r14 */
0x4c, 0x89, 0x7e, 0x30, /* mov [rsi+48],r15 */
0x48, 0x8b, 0x6f, 0x08, /* mov rbp,[rdi+ 8] */
0x48, 0x8b, 0x5f, 0x10, /* mov rbx,[rdi+16] */
0x4c, 0x8b, 0x67, 0x18, /* mov r12,[rdi+24] */
0x4c, 0x8b, 0x6f, 0x20, /* mov r13,[rdi+32] */
0x4c, 0x8b, 0x77, 0x28, /* mov r14,[rdi+40] */
0x4c, 0x8b, 0x7f, 0x30, /* mov r15,[rdi+48] */
0xff, 0xe0, /* jmp rax */
};
#include "valgrind.h"
#ifdef LIBCO_MPROTECT
#include <unistd.h>
#include <sys/mman.h>
#endif
static void co_init(void) {
#ifdef LIBCO_MPROTECT
unsigned long long addr = (unsigned long long)co_swap_function;
unsigned long long base = addr - (addr % sysconf(_SC_PAGESIZE));
unsigned long long size = (addr - base) + sizeof co_swap_function;
mprotect((void*)base, size, PROT_READ | PROT_EXEC);
#endif
}
#endif
static void crash(void) {
LIBCO_ASSERT(0); /* called only if cothread_t entrypoint returns */
}
cothread_t co_active(void) {
if(!co_active_handle) co_active_handle = &co_active_buffer;
return co_active_handle;
}
cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) {
cothread_t handle;
if(!co_swap) {
co_init();
co_swap = (void (*)(cothread_t, cothread_t))co_swap_function;
}
if(!co_active_handle) co_active_handle = &co_active_buffer;
#if defined(__VALGRIND_MAJOR__)
VALGRIND_STACK_REGISTER(memory, memory + size);
#endif
if((handle = (cothread_t)memory)) {
unsigned long long stack_top = (unsigned long long)handle + size;
long long *p;
stack_top -= 32;
stack_top &= ~((unsigned long long) 15);
p = (long long*)(stack_top); /* seek to top of stack */
*--p = (long long)crash; /* crash if entrypoint returns */
*--p = (long long)entrypoint; /* start of function */
*(long long*)handle = (long long)p; /* stack pointer */
}
return handle;
}
cothread_t co_create(unsigned int size, void (*entrypoint)(void)) {
void* memory = LIBCO_MALLOC(size);
if(!memory) return (cothread_t)0;
return co_derive(memory, size, entrypoint);
}
void co_delete(cothread_t handle) {
LIBCO_FREE(handle);
}
void co_switch(cothread_t handle) {
register cothread_t co_previous_handle = co_active_handle;
co_swap(co_active_handle = handle, co_previous_handle);
}
int co_serializable(void) {
return 1;
}
#ifdef __cplusplus
}
#endif
+87
View File
@@ -0,0 +1,87 @@
#define LIBCO_C
#include "libco.h"
#include "settings.h"
#include "valgrind.h"
#ifdef LIBCO_MPROTECT
#include <unistd.h>
#include <sys/mman.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
static thread_local unsigned long co_active_buffer[64];
static thread_local cothread_t co_active_handle = 0;
static void (*co_swap)(cothread_t, cothread_t) = 0;
#ifdef LIBCO_MPROTECT
alignas(4096)
#else
section(text)
#endif
static const unsigned long co_swap_function[1024] = {
0xe8a16ff0, /* stmia r1!, {r4-r11,sp,lr} */
0xe8b0aff0, /* ldmia r0!, {r4-r11,sp,pc} */
0xe12fff1e, /* bx lr */
};
static void co_init(void) {
#ifdef LIBCO_MPROTECT
unsigned long addr = (unsigned long)co_swap_function;
unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE));
unsigned long size = (addr - base) + sizeof co_swap_function;
mprotect((void*)base, size, PROT_READ | PROT_EXEC);
#endif
}
cothread_t co_active(void) {
if(!co_active_handle) co_active_handle = &co_active_buffer;
return co_active_handle;
}
cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) {
unsigned long* handle;
if(!co_swap) {
co_init();
co_swap = (void (*)(cothread_t, cothread_t))co_swap_function;
}
if(!co_active_handle) co_active_handle = &co_active_buffer;
VALGRIND_STACK_REGISTER(memory, memory + size);
if((handle = (unsigned long*)memory)) {
unsigned long stack_top = (unsigned long)handle + size;
unsigned long *p;
stack_top &= ~((unsigned long) 15);
p = (unsigned long*)(stack_top);
handle[8] = (unsigned long)p;
handle[9] = (unsigned long)entrypoint;
}
return handle;
}
cothread_t co_create(unsigned int size, void (*entrypoint)(void)) {
void* memory = LIBCO_MALLOC(size);
if(!memory) return (cothread_t)0;
return co_derive(memory, size, entrypoint);
}
void co_delete(cothread_t handle) {
LIBCO_FREE(handle);
}
void co_switch(cothread_t handle) {
cothread_t co_previous_handle = co_active_handle;
co_swap(co_active_handle = handle, co_previous_handle);
}
int co_serializable(void) {
return 1;
}
#ifdef __cplusplus
}
#endif
+55
View File
@@ -0,0 +1,55 @@
#define LIBCO_C
#include "libco.h"
#include "settings.h"
#define WINVER 0x0400
#define _WIN32_WINNT 0x0400
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
static thread_local cothread_t co_active_ = 0;
static void __stdcall co_thunk(void* coentry) {
((void (*)(void))coentry)();
}
cothread_t co_active(void) {
if(!co_active_) {
ConvertThreadToFiber(0);
co_active_ = GetCurrentFiber();
}
return co_active_;
}
cothread_t co_derive(void* memory, unsigned int heapsize, void (*coentry)(void)) {
/* Windows fibers do not allow users to supply their own memory */
return (cothread_t)0;
}
cothread_t co_create(unsigned int heapsize, void (*coentry)(void)) {
if(!co_active_) {
ConvertThreadToFiber(0);
co_active_ = GetCurrentFiber();
}
return (cothread_t)CreateFiber(heapsize, co_thunk, (void*)coentry);
}
void co_delete(cothread_t cothread) {
DeleteFiber(cothread);
}
void co_switch(cothread_t cothread) {
co_active_ = cothread;
SwitchToFiber(cothread);
}
int co_serializable(void) {
return 0;
}
#ifdef __cplusplus
}
#endif
+37
View File
@@ -0,0 +1,37 @@
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wparentheses"
/* placing code in section(text) does not mark it executable with Clang. */
#undef LIBCO_MPROTECT
#define LIBCO_MPROTECT
#endif
#if defined(__clang__) || defined(__GNUC__)
#if defined(__i386__)
#include "x86.c"
#elif defined(__amd64__)
#include "amd64.c"
#elif defined(__arm__)
#include "arm.c"
#elif defined(__aarch64__)
#include "aarch64.c"
#elif defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2
#include "ppc64v2.c"
#elif defined(_ARCH_PPC) && !defined(__LITTLE_ENDIAN__)
#include "ppc.c"
#elif defined(_WIN32)
#include "fiber.c"
#else
#include "sjlj.c"
#endif
#elif defined(_MSC_VER)
#if defined(_M_IX86)
#include "x86.c"
#elif defined(_M_AMD64)
#include "amd64.c"
#else
#include "fiber.c"
#endif
#else
#error "libco: unsupported processor, compiler or operating system"
#endif
+28
View File
@@ -0,0 +1,28 @@
/*
libco v20 (2019-10-16)
author: byuu
license: ISC
*/
#ifndef LIBCO_H
#define LIBCO_H
#ifdef __cplusplus
extern "C" {
#endif
typedef void* cothread_t;
cothread_t co_active(void);
cothread_t co_derive(void*, unsigned int, void (*)(void));
cothread_t co_create(unsigned int, void (*)(void));
void co_delete(cothread_t);
void co_switch(cothread_t);
int co_serializable(void);
#ifdef __cplusplus
}
#endif
/* ifndef LIBCO_H */
#endif
+435
View File
@@ -0,0 +1,435 @@
/* ppc64le (ELFv2) is not currently supported */
#define LIBCO_C
#include "libco.h"
#include "settings.h"
#include "valgrind.h"
#include <stdint.h>
#include <string.h>
#ifdef LIBCO_MPROTECT
#include <unistd.h>
#include <sys/mman.h>
#endif
/* state format (offsets in 32-bit words)
+0 pointer to swap code
rest of function descriptor for entry function
+8 PC
+10 SP
special registers
GPRs
FPRs
VRs
stack
*/
enum { state_size = 1024 };
enum { above_stack = 2048 };
enum { stack_align = 256 };
static thread_local cothread_t co_active_handle = 0;
/* determine environment */
#define LIBCO_PPC64 (_ARCH_PPC64 || __PPC64__ || __ppc64__ || __powerpc64__)
/* whether function calls are indirect through a descriptor, or are directly to function */
#ifndef LIBCO_PPCDESC
#if !_CALL_SYSV && (_CALL_AIX || _CALL_AIXDESC || (LIBCO_PPC64 && (!defined(_CALL_ELF) || _CALL_ELF == 1)))
#define LIBCO_PPCDESC 1
#endif
#endif
#ifdef LIBCO_MPROTECT
alignas(4096)
#else
section(text)
#endif
static const uint32_t libco_ppc_code[1024] = {
#if LIBCO_PPC64
0x7d000026, /* mfcr r8 */
0xf8240028, /* std r1,40(r4) */
0x7d2802a6, /* mflr r9 */
0xf9c40048, /* std r14,72(r4) */
0xf9e40050, /* std r15,80(r4) */
0xfa040058, /* std r16,88(r4) */
0xfa240060, /* std r17,96(r4) */
0xfa440068, /* std r18,104(r4) */
0xfa640070, /* std r19,112(r4) */
0xfa840078, /* std r20,120(r4) */
0xfaa40080, /* std r21,128(r4) */
0xfac40088, /* std r22,136(r4) */
0xfae40090, /* std r23,144(r4) */
0xfb040098, /* std r24,152(r4) */
0xfb2400a0, /* std r25,160(r4) */
0xfb4400a8, /* std r26,168(r4) */
0xfb6400b0, /* std r27,176(r4) */
0xfb8400b8, /* std r28,184(r4) */
0xfba400c0, /* std r29,192(r4) */
0xfbc400c8, /* std r30,200(r4) */
0xfbe400d0, /* std r31,208(r4) */
0xf9240020, /* std r9,32(r4) */
0xe8e30020, /* ld r7,32(r3) */
0xe8230028, /* ld r1,40(r3) */
0x48000009, /* bl 1 */
0x7fe00008, /* trap */
0x91040030, /*1:stw r8,48(r4) */
0x80c30030, /* lwz r6,48(r3) */
0x7ce903a6, /* mtctr r7 */
0xe9c30048, /* ld r14,72(r3) */
0xe9e30050, /* ld r15,80(r3) */
0xea030058, /* ld r16,88(r3) */
0xea230060, /* ld r17,96(r3) */
0xea430068, /* ld r18,104(r3) */
0xea630070, /* ld r19,112(r3) */
0xea830078, /* ld r20,120(r3) */
0xeaa30080, /* ld r21,128(r3) */
0xeac30088, /* ld r22,136(r3) */
0xeae30090, /* ld r23,144(r3) */
0xeb030098, /* ld r24,152(r3) */
0xeb2300a0, /* ld r25,160(r3) */
0xeb4300a8, /* ld r26,168(r3) */
0xeb6300b0, /* ld r27,176(r3) */
0xeb8300b8, /* ld r28,184(r3) */
0xeba300c0, /* ld r29,192(r3) */
0xebc300c8, /* ld r30,200(r3) */
0xebe300d0, /* ld r31,208(r3) */
0x7ccff120, /* mtcr r6 */
#else
0x7d000026, /* mfcr r8 */
0x90240028, /* stw r1,40(r4) */
0x7d2802a6, /* mflr r9 */
0x91a4003c, /* stw r13,60(r4) */
0x91c40040, /* stw r14,64(r4) */
0x91e40044, /* stw r15,68(r4) */
0x92040048, /* stw r16,72(r4) */
0x9224004c, /* stw r17,76(r4) */
0x92440050, /* stw r18,80(r4) */
0x92640054, /* stw r19,84(r4) */
0x92840058, /* stw r20,88(r4) */
0x92a4005c, /* stw r21,92(r4) */
0x92c40060, /* stw r22,96(r4) */
0x92e40064, /* stw r23,100(r4) */
0x93040068, /* stw r24,104(r4) */
0x9324006c, /* stw r25,108(r4) */
0x93440070, /* stw r26,112(r4) */
0x93640074, /* stw r27,116(r4) */
0x93840078, /* stw r28,120(r4) */
0x93a4007c, /* stw r29,124(r4) */
0x93c40080, /* stw r30,128(r4) */
0x93e40084, /* stw r31,132(r4) */
0x91240020, /* stw r9,32(r4) */
0x80e30020, /* lwz r7,32(r3) */
0x80230028, /* lwz r1,40(r3) */
0x48000009, /* bl 1 */
0x7fe00008, /* trap */
0x91040030, /*1:stw r8,48(r4) */
0x80c30030, /* lwz r6,48(r3) */
0x7ce903a6, /* mtctr r7 */
0x81a3003c, /* lwz r13,60(r3) */
0x81c30040, /* lwz r14,64(r3) */
0x81e30044, /* lwz r15,68(r3) */
0x82030048, /* lwz r16,72(r3) */
0x8223004c, /* lwz r17,76(r3) */
0x82430050, /* lwz r18,80(r3) */
0x82630054, /* lwz r19,84(r3) */
0x82830058, /* lwz r20,88(r3) */
0x82a3005c, /* lwz r21,92(r3) */
0x82c30060, /* lwz r22,96(r3) */
0x82e30064, /* lwz r23,100(r3) */
0x83030068, /* lwz r24,104(r3) */
0x8323006c, /* lwz r25,108(r3) */
0x83430070, /* lwz r26,112(r3) */
0x83630074, /* lwz r27,116(r3) */
0x83830078, /* lwz r28,120(r3) */
0x83a3007c, /* lwz r29,124(r3) */
0x83c30080, /* lwz r30,128(r3) */
0x83e30084, /* lwz r31,132(r3) */
0x7ccff120, /* mtcr r6 */
#endif
#ifndef LIBCO_PPC_NOFP
0xd9c400e0, /* stfd f14,224(r4) */
0xd9e400e8, /* stfd f15,232(r4) */
0xda0400f0, /* stfd f16,240(r4) */
0xda2400f8, /* stfd f17,248(r4) */
0xda440100, /* stfd f18,256(r4) */
0xda640108, /* stfd f19,264(r4) */
0xda840110, /* stfd f20,272(r4) */
0xdaa40118, /* stfd f21,280(r4) */
0xdac40120, /* stfd f22,288(r4) */
0xdae40128, /* stfd f23,296(r4) */
0xdb040130, /* stfd f24,304(r4) */
0xdb240138, /* stfd f25,312(r4) */
0xdb440140, /* stfd f26,320(r4) */
0xdb640148, /* stfd f27,328(r4) */
0xdb840150, /* stfd f28,336(r4) */
0xdba40158, /* stfd f29,344(r4) */
0xdbc40160, /* stfd f30,352(r4) */
0xdbe40168, /* stfd f31,360(r4) */
0xc9c300e0, /* lfd f14,224(r3) */
0xc9e300e8, /* lfd f15,232(r3) */
0xca0300f0, /* lfd f16,240(r3) */
0xca2300f8, /* lfd f17,248(r3) */
0xca430100, /* lfd f18,256(r3) */
0xca630108, /* lfd f19,264(r3) */
0xca830110, /* lfd f20,272(r3) */
0xcaa30118, /* lfd f21,280(r3) */
0xcac30120, /* lfd f22,288(r3) */
0xcae30128, /* lfd f23,296(r3) */
0xcb030130, /* lfd f24,304(r3) */
0xcb230138, /* lfd f25,312(r3) */
0xcb430140, /* lfd f26,320(r3) */
0xcb630148, /* lfd f27,328(r3) */
0xcb830150, /* lfd f28,336(r3) */
0xcba30158, /* lfd f29,344(r3) */
0xcbc30160, /* lfd f30,352(r3) */
0xcbe30168, /* lfd f31,360(r3) */
#endif
#ifdef __ALTIVEC__
0x7ca042a6, /* mfvrsave r5 */
0x39040180, /* addi r8,r4,384 */
0x39240190, /* addi r9,r4,400 */
0x70a00fff, /* andi. r0,r5,4095 */
0x90a40034, /* stw r5,52(r4) */
0x4182005c, /* beq- 2 */
0x7e8041ce, /* stvx v20,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7ea049ce, /* stvx v21,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7ec041ce, /* stvx v22,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7ee049ce, /* stvx v23,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7f0041ce, /* stvx v24,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7f2049ce, /* stvx v25,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7f4041ce, /* stvx v26,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7f6049ce, /* stvx v27,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7f8041ce, /* stvx v28,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7fa049ce, /* stvx v29,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7fc041ce, /* stvx v30,r0,r8 */
0x7fe049ce, /* stvx v31,r0,r9 */
0x80a30034, /*2:lwz r5,52(r3) */
0x39030180, /* addi r8,r3,384 */
0x39230190, /* addi r9,r3,400 */
0x70a00fff, /* andi. r0,r5,4095 */
0x7ca043a6, /* mtvrsave r5 */
0x4d820420, /* beqctr */
0x7e8040ce, /* lvx v20,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7ea048ce, /* lvx v21,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7ec040ce, /* lvx v22,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7ee048ce, /* lvx v23,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7f0040ce, /* lvx v24,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7f2048ce, /* lvx v25,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7f4040ce, /* lvx v26,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7f6048ce, /* lvx v27,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7f8040ce, /* lvx v28,r0,r8 */
0x39080020, /* addi r8,r8,32 */
0x7fa048ce, /* lvx v29,r0,r9 */
0x39290020, /* addi r9,r9,32 */
0x7fc040ce, /* lvx v30,r0,r8 */
0x7fe048ce, /* lvx v31,r0,r9 */
#endif
0x4e800420, /* bctr */
};
#if LIBCO_PPCDESC
/* function call goes through indirect descriptor */
#define CO_SWAP_ASM(x, y) ((void (*)(cothread_t, cothread_t))(uintptr_t)x)(x, y)
#else
/* function call goes directly to code */
#define CO_SWAP_ASM(x, y) ((void (*)(cothread_t, cothread_t))(uintptr_t)libco_ppc_code)(x, y)
#endif
static uint32_t* co_derive_(void* memory, unsigned size, uintptr_t entry) {
uint32_t* t = (uint32_t*)memory;
(void)entry;
#if LIBCO_PPCDESC
if(t) {
memcpy(t, (void*)entry, sizeof(void*) * 3); /* copy entry's descriptor */
*(const void**)t = libco_ppc_code; /* set function pointer to swap routine */
}
#endif
return t;
}
cothread_t co_derive(void* memory, unsigned int size, void (*entry_)(void)) {
uintptr_t entry = (uintptr_t)entry_;
uint32_t* t = 0;
/* be sure main thread was successfully allocated */
if(co_active()) {
t = co_derive_(memory, size, entry);
}
if(t) {
uintptr_t sp;
int shift;
VALGRIND_STACK_REGISTER(t, (char*)t + size);
/* save current registers into new thread, so that any special ones will have proper values when thread is begun */
CO_SWAP_ASM(t, t);
#if LIBCO_PPCDESC
entry = (uintptr_t)*(void**)entry; /* get real address */
#endif
/* put stack near end of block, and align */
sp = (uintptr_t)t + size - above_stack;
sp -= sp % stack_align;
/* on PPC32, we save and restore GPRs as 32 bits. for PPC64, we
save and restore them as 64 bits, regardless of the size the ABI
uses. so, we manually write pointers at the proper size. we always
save and restore at the same address, and since PPC is big-endian,
we must put the low byte first on PPC32. */
/* if uintptr_t is 32 bits, >>32 is undefined behavior,
so we do two shifts and don't have to care how many bits uintptr_t is. */
#if LIBCO_PPC64
shift = 16;
#else
shift = 0;
#endif
/* set up so entry will be called on next swap */
t[ 8] = (uint32_t)(entry >> shift >> shift);
t[ 9] = (uint32_t)entry;
t[10] = (uint32_t)(sp >> shift >> shift);
t[11] = (uint32_t)sp;
}
return t;
}
static uint32_t* co_create_(unsigned size, uintptr_t entry) {
uint32_t* t = (uint32_t*)LIBCO_MALLOC(size);
(void)entry;
#if LIBCO_PPCDESC
if(t) {
memcpy(t, (void*)entry, sizeof(void*) * 3); /* copy entry's descriptor */
*(const void**)t = libco_ppc_code; /* set function pointer to swap routine */
}
#endif
return t;
}
cothread_t co_create(unsigned int size, void (*entry_)(void)) {
uintptr_t entry = (uintptr_t)entry_;
uint32_t* t = 0;
/* be sure main thread was successfully allocated */
if(co_active()) {
size += state_size + above_stack + stack_align;
t = co_create_(size, entry);
}
if(t) {
uintptr_t sp;
int shift;
VALGRIND_STACK_REGISTER(t, (char*)t + size);
/* save current registers into new thread, so that any special ones will have proper values when thread is begun */
CO_SWAP_ASM(t, t);
#if LIBCO_PPCDESC
entry = (uintptr_t)*(void**)entry; /* get real address */
#endif
/* put stack near end of block, and align */
sp = (uintptr_t)t + size - above_stack;
sp -= sp % stack_align;
/* on PPC32, we save and restore GPRs as 32 bits. for PPC64, we
save and restore them as 64 bits, regardless of the size the ABI
uses. so, we manually write pointers at the proper size. we always
save and restore at the same address, and since PPC is big-endian,
we must put the low byte first on PPC32. */
/* if uintptr_t is 32 bits, >>32 is undefined behavior,
so we do two shifts and don't have to care how many bits uintptr_t is. */
#if LIBCO_PPC64
shift = 16;
#else
shift = 0;
#endif
/* set up so entry will be called on next swap */
t[ 8] = (uint32_t)(entry >> shift >> shift);
t[ 9] = (uint32_t)entry;
t[10] = (uint32_t)(sp >> shift >> shift);
t[11] = (uint32_t)sp;
}
return t;
}
void co_delete(cothread_t t) {
LIBCO_FREE(t);
}
static void co_init_(void) {
#if LIBCO_MPROTECT
long page_size = sysconf(_SC_PAGESIZE);
if(page_size > 0) {
uintptr_t align = page_size;
uintptr_t begin = (uintptr_t)libco_ppc_code;
uintptr_t end = begin + sizeof libco_ppc_code;
/* align beginning and end */
end += align - 1;
end -= end % align;
begin -= begin % align;
mprotect((void*)begin, end - begin, PROT_READ | PROT_EXEC);
}
#endif
co_active_handle = co_create_(state_size, (uintptr_t)&co_switch);
}
cothread_t co_active(void) {
if(!co_active_handle) co_init_();
return co_active_handle;
}
void co_switch(cothread_t t) {
cothread_t old = co_active_handle;
co_active_handle = t;
CO_SWAP_ASM(t, old);
}
int co_serializable(void) {
return 0;
}
+281
View File
@@ -0,0 +1,281 @@
/* author: Shawn Anastasio */
#define LIBCO_C
#include "libco.h"
#include "settings.h"
#include "valgrind.h"
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
struct ppc64_context {
/* GPRs */
uint64_t gprs[32];
uint64_t lr;
uint64_t ccr;
/* FPRs */
uint64_t fprs[32];
#ifdef __ALTIVEC__
/* Altivec (VMX) */
uint64_t vmx[12 * 2];
uint32_t vrsave;
#endif
};
static thread_local struct ppc64_context* co_active_handle = 0;
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define ALIGN(p, x) ((void*)((uintptr_t)(p) & ~((x) - 1)))
#define MIN_STACK 0x10000lu
#define MIN_STACK_FRAME 0x20lu
#define STACK_ALIGN 0x10lu
void swap_context(struct ppc64_context* read, struct ppc64_context* write);
__asm__(
".text\n"
".align 4\n"
".type swap_context @function\n"
"swap_context:\n"
".cfi_startproc\n"
/* save GPRs */
"std 1, 8(4)\n"
"std 2, 16(4)\n"
"std 12, 96(4)\n"
"std 13, 104(4)\n"
"std 14, 112(4)\n"
"std 15, 120(4)\n"
"std 16, 128(4)\n"
"std 17, 136(4)\n"
"std 18, 144(4)\n"
"std 19, 152(4)\n"
"std 20, 160(4)\n"
"std 21, 168(4)\n"
"std 22, 176(4)\n"
"std 23, 184(4)\n"
"std 24, 192(4)\n"
"std 25, 200(4)\n"
"std 26, 208(4)\n"
"std 27, 216(4)\n"
"std 28, 224(4)\n"
"std 29, 232(4)\n"
"std 30, 240(4)\n"
"std 31, 248(4)\n"
/* save LR */
"mflr 5\n"
"std 5, 256(4)\n"
/* save CCR */
"mfcr 5\n"
"std 5, 264(4)\n"
/* save FPRs */
"stfd 14, 384(4)\n"
"stfd 15, 392(4)\n"
"stfd 16, 400(4)\n"
"stfd 17, 408(4)\n"
"stfd 18, 416(4)\n"
"stfd 19, 424(4)\n"
"stfd 20, 432(4)\n"
"stfd 21, 440(4)\n"
"stfd 22, 448(4)\n"
"stfd 23, 456(4)\n"
"stfd 24, 464(4)\n"
"stfd 25, 472(4)\n"
"stfd 26, 480(4)\n"
"stfd 27, 488(4)\n"
"stfd 28, 496(4)\n"
"stfd 29, 504(4)\n"
"stfd 30, 512(4)\n"
"stfd 31, 520(4)\n"
#ifdef __ALTIVEC__
/* save VMX */
"li 5, 528\n"
"stvxl 20, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 21, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 22, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 23, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 24, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 25, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 26, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 27, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 28, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 29, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 30, 4, 5\n"
"addi 5, 5, 16\n"
"stvxl 31, 4, 5\n"
"addi 5, 5, 16\n"
/* save VRSAVE */
"mfvrsave 5\n"
"stw 5, 736(4)\n"
#endif
/* restore GPRs */
"ld 1, 8(3)\n"
"ld 2, 16(3)\n"
"ld 12, 96(3)\n"
"ld 13, 104(3)\n"
"ld 14, 112(3)\n"
"ld 15, 120(3)\n"
"ld 16, 128(3)\n"
"ld 17, 136(3)\n"
"ld 18, 144(3)\n"
"ld 19, 152(3)\n"
"ld 20, 160(3)\n"
"ld 21, 168(3)\n"
"ld 22, 176(3)\n"
"ld 23, 184(3)\n"
"ld 24, 192(3)\n"
"ld 25, 200(3)\n"
"ld 26, 208(3)\n"
"ld 27, 216(3)\n"
"ld 28, 224(3)\n"
"ld 29, 232(3)\n"
"ld 30, 240(3)\n"
"ld 31, 248(3)\n"
/* restore LR */
"ld 5, 256(3)\n"
"mtlr 5\n"
/* restore CCR */
"ld 5, 264(3)\n"
"mtcr 5\n"
/* restore FPRs */
"lfd 14, 384(3)\n"
"lfd 15, 392(3)\n"
"lfd 16, 400(3)\n"
"lfd 17, 408(3)\n"
"lfd 18, 416(3)\n"
"lfd 19, 424(3)\n"
"lfd 20, 432(3)\n"
"lfd 21, 440(3)\n"
"lfd 22, 448(3)\n"
"lfd 23, 456(3)\n"
"lfd 24, 464(3)\n"
"lfd 25, 472(3)\n"
"lfd 26, 480(3)\n"
"lfd 27, 488(3)\n"
"lfd 28, 496(3)\n"
"lfd 29, 504(3)\n"
"lfd 30, 512(3)\n"
"lfd 31, 520(3)\n"
#ifdef __ALTIVEC__
/* restore VMX */
"li 5, 528\n"
"lvxl 20, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 21, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 22, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 23, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 24, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 25, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 26, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 27, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 28, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 29, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 30, 3, 5\n"
"addi 5, 5, 16\n"
"lvxl 31, 3, 5\n"
"addi 5, 5, 16\n"
/* restore VRSAVE */
"lwz 5, 720(3)\n"
"mtvrsave 5\n"
#endif
/* branch to LR */
"blr\n"
".cfi_endproc\n"
".size swap_context, .-swap_context\n"
);
cothread_t co_active(void) {
if(!co_active_handle) {
co_active_handle = (struct ppc64_context*)LIBCO_MALLOC(MIN_STACK + sizeof(struct ppc64_context));
}
return (cothread_t)co_active_handle;
}
cothread_t co_derive(void* memory, unsigned int size, void (*coentry)(void)) {
uint8_t* sp;
struct ppc64_context* context = (struct ppc64_context*)memory;
VALGRIND_STACK_REGISTER(memory, memory + size);
/* save current context into new context to initialize it */
swap_context(context, context);
/* align stack */
sp = (uint8_t*)memory + size - STACK_ALIGN;
sp = (uint8_t*)ALIGN(sp, STACK_ALIGN);
/* write 0 for initial backchain */
*(uint64_t*)sp = 0;
/* create new frame with backchain */
sp -= MIN_STACK_FRAME;
*(uint64_t*)sp = (uint64_t)(sp + MIN_STACK_FRAME);
/* update context with new stack (r1) and entrypoint (r12, lr) */
context->gprs[ 1] = (uint64_t)sp;
context->gprs[12] = (uint64_t)coentry;
context->lr = (uint64_t)coentry;
return (cothread_t)memory;
}
cothread_t co_create(unsigned int size, void (*coentry)(void)) {
void* memory = LIBCO_MALLOC(size);
if(!memory) return (cothread_t)0;
return co_derive(memory, size, coentry);
}
void co_delete(cothread_t handle) {
LIBCO_FREE(handle);
}
void co_switch(cothread_t to) {
struct ppc64_context* from = co_active_handle;
co_active_handle = (struct ppc64_context*)to;
swap_context((struct ppc64_context*)to, from);
}
int co_serializable(void) {
return 1;
}
#ifdef __cplusplus
}
#endif
+131
View File
@@ -0,0 +1,131 @@
#if defined(LIBCO_C)
/*[amd64, arm, ppc, x86]:
by default, co_swap_function is marked as a text (code) section
if not supported, uncomment the below line to use mprotect instead */
/* #define LIBCO_MPROTECT */
/*[amd64]:
Win64 only: provides a substantial speed-up, but will thrash XMM regs
do not use this unless you are certain your application won't use SSE */
/* #define LIBCO_NO_SSE */
#if !defined(thread_local) /* User can override thread_local for obscure compilers */
#if !defined(LIBCO_MP) /* Running in single-threaded environment */
#define thread_local
#else /* Running in multi-threaded environment */
#if defined(__STDC__) /* Compiling as C Language */
#if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */
#define thread_local __declspec(thread)
#elif __STDC_VERSION__ < 201112L /* If we are on C90/99 */
#if defined(__clang__) || defined(__GNUC__) /* Clang and GCC */
#define thread_local __thread
#else /* Otherwise, we ignore the directive (unless user provides their own) */
#define thread_local
#endif
#else /* C11 and newer define thread_local in threads.h */
#include <threads.h>
#endif
#elif defined(__cplusplus) /* Compiling as C++ Language */
#if __cplusplus < 201103L /* thread_local is a C++11 feature */
#if defined(_MSC_VER)
#define thread_local __declspec(thread)
#elif defined(__clang__) || defined(__GNUC__)
#define thread_local __thread
#else /* Otherwise, we ignore the directive (unless user provides their own) */
#define thread_local
#endif
#else /* In C++ >= 11, thread_local in a builtin keyword */
/* Don't do anything */
#endif
#endif
#endif
#endif
/* In alignas(a), 'a' should be a power of two that is at least the type's
alignment and at most the implementation's alignment limit. This limit is
2**13 on MSVC. To be portable to MSVC through at least version 10.0,
'a' should be an integer constant, as MSVC does not support expressions
such as 1 << 3.
The following C11 requirements are NOT supported on MSVC:
- If 'a' is zero, alignas has no effect.
- alignas can be used multiple times; the strictest one wins.
- alignas (TYPE) is equivalent to alignas (alignof (TYPE)).
*/
#if !defined(alignas)
#if defined(__STDC__) /* C Language */
#if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */
#define alignas(bytes) __declspec(align(bytes))
#elif __STDC_VERSION__ >= 201112L /* C11 and above */
#include <stdalign.h>
#elif defined(__clang__) || defined(__GNUC__) /* C90/99 on Clang/GCC */
#define alignas(bytes) __attribute__ ((aligned (bytes)))
#else /* Otherwise, we ignore the directive (user should provide their own) */
#define alignas(bytes)
#endif
#elif defined(__cplusplus) /* C++ Language */
#if __cplusplus < 201103L
#if defined(_MSC_VER)
#define alignas(bytes) __declspec(align(bytes))
#elif defined(__clang__) || defined(__GNUC__) /* C++98/03 on Clang/GCC */
#define alignas(bytes) __attribute__ ((aligned (bytes)))
#else /* Otherwise, we ignore the directive (unless user provides their own) */
#define alignas(bytes)
#endif
#else /* C++ >= 11 has alignas keyword */
/* Do nothing */
#endif
#endif /* = !defined(__STDC_VERSION__) && !defined(__cplusplus) */
#endif
#if !defined(LIBCO_ASSERT)
#include <assert.h>
#define LIBCO_ASSERT assert
#endif
#if defined (__OpenBSD__)
#if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE)
#include <unistd.h>
#include <sys/mman.h>
static void* malloc_obsd(size_t size) {
long pagesize = sysconf(_SC_PAGESIZE);
char* memory = (char*)mmap(NULL, size + pagesize, PROT_READ|PROT_WRITE, MAP_STACK|MAP_PRIVATE|MAP_ANON, -1, 0);
if (memory == MAP_FAILED) return NULL;
*(size_t*)memory = size + pagesize;
memory += pagesize;
return (void*)memory;
}
static void free_obsd(void *ptr) {
char* memory = (char*)ptr - sysconf(_SC_PAGESIZE);
munmap(memory, *(size_t*)memory);
}
#define LIBCO_MALLOC malloc_obsd
#define LIBCO_FREE free_obsd
#endif
#endif
#if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE)
#include <stdlib.h>
#define LIBCO_MALLOC malloc
#define LIBCO_FREE free
#endif
#if defined(_MSC_VER)
/* workaround for msvc preprocessor stringification behavior */
#define LIBCO_STRINGIFY(x) #x
#define LIBCO_TOSTRING(x) LIBCO_STRINGIFY(x)
#define section(name) __pragma(code_seg(LIBCO_TOSTRING("." #name))) __declspec(allocate(LIBCO_TOSTRING("." #name)))
#elif defined(__APPLE__)
#define section(name) __attribute__((section("__TEXT,__" #name)))
#else
#define section(name) __attribute__((section("." #name "#")))
#endif
/* if defined(LIBCO_C) */
#endif
+155
View File
@@ -0,0 +1,155 @@
/*
note this was designed for UNIX systems. Based on ideas expressed in a paper by Ralf Engelschall.
for SJLJ on other systems, one would want to rewrite springboard() and co_create() and hack the jmb_buf stack pointer.
*/
/* for sigsetjmp(), sigjmp_buf, and stack_t */
#define _POSIX_C_SOURCE 200809L
/* for SA_ONSTACK */
#define _XOPEN_SOURCE 600
#define LIBCO_C
#include "libco.h"
#include "settings.h"
#include "valgrind.h"
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
sigjmp_buf context;
void (*coentry)(void);
void* stack;
} cothread_struct;
static thread_local cothread_struct co_primary;
static thread_local cothread_struct* creating;
static thread_local cothread_struct* co_running = 0;
static void springboard(int ignored) {
if(sigsetjmp(creating->context, 0)) {
co_running->coentry();
}
}
cothread_t co_active(void) {
if(!co_running) co_running = &co_primary;
return (cothread_t)co_running;
}
cothread_t co_derive(void* memory, unsigned int size, void (*coentry)(void)) {
cothread_struct* thread;
if(!co_running) co_running = &co_primary;
thread = (cothread_struct*)memory;
memory = (unsigned char*)memory + sizeof(cothread_struct);
size -= sizeof(cothread_struct);
if(thread) {
struct sigaction handler;
struct sigaction old_handler;
stack_t stack;
stack_t old_stack;
thread->coentry = thread->stack = 0;
stack.ss_flags = 0;
stack.ss_size = size;
thread->stack = stack.ss_sp = memory;
if(stack.ss_sp && !sigaltstack(&stack, &old_stack)) {
handler.sa_handler = springboard;
handler.sa_flags = SA_ONSTACK;
sigemptyset(&handler.sa_mask);
creating = thread;
if(!sigaction(SIGUSR1, &handler, &old_handler)) {
if(!raise(SIGUSR1)) {
thread->coentry = coentry;
}
sigaltstack(&old_stack, 0);
sigaction(SIGUSR1, &old_handler, 0);
}
}
if(thread->coentry != coentry) {
co_delete(thread);
thread = 0;
} else {
VALGRIND_STACK_REGISTER(stack.ss_sp, stack.ss_sp + size);
}
}
return (cothread_t)thread;
}
cothread_t co_create(unsigned int size, void (*coentry)(void)) {
cothread_struct* thread;
if(!co_running) co_running = &co_primary;
thread = (cothread_struct*)malloc(sizeof(cothread_struct));
if(thread) {
struct sigaction handler;
struct sigaction old_handler;
stack_t stack;
stack_t old_stack;
thread->coentry = thread->stack = 0;
stack.ss_flags = 0;
stack.ss_size = size;
thread->stack = stack.ss_sp = malloc(size);
if(stack.ss_sp && !sigaltstack(&stack, &old_stack)) {
handler.sa_handler = springboard;
handler.sa_flags = SA_ONSTACK;
sigemptyset(&handler.sa_mask);
creating = thread;
if(!sigaction(SIGUSR1, &handler, &old_handler)) {
if(!raise(SIGUSR1)) {
thread->coentry = coentry;
}
sigaltstack(&old_stack, 0);
sigaction(SIGUSR1, &old_handler, 0);
}
}
if(thread->coentry != coentry) {
co_delete(thread);
thread = 0;
} else {
VALGRIND_STACK_REGISTER(stack.ss_sp, stack.ss_sp + size);
}
}
return (cothread_t)thread;
}
void co_delete(cothread_t cothread) {
if(cothread) {
if(((cothread_struct*)cothread)->stack) {
free(((cothread_struct*)cothread)->stack);
}
free(cothread);
}
}
void co_switch(cothread_t cothread) {
if(!sigsetjmp(co_running->context, 0)) {
co_running = (cothread_struct*)cothread;
siglongjmp(co_running->context, 1);
}
}
int co_serializable(void) {
return 0;
}
#ifdef __cplusplus
}
#endif
+90
View File
@@ -0,0 +1,90 @@
/*
WARNING: the overhead of POSIX ucontext is very high,
assembly versions of libco or libco_sjlj should be much faster
this library only exists for two reasons:
1: as an initial test for the viability of a ucontext implementation
2: to demonstrate the power and speed of libco over existing implementations,
such as pth (which defaults to wrapping ucontext on unix targets)
use this library only as a *last resort*
*/
#define _POSIX_C_SOURCE 200112L
#define LIBCO_C
#include "libco.h"
#include "settings.h"
#include "valgrind.h"
#include <stdlib.h>
#include <ucontext.h>
#ifdef __cplusplus
extern "C" {
#endif
static thread_local ucontext_t co_primary;
static thread_local ucontext_t* co_running = 0;
cothread_t co_active(void) {
if(!co_running) co_running = &co_primary;
return (cothread_t)co_running;
}
cothread_t co_derive(void* memory, unsigned int heapsize, void (*coentry)(void)) {
ucontext_t* thread;
if(!co_running) co_running = &co_primary;
thread = (ucontext_t*)memory;
memory = (unsigned char*)memory + sizeof(ucontext_t);
heapsize -= sizeof(ucontext_t);
if(thread) {
if((!getcontext(thread) && !(thread->uc_stack.ss_sp = 0)) && (thread->uc_stack.ss_sp = memory)) {
thread->uc_link = co_running;
thread->uc_stack.ss_size = heapsize;
makecontext(thread, coentry, 0);
VALGRIND_STACK_REGISTER(thread->uc_stack.ss_sp, thread->uc_stack.ss_sp + heapsize);
} else {
thread = 0;
}
}
return (cothread_t)thread;
}
cothread_t co_create(unsigned int heapsize, void (*coentry)(void)) {
ucontext_t* thread;
if(!co_running) co_running = &co_primary;
thread = (ucontext_t*)malloc(sizeof(ucontext_t));
if(thread) {
if((!getcontext(thread) && !(thread->uc_stack.ss_sp = 0)) && (thread->uc_stack.ss_sp = malloc(heapsize))) {
thread->uc_link = co_running;
thread->uc_stack.ss_size = heapsize;
makecontext(thread, coentry, 0);
VALGRIND_STACK_REGISTER(thread->uc_stack.ss_sp, thread->uc_stack.ss_sp + heapsize);
} else {
co_delete((cothread_t)thread);
thread = 0;
}
}
return (cothread_t)thread;
}
void co_delete(cothread_t cothread) {
if(cothread) {
if(((ucontext_t*)cothread)->uc_stack.ss_sp) { free(((ucontext_t*)cothread)->uc_stack.ss_sp); }
free(cothread);
}
}
void co_switch(cothread_t cothread) {
ucontext_t* old_thread = co_running;
co_running = (ucontext_t*)cothread;
swapcontext(old_thread, co_running);
}
int co_serializable(void) {
return 0;
}
#ifdef __cplusplus
}
#endif
File diff suppressed because it is too large Load Diff
+131
View File
@@ -0,0 +1,131 @@
#define LIBCO_C
#include "libco.h"
#include "settings.h"
#ifdef __cplusplus
extern "C" {
#endif
#if defined(__clang__) || defined(__GNUC__)
#define fastcall __attribute__((fastcall))
#elif defined(_MSC_VER)
#define fastcall __fastcall
#else
#error "libco: please define fastcall macro"
#endif
static thread_local long co_active_buffer[64];
static thread_local cothread_t co_active_handle = 0;
static void (fastcall *co_swap)(cothread_t, cothread_t) = 0;
#ifdef LIBCO_MPROTECT
alignas(4096)
#else
section(text)
#endif
/* ABI: fastcall */
static const unsigned char co_swap_function[4096] = {
0x89, 0x22, /* mov [edx],esp */
0x8b, 0x21, /* mov esp,[ecx] */
0x58, /* pop eax */
0x89, 0x6a, 0x04, /* mov [edx+ 4],ebp */
0x89, 0x72, 0x08, /* mov [edx+ 8],esi */
0x89, 0x7a, 0x0c, /* mov [edx+12],edi */
0x89, 0x5a, 0x10, /* mov [edx+16],ebx */
0x8b, 0x69, 0x04, /* mov ebp,[ecx+ 4] */
0x8b, 0x71, 0x08, /* mov esi,[ecx+ 8] */
0x8b, 0x79, 0x0c, /* mov edi,[ecx+12] */
0x8b, 0x59, 0x10, /* mov ebx,[ecx+16] */
0xff, 0xe0, /* jmp eax */
};
#ifdef _WIN32
/* The macro logic below matches what valgrind.h is able to handle. Although
* there's no Valgrind on Windows, it's possible to run a Windows exe on Linux
* with Wine and Valgrind. See https://wiki.winehq.org/Wine_and_Valgrind. */
#if defined(__GNUC__) || defined(_MSC_VER)
#include "valgrind.h"
#endif
#include <windows.h>
static void co_init(void) {
#ifdef LIBCO_MPROTECT
DWORD old_privileges;
VirtualProtect((void*)co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READ, &old_privileges);
#endif
}
#else
#include "valgrind.h"
#ifdef LIBCO_MPROTECT
#include <unistd.h>
#include <sys/mman.h>
#endif
static void co_init(void) {
#ifdef LIBCO_MPROTECT
unsigned long addr = (unsigned long)co_swap_function;
unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE));
unsigned long size = (addr - base) + sizeof co_swap_function;
mprotect((void*)base, size, PROT_READ | PROT_EXEC);
#endif
}
#endif
static void crash(void) {
LIBCO_ASSERT(0); /* called only if cothread_t entrypoint returns */
}
cothread_t co_active(void) {
if(!co_active_handle) co_active_handle = &co_active_buffer;
return co_active_handle;
}
cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) {
cothread_t handle;
if(!co_swap) {
co_init();
co_swap = (void (fastcall*)(cothread_t, cothread_t))co_swap_function;
}
if(!co_active_handle) co_active_handle = &co_active_buffer;
#if defined(__VALGRIND_MAJOR__)
VALGRIND_STACK_REGISTER(memory, (char*)memory + size);
#endif
if((handle = (cothread_t)memory)) {
unsigned long stack_top = (unsigned long)handle + size;
long *p;
stack_top -= 32;
stack_top &= ~((unsigned long) 15);
p = (long*)(stack_top); /* seek to top of stack */
*--p = (long)crash; /* crash if entrypoint returns */
*--p = (long)entrypoint; /* start of function */
*(long*)handle = (long)p; /* stack pointer */
}
return handle;
}
cothread_t co_create(unsigned int size, void (*entrypoint)(void)) {
void* memory = LIBCO_MALLOC(size);
if(!memory) return (cothread_t)0;
return co_derive(memory, size, entrypoint);
}
void co_delete(cothread_t handle) {
LIBCO_FREE(handle);
}
void co_switch(cothread_t handle) {
register cothread_t co_previous_handle = co_active_handle;
co_swap(co_active_handle = handle, co_previous_handle);
}
int co_serializable(void) {
return 1;
}
#ifdef __cplusplus
}
#endif