Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add aco_yield_to() #32

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Note: Please use [releases][github-release] instead of the `master` to build the
* [aco_create](#aco_create)
* [aco_resume](#aco_resume)
* [aco_yield](#aco_yield)
* [aco_yield_to](#aco_yield_to)
* [aco_get_co](#aco_get_co)
* [aco_get_arg](#aco_get_arg)
* [aco_exit](#aco_exit)
Expand Down Expand Up @@ -265,7 +266,7 @@ One of the rules in libaco is to call `aco_exit()` to terminate the execution of

You could also define your own protector to substitute the default one (to do some customized "last words" stuff). But no matter in what case, the process will be aborted after the protector was executed. The `test_aco_tutorial_5.c` shows how to define the customized last word function.

The last example is a simple coroutine scheduler in `test_aco_tutorial_6.c`.
The example `test_aco_tutorial_6.c` is a simple coroutine scheduler. And the last example, namely `test_aco_tutorial_7.c`, is the same scheduler using `aco_yield_to`.

# API

Expand Down Expand Up @@ -375,6 +376,18 @@ Yield the execution of `co` and resume `co->main_co`. The caller of this functio

After the call of `aco_yield`, we name the state of the caller — `co` as "yielded".

## aco_yield_to

```c
void aco_yield_to(aco_t* resume_co);
```

Yield the execution of `co` and resume `resume_co`. The caller of this function must be a non-main co (i.e. `co->main_co` is not NULL). If `co` is different of `resume_co`, they cannot share the same stack and `co->main_co` and `resume_co->main_co` must be equal.

After the call of `aco_yield_to`, we name the state of the caller — `co` as "yielded".

`aco_yield_to()` is useful when a non-main co wants to yield to another co with a single context switch. Without `aco_yield_to()`, one has to go through two switches: one switch back to main co and another switch to the desired non-main co.

## aco_get_co

```c
Expand Down
220 changes: 128 additions & 92 deletions aco.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,121 +361,157 @@ aco_t* aco_create(
}

aco_attr_no_asan
void aco_resume(aco_t* resume_co){
assert(resume_co != NULL && resume_co->main_co != NULL
&& resume_co->is_end == 0
);
if(resume_co->share_stack->owner != resume_co){
if(resume_co->share_stack->owner != NULL){
aco_t* owner_co = resume_co->share_stack->owner;
assert(owner_co->share_stack == resume_co->share_stack);
static void aco_own_stack(aco_t* co){
if(co->share_stack->owner != NULL){
aco_t* owner_co = co->share_stack->owner;
assert(owner_co->share_stack == co->share_stack);
#if defined(__i386__) || defined(__x86_64__)
assert(
(
(uintptr_t)(owner_co->share_stack->align_retptr)
>=
(uintptr_t)(owner_co->reg[ACO_REG_IDX_SP])
)
&&
(
(uintptr_t)(owner_co->share_stack->align_highptr)
-
(uintptr_t)(owner_co->share_stack->align_limit)
<=
(uintptr_t)(owner_co->reg[ACO_REG_IDX_SP])
)
);
owner_co->save_stack.valid_sz =
assert(
(
(uintptr_t)(owner_co->share_stack->align_retptr)
>=
(uintptr_t)(owner_co->reg[ACO_REG_IDX_SP])
)
&&
(
(uintptr_t)(owner_co->share_stack->align_highptr)
-
(uintptr_t)(owner_co->reg[ACO_REG_IDX_SP]);
if(owner_co->save_stack.sz < owner_co->save_stack.valid_sz){
free(owner_co->save_stack.ptr);
owner_co->save_stack.ptr = NULL;
while(1){
owner_co->save_stack.sz = owner_co->save_stack.sz << 1;
assert(owner_co->save_stack.sz > 0);
if(owner_co->save_stack.sz >= owner_co->save_stack.valid_sz){
break;
}
(uintptr_t)(owner_co->share_stack->align_limit)
<=
(uintptr_t)(owner_co->reg[ACO_REG_IDX_SP])
)
);
owner_co->save_stack.valid_sz =
(uintptr_t)(owner_co->share_stack->align_retptr)
-
(uintptr_t)(owner_co->reg[ACO_REG_IDX_SP]);
if(owner_co->save_stack.sz < owner_co->save_stack.valid_sz){
free(owner_co->save_stack.ptr);
owner_co->save_stack.ptr = NULL;
while(1){
owner_co->save_stack.sz = owner_co->save_stack.sz << 1;
assert(owner_co->save_stack.sz > 0);
if(owner_co->save_stack.sz >= owner_co->save_stack.valid_sz){
break;
}
owner_co->save_stack.ptr = malloc(owner_co->save_stack.sz);
assertalloc_ptr(owner_co->save_stack.ptr);
}
// TODO: optimize the performance penalty of memcpy function call
// for very short memory span
if(owner_co->save_stack.valid_sz > 0) {
#ifdef __x86_64__
aco_amd64_optimized_memcpy_drop_in(
owner_co->save_stack.ptr,
owner_co->reg[ACO_REG_IDX_SP],
owner_co->save_stack.valid_sz
);
#else
memcpy(
owner_co->save_stack.ptr,
owner_co->reg[ACO_REG_IDX_SP],
owner_co->save_stack.valid_sz
);
#endif
owner_co->save_stack.ct_save++;
}
if(owner_co->save_stack.valid_sz > owner_co->save_stack.max_cpsz){
owner_co->save_stack.max_cpsz = owner_co->save_stack.valid_sz;
}
owner_co->share_stack->owner = NULL;
owner_co->share_stack->align_validsz = 0;
#else
#error "platform no support yet"
#endif
owner_co->save_stack.ptr = malloc(owner_co->save_stack.sz);
assertalloc_ptr(owner_co->save_stack.ptr);
}
assert(resume_co->share_stack->owner == NULL);
#if defined(__i386__) || defined(__x86_64__)
assert(
resume_co->save_stack.valid_sz
<=
resume_co->share_stack->align_limit - sizeof(void*)
);
// TODO: optimize the performance penalty of memcpy function call
// for very short memory span
if(resume_co->save_stack.valid_sz > 0) {
#ifdef __x86_64__
if(owner_co->save_stack.valid_sz > 0) {
#ifdef __x86_64__
aco_amd64_optimized_memcpy_drop_in(
(void*)(
(uintptr_t)(resume_co->share_stack->align_retptr)
-
resume_co->save_stack.valid_sz
),
resume_co->save_stack.ptr,
resume_co->save_stack.valid_sz
owner_co->save_stack.ptr,
owner_co->reg[ACO_REG_IDX_SP],
owner_co->save_stack.valid_sz
);
#else
#else
memcpy(
(void*)(
(uintptr_t)(resume_co->share_stack->align_retptr)
-
resume_co->save_stack.valid_sz
),
resume_co->save_stack.ptr,
resume_co->save_stack.valid_sz
owner_co->save_stack.ptr,
owner_co->reg[ACO_REG_IDX_SP],
owner_co->save_stack.valid_sz
);
#endif
resume_co->save_stack.ct_restore++;
#endif
owner_co->save_stack.ct_save++;
}
if(resume_co->save_stack.valid_sz > resume_co->save_stack.max_cpsz){
resume_co->save_stack.max_cpsz = resume_co->save_stack.valid_sz;
if(owner_co->save_stack.valid_sz > owner_co->save_stack.max_cpsz){
owner_co->save_stack.max_cpsz = owner_co->save_stack.valid_sz;
}
resume_co->share_stack->align_validsz = resume_co->save_stack.valid_sz + sizeof(void*);
resume_co->share_stack->owner = resume_co;
owner_co->share_stack->owner = NULL;
owner_co->share_stack->align_validsz = 0;
#else
#error "platform no support yet"
#endif
}
assert(co->share_stack->owner == NULL);
#if defined(__i386__) || defined(__x86_64__)
assert(
co->save_stack.valid_sz
<=
co->share_stack->align_limit - sizeof(void*)
);
// TODO: optimize the performance penalty of memcpy function call
// for very short memory span
if(co->save_stack.valid_sz > 0) {
#ifdef __x86_64__
aco_amd64_optimized_memcpy_drop_in(
(void*)(
(uintptr_t)(co->share_stack->align_retptr)
-
co->save_stack.valid_sz
),
co->save_stack.ptr,
co->save_stack.valid_sz
);
#else
memcpy(
(void*)(
(uintptr_t)(co->share_stack->align_retptr)
-
co->save_stack.valid_sz
),
co->save_stack.ptr,
co->save_stack.valid_sz
);
#endif
co->save_stack.ct_restore++;
}
if(co->save_stack.valid_sz > co->save_stack.max_cpsz){
co->save_stack.max_cpsz = co->save_stack.valid_sz;
}
co->share_stack->align_validsz = co->save_stack.valid_sz + sizeof(void*);
co->share_stack->owner = co;
#else
#error "platform no support yet"
#endif
}

aco_attr_no_asan
void aco_resume(aco_t* resume_co){
assert(resume_co != NULL && resume_co->main_co != NULL
&& resume_co->is_end == 0
);
if(resume_co->share_stack->owner != resume_co){
aco_own_stack(resume_co);
}
aco_gtls_co = resume_co;
acosw(resume_co->main_co, resume_co);
aco_gtls_co = resume_co->main_co;
}

aco_attr_no_asan
void aco_yield_to(aco_t* resume_co){
if(aco_unlikely(resume_co == NULL || resume_co->main_co == NULL
|| resume_co->is_end != 0)){
// An error message here is helpful because
// we are running in a non-main co
fprintf(stderr, "Aborting: %s(resume_co=%p): resume_co is not valid: %s:%d\n",
__PRETTY_FUNCTION__, resume_co, __FILE__, __LINE__);
abort();
}
aco_t* yield_co = aco_gtls_co;
if(aco_unlikely(resume_co == yield_co)){
// Nothing to do
return;
}
if(aco_unlikely(resume_co->main_co != yield_co->main_co
// A co cannot save its own stack
|| resume_co->share_stack == yield_co->share_stack)){
fprintf(stderr, "Aborting: %s(resume_co=%p): resume_co has a different main co or share the same stack: %s:%d\n",
__PRETTY_FUNCTION__, resume_co, __FILE__, __LINE__);
abort();
}
// The test below is unlikely because
// aco_yield_to() is often called between two non-main cos
if(aco_unlikely(resume_co->share_stack->owner != resume_co)){
aco_own_stack(resume_co);
}
aco_gtls_co = resume_co;
acosw(yield_co, resume_co);
}

void aco_destroy(aco_t* co){
assertptr(co);
if(aco_is_main_co(co)){
Expand Down
3 changes: 3 additions & 0 deletions aco.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ extern __thread aco_t* aco_gtls_co;
aco_attr_no_asan
extern void aco_resume(aco_t* resume_co);

aco_attr_no_asan
void aco_yield_to(aco_t* resume_co);

//extern void aco_yield1(aco_t* yield_co);
#define aco_yield1(yield_co) do { \
aco_assertptr((yield_co)); \
Expand Down
1 change: 1 addition & 0 deletions make.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ test_aco_tutorial_3 -lpthread
test_aco_tutorial_4
test_aco_tutorial_5
test_aco_tutorial_6
test_aco_tutorial_7
test_aco_synopsis
test_aco_benchmark
'''
Expand Down
Loading