JesseBusman /
ARM_C_Coroutines
Light-weight and blazing fast C coroutines for ARMv7 processors. Read the wiki: https://github.com/JesseBusman/ARM_C_Coroutines/wiki
33/100 healthLoading repository dataβ¦
hnes / repository
A blazing fast and lightweight C asymmetric coroutine library π β πβ π
A transparent discovery signal based on current public GitHub metadata.
This score does not audit code, security, maintainers, documentation quality, or suitability. Verify the repository and its current documentation before adoption.
libaco - A blazing fast and lightweight C asymmetric coroutine library.
The code name of this project is Arkenstone π
Asymmetric COroutine & Arkenstone is the reason why it's been named aco.
Currently supports Sys V ABI of Intel386 and x86-64.
Here is a brief summary of this project:
The phrase "fastest" in above means the fastest context switching implementation which complies to the Sys V ABI of Intel386 or AMD64.
Issues and PRs are welcome πππ
Note: Please use releases instead of the master to build the final binary.
Besides this readme, you could also visit the documentation from https://libaco.org/docs. Please follow this readme if there are any differences because the documentation on the website may be lagging behind from this readme.
Production ready.
#include "aco.h"
#include <stdio.h>
// this header would override the default C `assert`;
// you may refer the "API : MACROS" part for more details.
#include "aco_assert_override.h"
void foo(int ct) {
printf("co: %p: yield to main_co: %d\n", aco_get_co(), *((int*)(aco_get_arg())));
aco_yield();
*((int*)(aco_get_arg())) = ct + 1;
}
void co_fp0() {
printf("co: %p: entry: %d\n", aco_get_co(), *((int*)(aco_get_arg())));
int ct = 0;
while(ct < 6){
foo(ct);
ct++;
}
printf("co: %p: exit to main_co: %d\n", aco_get_co(), *((int*)(aco_get_arg())));
aco_exit();
}
int main() {
aco_thread_init(NULL);
aco_t* main_co = aco_create(NULL, NULL, 0, NULL, NULL);
aco_share_stack_t* sstk = aco_share_stack_new(0);
int co_ct_arg_point_to_me = 0;
aco_t* co = aco_create(main_co, sstk, 0, co_fp0, &co_ct_arg_point_to_me);
int ct = 0;
while(ct < 6){
assert(co->is_end == 0);
printf("main_co: yield to co: %p: %d\n", co, ct);
aco_resume(co);
assert(co_ct_arg_point_to_me == ct);
ct++;
}
printf("main_co: yield to co: %p: %d\n", co, ct);
aco_resume(co);
assert(co_ct_arg_point_to_me == ct);
assert(co->is_end);
printf("main_co: destroy and exit\n");
aco_destroy(co);
co = NULL;
aco_share_stack_destroy(sstk);
sstk = NULL;
aco_destroy(main_co);
main_co = NULL;
return 0;
}
# default build
$ gcc -g -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
$ ./test_aco_synopsis
main_co: yield to co: 0x1887120: 0
co: 0x1887120: entry: 0
co: 0x1887120: yield to main_co: 0
main_co: yield to co: 0x1887120: 1
co: 0x1887120: yield to main_co: 1
main_co: yield to co: 0x1887120: 2
co: 0x1887120: yield to main_co: 2
main_co: yield to co: 0x1887120: 3
co: 0x1887120: yield to main_co: 3
main_co: yield to co: 0x1887120: 4
co: 0x1887120: yield to main_co: 4
main_co: yield to co: 0x1887120: 5
co: 0x1887120: yield to main_co: 5
main_co: yield to co: 0x1887120: 6
co: 0x1887120: exit to main_co: 6
main_co: destroy and exit
# i386
$ gcc -g -m32 -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
# share fpu and mxcsr env
$ gcc -g -D ACO_CONFIG_SHARE_FPU_MXCSR_ENV -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
# with valgrind friendly support
$ gcc -g -D ACO_USE_VALGRIND -O2 acosw.S aco.c test_aco_synopsis.c -o test_aco_synopsis
$ valgrind --leak-check=full --tool=memcheck ./test_aco_synopsis
For more information you may refer to the "Build and Test" part.
There are 4 basic elements of an ordinary execution state: {cpu_registers, code, heap, stack}.
Since the code information is indicated by ({E|R})?IP register, and the address of the memory allocated from heap is normally stored in the stack directly or indirectly, thus we could simplify the 4 elements into only 2 of them: {cpu_registers, stack}.
We define the main co as the coroutine who monopolizes the default stack of the current thread. And since the main co is the only user of this stack, we only need to save/restore the necessary cpu registers' state of the main co when it's been yielded-from/resumed-to (switched-out/switched-in).
Next, the definition of the non-main co is the coroutine whose execution stack is a stack which is not the default stack of the current thread and may be shared with the other non-main co. Thus the non-main co must have a private save stack memory buffer to save/restore its execution stack when it is been switched-out/switched-in (because the succeeding/preceding co may would/had use/used the share stack as its execution stack).
There is a special case of non-main co, that is standalone non-main co what we called in libaco: the share stack of the non-main coroutine has only one co user. Thus there is no need to do saving/restoring stuff of its private save stack when it is been switched-out/switched-in since there is no other co will touch the execution stack of the standalone non-main co except itself.
Finally, we get the big picture of libaco.
There is a "Proof of Correctness" part you may find really helpful if you want to dive into the internal of libaco or want to implement your own coroutine library.
It is also highly recommended to read the source code of the tutorials and benchmark next. The benchmark result is very impressive and enlightening too.
-m32The -m32 option of gcc could help you to build the i386 application of libaco on a x86_64 machine.
ACO_CONFIG_SHARE_FPU_MXCSR_ENVYou could define the global C macro ACO_CONFIG_SHARE_FPU_MXCSR_ENV to speed up the performance of context switching between coroutines slightly if none of your code would change the control words of FPU and MXCSR. If the macro is not defined, all the co would maintain its own copy of the FPU and MXCSR control words. It is recommended to always define this macro globally since it is very rare that one function needs to set its own special env of FPU or MXCSR instead of using the default env defined by the ISO C. But you may not need to define this macro if you are not sure of it.
ACO_USE_VALGRINDIf you want to use the tool memcheck of valgrind to test the application, then you may need to define the global C macro ACO_USE_VALGRIND to enable the friendly support of valgrind in libaco. But it is not recommended to define this macro in the final release build for the performance reason. You may also need to install the valgrind headers (package name is "valgrind-devel" in centos for example) to build libaco application with C macro ACO_USE_VALGRIND defined. (The memcheck of valgrind only works well with the standalone co currently. In the case of the shared stack used by more than one non-main co, the memcheck of valgrind would generate many false positive reports. For more information you may refer to "test_aco_tutorial_6.c".)
ACO_USE_ASANThe global C macro ACO_USE_ASAN would enable the friendly support of Address Sanitizer in libaco (support both gcc and clang).
To build the test suites of libaco:
$ mkdir output
$ bash make.sh
There is also some detailed options in make.sh:
$bash make.sh -h
Usage: make.sh [-o <no-m32|no-valgrind>] [-h]
Example:
# default build
bash make.sh
# build without the i386 binary output
bash make.sh -o no-m32
# build without the valgrind supported binary output
bash make.sh -o no-valgrind
# build without the valgrind supported and i386 binary output
bash make.sh -o no-valgrind -o no-m32
In short, using -o no-valgrind if you have no valgrind headers installed, -o no-m32 if you have no 32-bit gcc development tools installed on a AMD64 host.
On MacOS, you need to replace the default sed and grep commands of MacOS with the GNU sed and grep to run make.sh and test.sh (such requirement would be removed in the future):
$ brew install grep --with-default-names
$ brew install gnu-sed --with-default-names
$ cd output
$ bash ../test.sh
The test_aco_tutorial_0.c in this repository shows the basic usage of libaco. There is only one main co and one standalone non-main co in this tutorial. The comments in the source code is also very helpful.
The test_aco_tutorial_1.c shows the usage of some statistics of non-main co. The data structure of aco_t is very clear and is defined in aco.h.
There are one main co, one standalone non-main co and two non-main co (pointing to the same share stack) in test_aco_tutorial_2.c.
The test_aco_tutorial_3.c shows how to use li
Selected from shared topics, language and repository descriptionβnot editorial ratings.
JesseBusman /
Light-weight and blazing fast C coroutines for ARMv7 processors. Read the wiki: https://github.com/JesseBusman/ARM_C_Coroutines/wiki
33/100 health