diff --git a/README.md b/README.md index 77c2da3..f7acd79 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 @@ -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 diff --git a/aco.c b/aco.c index 812914c..fe97f48 100644 --- a/aco.c +++ b/aco.c @@ -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)){ diff --git a/aco.h b/aco.h index 97ea2ec..f8cd41e 100644 --- a/aco.h +++ b/aco.h @@ -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)); \ diff --git a/make.sh b/make.sh index 82f81bc..7054d87 100644 --- a/make.sh +++ b/make.sh @@ -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 ''' diff --git a/test_aco_tutorial_7.c b/test_aco_tutorial_7.c new file mode 100644 index 0000000..b221b35 --- /dev/null +++ b/test_aco_tutorial_7.c @@ -0,0 +1,112 @@ +// Copyright 2018 Sen Han <00hnes@gmail.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. + +// A pretty simple scheduler demo using aco_yield_to(). + +#include "aco.h" +#include +#include +#include +#include "aco_assert_override.h" + +size_t curr_co_amount; +size_t curr_co_index; +aco_t** coarray; + +void yield_to_next_co(){ + assert(curr_co_amount > 0); + curr_co_index = (curr_co_index + 1) % curr_co_amount; + aco_yield_to(coarray[curr_co_index]); +} + +void co_fp0(){ + int ct = 0; + int loop_ct = (int)((uintptr_t)(aco_get_co()->arg)); + if(loop_ct < 0){ + loop_ct = 0; + } + while(ct < loop_ct){ + yield_to_next_co(); + ct++; + } + aco_exit(); +} + +int main() { + aco_thread_init(NULL); + + time_t seed_t = time(NULL); + assert((time_t)-1 != seed_t); + srand(seed_t); + + size_t co_amount = 100; + curr_co_amount = co_amount; + + // create co + assert(co_amount > 0); + aco_t* main_co = aco_create(NULL, NULL, 0, NULL, NULL); + // NOTE: size_t_safe_mul + coarray = (aco_t**) malloc(sizeof(void*) * co_amount); + assertptr(coarray); + memset(coarray, 0, sizeof(void*) * co_amount); + size_t ct = 0; + while(ct < co_amount){ + aco_share_stack_t* private_sstk = aco_share_stack_new2( + 0, ct % 2 + ); + coarray[ct] = aco_create( + main_co, private_sstk, 0, co_fp0, + (void*)((uintptr_t)rand() % 1000) + ); + private_sstk = NULL; + ct++; + } + + // naive scheduler + printf("scheduler start: co_amount:%zu\n", co_amount); + aco_t* curr_co = coarray[curr_co_index]; + while(curr_co_amount > 0){ + aco_resume(curr_co); + // Update curr_co because aco_yield_to() may have changed it + curr_co = coarray[curr_co_index]; + assert(curr_co->is_end != 0); + printf("aco_destroy: co currently at:%zu\n", curr_co_index); + aco_share_stack_t* private_sstk = curr_co->share_stack; + aco_destroy(curr_co); + aco_share_stack_destroy(private_sstk); + private_sstk = NULL; + curr_co_amount--; + if(curr_co_index < curr_co_amount){ + coarray[curr_co_index] = coarray[curr_co_amount]; + }else{ + curr_co_index = 0; + } + coarray[curr_co_amount] = NULL; + curr_co = coarray[curr_co_index]; + } + + // co cleaning + ct = 0; + while(ct < co_amount){ + assert(coarray[ct] == NULL); + ct++; + } + aco_destroy(main_co); + main_co = NULL; + free(coarray); + + printf("sheduler exit"); + + return 0; +}