mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
Replace copy coroutine with pthread implementation.
This commit is contained in:
parent
9c9531950c
commit
42130a64f0
30 changed files with 492 additions and 449 deletions
|
@ -1,21 +0,0 @@
|
|||
#ifndef COROUTINE_STACK_H
|
||||
#define COROUTINE_STACK_H 1
|
||||
|
||||
/*
|
||||
* This file is part of the "Coroutine" project and released under the MIT License.
|
||||
*
|
||||
* Created by Samuel Williams on 10/11/2020.
|
||||
* Copyright, 2020, by Samuel Williams.
|
||||
*/
|
||||
|
||||
#include COROUTINE_H
|
||||
|
||||
#ifdef COROUTINE_PRIVATE_STACK
|
||||
#define COROUTINE_STACK_LOCAL(type, name) type *name = ruby_xmalloc(sizeof(type))
|
||||
#define COROUTINE_STACK_FREE(name) ruby_xfree(name)
|
||||
#else
|
||||
#define COROUTINE_STACK_LOCAL(type, name) type name##_local; type * name = &name##_local
|
||||
#define COROUTINE_STACK_FREE(name)
|
||||
#endif
|
||||
|
||||
#endif /* COROUTINE_STACK_H */
|
|
@ -22,6 +22,7 @@ enum {COROUTINE_REGISTERS = 6};
|
|||
struct coroutine_context
|
||||
{
|
||||
void **stack_pointer;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
|
|
@ -23,6 +23,7 @@ enum {COROUTINE_REGISTERS = 8};
|
|||
struct coroutine_context
|
||||
{
|
||||
void **stack_pointer;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
|
|
@ -22,6 +22,7 @@ enum {COROUTINE_REGISTERS = 0xb0 / 8};
|
|||
struct coroutine_context
|
||||
{
|
||||
void **stack_pointer;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
/*
|
||||
* This file is part of the "Coroutine" project and released under the MIT License.
|
||||
*
|
||||
* Created by Samuel Williams on 24/6/2019.
|
||||
* Copyright, 2019, by Samuel Williams.
|
||||
*/
|
||||
|
||||
#include "Context.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// http://gcc.gnu.org/onlinedocs/gcc/Alternate-Keywords.html
|
||||
#ifndef __GNUC__
|
||||
#define __asm__ asm
|
||||
#endif
|
||||
|
||||
#if defined(__sparc)
|
||||
__attribute__((noinline))
|
||||
// https://marc.info/?l=linux-sparc&m=131914569320660&w=2
|
||||
static void coroutine_flush_register_windows(void) {
|
||||
__asm__
|
||||
#ifdef __GNUC__
|
||||
__volatile__
|
||||
#endif
|
||||
#if defined(__sparcv9) || defined(__sparc_v9__) || defined(__arch64__)
|
||||
#ifdef __GNUC__
|
||||
("flushw" : : : "%o7")
|
||||
#else
|
||||
("flushw")
|
||||
#endif
|
||||
#else
|
||||
("ta 0x03")
|
||||
#endif
|
||||
;
|
||||
}
|
||||
#else
|
||||
static void coroutine_flush_register_windows(void) {}
|
||||
#endif
|
||||
|
||||
__attribute__((noinline))
|
||||
void *coroutine_stack_pointer(void) {
|
||||
return (void*)(
|
||||
(char*)__builtin_frame_address(0)
|
||||
);
|
||||
}
|
||||
|
||||
// Save the current stack to a private area. It is likely that when restoring the stack, this stack frame will be incomplete. But that is acceptable since the previous stack frame which called `setjmp` should be correctly restored.
|
||||
__attribute__((noinline))
|
||||
int coroutine_save_stack_1(struct coroutine_context * context) {
|
||||
assert(context->stack);
|
||||
assert(context->base);
|
||||
|
||||
void *stack_pointer = coroutine_stack_pointer();
|
||||
|
||||
// At this point, you may need to ensure on architectures that use register windows, that all registers are flushed to the stack, otherwise the copy of the stack will not contain the valid registers:
|
||||
coroutine_flush_register_windows();
|
||||
|
||||
// Save stack to private area:
|
||||
if (stack_pointer < context->base) {
|
||||
size_t size = (char*)context->base - (char*)stack_pointer;
|
||||
assert(size <= context->size);
|
||||
|
||||
memcpy(context->stack, stack_pointer, size);
|
||||
context->used = size;
|
||||
} else {
|
||||
size_t size = (char*)stack_pointer - (char*)context->base;
|
||||
assert(size <= context->size);
|
||||
|
||||
memcpy(context->stack, context->base, size);
|
||||
context->used = size;
|
||||
}
|
||||
|
||||
// Initialized:
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the current stack to a private memory buffer.
|
||||
int coroutine_save_stack(struct coroutine_context * context) {
|
||||
if (_setjmp(context->state)) {
|
||||
// Restored.
|
||||
return 1;
|
||||
}
|
||||
|
||||
// We need to invoke the memory copy from one stack frame deeper than the one that calls setjmp. That is because if you don't do this, the setjmp might be restored into an invalid stack frame (truncated, etc):
|
||||
return coroutine_save_stack_1(context);
|
||||
}
|
||||
|
||||
__attribute__((noreturn, noinline))
|
||||
void coroutine_restore_stack_padded(struct coroutine_context *context, void * buffer) {
|
||||
void *stack_pointer = coroutine_stack_pointer();
|
||||
|
||||
assert(context->base);
|
||||
|
||||
// At this point, you may need to ensure on architectures that use register windows, that all registers are flushed to the stack, otherwise when we copy in the new stack, the registers would not be updated:
|
||||
coroutine_flush_register_windows();
|
||||
|
||||
// Restore stack from private area:
|
||||
if (stack_pointer < context->base) {
|
||||
void * bottom = (char*)context->base - context->used;
|
||||
assert(bottom > stack_pointer);
|
||||
|
||||
memcpy(bottom, context->stack, context->used);
|
||||
} else {
|
||||
void * top = (char*)context->base + context->used;
|
||||
assert(top < stack_pointer);
|
||||
|
||||
memcpy(context->base, context->stack, context->used);
|
||||
}
|
||||
|
||||
// Restore registers. The `buffer` is to force the compiler NOT to elide he buffer and `alloca`:
|
||||
_longjmp(context->state, (int)(1 | (intptr_t)buffer));
|
||||
}
|
||||
|
||||
// In order to swap between coroutines, we need to swap the stack and registers.
|
||||
// `setjmp` and `longjmp` are able to swap registers, but what about swapping stacks? You can use `memcpy` to copy the current stack to a private area and `memcpy` to copy the private stack of the next coroutine to the main stack.
|
||||
// But if the stack yop are copying in to the main stack is bigger than the currently executing stack, the `memcpy` will clobber the current stack frame (including the context argument). So we use `alloca` to push the current stack frame *beyond* the stack we are about to copy in. This ensures the current stack frame in `coroutine_restore_stack_padded` remains valid for calling `longjmp`.
|
||||
__attribute__((noreturn))
|
||||
void coroutine_restore_stack(struct coroutine_context *context) {
|
||||
void *stack_pointer = coroutine_stack_pointer();
|
||||
void *buffer = NULL;
|
||||
|
||||
// We must ensure that the next stack frame is BEYOND the stack we are restoring:
|
||||
if (stack_pointer < context->base) {
|
||||
intptr_t offset = (intptr_t)stack_pointer - ((intptr_t)context->base - context->used);
|
||||
if (offset > 0) buffer = alloca(offset);
|
||||
} else {
|
||||
intptr_t offset = ((intptr_t)context->base + context->used) - (intptr_t)stack_pointer;
|
||||
if (offset > 0) buffer = alloca(offset);
|
||||
}
|
||||
|
||||
assert(context->used > 0);
|
||||
|
||||
coroutine_restore_stack_padded(context, buffer);
|
||||
}
|
||||
|
||||
struct coroutine_context *coroutine_transfer(struct coroutine_context *current, struct coroutine_context *target)
|
||||
{
|
||||
struct coroutine_context *previous = target->from;
|
||||
|
||||
// In theory, either this condition holds true, or we should assign the base address to target:
|
||||
assert(current->base == target->base);
|
||||
// If you are trying to copy the coroutine to a different thread
|
||||
// target->base = current->base
|
||||
|
||||
target->from = current;
|
||||
|
||||
assert(current != target);
|
||||
|
||||
// It's possible to come here, even thought the current fiber has been terminated. We are never going to return so we don't bother saving the stack.
|
||||
|
||||
if (current->stack) {
|
||||
if (coroutine_save_stack(current) == 0) {
|
||||
coroutine_restore_stack(target);
|
||||
}
|
||||
} else {
|
||||
coroutine_restore_stack(target);
|
||||
}
|
||||
|
||||
target->from = previous;
|
||||
|
||||
return target;
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
#ifndef COROUTINE_COPY_CONTEXT_H
|
||||
#define COROUTINE_COPY_CONTEXT_H 1
|
||||
|
||||
/*
|
||||
* This file is part of the "Coroutine" project and released under the MIT License.
|
||||
*
|
||||
* Created by Samuel Williams on 27/6/2019.
|
||||
* Copyright, 2019, by Samuel Williams.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <setjmp.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* OpenBSD supports alloca, but does not include alloca.h */
|
||||
#ifndef __OpenBSD__
|
||||
#include <alloca.h>
|
||||
#endif
|
||||
|
||||
#define COROUTINE __attribute__((noreturn)) void
|
||||
|
||||
#ifdef HAVE_STDINT_H
|
||||
#include <stdint.h>
|
||||
#if INTPTR_MAX <= INT32_MAX
|
||||
#define COROUTINE_LIMITED_ADDRESS_SPACE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// This stack copying implementation which uses a private stack for each coroutine, including the main one.
|
||||
#define COROUTINE_PRIVATE_STACK
|
||||
|
||||
struct coroutine_context
|
||||
{
|
||||
// Private stack:
|
||||
void *stack;
|
||||
size_t size, used;
|
||||
|
||||
// The top (or bottom) of the currently executing stack:
|
||||
void *base;
|
||||
|
||||
jmp_buf state;
|
||||
|
||||
struct coroutine_context *from;
|
||||
};
|
||||
|
||||
typedef COROUTINE(*coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
||||
int coroutine_save_stack(struct coroutine_context * context);
|
||||
COROUTINE coroutine_restore_stack(struct coroutine_context *context);
|
||||
|
||||
// @param stack The private stack area memory allocation (pointer to lowest address).
|
||||
// @param size The size of the private stack area.
|
||||
// @param base A stack pointer to the base of the main stack. On x86 hardware, this is the upper extent of the region that will be copied to the private stack.
|
||||
static inline void coroutine_initialize_main(struct coroutine_context *context, void *stack, size_t size, void *base) {
|
||||
assert(stack);
|
||||
assert(size >= 1024);
|
||||
|
||||
context->stack = stack;
|
||||
context->size = size;
|
||||
context->used = 0;
|
||||
|
||||
assert(base);
|
||||
context->base = base;
|
||||
|
||||
context->from = NULL;
|
||||
}
|
||||
|
||||
// @param start The start function to invoke.
|
||||
static inline void coroutine_initialize(
|
||||
struct coroutine_context *context,
|
||||
coroutine_start start,
|
||||
void *stack,
|
||||
size_t size,
|
||||
void *base
|
||||
) {
|
||||
assert(start);
|
||||
|
||||
coroutine_initialize_main(context, stack, size, base);
|
||||
|
||||
if (coroutine_save_stack(context)) {
|
||||
start(context->from, context);
|
||||
}
|
||||
}
|
||||
|
||||
struct coroutine_context *coroutine_transfer(struct coroutine_context *current, register struct coroutine_context *target);
|
||||
|
||||
static inline void coroutine_destroy(struct coroutine_context *context)
|
||||
{
|
||||
context->stack = NULL;
|
||||
context->size = 0;
|
||||
context->from = NULL;
|
||||
}
|
||||
|
||||
#endif /* COROUTINE_COPY_CONTEXT_H */
|
|
@ -26,6 +26,7 @@ struct coroutine_context
|
|||
emscripten_fiber_t state;
|
||||
coroutine_start entry_func;
|
||||
struct coroutine_context * from;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
COROUTINE coroutine_trampoline(void * _context);
|
||||
|
|
|
@ -19,6 +19,7 @@ enum {
|
|||
struct coroutine_context
|
||||
{
|
||||
void **stack_pointer;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
|
268
coroutine/pthread/Context.c
Normal file
268
coroutine/pthread/Context.c
Normal file
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* This file is part of the "Coroutine" project and released under the MIT License.
|
||||
*
|
||||
* Created by Samuel Williams on 24/6/2021.
|
||||
* Copyright, 2021, by Samuel Williams.
|
||||
*/
|
||||
|
||||
#include "Context.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
static const int DEBUG = 0;
|
||||
|
||||
static
|
||||
int check(const char * message, int result) {
|
||||
if (result) {
|
||||
switch (result) {
|
||||
case EDEADLK:
|
||||
if (DEBUG) fprintf(stderr, "deadlock detected result=%d errno=%d\n", result, errno);
|
||||
break;
|
||||
default:
|
||||
if (DEBUG) fprintf(stderr, "error detected result=%d errno=%d\n", result, errno);
|
||||
perror(message);
|
||||
}
|
||||
}
|
||||
|
||||
assert(result == 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void coroutine_initialize_main(struct coroutine_context * context) {
|
||||
context->id = pthread_self();
|
||||
|
||||
check("coroutine_initialize_main:pthread_cond_init",
|
||||
pthread_cond_init(&context->schedule, NULL)
|
||||
);
|
||||
|
||||
context->shared = (struct coroutine_shared*)malloc(sizeof(struct coroutine_shared));
|
||||
assert(context->shared);
|
||||
|
||||
context->shared->main = context;
|
||||
context->shared->count = 1;
|
||||
|
||||
if (DEBUG) {
|
||||
pthread_mutexattr_t attr;
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
|
||||
|
||||
check("coroutine_initialize_main:pthread_mutex_init",
|
||||
pthread_mutex_init(&context->shared->guard, &attr)
|
||||
);
|
||||
} else {
|
||||
check("coroutine_initialize_main:pthread_mutex_init",
|
||||
pthread_mutex_init(&context->shared->guard, NULL)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void coroutine_release(struct coroutine_context *context) {
|
||||
if (context->shared) {
|
||||
size_t count = (context->shared->count -= 1);
|
||||
|
||||
if (count == 0) {
|
||||
if (DEBUG) fprintf(stderr, "coroutine_release:pthread_mutex_destroy(%p)\n", &context->shared->guard);
|
||||
pthread_mutex_destroy(&context->shared->guard);
|
||||
free(context->shared);
|
||||
}
|
||||
|
||||
context->shared = NULL;
|
||||
|
||||
if (DEBUG) fprintf(stderr, "coroutine_release:pthread_cond_destroy(%p)\n", &context->schedule);
|
||||
pthread_cond_destroy(&context->schedule);
|
||||
}
|
||||
}
|
||||
|
||||
void coroutine_initialize(
|
||||
struct coroutine_context *context,
|
||||
coroutine_start start,
|
||||
void *stack,
|
||||
size_t size
|
||||
) {
|
||||
assert(start && stack && size >= 1024);
|
||||
|
||||
// We will create the thread when we first transfer, but save the details now:
|
||||
context->shared = NULL;
|
||||
context->start = start;
|
||||
context->stack = stack;
|
||||
context->size = size;
|
||||
}
|
||||
|
||||
static
|
||||
int is_locked(pthread_mutex_t * mutex) {
|
||||
int result = pthread_mutex_trylock(mutex);
|
||||
|
||||
// If we could successfully lock the mutex:
|
||||
if (result == 0) {
|
||||
pthread_mutex_unlock(mutex);
|
||||
// We could lock the mutex, so it wasn't locked:
|
||||
return 0;
|
||||
} else {
|
||||
// Otherwise we couldn't lock it because it's already locked:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void coroutine_guard_unlock(void * _context)
|
||||
{
|
||||
struct coroutine_context * context = _context;
|
||||
|
||||
if (DEBUG) fprintf(stderr, "coroutine_guard_unlock:pthread_mutex_unlock\n");
|
||||
|
||||
check("coroutine_guard_unlock:pthread_mutex_unlock",
|
||||
pthread_mutex_unlock(&context->shared->guard)
|
||||
);
|
||||
}
|
||||
|
||||
static
|
||||
void coroutine_wait(struct coroutine_context *context)
|
||||
{
|
||||
if (DEBUG) fprintf(stderr, "coroutine_wait:pthread_mutex_lock(guard=%p is_locked=%d)\n", &context->shared->guard, is_locked(&context->shared->guard));
|
||||
check("coroutine_wait:pthread_mutex_lock",
|
||||
pthread_mutex_lock(&context->shared->guard)
|
||||
);
|
||||
|
||||
if (DEBUG) fprintf(stderr, "coroutine_wait:pthread_mutex_unlock(guard)\n");
|
||||
pthread_mutex_unlock(&context->shared->guard);
|
||||
}
|
||||
|
||||
static
|
||||
void coroutine_trampoline_cleanup(void *_context) {
|
||||
struct coroutine_context * context = _context;
|
||||
coroutine_release(context);
|
||||
}
|
||||
|
||||
void * coroutine_trampoline(void * _context)
|
||||
{
|
||||
struct coroutine_context * context = _context;
|
||||
assert(context->shared);
|
||||
|
||||
pthread_cleanup_push(coroutine_trampoline_cleanup, context);
|
||||
|
||||
coroutine_wait(context);
|
||||
|
||||
context->start(context->from, context);
|
||||
|
||||
pthread_cleanup_pop(1);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static
|
||||
int coroutine_create_thread(struct coroutine_context *context)
|
||||
{
|
||||
int result;
|
||||
|
||||
pthread_attr_t attr;
|
||||
result = pthread_attr_init(&attr);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = pthread_attr_setstack(&attr, context->stack, (size_t)context->size);
|
||||
if (result != 0) {
|
||||
pthread_attr_destroy(&attr);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = pthread_cond_init(&context->schedule, NULL);
|
||||
if (result != 0) {
|
||||
pthread_attr_destroy(&attr);
|
||||
return result;
|
||||
}
|
||||
|
||||
result = pthread_create(&context->id, &attr, coroutine_trampoline, context);
|
||||
if (result != 0) {
|
||||
pthread_attr_destroy(&attr);
|
||||
if (DEBUG) fprintf(stderr, "coroutine_create_thread:pthread_cond_destroy(%p)\n", &context->schedule);
|
||||
pthread_cond_destroy(&context->schedule);
|
||||
return result;
|
||||
}
|
||||
|
||||
context->shared->count += 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct coroutine_context * coroutine_transfer(struct coroutine_context * current, struct coroutine_context * target)
|
||||
{
|
||||
assert(current->shared);
|
||||
|
||||
struct coroutine_context * previous = target->from;
|
||||
target->from = current;
|
||||
|
||||
if (DEBUG) fprintf(stderr, "coroutine_transfer:pthread_mutex_lock(guard=%p is_locked=%d)\n", ¤t->shared->guard, is_locked(¤t->shared->guard));
|
||||
pthread_mutex_lock(¤t->shared->guard);
|
||||
pthread_cleanup_push(coroutine_guard_unlock, current);
|
||||
|
||||
// First transfer:
|
||||
if (target->shared == NULL) {
|
||||
target->shared = current->shared;
|
||||
|
||||
if (DEBUG) fprintf(stderr, "coroutine_transfer:coroutine_create_thread...\n");
|
||||
if (coroutine_create_thread(target)) {
|
||||
if (DEBUG) fprintf(stderr, "coroutine_transfer:coroutine_create_thread failed\n");
|
||||
target->shared = NULL;
|
||||
target->from = previous;
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
if (DEBUG) fprintf(stderr, "coroutine_transfer:pthread_cond_signal(target)\n");
|
||||
pthread_cond_signal(&target->schedule);
|
||||
}
|
||||
|
||||
// A side effect of acting upon a cancellation request while in a condition wait is that the mutex is (in effect) re-acquired before calling the first cancellation cleanup handler. If cancelled, pthread_cond_wait immediately invokes cleanup handlers.
|
||||
if (DEBUG) fprintf(stderr, "coroutine_transfer:pthread_cond_wait(schedule=%p, guard=%p, is_locked=%d)\n", ¤t->schedule, ¤t->shared->guard, is_locked(¤t->shared->guard));
|
||||
check("coroutine_transfer:pthread_cond_wait",
|
||||
pthread_cond_wait(¤t->schedule, ¤t->shared->guard)
|
||||
);
|
||||
|
||||
if (DEBUG) fprintf(stderr, "coroutine_transfer:pthread_cleanup_pop\n");
|
||||
pthread_cleanup_pop(1);
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
// Apparently required for FreeBSD:
|
||||
pthread_testcancel();
|
||||
#endif
|
||||
|
||||
target->from = previous;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
static
|
||||
void coroutine_join(struct coroutine_context * context) {
|
||||
if (DEBUG) fprintf(stderr, "coroutine_join:pthread_cancel\n");
|
||||
check("coroutine_join:pthread_cancel",
|
||||
pthread_cancel(context->id)
|
||||
);
|
||||
|
||||
if (DEBUG) fprintf(stderr, "coroutine_join:pthread_join\n");
|
||||
check("coroutine_join:pthread_join",
|
||||
pthread_join(context->id, NULL)
|
||||
);
|
||||
|
||||
if (DEBUG) fprintf(stderr, "coroutine_join:pthread_join done\n");
|
||||
}
|
||||
|
||||
void coroutine_destroy(struct coroutine_context * context)
|
||||
{
|
||||
if (DEBUG) fprintf(stderr, "coroutine_destroy\n");
|
||||
|
||||
assert(context);
|
||||
|
||||
// We are already destroyed or never created:
|
||||
if (context->shared == NULL) return;
|
||||
|
||||
if (context == context->shared->main) {
|
||||
context->shared->main = NULL;
|
||||
coroutine_release(context);
|
||||
} else {
|
||||
coroutine_join(context);
|
||||
assert(context->shared == NULL);
|
||||
}
|
||||
}
|
63
coroutine/pthread/Context.h
Normal file
63
coroutine/pthread/Context.h
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* This file is part of the "Coroutine" project and released under the MIT License.
|
||||
*
|
||||
* Created by Samuel Williams on 24/6/2021.
|
||||
* Copyright, 2021, by Samuel Williams.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define COROUTINE void
|
||||
|
||||
#define COROUTINE_PTHREAD_CONTEXT
|
||||
|
||||
#ifdef HAVE_STDINT_H
|
||||
#include <stdint.h>
|
||||
#if INTPTR_MAX <= INT32_MAX
|
||||
#define COROUTINE_LIMITED_ADDRESS_SPACE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
struct coroutine_context;
|
||||
|
||||
struct coroutine_shared
|
||||
{
|
||||
pthread_mutex_t guard;
|
||||
struct coroutine_context * main;
|
||||
|
||||
size_t count;
|
||||
};
|
||||
|
||||
typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
||||
struct coroutine_context
|
||||
{
|
||||
struct coroutine_shared * shared;
|
||||
|
||||
coroutine_start start;
|
||||
void *argument;
|
||||
|
||||
void *stack;
|
||||
size_t size;
|
||||
|
||||
pthread_t id;
|
||||
pthread_cond_t schedule;
|
||||
struct coroutine_context * from;
|
||||
};
|
||||
|
||||
void coroutine_initialize_main(struct coroutine_context * context);
|
||||
|
||||
void coroutine_initialize(
|
||||
struct coroutine_context *context,
|
||||
coroutine_start start,
|
||||
void *stack,
|
||||
size_t size
|
||||
);
|
||||
|
||||
struct coroutine_context * coroutine_transfer(struct coroutine_context * current, struct coroutine_context * target);
|
||||
|
||||
void coroutine_destroy(struct coroutine_context * context);
|
|
@ -12,6 +12,7 @@ enum {COROUTINE_REGISTERS = 0xd0 / 8};
|
|||
struct coroutine_context
|
||||
{
|
||||
void **stack_pointer;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#if defined(__sun) && !defined(__EXTENSIONS__)
|
||||
#define __EXTENSIONS__
|
||||
#endif
|
||||
|
||||
#include "Context.h"
|
||||
|
||||
void coroutine_trampoline(void * _start, void * _context)
|
||||
|
|
|
@ -27,6 +27,7 @@ struct coroutine_context
|
|||
{
|
||||
ucontext_t state;
|
||||
struct coroutine_context * from;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
|
|
@ -24,6 +24,7 @@ enum {COROUTINE_REGISTERS = 4};
|
|||
struct coroutine_context
|
||||
{
|
||||
void **stack_pointer;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
typedef void(__fastcall * coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
|
|
@ -25,6 +25,7 @@ enum {
|
|||
struct coroutine_context
|
||||
{
|
||||
void **stack_pointer;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
typedef void(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
|
||||
|
|
|
@ -23,6 +23,7 @@ enum {COROUTINE_REGISTERS = 4};
|
|||
struct coroutine_context
|
||||
{
|
||||
void **stack_pointer;
|
||||
void *argument;
|
||||
};
|
||||
|
||||
typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self) __attribute__((fastcall));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue