Download
213 lines 6.5 KiBCModified 2025-08-25 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
// A coroutine library
// Copyright 2018 Sen Han <00hnes@gmail.com>
// Modifications copyright 2025 Bruce Hill <bruce@bruce-hill.com>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#pragma once

#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>

#ifdef ACO_USE_VALGRIND
#include <valgrind/valgrind.h>
#endif

#ifdef __cplusplus
extern "C" {
#endif

#define ACO_VERSION_MAJOR 2
#define ACO_VERSION_MINOR 0
#define ACO_VERSION_PATCH 0

#ifdef __i386__
#define ACO_REG_IDX_RETADDR 0
#define ACO_REG_IDX_SP 1
#define ACO_REG_IDX_BP 2
#define ACO_REG_IDX_ARG1 0
#define ACO_REG_IDX_FPU 6
#elif __x86_64__
#define ACO_REG_IDX_RETADDR 4
#define ACO_REG_IDX_SP 5
#define ACO_REG_IDX_BP 7
#define ACO_REG_IDX_EDI 8
#define ACO_REG_IDX_FPU 8
#else
#error "platform not supported yet"
#endif

typedef struct {
    void *ptr;
    size_t sz;
    size_t valid_sz;
    // max copy size in bytes
    size_t max_cpsz;
    // copy from shared stack to this saved stack
    size_t ct_save;
    // copy from this saved stack to shared stack
    size_t ct_restore;
} aco_saved_stack_t;

struct aco_s;
typedef struct aco_s aco_t;

typedef struct {
    void *ptr;
    size_t sz;
    void *align_highptr;
    void *align_retptr;
    size_t align_validsz;
    size_t align_limit;
    aco_t *owner;

    bool guard_page_enabled;
    void *real_ptr;
    size_t real_sz;

#ifdef ACO_USE_VALGRIND
    unsigned long valgrind_stk_id;
#endif
} aco_shared_stack_t;

typedef void (*aco_cofuncp_t)(void *);

struct aco_s {
    // cpu registers' state
#ifdef __i386__
#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
    void *reg[6];
#else
    void *reg[8];
#endif
#elif __x86_64__
#ifdef ACO_CONFIG_SHARE_FPU_MXCSR_ENV
    void *reg[8];
#else
    void *reg[9];
#endif
#else
#error "platform not supported yet"
#endif
    aco_t *main_co;
    void *arg;
    bool is_finished;

    aco_cofuncp_t fp;

    aco_saved_stack_t saved_stack;
    aco_shared_stack_t *shared_stack;
};

#define aco_likely(x) (__builtin_expect(!!(x), 1))

#define aco_unlikely(x) (__builtin_expect(!!(x), 0))

#define aco_assert(EX) ((aco_likely(EX)) ? ((void)0) : (abort()))

#define aco_assertptr(ptr) ((aco_likely((ptr) != NULL)) ? ((void)0) : (abort()))

#if defined(aco_attr_no_asan)
#error "aco_attr_no_asan already defined"
#endif
#if defined(ACO_USE_ASAN)
#if defined(__has_feature)
#if __has_feature(__address_sanitizer__)
#define aco_attr_no_asan __attribute__((__no_sanitize_address__))
#endif
#endif
#if defined(__SANITIZE_ADDRESS__) && !defined(aco_attr_no_asan)
#define aco_attr_no_asan __attribute__((__no_sanitize_address__))
#endif
#endif
#ifndef aco_attr_no_asan
#define aco_attr_no_asan
#endif

void aco_runtime_test(void);

void aco_set_allocator(void *(*alloc)(size_t), void (*dealloc)(void *));

void aco_thread_init(aco_cofuncp_t last_word_co_fp);

void aco_yield_asm(aco_t *from_co, aco_t *to_co) __asm__("aco_yield_asm"); // asm

void aco_save_fpucw_mxcsr(void *p) __asm__("aco_save_fpucw_mxcsr"); // asm

void aco_funcp_protector_asm(void) __asm__("aco_funcp_protector_asm"); // asm

void aco_funcp_protector(void);

aco_shared_stack_t *aco_shared_stack_new(size_t sz);

aco_shared_stack_t *aco_shared_stack_new2(size_t sz, bool guard_page_enabled);

void aco_shared_stack_destroy(aco_shared_stack_t *sstk);

aco_t *aco_create(aco_t *main_co, aco_shared_stack_t *shared_stack, size_t saved_stack_sz, aco_cofuncp_t fp, void *arg);

// aco's Global Thread Local Storage variable `co`
#ifdef __TINYC__
#error "TinyCC doesn't support thread-local storage!"
#else
extern __thread aco_t *aco_gtls_co;
#endif

aco_attr_no_asan void aco_resume(aco_t *resume_co);

// void aco_yield1(aco_t* yield_co);
#define aco_yield1(yield_co)                                                                                           \
    do {                                                                                                               \
        aco_assertptr((yield_co));                                                                                     \
        aco_assertptr((yield_co)->main_co);                                                                            \
        aco_yield_asm((yield_co), (yield_co)->main_co);                                                                \
    } while (0)

#define aco_yield() aco_yield1(aco_gtls_co)

#define aco_get_arg() (aco_gtls_co->arg)

#define aco_get_co()                                                                                                   \
    ({                                                                                                                 \
        (void)0;                                                                                                       \
        aco_gtls_co;                                                                                                   \
    })

void aco_destroy(aco_t *co);

#define aco_is_main_co(co) ({ ((co)->main_co) == NULL; })

#define aco_exit1(co)                                                                                                  \
    do {                                                                                                               \
        (co)->is_finished = true;                                                                                      \
        aco_assert((co)->shared_stack->owner == (co));                                                                 \
        (co)->shared_stack->owner = NULL;                                                                              \
        (co)->shared_stack->align_validsz = 0;                                                                         \
        aco_yield1((co));                                                                                              \
        aco_assert(0);                                                                                                 \
    } while (0)

#define aco_exit() aco_exit1(aco_gtls_co)

void aco_exit_fn(void *);

#ifdef __cplusplus
}
#endif