From ee209f715147eeca535988eac20c7703702cc1d4 Mon Sep 17 00:00:00 2001 From: tidwall Date: Mon, 8 Apr 2024 10:21:38 -0700 Subject: [PATCH] first commit --- .github/CONTRIBUTING.md | 9 + .github/PULL_REQUEST_TEMPLATE.md | 16 + .github/workflows/main.yml | 17 + .gitignore | 16 + LICENSE | 19 + README.md | 580 ++ deps/.gitignore | 1 + deps/.package | 2 + deps/aat.h | 337 ++ deps/embed.sh | 38 + deps/sco.c | 2044 +++++++ deps/sco.h | 94 + deps/stack.c | 413 ++ deps/stack.h | 40 + deps/worker.c | 201 + deps/worker.h | 30 + docs/.gitignore | 1 + docs/API.md | 2389 ++++++++ docs/README.md | 9 + docs/TECHNICAL.md | 20 + docs/assets/API_foot.md | 3 + docs/assets/API_head.md | 150 + docs/assets/logo-dark.png | Bin 0 -> 41574 bytes docs/assets/logo-light.png | Bin 0 -> 46193 bytes docs/tools/README.md | 4 + docs/tools/build-api.sh | 36 + docs/tools/doxygen-md/README.md | 5 + docs/tools/doxygen-md/go.mod | 11 + docs/tools/doxygen-md/go.sum | 9 + docs/tools/doxygen-md/main.go | 834 +++ examples/README.md | 6 + examples/channel-buffering.c | 31 + examples/channels.c | 39 + examples/channels2.c | 40 + examples/coroutines.c | 28 + examples/echo-client.c | 28 + examples/echo-server.c | 35 + examples/generators.c | 27 + examples/select.c | 56 + examples/signals.c | 26 + neco.c | 8714 ++++++++++++++++++++++++++++++ neco.h | 452 ++ tests/README.md | 26 + tests/bench.c | 6 + tests/fail_counters.h | 112 + tests/panic.h | 29 + tests/priv_funcs.h | 44 + tests/run.sh | 245 + tests/signals.h | 17 + tests/test_basic.c | 221 + tests/test_cancel.c | 109 + tests/test_chan.c | 511 ++ tests/test_errors.c | 113 + tests/test_gen.c | 69 + tests/test_join.c | 78 + tests/test_misc.c | 33 + tests/test_net.c | 571 ++ tests/test_panic.c | 32 + tests/test_pipe.c | 63 + tests/test_rand.c | 73 + tests/test_signal.c | 222 + tests/test_sleep.c | 59 + tests/test_stream.c | 414 ++ tests/test_suspend.c | 57 + tests/test_sync.c | 589 ++ tests/test_wait.c | 35 + tests/test_work.c | 31 + tests/tests.h | 354 ++ 68 files changed, 20923 insertions(+) create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/main.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 deps/.gitignore create mode 100644 deps/.package create mode 100644 deps/aat.h create mode 100755 deps/embed.sh create mode 100644 deps/sco.c create mode 100644 deps/sco.h create mode 100644 deps/stack.c create mode 100644 deps/stack.h create mode 100644 deps/worker.c create mode 100644 deps/worker.h create mode 100644 docs/.gitignore create mode 100644 docs/API.md create mode 100644 docs/README.md create mode 100644 docs/TECHNICAL.md create mode 100644 docs/assets/API_foot.md create mode 100644 docs/assets/API_head.md create mode 100644 docs/assets/logo-dark.png create mode 100644 docs/assets/logo-light.png create mode 100644 docs/tools/README.md create mode 100755 docs/tools/build-api.sh create mode 100644 docs/tools/doxygen-md/README.md create mode 100644 docs/tools/doxygen-md/go.mod create mode 100644 docs/tools/doxygen-md/go.sum create mode 100644 docs/tools/doxygen-md/main.go create mode 100644 examples/README.md create mode 100644 examples/channel-buffering.c create mode 100644 examples/channels.c create mode 100644 examples/channels2.c create mode 100644 examples/coroutines.c create mode 100644 examples/echo-client.c create mode 100644 examples/echo-server.c create mode 100644 examples/generators.c create mode 100644 examples/select.c create mode 100644 examples/signals.c create mode 100644 neco.c create mode 100644 neco.h create mode 100644 tests/README.md create mode 100644 tests/bench.c create mode 100644 tests/fail_counters.h create mode 100644 tests/panic.h create mode 100644 tests/priv_funcs.h create mode 100755 tests/run.sh create mode 100644 tests/signals.h create mode 100644 tests/test_basic.c create mode 100644 tests/test_cancel.c create mode 100644 tests/test_chan.c create mode 100644 tests/test_errors.c create mode 100644 tests/test_gen.c create mode 100644 tests/test_join.c create mode 100644 tests/test_misc.c create mode 100644 tests/test_net.c create mode 100644 tests/test_panic.c create mode 100644 tests/test_pipe.c create mode 100644 tests/test_rand.c create mode 100644 tests/test_signal.c create mode 100644 tests/test_sleep.c create mode 100644 tests/test_stream.c create mode 100644 tests/test_suspend.c create mode 100644 tests/test_sync.c create mode 100644 tests/test_wait.c create mode 100644 tests/test_work.c create mode 100644 tests/tests.h diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..d07518f --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,9 @@ +### Contributing + +- **[Bugs]** If you find a bug, file an issue. Include a detailed description and steps to reproduce the problem. + +- **[New features]** I don't accept new features without prior discussion. If you or your company needs a specialized feature, make sure to express your willingness to fund the work and maintenance. + +- **[Pull requests]** Please do not open a pull request without filing an issue and/or discussing it with me beforehand. + +- **[Support]** My software is free and comes with no warranty. If you need priority support, contact me directly. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f1b9111 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +Please do not open a pull request without first filing an issue and/or discussing the feature directly with me. + +### Please ensure you adhere to every item in this list + +- [ ] This PR was pre-approved by the project maintainer +- [ ] I have self-reviewed the code +- [ ] I have added all necessary tests + +### Describe your changes + +Please provide detailed description of the changes. + +### Issue number and link + +Pull request require a prior issue with discussion. +Include the issue number of link here. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..78fa3b6 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,17 @@ +name: Vanilla C CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: test + run: tests/run.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a39b87 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.DS_Store +*.dSYM +a.out +.vscode/ +*.o +tmp.* +*.out.js +*.out.wasm +*.c.wasm +*.c.test +*.c.worker.js +*.profraw +*.profdata +*.testdata +*.sqlite3 +*.db diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ebd59d3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2024 Joshua J Baker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..937e2ff --- /dev/null +++ b/README.md @@ -0,0 +1,580 @@ + + +

+ + + + Neco + +

+

+API Reference +

+ +Neco is a C library that provides concurrency using coroutines. +It's small & fast, and intended to make concurrent I/O & network programming +easy. + +## Features + +- [Coroutines](docs/API.md#basic-operations): starting, sleeping, suspending, resuming, yielding, and joining. +- [Synchronization](docs/API.md#channels): channels, generators, mutexes, condition variables, and waitgroups. +- Support for [deadlines and cancelation](docs/API.md#deadlines-and-cancelation). +- [Posix friendly](docs/API.md#posix-wrappers) interface using file descriptors. +- Addtional APIs for [networking](docs/API.md#networking-utilities), +[signals](docs/API.md#signals), [random data](docs/API.md#random-number-generator), [streams](docs/API.md#streams-and-buffered-io), and [buffered I/O](docs/API.md#streams-and-buffered-io). +- Lightweight runtime with a fair and deterministic [scheduler](#the-scheduler). +- [Fast](#fast-context-switching) user-space context switching. Uses assembly in most cases. +- Cross-platform. Linux, Mac, FreeBSD. _(Also WebAssembly and Windows with [some limitations](#platform-notes))_. +- Single file amalgamation. No dependencies. +- [Test suite](tests/README.md) with 100% coverage using sanitizers and [Valgrind](https://valgrind.org). + +For a deeper dive, check out the [API reference](docs/API.md). + +It may also be worthwhile to see the [Bluebox](https://github.com/tidwall/bluebox) project for a +more complete example of using Neco, including benchmarks. + +## Goals + +- Give C programs fast single-threaded concurrency. +- To use a concurrency model that resembles the simplicity of pthreads or Go. +- Provide an API for concurrent networking and I/O. +- Make it easy to interop with existing Posix functions. + +It's a non-goal for Neco to provide a scalable multithreaded runtime, where the +coroutine scheduler is shared among multiple cpu cores. Or to use other +concurrency models like async/await. + +## Using + +Just drop the "neco.c" and "neco.h" files into your project. Uses standard C11 so most modern C compilers should work. + +```sh +cc -c neco.c +``` + +## Example 1 (Start a coroutine) + +A coroutine is started with the [`neco_start()`](docs/API.md#neco_start) function. + +When `neco_start()` is called for the first time it will initialize a Neco runtime and scheduler for the current thread, and then blocks until the coroutine and all child coroutines have terminated. + +```c +#include +#include "neco.h" + +void coroutine(int argc, void *argv[]) { + printf("main coroutine started\n"); +} + +int main(int argc, char *argv[]) { + neco_start(coroutine, 0); + return 0; +} +``` + +## Example 2 (Use neco_main instead of main) + +Optionally, [`neco_main()`](docs/API.md#neco_main) can be used in place of the standard `main()`. +This is for when the entirety of your program is intended to be run from only coroutines. +It [adjusts the behavior](docs/API.md#neco_main) of the program slightly to make development and error checking easier. + +```c +#include +#include "neco.h" + +int neco_main(int argc, char *argv[]) { + printf("main coroutine started\n"); + return 0; +} +``` + +## Example 3 (Multiple coroutines) + +Here we'll start two coroutines that continuously prints "tick" every one second and "tock" every two. + +```c +#include +#include "neco.h" + +void ticker(int argc, void *argv[]) { + while (1) { + neco_sleep(NECO_SECOND); + printf("tick\n"); + } +} + +void tocker(int argc, void *argv[]) { + while (1) { + neco_sleep(NECO_SECOND*2); + printf("tock\n"); + } +} + +int neco_main(int argc, char *argv[]) { + neco_start(ticker, 0); + neco_start(tocker, 0); + + // Keep the program alive for an hour. + neco_sleep(NECO_HOUR); + return 0; +} +``` + +## Example 4 (Coroutine arguments) + +A coroutine is like its own little program that accepts any number of arguments. + +```c +void coroutine(int argc, void *argv[]) +``` + +The arguments are a series of pointers passed to the coroutine. +All arguments are guaranteed to be in scope when the coroutine starts and until the first `neco_` function is called. This allows you an opportunity to validate and/or copy them. + +```c +#include +#include +#include +#include "neco.h" + +void coroutine(int argc, void *argv[]) { + + // All arguments are currently in scope and should be copied before first + // neco_*() function is called in this coroutine. + + int arg0 = *(int*)argv[0]; + int arg1 = *(int*)argv[1]; + int arg2 = *(int*)argv[2]; + char *arg3 = argv[3]; + char *arg4 = argv[4]; + + printf("arg0=%d, arg1=%d, arg2=%d, arg3=%s, arg4=%s\n", + arg0, arg1, arg2, arg3, arg4); + + neco_sleep(NECO_SECOND/2); + + // The arguments are no longer in scope and it's unsafe to use the argv + // variable any further. + + printf("second done\n"); + +} + +int neco_main(int argc, char *argv[]) { + + int arg0 = 0; + int *arg1 = malloc(sizeof(int)); + *arg1 = 1; + + neco_start(coroutine, 5, &arg0, arg1, &(int){2}, NULL, "hello world"); + free(arg2); + + neco_sleep(NECO_SECOND); + printf("first done\n"); + + return 0; +} +``` + +## Example 5 (Channels) + +A [channel](docs/API.md#channels) is a mechanism for communicating between two or more coroutines. + +Here we'll create a second coroutine that sends the message 'ping' to the first coroutine. + +```c +#include +#include +#include "neco.h" + +void coroutine(int argc, void *argv[]) { + neco_chan *messages = argv[0]; + + // Send a message of the 'messages' channel. + char *msg = "ping"; + neco_chan_send(messages, &msg); + + // This coroutine no longer needs the channel. + neco_chan_release(messages); +} + +int neco_main(int argc, char *argv[]) { + + // Create a new channel that is used to send 'char*' string messages. + neco_chan *messages; + neco_chan_make(&messages, sizeof(char*), 0); + + // Start a coroutine that sends messages over the channel. + // It's a good idea to use neco_chan_retain on a channel before using it + // in a new coroutine. This will avoid potential use-after-free bugs. + neco_chan_retain(messages); + neco_start(coroutine, 1, messages); + + // Receive the next incoming message. Here we’ll receive the "ping" + // message we sent above and print it out. + char *msg = NULL; + neco_chan_recv(messages, &msg); + printf("%s\n", msg); + + // This coroutine no longer needs the channel. + neco_chan_release(messages); + + return 0; +} +``` + +## Example 6 (Generators) + +A [generator](docs/API.md#generators) is like channel but is stricly bound to a coroutine and is intended to treat the coroutine like an iterator. + +```c +#include +#include +#include "neco.h" + +void coroutine(int argc, void *argv[]) { + // Yield each int to the caller, one at a time. + for (int i = 0; i < 10; i++) { + neco_gen_yield(&i); + } +} + +int neco_main(int argc, char *argv[]) { + + // Create a new generator coroutine that is used to send ints. + neco_gen *gen; + neco_gen_start(&gen, sizeof(int), coroutine, 0); + + // Iterate over each int until the generator is closed. + int i; + while (neco_gen_next(gen, &i) != NECO_CLOSED) { + printf("%d\n", i); + } + + // This coroutine no longer needs the generator. + neco_gen_release(gen); + return 0; +} +``` + +## Example 7 (Connect to server) + +Neco provides [`neco_dial()`](docs/API.md#neco_dial) for easily connecting +to server. + +Here we'll performing a (very simple) HTTP request which prints the homepage of +the http://example.com website. + +```c +#include +#include +#include "neco.h" + +int neco_main(int argc, char *argv[]) { + int fd = neco_dial("tcp", "example.com:80"); + if (fd < 0) { + printf("neco_dial: %s\n", neco_strerror(fd)); + return 0; + } + char req[] = "GET / HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: close\r\n" + "\r\n"; + neco_write(fd, req, sizeof(req)); + while (1) { + char buf[256]; + int n = neco_read(fd, buf, sizeof(buf)); + if (n <= 0) { + break; + } + printf("%.*s", n, buf); + } + close(fd); + return 0; +} +``` + +## Example 8 (Create a server) + +Use [`neco_serve()`](docs/API.md) to quickly bind and listen on an address. + +Here we'll run a tiny webserver at http://127.0.0.1:8080 + +```c +#include +#include +#include "../neco.h" + +void request(int argc, void *argv[]) { + int fd = *(int*)argv[0]; + char req[256]; + int n = neco_read(fd, req, sizeof(req)); + if (n > 0) { + char res[] = "HTTP/1.0 200 OK\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 21\r\n" + "\r\n" + "

Hello Neco!

\n"; + neco_write(fd, res, sizeof(res)); + } + close(fd); +} + +int neco_main(int argc, char *argv[]) { + int servfd = neco_serve("tcp", "127.0.0.1:8080"); + if (servfd < 0) { + printf("neco_serve: %s\n", neco_strerror(servfd)); + return 0; + } + printf("Serving at http://127.0.0.1:8080\n"); + while (1) { + int fd = neco_accept(servfd, 0, 0); + if (servfd < 0) { + printf("neco_accept: %s\n", neco_strerror(fd)); + continue; + } + neco_start(request, 1, &fd); + } + return 0; +} +``` + +## Example 9 (Echo server and client) + +Run server with: + +```sh +cc neco.c echo-server.c && ./a.out +``` + +Run client with: + +```sh +cc neco.c echo-client.c && ./a.out +``` + +**echo-server.c** + +```c +#include +#include +#include "neco.h" + +void client(int argc, void *argv[]) { + int conn = *(int*)argv[0]; + printf("client connected\n"); + char buf[64]; + while (1) { + ssize_t n = neco_read(conn, buf, sizeof(buf)); + if (n <= 0) { + break; + } + printf("%.*s", (int)n, buf); + } + printf("client disconnected\n"); + close(conn); +} + +int neco_main(int argc, char *argv[]) { + int ln = neco_serve("tcp", "localhost:19203"); + if (ln == -1) { + perror("neco_serve"); + exit(1); + } + printf("listening at localhost:19203\n"); + while (1) { + int conn = neco_accept(ln, 0, 0); + if (conn > 0) { + neco_start(client, 1, &conn); + } + } + close(ln); + return 0; +} +``` + +**echo-client.c** + +```c +#include +#include +#include "neco.h" + +int neco_main(int argc, char *argv[]) { + int fd = neco_dial("tcp", "localhost:19203"); + if (fd == -1) { + perror("neco_listen"); + exit(1); + } + printf("connected\n"); + char buf[64]; + while (1) { + printf("> "); + fflush(stdout); + ssize_t nbytes = neco_read(STDIN_FILENO, buf, sizeof(buf)); + if (nbytes < 0) { + break; + } + ssize_t ret = neco_write(fd, buf, nbytes); + if (ret < 0) { + break; + } + } + printf("disconnected\n"); + close(fd); + return 0; +} +``` + +## Example 10 (Suspending and resuming a coroutine) + +Any coroutines can suspended itself indefinetly and then be resumed by other +coroutines by using [`neco_suspend()`](docs/API.md#neco_suspend) and +[`neco_resume()`](docs/API.md#neco_resume). + +```c +#include +#include +#include "neco.h" + +void coroutine(int argc, void *argv[]) { + printf("Suspending coroutine\n"); + neco_suspend(); + printf("Coroutine resumed\n"); +} + +int neco_main(int argc, char *argv[]) { + neco_start(coroutine, 0); + + for (int i = 0; i < 3; i++) { + printf("%d\n", i+1); + neco_sleep(NECO_SECOND); + } + + // Resume the suspended. The neco_lastid() returns the identifier for the + // last coroutine started by the current coroutine. + neco_resume(neco_lastid()); + return 0; +} +// Output: +// Suspending coroutine +// 1 +// 2 +// 3 +// Coroutine resumed +``` + +### More examples + +You can find more [examples here](examples). + +## Platform notes + +Linux, Mac, and FreeBSD supports all features. + +Windows and WebAssembly support the core coroutine features, but have some key +limitiations, mostly with working with file descriptors and networking. +This is primarly because the Neco event queue works with epoll and kqueue, +which are only available on Linux and Mac/BSD respectively. This means that the +`neco_wait()` (which allows for a coroutine to wait for a file descriptor to be +readable or writeable) is not currently available on those platforms. + +Other limitations include: + +- Windows only supports amd64. +- Windows and WebAssembly use smaller default stacks of 1MB. +- Windows and WebAssembly do not support guards or gaps. +- Windows and WebAssembly do not support NECO_CSPRNG (Cryptographically secure + pseudorandom number generator) +- Windows does not support stack unwinding. + +Other than that, Neco works great on those platforms. + +Any contributions towards making Windows and WebAssembly feature complete are +welcome. + +## The scheduler + +Neco uses [sco](https://github.com/tidwall/sco), which is a fair and +deterministic scheduler. This means that no coroutine takes priority over +another and that all concurrent operations will reproduce in an expected order. + +### Fast context switching + +The coroutine context switching is powered by +[llco](https://github.com/tidwall/llco) and uses assembly code in most +cases. On my lab machine (AMD Ryzen 9 5950X) a context switch takes about 11 +nanoseconds. + +### Thread local runtime + +There can be no more than one scheduler per thread. + +When the first coroutine is started using `neco_start()`, a new Neco +runtime is initialized in the current thread, and each runtime has its own +scheduler. + +Communicating between coroutines that are running in different threads will +require I/O mechanisms that do not block the current schedulers, such as +`pipe()`, `eventfd()` or atomics. + +_Pthread utilties such as `pthread_mutex_t` and `pthread_cond_t` do not work very well in coroutines._ + +For example, here we'll create two threads, running their own Neco schedulers. +Each using pipes to communicate with the other. + +```c +#include +#include +#include +#include "neco.h" + +void coro1(int argc, void *argv[]) { + // This coroutine is running in a different scheduler than coro2. + int rd = *(int*)argv[0]; + int wr = *(int*)argv[1]; + int val; + neco_read(rd, &val, sizeof(int)); + printf("coro1: %d\n", val); + neco_write(wr, &(int){ 2 }, sizeof(int)); +} + +void coro2(int argc, void *argv[]) { + // This coroutine is running in a different scheduler than coro1. + int rd = *(int*)argv[0]; + int wr = *(int*)argv[1]; + int val; + neco_write(wr, &(int){ 1 }, sizeof(int)); + neco_read(rd, &val, sizeof(int)); + printf("coro2: %d\n", val); +} + +void *runtime1(void *arg) { + int *pipefds = arg; + neco_start(coro1, 2, &pipefds[0], &pipefds[3]); + return 0; +} + +void *runtime2(void *arg) { + int *pipefds = arg; + neco_start(coro2, 2, &pipefds[2], &pipefds[1]); + return 0; +} + +int main() { + int pipefds[4]; + pipe(&pipefds[0]); + pipe(&pipefds[2]); + pthread_t th1, th2; + pthread_create(&th1, 0, runtime1, pipefds); + pthread_create(&th2, 0, runtime2, pipefds); + pthread_join(th1, 0); + pthread_join(th2, 0); + return 0; +} +``` + +## License + +Source code is available under the MIT [License](LICENSE). diff --git a/deps/.gitignore b/deps/.gitignore new file mode 100644 index 0000000..875e994 --- /dev/null +++ b/deps/.gitignore @@ -0,0 +1 @@ +tmp.* \ No newline at end of file diff --git a/deps/.package b/deps/.package new file mode 100644 index 0000000..149cfb4 --- /dev/null +++ b/deps/.package @@ -0,0 +1,2 @@ +import github.com/tidwall/sco v0.1.0 +import github.com/tidwall/stack v0.1.0 diff --git a/deps/aat.h b/deps/aat.h new file mode 100644 index 0000000..b401832 --- /dev/null +++ b/deps/aat.h @@ -0,0 +1,337 @@ +// https://github.com/tidwall/aatree +// +// Copyright 2023 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Single header file for generating aat binary search trees. + +#ifndef AAT_H +#define AAT_H + +#define AAT_DEF(specifiers, prefix, type) \ +specifiers type *prefix##_insert(type **root, type *item); \ +specifiers type *prefix##_delete(type **root, type *key); \ +specifiers type *prefix##_search(type **root, type *key); \ +specifiers type *prefix##_delete_first(type **root); \ +specifiers type *prefix##_delete_last(type **root); \ +specifiers type *prefix##_first(type **root); \ +specifiers type *prefix##_last(type **root); \ +specifiers type *prefix##_iter(type **root, type *key); \ +specifiers type *prefix##_prev(type **root, type *item); \ +specifiers type *prefix##_next(type **root, type *item); \ + +#define AAT_FIELDS(type, left, right, level) \ +type *left; \ +type *right; \ +int level; \ + +#define AAT_IMPL(prefix, type, left, right, level, compare) \ +static void prefix##_clear(type *node) { \ + if (node) { \ + node->left = 0; \ + node->right = 0; \ + node->level = 0; \ + } \ +} \ + \ +static type *prefix##_skew(type *node) { \ + if (node && node->left && \ + node->left->level == node->level) \ + { \ + type *left_node = node->left; \ + node->left = left_node->right; \ + left_node->right = node; \ + node = left_node; \ + } \ + return node; \ +} \ + \ +static type *prefix##_split(type *node) { \ + if (node && node->right && node->right->right && \ + node->right->right->level == node->level) \ + { \ + type *right_node = node->right; \ + node->right = right_node->left; \ + right_node->left = node; \ + right_node->level++; \ + node = right_node; \ + } \ + return node; \ +} \ + \ +static type *prefix##_insert0(type *node, type *item, type **replaced) { \ + if (!node) { \ + item->left = 0; \ + item->right = 0; \ + item->level = 1; \ + node = item; \ + } else { \ + int cmp = compare(item, node); \ + if (cmp < 0) { \ + node->left = prefix##_insert0(node->left, item, replaced); \ + } else if (cmp > 0) { \ + node->right = prefix##_insert0(node->right, item, replaced); \ + } else { \ + *replaced = node; \ + item->left = node->left; \ + item->right = node->right; \ + item->level = node->level; \ + node = item; \ + } \ + } \ + node = prefix##_skew(node); \ + node = prefix##_split(node); \ + return node; \ +} \ + \ +type *prefix##_insert(type **root, type *item) { \ + type *replaced = 0; \ + *root = prefix##_insert0(*root, item, &replaced); \ + if (replaced != item) { \ + prefix##_clear(replaced); \ + } \ + return replaced; \ +} \ + \ +static type *prefix##_decrease_level(type *node) { \ + if (node->left || node->right) { \ + int new_level = 0; \ + if (node->left && node->right) { \ + if (node->left->level < node->right->level) { \ + new_level = node->left->level; \ + } else { \ + new_level = node->right->level; \ + } \ + } \ + new_level++; \ + if (new_level < node->level) { \ + node->level = new_level; \ + if (node->right && new_level < node->right->level) { \ + node->right->level = new_level; \ + } \ + } \ + } \ + return node; \ +} \ + \ +static type *prefix##_delete_fixup(type *node) { \ + node = prefix##_decrease_level(node); \ + node = prefix##_skew(node); \ + node->right = prefix##_skew(node->right); \ + if (node->right && node->right->right) { \ + node->right->right = prefix##_skew(node->right->right); \ + } \ + node = prefix##_split(node); \ + node->right = prefix##_split(node->right); \ + return node; \ +} \ + \ +static type *prefix##_delete_first0(type *node, \ + type **deleted) \ +{ \ + if (node) { \ + if (!node->left) { \ + *deleted = node; \ + if (node->right) { \ + node = node->right; \ + } else { \ + node = 0; \ + } \ + } else { \ + node->left = prefix##_delete_first0(node->left, deleted); \ + node = prefix##_delete_fixup(node); \ + } \ + } \ + return node; \ +} \ + \ +static type *prefix##_delete_last0(type *node, \ + type **deleted) \ +{ \ + if (node) { \ + if (!node->right) { \ + *deleted = node; \ + if (node->left) { \ + node = node->left; \ + } else { \ + node = 0; \ + } \ + } else { \ + node->right = prefix##_delete_last0(node->right, deleted); \ + node = prefix##_delete_fixup(node); \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_delete_first(type **root) { \ + type *deleted = 0; \ + *root = prefix##_delete_first0(*root, &deleted); \ + prefix##_clear(deleted); \ + return deleted; \ +} \ + \ +type *prefix##_delete_last(type **root) { \ + type *deleted = 0; \ + *root = prefix##_delete_last0(*root, &deleted); \ + prefix##_clear(deleted); \ + return deleted; \ +} \ + \ +static type *prefix##_delete0(type *node, \ + type *key, type **deleted) \ +{ \ + if (node) { \ + int cmp = compare(key, node); \ + if (cmp < 0) { \ + node->left = prefix##_delete0(node->left, key, deleted); \ + } else if (cmp > 0) { \ + node->right = prefix##_delete0(node->right, key, deleted); \ + } else { \ + *deleted = node; \ + if (!node->left && !node->right) { \ + node = 0; \ + } else { \ + type *leaf_deleted = 0; \ + if (!node->left) { \ + node->right = prefix##_delete_first0(node->right, \ + &leaf_deleted); \ + } else { \ + node->left = prefix##_delete_last0(node->left, \ + &leaf_deleted); \ + } \ + leaf_deleted->left = node->left; \ + leaf_deleted->right = node->right; \ + leaf_deleted->level = node->level; \ + node = leaf_deleted; \ + } \ + } \ + if (node) { \ + node = prefix##_delete_fixup(node); \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_delete(type **root, type *key) { \ + type *deleted = 0; \ + *root = prefix##_delete0(*root, key, &deleted); \ + prefix##_clear(deleted); \ + return deleted; \ +} \ + \ +type *prefix##_search(type **root, type *key) { \ + type *found = 0; \ + type *node = *root; \ + while (node) { \ + int cmp = compare(key, node); \ + if (cmp < 0) { \ + node = node->left; \ + } else if (cmp > 0) { \ + node = node->right; \ + } else { \ + found = node; \ + node = 0; \ + } \ + } \ + return found; \ +} \ + \ +type *prefix##_first(type **root) { \ + type *node = *root; \ + if (node) { \ + while (node->left) { \ + node = node->left; \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_last(type **root) { \ + type *node = *root; \ + if (node) { \ + while (node->right) { \ + node = node->right; \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_iter(type **root, type *key) { \ + type *found = 0; \ + type *node = *root; \ + while (node) { \ + int cmp = compare(key, node); \ + if (cmp < 0) { \ + found = node; \ + node = node->left; \ + } else if (cmp > 0) { \ + node = node->right; \ + } else { \ + found = node; \ + node = 0; \ + } \ + } \ + return found; \ +} \ + \ +static type *prefix##_parent(type **root, \ + type *item) \ +{ \ + type *parent = 0; \ + type *node = *root; \ + while (node) { \ + int cmp = compare(item, node); \ + if (cmp < 0) { \ + parent = node; \ + node = node->left; \ + } else if (cmp > 0) { \ + parent = node; \ + node = node->right; \ + } else { \ + node = 0; \ + } \ + } \ + return parent; \ +} \ + \ +type *prefix##_next(type **root, type *node) { \ + if (node) { \ + if (node->right) { \ + node = node->right; \ + while (node->left) { \ + node = node->left; \ + } \ + } else { \ + type *parent = prefix##_parent(root, node); \ + while (parent && parent->left != node) { \ + node = parent; \ + parent = prefix##_parent(root, parent); \ + } \ + node = parent; \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_prev(type **root, type *node) { \ + if (node) { \ + if (node->left) { \ + node = node->left; \ + while (node->right) { \ + node = node->right; \ + } \ + } else { \ + type *parent = prefix##_parent(root, node); \ + while (parent && parent->right != node) { \ + node = parent; \ + parent = prefix##_parent(root, parent); \ + } \ + node = parent; \ + } \ + } \ + return node; \ +} \ + +#endif // AAT_H diff --git a/deps/embed.sh b/deps/embed.sh new file mode 100755 index 0000000..9571a92 --- /dev/null +++ b/deps/embed.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Statically embeds the dependencies into the root source file +# Each source file must have a // BEGIN file.c and // END file.c + +SOURCE_FILE=../neco.c +EMBEDDED_FILES=("sco.c" "stack.c" "aat.h" "worker.c") + +set -e +cd $(dirname "${BASH_SOURCE[0]}") + +cp $SOURCE_FILE tmp.1 + +embed() { + BEGIN=$(cat tmp.1 | grep -n -w $"// BEGIN $1" | cut -d : -f 1) + END=$(cat tmp.1 | grep -n -w $"// END $1" | cut -d : -f 1) + if [[ "$BEGIN" == "" ]]; then + echo "missing // BEGIN $1" + exit 1 + elif [[ "$END" == "" ]]; then + echo "missing // END $1" + exit 1 + elif [[ $BEGIN -gt $END ]]; then + echo "missing // BEGIN $1 must be after // END $1" + exit 1 + fi + sed -n 1,${BEGIN}p tmp.1 > tmp.2 + cat $1 >> tmp.2 + sed -n ${END},999999999999p tmp.1 >> tmp.2 + mv tmp.2 tmp.1 +} + +for file in "${EMBEDDED_FILES[@]}"; do + embed "$file" +done + +# overwrite the original source file +mv tmp.1 $SOURCE_FILE \ No newline at end of file diff --git a/deps/sco.c b/deps/sco.c new file mode 100644 index 0000000..d4e3636 --- /dev/null +++ b/deps/sco.c @@ -0,0 +1,2044 @@ +// https://github.com/tidwall/sco +// +// Copyright 2023 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Coroutine scheduler + +#include +#include + +#ifndef SCO_STATIC +#include "sco.h" +#else +#define SCO_EXTERN static +#include +#include +struct sco_desc { + void *stack; + size_t stack_size; + void (*entry)(void *udata); + void (*cleanup)(void *stack, size_t stack_size, void *udata); + void *udata; +}; +struct sco_symbol { + void *cfa; // Canonical Frame Address + void *ip; // Instruction Pointer + const char *fname; // Pathname of shared object + void *fbase; // Base address of shared object + const char *sname; // Name of nearest symbol + void *saddr; // Address of nearest symbol +}; +#define SCO_MINSTACKSIZE 131072 +#endif + +#ifndef SCO_EXTERN +#define SCO_EXTERN +#endif + +//////////////////////////////////////////////////////////////////////////////// +// llco.c +//////////////////////////////////////////////////////////////////////////////// +#ifdef SCO_NOAMALGA + +#include "deps/llco.h" + +#else + +#define LLCO_STATIC + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +// BEGIN llco.c +// https://github.com/tidwall/llco +// +// Copyright (c) 2024 Joshua J Baker. +// This software is available as a choice of Public Domain or MIT-0. + +#ifdef _FORTIFY_SOURCE +#define LLCO_FORTIFY_SOURCE _FORTIFY_SOURCE +// Disable __longjmp_chk validation so that we can jump between stacks. +#pragma push_macro("_FORTIFY_SOURCE") +#undef _FORTIFY_SOURCE +#include +#define _FORTIFY_SOURCE LLCO_FORTIFY_SOURCE +#undef LLCO_FORTIFY_SOURCE +#pragma pop_macro("_FORTIFY_SOURCE") +#endif + +#ifndef LLCO_STATIC +#include "llco.h" +#else +#include +#include +#define LLCO_MINSTACKSIZE 16384 +#define LLCO_EXTERN static +struct llco_desc { + void *stack; + size_t stack_size; + void (*entry)(void *udata); + void (*cleanup)(void *stack, size_t stack_size, void *udata); + void *udata; +}; +struct llco_symbol { + void *cfa; + void *ip; + const char *fname; + void *fbase; + const char *sname; + void *saddr; +}; +#endif + +#include + +#ifdef LLCO_VALGRIND +#include +#endif + +#ifndef LLCO_EXTERN +#define LLCO_EXTERN +#endif + +#if defined(__GNUC__) +#ifdef noinline +#define LLCO_NOINLINE noinline +#else +#define LLCO_NOINLINE __attribute__ ((noinline)) +#endif +#ifdef noreturn +#define LLCO_NORETURN noreturn +#else +#define LLCO_NORETURN __attribute__ ((noreturn)) +#endif +#else +#define LLCO_NOINLINE +#define LLCO_NORETURN +#endif + +#if defined(_MSC_VER) +#define __thread __declspec(thread) +#endif + +static void llco_entry(void *arg); + +LLCO_NORETURN +static void llco_exit(void) { + _Exit(0); +} + +#ifdef LLCO_ASM +#error LLCO_ASM must not be defined +#endif + +// Passing the entry function into assembly requires casting the function +// pointer to an object pointer, which is forbidden in the ISO C spec but +// allowed in posix. Ignore the warning attributed to this requirement when +// the -pedantic compiler flag is provide. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Below is various assembly code adapted from the Lua Coco [MIT] and Minicoro +// [MIT-0] projects by Mike Pall and Eduardo Bart respectively. +//////////////////////////////////////////////////////////////////////////////// + +/* +Lua Coco (coco.luajit.org) +Copyright (C) 2004-2016 Mike Pall. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +//////////////////////////////////////////////////////////////////////////////// +// ARM +//////////////////////////////////////////////////////////////////////////////// +#if defined(__ARM_EABI__) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,arm_eabi" + +struct llco_asmctx { +#ifndef __SOFTFP__ + void* f[16]; +#endif + void *d[4]; /* d8-d15 */ + void *r[4]; /* r4-r11 */ + void *lr; + void *sp; +}; + +void _llco_asm_entry(void); +int _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __llco_asm_switch\n" + "__llco_asm_switch:\n" +#else + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch #function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#endif +#ifndef __SOFTFP__ + " vstmia r0!, {d8-d15}\n" +#endif + " stmia r0, {r4-r11, lr}\n" + " str sp, [r0, #9*4]\n" +#ifndef __SOFTFP__ + " vldmia r1!, {d8-d15}\n" +#endif + " ldr sp, [r1, #9*4]\n" + " ldmia r1, {r4-r11, pc}\n" +#ifndef __APPLE__ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +#endif +); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __llco_asm_entry\n" + "__llco_asm_entry:\n" +#else + ".globl _llco_asm_entry\n" + ".type _llco_asm_entry #function\n" + ".hidden _llco_asm_entry\n" + "_llco_asm_entry:\n" +#endif + " mov r0, r4\n" + " mov ip, r5\n" + " mov lr, r6\n" + " bx ip\n" +#ifndef __APPLE__ + ".size _llco_asm_entry, .-_llco_asm_entry\n" +#endif +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, void* stack_base, + size_t stack_size, void *arg) +{ + ctx->d[0] = (void*)(arg); + ctx->d[1] = (void*)(llco_entry); + ctx->d[2] = (void*)(0xdeaddead); /* Dummy return address. */ + ctx->lr = (void*)(_llco_asm_entry); + ctx->sp = (void*)((size_t)stack_base + stack_size); +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// ARM 64-bit +//////////////////////////////////////////////////////////////////////////////// +#if defined(__aarch64__) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,aarch64" + +struct llco_asmctx { + void *x[12]; /* x19-x30 */ + void *sp; + void *lr; + void *d[8]; /* d8-d15 */ +}; + +void _llco_asm_entry(void); +int _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __llco_asm_switch\n" + "__llco_asm_switch:\n" +#else + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch #function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#endif + + " mov x10, sp\n" + " mov x11, x30\n" + " stp x19, x20, [x0, #(0*16)]\n" + " stp x21, x22, [x0, #(1*16)]\n" + " stp d8, d9, [x0, #(7*16)]\n" + " stp x23, x24, [x0, #(2*16)]\n" + " stp d10, d11, [x0, #(8*16)]\n" + " stp x25, x26, [x0, #(3*16)]\n" + " stp d12, d13, [x0, #(9*16)]\n" + " stp x27, x28, [x0, #(4*16)]\n" + " stp d14, d15, [x0, #(10*16)]\n" + " stp x29, x30, [x0, #(5*16)]\n" + " stp x10, x11, [x0, #(6*16)]\n" + " ldp x19, x20, [x1, #(0*16)]\n" + " ldp x21, x22, [x1, #(1*16)]\n" + " ldp d8, d9, [x1, #(7*16)]\n" + " ldp x23, x24, [x1, #(2*16)]\n" + " ldp d10, d11, [x1, #(8*16)]\n" + " ldp x25, x26, [x1, #(3*16)]\n" + " ldp d12, d13, [x1, #(9*16)]\n" + " ldp x27, x28, [x1, #(4*16)]\n" + " ldp d14, d15, [x1, #(10*16)]\n" + " ldp x29, x30, [x1, #(5*16)]\n" + " ldp x10, x11, [x1, #(6*16)]\n" + " mov sp, x10\n" + " br x11\n" +#ifndef __APPLE__ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +#endif +); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __llco_asm_entry\n" + "__llco_asm_entry:\n" +#else + ".globl _llco_asm_entry\n" + ".type _llco_asm_entry #function\n" + ".hidden _llco_asm_entry\n" + "_llco_asm_entry:\n" +#endif + " mov x0, x19\n" + " mov x30, x21\n" + " br x20\n" +#ifndef __APPLE__ + ".size _llco_asm_entry, .-_llco_asm_entry\n" +#endif +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, void* stack_base, + size_t stack_size, void *arg) +{ + ctx->x[0] = (void*)(arg); + ctx->x[1] = (void*)(llco_entry); + ctx->x[2] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */ + ctx->sp = (void*)((size_t)stack_base + stack_size); + ctx->lr = (void*)(_llco_asm_entry); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// RISC-V (rv64/rv32) +//////////////////////////////////////////////////////////////////////////////// +#if defined(__riscv) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,riscv" + +struct llco_asmctx { + void* s[12]; /* s0-s11 */ + void* ra; + void* pc; + void* sp; +#ifdef __riscv_flen +#if __riscv_flen == 64 + double fs[12]; /* fs0-fs11 */ +#elif __riscv_flen == 32 + float fs[12]; /* fs0-fs11 */ +#endif +#endif /* __riscv_flen */ +}; + +void _llco_asm_entry(void); +int _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( + ".text\n" + ".globl _llco_asm_entry\n" + ".type _llco_asm_entry @function\n" + ".hidden _llco_asm_entry\n" + "_llco_asm_entry:\n" + " mv a0, s0\n" + " jr s1\n" + ".size _llco_asm_entry, .-_llco_asm_entry\n" +); + +__asm__( + ".text\n" + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch @function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#if __riscv_xlen == 64 + " sd s0, 0x00(a0)\n" + " sd s1, 0x08(a0)\n" + " sd s2, 0x10(a0)\n" + " sd s3, 0x18(a0)\n" + " sd s4, 0x20(a0)\n" + " sd s5, 0x28(a0)\n" + " sd s6, 0x30(a0)\n" + " sd s7, 0x38(a0)\n" + " sd s8, 0x40(a0)\n" + " sd s9, 0x48(a0)\n" + " sd s10, 0x50(a0)\n" + " sd s11, 0x58(a0)\n" + " sd ra, 0x60(a0)\n" + " sd ra, 0x68(a0)\n" /* pc */ + " sd sp, 0x70(a0)\n" +#ifdef __riscv_flen +#if __riscv_flen == 64 + " fsd fs0, 0x78(a0)\n" + " fsd fs1, 0x80(a0)\n" + " fsd fs2, 0x88(a0)\n" + " fsd fs3, 0x90(a0)\n" + " fsd fs4, 0x98(a0)\n" + " fsd fs5, 0xa0(a0)\n" + " fsd fs6, 0xa8(a0)\n" + " fsd fs7, 0xb0(a0)\n" + " fsd fs8, 0xb8(a0)\n" + " fsd fs9, 0xc0(a0)\n" + " fsd fs10, 0xc8(a0)\n" + " fsd fs11, 0xd0(a0)\n" + " fld fs0, 0x78(a1)\n" + " fld fs1, 0x80(a1)\n" + " fld fs2, 0x88(a1)\n" + " fld fs3, 0x90(a1)\n" + " fld fs4, 0x98(a1)\n" + " fld fs5, 0xa0(a1)\n" + " fld fs6, 0xa8(a1)\n" + " fld fs7, 0xb0(a1)\n" + " fld fs8, 0xb8(a1)\n" + " fld fs9, 0xc0(a1)\n" + " fld fs10, 0xc8(a1)\n" + " fld fs11, 0xd0(a1)\n" +#else +#error "Unsupported RISC-V FLEN" +#endif +#endif /* __riscv_flen */ + " ld s0, 0x00(a1)\n" + " ld s1, 0x08(a1)\n" + " ld s2, 0x10(a1)\n" + " ld s3, 0x18(a1)\n" + " ld s4, 0x20(a1)\n" + " ld s5, 0x28(a1)\n" + " ld s6, 0x30(a1)\n" + " ld s7, 0x38(a1)\n" + " ld s8, 0x40(a1)\n" + " ld s9, 0x48(a1)\n" + " ld s10, 0x50(a1)\n" + " ld s11, 0x58(a1)\n" + " ld ra, 0x60(a1)\n" + " ld a2, 0x68(a1)\n" /* pc */ + " ld sp, 0x70(a1)\n" + " jr a2\n" +#elif __riscv_xlen == 32 + " sw s0, 0x00(a0)\n" + " sw s1, 0x04(a0)\n" + " sw s2, 0x08(a0)\n" + " sw s3, 0x0c(a0)\n" + " sw s4, 0x10(a0)\n" + " sw s5, 0x14(a0)\n" + " sw s6, 0x18(a0)\n" + " sw s7, 0x1c(a0)\n" + " sw s8, 0x20(a0)\n" + " sw s9, 0x24(a0)\n" + " sw s10, 0x28(a0)\n" + " sw s11, 0x2c(a0)\n" + " sw ra, 0x30(a0)\n" + " sw ra, 0x34(a0)\n" /* pc */ + " sw sp, 0x38(a0)\n" +#ifdef __riscv_flen +#if __riscv_flen == 64 + " fsd fs0, 0x3c(a0)\n" + " fsd fs1, 0x44(a0)\n" + " fsd fs2, 0x4c(a0)\n" + " fsd fs3, 0x54(a0)\n" + " fsd fs4, 0x5c(a0)\n" + " fsd fs5, 0x64(a0)\n" + " fsd fs6, 0x6c(a0)\n" + " fsd fs7, 0x74(a0)\n" + " fsd fs8, 0x7c(a0)\n" + " fsd fs9, 0x84(a0)\n" + " fsd fs10, 0x8c(a0)\n" + " fsd fs11, 0x94(a0)\n" + " fld fs0, 0x3c(a1)\n" + " fld fs1, 0x44(a1)\n" + " fld fs2, 0x4c(a1)\n" + " fld fs3, 0x54(a1)\n" + " fld fs4, 0x5c(a1)\n" + " fld fs5, 0x64(a1)\n" + " fld fs6, 0x6c(a1)\n" + " fld fs7, 0x74(a1)\n" + " fld fs8, 0x7c(a1)\n" + " fld fs9, 0x84(a1)\n" + " fld fs10, 0x8c(a1)\n" + " fld fs11, 0x94(a1)\n" +#elif __riscv_flen == 32 + " fsw fs0, 0x3c(a0)\n" + " fsw fs1, 0x40(a0)\n" + " fsw fs2, 0x44(a0)\n" + " fsw fs3, 0x48(a0)\n" + " fsw fs4, 0x4c(a0)\n" + " fsw fs5, 0x50(a0)\n" + " fsw fs6, 0x54(a0)\n" + " fsw fs7, 0x58(a0)\n" + " fsw fs8, 0x5c(a0)\n" + " fsw fs9, 0x60(a0)\n" + " fsw fs10, 0x64(a0)\n" + " fsw fs11, 0x68(a0)\n" + " flw fs0, 0x3c(a1)\n" + " flw fs1, 0x40(a1)\n" + " flw fs2, 0x44(a1)\n" + " flw fs3, 0x48(a1)\n" + " flw fs4, 0x4c(a1)\n" + " flw fs5, 0x50(a1)\n" + " flw fs6, 0x54(a1)\n" + " flw fs7, 0x58(a1)\n" + " flw fs8, 0x5c(a1)\n" + " flw fs9, 0x60(a1)\n" + " flw fs10, 0x64(a1)\n" + " flw fs11, 0x68(a1)\n" +#else +#error "Unsupported RISC-V FLEN" +#endif +#endif /* __riscv_flen */ + " lw s0, 0x00(a1)\n" + " lw s1, 0x04(a1)\n" + " lw s2, 0x08(a1)\n" + " lw s3, 0x0c(a1)\n" + " lw s4, 0x10(a1)\n" + " lw s5, 0x14(a1)\n" + " lw s6, 0x18(a1)\n" + " lw s7, 0x1c(a1)\n" + " lw s8, 0x20(a1)\n" + " lw s9, 0x24(a1)\n" + " lw s10, 0x28(a1)\n" + " lw s11, 0x2c(a1)\n" + " lw ra, 0x30(a1)\n" + " lw a2, 0x34(a1)\n" /* pc */ + " lw sp, 0x38(a1)\n" + " jr a2\n" +#else +#error "Unsupported RISC-V XLEN" +#endif /* __riscv_xlen */ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, + void* stack_base, size_t stack_size, void *arg) +{ + ctx->s[0] = (void*)(arg); + ctx->s[1] = (void*)(llco_entry); + ctx->pc = (void*)(_llco_asm_entry); +#if __riscv_xlen == 64 + ctx->ra = (void*)(0xdeaddeaddeaddead); +#elif __riscv_xlen == 32 + ctx->ra = (void*)(0xdeaddead); +#endif + ctx->sp = (void*)((size_t)stack_base + stack_size); +} + +#endif // riscv + +//////////////////////////////////////////////////////////////////////////////// +// x86 +//////////////////////////////////////////////////////////////////////////////// +#if (defined(__i386) || defined(__i386__)) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,i386" + +struct llco_asmctx { + void *eip, *esp, *ebp, *ebx, *esi, *edi; +}; + +void _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( +#ifdef __DJGPP__ /* DOS compiler */ + "__llco_asm_switch:\n" +#else + ".text\n" + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch @function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#endif + " call 1f\n" + " 1:\n" + " popl %ecx\n" + " addl $(2f-1b), %ecx\n" + " movl 4(%esp), %eax\n" + " movl 8(%esp), %edx\n" + " movl %ecx, (%eax)\n" + " movl %esp, 4(%eax)\n" + " movl %ebp, 8(%eax)\n" + " movl %ebx, 12(%eax)\n" + " movl %esi, 16(%eax)\n" + " movl %edi, 20(%eax)\n" + " movl 20(%edx), %edi\n" + " movl 16(%edx), %esi\n" + " movl 12(%edx), %ebx\n" + " movl 8(%edx), %ebp\n" + " movl 4(%edx), %esp\n" + " jmp *(%edx)\n" + " 2:\n" + " ret\n" +#ifndef __DJGPP__ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +#endif +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, + void* stack_base, size_t stack_size, void *arg) +{ + void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - 16 - + 1*sizeof(size_t)); + stack_high_ptr[0] = (void*)(0xdeaddead); // Dummy return address. + stack_high_ptr[1] = (void*)(arg); + ctx->eip = (void*)(llco_entry); + ctx->esp = (void*)(stack_high_ptr); +} +#endif // __i386__ + +//////////////////////////////////////////////////////////////////////////////// +// x64 +//////////////////////////////////////////////////////////////////////////////// +#if (defined(__x86_64__) || defined(_M_X64)) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,x64" + +#ifdef _WIN32 + +struct llco_asmctx { + void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15, *rdi, *rsi; + void* xmm[20]; /* xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, + xmm14, xmm15 */ + void* fiber_storage; + void* dealloc_stack; + void* stack_limit; + void* stack_base; +}; + +#if defined(__GNUC__) +#define LLCO_ASM_BLOB __attribute__((section(".text"))) +#elif defined(_MSC_VER) +#define LLCO_ASM_BLOB __declspec(allocate(".text")) +#pragma section(".text") +#endif + +LLCO_ASM_BLOB static unsigned char llco_wrap_main_code_entry[] = { + 0x4c,0x89,0xe9, // mov %r13,%rcx + 0x41,0xff,0xe4, // jmpq *%r12 + 0xc3, // retq + 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90 // nop +}; + +LLCO_ASM_BLOB static unsigned char llco_asm_switch_code[] = { + 0x48,0x8d,0x05,0x3e,0x01,0x00,0x00, // lea 0x13e(%rip),%rax + 0x48,0x89,0x01, // mov %rax,(%rcx) + 0x48,0x89,0x61,0x08, // mov %rsp,0x8(%rcx) + 0x48,0x89,0x69,0x10, // mov %rbp,0x10(%rcx) + 0x48,0x89,0x59,0x18, // mov %rbx,0x18(%rcx) + 0x4c,0x89,0x61,0x20, // mov %r12,0x20(%rcx) + 0x4c,0x89,0x69,0x28, // mov %r13,0x28(%rcx) + 0x4c,0x89,0x71,0x30, // mov %r14,0x30(%rcx) + 0x4c,0x89,0x79,0x38, // mov %r15,0x38(%rcx) + 0x48,0x89,0x79,0x40, // mov %rdi,0x40(%rcx) + 0x48,0x89,0x71,0x48, // mov %rsi,0x48(%rcx) + 0x0f,0x11,0x71,0x50, // movups %xmm6,0x50(%rcx) + 0x0f,0x11,0x79,0x60, // movups %xmm7,0x60(%rcx) + 0x44,0x0f,0x11,0x41,0x70, // movups %xmm8,0x70(%rcx) + 0x44,0x0f,0x11,0x89,0x80,0x00,0x00,0x00, // movups %xmm9,0x80(%rcx) + 0x44,0x0f,0x11,0x91,0x90,0x00,0x00,0x00, // movups %xmm10,0x90(%rcx) + 0x44,0x0f,0x11,0x99,0xa0,0x00,0x00,0x00, // movups %xmm11,0xa0(%rcx) + 0x44,0x0f,0x11,0xa1,0xb0,0x00,0x00,0x00, // movups %xmm12,0xb0(%rcx) + 0x44,0x0f,0x11,0xa9,0xc0,0x00,0x00,0x00, // movups %xmm13,0xc0(%rcx) + 0x44,0x0f,0x11,0xb1,0xd0,0x00,0x00,0x00, // movups %xmm14,0xd0(%rcx) + 0x44,0x0f,0x11,0xb9,0xe0,0x00,0x00,0x00, // movups %xmm15,0xe0(%rcx) + 0x65,0x4c,0x8b,0x14,0x25,0x30,0x00,0x00,0x00, // mov %gs:0x30,%r10 + 0x49,0x8b,0x42,0x20, // mov 0x20(%r10),%rax + 0x48,0x89,0x81,0xf0,0x00,0x00,0x00, // mov %rax,0xf0(%rcx) + 0x49,0x8b,0x82,0x78,0x14,0x00,0x00, // mov 0x1478(%r10),%rax + 0x48,0x89,0x81,0xf8,0x00,0x00,0x00, // mov %rax,0xf8(%rcx) + 0x49,0x8b,0x42,0x10, // mov 0x10(%r10),%rax + 0x48,0x89,0x81,0x00,0x01,0x00,0x00, // mov %rax,0x100(%rcx) + 0x49,0x8b,0x42,0x08, // mov 0x8(%r10),%rax + 0x48,0x89,0x81,0x08,0x01,0x00,0x00, // mov %rax,0x108(%rcx) + 0x48,0x8b,0x82,0x08,0x01,0x00,0x00, // mov 0x108(%rdx),%rax + 0x49,0x89,0x42,0x08, // mov %rax,0x8(%r10) + 0x48,0x8b,0x82,0x00,0x01, 0x00, 0x00, // mov 0x100(%rdx),%rax + 0x49,0x89,0x42,0x10, // mov %rax,0x10(%r10) + 0x48,0x8b,0x82,0xf8,0x00, 0x00, 0x00, // mov 0xf8(%rdx),%rax + 0x49,0x89,0x82,0x78,0x14, 0x00, 0x00, // mov %rax,0x1478(%r10) + 0x48,0x8b,0x82,0xf0,0x00, 0x00, 0x00, // mov 0xf0(%rdx),%rax + 0x49,0x89,0x42,0x20, // mov %rax,0x20(%r10) + 0x44,0x0f,0x10,0xba,0xe0,0x00,0x00,0x00, // movups 0xe0(%rdx),%xmm15 + 0x44,0x0f,0x10,0xb2,0xd0,0x00,0x00,0x00, // movups 0xd0(%rdx),%xmm14 + 0x44,0x0f,0x10,0xaa,0xc0,0x00,0x00,0x00, // movups 0xc0(%rdx),%xmm13 + 0x44,0x0f,0x10,0xa2,0xb0,0x00,0x00,0x00, // movups 0xb0(%rdx),%xmm12 + 0x44,0x0f,0x10,0x9a,0xa0,0x00,0x00,0x00, // movups 0xa0(%rdx),%xmm11 + 0x44,0x0f,0x10,0x92,0x90,0x00,0x00,0x00, // movups 0x90(%rdx),%xmm10 + 0x44,0x0f,0x10,0x8a,0x80,0x00,0x00,0x00, // movups 0x80(%rdx),%xmm9 + 0x44,0x0f,0x10,0x42,0x70, // movups 0x70(%rdx),%xmm8 + 0x0f,0x10,0x7a,0x60, // movups 0x60(%rdx),%xmm7 + 0x0f,0x10,0x72,0x50, // movups 0x50(%rdx),%xmm6 + 0x48,0x8b,0x72,0x48, // mov 0x48(%rdx),%rsi + 0x48,0x8b,0x7a,0x40, // mov 0x40(%rdx),%rdi + 0x4c,0x8b,0x7a,0x38, // mov 0x38(%rdx),%r15 + 0x4c,0x8b,0x72,0x30, // mov 0x30(%rdx),%r14 + 0x4c,0x8b,0x6a,0x28, // mov 0x28(%rdx),%r13 + 0x4c,0x8b,0x62,0x20, // mov 0x20(%rdx),%r12 + 0x48,0x8b,0x5a,0x18, // mov 0x18(%rdx),%rbx + 0x48,0x8b,0x6a,0x10, // mov 0x10(%rdx),%rbp + 0x48,0x8b,0x62,0x08, // mov 0x8(%rdx),%rsp + 0xff,0x22, // jmpq *(%rdx) + 0xc3, // retq + 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90, // nop + 0x90,0x90, // nop +}; + +void (*_llco_asm_entry)(void) = + (void(*)(void))(void*)llco_wrap_main_code_entry; +void (*_llco_asm_switch)(struct llco_asmctx *from, + struct llco_asmctx *to) = (void(*)(struct llco_asmctx *from, + struct llco_asmctx *to))(void*)llco_asm_switch_code; + +static void llco_asmctx_make(struct llco_asmctx *ctx, + void* stack_base, size_t stack_size, void *arg) +{ + stack_size = stack_size - 32; // Reserve 32 bytes for the shadow space. + void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - + sizeof(size_t)); + stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); // Dummy return address. + ctx->rip = (void*)(_llco_asm_entry); + ctx->rsp = (void*)(stack_high_ptr); + ctx->r12 = (void*)(llco_entry); + ctx->r13 = (void*)(arg); + void* stack_top = (void*)((size_t)stack_base + stack_size); + ctx->stack_base = stack_top; + ctx->stack_limit = stack_base; + ctx->dealloc_stack = stack_base; +} + +#else + +struct llco_asmctx { + void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15; +}; + +void _llco_asm_entry(void); +int _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( + ".text\n" +#ifdef __MACH__ /* Mac OS X assembler */ + ".globl __llco_asm_entry\n" + "__llco_asm_entry:\n" +#else /* Linux assembler */ + ".globl _llco_asm_entry\n" + ".type _llco_asm_entry @function\n" + ".hidden _llco_asm_entry\n" + "_llco_asm_entry:\n" +#endif + " movq %r13, %rdi\n" + " jmpq *%r12\n" +#ifndef __MACH__ + ".size _llco_asm_entry, .-_llco_asm_entry\n" +#endif +); + +__asm__( + ".text\n" +#ifdef __MACH__ /* Mac OS assembler */ + ".globl __llco_asm_switch\n" + "__llco_asm_switch:\n" +#else /* Linux assembler */ + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch @function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#endif + " leaq 0x3d(%rip), %rax\n" + " movq %rax, (%rdi)\n" + " movq %rsp, 8(%rdi)\n" + " movq %rbp, 16(%rdi)\n" + " movq %rbx, 24(%rdi)\n" + " movq %r12, 32(%rdi)\n" + " movq %r13, 40(%rdi)\n" + " movq %r14, 48(%rdi)\n" + " movq %r15, 56(%rdi)\n" + " movq 56(%rsi), %r15\n" + " movq 48(%rsi), %r14\n" + " movq 40(%rsi), %r13\n" + " movq 32(%rsi), %r12\n" + " movq 24(%rsi), %rbx\n" + " movq 16(%rsi), %rbp\n" + " movq 8(%rsi), %rsp\n" + " jmpq *(%rsi)\n" + " ret\n" +#ifndef __MACH__ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +#endif +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, + void* stack_base, size_t stack_size, void *arg) +{ + // Reserve 128 bytes for the Red Zone space (System V AMD64 ABI). + stack_size = stack_size - 128; + void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - + sizeof(size_t)); + stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); // Dummy return address. + ctx->rip = (void*)(_llco_asm_entry); + ctx->rsp = (void*)(stack_high_ptr); + ctx->r12 = (void*)(llco_entry); + ctx->r13 = (void*)(arg); +} + +#endif +#endif // x64 + +// --- END ASM Code --- // + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +//////////////////////////////////////////////////////////////////////////////// +// ASM with stackjmp activated +//////////////////////////////////////////////////////////////////////////////// +#if defined(LLCO_READY) && defined(LLCO_STACKJMP) +LLCO_NOINLINE LLCO_NORETURN +static void llco_stackjmp(void *stack, size_t stack_size, + void(*entry)(void *arg)) +{ + struct llco_asmctx ctx = { 0 }; + llco_asmctx_make(&ctx, stack, stack_size, 0); + struct llco_asmctx ctx0 = { 0 }; + _llco_asm_switch(&ctx0, &ctx); + llco_exit(); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Windows Fibers +//////////////////////////////////////////////////////////////////////////////// +#if defined(_WIN32) && !defined(LLCO_READY) +#define LLCO_WINDOWS +#define LLCO_READY +#define LLCO_METHOD "fibers,windows" + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0400 +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#error Windows fibers unsupported + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Webassembly Fibers +//////////////////////////////////////////////////////////////////////////////// +#if defined(__EMSCRIPTEN__) && !defined(LLCO_READY) +#define LLCO_WASM +#define LLCO_READY +#define LLCO_METHOD "fibers,emscripten" + +#include +#include + +#ifndef LLCO_ASYNCIFY_STACK_SIZE +#define LLCO_ASYNCIFY_STACK_SIZE 4096 +#endif + +static __thread char llco_main_stack[LLCO_ASYNCIFY_STACK_SIZE]; + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Ucontext +//////////////////////////////////////////////////////////////////////////////// +#if !defined(LLCO_READY) +#define LLCO_UCONTEXT +#define LLCO_READY +#define LLCO_METHOD "ucontext" +#ifndef LLCO_STACKJMP +#define LLCO_STACKJMP +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#define _XOPEN_SOURCE +#endif +#include + +static __thread ucontext_t stackjmp_ucallee; +static __thread int stackjmp_ucallee_gotten = 0; + +#if defined(__APPLE__) && defined(__aarch64__) && !defined(LLCO_NOSTACKADJUST) +// Here we ensure that the initial context switch will *not* page the +// entire stack into process memory before executing the entry point +// function. Which is a behavior that can be observed on Mac OS with +// Apple Silicon. This "trick" can be optionally removed at the expense +// of slower initial jumping into large stacks. +enum llco_stack_grows { DOWNWARDS, UPWARDS }; + +static enum llco_stack_grows llco_stack_grows0(int *addr0) { + int addr1; + return addr0 < &addr1 ? UPWARDS : DOWNWARDS; +} + +static enum llco_stack_grows llco_stack_grows(void) { + int addr0; + return llco_stack_grows0(&addr0); +} + +static void llco_adjust_ucontext_stack(ucontext_t *ucp) { + if (llco_stack_grows() == UPWARDS) { + ucp->uc_stack.ss_sp = (char*)ucp->uc_stack.ss_sp+ucp->uc_stack.ss_size; + ucp->uc_stack.ss_size = 0; + } +} +#else +#define llco_adjust_ucontext_stack(ucp) +#endif + +// Ucontext always uses stackjmp with setjmp/longjmp, instead of swapcontext +// becuase it's much faster. +LLCO_NOINLINE LLCO_NORETURN +static void llco_stackjmp(void *stack, size_t stack_size, + void(*entry)(void *arg)) +{ + if (!stackjmp_ucallee_gotten) { + stackjmp_ucallee_gotten = 1; + getcontext(&stackjmp_ucallee); + } + stackjmp_ucallee.uc_stack.ss_sp = stack; + stackjmp_ucallee.uc_stack.ss_size = stack_size; + llco_adjust_ucontext_stack(&stackjmp_ucallee); + makecontext(&stackjmp_ucallee, (void(*)(void))entry, 0); + setcontext(&stackjmp_ucallee); + llco_exit(); +} + +#endif // Ucontext + +#if defined(LLCO_STACKJMP) +#include +#ifdef _WIN32 +// For reasons outside of my understanding, Windows does not allow for jumping +// between stacks using the setjmp/longjmp mechanism. +#error Windows stackjmp not supported +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// llco switching code +//////////////////////////////////////////////////////////////////////////////// + +#include + +struct llco { + struct llco_desc desc; +#if defined(LLCO_STACKJMP) + jmp_buf buf; +#elif defined(LLCO_ASM) + struct llco_asmctx ctx; +#elif defined(LLCO_WASM) + emscripten_fiber_t fiber; +#elif defined(LLCO_WINDOWS) + LPVOID fiber; +#endif +#ifdef LLCO_VALGRIND + int valgrind_stack_id; +#endif +#if defined(__GNUC__) + void *uw_stop_ip; // record of the last unwind ip. +#endif +}; + +#ifdef LLCO_VALGRIND +static __thread unsigned int llco_valgrind_stack_id = 0; +static __thread unsigned int llco_cleanup_valgrind_stack_id = 0; +#endif + +static __thread struct llco llco_thread = { 0 }; +static __thread struct llco *llco_cur = NULL; +static __thread struct llco_desc llco_desc; +static __thread volatile bool llco_cleanup_needed = false; +static __thread volatile struct llco_desc llco_cleanup_desc; +static __thread volatile bool llco_cleanup_active = false; + +#define llco_cleanup_guard() { \ + if (llco_cleanup_active) { \ + fprintf(stderr, "%s not available during cleanup\n", __func__); \ + abort(); \ + } \ +} + +static void llco_cleanup_last(void) { + if (llco_cleanup_needed) { + if (llco_cleanup_desc.cleanup) { + llco_cleanup_active = true; +#ifdef LLCO_VALGRIND + VALGRIND_STACK_DEREGISTER(llco_cleanup_valgrind_stack_id); +#endif + llco_cleanup_desc.cleanup(llco_cleanup_desc.stack, + llco_cleanup_desc.stack_size, llco_cleanup_desc.udata); + llco_cleanup_active = false; + } + llco_cleanup_needed = false; + } +} + +LLCO_NOINLINE +static void llco_entry_wrap(void *arg) { + llco_cleanup_last(); +#if defined(LLCO_WASM) + llco_cur = arg; + llco_cur->desc = llco_desc; +#else + (void)arg; + struct llco self = { .desc = llco_desc }; + llco_cur = &self; +#endif +#ifdef LLCO_VALGRIND + llco_cur->valgrind_stack_id = llco_valgrind_stack_id; +#endif +#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) + llco_cur->uw_stop_ip = __builtin_return_address(0); +#endif + llco_cur->desc.entry(llco_cur->desc.udata); +} + + +LLCO_NOINLINE LLCO_NORETURN +static void llco_entry(void *arg) { + llco_entry_wrap(arg); + llco_exit(); +} + +LLCO_NOINLINE +static void llco_switch1(struct llco *from, struct llco *to, + void *stack, size_t stack_size) +{ +#ifdef LLCO_VALGRIND + llco_valgrind_stack_id = VALGRIND_STACK_REGISTER(stack, stack + stack_size); +#endif +#if defined(LLCO_STACKJMP) + if (to) { + if (!_setjmp(from->buf)) { + _longjmp(to->buf, 1); + } + } else { + if (!_setjmp(from->buf)) { + llco_stackjmp(stack, stack_size, llco_entry); + } + } +#elif defined(LLCO_ASM) + if (to) { + _llco_asm_switch(&from->ctx, &to->ctx); + } else { + struct llco_asmctx ctx = { 0 }; + llco_asmctx_make(&ctx, stack, stack_size, 0); + _llco_asm_switch(&from->ctx, &ctx); + } +#elif defined(LLCO_WASM) + if (to) { + emscripten_fiber_swap(&from->fiber, &to->fiber); + } else { + if (from == &llco_thread) { + emscripten_fiber_init_from_current_context(&from->fiber, + llco_main_stack, LLCO_ASYNCIFY_STACK_SIZE); + } + stack_size -= LLCO_ASYNCIFY_STACK_SIZE; + char *astack = ((char*)stack) + stack_size; + size_t astack_size = LLCO_ASYNCIFY_STACK_SIZE - sizeof(struct llco); + struct llco *self = (void*)(astack + astack_size); + memset(self, 0, sizeof(struct llco)); + emscripten_fiber_init(&self->fiber, llco_entry, + self, stack, stack_size, astack, astack_size); + emscripten_fiber_swap(&from->fiber, &self->fiber); + } +#elif defined(LLCO_WINDOWS) + // Unsupported +#endif +} + +static void llco_switch0(struct llco_desc *desc, struct llco *co, + bool final) +{ + struct llco *from = llco_cur ? llco_cur : &llco_thread; + struct llco *to = desc ? NULL : co ? co : &llco_thread; + if (from != to) { + if (final) { + llco_cleanup_needed = true; + llco_cleanup_desc = from->desc; +#ifdef LLCO_VALGRIND + llco_cleanup_valgrind_stack_id = from->valgrind_stack_id; +#endif + } + if (desc) { + llco_desc = *desc; + llco_switch1(from, 0, desc->stack, desc->stack_size); + } else { + llco_cur = to; + llco_switch1(from, to, 0, 0); + } + llco_cleanup_last(); + } +} + + + +//////////////////////////////////////////////////////////////////////////////// +// Exported methods +//////////////////////////////////////////////////////////////////////////////// + +// Start a new coroutine. +LLCO_EXTERN +void llco_start(struct llco_desc *desc, bool final) { + if (!desc || desc->stack_size < LLCO_MINSTACKSIZE) { + fprintf(stderr, "stack too small\n"); + abort(); + } + llco_cleanup_guard(); + llco_switch0(desc, 0, final); +} + +// Switch to another coroutine. +LLCO_EXTERN +void llco_switch(struct llco *co, bool final) { +#if defined(LLCO_ASM) + // fast track context switch. Saves a few nanoseconds by checking the + // exception condition first. + if (!llco_cleanup_active && llco_cur && co && llco_cur != co && !final) { + struct llco *from = llco_cur; + llco_cur = co; + _llco_asm_switch(&from->ctx, &co->ctx); + llco_cleanup_last(); + return; + } +#endif + llco_cleanup_guard(); + llco_switch0(0, co, final); +} + +// Return the current coroutine or NULL if not currently running in a +// coroutine. +LLCO_EXTERN +struct llco *llco_current(void) { + llco_cleanup_guard(); + return llco_cur == &llco_thread ? 0 : llco_cur; +} + +// Returns a string that indicates which coroutine method is being used by +// the program. Such as "asm" or "ucontext", etc. +LLCO_EXTERN +const char *llco_method(void *caps) { + (void)caps; + return LLCO_METHOD +#ifdef LLCO_STACKJMP + ",stackjmp" +#endif + ; +} + +#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) && !defined(_WIN32) && \ + !defined(LLCO_NOUNWIND) + +#include +#include +#include + +struct llco_dlinfo { + const char *dli_fname; /* Pathname of shared object */ + void *dli_fbase; /* Base address of shared object */ + const char *dli_sname; /* Name of nearest symbol */ + void *dli_saddr; /* Address of nearest symbol */ +}; + +#ifdef __linux__ +int dladdr(const void *, void *); +#endif + +static void llco_getsymbol(struct _Unwind_Context *uwc, + struct llco_symbol *sym) +{ + memset(sym, 0, sizeof(struct llco_symbol)); + sym->cfa = (void*)_Unwind_GetCFA(uwc); + int ip_before; /* unused */ + sym->ip = (void*)_Unwind_GetIPInfo(uwc, &ip_before); + struct llco_dlinfo dlinfo = { 0 }; + if (sym->ip && dladdr(sym->ip, (void*)&dlinfo)) { + sym->fname = dlinfo.dli_fname; + sym->fbase = dlinfo.dli_fbase; + sym->sname = dlinfo.dli_sname; + sym->saddr = dlinfo.dli_saddr; + } +} + +struct llco_unwind_context { + void *udata; + void *start_ip; + bool started; + int nsymbols; + int nsymbols_actual; + struct llco_symbol last; + bool (*func)(struct llco_symbol *, void *); + void *unwind_addr; +}; + +static _Unwind_Reason_Code llco_func(struct _Unwind_Context *uwc, void *ptr) { + struct llco_unwind_context *ctx = ptr; + + struct llco *cur = llco_current(); + if (cur && !cur->uw_stop_ip) { + return _URC_END_OF_STACK; + } + struct llco_symbol sym; + llco_getsymbol(uwc, &sym); + if (ctx->start_ip && !ctx->started && sym.ip != ctx->start_ip) { + return _URC_NO_REASON; + } + ctx->started = true; + if (!sym.ip || (cur && sym.ip == cur->uw_stop_ip)) { + return _URC_END_OF_STACK; + } + ctx->nsymbols++; + if (!cur) { + ctx->nsymbols_actual++; + if (ctx->func && !ctx->func(&sym, ctx->udata)) { + return _URC_END_OF_STACK; + } + } else { + if (ctx->nsymbols > 1) { + ctx->nsymbols_actual++; + if (ctx->func && !ctx->func(&ctx->last, ctx->udata)) { + return _URC_END_OF_STACK; + } + } + ctx->last = sym; + } + return _URC_NO_REASON; +} + +LLCO_EXTERN +int llco_unwind(bool(*func)(struct llco_symbol *sym, void *udata), void *udata){ + struct llco_unwind_context ctx = { +#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) + .start_ip = __builtin_return_address(0), +#endif + .func = func, + .udata = udata + }; + _Unwind_Backtrace(llco_func, &ctx); + return ctx.nsymbols_actual; +} + +#else + +LLCO_EXTERN +int llco_unwind(bool(*func)(struct llco_symbol *sym, void *udata), void *udata){ + (void)func; (void)udata; + /* Unsupported */ + return 0; +} + +#endif +// END llco.c + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__linux__) +#include +static void sched_yield0(void) { + sched_yield(); +} +#else +#define sched_yield0() +#endif + +//////////////////////////////////////////////////////////////////////////////// +// aat.h +//////////////////////////////////////////////////////////////////////////////// +#ifdef SCO_NOAMALGA + +#include "deps/aat.h" + +#else + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +// BEGIN aat.h +// https://github.com/tidwall/aatree +// +// Copyright 2023 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Single header file for generating aat binary search trees. + +#ifndef AAT_H +#define AAT_H + +#define AAT_DEF(specifiers, prefix, type) \ +specifiers type *prefix##_insert(type **root, type *item); \ +specifiers type *prefix##_delete(type **root, type *key); \ +specifiers type *prefix##_search(type **root, type *key); \ +specifiers type *prefix##_delete_first(type **root); \ +specifiers type *prefix##_delete_last(type **root); \ +specifiers type *prefix##_first(type **root); \ +specifiers type *prefix##_last(type **root); \ +specifiers type *prefix##_iter(type **root, type *key); \ +specifiers type *prefix##_prev(type **root, type *item); \ +specifiers type *prefix##_next(type **root, type *item); \ + +#define AAT_FIELDS(type, left, right, level) \ +type *left; \ +type *right; \ +int level; \ + +#define AAT_IMPL(prefix, type, left, right, level, compare) \ +static void prefix##_clear(type *node) { \ + if (node) { \ + node->left = 0; \ + node->right = 0; \ + node->level = 0; \ + } \ +} \ + \ +static type *prefix##_skew(type *node) { \ + if (node && node->left && \ + node->left->level == node->level) \ + { \ + type *left_node = node->left; \ + node->left = left_node->right; \ + left_node->right = node; \ + node = left_node; \ + } \ + return node; \ +} \ + \ +static type *prefix##_split(type *node) { \ + if (node && node->right && node->right->right && \ + node->right->right->level == node->level) \ + { \ + type *right_node = node->right; \ + node->right = right_node->left; \ + right_node->left = node; \ + right_node->level++; \ + node = right_node; \ + } \ + return node; \ +} \ + \ +static type *prefix##_insert0(type *node, type *item, type **replaced) { \ + if (!node) { \ + item->left = 0; \ + item->right = 0; \ + item->level = 1; \ + node = item; \ + } else { \ + int cmp = compare(item, node); \ + if (cmp < 0) { \ + node->left = prefix##_insert0(node->left, item, replaced); \ + } else if (cmp > 0) { \ + node->right = prefix##_insert0(node->right, item, replaced); \ + } else { \ + *replaced = node; \ + item->left = node->left; \ + item->right = node->right; \ + item->level = node->level; \ + node = item; \ + } \ + } \ + node = prefix##_skew(node); \ + node = prefix##_split(node); \ + return node; \ +} \ + \ +type *prefix##_insert(type **root, type *item) { \ + type *replaced = 0; \ + *root = prefix##_insert0(*root, item, &replaced); \ + if (replaced != item) { \ + prefix##_clear(replaced); \ + } \ + return replaced; \ +} \ + \ +static type *prefix##_decrease_level(type *node) { \ + if (node->left || node->right) { \ + int new_level = 0; \ + if (node->left && node->right) { \ + if (node->left->level < node->right->level) { \ + new_level = node->left->level; \ + } else { \ + new_level = node->right->level; \ + } \ + } \ + new_level++; \ + if (new_level < node->level) { \ + node->level = new_level; \ + if (node->right && new_level < node->right->level) { \ + node->right->level = new_level; \ + } \ + } \ + } \ + return node; \ +} \ + \ +static type *prefix##_delete_fixup(type *node) { \ + node = prefix##_decrease_level(node); \ + node = prefix##_skew(node); \ + node->right = prefix##_skew(node->right); \ + if (node->right && node->right->right) { \ + node->right->right = prefix##_skew(node->right->right); \ + } \ + node = prefix##_split(node); \ + node->right = prefix##_split(node->right); \ + return node; \ +} \ + \ +static type *prefix##_delete_first0(type *node, \ + type **deleted) \ +{ \ + if (node) { \ + if (!node->left) { \ + *deleted = node; \ + if (node->right) { \ + node = node->right; \ + } else { \ + node = 0; \ + } \ + } else { \ + node->left = prefix##_delete_first0(node->left, deleted); \ + node = prefix##_delete_fixup(node); \ + } \ + } \ + return node; \ +} \ + \ +static type *prefix##_delete_last0(type *node, \ + type **deleted) \ +{ \ + if (node) { \ + if (!node->right) { \ + *deleted = node; \ + if (node->left) { \ + node = node->left; \ + } else { \ + node = 0; \ + } \ + } else { \ + node->right = prefix##_delete_last0(node->right, deleted); \ + node = prefix##_delete_fixup(node); \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_delete_first(type **root) { \ + type *deleted = 0; \ + *root = prefix##_delete_first0(*root, &deleted); \ + prefix##_clear(deleted); \ + return deleted; \ +} \ + \ +type *prefix##_delete_last(type **root) { \ + type *deleted = 0; \ + *root = prefix##_delete_last0(*root, &deleted); \ + prefix##_clear(deleted); \ + return deleted; \ +} \ + \ +static type *prefix##_delete0(type *node, \ + type *key, type **deleted) \ +{ \ + if (node) { \ + int cmp = compare(key, node); \ + if (cmp < 0) { \ + node->left = prefix##_delete0(node->left, key, deleted); \ + } else if (cmp > 0) { \ + node->right = prefix##_delete0(node->right, key, deleted); \ + } else { \ + *deleted = node; \ + if (!node->left && !node->right) { \ + node = 0; \ + } else { \ + type *leaf_deleted = 0; \ + if (!node->left) { \ + node->right = prefix##_delete_first0(node->right, \ + &leaf_deleted); \ + } else { \ + node->left = prefix##_delete_last0(node->left, \ + &leaf_deleted); \ + } \ + leaf_deleted->left = node->left; \ + leaf_deleted->right = node->right; \ + leaf_deleted->level = node->level; \ + node = leaf_deleted; \ + } \ + } \ + if (node) { \ + node = prefix##_delete_fixup(node); \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_delete(type **root, type *key) { \ + type *deleted = 0; \ + *root = prefix##_delete0(*root, key, &deleted); \ + prefix##_clear(deleted); \ + return deleted; \ +} \ + \ +type *prefix##_search(type **root, type *key) { \ + type *found = 0; \ + type *node = *root; \ + while (node) { \ + int cmp = compare(key, node); \ + if (cmp < 0) { \ + node = node->left; \ + } else if (cmp > 0) { \ + node = node->right; \ + } else { \ + found = node; \ + node = 0; \ + } \ + } \ + return found; \ +} \ + \ +type *prefix##_first(type **root) { \ + type *node = *root; \ + if (node) { \ + while (node->left) { \ + node = node->left; \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_last(type **root) { \ + type *node = *root; \ + if (node) { \ + while (node->right) { \ + node = node->right; \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_iter(type **root, type *key) { \ + type *found = 0; \ + type *node = *root; \ + while (node) { \ + int cmp = compare(key, node); \ + if (cmp < 0) { \ + found = node; \ + node = node->left; \ + } else if (cmp > 0) { \ + node = node->right; \ + } else { \ + found = node; \ + node = 0; \ + } \ + } \ + return found; \ +} \ + \ +static type *prefix##_parent(type **root, \ + type *item) \ +{ \ + type *parent = 0; \ + type *node = *root; \ + while (node) { \ + int cmp = compare(item, node); \ + if (cmp < 0) { \ + parent = node; \ + node = node->left; \ + } else if (cmp > 0) { \ + parent = node; \ + node = node->right; \ + } else { \ + node = 0; \ + } \ + } \ + return parent; \ +} \ + \ +type *prefix##_next(type **root, type *node) { \ + if (node) { \ + if (node->right) { \ + node = node->right; \ + while (node->left) { \ + node = node->left; \ + } \ + } else { \ + type *parent = prefix##_parent(root, node); \ + while (parent && parent->left != node) { \ + node = parent; \ + parent = prefix##_parent(root, parent); \ + } \ + node = parent; \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_prev(type **root, type *node) { \ + if (node) { \ + if (node->left) { \ + node = node->left; \ + while (node->right) { \ + node = node->right; \ + } \ + } else { \ + type *parent = prefix##_parent(root, node); \ + while (parent && parent->right != node) { \ + node = parent; \ + parent = prefix##_parent(root, parent); \ + } \ + node = parent; \ + } \ + } \ + return node; \ +} \ + +#endif // AAT_H +// END aat.h + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif + + +//////////////////////////////////////////////////////////////////////////////// +// Platform independent code below +//////////////////////////////////////////////////////////////////////////////// + +struct sco_link { + struct sco *prev; + struct sco *next; +}; + +struct sco { + union { + // Linked list + struct { + struct sco *prev; + struct sco *next; + }; + // Binary tree (AA-tree) + struct { + AAT_FIELDS(struct sco, left, right, level) + }; + }; + int64_t id; + void *udata; + struct llco *llco; +}; + +static int sco_compare(struct sco *a, struct sco *b) { + return a->id < b->id ? -1: a->id > b->id; +} + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +AAT_DEF(static, sco_aat, struct sco) +AAT_IMPL(sco_aat, struct sco, left, right, level, sco_compare) + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +// https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html +// hash u64 using mix13 +static uint64_t sco_mix13(uint64_t key) { + key ^= (key >> 30); + key *= UINT64_C(0xbf58476d1ce4e5b9); + key ^= (key >> 27); + key *= UINT64_C(0x94d049bb133111eb); + key ^= (key >> 31); + return key; +} + + +//////////////////////////////////////////////////////////////////////////////// +// sco_map - A hashmap-style structure that stores sco types using multiple +// binary search trees (aa-tree) in hashed shards. This allows for the map to +// grow evenly, without allocations, and performing much faster than using a +// single BST. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef SCO_NSHARDS +#define SCO_NSHARDS 512 +#endif + +struct sco_map { + struct sco *roots[SCO_NSHARDS]; + int count; +}; + +#define scp_map_getaat(sco) \ + (&map->roots[sco_mix13((sco)->id) & (SCO_NSHARDS-1)]) + +static struct sco *sco_map_insert(struct sco_map *map, struct sco *sco) { + struct sco *prev = sco_aat_insert(scp_map_getaat(sco), sco); + if (!prev) { + map->count++; + } + return prev; +} + +static struct sco *sco_map_delete(struct sco_map *map, struct sco *key){ + struct sco *prev = sco_aat_delete(scp_map_getaat(key), key); + if (prev) { + map->count--; + } + return prev; +} + +struct sco_list { + struct sco_link head; + struct sco_link tail; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Global and thread-local variables. +//////////////////////////////////////////////////////////////////////////////// + +static __thread bool sco_initialized = false; +static __thread size_t sco_nrunners = 0; +static __thread struct sco_list sco_runners = { 0 }; +static __thread size_t sco_nyielders = 0; +static __thread struct sco_list sco_yielders = { 0 }; +static __thread struct sco *sco_cur = NULL; +static __thread struct sco_map sco_paused = { 0 }; +static __thread size_t sco_npaused = 0; +static __thread bool sco_exit_to_main_requested = false; +static __thread void(*sco_user_entry)(void *udata); + +static atomic_int_fast64_t sco_next_id = 0; +static atomic_bool sco_locker = 0; +static struct sco_map sco_detached = { 0 }; +static size_t sco_ndetached = 0; + +static void sco_lock(void) { + bool expected = false; + while(!atomic_compare_exchange_weak(&sco_locker, &expected, true)) { + expected = false; + sched_yield0(); + } +} + +static void sco_unlock(void) { + atomic_store(&sco_locker, false); +} + +static void sco_list_init(struct sco_list *list) { + list->head.prev = NULL; + list->head.next = (struct sco*)&list->tail; + list->tail.prev = (struct sco*)&list->head; + list->tail.next = NULL; +} + +// Remove the coroutine from the runners or yielders list. +static void sco_remove_from_list(struct sco *co) { + co->prev->next = co->next; + co->next->prev = co->prev; + co->next = co; + co->prev = co; +} + +static void sco_init(void) { + if (!sco_initialized) { + sco_list_init(&sco_runners); + sco_list_init(&sco_yielders); + sco_initialized = true; + } +} + +static struct sco *sco_list_pop_front(struct sco_list *list) { + struct sco *co = NULL; + if (list->head.next != (struct sco*)&list->tail) { + co = list->head.next; + sco_remove_from_list(co); + } + return co; +} + +static void sco_list_push_back(struct sco_list *list, struct sco *co) { + sco_remove_from_list(co); + list->tail.prev->next = co; + co->prev = list->tail.prev; + co->next = (struct sco*)&list->tail; + list->tail.prev = co; +} + +static void sco_return_to_main(bool final) { + sco_cur = NULL; + sco_exit_to_main_requested = false; + llco_switch(0, final); +} + +static void sco_switch(bool resumed_from_main, bool final) { + if (sco_nrunners == 0) { + // No more runners. + if (sco_nyielders == 0 || sco_exit_to_main_requested || + (!resumed_from_main && sco_npaused > 0)) { + sco_return_to_main(final); + return; + } + // Convert the yielders to runners + sco_runners.head.next = sco_yielders.head.next; + sco_runners.head.next->prev = (struct sco*)&sco_runners.head; + sco_runners.tail.prev = sco_yielders.tail.prev; + sco_runners.tail.prev->next = (struct sco*)&sco_runners.tail; + sco_yielders.head.next = (struct sco*)&sco_yielders.tail; + sco_yielders.tail.prev = (struct sco*)&sco_yielders.head; + sco_nrunners = sco_nyielders; + sco_nyielders = 0; + } + sco_cur = sco_list_pop_front(&sco_runners); + sco_nrunners--; + llco_switch(sco_cur->llco, final); +} + +static void sco_entry(void *udata) { + // Initialize a new coroutine on the user's stack. + struct sco scostk = { 0 }; + struct sco *co = &scostk; + co->llco = llco_current(); + co->id = atomic_fetch_add(&sco_next_id, 1) + 1; + co->udata = udata; + co->prev = co; + co->next = co; + if (sco_cur) { + // Reschedule the coroutine that started this one + sco_list_push_back(&sco_yielders, co); + sco_list_push_back(&sco_yielders, sco_cur); + sco_nyielders += 2; + sco_switch(false, false); + } + sco_cur = co; + if (sco_user_entry) { + sco_user_entry(udata); + } + // This coroutine is finished. Switch to the next coroutine. + sco_switch(false, true); +} + +SCO_EXTERN +void sco_exit(void) { + if (sco_cur) { + sco_exit_to_main_requested = true; + sco_switch(false, true); + } +} + +SCO_EXTERN +void sco_start(struct sco_desc *desc) { + sco_init(); + struct llco_desc llco_desc = { + .entry = sco_entry, + .cleanup = desc->cleanup, + .stack = desc->stack, + .stack_size = desc->stack_size, + .udata = desc->udata, + }; + sco_user_entry = desc->entry; + llco_start(&llco_desc, false); +} + +SCO_EXTERN +int64_t sco_id(void) { + return sco_cur ? sco_cur->id : 0; +} + +SCO_EXTERN +void sco_yield(void) { + if (sco_cur) { + sco_list_push_back(&sco_yielders, sco_cur); + sco_nyielders++; + sco_switch(false, false); + } +} + +SCO_EXTERN +void sco_pause(void) { + if (sco_cur) { + sco_map_insert(&sco_paused, sco_cur); + sco_npaused++; + sco_switch(false, false); + } +} + +SCO_EXTERN +void sco_resume(int64_t id) { + sco_init(); + if (id == 0 && !sco_cur) { + // Resuming from main + sco_switch(true, false); + } else { + // Resuming from coroutine + struct sco *co = sco_map_delete(&sco_paused, &(struct sco){ .id = id }); + if (co) { + sco_npaused--; + co->prev = co; + co->next = co; + sco_list_push_back(&sco_yielders, co); + sco_nyielders++; + sco_yield(); + } + } +} + +SCO_EXTERN +void sco_detach(int64_t id) { + struct sco *co = sco_map_delete(&sco_paused, &(struct sco){ .id = id }); + if (co) { + sco_npaused--; + sco_lock(); + sco_map_insert(&sco_detached, co); + sco_ndetached++; + sco_unlock(); + } +} + +SCO_EXTERN +void sco_attach(int64_t id) { + sco_lock(); + struct sco *co = sco_map_delete(&sco_detached, &(struct sco){ .id = id }); + if (co) { + sco_ndetached--; + } + sco_unlock(); + if (co) { + sco_map_insert(&sco_paused, co); + sco_npaused++; + } +} + +SCO_EXTERN +void *sco_udata(void) { + return sco_cur ? sco_cur->udata : NULL; +} + +SCO_EXTERN +size_t sco_info_scheduled(void) { + return sco_nyielders; +} + +SCO_EXTERN +size_t sco_info_paused(void) { + return sco_npaused; +} + +SCO_EXTERN +size_t sco_info_running(void) { + size_t running = sco_nrunners; + if (sco_cur) { + // Count the current coroutine + running++; + } + return running; +} + +SCO_EXTERN +size_t sco_info_detached(void) { + sco_lock(); + size_t ndetached = sco_ndetached; + sco_unlock(); + return ndetached; +} + +// Returns true if there are any coroutines running, yielding, or paused. +SCO_EXTERN +bool sco_active(void) { + // Notice that detached coroutinues are not included. + return (sco_nyielders + sco_npaused + sco_nrunners + !!sco_cur) > 0; +} + +SCO_EXTERN +const char *sco_info_method(void) { + return llco_method(0); +} + +struct sco_unwind_context { + int nsymbols_actual; + bool started; + void *start_ip; + void *udata; + bool (*func)(struct sco_symbol*, void*); +}; + +static bool sco_unwind_step(struct llco_symbol *llco_sym, void *udata) { + struct sco_unwind_context *ctx = udata; + if (ctx->start_ip && !ctx->started && llco_sym->ip != ctx->start_ip) { + return true; + } + struct sco_symbol sym = { + .cfa = llco_sym->cfa, + .fbase = llco_sym->fbase, + .fname = llco_sym->fname, + .ip = llco_sym->ip, + .saddr = llco_sym->saddr, + .sname = llco_sym->sname, + }; + ctx->started = true; + ctx->nsymbols_actual++; + return !ctx->func || ctx->func(&sym, ctx->udata); +} + +// Unwinds the stack and returns the number of symbols +SCO_EXTERN +int sco_unwind(bool(*func)(struct sco_symbol *sym, void *udata), void *udata) { + struct sco_unwind_context ctx = { +#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) + .start_ip = __builtin_return_address(0), +#endif + .func = func, + .udata = udata, + }; + llco_unwind(sco_unwind_step, &ctx); + return ctx.nsymbols_actual; +} diff --git a/deps/sco.h b/deps/sco.h new file mode 100644 index 0000000..60617a8 --- /dev/null +++ b/deps/sco.h @@ -0,0 +1,94 @@ +// https://github.com/tidwall/sco +// +// Copyright 2023 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +#ifndef SCO_H +#define SCO_H + +#include +#include +#include + +#define SCO_MINSTACKSIZE 131072 // Recommended minimum stack size + +struct sco_desc { + void *stack; + size_t stack_size; + void (*entry)(void *udata); + void (*cleanup)(void *stack, size_t stack_size, void *udata); + void *udata; +}; + +// Starts a new coroutine with the provided description. +void sco_start(struct sco_desc *desc); + +// Causes the calling coroutine to relinquish the CPU. +// This operation should be called from a coroutine, otherwise it does nothing. +void sco_yield(void); + +// Get the identifier for the current coroutine. +// This operation should be called from a coroutine, otherwise it returns zero. +int64_t sco_id(void); + +// Pause the current coroutine. +// This operation should be called from a coroutine, otherwise it does nothing. +void sco_pause(void); + +// Resume a paused coroutine. +// If the id is invalid or does not belong to a paused coroutine then this +// operation does nothing. +// Calling sco_resume(0) is a special case that continues a runloop. See the +// README for an example. +void sco_resume(int64_t id); + +// Returns true if there are any coroutines running, yielding, or paused. +bool sco_active(void); + +// Detach a coroutine from a thread. +// This allows for moving coroutines between threads. +// The coroutine must be currently paused before it can be detached, thus this +// operation cannot be called from the coroutine belonging to the provided id. +// If the id is invalid or does not belong to a paused coroutine then this +// operation does nothing. +void sco_detach(int64_t id); + +// Attach a coroutine to a thread. +// This allows for moving coroutines between threads. +// If the id is invalid or does not belong to a detached coroutine then this +// operation does nothing. +// Once attached, the coroutine will be paused. +void sco_attach(int64_t id); + +// Exit a coroutine early. +// This _will not_ exit the program. Rather, it's for ending the current +// coroutine and quickly switching to the thread's runloop before any other +// scheduled (yielded) coroutines run. +// This operation should be called from a coroutine, otherwise it does nothing. +void sco_exit(void); + +// Returns the user data of the currently running coroutine. +void *sco_udata(void); + +// General information and statistics +size_t sco_info_scheduled(void); +size_t sco_info_running(void); +size_t sco_info_paused(void); +size_t sco_info_detached(void); +const char *sco_info_method(void); + +// Coroutine stack unwinding +struct sco_symbol { + void *cfa; // Canonical Frame Address + void *ip; // Instruction Pointer + const char *fname; // Pathname of shared object + void *fbase; // Base address of shared object + const char *sname; // Name of nearest symbol + void *saddr; // Address of nearest symbol +}; + +// Unwinds the stack and returns the number of symbols +int sco_unwind(bool (*func)(struct sco_symbol *sym, void *udata), void *udata); + +#endif // SCO_H diff --git a/deps/stack.c b/deps/stack.c new file mode 100644 index 0000000..e142f1a --- /dev/null +++ b/deps/stack.c @@ -0,0 +1,413 @@ +// https://github.com/tidwall/stack +// +// Copyright 2024 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Coroutine stack allocator + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + +#ifndef STACK_STATIC +#include "stack.h" +#else +#define STACK_API static +struct stack_opts { + size_t stacksz; + size_t defcap; + size_t maxcap; + size_t gapsz; + bool useguards; + bool nostackfreelist; + bool nopagerelease; + bool onlymalloc; +}; +struct stack { char _[32]; }; +struct stack_mgr { char _[320]; }; +#endif + +#ifndef STACK_API +#define STACK_API +#endif + +struct stack_group { + struct stack_group *prev; + struct stack_group *next; + size_t allocsz; + size_t stacksz; + size_t gapsz; + size_t pagesz; + bool guards; + char *stack0; + size_t cap; // max number of stacks + size_t pos; // index of next stack to use + size_t use; // how many are used +}; + +struct stack_freed { + struct stack_freed *prev; + struct stack_freed *next; + struct stack_group *group; +}; + +struct stack_mgr0 { + size_t pagesz; + size_t stacksz; + size_t defcap; + size_t maxcap; + size_t gapsz; + bool useguards; + bool nostackfreelist; + bool nopagerelease; + bool onlymalloc; + struct stack_group gendcaps[2]; + struct stack_group *group_head; + struct stack_group *group_tail; + struct stack_freed fendcaps[2]; + struct stack_freed *free_head; + struct stack_freed *free_tail; +}; + +struct stack0 { + void *addr; + size_t size; + struct stack_group *group; +}; + +static_assert(sizeof(struct stack) >= sizeof(struct stack0), ""); +static_assert(sizeof(struct stack_mgr) >= sizeof(struct stack_mgr0), ""); + +// Returns a size that is aligned to a boundary. +// The boundary must be a power of 2. +static size_t stack_align_size(size_t size, size_t boundary) { + return size < boundary ? boundary : + size&(boundary-1) ? size+boundary-(size&(boundary-1)) : + size; +} + +#ifndef _WIN32 + +// allocate memory using mmap. Used primarily for stack group memory. +static void *stack_mmap_alloc(size_t size) { + void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (addr == MAP_FAILED || addr == NULL) { + return NULL; + } + return addr; +} + +// free the stack group memory +static void stack_mmap_free(void *addr, size_t size) { + if (addr) { + munmap(addr, size); + } +} + +static struct stack_group *stack_group_new(size_t stacksz, size_t pagesz, + size_t cap, size_t gapsz, bool useguards) +{ + bool guards; + if (gapsz == 0) { + guards = false; + } else { + gapsz = stack_align_size(gapsz, pagesz); + guards = useguards; + } + // Calculate the allocation size of the group. + // A group allocation contains the group struct and all its stacks. + // Each stack is separated by an optional gap page, which can also act as + // the guard page. There will be one more gap pages than stack to ensure + // that each stack is sandwiched by gaps or guards, allowing for overflow + // padding or segv detection for stacks that grow up or down. + // For example, let's say the group has a capacity of 3. The memory layout + // will end up look something like this + // + // [ GUARD ][ Stack1 ][ GUARD ][ Stack2 ][ GUARD ][ Stack3 ][ GUARD ] + // + // where the entire group is a single mmap and each stack is sandwiched by + // guard pages. + size_t allocsz = stack_align_size(sizeof(struct stack_group), pagesz); + allocsz += gapsz; // add space for prefix gap/guard + size_t stack0 = allocsz; // offset of first stack + allocsz += (stacksz + gapsz) * cap; // add the remainder size + struct stack_group *group = stack_mmap_alloc(allocsz); + if (!group) { + return NULL; + } + memset(group, 0, sizeof(struct stack_group)); + group->allocsz = allocsz; + group->next = group; + group->prev = group; + group->guards = guards; + group->gapsz = gapsz; + group->stacksz = stacksz; + group->pagesz = pagesz; + group->stack0 = ((char*)group)+stack0; + group->cap = cap; + return group; +} + +static void stack_group_free(struct stack_group *group) { + stack_mmap_free(group, group->allocsz); +} + +static void stack_group_remove(struct stack_group *group) { + group->prev->next = group->next; + group->next->prev = group->prev; + group->next = NULL; + group->prev = NULL; +} + +static struct stack_group *stack_freed_remove(struct stack_freed *stack) { + stack->prev->next = stack->next; + stack->next->prev = stack->prev; + stack->next = NULL; + stack->prev = NULL; + struct stack_group *group = stack->group; + stack->group = NULL; + return group; +} + +// push a stack_group to the end of the manager group list. +static void stack_push_group(struct stack_mgr0 *mgr, struct stack_group *group) +{ + mgr->group_tail->prev->next = group; + group->prev = mgr->group_tail->prev; + group->next = mgr->group_tail; + mgr->group_tail->prev = group; +} + +static void stack_push_freed_stack(struct stack_mgr0 *mgr, + struct stack_freed *stack, struct stack_group *group) +{ + mgr->free_tail->prev->next = stack; + stack->prev = mgr->free_tail->prev; + stack->next = mgr->free_tail; + mgr->free_tail->prev = stack; + stack->group = group; +} +#endif + +// initialize a stack manager + +static void stack_mgr_init_(struct stack_mgr0 *mgr, struct stack_opts *opts) { +#ifdef _WIN32 + size_t pagesz = 4096; +#else + size_t pagesz = (size_t)sysconf(_SC_PAGESIZE); +#endif + size_t stacksz = opts && opts->stacksz ? opts->stacksz : 8388608; + stacksz = stack_align_size(stacksz, pagesz); + memset(mgr, 0, sizeof(struct stack_mgr0)); + mgr->stacksz = stacksz; + mgr->defcap = opts && opts->defcap ? opts->defcap : 4; + mgr->maxcap = opts && opts->maxcap ? opts->maxcap : 8192; + mgr->gapsz = opts && opts->gapsz ? opts->gapsz : 1048576; + mgr->useguards = opts && opts->useguards; + mgr->nostackfreelist = opts && opts->nostackfreelist; + mgr->nopagerelease = opts && opts->nopagerelease; + mgr->onlymalloc = opts && opts->onlymalloc; + mgr->pagesz = pagesz; + mgr->group_head = &mgr->gendcaps[0]; + mgr->group_tail = &mgr->gendcaps[1]; + mgr->group_head->next = mgr->group_tail; + mgr->group_tail->prev = mgr->group_head; + if (!mgr->nostackfreelist) { + mgr->free_head = &mgr->fendcaps[0]; + mgr->free_tail = &mgr->fendcaps[1]; + mgr->free_head->next = mgr->free_tail; + mgr->free_tail->prev = mgr->free_head; + } +#ifdef _WIN32 + mgr->onlymalloc = true; +#endif +} + +STACK_API +void stack_mgr_init(struct stack_mgr *mgr, struct stack_opts *opts) { + stack_mgr_init_((void*)mgr, opts); +} + + +static void stack_mgr_destroy_(struct stack_mgr0 *mgr) { +#ifndef _WIN32 + struct stack_group *group = mgr->group_head->next; + while (group != mgr->group_tail) { + struct stack_group *next = group->next; + stack_group_free(group); + group = next; + } +#endif + memset(mgr, 0, sizeof(struct stack_mgr0)); +} + +STACK_API +void stack_mgr_destroy(struct stack_mgr *mgr) { + stack_mgr_destroy_((void*)mgr); +} + +#ifndef _WIN32 +static void stack_release_group(struct stack_group *group, bool nofreelist) { + // Remove all stacks from free list, remove the group from the group list, + // and free group. + if (!nofreelist) { + struct stack_freed *stack; + for (size_t i = 0; i < group->pos; i++) { + stack = (void*)(group->stack0 + (group->stacksz+group->gapsz) * i); + stack_freed_remove(stack); + } + } + stack_group_remove(group); + stack_group_free(group); +} +#endif + +static int stack_get_(struct stack_mgr0 *mgr, struct stack0 *stack) { + if (mgr->onlymalloc) { + void *addr = malloc(mgr->stacksz); + if (!addr) { + return -1; + } + stack->addr = addr; + stack->size = mgr->stacksz; + stack->group = 0; + return 0; + } +#ifndef _WIN32 + struct stack_group *group; + if (!mgr->nostackfreelist) { + struct stack_freed *fstack = mgr->free_tail->prev; + if (fstack != mgr->free_head) { + group = stack_freed_remove(fstack); + group->use++; + stack->addr = fstack; + stack->size = mgr->stacksz; + stack->group = group; + return 0; + } + } + group = mgr->group_tail->prev; + if (group->pos == group->cap) { + size_t cap = group->cap ? group->cap * 2 : mgr->defcap; + if (cap > mgr->maxcap) { + cap = mgr->maxcap; + } + group = stack_group_new(mgr->stacksz, mgr->pagesz, cap, mgr->gapsz, + mgr->useguards); + if (!group) { + return -1; + } + stack_push_group(mgr, group); + } + char *addr = group->stack0 + (group->stacksz+group->gapsz) * group->pos; + if (group->guards) { + // Add page guards to the coroutine stack. + // A failure here usually means that there isn't enough system memory + // to split the a mmap'd virtual memory region into two. + // Linux assigns limits to how many distinct mapped regions a process + // may have. Typically around 64K. This means that when used with + // stack guards on Linux, you will probably be limited to about 64K + // concurrent threads and coroutines. To increase this limit, increase + // the value in the /proc/sys/vm/max_map_count, or disable page guards + // by setting 'guards' option to false. + int ret = 0; + if (addr == group->stack0) { + // Add the prefix guard page. + // This separates the group struct page and the first stack. + ret = mprotect(addr-group->gapsz, group->gapsz, PROT_NONE); + } + // Add the suffix guard page. + if (ret == 0) { + ret = mprotect(addr+group->stacksz, group->gapsz, PROT_NONE); + } + if (ret == -1) { + return -1; + } + } + group->pos++; + group->use++; + stack->addr = addr; + stack->size = mgr->stacksz; + stack->group = group; +#endif + return 0; +} + +STACK_API +int stack_get(struct stack_mgr *mgr, struct stack *stack) { + return stack_get_((void*)mgr, (void*)stack); +} + +static void stack_put_(struct stack_mgr0 *mgr, struct stack0 *stack) { + if (mgr->onlymalloc) { + free(stack->addr); + return; + } +#ifndef _WIN32 + void *addr = stack->addr; + struct stack_group *group = stack->group; + if (!mgr->nopagerelease){ + char *stack0 = addr; + size_t stacksz = group->stacksz; + if (!mgr->nostackfreelist) { + // The first page does not need to be released. + stack0 += group->pagesz; + stacksz -= group->pagesz; + } + if (stacksz > 0) { + // Re-mmap the pages that encompass the stack. The MAP_FIXED option + // releases the pages back to the operating system. Yet the entire + // stack will still exists in the processes virtual memory. + mmap(stack0, stacksz, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + } + } + group->use--; + if (!mgr->nostackfreelist) { + // Add the stack to the stack freed linked list. + // This will cause the first page of the stack to being paged into + // process memory, thus using up at least page_size of data per linked + // stack. + stack_push_freed_stack(mgr, addr, group); + } + if (group->use == 0) { + // There are no more stacks in use for this group that belongs to the + // provided stack, and all other stacks have been used at least once in + // the past. + // The group should be fully released back to the operating system. + stack_release_group(group, mgr->nostackfreelist); + } +#endif +} + +STACK_API +void stack_put(struct stack_mgr *mgr, struct stack *stack) { + stack_put_((void*)mgr, (void*)stack); +} + +static size_t stack_size_(struct stack0 *stack) { + return stack->size; +} + +STACK_API +size_t stack_size(struct stack *stack) { + return stack_size_((void*)stack); +} + +static void *stack_addr_(struct stack0 *stack) { + return stack->addr; +} + +STACK_API +void *stack_addr(struct stack *stack) { + return stack_addr_((void*)stack); +} diff --git a/deps/stack.h b/deps/stack.h new file mode 100644 index 0000000..4847f55 --- /dev/null +++ b/deps/stack.h @@ -0,0 +1,40 @@ +// https://github.com/tidwall/stack +// +// Copyright 2024 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Coroutine stack allocator + +#ifndef STACK_H +#define STACK_H + +#include +#include + +struct stack_opts { + size_t stacksz; // Stack size (default 8388608) + size_t defcap; // Default stack_group capacity (default 4) + size_t maxcap; // Max stack_group capacity (default 8192) + size_t gapsz; // Size of gap (guard) pages (default 1048576) + bool useguards; // Use mprotect'ed guard pages (default false) + bool nostackfreelist; // Do not use a stack free list (default false) + bool nopagerelease; // Do not early release mmapped pages (default false) + bool onlymalloc; // Only use malloc. Everything but stacksz is ignored. +}; + +struct stack { char _[32]; }; +struct stack_mgr { char _[320]; }; + +void stack_mgr_init(struct stack_mgr *mgr, struct stack_opts *opts); +void stack_mgr_destroy(struct stack_mgr *mgr); +int stack_get(struct stack_mgr *mgr, struct stack *stack); +void stack_put(struct stack_mgr *mgr, struct stack *stack); + +// The base address of the stack. +void *stack_addr(struct stack *stack); + +// Returns the size of the stack. +size_t stack_size(struct stack *stack); + +#endif // STACK_H diff --git a/deps/worker.c b/deps/worker.c new file mode 100644 index 0000000..44e2b8e --- /dev/null +++ b/deps/worker.c @@ -0,0 +1,201 @@ +// https://github.com/tidwall/worker.c +// +// Copyright 2024 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Simple background worker for C + +#include +#include +#include +#include +#include + +#define WORKER_DEF_TIMEOUT INT64_C(1000000000) // one second +#define WORKER_DEF_MAX_THREADS 2 +#define WORKER_DEF_MAX_THREAD_ENTRIES 32 + +struct worker_opts { + int max_threads; // def: 2 + int max_thread_entries; // def: 32 + int64_t thread_timeout; // nanoseconds, def: 1 second + void*(*malloc)(size_t); // def: system malloc + void(*free)(void*); // def: system free +}; + +struct worker_entry { + void (*work)(void *udata); + void *udata; +}; + +struct worker_thread { + pthread_mutex_t mu; + pthread_cond_t cond; + pthread_t th; + int64_t timeout; + bool end; + int pos, len; + int nentries; + struct worker_entry *entries; +}; + +struct worker { + int nthreads; + struct worker_thread **threads; + void (*free)(void*); +}; + +void worker_free(struct worker *worker) { + if (worker) { + if (worker->threads) { + for (int i = 0; i < worker->nthreads; i++) { + struct worker_thread *thread = worker->threads[i]; + if (thread) { + pthread_mutex_lock(&thread->mu); + thread->end = true; + pthread_t th = thread->th; + pthread_cond_signal(&thread->cond); + pthread_mutex_unlock(&thread->mu); + if (th) { + pthread_join(th, 0); + } + worker->free(thread->entries); + worker->free(thread); + } + } + worker->free(worker->threads); + } + worker->free(worker); + } +} + +struct worker *worker_new(struct worker_opts *opts) { + // Load options + int nthreads = opts ? opts->max_threads : 0; + int nentries = opts ? opts->max_thread_entries : 0; + int64_t timeout = opts ? opts->thread_timeout : 0; + void*(*malloc_)(size_t) = opts ? opts->malloc : 0; + void(*free_)(void*) = opts ? opts->free : 0; + nthreads = nthreads <= 0 ? WORKER_DEF_MAX_THREADS : + nthreads > 65536 ? 65536 : nthreads; + nentries = nentries <= 0 ? WORKER_DEF_MAX_THREAD_ENTRIES : + nentries > 65536 ? 65536 : nentries; + timeout = timeout <= 0 ? WORKER_DEF_TIMEOUT : timeout; + malloc_ = malloc_ ? malloc_ : malloc; + free_ = free_ ? free_ : free; + + struct worker *worker = malloc_(sizeof(struct worker)); + if (!worker) { + return NULL; + } + memset(worker, 0, sizeof(struct worker)); + worker->free = free_; + worker->nthreads = nthreads; + worker->threads = malloc_(sizeof(struct worker_thread*) * nthreads); + if (!worker->threads) { + worker_free(worker); + return NULL; + } + memset(worker->threads, 0, sizeof(struct worker_thread*) * nthreads); + for (int i = 0; i < worker->nthreads; i++) { + struct worker_thread *thread = malloc_(sizeof(struct worker_thread)); + if (!thread) { + worker_free(worker); + return NULL; + } + memset(thread, 0, sizeof(struct worker_thread)); + worker->threads[i] = thread; + thread->timeout = timeout; + thread->nentries = nentries; + thread->entries = malloc_(sizeof(struct worker_entry) * nentries); + if (!thread->entries) { + worker_free(worker); + return NULL; + } + memset(thread->entries, 0, sizeof(struct worker_entry) * nentries); + pthread_mutex_init(&thread->mu, 0); + pthread_cond_init(&thread->cond, 0); + thread->nentries = nentries; + } + return worker; +} + +static void *worker_entry(void *arg) { + // printf("thread created\n"); + struct worker_thread *thread = arg; + pthread_mutex_lock(&thread->mu); + while (1) { + while (thread->len > 0) { + struct worker_entry entry = thread->entries[thread->pos]; + thread->pos++; + if (thread->pos == thread->nentries) { + thread->pos = 0; + } + thread->len--; + pthread_mutex_unlock(&thread->mu); + if (entry.work) { + entry.work(entry.udata); + } + pthread_mutex_lock(&thread->mu); + } + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; + pthread_cond_timedwait(&thread->cond, &thread->mu, &ts); + if (thread->len == 0) { + thread->th = 0; + if (!thread->end) { + pthread_detach(thread->th); + } + thread->end = false; + break; + } + } + pthread_mutex_unlock(&thread->mu); + // printf("thread ended\n"); + return NULL; +} + +/// Submit work +/// @param worker the worker +/// @param pin pin to a thread or set to -1 for Round-robin selection +/// @param work the work to perform +/// @param udata any user data +/// @return true for success or false if no worker is available. +/// @return false for invalid arguments. Worker and work must no be null. +bool worker_submit(struct worker *worker, int64_t pin, void(*work)(void *udata), + void *udata) +{ + if (!worker || !work) { + return false; + } + static __thread uint32_t worker_next_index = 0; + if (pin < 0) { + pin = worker_next_index; + } + worker_next_index++; + struct worker_thread *thread = worker->threads[pin%worker->nthreads]; + bool submitted = false; + pthread_mutex_lock(&thread->mu); + if (thread->len < thread->nentries) { + int pos = thread->pos + thread->len; + if (pos >= thread->nentries) { + pos -= thread->nentries; + } + thread->entries[pos].work = work; + thread->entries[pos].udata = udata; + thread->len++; + if (!thread->th) { + int ret = pthread_create(&thread->th, 0, worker_entry, thread); + if (ret == -1) { + pthread_mutex_unlock(&thread->mu); + return false; + } + } + submitted = true; + pthread_cond_signal(&thread->cond); + } + pthread_mutex_unlock(&thread->mu); + return submitted; +} diff --git a/deps/worker.h b/deps/worker.h new file mode 100644 index 0000000..a16f113 --- /dev/null +++ b/deps/worker.h @@ -0,0 +1,30 @@ +// https://github.com/tidwall/worker.c +// +// Copyright 2024 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Simple background worker for C + +#ifndef WORKER_H +#define WORKER_H + +#include +#include +#include + +struct worker_opts { + int max_threads; // def: 2 + int max_thread_entries; // def: 32 + int64_t thread_timeout; // nanoseconds, def: 1 second + void*(*malloc)(size_t); // def: system malloc + void(*free)(void*); // def: system free +}; + +struct worker *worker_new(struct worker_opts *opts); +void worker_free(struct worker *worker); +bool worker_submit(struct worker *worker, int64_t pin, void(*work)(void *udata), void *udata); +bool worker_submit_read(struct worker *worker, int64_t pin, int fd, void *data, size_t count, void(*complete)(int res, void *udata), void *udata); +bool worker_submit_write(struct worker *worker, int64_t pin, int fd, const void *data, size_t count, void(*complete)(int res, void *udata), void *udata); + +#endif // WORKER_H diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..c036379 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +tmp/ \ No newline at end of file diff --git a/docs/API.md b/docs/API.md new file mode 100644 index 0000000..975f2f1 --- /dev/null +++ b/docs/API.md @@ -0,0 +1,2389 @@ + + +# Neco C API + +C API for the Neco coroutine library. + +This document provides a detailed description of the functions and types in the +[neco.h](../neco.h) and [neco.c](../neco.c) source files for the Neco library. + +For a more general overview please see the project +[README](https://github.com/tidwall/neco). + + +## Table of contents + +- [Programing notes](#programming-notes) +- [Basic operations](#basic-operations) +- [Channels](#channels) +- [Generators](#generators) +- [Mutexes](#mutexes) +- [WaitGroups](#waitgroups) +- [Condition variables](#condition-variables) +- [Posix wrappers](#posix-wrappers) +- [File descriptor helpers](#file-descriptor-helpers) +- [Networking utilities](#Networking-utilities) +- [Streams and buffered I/O](#streams-and-buffered-io) +- [Random number generator](#random-number-generator) +- [Error handling](#error-handling) +- [Background worker](#background-worker) +- [Time](#time) +- [Signals](#signals) +- [Cancelation](#cancelation) +- [Stats and information](#stats-and-information) +- [Global environment](#global-environment) + +## Programming notes + +### neco_main + +The `neco_main()` function may be used instead of the standard C `main()`, +which effectively runs the entire program in a Neco coroutine context. + +```c +#include "neco.h" + +int neco_main(int argc, char *argv[]) { + // Running inside of a Neco coroutine + return 0; +} +``` + +Doing so adjusts Neco to behave as follows: + +- `neco_env_setpaniconerror(true)` +- `neco_env_setcanceltype(NECO_CANCEL_ASYNC)` +- The program will exit after the the main coroutine returns. + + +### Neco errors + +Neco functions return Neco errors. + +```c +#define NECO_OK 0 ///< Successful result (no error) +#define NECO_ERROR -1 ///< System error (check errno) +#define NECO_INVAL -2 ///< Invalid argument +#define NECO_PERM -3 ///< Operation not permitted +#define NECO_NOMEM -4 ///< Cannot allocate memory +#define NECO_EOF -5 ///< End of file or stream (neco_stream_*) +#define NECO_NOTFOUND -6 ///< No such coroutine (neco_cancel) +#define NECO_NOSIGWATCH -7 ///< Not watching on a signal +#define NECO_CLOSED -8 ///< Channel is closed +#define NECO_EMPTY -9 ///< Channel is empty (neco_chan_tryrecv) +#define NECO_TIMEDOUT -10 ///< Deadline has elapsed (by neco_*_dl) +#define NECO_CANCELED -11 ///< Operation canceled (by neco_cancel) +#define NECO_BUSY -12 ///< Resource busy (by mutex_trylock) +#define NECO_NEGWAITGRP -13 ///< Negative waitgroup counter +#define NECO_GAIERROR -14 ///< Error with getaddrinfo (check neco_gai_error) +#define NECO_UNREADFAIL -15 ///< Failed to unread byte (neco_stream_unread_byte) +#define NECO_PARTIALWRITE -16 ///< Failed to write all data (neco_stream_flush) +#define NECO_NOTGENERATOR -17 ///< Coroutine is not a generator (neco_gen_yield) +#define NECO_NOTSUSPENDED -18 ///< Coroutine is not suspended (neco_resume) +``` + +Three of those errors will panic when `neco_env_setpaniconerror(true)`: +`NECO_INVAL`, `NECO_PERM`, and `NECO_NOMEM`. + +Some Neco functions are Posix wrappers, which return the `NECO_ERROR` (-1) and +it's the programmers responsibilty to check the errno. +Those functions include [neco_read()](#neco_read), [neco_write()](#neco_write), +[neco_accept()](#neco_accept), and [neco_connect()](#neco_connect). + + +At any point the programmer may check the `neco_lasterr()` function to get the +Neco error from the last `neco_*` call. This function will ensure that system +errors will be automatically converted to its Neco equivalent. For example, +let's say that a `neco_read()` (which is Posix wrapper for `read()`) returns +`-1` and the `errno` is `EINVAL`, then `neco_lasterr()` will return +`NECO_INVAL`. + +### Deadlines and cancelation + +All operations that may block a coroutine will have an extended function with +a `_dl` suffix that provides an additional deadline argument. +A deadline is a timestamp in nanoseconds. NECO_TIMEDOUT will be returned if +the operation does not complete in the provide time. + +All operations that may block can also be canceled using the [`neco_cancel()`](docs/API.md#neco_cancel) +function from a different coroutine. +Calling `neco_cancel()` will not cancel an operation that does not block. +A cancel takes higher priority than a deadline. Such that if an operation was +canceled and also has timedout at the same time the cancel wins and +NECO_CANCELED will be returned. + +```c + +// Try to connect, timing out after one second. +int fd = neco_dial_dl("tcp", "google.com:80", neco_now() + NECO_SECOND); +if (fd < 0) { + // Connection failed + if (fd == NECO_CANCELED) { + // Operation canceled + } else if (fd == NECO_TIMEDOUT) { + // Operation timedout + } else { + // Some other error + } + return; +} +// Success +``` + +#### Async cancelation + +By default, when a coroutine is canceled it's the responsibility of the +programmer to check the return value and perform any clean up. But it's also +possible to have Neco handle all that by enabling the async cancelation with +`neco_setcanceltype(NECO_CANCEL_ASYNC, 0)` at the coroutine level or +`neco_env_setcanceltype(NECO_CANCEL_ASYNC)` globally from the main function. + +When async cancelation is enabled, the coroutine that is canceled is terminated +right away, and any cleanup handled using the `neco_cleanup_push()` and +`neco_cleanup_pop()` functions. + +Async cancelation is automatically enabled when using `neco_main()`, instead of +`main()`, as the program startup function. + +#### Disabling cancelation + +Finally, coroutine cancelation can be totally disabled by calling +`neco_setcancelstate(NECO_CANCEL_DISABLE, 0)` at the coroutine level or +`neco_env_setcancelstate(NECO_CANCEL_DISABLE)` globally from the main function. + + + +## Basic operations + +Neco provides standard operations for starting a coroutine, sleeping, suspending, resuming, yielding to another coroutine, joining/waiting for child coroutines, and exiting a running coroutine. + + + +- [neco_start()](#group___basic_operations_1gacfbcf6055ddaa921e490d1126b098c6b) +- [neco_startv()](#group___basic_operations_1ga5b0f6236cd9a5474a64d4bf255e78cc3) +- [neco_yield()](#group___basic_operations_1ga452ce9548546546212f543d520e80d56) +- [neco_sleep()](#group___basic_operations_1gae87a3246f84cf2db5740657b7d154be8) +- [neco_sleep_dl()](#group___basic_operations_1ga59a2c087c1f101f2ae5a4a1453c1ca01) +- [neco_join()](#group___basic_operations_1ga64637448098a3c511e0c0c048c40103e) +- [neco_join_dl()](#group___basic_operations_1gaccb867c094a5e8b4e831c8a27f181773) +- [neco_suspend()](#group___basic_operations_1ga552dc7e3c3f0ee58d4904dac8a4f7321) +- [neco_suspend_dl()](#group___basic_operations_1ga32850f9848273874245218b6b3392471) +- [neco_resume()](#group___basic_operations_1gafd0eb7bf4f11111d42375dd9bf8ede79) +- [neco_exit()](#group___basic_operations_1ga0164114b476cd4d66ec80755c384d4c3) +- [neco_getid()](#group___basic_operations_1gabf1388fdcd0eaf2d299ae3699fa1a69a) +- [neco_lastid()](#group___basic_operations_1ga099df89d4ddd5816d896f4d74131dcbe) +- [neco_starterid()](#group___basic_operations_1ga56c02a14469157b84af23887f084db73) + + + +## Channels + +Channels allow for sending and receiving values between coroutines. By default, sends and receives will block until the other side is ready. This allows the coroutines to synchronize without using locks or condition variables. + + + +- [neco_chan_make()](#group___channels_1gad7f6dc81bcb94cd4a144daebabfc8c52) +- [neco_chan_retain()](#group___channels_1gab843bd5df882ec0aaa6cbd5083b9b721) +- [neco_chan_release()](#group___channels_1ga2808f4ad91d72f8ce57fa3c0a2d3fafb) +- [neco_chan_send()](#group___channels_1gaddc9839d4aba79e1d7a19c9bdc10a9a5) +- [neco_chan_send_dl()](#group___channels_1ga18583ad54f7e44bf8838ece1501e89be) +- [neco_chan_broadcast()](#group___channels_1ga4c5abf01d047840d3e637e2424fab06d) +- [neco_chan_recv()](#group___channels_1ga2f6bb94175df1adcb02fcff0ba607714) +- [neco_chan_recv_dl()](#group___channels_1ga5f0b70f0982b2e3e32278e158121a565) +- [neco_chan_tryrecv()](#group___channels_1gac6e63d29f8ab1a2ab2fa26e2b5ef4b0e) +- [neco_chan_close()](#group___channels_1ga0bcf858aef2c63b06262ce9613e8d436) +- [neco_chan_select()](#group___channels_1ga4422a483d68b1426026ba7c432647046) +- [neco_chan_select_dl()](#group___channels_1gab3afdfa2446b1a698a251ff4bfe05d07) +- [neco_chan_selectv()](#group___channels_1ga47f4559dca0426dce566a070ce77a324) +- [neco_chan_selectv_dl()](#group___channels_1gad55a1acfaa4035d68047b716a4cc22b4) +- [neco_chan_tryselect()](#group___channels_1ga3eef207727751d43f1b00258ff50003b) +- [neco_chan_tryselectv()](#group___channels_1ga9f29bd7474bbe176ae51a3c6959981c6) +- [neco_chan_case()](#group___channels_1ga9b94a8a90ecd46807add2ee9d45c2325) + + + +## Generators + +A generator is a specialized iterator-bound coroutine that can produce a sequence of values to be iterated over. + + + +- [neco_gen_start()](#group___generators_1ga76747762182e1e4e09b875c050a78b78) +- [neco_gen_startv()](#group___generators_1ga007f921308ba7e35becc465cc756cae1) +- [neco_gen_retain()](#group___generators_1ga20fc0ad0de7d0665c399e628dd61ae8c) +- [neco_gen_release()](#group___generators_1ga23c2af17111c63566cb2b07a1a34ee84) +- [neco_gen_yield()](#group___generators_1gad7c3391c16f5552560484087cf75d7bc) +- [neco_gen_yield_dl()](#group___generators_1gafcbfd40e4aea60194aa64d33f892da65) +- [neco_gen_next()](#group___generators_1ga34283229abf0393bc05b720239f2c29b) +- [neco_gen_next_dl()](#group___generators_1gaaf4f60adae59dc293bc6ad48b525baf9) +- [neco_gen_close()](#group___generators_1gaf783371ccc65f5f50665a6aa3be60da4) + + + +## Mutexes + +A mutex is synchronization mechanism that blocks access to variables by multiple coroutines at once. This enforces exclusive access by a coroutine to a variable or set of variables and helps to avoid data inconsistencies due to race conditions. + + + +- [neco_mutex_init()](#group___mutexes_1gae779cdf3a6496968d1739478fb942a2f) +- [neco_mutex_lock()](#group___mutexes_1ga5f9a1499741979555215c0d32df28036) +- [neco_mutex_lock_dl()](#group___mutexes_1ga7078809a05ef4865105dc2badcba16c8) +- [neco_mutex_trylock()](#group___mutexes_1gab87dcdc89e9bee98acbb526a0a241470) +- [neco_mutex_unlock()](#group___mutexes_1ga51f3a1467736c2569e3d784c679d906f) +- [neco_mutex_rdlock()](#group___mutexes_1gaf0557f23118f870f726af5ec2be2338c) +- [neco_mutex_rdlock_dl()](#group___mutexes_1gaab33cb2e6ac8a339be65f45d7560e6ea) +- [neco_mutex_tryrdlock()](#group___mutexes_1ga6a19e3ee7407825f7eec426e0b2b07ab) + + + +## WaitGroups + +A WaitGroup waits for a multiple coroutines to finish. The main coroutine calls [neco_waitgroup_add()](#group___wait_groups_1ga6e9fbd0b59edd0e043a3f5513c89e355) to set the number of coroutines to wait for. Then each of the coroutines runs and calls [neco_waitgroup_done()](#group___wait_groups_1gaec7cebb64cc152a3e5d08473b817aa8d) when complete. At the same time, [neco_waitgroup_wait()](#group___wait_groups_1ga50a892457d7275ef71414dc3444d8ca3) can be used to block until all coroutines are completed. + + + +- [neco_waitgroup_init()](#group___wait_groups_1ga70bb6ff8f8e15578ec43d9a02c463a55) +- [neco_waitgroup_add()](#group___wait_groups_1ga6e9fbd0b59edd0e043a3f5513c89e355) +- [neco_waitgroup_done()](#group___wait_groups_1gaec7cebb64cc152a3e5d08473b817aa8d) +- [neco_waitgroup_wait()](#group___wait_groups_1ga50a892457d7275ef71414dc3444d8ca3) +- [neco_waitgroup_wait_dl()](#group___wait_groups_1gae8bf9aac9f44942100aabe108774e7cf) + + + +## Condition variables + +A condition variable is a synchronization mechanism that allows coroutines to suspend execution until some condition is true. + + + +- [neco_cond_init()](#group___cond_var_1ga7ba9fc6b2cc9da91baa449a88fde1f16) +- [neco_cond_signal()](#group___cond_var_1ga0e0a94263080390d3fed8f3def0bbf25) +- [neco_cond_broadcast()](#group___cond_var_1ga8dacdb4efb8a82d69b43dec8a1eec199) +- [neco_cond_wait()](#group___cond_var_1gaa6c18058d4bbb2274415bfc6a821d04f) +- [neco_cond_wait_dl()](#group___cond_var_1ga6b374b1be4ba2e3d60f5462728196205) + + + +## Posix wrappers + +Functions that work like their Posix counterpart but do not block, allowing for usage in a Neco coroutine. + + + +- [neco_read()](#group___posix_1ga1869cc07ba78a167b8462a9cef09c0ba) +- [neco_read_dl()](#group___posix_1ga9077362d45d7e4f71dbb9b24a6134e4d) +- [neco_write()](#group___posix_1ga5c8ea872889626c86c57cc3180a673be) +- [neco_write_dl()](#group___posix_1gad1fe0b6ed23aadaca0dbf2031ffbebab) +- [neco_accept()](#group___posix_1ga49ed3b4837ffcd995b054cfb4bb178b1) +- [neco_accept_dl()](#group___posix_1ga963e70ab9a894c49624dc6b03d7494a4) +- [neco_connect()](#group___posix_1ga16815a8a7a51d95281f1d70555168383) +- [neco_connect_dl()](#group___posix_1ga50ccd758a7423fa97b717d595efe9a86) +- [neco_getaddrinfo()](#group___posix_1gae80194a21ffdc035a7054276b537a8c3) +- [neco_getaddrinfo_dl()](#group___posix_1ga8a828e8a4e27f41aa5dfd966a3e6702d) + + + +## File descriptor helpers + +Functions for working with file descriptors. + + + +- [neco_setnonblock()](#group___posix2_1ga6f612b7e64d5a9ec7ff607315cc8ec3a) +- [neco_wait()](#group___posix2_1ga99dc783673fbf7e3e12479d59ad7ee4c) +- [neco_wait_dl()](#group___posix2_1ga8392f97cbef774c6d044c75b393fbc03) + + + +## Networking utilities + +- [neco_serve()](#group___networking_1ga62b996c86bb80c1cab2092d82c6ea53c) +- [neco_serve_dl()](#group___networking_1gae0fe390fce4ecf3f80cbb9668020a59c) +- [neco_dial()](#group___networking_1ga327b79d78328e50dd1a98fbf444c9d0c) +- [neco_dial_dl()](#group___networking_1ga02c848d20aeee63790bb2def69fea4c9) + + + +## Cancelation + +- [neco_cancel()](#group___cancelation_1gae6426af020deb390047106686d5da031) +- [neco_cancel_dl()](#group___cancelation_1ga85143c030d2055da855a9451980585af) +- [neco_setcanceltype()](#group___cancelation_1gad6dfa0851bbb7317821b27f16ef0553d) +- [neco_setcancelstate()](#group___cancelation_1ga3cdc3d0b6f48a805fafa94247122cb75) + + + +## Random number generator + +- [neco_rand_setseed()](#group___random_1gaefd0991a1cdf430f22899fd34bdba892) +- [neco_rand()](#group___random_1ga4567eff3299e3b00e829b82ca657d8cd) +- [neco_rand_dl()](#group___random_1gaaad47a533a78ff549be6d41339846493) + + + +## Signals + +Allows for signals, such as SIGINT (Ctrl-C), to be intercepted or ignored. + + + +- [neco_signal_watch()](#group___signals_1ga4848017f351283eed5e432a9af9ddd7b) +- [neco_signal_wait()](#group___signals_1gaa34972437b6b9e85ba0efec6cd388516) +- [neco_signal_wait_dl()](#group___signals_1gacd8061a747344fbde13cbdfcc99efd8f) +- [neco_signal_unwatch()](#group___signals_1ga58c262e5ded1ec30c64618959453517a) + + + +## Background worker + +Run arbritary code in a background worker thread + + + +- [neco_work()](#group___worker_1ga6a8c55b440330f4a1f7b654787d96946) + + + +## Stats and information + +- [neco_getstats()](#group___stats_1ga9728fa5ef28c4d88a951faaf7d26a506) +- [neco_is_main_thread()](#group___stats_1ga44d9b10110f57fb7491b236d3ec7e731) +- [neco_switch_method()](#group___stats_1ga60ee198bb5309531aac43b8276ac7a64) + + + +## Global environment + +- [neco_env_setallocator()](#group___global_funcs_1gae6a60529f176d23f51f10a3906beb568) +- [neco_env_setpaniconerror()](#group___global_funcs_1ga10db728074cebc8d35cfcec01bb1dc97) +- [neco_env_setcanceltype()](#group___global_funcs_1gacab37dcac0bef3c4a29f523ea32b8df3) +- [neco_env_setcancelstate()](#group___global_funcs_1ga09e93872d1245a3bcba13b3b45b43618) + + + +## Time + +Functions for working with time. + +The following defines are available for convenience. + +```c +#define NECO_NANOSECOND INT64_C(1) +#define NECO_MICROSECOND INT64_C(1000) +#define NECO_MILLISECOND INT64_C(1000000) +#define NECO_SECOND INT64_C(1000000000) +#define NECO_MINUTE INT64_C(60000000000) +#define NECO_HOUR INT64_C(3600000000000) +``` + + + + +- [neco_now()](#group___time_1gad969c647e4b3b661eab3f7f5b5372d38) + + + +## Error handling + +Functions for working with [Neco errors](./API.md#neco-errors). + + + +- [neco_strerror()](#group___error_funcs_1ga053c32e9432aef83bda474e41fd7b2d4) +- [neco_lasterr()](#group___error_funcs_1ga82ade5f26218caa8e0b4883575a683bd) +- [neco_gai_lasterr()](#group___error_funcs_1ga6b2d592c3044f9756073daabfe25b470) +- [neco_panic()](#group___error_funcs_1gae611410883c5d2cfa24def14f3a2cc38) + + + +## Streams and Buffered I/O + +Create a Neco stream from a file descriptor using [neco_stream_make()](#group___streams_1ga74885d1c39c7c8442fc9e82b8411243e) or a buffered stream using [neco_stream_make_buffered()](#group___streams_1ga70276b8dd3bfe1a3e715ffd4c8486620). + + + +- [neco_stream_make()](#group___streams_1ga74885d1c39c7c8442fc9e82b8411243e) +- [neco_stream_make_buffered()](#group___streams_1ga70276b8dd3bfe1a3e715ffd4c8486620) +- [neco_stream_close()](#group___streams_1ga526b51cbef51b1e36723ca718368f215) +- [neco_stream_close_dl()](#group___streams_1gaaec08f5c1c1d3d630fa2faba75b6a6fb) +- [neco_stream_read()](#group___streams_1gae6c836fd60b4a2225b5ccdecbe529c38) +- [neco_stream_read_dl()](#group___streams_1gad8f1e0ac6eae64fc4d8cb995b17a0bb6) +- [neco_stream_write()](#group___streams_1gaa3f1c676605a7441c527f1a798cedacf) +- [neco_stream_write_dl()](#group___streams_1ga09f9bdd323f2ba29669d71968c7fd3cd) +- [neco_stream_readfull()](#group___streams_1ga1b3b8a4e10113768f22c6cb5b7992ce4) +- [neco_stream_readfull_dl()](#group___streams_1ga1f08421b4cac63e930046c328c6f3c6a) +- [neco_stream_read_byte()](#group___streams_1gaf8dc61bc7e0b9c6c105c8ab51693ee3d) +- [neco_stream_read_byte_dl()](#group___streams_1ga3096f4f082cee9cddcfaad4ca3694f7a) +- [neco_stream_unread_byte()](#group___streams_1ga1ec67ae2dfa4cae9a26c536e3c44c4f6) +- [neco_stream_flush()](#group___streams_1ga1c847c02240767c06daf832eb85022e2) +- [neco_stream_flush_dl()](#group___streams_1gac4818a4bc2497ca46db221a1fea96746) +- [neco_stream_buffered_read_size()](#group___streams_1gaf8d0174f3c7627ae4b0635d493528616) +- [neco_stream_buffered_write_size()](#group___streams_1gae870778b3faddfcd71de62880adb6172) + + +## neco_stats +```c +struct neco_stats { + size_t coroutines; // Number of active coroutines. + size_t sleepers; // Number of sleeping coroutines. + size_t evwaiters; // Number of coroutines waiting on I/O events. + size_t sigwaiters; + size_t senders; + size_t receivers; + size_t locked; + size_t waitgroupers; + size_t condwaiters; + size_t suspended; + size_t workers; // Number of background worker threads. +}; +``` + + +## neco_mutex +```c +struct neco_mutex { + int64_t rtid; + bool locked; + int rlocked; + struct colist queue; + char _; +}; +``` + + +## neco_waitgroup +```c +struct neco_waitgroup { + int64_t rtid; + int count; + struct colist queue; + char _; +}; +``` + + +## neco_cond +```c +struct neco_cond { + int64_t rtid; + struct colist queue; + char _; +}; +``` + + +## neco_chan +```c +struct neco_chan; +``` + + +## neco_stream +```c +struct neco_stream; +``` + + +## neco_gen +```c +struct neco_gen; +``` + + +## neco_start() +```c +int neco_start(void(*coroutine)(int argc, void *argv[]), int argc,...); +``` +Starts a new coroutine. + +If this is the first coroutine started for the program (or thread) then this will also create a neco runtime scheduler which blocks until the provided coroutine and all of its subsequent child coroutines finish. + +**Example** + +```c +// Here we'll start a coroutine that prints "hello world". + +void coroutine(int argc, void *argv[]) { + char *msg = argv[0]; + printf("%s\n", msg); +} + +neco_start(coroutine, 1, "hello world"); +``` + + + + +**Parameters** + +- **coroutine**: The coroutine that will soon run +- **argc**: Number of arguments +- **...**: Arguments passed to the coroutine + + + +**Return** + +- NECO_OK Success +- NECO_NOMEM The system lacked the necessary resources +- NECO_INVAL An invalid parameter was provided + + + + +## neco_startv() +```c +int neco_startv(void(*coroutine)(int argc, void *argv[]), int argc, void *argv[]); +``` +Starts a new coroutine using an array for arguments. + +**See also** + +- [neco_start](#group___basic_operations_1gacfbcf6055ddaa921e490d1126b098c6b) + + + + +## neco_yield() +```c +int neco_yield(); +``` +Cause the calling coroutine to relinquish the CPU. The coroutine is moved to the end of the queue. + +**Return** + +- NECO_OK Success +- NECO_PERM Operation called outside of a coroutine + + + + +## neco_sleep() +```c +int neco_sleep(int64_t nanosecs); +``` +Causes the calling coroutine to sleep until the number of specified nanoseconds have elapsed. + +**Parameters** + +- **nanosecs**: duration nanoseconds + + + +**Return** + +- NECO_OK Coroutine slept until nanosecs elapsed +- NECO_TIMEDOUT nanosecs is a negative number +- NECO_CANCELED Operation canceled +- NECO_PERM Operation called outside of a coroutine + + +**See also** + +- [neco_sleep_dl()](#group___basic_operations_1ga59a2c087c1f101f2ae5a4a1453c1ca01) + + + + +## neco_sleep_dl() +```c +int neco_sleep_dl(int64_t deadline); +``` +Same as [neco_sleep()](#group___basic_operations_1gae87a3246f84cf2db5740657b7d154be8) but with a deadline parameter. + + + +## neco_join() +```c +int neco_join(int64_t id); +``` +Wait for a coroutine to terminate. If that coroutine has already terminated or is not found, then this operation returns immediately. + +**Example** + +```c +// Start a new coroutine +neco_start(coroutine, 0); + +// Get the identifier of the new coroutine. +int64_t id = neco_lastid(); + +// Wait until the coroutine has terminated. +neco_join(id); +``` + + + + +**Parameters** + +- **id**: Coroutine identifier + + + +**Return** + +- NECO_OK Success +- NECO_CANCELED Operation canceled +- NECO_PERM Operation called outside of a coroutine + + +**See also** + +- [neco_join_dl()](#group___basic_operations_1gaccb867c094a5e8b4e831c8a27f181773) + + + + +## neco_join_dl() +```c +int neco_join_dl(int64_t id, int64_t deadline); +``` +Same as [neco_join()](#group___basic_operations_1ga64637448098a3c511e0c0c048c40103e) but with a deadline parameter. + + + +## neco_suspend() +```c +int neco_suspend(); +``` +Suspend the current coroutine. + +**Return** + +- NECO_OK Success +- NECO_PERM Operation called outside of a coroutine +- NECO_CANCELED Operation canceled + + +**See also** + +- [neco_resume](#group___basic_operations_1gafd0eb7bf4f11111d42375dd9bf8ede79) +- [neco_suspend_dl](#group___basic_operations_1ga32850f9848273874245218b6b3392471) + + + + +## neco_suspend_dl() +```c +int neco_suspend_dl(int64_t deadline); +``` +Same as [neco_suspend()](#group___basic_operations_1ga552dc7e3c3f0ee58d4904dac8a4f7321) but with a deadline parameter. + + + +## neco_resume() +```c +int neco_resume(int64_t id); +``` +Resume a suspended roroutine + +**Return** + +- NECO_OK Success +- NECO_PERM Operation called outside of a coroutine +- NECO_NOTFOUND Coroutine not found +- NECO_NOTSUSPENDED Coroutine not suspended + + +**See also** + +- [neco_suspend](#group___basic_operations_1ga552dc7e3c3f0ee58d4904dac8a4f7321) + + + + +## neco_exit() +```c +void neco_exit(); +``` +Terminate the current coroutine. + +Any clean-up handlers established by [neco_cleanup_push()](#group___cancelation_1gae48d7d3dd6218995e4d058b98aad5c9f) that have not yet been popped, are popped (in the reverse of the order in which they were pushed) and executed. + +Calling this from outside of a coroutine context does nothing and will be treated effectivley as a no-op. + + + +## neco_getid() +```c +int64_t neco_getid(); +``` +Returns the identifier for the currently running coroutine. + +This value is guaranteed to be unique for the duration of the program. + +**Return** + +- The coroutine identifier +- NECO_PERM Operation called outside of a coroutine + + + + +## neco_lastid() +```c +int64_t neco_lastid(); +``` +Returns the identifier for the coroutine started by the current coroutine. + +For example, here a coroutine is started and its identifer is then retreived. + +```c +neco_start(coroutine, 0); +int64_t id = neco_lastid(); +``` + + + + +**Return** + +- A coroutine identifier, or zero if the current coroutine has not yet started any coroutines. +- NECO_PERM Operation called outside of a coroutine + + + + +## neco_starterid() +```c +int64_t neco_starterid(); +``` +Get the identifier for the coroutine that started the current coroutine. + +```c +void child_coroutine(int argc, void *argv[]) { + int parent_id = neco_starterid(); + // The parent_id is equal as the neco_getid() from the parent_coroutine + // below. +} + +void parent_coroutine(int argc, void *argv[]) { + int id = neco_getid(); + neco_start(child_coroutine, 0); +} +``` + + +**Return** + +- A coroutine identifier, or zero if the coroutine is the first coroutine started. + + + + +## neco_chan_make() +```c +int neco_chan_make(neco_chan **chan, size_t data_size, size_t capacity); +``` +Creates a new channel for sharing messages with other coroutines. + +**Example** + +```c +void coroutine(int argc, void *argv[]) { + neco_chan *ch = argv[0]; + + // Send a message + neco_chan_send(ch, &(int){ 1 }); + + // Release the channel + neco_chan_release(ch); +} + +int neco_start(int argc, char *argv[]) { + neco_chan *ch; + neco_chan_make(&ch, sizeof(int), 0); + + // Retain a reference of the channel and provide it to a newly started + // coroutine. + neco_chan_retain(ch); + neco_start(coroutine, 1, ch); + + // Receive a message + int msg; + neco_chan_recv(ch, &msg); + printf("%d\n", msg); // prints '1' + + // Always release the channel when you are done + neco_chan_release(ch); +} +``` + + + + +**Parameters** + +- **chan**: Channel +- **data_size**: Data size of messages +- **capacity**: Buffer capacity + + + +**Return** + +- NECO_OK Success +- NECO_NOMEM The system lacked the necessary resources +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine + + +**Note** + +- The caller is responsible for freeing with [neco_chan_release()](#group___channels_1ga2808f4ad91d72f8ce57fa3c0a2d3fafb) +- data_size and capacity cannot be greater than INT_MAX + + + + +## neco_chan_retain() +```c +int neco_chan_retain(neco_chan *chan); +``` +Retain a reference of the channel so it can be shared with other coroutines. + +This is needed for avoiding use-after-free bugs. + +See [neco_chan_make()](#group___channels_1gad7f6dc81bcb94cd4a144daebabfc8c52) for an example. + + + +**Parameters** + +- **chan**: The channel + + + +**Return** + +- NECO_OK Success +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine + + +**Note** + +- The caller is responsible for releasing the reference with [neco_chan_release()](#group___channels_1ga2808f4ad91d72f8ce57fa3c0a2d3fafb) + + +**See also** + +- [Channels](#group___channels) +- [neco_chan_release()](#group___channels_1ga2808f4ad91d72f8ce57fa3c0a2d3fafb) + + + + +## neco_chan_release() +```c +int neco_chan_release(neco_chan *chan); +``` +Release a reference to a channel + +See [neco_chan_make()](#group___channels_1gad7f6dc81bcb94cd4a144daebabfc8c52) for an example. + + + +**Parameters** + +- **chan**: The channel + + + +**Return** + +- NECO_OK Success +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine + + +**See also** + +- [Channels](#group___channels) +- [neco_chan_retain()](#group___channels_1gab843bd5df882ec0aaa6cbd5083b9b721) + + + + +## neco_chan_send() +```c +int neco_chan_send(neco_chan *chan, void *data); +``` +Send a message + +See [neco_chan_make()](#group___channels_1gad7f6dc81bcb94cd4a144daebabfc8c52) for an example. + + + +**Return** + +- NECO_OK Success +- NECO_PERM Operation called outside of a coroutine +- NECO_INVAL An invalid parameter was provided +- NECO_CANCELED Operation canceled +- NECO_CLOSED Channel closed + + +**See also** + +- [Channels](#group___channels) +- [neco_chan_recv()](#group___channels_1ga2f6bb94175df1adcb02fcff0ba607714) + + + + +## neco_chan_send_dl() +```c +int neco_chan_send_dl(neco_chan *chan, void *data, int64_t deadline); +``` +Same as [neco_chan_send()](#group___channels_1gaddc9839d4aba79e1d7a19c9bdc10a9a5) but with a deadline parameter. + + + +## neco_chan_broadcast() +```c +int neco_chan_broadcast(neco_chan *chan, void *data); +``` +Sends message to all receiving channels. + +**Return** + +- The number of channels that received the message +- NECO_PERM Operation called outside of a coroutine +- NECO_INVAL An invalid parameter was provided +- NECO_CLOSED Channel closed + + +**Note** + +- This operation cannot be canceled and does not timeout + + +**See also** + +- [Channels](#group___channels) +- [neco_chan_recv()](#group___channels_1ga2f6bb94175df1adcb02fcff0ba607714) + + + + +## neco_chan_recv() +```c +int neco_chan_recv(neco_chan *chan, void *data); +``` +Receive a message + +See [neco_chan_make()](#group___channels_1gad7f6dc81bcb94cd4a144daebabfc8c52) for an example. + + + +**Parameters** + +- **chan**: channel +- **data**: data pointer + + + +**Return** + +- NECO_OK Success +- NECO_PERM Operation called outside of a coroutine +- NECO_INVAL An invalid parameter was provided +- NECO_CANCELED Operation canceled +- NECO_CLOSED Channel closed + + +**See also** + +- [Channels](#group___channels) +- [neco_chan_send()](#group___channels_1gaddc9839d4aba79e1d7a19c9bdc10a9a5) + + + + +## neco_chan_recv_dl() +```c +int neco_chan_recv_dl(neco_chan *chan, void *data, int64_t deadline); +``` +Same as [neco_chan_recv()](#group___channels_1ga2f6bb94175df1adcb02fcff0ba607714) but with a deadline parameter. + + + +## neco_chan_tryrecv() +```c +int neco_chan_tryrecv(neco_chan *chan, void *data); +``` +Receive a message, but do not wait if the message is not available. + +**Parameters** + +- **chan**: channel +- **data**: data pointer + + + +**Return** + +- NECO_OK Success +- NECO_EMPTY No message available +- NECO_CLOSED Channel closed +- NECO_PERM Operation called outside of a coroutine +- NECO_INVAL An invalid parameter was provided + + +**See also** + +- [Channels](#group___channels) +- [neco_chan_recv()](#group___channels_1ga2f6bb94175df1adcb02fcff0ba607714) + + + + +## neco_chan_close() +```c +int neco_chan_close(neco_chan *chan); +``` +Close a channel for sending. + +**Parameters** + +- **chan**: channel + + + +**Return** + +- NECO_OK Success +- NECO_PERM Operation called outside of a coroutine +- NECO_INVAL An invalid parameter was provided +- NECO_CLOSED Channel already closed + + + + +## neco_chan_select() +```c +int neco_chan_select(int nchans,...); +``` +Wait on multiple channel operations at the same time. + +**Example** + +```c +// Let's say we have two channels 'c1' and 'c2' that both transmit 'char *' +// messages. + +// Use neco_chan_select() to wait on both channels. + +char *msg; +int idx = neco_chan_select(2, c1, c2); +switch (idx) { +case 0: + neco_chan_case(c1, &msg); + break; +case 1: + neco_chan_case(c2, &msg); + break; +default: + // Error occured. The return value 'idx' is the error +} + +printf("%s\n", msg); +``` + + + + +**Parameters** + +- **nchans**: Number of channels +- **...**: The channels + + + +**Return** + +- The index of channel with an available message +- NECO_PERM Operation called outside of a coroutine +- NECO_INVAL An invalid parameter was provided +- NECO_NOMEM The system lacked the necessary resources +- NECO_CANCELED Operation canceled + + +**See also** + +- [Channels](#group___channels) + + + + +## neco_chan_select_dl() +```c +int neco_chan_select_dl(int64_t deadline, int nchans,...); +``` +Same as [neco_chan_select()](#group___channels_1ga4422a483d68b1426026ba7c432647046) but with a deadline parameter. + + + +## neco_chan_selectv() +```c +int neco_chan_selectv(int nchans, neco_chan *chans[]); +``` +Same as [neco_chan_select()](#group___channels_1ga4422a483d68b1426026ba7c432647046) but using an array for arguments. + + + +## neco_chan_selectv_dl() +```c +int neco_chan_selectv_dl(int nchans, neco_chan *chans[], int64_t deadline); +``` +Same as [neco_chan_selectv()](#group___channels_1ga47f4559dca0426dce566a070ce77a324) but with a deadline parameter. + + + +## neco_chan_tryselect() +```c +int neco_chan_tryselect(int nchans,...); +``` +Same as [neco_chan_select()](#group___channels_1ga4422a483d68b1426026ba7c432647046) but does not wait if a message is not available + +**Return** + +- NECO_EMPTY No message available + + +**See also** + +- [Channels](#group___channels) +- [neco_chan_select()](#group___channels_1ga4422a483d68b1426026ba7c432647046) + + + + +## neco_chan_tryselectv() +```c +int neco_chan_tryselectv(int nchans, neco_chan *chans[]); +``` +Same as [neco_chan_tryselect()](#group___channels_1ga3eef207727751d43f1b00258ff50003b) but uses an array for arguments. + + + +## neco_chan_case() +```c +int neco_chan_case(neco_chan *chan, void *data); +``` +Receive the message after a successful [neco_chan_select()](#group___channels_1ga4422a483d68b1426026ba7c432647046). See [neco_chan_select()](#group___channels_1ga4422a483d68b1426026ba7c432647046) for an example. + +**Parameters** + +- **chan**: The channel +- **data**: The data + + + +**Return** + +- NECO_OK Success +- NECO_PERM Operation called outside of a coroutine +- NECO_INVAL An invalid parameter was provided +- NECO_CLOSED Channel closed + + + + +## neco_gen_start() +```c +int neco_gen_start(neco_gen **gen, size_t data_size, void(*coroutine)(int argc, void *argv[]), int argc,...); +``` +Start a generator coroutine + +**Example** + +```c +void coroutine(int argc, void *argv[]) { + // Yield each int to the caller, one at a time. + for (int i = 0; i < 10; i++) { + neco_gen_yield(&i); + } +} + +int neco_main(int argc, char *argv[]) { + + // Create a new generator coroutine that is used to send ints. + neco_gen *gen; + neco_gen_start(&gen, sizeof(int), coroutine, 0); + + // Iterate over each int until the generator is closed. + int i; + while (neco_gen_next(gen, &i) != NECO_CLOSED) { + printf("%d\n", i); + } + + // This coroutine no longer needs the generator. + neco_gen_release(gen); + return 0; +} +``` + + + + +**Parameters** + +- **gen**: Generator object +- **data_size**: Data size of messages +- **coroutine**: Generator coroutine +- **argc**: Number of arguments + + + +**Return** + +- NECO_OK Success +- NECO_NOMEM The system lacked the necessary resources +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine + + +**Note** + +- The caller is responsible for freeing the generator object with with [neco_gen_release()](#group___generators_1ga23c2af17111c63566cb2b07a1a34ee84). + + + + +## neco_gen_startv() +```c +int neco_gen_startv(neco_gen **gen, size_t data_size, void(*coroutine)(int argc, void *argv[]), int argc, void *argv[]); +``` +Same as [neco_gen_start()](#group___generators_1ga76747762182e1e4e09b875c050a78b78) but using an array for arguments. + + + +## neco_gen_retain() +```c +int neco_gen_retain(neco_gen *gen); +``` +Retain a reference of the generator so it can be shared with other coroutines. + +This is needed for avoiding use-after-free bugs. + +See [neco_gen_start()](#group___generators_1ga76747762182e1e4e09b875c050a78b78) for an example. + + + +**Parameters** + +- **gen**: The generator + + + +**Return** + +- NECO_OK Success +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine + + +**Note** + +- The caller is responsible for releasing the reference with [neco_gen_release()](#group___generators_1ga23c2af17111c63566cb2b07a1a34ee84) + + +**See also** + +- [Generators](#group___generators) +- [neco_gen_release()](#group___generators_1ga23c2af17111c63566cb2b07a1a34ee84) + + + + +## neco_gen_release() +```c +int neco_gen_release(neco_gen *gen); +``` +Release a reference to a generator + +See [neco_gen_start()](#group___generators_1ga76747762182e1e4e09b875c050a78b78) for an example. + + + +**Parameters** + +- **gen**: The generator + + + +**Return** + +- NECO_OK Success +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine + + +**See also** + +- [Generators](#group___generators) +- [neco_gen_retain()](#group___generators_1ga20fc0ad0de7d0665c399e628dd61ae8c) + + + + +## neco_gen_yield() +```c +int neco_gen_yield(void *data); +``` +Send a value to the generator for the next iteration. + +See [neco_gen_start()](#group___generators_1ga76747762182e1e4e09b875c050a78b78) for an example. + + + +## neco_gen_yield_dl() +```c +int neco_gen_yield_dl(void *data, int64_t deadline); +``` +Same as [neco_gen_yield()](#group___generators_1gad7c3391c16f5552560484087cf75d7bc) but with a deadline parameter. + + + +## neco_gen_next() +```c +int neco_gen_next(neco_gen *gen, void *data); +``` +Receive the next value from a generator. + +See [neco_gen_start()](#group___generators_1ga76747762182e1e4e09b875c050a78b78) for an example. + + + +## neco_gen_next_dl() +```c +int neco_gen_next_dl(neco_gen *gen, void *data, int64_t deadline); +``` +Same as [neco_gen_next()](#group___generators_1ga34283229abf0393bc05b720239f2c29b) but with a deadline parameter. + + + +## neco_gen_close() +```c +int neco_gen_close(neco_gen *gen); +``` +Close the generator. + +**Parameters** + +- **gen**: Generator + + + +**Return** + +- NECO_OK Success + + + + +## neco_mutex_init() +```c +int neco_mutex_init(neco_mutex *mutex); +``` + + +## neco_mutex_lock() +```c +int neco_mutex_lock(neco_mutex *mutex); +``` + + +## neco_mutex_lock_dl() +```c +int neco_mutex_lock_dl(neco_mutex *mutex, int64_t deadline); +``` + + +## neco_mutex_trylock() +```c +int neco_mutex_trylock(neco_mutex *mutex); +``` + + +## neco_mutex_unlock() +```c +int neco_mutex_unlock(neco_mutex *mutex); +``` + + +## neco_mutex_rdlock() +```c +int neco_mutex_rdlock(neco_mutex *mutex); +``` + + +## neco_mutex_rdlock_dl() +```c +int neco_mutex_rdlock_dl(neco_mutex *mutex, int64_t deadline); +``` + + +## neco_mutex_tryrdlock() +```c +int neco_mutex_tryrdlock(neco_mutex *mutex); +``` + + +## neco_waitgroup_init() +```c +int neco_waitgroup_init(neco_waitgroup *waitgroup); +``` + + +## neco_waitgroup_add() +```c +int neco_waitgroup_add(neco_waitgroup *waitgroup, int delta); +``` + + +## neco_waitgroup_done() +```c +int neco_waitgroup_done(neco_waitgroup *waitgroup); +``` + + +## neco_waitgroup_wait() +```c +int neco_waitgroup_wait(neco_waitgroup *waitgroup); +``` + + +## neco_waitgroup_wait_dl() +```c +int neco_waitgroup_wait_dl(neco_waitgroup *waitgroup, int64_t deadline); +``` + + +## neco_cond_init() +```c +int neco_cond_init(neco_cond *cond); +``` + + +## neco_cond_signal() +```c +int neco_cond_signal(neco_cond *cond); +``` + + +## neco_cond_broadcast() +```c +int neco_cond_broadcast(neco_cond *cond); +``` + + +## neco_cond_wait() +```c +int neco_cond_wait(neco_cond *cond, neco_mutex *mutex); +``` + + +## neco_cond_wait_dl() +```c +int neco_cond_wait_dl(neco_cond *cond, neco_mutex *mutex, int64_t deadline); +``` + + +## neco_read() +```c +ssize_t neco_read(int fd, void *data, size_t nbytes); +``` +Read from a file descriptor. + +This operation attempts to read up to count from file descriptor fd into the buffer starting at buf. + +This is a Posix wrapper function for the purpose of running in a Neco coroutine. It's expected that the provided file descriptor is in non-blocking state. + + + +**Return** + +- On success, the number of bytes read is returned (zero indicates end of file) +- On error, value -1 (NECO_ERROR) is returned, and errno is set to indicate the error. + + +**See also** + +- [Posix wrappers](#group___posix) +- [neco_setnonblock()](#group___posix2_1ga6f612b7e64d5a9ec7ff607315cc8ec3a) +- [https://www.man7.org/linux/man-pages/man2/read.2.html](https://www.man7.org/linux/man-pages/man2/read.2.html) + + + + +## neco_read_dl() +```c +ssize_t neco_read_dl(int fd, void *data, size_t nbytes, int64_t deadline); +``` +Same as [neco_read()](#group___posix_1ga1869cc07ba78a167b8462a9cef09c0ba) but with a deadline parameter. + + + +## neco_write() +```c +ssize_t neco_write(int fd, const void *data, size_t nbytes); +``` +Write to a file descriptor. + +This operation attempts to write all bytes in the buffer starting at buf to the file referred to by the file descriptor fd. + +This is a Posix wrapper function for the purpose of running in a Neco coroutine. It's expected that the provided file descriptor is in non-blocking state. + +One difference from the Posix version is that this function will attempt to write *all* bytes in buffer. The programmer, at their discretion, may considered it as an error when fewer than count is returned. If so, the [neco_lasterr()](#group___error_funcs_1ga82ade5f26218caa8e0b4883575a683bd) will return the NECO_PARTIALWRITE. + + + +**Return** + +- On success, the number of bytes written is returned. +- On error, value -1 (NECO_ERROR) is returned, and errno is set to indicate the error. + + +**See also** + +- [Posix wrappers](#group___posix) +- [neco_setnonblock()](#group___posix2_1ga6f612b7e64d5a9ec7ff607315cc8ec3a) +- [https://www.man7.org/linux/man-pages/man2/write.2.html](https://www.man7.org/linux/man-pages/man2/write.2.html) + + + + +## neco_write_dl() +```c +ssize_t neco_write_dl(int fd, const void *data, size_t nbytes, int64_t deadline); +``` +Same as [neco_write()](#group___posix_1ga5c8ea872889626c86c57cc3180a673be) but with a deadline parameter. + + + +## neco_accept() +```c +int neco_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +``` +Accept a connection on a socket. + +While in a coroutine, this function should be used instead of the standard accept() to avoid blocking other coroutines from running concurrently. + +The the accepted file descriptor is returned in non-blocking mode. + + + +**Parameters** + +- **sockfd**: Socket file descriptor +- **addr**: Socket address out +- **addrlen**: Socket address length out + + + +**Return** + +- On success, file descriptor (non-blocking) +- On error, value -1 (NECO_ERROR) is returned, and errno is set to indicate the error. + + +**See also** + +- [Posix wrappers](#group___posix) + + + + +## neco_accept_dl() +```c +int neco_accept_dl(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int64_t deadline); +``` +Same as [neco_accept()](#group___posix_1ga49ed3b4837ffcd995b054cfb4bb178b1) but with a deadline parameter. + + + +## neco_connect() +```c +int neco_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +``` +Connects the socket referred to by the file descriptor sockfd to the address specified by addr. + +While in a coroutine, this function should be used instead of the standard connect() to avoid blocking other coroutines from running concurrently. + + + +**Parameters** + +- **sockfd**: Socket file descriptor +- **addr**: Socket address out +- **addrlen**: Socket address length out + + + +**Return** + +- NECO_OK Success +- On error, value -1 (NECO_ERROR) is returned, and errno is set to indicate the error. + + + + +## neco_connect_dl() +```c +int neco_connect_dl(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int64_t deadline); +``` +Same as [neco_connect()](#group___posix_1ga16815a8a7a51d95281f1d70555168383) but with a deadline parameter. + + + +## neco_getaddrinfo() +```c +int neco_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); +``` +The getaddrinfo() function is used to get a list of addresses and port numbers for node (hostname) and service. + +This is functionally identical to the Posix getaddrinfo function with the exception that it does not block, allowing for usage in a Neco coroutine. + + + +**Return** + +- On success, 0 is returned +- On error, a nonzero error code defined by the system. See the link below for a list. + + +**See also** + +- [Posix wrappers](#group___posix) +- [https://www.man7.org/linux/man-pages/man3/getaddrinfo.3.html](https://www.man7.org/linux/man-pages/man3/getaddrinfo.3.html) + + + + +## neco_getaddrinfo_dl() +```c +int neco_getaddrinfo_dl(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res, int64_t deadline); +``` +Same as [neco_getaddrinfo()](#group___posix_1gae80194a21ffdc035a7054276b537a8c3) but with a deadline parameter. + + + +## neco_setnonblock() +```c +int neco_setnonblock(int fd, bool nonblock, bool *oldnonblock); +``` +Change the non-blocking state for a file descriptor. + +**See also** + +- [File descriptor helpers](#group___posix2) + + + + +## neco_wait() +```c +int neco_wait(int fd, int mode); +``` +Wait for a file descriptor to be ready for reading or writing. + +Normally one should use [neco_read()](#group___posix_1ga1869cc07ba78a167b8462a9cef09c0ba) and [neco_write()](#group___posix_1ga5c8ea872889626c86c57cc3180a673be) to read and write data. But there may be times when you need more involved logic or to use alternative functions such as `recvmsg()` or `sendmsg()`. + +```c +while (1) { + int n = recvmsg(sockfd, msg, MSG_DONTWAIT); + if (n == -1) { + if (errno == EAGAIN) { + // The socket is not ready for reading. + neco_wait(sockfd, NECO_WAIT_READ); + continue; + } + // Socket error. + return; + } + // Message received. + break; +} +``` + + + + +**Parameters** + +- **fd**: The file descriptor +- **mode**: NECO_WAIT_READ or NECO_WAIT_WRITE + + + +**Return** + +- NECO_OK Success +- NECO_NOMEM The system lacked the necessary resources +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine +- NECO_ERROR Check errno for more info + + +**See also** + +- [File descriptor helpers](#group___posix2) + + + + +## neco_wait_dl() +```c +int neco_wait_dl(int fd, int mode, int64_t deadline); +``` +Same as [neco_wait()](#group___posix2_1ga99dc783673fbf7e3e12479d59ad7ee4c) but with a deadline parameter. + + + +## neco_serve() +```c +int neco_serve(const char *network, const char *address); +``` +Listen on a local network address. + +**Example** + +```c +int servefd = neco_serve("tcp", "127.0.0.1:8080"); +if (servefd < 0) { + // .. error, do something with it. +} +while (1) { + int fd = neco_accept(servefd, 0, 0); + // client accepted +} + +close(servefd); +``` + + +**Parameters** + +- **network**: must be "tcp", "tcp4", "tcp6", or "unix". +- **address**: the address to serve on + + + +**Return** + +- On success, file descriptor (non-blocking) +- On error, Neco error + + +**See also** + +- [Networking utilities](#group___networking) +- [neco_dial()](#group___networking_1ga327b79d78328e50dd1a98fbf444c9d0c) + + + + +## neco_serve_dl() +```c +int neco_serve_dl(const char *network, const char *address, int64_t deadline); +``` +Same as [neco_serve()](#group___networking_1ga62b996c86bb80c1cab2092d82c6ea53c) but with a deadline parameter. + + + +## neco_dial() +```c +int neco_dial(const char *network, const char *address); +``` +Connect to a remote server. + +**Example** + +```c +int fd = neco_dial("tcp", "google.com:80"); +if (fd < 0) { + // .. error, do something with it. +} +// Connected to google.com. Use neco_read(), neco_write(), or create a +// stream using neco_stream_make(fd). +close(fd); +``` + + +**Parameters** + +- **network**: must be "tcp", "tcp4", "tcp6", or "unix". +- **address**: the address to dial + + + +**Return** + +- On success, file descriptor (non-blocking) +- On error, Neco error + + +**See also** + +- [Networking utilities](#group___networking) +- [neco_serve()](#group___networking_1ga62b996c86bb80c1cab2092d82c6ea53c) + + + + +## neco_dial_dl() +```c +int neco_dial_dl(const char *network, const char *address, int64_t deadline); +``` +Same as [neco_dial()](#group___networking_1ga327b79d78328e50dd1a98fbf444c9d0c) but with a deadline parameter. + + + +## neco_cancel() +```c +int neco_cancel(int64_t id); +``` + + +## neco_cancel_dl() +```c +int neco_cancel_dl(int64_t id, int64_t deadline); +``` + + +## neco_setcanceltype() +```c +int neco_setcanceltype(int type, int *oldtype); +``` + + +## neco_setcancelstate() +```c +int neco_setcancelstate(int state, int *oldstate); +``` + + +## neco_rand_setseed() +```c +int neco_rand_setseed(int64_t seed, int64_t *oldseed); +``` +Set the random seed for the Neco pseudorandom number generator. + +The provided seed is only used for the (non-crypto) NECO_PRNG and is ignored for NECO_CPRNG. + + + +**Parameters** + +- **seed**: +- **oldseed[out]**: The previous seed + + + +**Return** + +- NECO_OK Success +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine + + +**See also** + +- [Random number generator](#group___random) + + + + +## neco_rand() +```c +int neco_rand(void *data, size_t nbytes, int attr); +``` +Generator random bytes + +This operation can generate cryptographically secure data by providing the NECO_CSPRNG option or non-crypto secure data with NECO_PRNG. + +Non-crypto secure data use the [pcg-family](https://www.pcg-random.org) random number generator. + + + +**Parameters** + +- **data**: buffer for storing random bytes +- **nbytes**: number of bytes to generate +- **attr**: NECO_PRNG or NECO_CSPRNG + + + +**Return** + +- NECO_OK Success +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine +- NECO_CANCELED Operation canceled + + + + +## neco_rand_dl() +```c +int neco_rand_dl(void *data, size_t nbytes, int attr, int64_t deadline); +``` +Same as [neco_rand()](#group___random_1ga4567eff3299e3b00e829b82ca657d8cd) but with a deadline parameter. + + + +## neco_signal_watch() +```c +int neco_signal_watch(int signo); +``` +Have the current coroutine watch for a signal. + +This can be used to intercept or ignore signals. + +Signals that can be watched: SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, SIGALRM + +Signals that *can not* be watched: SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP + +**Example** + +```c +// Set up this coroutine to watich for the SIGINT (Ctrl-C) and SIGQUIT +// (Ctrl-\) signals. +neco_signal_watch(SIGINT); +neco_signal_watch(SIGQUIT); + +printf("Waiting for Ctrl-C or Ctrl-\\ signals...\n"); +int sig = neco_signal_wait(); +if (sig == SIGINT) { + printf("\nReceived Ctrl-C\n"); +} else if (sig == SIGQUIT) { + printf("\nReceived Ctrl-\\\n"); +} + +// The neco_signal_unwatch is used to stop watching. +neco_signal_unwatch(SIGINT); +neco_signal_unwatch(SIGQUIT); +``` + + + + +**Parameters** + +- **signo**: The signal number + + + +**Return** + +- NECO_OK Success +- NECO_INVAL An invalid parameter was provided +- NECO_PERM Operation called outside of a coroutine + + +**See also** + +- [Signals](#group___signals) + + + + +## neco_signal_wait() +```c +int neco_signal_wait(); +``` +Wait for a signal to arrive. + +**Return** + +- A signal number or an error. +- NECO_PERM +- NECO_NOSIGWATCH if not currently watching for signals. +- NECO_CANCELED + + +**See also** + +- [Signals](#group___signals) +- [neco_signal_wait_dl()](#group___signals_1gacd8061a747344fbde13cbdfcc99efd8f) + + + + +## neco_signal_wait_dl() +```c +int neco_signal_wait_dl(int64_t deadline); +``` +Same as [neco_signal_wait()](#group___signals_1gaa34972437b6b9e85ba0efec6cd388516) but with a deadline parameter. + + + +## neco_signal_unwatch() +```c +int neco_signal_unwatch(int signo); +``` +Stop watching for a siganl to arrive + +**Parameters** + +- **signo**: The signal number + + + +**Return** + +- NECO_OK on success or an error + + + + +## neco_work() +```c +int neco_work(int64_t pin, void(*work)(void *udata), void *udata); +``` +Perform work in a background thread and wait until the work is done. + +This operation cannot be canceled and cannot timeout. It's the responibilty of the caller to figure out a mechanism for doing those things from inside of the work function. + +The work function will not be inside of a Neco context, thus all `neco_*` functions will fail if called from inside of the work function. + + + +**Parameters** + +- **pin**: pin to a thread, or use -1 for round robin selection. +- **work**: the work, must not be null +- **udata**: any user data + + + +**Return** + +- NECO_OK Success +- NECO_NOMEM The system lacked the necessary resources +- NECO_INVAL An invalid parameter was provided + + +**Note** + +- There is no way to cancel or timeout this operation +- There is no way to cancel or timeout this operation + + + + +## neco_getstats() +```c +int neco_getstats(neco_stats *stats); +``` +Returns various stats for the current Neco runtime. + +```c +// Print the number of active coroutines +neco_stats stats; +neco_getstats(&stats); +printf("%zu\n", stats.coroutines); +``` + + +Other stats include: + +```c +coroutines +sleepers +evwaiters +sigwaiters +senders +receivers +locked +waitgroupers +condwaiters +suspended +``` + + + + +## neco_is_main_thread() +```c +int neco_is_main_thread(); +``` +Test if coroutine is running on main thread. + +**Return** + +- 1 for true, 0 for false, or a negative value for error. + + + + +## neco_switch_method() +```c +const char *neco_switch_method(); +``` + + +## neco_env_setallocator() +```c +void neco_env_setallocator(void *(*malloc)(size_t), void *(*realloc)(void *, size_t), void(*free)(void *)); +``` +Globally set the allocators for all Neco functions. + +*This should only be run once at program startup and before the first neco_start function is called*. + +**See also** + +- [Global environment](#group___global_funcs) + + + + +## neco_env_setpaniconerror() +```c +void neco_env_setpaniconerror(bool paniconerror); +``` +Globally set the panic-on-error state for all coroutines. + +This will cause panics (instead of returning the error) for three errors: `NECO_INVAL`, `NECO_PERM`, and `NECO_NOMEM`. + +*This should only be run once at program startup and before the first neco_start function is called*. + +**See also** + +- [Global environment](#group___global_funcs) + + + + +## neco_env_setcanceltype() +```c +void neco_env_setcanceltype(int type); +``` +Globally set the canceltype for all coroutines. + +*This should only be run once at program startup and before the first neco_start function is called*. + +**See also** + +- [Global environment](#group___global_funcs) + + + + +## neco_env_setcancelstate() +```c +void neco_env_setcancelstate(int state); +``` +Globally set the cancelstate for all coroutines. + +*This should only be run once at program startup and before the first neco_start function is called*. + +**See also** + +- [Global environment](#group___global_funcs) + + + + +## neco_now() +```c +int64_t neco_now(); +``` +Get the current time. + +This operation calls gettime(CLOCK_MONOTONIC) to retreive a monotonically increasing value that is not affected by discontinuous jumps in the system time. + +This value IS NOT the same as the local system time; for that the user should call gettime(CLOCK_REALTIME). + +The main purpose of this function to work with operations that use deadlines, i.e. functions with the `*_dl()` suffix. + +**Example** + +```c +// Receive a message from a channel using a deadline of one second from now. +int ret = neco_chan_recv_dl(ch, &msg, neco_now() + NECO_SECOND); +if (ret == NECO_TIMEDOUT) { + // The operation timed out +} +``` + + + + +**Return** + +- On success, the current time as nanoseconds. +- NECO_PERM Operation called outside of a coroutine + + +**See also** + +- [Time](#group___time) + + + + +## neco_strerror() +```c +const char *neco_strerror(ssize_t errcode); +``` +Returns a string representation of an error code. + +**See also** + +- Errors + + + + +## neco_lasterr() +```c +int neco_lasterr(); +``` +Returns last known error from a Neco operation + +See [Neco errors](./API.md#errors) for a list. + + + +## neco_gai_lasterr() +```c +int neco_gai_lasterr(); +``` +Get the last error from a [neco_getaddrinfo()](#group___posix_1gae80194a21ffdc035a7054276b537a8c3) call. + +See the [man page](https://man.freebsd.org/cgi/man.cgi?query=gai_strerror) for a list of errors. + + + +## neco_panic() +```c +int neco_panic(const char *fmt,...); +``` +Stop normal execution of the current coroutine, print stack trace, and exit the program immediately. + + + +## neco_stream_make() +```c +int neco_stream_make(neco_stream **stream, int fd); +``` + + +## neco_stream_make_buffered() +```c +int neco_stream_make_buffered(neco_stream **stream, int fd); +``` + + +## neco_stream_close() +```c +int neco_stream_close(neco_stream *stream); +``` +Close a stream. + + + +## neco_stream_close_dl() +```c +int neco_stream_close_dl(neco_stream *stream, int64_t deadline); +``` +Close a stream with a deadline. A deadline is provided to accomodate for buffered streams that may need to flush bytes on close + + + +## neco_stream_read() +```c +ssize_t neco_stream_read(neco_stream *stream, void *data, size_t nbytes); +``` + + +## neco_stream_read_dl() +```c +ssize_t neco_stream_read_dl(neco_stream *stream, void *data, size_t nbytes, int64_t deadline); +``` + + +## neco_stream_write() +```c +ssize_t neco_stream_write(neco_stream *stream, const void *data, size_t nbytes); +``` + + +## neco_stream_write_dl() +```c +ssize_t neco_stream_write_dl(neco_stream *stream, const void *data, size_t nbytes, int64_t deadline); +``` + + +## neco_stream_readfull() +```c +ssize_t neco_stream_readfull(neco_stream *stream, void *data, size_t nbytes); +``` + + +## neco_stream_readfull_dl() +```c +ssize_t neco_stream_readfull_dl(neco_stream *stream, void *data, size_t nbytes, int64_t deadline); +``` + + +## neco_stream_read_byte() +```c +int neco_stream_read_byte(neco_stream *stream); +``` +Read and returns a single byte. If no byte is available, returns an error. + + + +## neco_stream_read_byte_dl() +```c +int neco_stream_read_byte_dl(neco_stream *stream, int64_t deadline); +``` +Same as [neco_stream_read_byte()](#group___streams_1gaf8dc61bc7e0b9c6c105c8ab51693ee3d) but with a deadline parameter. + + + +## neco_stream_unread_byte() +```c +int neco_stream_unread_byte(neco_stream *stream); +``` +Unread the last byte. Only the most recently read byte can be unread. + + + +## neco_stream_flush() +```c +int neco_stream_flush(neco_stream *stream); +``` +Flush writes any buffered data to the underlying file descriptor. + + + +## neco_stream_flush_dl() +```c +int neco_stream_flush_dl(neco_stream *stream, int64_t deadline); +``` +Same as [neco_stream_flush()](#group___streams_1ga1c847c02240767c06daf832eb85022e2) but with a deadline parameter. + + + +## neco_stream_buffered_read_size() +```c +ssize_t neco_stream_buffered_read_size(neco_stream *stream); +``` + + +## neco_stream_buffered_write_size() +```c +ssize_t neco_stream_buffered_write_size(neco_stream *stream); +``` + +*** + +Generated with the help of [doxygen](https://www.doxygen.nl/index.html) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..152b512 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,9 @@ +# Neco Documentation + +## [API.md](API.md) + +The Neco library API reference. This document includes information about each +type and function and is generated from the [neco.h](../neco.h) and [neco.c](../neco.c) +source files using a [doxygen](https://www.doxygen.nl) syntax. + +To manually generate the document, run the `docs/tools/build-api.sh`. diff --git a/docs/TECHNICAL.md b/docs/TECHNICAL.md new file mode 100644 index 0000000..e2a4fb1 --- /dev/null +++ b/docs/TECHNICAL.md @@ -0,0 +1,20 @@ +# Neco Technical Details + +Technical details of the Neco coroutine library. + +_Work in progress_ + +This is a list topics that may be of interest to the community. +I look forward to providing more information when time permits. + +- The runtime and scheduler +- Coroutine context switching +- Signal handling +- Deadlines +- Cancelations +- Epoll and Kqueue +- Networking +- Error handling +- Panics +- Stack unwinding +- Profiling \ No newline at end of file diff --git a/docs/assets/API_foot.md b/docs/assets/API_foot.md new file mode 100644 index 0000000..6aa62df --- /dev/null +++ b/docs/assets/API_foot.md @@ -0,0 +1,3 @@ +*** + +Generated with the help of [doxygen](https://www.doxygen.nl/index.html) diff --git a/docs/assets/API_head.md b/docs/assets/API_head.md new file mode 100644 index 0000000..8db3f2c --- /dev/null +++ b/docs/assets/API_head.md @@ -0,0 +1,150 @@ +# Neco C API + +C API for the Neco coroutine library. + +This document provides a detailed description of the functions and types in the +[neco.h](../neco.h) and [neco.c](../neco.c) source files for the Neco library. + +For a more general overview please see the project +[README](https://github.com/tidwall/neco). + + +## Table of contents + +- [Programing notes](#programming-notes) +- [Basic operations](#basic-operations) +- [Channels](#channels) +- [Generators](#generators) +- [Mutexes](#mutexes) +- [WaitGroups](#waitgroups) +- [Condition variables](#condition-variables) +- [Posix wrappers](#posix-wrappers) +- [File descriptor helpers](#file-descriptor-helpers) +- [Networking utilities](#Networking-utilities) +- [Streams and buffered I/O](#streams-and-buffered-io) +- [Random number generator](#random-number-generator) +- [Error handling](#error-handling) +- [Background worker](#background-worker) +- [Time](#time) +- [Signals](#signals) +- [Cancelation](#cancelation) +- [Stats and information](#stats-and-information) +- [Global environment](#global-environment) + +## Programming notes + +### neco_main + +The `neco_main()` function may be used instead of the standard C `main()`, +which effectively runs the entire program in a Neco coroutine context. + +```c +#include "neco.h" + +int neco_main(int argc, char *argv[]) { + // Running inside of a Neco coroutine + return 0; +} +``` + +Doing so adjusts Neco to behave as follows: + +- `neco_env_setpaniconerror(true)` +- `neco_env_setcanceltype(NECO_CANCEL_ASYNC)` +- The program will exit after the the main coroutine returns. + + +### Neco errors + +Neco functions return Neco errors. + +```c +#define NECO_OK 0 ///< Successful result (no error) +#define NECO_ERROR -1 ///< System error (check errno) +#define NECO_INVAL -2 ///< Invalid argument +#define NECO_PERM -3 ///< Operation not permitted +#define NECO_NOMEM -4 ///< Cannot allocate memory +#define NECO_EOF -5 ///< End of file or stream (neco_stream_*) +#define NECO_NOTFOUND -6 ///< No such coroutine (neco_cancel) +#define NECO_NOSIGWATCH -7 ///< Not watching on a signal +#define NECO_CLOSED -8 ///< Channel is closed +#define NECO_EMPTY -9 ///< Channel is empty (neco_chan_tryrecv) +#define NECO_TIMEDOUT -10 ///< Deadline has elapsed (by neco_*_dl) +#define NECO_CANCELED -11 ///< Operation canceled (by neco_cancel) +#define NECO_BUSY -12 ///< Resource busy (by mutex_trylock) +#define NECO_NEGWAITGRP -13 ///< Negative waitgroup counter +#define NECO_GAIERROR -14 ///< Error with getaddrinfo (check neco_gai_error) +#define NECO_UNREADFAIL -15 ///< Failed to unread byte (neco_stream_unread_byte) +#define NECO_PARTIALWRITE -16 ///< Failed to write all data (neco_stream_flush) +#define NECO_NOTGENERATOR -17 ///< Coroutine is not a generator (neco_gen_yield) +#define NECO_NOTSUSPENDED -18 ///< Coroutine is not suspended (neco_resume) +``` + +Three of those errors will panic when `neco_env_setpaniconerror(true)`: +`NECO_INVAL`, `NECO_PERM`, and `NECO_NOMEM`. + +Some Neco functions are Posix wrappers, which return the `NECO_ERROR` (-1) and +it's the programmers responsibilty to check the errno. +Those functions include [neco_read()](#neco_read), [neco_write()](#neco_write), +[neco_accept()](#neco_accept), and [neco_connect()](#neco_connect). + + +At any point the programmer may check the `neco_lasterr()` function to get the +Neco error from the last `neco_*` call. This function will ensure that system +errors will be automatically converted to its Neco equivalent. For example, +let's say that a `neco_read()` (which is Posix wrapper for `read()`) returns +`-1` and the `errno` is `EINVAL`, then `neco_lasterr()` will return +`NECO_INVAL`. + +### Deadlines and cancelation + +All operations that may block a coroutine will have an extended function with +a `_dl` suffix that provides an additional deadline argument. +A deadline is a timestamp in nanoseconds. NECO_TIMEDOUT will be returned if +the operation does not complete in the provide time. + +All operations that may block can also be canceled using the [`neco_cancel()`](docs/API.md#neco_cancel) +function from a different coroutine. +Calling `neco_cancel()` will not cancel an operation that does not block. +A cancel takes higher priority than a deadline. Such that if an operation was +canceled and also has timedout at the same time the cancel wins and +NECO_CANCELED will be returned. + +```c + +// Try to connect, timing out after one second. +int fd = neco_dial_dl("tcp", "google.com:80", neco_now() + NECO_SECOND); +if (fd < 0) { + // Connection failed + if (fd == NECO_CANCELED) { + // Operation canceled + } else if (fd == NECO_TIMEDOUT) { + // Operation timedout + } else { + // Some other error + } + return; +} +// Success +``` + +#### Async cancelation + +By default, when a coroutine is canceled it's the responsibility of the +programmer to check the return value and perform any clean up. But it's also +possible to have Neco handle all that by enabling the async cancelation with +`neco_setcanceltype(NECO_CANCEL_ASYNC, 0)` at the coroutine level or +`neco_env_setcanceltype(NECO_CANCEL_ASYNC)` globally from the main function. + +When async cancelation is enabled, the coroutine that is canceled is terminated +right away, and any cleanup handled using the `neco_cleanup_push()` and +`neco_cleanup_pop()` functions. + +Async cancelation is automatically enabled when using `neco_main()`, instead of +`main()`, as the program startup function. + +#### Disabling cancelation + +Finally, coroutine cancelation can be totally disabled by calling +`neco_setcancelstate(NECO_CANCEL_DISABLE, 0)` at the coroutine level or +`neco_env_setcancelstate(NECO_CANCEL_DISABLE)` globally from the main function. diff --git a/docs/assets/logo-dark.png b/docs/assets/logo-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..46cc65a6c4817221dbaff54d96dac9bf8bf5ba59 GIT binary patch literal 41574 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%0Dhu&^1Kq#QGMUSak?Q?Ci{)yw+K5{ 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 .doxygen.cfg +echo "EXTRACT_ALL = YES" >> .doxygen.cfg +echo "ENABLE_PREPROCESSING=YES" >> .doxygen.cfg +echo "GENERATE_HTML = NO" >> .doxygen.cfg +echo "GENERATE_LATEX = NO" >> .doxygen.cfg +echo "FILE_PATTERNS = *.h *.c" >> .doxygen.cfg +echo "OPTIMIZE_OUTPUT_FOR_C = YES" >> .doxygen.cfg +echo "INPUT = $SRCDIR/.." >> .doxygen.cfg + +# First pass to xml +doxygen .doxygen.cfg + +# Run the doxygen-md tool +cd ../tools/doxygen-md +go build . +mv doxygen-md ../../tmp +cd ../../tmp +echo "" >> API.md +echo "" >> API.md +cat ../assets/API_head.md >> API.md +./doxygen-md -ns "$NAMESPACE" >> API.md +cat ../assets/API_foot.md >> API.md +mv API.md ../ +cd .. diff --git a/docs/tools/doxygen-md/README.md b/docs/tools/doxygen-md/README.md new file mode 100644 index 0000000..59796f1 --- /dev/null +++ b/docs/tools/doxygen-md/README.md @@ -0,0 +1,5 @@ +# doxygen-md + +Doxygen to Markdown + +Not for general use. diff --git a/docs/tools/doxygen-md/go.mod b/docs/tools/doxygen-md/go.mod new file mode 100644 index 0000000..210fb3b --- /dev/null +++ b/docs/tools/doxygen-md/go.mod @@ -0,0 +1,11 @@ +module github.com/tidwall/doxygen-md + +go 1.20 + +require ( + github.com/tidwall/gjson v1.16.0 + github.com/tidwall/pretty v1.2.1 + github.com/tidwall/x2j v1.0.0 +) + +require github.com/tidwall/match v1.1.1 // indirect diff --git a/docs/tools/doxygen-md/go.sum b/docs/tools/doxygen-md/go.sum new file mode 100644 index 0000000..fffe3b6 --- /dev/null +++ b/docs/tools/doxygen-md/go.sum @@ -0,0 +1,9 @@ +github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= +github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/x2j v1.0.0 h1:FqJvL1KthoyDdSWZh8qSDMux9ta2tkvgUu7FXJbhvyg= +github.com/tidwall/x2j v1.0.0/go.mod h1:tMBYGj9wT5AZBYjXv3JqjBtb+rBvlW1S05sAQpBxx88= diff --git a/docs/tools/doxygen-md/main.go b/docs/tools/doxygen-md/main.go new file mode 100644 index 0000000..272cf98 --- /dev/null +++ b/docs/tools/doxygen-md/main.go @@ -0,0 +1,834 @@ +package main + +import ( + "flag" + "fmt" + "io" + "log" + "os" + "path/filepath" + "runtime" + "sort" + "strings" + + "github.com/tidwall/gjson" + "github.com/tidwall/pretty" + "github.com/tidwall/x2j" +) + +const ShowIndexBullets = true + +func convertXMLDirectoryToIndexJSON() error { + entries, err := os.ReadDir("xml") + if err != nil { + return err + } + var dst []byte + dst = append(dst, '{') + var n int + for _, entry := range entries { + if !strings.HasSuffix(entry.Name(), ".xml") { + continue + } + xmldata, err := os.ReadFile(filepath.Join("xml", entry.Name())) + if err != nil { + return err + } + jraw, err := x2j.Convert(xmldata) + if err != nil { + return err + } + name := entry.Name()[:len(entry.Name())-4] + if n > 0 { + dst = append(dst, ',') + } + dst = gjson.AppendJSONString(dst, name) + dst = append(dst, ':') + dst = append(dst, jraw...) + n++ + } + dst = append(dst, '}') + return os.WriteFile("index.json", pretty.Pretty(dst), 0666) +} + +func modDedup(json string, arg string) string { + arr := gjson.Parse(json) + if !arr.IsArray() { + return "" + } + all := make(map[string]bool) + var out []byte + out = append(out, '[') + arr.ForEach(func(_, res gjson.Result) bool { + str := res.String() + if exists := all[str]; !exists { + if len(all) > 0 { + out = append(out, ',') + } + out = append(out, res.Raw...) + all[str] = true + } + return true + }) + out = append(out, ']') + return string(out) +} + +func genDefsJSON(ns string, json string) gjson.Result { + // Get array of all valid files. Includes *.c, *.h, etc. + files := gjson.Parse(json). + Get(`index.children.#(name=compound)#`). + Get(`#(attrs.kind=file)#`) + + // Get the first known public public header file + hfile := files. + Get(`#(children.0.name=name)#`). + Get(`#(children.0.children.0%*.h)#`). + Get(`0`) + hid := hfile.Get("attrs.refid").String() + + // header details file + hdetails := gjson.Get(json, hid) + + // Get innerclass types, which are struct that are declared in the header + // which members that should be shown (not private). + hpublictypes := hdetails. + Get("children.#(name=compounddef)#"). + Get("#.children.#(name=innerclass)#"). + Get("@flatten") + + // Get a list of all refids + refids0 := hfile.Get(`@dig:refid`) + refids1 := hdetails.Get(`@dig:refid`) + refids2 := gjson.Parse(json). + Get(`index.children.#(name=compound)#`). + Get(`#(attrs.kind=group)#`). + Get(`#.attrs.refid`) + refidsAll := "[" + refids0.Raw + "," + refids1.Raw + "," + refids2.Raw + "]" + refids := gjson.Get(refidsAll, "@flatten|@dedup") + + mdefs := gjson.Get(json, `@dig:#(name=memberdef)#|@flatten`) + nprocs := runtime.NumCPU() + chout := make(chan string) + chin := make(chan string) + chdoutone := make(chan bool) + for i := 0; i < nprocs; i++ { + go func(i int) { + for refid := range chin { + hdef := mdefs.Get(`#(attrs.id=` + refid + `)`) + cdef := gjson.Get(json, refid). + Get("children.#(name=compounddef)") + name := "" + kind := "" + if hdef.Exists() { + name = hdef. + Get("children.#(name=name)|children.0").String() + kind = hdef.Get("attrs.kind").String() + } else if cdef.Exists() { + name = cdef. + Get("children.#(name=compoundname)|children.0").String() + kind = cdef.Get("attrs.kind").String() + } + if name == "" || kind == "file" { + continue + } + if kind != "group" && !strings.HasPrefix(name, ns) { + continue + } + showdef := hpublictypes.Get("#(children.0="+name+")"). + Get("attrs.prot").String() == "public" + var out []byte + out = append(out, '{') + out = append(out, `"name":`...) + out = gjson.AppendJSONString(out, name) + out = append(out, `,"kind":`...) + out = gjson.AppendJSONString(out, kind) + out = append(out, `,"refid":`...) + out = gjson.AppendJSONString(out, refid) + if hdef.Exists() { + out = append(out, `,"hdef":`...) + out = append(out, hdef.Raw...) + } + if cdef.Exists() { + out = append(out, `,"cdef":`...) + out = append(out, cdef.Raw...) + } + if showdef { + out = append(out, `,"showdef":true`...) + } + out = append(out, '}') + chout <- string(out) + } + chdoutone <- true + }(i) + } + all := make(map[string]string) + go func() { + for out := range chout { + all[gjson.Get(out, "refid").String()] = out + } + chdoutone <- true + }() + refids.ForEach(func(_, res gjson.Result) bool { + chin <- res.String() + return true + }) + close(chin) + for i := 0; i < nprocs; i++ { + <-chdoutone + } + close(chout) + <-chdoutone + var out []byte + out = append(out, '[') + n := 0 + refids.ForEach(func(_, res gjson.Result) bool { + refid := res.String() + if val, ok := all[refid]; ok { + if n > 0 { + out = append(out, ',') + } + out = append(out, val...) + n++ + } + return true + }) + out = append(out, ']') + defs := string(out) // defs, unordered + arr := gjson.Parse(defs).Array() + sort.SliceStable(arr, func(i, j int) bool { + a := arr[i].Get("hdef.children.#(name=location).attrs.line") + if !a.Exists() { + a = arr[i].Get("cdef.children.#(name=location).attrs.line") + } + b := arr[j].Get("hdef.children.#(name=location).attrs.line") + if !b.Exists() { + b = arr[j].Get("cdef.children.#(name=location).attrs.line") + } + return a.Int() < b.Int() + }) + + out = append(out[:0], '[') + for i := range arr { + if i > 0 { + out = append(out, ',') + } + out = append(out, arr[i].Raw...) + } + out = append(out, ']') + defs = string(out) // defs, ordered + return gjson.Parse(defs).Get("@pretty") +} + +func main() { + gjson.AddModifier("dedup", modDedup) + + var ns string + flag.StringVar(&ns, "ns", "", "public namespace") + flag.Parse() + + if err := convertXMLDirectoryToIndexJSON(); err != nil { + log.Fatal(err) + } + jsondata, err := os.ReadFile("index.json") + if err != nil { + log.Fatal(err) + + } + defs := genDefsJSON(ns, string(jsondata)) + os.WriteFile("defs.json", []byte(defs.Raw), 0666) + + dox := Doxygen{defs} + md := dox.Markdown() + os.Stdout.Write([]byte(md)) + +} + +const briefDescPath = "children.#(name=briefdescription).children." + + "#(name=para).children" + +func toSimpleText(text gjson.Result) string { + var str string + text.ForEach(func(_, part gjson.Result) bool { + if part.Type == gjson.String { + str += part.String() + } else { + str += toSimpleText(part.Get("children")) + } + return true + }) + return str +} + +type Doxygen struct{ gjson.Result } +type Struct struct{ gjson.Result } +type StructMember struct{ gjson.Result } +type Enum struct{ gjson.Result } +type EnumMember struct{ gjson.Result } +type Func struct{ gjson.Result } +type FuncParam struct{ gjson.Result } +type Group struct{ gjson.Result } + +func mdwrap(list gjson.Result, start, end string, plain bool) string { + var md string + if !plain { + md += start + } + // if len(start) > 0 && start[0] == '`' { + // md += markdownDescPara(list, true) + // } else { + md += markdownDesc(list, plain) + // } + if !plain { + md += end + } + return md +} + +func markdownDesc(list gjson.Result, plain bool) string { + var md string + var lastsimplekind string + list.ForEach(func(_, val gjson.Result) bool { + if val.IsObject() { + switch val.Get("name").String() { + case "para": + md += markdownDesc(val.Get("children"), plain) + md += "\n\n" + case "parameterlist": + md += "\n\n**Parameters**\n\n" + params := val.Get("children.#(name=parameteritem)#|@pretty") + params.ForEach(func(_, pval gjson.Result) bool { + name := markdownDesc( + pval.Get("children.#(name=parameternamelist)"). + Get("children.#(name=parametername)"). + Get("children"), plain) + desc := markdownDesc( + pval.Get("children.#(name=parameterdescription)"). + Get("children"), plain) + name = strings.TrimSpace(name) + desc = strings.TrimSpace(desc) + md += "- **" + name + "**: " + desc + "\n" + return true + }) + md += "\n" + case "simplesect": + kind := val.Get("attrs.kind").String() + if kind != "" { + if kind == "see" { + kind = "See also" + } + kind = strings.ToUpper(kind[0:1]) + kind[1:] + } + if lastsimplekind != kind { + md += "\n\n**" + kind + "**\n\n" + } + lastsimplekind = kind + line := markdownDesc(val.Get("children"), plain) + line = strings.TrimSpace(line) + md += "- " + line + "\n" + case "ref": + refid := val.Get("attrs.refid").String() + text := val.Get("children.0").String() + if !plain && refid != "" && text != "" { + md += "[" + text + "](#" + refid + ")" + } else { + md += text + } + case "ulink": + url := val.Get("attrs.url").String() + text := val.Get("children.0").String() + if !plain && url != "" && text != "" { + md += "[" + text + "](" + url + ")" + } else { + md += text + } + case "computeroutput": + md += mdwrap(val.Get("children"), "`", "`", plain) + case "emphasis": + md += mdwrap(val.Get("children"), "*", "*", plain) + case "bold": + md += mdwrap(val.Get("children"), "**", "**", plain) + case "programlisting": + md += mdwrap(val.Get("children"), "```c\n", "```\n", plain) + case "codeline": + md += markdownDesc(val.Get("children"), true) + "\n" + case "sp": + md += " " + default: + // println(val.Get("name").String()) + md += markdownDesc(val.Get("children"), plain) + } + } else { + md += val.String() + } + return true + }) + return md +} + +func (dox *Doxygen) Structs() []Struct { + var structs []Struct + path := "#(kind=struct)#" + dox.Get(path).ForEach(func(_, val gjson.Result) bool { + if val.Get("cdef.children.#(name=includes)").Exists() { + structs = append(structs, Struct{val}) + } + return true + }) + return structs +} + +func (dox *Doxygen) Objects() []Struct { + // objects are structs that do not have a header structure. + var structs []Struct + path := "#(kind=struct)#" + dox.Get(path).ForEach(func(_, val gjson.Result) bool { + if !val.Get("cdef.children.#(name=includes)").Exists() { + structs = append(structs, Struct{val}) + } + return true + }) + return structs +} + +func (dox *Doxygen) Typedefs() []Struct { + var structs []Struct + path := "#(kind=typedef)#" + dox.Get(path).ForEach(func(_, val gjson.Result) bool { + if !val.Get("cdef.children.#(name=includes)").Exists() { + structs = append(structs, Struct{val}) + } + return true + }) + return structs +} + +func getDetailsChildren(val gjson.Result) gjson.Result { + arr := val.Get("hdef.children.#(name=detaileddescription).children") + if !arr.Exists() { + arr = val.Get("cdef.children.#(name=detaileddescription).children") + } + if !arr.Exists() { + arr = val.Get("hdef.children.#(name=briefdescription).children") + } + if !arr.Exists() { + arr = val.Get("cdef.children.#(name=briefdescription).children") + } + return arr +} + +func getBriefChildren(val gjson.Result) gjson.Result { + arr := val.Get("hdef.children.#(name=briefdescription).children") + if !arr.Exists() { + arr = val.Get("cdef.children.#(name=briefdescription).children") + } + return arr +} + +func (val *Struct) DetailsMarkdown(plain bool) string { + list := getDetailsChildren(val.Result) + return markdownDesc(list, plain) +} + +func (val *Struct) BriefMarkdown(plain bool) string { + list := getBriefChildren(val.Result) + md := strings.TrimSpace(markdownDesc(list, plain)) + return md +} + +func (val *Struct) Name() string { + return val.Get("name").String() +} + +func (val *Struct) RefID() string { + return val.Get("refid").String() +} + +func (val *Struct) Signature() string { + sig := "struct " + val.Name() + if val.Get("showdef").Bool() { + sig += " {\n" + var maxnlen int + for _, mdef := range val.Members() { + label := mdef.Type() + " " + mdef.Name() + ";" + if len(label) > maxnlen { + maxnlen = len(label) + } + } + for _, mdef := range val.Members() { + label := mdef.Type() + " " + mdef.Name() + ";" + if maxnlen > 0 { + label += strings.Repeat(" ", maxnlen-len(label)) + } + sig += " " + label + brief := mdef.Brief() + if brief != "" { + sig += " // " + brief + } + sig += "\n" + } + sig += "}" + } + sig += ";" + return sig +} + +func (val *Struct) Members() []StructMember { + var members []StructMember + path := "cdef.children.#(name=sectiondef).children.#(name=memberdef)#" + val.Get(path).ForEach(func(_, mdef gjson.Result) bool { + members = append(members, StructMember{mdef}) + return true + }) + return members +} + +func (val *StructMember) Type() string { + path := "children.#(name=type).children" + return toSimpleText(val.Get(path)) +} + +func (val *StructMember) Name() string { + return val.Get("children.#(name=name).children.0").String() +} + +func (val *StructMember) Brief() string { + return toSimpleText(val.Get(briefDescPath)) +} + +func (val *Enum) Name() string { + return val.Get("name").String() +} + +func (val *Enum) RefID() string { + return val.Get("refid").String() +} + +func (val *Enum) DetailsMarkdown(plain bool) string { + list := getDetailsChildren(val.Result) + return markdownDesc(list, plain) +} + +func (val *Enum) BriefMarkdown(plain bool) string { + list := getBriefChildren(val.Result) + md := strings.TrimSpace(markdownDesc(list, plain)) + return md +} + +func (val *Enum) Members() []EnumMember { + var members []EnumMember + path := "hdef.children.#(name=enumvalue)#" + val.Get(path).ForEach(func(_, mdef gjson.Result) bool { + members = append(members, EnumMember{mdef}) + return true + }) + return members +} +func (val *Enum) Signature() string { + sig := "enum " + val.Name() + " {\n" + var maxnlen int + var maxilen int + for _, eval := range val.Members() { + nlen := len(eval.Name()) + if nlen > maxnlen { + maxnlen = nlen + } + ilen := len(eval.Initializer()) + if ilen > maxilen { + maxilen = ilen + } + } + for _, eval := range val.Members() { + name := eval.Name() + initializer := eval.Initializer() + brief := eval.Brief() + label := name + if maxilen > 0 { + label += strings.Repeat(" ", maxnlen-len(name)) + label += " " + label += initializer + } + label += "," + if maxilen == 0 { + label += strings.Repeat(" ", maxnlen-len(name)) + } else { + label += strings.Repeat(" ", maxilen-len(initializer)) + } + if brief != "" { + label += " // " + brief + } + label = strings.TrimSpace(label) + sig += " " + label + "\n" + } + sig += "};" + return sig +} + +func (val *EnumMember) Name() string { + return val.Get("children.#(name=name).children.0").String() +} + +func (val *EnumMember) Initializer() string { + return val.Get("children.#(name=initializer).children.0").String() +} +func (val *EnumMember) Brief() string { + return toSimpleText(val.Get(briefDescPath)) +} + +func (dox *Doxygen) Enums() []Enum { + var enums []Enum + path := "#(kind=enum)#" + dox.Get(path).ForEach(func(_, val gjson.Result) bool { + enums = append(enums, Enum{val}) + return true + }) + return enums +} + +func (dox *Doxygen) Funcs() []Func { + var vals []Func + path := "#(kind=function)#" + dox.Get(path).ForEach(func(_, val gjson.Result) bool { + vals = append(vals, Func{val}) + return true + }) + return vals +} + +func (val *Func) Name() string { + return val.Get("name").String() +} + +func (val *Func) RefID() string { + return val.Get("refid").String() +} + +func (val *Func) Signature() string { + typ := toSimpleText(val.Get("hdef.children.#(name=type).children")) + name := val.Get("hdef.children.#(name=name).children.0").String() + args := val.Get("hdef.children.#(name=argsstring).children.0").String() + sig := typ + if !strings.HasSuffix(sig, "*") { + sig += " " + } + args = strings.TrimSpace(args) + if args == "(void)" { + args = "()" + } + sig += strings.TrimSpace(name) + args + ";" + return sig +} + +func (val *Func) DetailsMarkdown(plain bool) string { + list := getDetailsChildren(val.Result) + return markdownDesc(list, plain) +} + +func (val *Func) BriefMarkdown(plain bool) string { + list := getBriefChildren(val.Result) + return markdownDesc(list, plain) +} + +func (val *FuncParam) Name() string { + path := "children.#(name=parameternamelist)." + + "children.#(name=parametername).children.0" + return val.Get(path).String() +} + +func (dox *Doxygen) Groups() []Group { + var vals []Group + path := "#(kind=group)#" + dox.Get(path).ForEach(func(_, val gjson.Result) bool { + vals = append(vals, Group{val}) + return true + }) + return vals +} + +func (val *Group) Name() string { + return val.Get("name").String() +} + +func (val *Group) RefID() string { + return val.Get("refid").String() +} + +func (val *Group) DetailsMarkdown(plain bool) string { + list := getDetailsChildren(val.Result) + return markdownDesc(list, plain) +} + +func (val *Group) BriefMarkdown(plain bool) string { + list := getBriefChildren(val.Result) + return markdownDesc(list, plain) +} + +// func (val *Group) Desc() string { +// const path = "cdef.children.#(name=detaileddescription).children." + +// "#(name=para).children" +// return toSimpleText(val.Get(path)) +// } + +func (val *Group) Title() string { + path := "cdef.children.#(name=title).children" + return toSimpleText(val.Get(path)) +} + +func (dox *Doxygen) writeIndexMarkdown(wr io.Writer) { + const idxfmt = "[%s](#%s)" + + // fmt.Fprintf(wr, "## Structs\n\n") + var n int + // for _, val := range dox.Structs() { + // if ShowIndexBullets { + // fmt.Fprintf(wr, "- "+idxfmt+"\n", val.Name(), val.RefID()) + // // fmt.Fprintf(wr, idxfmt+" \n", val.Name(), val.RefID()) + // } else { + // if n > 0 { + // fmt.Fprintf(wr, ",\n") + // } + // fmt.Fprintf(wr, idxfmt, val.Name(), val.RefID()) + // n++ + // } + // } + + // tdefs := dox.Typedefs() + n = 0 + // if len(tdefs) > 0 { + // fmt.Fprintf(wr, "## Types\n\n") + // for _, val := range dox.Typedefs() { + // if ShowIndexBullets { + // fmt.Fprintf(wr, "- "+idxfmt+"\n", val.Name(), val.RefID()) + // // fmt.Fprintf(wr, idxfmt+" \n", val.Name(), val.RefID()) + // } else { + // if n > 0 { + // fmt.Fprintf(wr, ",\n") + // } + // fmt.Fprintf(wr, idxfmt, val.Name(), val.RefID()) + // n++ + // } + // } + // } + // fmt.Fprintf(wr, "## Objects\n\n") + // n = 0; + // for _, val := range dox.Objects() { + // if ShowIndexBullets { + // fmt.Fprintf(wr, "- "+idxfmt+"\n", val.Name(), val.RefID()) + // // fmt.Fprintf(wr, idxfmt+" \n", val.Name(), val.RefID()) + // } else { + // if n > 0 { + // fmt.Fprintf(wr, ",\n") + // } + // fmt.Fprintf(wr, idxfmt, val.Name(), val.RefID()) + // n++ + // } + // } + + enums := dox.Enums() + if len(enums) > 0 { + fmt.Fprintf(wr, "## Enums\n\n") + n = 0 + for _, val := range enums { + if ShowIndexBullets { + fmt.Fprintf(wr, "- "+idxfmt+"\n", val.Name(), val.RefID()) + // fmt.Fprintf(wr, idxfmt+" \n", val.Name(), val.RefID()) + } else { + if n > 0 { + fmt.Fprintf(wr, ",\n") + } + fmt.Fprintf(wr, idxfmt, val.Name(), val.RefID()) + n++ + } + } + } + fmt.Fprintf(wr, "\n\n") + + groups := dox.Groups() + + var lastGroupID string + n = 0 + for _, val := range dox.Funcs() { + var group Group + var ngroupf string + if strings.HasPrefix(val.RefID(), "group__") { + for j := range groups { + if strings.HasPrefix(val.RefID(), groups[j].RefID()) { + if len(groups[j].RefID()) > len(ngroupf) { + ngroupf = groups[j].RefID() + group = groups[j] + } + } + } + } + if group.RefID() != lastGroupID { + if n > 0 { + fmt.Fprintf(wr, "\n\n") + } + fmt.Fprintf(wr, "\n", group.RefID()) + fmt.Fprintf(wr, "## %s\n\n", group.Title()) + desc := group.DetailsMarkdown(false) + if desc != "" { + fmt.Fprintf(wr, "%s\n\n", desc) + } + lastGroupID = group.RefID() + n = 0 + } + if ShowIndexBullets { + fmt.Fprintf(wr, "- "+idxfmt+"\n", val.Name()+"()", val.RefID()) + // fmt.Fprintf(wr, idxfmt+" \n", val.Name()+"()", val.RefID()) + } else { + + if n > 0 { + fmt.Fprintf(wr, ",\n") + } + fmt.Fprintf(wr, idxfmt, val.Name()+"()", val.RefID()) + } + n++ + } + fmt.Fprintf(wr, "\n") +} + +type DefType interface { + RefID() string + Name() string + Signature() string + DetailsMarkdown(plain bool) string +} + +func writeDefType(val DefType, wr io.Writer, plain bool, isfunc bool) { + fmt.Fprintf(wr, "\n", val.RefID()) + var ex string + if isfunc { + ex = "()" + } + fmt.Fprintf(wr, "## %s%s\n", val.Name(), ex) + fmt.Fprintf(wr, "```c\n") + fmt.Fprintf(wr, "%s\n", val.Signature()) + fmt.Fprintf(wr, "```\n") + fmt.Fprintf(wr, "%s\n", val.DetailsMarkdown(false)) +} + +func (dox *Doxygen) writeDefinitionsMarkdown(wr io.Writer) { + for _, val := range dox.Structs() { + writeDefType(&val, wr, false, false) + } + + for _, val := range dox.Objects() { + writeDefType(&val, wr, false, false) + } + + for _, val := range dox.Enums() { + writeDefType(&val, wr, false, false) + } + + for _, val := range dox.Funcs() { + writeDefType(&val, wr, false, true) + } +} + +func (dox *Doxygen) Markdown() string { + out := strings.Builder{} + dox.writeIndexMarkdown(&out) + dox.writeDefinitionsMarkdown(&out) + return out.String() +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..f8f41dc --- /dev/null +++ b/examples/README.md @@ -0,0 +1,6 @@ +# Neco examples + +This directory contains various example of using Neco. + +Also check out the [Bluebox](https://github.com/tidwall/bluebox) project for a +complete networking example, including benchmarks. diff --git a/examples/channel-buffering.c b/examples/channel-buffering.c new file mode 100644 index 0000000..1a7ceb7 --- /dev/null +++ b/examples/channel-buffering.c @@ -0,0 +1,31 @@ +#include +#include +#include +#include "../neco.h" + + +int neco_main(int argc, char *argv[]) { + + // Here we make a channel of strings buffering up to 2 values. + neco_chan *messages; + neco_chan_make(&messages, sizeof(char*), 2); + + // Because this channel is buffered, we can send these values into the + // channel without a corresponding concurrent receive. + char *msg1 = "buffered"; + neco_chan_send(messages, &msg1); + char *msg2 = "channel"; + neco_chan_send(messages, &msg2); + + // Later we can receive these two values as usual. + char *msg = NULL; + neco_chan_recv(messages, &msg); + printf("%s\n", msg); + neco_chan_recv(messages, &msg); + printf("%s\n", msg); + + // This coroutine no longer needs this channel. + neco_chan_release(messages); + + return 0; +} \ No newline at end of file diff --git a/examples/channels.c b/examples/channels.c new file mode 100644 index 0000000..8f36a48 --- /dev/null +++ b/examples/channels.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include "../neco.h" + +void coroutine(int argc, void *argv[]) { + neco_chan *messages = argv[0]; + + // Send a message of the 'messages' channel. + char *msg = "ping"; + neco_chan_send(messages, &msg); + + // This coroutine no longer needs this channel. + neco_chan_release(messages); +} + +int neco_main(int argc, char *argv[]) { + + // Create a new channel that is used to send 'char*' string messages. + neco_chan *messages; + neco_chan_make(&messages, sizeof(char*), 0); + + // Start a coroutine that will send messages over the channel. + // It's a good idea to use neco_chan_retain on a channel before using it + // in a new coroutine. This will avoid potential use-after-free bugs. + neco_chan_retain(messages); + neco_start(coroutine, 1, messages); + + // Receive the next incoming message. Here we’ll receive the "ping" + // message we sent above and print it out. + char *msg = NULL; + neco_chan_recv(messages, &msg); + printf("%s\n", msg); + + // This coroutine no longer needs the channel. + neco_chan_release(messages); + + return 0; +} \ No newline at end of file diff --git a/examples/channels2.c b/examples/channels2.c new file mode 100644 index 0000000..8e43e40 --- /dev/null +++ b/examples/channels2.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include "../neco.h" + +void sum(int argc, void *argv[]) { + int *s = argv[0]; + int n = *(int*)argv[1]; + neco_chan *c = argv[2]; + + int sum = 0; + for (int i = 0; i < n; i++) { + sum += s[i]; + } + + neco_chan_send(c, &sum); + neco_chan_release(c); +} + +int neco_main(int argc, char *argv[]) { + int s[] = {7, 2, 8, -9, 4, 0}; + int n = sizeof(s)/sizeof(int); + + neco_chan *c; + neco_chan_make(&c, sizeof(int), 0); + + neco_chan_retain(c); + neco_start(sum, 3, &s[0], &(int){n/2}, c); + + neco_chan_retain(c); + neco_start(sum, 3, &s[n/2], &(int){n/2}, c); + + int x, y; + neco_chan_recv(c, &x); + neco_chan_recv(c, &y); + + printf("%d %d %d\n", x, y, x+y); + neco_chan_release(c); + return 0; +} diff --git a/examples/coroutines.c b/examples/coroutines.c new file mode 100644 index 0000000..6f0972c --- /dev/null +++ b/examples/coroutines.c @@ -0,0 +1,28 @@ +#include +#include +#include "../neco.h" + +void coroutine(int argc, void *argv[]) { + const char *name = (char*)argv[0]; + for (int i = 0; i < 5; i++) { + printf("coroutine: %s (%d)\n", name, i); + + // Yield to another coroutine. + neco_yield(); + } +} + +int neco_main(int argc, char *argv[]) { + + // Start three coroutines that will execute concurrently. + neco_start(coroutine, 1, "A"); + neco_start(coroutine, 1, "B"); + neco_start(coroutine, 1, "C"); + + // Our three function calls are now running asynchronously in separate + // coroutines. Wait for them to finish (for a more robust approach, use a + // neco_waitgroup or neco_join). + neco_sleep(NECO_MILLISECOND); + printf("done\n"); + return 0; +} \ No newline at end of file diff --git a/examples/echo-client.c b/examples/echo-client.c new file mode 100644 index 0000000..cbdcc97 --- /dev/null +++ b/examples/echo-client.c @@ -0,0 +1,28 @@ +#include +#include +#include "../neco.h" + +int neco_main(int argc, char *argv[]) { + int fd = neco_dial("tcp", "localhost:19203"); + if (fd == -1) { + perror("neco_listen"); + exit(1); + } + printf("connected\n"); + char buf[64]; + while (1) { + printf("> "); + fflush(stdout); + ssize_t nbytes = neco_read(STDIN_FILENO, buf, sizeof(buf)); + if (nbytes < 0) { + break; + } + ssize_t ret = neco_write(fd, buf, nbytes); + if (ret < 0) { + break; + } + } + printf("disconnected\n"); + close(fd); + return 0; +} \ No newline at end of file diff --git a/examples/echo-server.c b/examples/echo-server.c new file mode 100644 index 0000000..4d8c850 --- /dev/null +++ b/examples/echo-server.c @@ -0,0 +1,35 @@ +#include +#include +#include "../neco.h" + +void client(int argc, void *argv[]) { + int conn = *(int*)argv[0]; + printf("client connected\n"); + char buf[64]; + while (1) { + ssize_t n = neco_read(conn, buf, sizeof(buf)); + if (n <= 0) { + break; + } + printf("%.*s", (int)n, buf); + } + printf("client disconnected\n"); + close(conn); +} + +int neco_main(int argc, char *argv[]) { + int ln = neco_serve("tcp", "localhost:19203"); + if (ln == -1) { + perror("neco_serve"); + exit(1); + } + printf("listening at localhost:19203\n"); + while (1) { + int conn = neco_accept(ln, 0, 0); + if (conn > 0) { + neco_start(client, 1, &conn); + } + } + close(ln); + return 0; +} \ No newline at end of file diff --git a/examples/generators.c b/examples/generators.c new file mode 100644 index 0000000..ea12356 --- /dev/null +++ b/examples/generators.c @@ -0,0 +1,27 @@ +#include +#include +#include "../neco.h" + +void coroutine(int argc, void *argv[]) { + // Yield each int to the caller, one at a time. + for (int i = 0; i < 10; i++) { + neco_gen_yield(&i); + } +} + +int neco_main(int argc, char *argv[]) { + + // Create a new generator coroutine that is used to send ints. + neco_gen *gen; + neco_gen_start(&gen, sizeof(int), coroutine, 0); + + // Iterate over each int until the generator is closed. + int i; + while (neco_gen_next(gen, &i) != NECO_CLOSED) { + printf("%d\n", i); + } + + // This coroutine no longer needs the generator. + neco_gen_release(gen); + return 0; +} diff --git a/examples/select.c b/examples/select.c new file mode 100644 index 0000000..023a380 --- /dev/null +++ b/examples/select.c @@ -0,0 +1,56 @@ +#include +#include +#include "../neco.h" + +void coroutine1(int argc, void *argv[]) { + neco_chan *c1 = argv[0]; + neco_sleep(NECO_SECOND / 2); + char *msg = "one"; + neco_chan_send(c1, &msg); + neco_chan_release(c1); +} +void coroutine2(int argc, void *argv[]) { + neco_chan *c2 = argv[0]; + neco_sleep(NECO_SECOND / 2); + char *msg = "two"; + neco_chan_send(c2, &msg); + neco_chan_release(c2); +} + +int neco_main(int argc, char *argv[]) { + + // For our example we’ll select across two channels. + + neco_chan *c1; + neco_chan *c2; + + neco_chan_make(&c1, sizeof(char*), 0); + neco_chan_make(&c2, sizeof(char*), 0); + + // Each channel will receive a value after some amount of time, to + // simulate e.g. blocking RPC operations executing in concurrent coroutines. + neco_chan_retain(c1); + neco_start(coroutine1, 1, c1); + + neco_chan_retain(c2); + neco_start(coroutine2, 1, c2); + + // We’ll use neco_chan_select() to await both of these values + // simultaneously, printing each one as it arrives. + for (int i = 0; i < 2; i++) { + char *msg = NULL; + switch (neco_chan_select(2, c1, c2)) { + case 0: + neco_chan_case(c1, &msg); + break; + case 1: + neco_chan_case(c2, &msg); + break; + } + printf("received %s\n", msg); + } + + neco_chan_release(c1); + neco_chan_release(c2); + return 0; +} \ No newline at end of file diff --git a/examples/signals.c b/examples/signals.c new file mode 100644 index 0000000..717372b --- /dev/null +++ b/examples/signals.c @@ -0,0 +1,26 @@ +#include +#include +#include "../neco.h" + +int neco_main(int argc, char *argv[]) { + + // Set up this coroutine to wait for the SIGINT (Ctrl-C) or SIGQUIT + // (Ctrl-\) signals. + neco_signal_watch(SIGINT); + neco_signal_watch(SIGQUIT); + + printf("Waiting for Ctrl-C or Ctrl-\\ signals...\n"); + int sig = neco_signal_wait(); + if (sig == SIGINT) { + printf("\nReceived Ctrl-C\n"); + } else if (sig == SIGQUIT) { + printf("\nReceived Ctrl-\\\n"); + } + + // The neco_signal_unwatch can be used to when you no longer want to watch + // for a signal + neco_signal_unwatch(SIGINT); + neco_signal_unwatch(SIGQUIT); + + return 0; +} \ No newline at end of file diff --git a/neco.c b/neco.c new file mode 100644 index 0000000..322265c --- /dev/null +++ b/neco.c @@ -0,0 +1,8714 @@ +// https://github.com/tidwall/neco +// +// Copyright 2024 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +// +// Neco: Coroutine library for C + +/* +//////////////////////////////////////////////////////////////////////////////// +// Compilation options +//////////////////////////////////////////////////////////////////////////////// + +NECO_STACKSIZE // Size of each stack +NECO_DEFCAP // Default stack_group capacity +NECO_MAXCAP // Max stack_group capacity +NECO_GAPSIZE // Size of gap (guard) pages +NECO_SIGSTKSZ // Size of signal stack on main thread +NECO_BURST // Number of read attempts before waiting, def: disabled +NECO_MAXWORKER // Max number of worker threads, def: 2 + +// Additional options that activate features + +NECO_USEGUARDS // Use mprotect'ed guard pages +NECO_NOPAGERELEASE // Do not early release mmapped pages +NECO_NOSTACKFREELIST // Do not use a stack free list +NECO_NOPOOL // Do not use a coroutine and channel pools +NECO_USEHEAPSTACK // Allocate stacks on the heap using malloc +NECO_NOSIGNALS // Disable signal handling +NECO_USEROUNDROBINPIN // Use Round-robin when pinning background work +NECO_NOWORKERS // Disable all worker threads, work will run in coroutine +*/ + +// Windows and Webassembly have limited features. +#if defined(__EMSCRIPTEN__) || defined(__WIN32) +#define DEF_STACKSIZE 1048576 +#define DEF_DEFCAP 0 +#define DEF_MAXCAP 0 +#define DEF_GAPSIZE 0 +#define DEF_SIGSTKSZ 0 +#define DEF_BURST -1 +#define NECO_USEHEAPSTACK +#define NECO_NOSIGNALS +#define NECO_NOWORKER +#else +#define DEF_STACKSIZE 8388608 +#define DEF_DEFCAP 4 +#define DEF_MAXCAP 8192 +#define DEF_GAPSIZE 1048576 +#define DEF_SIGSTKSZ 1048576 +#define DEF_BURST -1 +#define DEF_MAXWORKER 2 +#define DEF_MAXRINGSIZE 32 +#endif + +#ifndef NECO_STACKSIZE +#define NECO_STACKSIZE DEF_STACKSIZE +#endif +#ifndef NECO_DEFCAP +#define NECO_DEFCAP DEF_DEFCAP +#endif +#ifndef NECO_MAXCAP +#define NECO_MAXCAP DEF_MAXCAP +#endif +#ifndef NECO_GAPSIZE +#define NECO_GAPSIZE DEF_GAPSIZE +#endif +#ifndef NECO_SIGSTKSZ +#define NECO_SIGSTKSZ DEF_SIGSTKSZ +#endif +#ifndef NECO_BURST +#define NECO_BURST DEF_BURST +#endif +#ifndef NECO_MAXWORKER +#define NECO_MAXWORKER DEF_MAXWORKER +#endif +#ifndef NECO_MAXRINGSIZE +#define NECO_MAXRINGSIZE DEF_MAXRINGSIZE +#endif + +#ifdef NECO_TESTING +#if NECO_BURST == -1 +#undef NECO_BURST +#define NECO_BURST 1 +#endif +#endif + +// The following is only needed when LLCO_NOASM or LLCO_STACKJMP is defined. +// This same block is duplicated in the llco.c block below. +#ifdef _FORTIFY_SOURCE +#define LLCO_FORTIFY_SOURCE _FORTIFY_SOURCE +// Disable __longjmp_chk validation so that we can jump between stacks. +#pragma push_macro("_FORTIFY_SOURCE") +#undef _FORTIFY_SOURCE +#include +#define _FORTIFY_SOURCE LLCO_FORTIFY_SOURCE +#undef LLCO_FORTIFY_SOURCE +#pragma pop_macro("_FORTIFY_SOURCE") +#endif + +#ifdef _WIN32 +#define _POSIX +#define __USE_MINGW_ALARM +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Embedded dependencies +// The deps/embed.sh command embeds all dependencies into this source file to +// create a single amalgamation file. +//////////////////////////////////////////////////////////////////////////////// +#ifdef NECO_NOAMALGA + +#include "deps/sco.h" +#include "deps/aat.h" +#include "deps/stack.h" + +#ifndef NECO_NOWORKER +#include "deps/worker.h" +#endif + +#else + +#define SCO_STATIC +#define STACK_STATIC + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +// BEGIN sco.c +// https://github.com/tidwall/sco +// +// Copyright 2023 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Coroutine scheduler + +#include +#include + +#ifndef SCO_STATIC +#include "sco.h" +#else +#define SCO_EXTERN static +#include +#include +struct sco_desc { + void *stack; + size_t stack_size; + void (*entry)(void *udata); + void (*cleanup)(void *stack, size_t stack_size, void *udata); + void *udata; +}; +struct sco_symbol { + void *cfa; // Canonical Frame Address + void *ip; // Instruction Pointer + const char *fname; // Pathname of shared object + void *fbase; // Base address of shared object + const char *sname; // Name of nearest symbol + void *saddr; // Address of nearest symbol +}; +#define SCO_MINSTACKSIZE 131072 +#endif + +#ifndef SCO_EXTERN +#define SCO_EXTERN +#endif + +//////////////////////////////////////////////////////////////////////////////// +// llco.c +//////////////////////////////////////////////////////////////////////////////// +#ifdef SCO_NOAMALGA + +#include "deps/llco.h" + +#else + +#define LLCO_STATIC + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +// BEGIN llco.c +// https://github.com/tidwall/llco +// +// Copyright (c) 2024 Joshua J Baker. +// This software is available as a choice of Public Domain or MIT-0. + +#ifdef _FORTIFY_SOURCE +#define LLCO_FORTIFY_SOURCE _FORTIFY_SOURCE +// Disable __longjmp_chk validation so that we can jump between stacks. +#pragma push_macro("_FORTIFY_SOURCE") +#undef _FORTIFY_SOURCE +#include +#define _FORTIFY_SOURCE LLCO_FORTIFY_SOURCE +#undef LLCO_FORTIFY_SOURCE +#pragma pop_macro("_FORTIFY_SOURCE") +#endif + +#ifndef LLCO_STATIC +#include "llco.h" +#else +#include +#include +#define LLCO_MINSTACKSIZE 16384 +#define LLCO_EXTERN static +struct llco_desc { + void *stack; + size_t stack_size; + void (*entry)(void *udata); + void (*cleanup)(void *stack, size_t stack_size, void *udata); + void *udata; +}; +struct llco_symbol { + void *cfa; + void *ip; + const char *fname; + void *fbase; + const char *sname; + void *saddr; +}; +#endif + +#include + +#ifdef LLCO_VALGRIND +#include +#endif + +#ifndef LLCO_EXTERN +#define LLCO_EXTERN +#endif + +#if defined(__GNUC__) +#ifdef noinline +#define LLCO_NOINLINE noinline +#else +#define LLCO_NOINLINE __attribute__ ((noinline)) +#endif +#ifdef noreturn +#define LLCO_NORETURN noreturn +#else +#define LLCO_NORETURN __attribute__ ((noreturn)) +#endif +#else +#define LLCO_NOINLINE +#define LLCO_NORETURN +#endif + +#if defined(_MSC_VER) +#define __thread __declspec(thread) +#endif + +static void llco_entry(void *arg); + +LLCO_NORETURN +static void llco_exit(void) { + _Exit(0); +} + +#ifdef LLCO_ASM +#error LLCO_ASM must not be defined +#endif + +// Passing the entry function into assembly requires casting the function +// pointer to an object pointer, which is forbidden in the ISO C spec but +// allowed in posix. Ignore the warning attributed to this requirement when +// the -pedantic compiler flag is provide. +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Below is various assembly code adapted from the Lua Coco [MIT] and Minicoro +// [MIT-0] projects by Mike Pall and Eduardo Bart respectively. +//////////////////////////////////////////////////////////////////////////////// + +/* +Lua Coco (coco.luajit.org) +Copyright (C) 2004-2016 Mike Pall. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +//////////////////////////////////////////////////////////////////////////////// +// ARM +//////////////////////////////////////////////////////////////////////////////// +#if defined(__ARM_EABI__) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,arm_eabi" + +struct llco_asmctx { +#ifndef __SOFTFP__ + void* f[16]; +#endif + void *d[4]; /* d8-d15 */ + void *r[4]; /* r4-r11 */ + void *lr; + void *sp; +}; + +void _llco_asm_entry(void); +int _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __llco_asm_switch\n" + "__llco_asm_switch:\n" +#else + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch #function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#endif +#ifndef __SOFTFP__ + " vstmia r0!, {d8-d15}\n" +#endif + " stmia r0, {r4-r11, lr}\n" + " str sp, [r0, #9*4]\n" +#ifndef __SOFTFP__ + " vldmia r1!, {d8-d15}\n" +#endif + " ldr sp, [r1, #9*4]\n" + " ldmia r1, {r4-r11, pc}\n" +#ifndef __APPLE__ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +#endif +); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __llco_asm_entry\n" + "__llco_asm_entry:\n" +#else + ".globl _llco_asm_entry\n" + ".type _llco_asm_entry #function\n" + ".hidden _llco_asm_entry\n" + "_llco_asm_entry:\n" +#endif + " mov r0, r4\n" + " mov ip, r5\n" + " mov lr, r6\n" + " bx ip\n" +#ifndef __APPLE__ + ".size _llco_asm_entry, .-_llco_asm_entry\n" +#endif +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, void* stack_base, + size_t stack_size, void *arg) +{ + ctx->d[0] = (void*)(arg); + ctx->d[1] = (void*)(llco_entry); + ctx->d[2] = (void*)(0xdeaddead); /* Dummy return address. */ + ctx->lr = (void*)(_llco_asm_entry); + ctx->sp = (void*)((size_t)stack_base + stack_size); +} + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// ARM 64-bit +//////////////////////////////////////////////////////////////////////////////// +#if defined(__aarch64__) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,aarch64" + +struct llco_asmctx { + void *x[12]; /* x19-x30 */ + void *sp; + void *lr; + void *d[8]; /* d8-d15 */ +}; + +void _llco_asm_entry(void); +int _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __llco_asm_switch\n" + "__llco_asm_switch:\n" +#else + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch #function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#endif + + " mov x10, sp\n" + " mov x11, x30\n" + " stp x19, x20, [x0, #(0*16)]\n" + " stp x21, x22, [x0, #(1*16)]\n" + " stp d8, d9, [x0, #(7*16)]\n" + " stp x23, x24, [x0, #(2*16)]\n" + " stp d10, d11, [x0, #(8*16)]\n" + " stp x25, x26, [x0, #(3*16)]\n" + " stp d12, d13, [x0, #(9*16)]\n" + " stp x27, x28, [x0, #(4*16)]\n" + " stp d14, d15, [x0, #(10*16)]\n" + " stp x29, x30, [x0, #(5*16)]\n" + " stp x10, x11, [x0, #(6*16)]\n" + " ldp x19, x20, [x1, #(0*16)]\n" + " ldp x21, x22, [x1, #(1*16)]\n" + " ldp d8, d9, [x1, #(7*16)]\n" + " ldp x23, x24, [x1, #(2*16)]\n" + " ldp d10, d11, [x1, #(8*16)]\n" + " ldp x25, x26, [x1, #(3*16)]\n" + " ldp d12, d13, [x1, #(9*16)]\n" + " ldp x27, x28, [x1, #(4*16)]\n" + " ldp d14, d15, [x1, #(10*16)]\n" + " ldp x29, x30, [x1, #(5*16)]\n" + " ldp x10, x11, [x1, #(6*16)]\n" + " mov sp, x10\n" + " br x11\n" +#ifndef __APPLE__ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +#endif +); + +__asm__( + ".text\n" +#ifdef __APPLE__ + ".globl __llco_asm_entry\n" + "__llco_asm_entry:\n" +#else + ".globl _llco_asm_entry\n" + ".type _llco_asm_entry #function\n" + ".hidden _llco_asm_entry\n" + "_llco_asm_entry:\n" +#endif + " mov x0, x19\n" + " mov x30, x21\n" + " br x20\n" +#ifndef __APPLE__ + ".size _llco_asm_entry, .-_llco_asm_entry\n" +#endif +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, void* stack_base, + size_t stack_size, void *arg) +{ + ctx->x[0] = (void*)(arg); + ctx->x[1] = (void*)(llco_entry); + ctx->x[2] = (void*)(0xdeaddeaddeaddead); /* Dummy return address. */ + ctx->sp = (void*)((size_t)stack_base + stack_size); + ctx->lr = (void*)(_llco_asm_entry); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// RISC-V (rv64/rv32) +//////////////////////////////////////////////////////////////////////////////// +#if defined(__riscv) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,riscv" + +struct llco_asmctx { + void* s[12]; /* s0-s11 */ + void* ra; + void* pc; + void* sp; +#ifdef __riscv_flen +#if __riscv_flen == 64 + double fs[12]; /* fs0-fs11 */ +#elif __riscv_flen == 32 + float fs[12]; /* fs0-fs11 */ +#endif +#endif /* __riscv_flen */ +}; + +void _llco_asm_entry(void); +int _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( + ".text\n" + ".globl _llco_asm_entry\n" + ".type _llco_asm_entry @function\n" + ".hidden _llco_asm_entry\n" + "_llco_asm_entry:\n" + " mv a0, s0\n" + " jr s1\n" + ".size _llco_asm_entry, .-_llco_asm_entry\n" +); + +__asm__( + ".text\n" + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch @function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#if __riscv_xlen == 64 + " sd s0, 0x00(a0)\n" + " sd s1, 0x08(a0)\n" + " sd s2, 0x10(a0)\n" + " sd s3, 0x18(a0)\n" + " sd s4, 0x20(a0)\n" + " sd s5, 0x28(a0)\n" + " sd s6, 0x30(a0)\n" + " sd s7, 0x38(a0)\n" + " sd s8, 0x40(a0)\n" + " sd s9, 0x48(a0)\n" + " sd s10, 0x50(a0)\n" + " sd s11, 0x58(a0)\n" + " sd ra, 0x60(a0)\n" + " sd ra, 0x68(a0)\n" /* pc */ + " sd sp, 0x70(a0)\n" +#ifdef __riscv_flen +#if __riscv_flen == 64 + " fsd fs0, 0x78(a0)\n" + " fsd fs1, 0x80(a0)\n" + " fsd fs2, 0x88(a0)\n" + " fsd fs3, 0x90(a0)\n" + " fsd fs4, 0x98(a0)\n" + " fsd fs5, 0xa0(a0)\n" + " fsd fs6, 0xa8(a0)\n" + " fsd fs7, 0xb0(a0)\n" + " fsd fs8, 0xb8(a0)\n" + " fsd fs9, 0xc0(a0)\n" + " fsd fs10, 0xc8(a0)\n" + " fsd fs11, 0xd0(a0)\n" + " fld fs0, 0x78(a1)\n" + " fld fs1, 0x80(a1)\n" + " fld fs2, 0x88(a1)\n" + " fld fs3, 0x90(a1)\n" + " fld fs4, 0x98(a1)\n" + " fld fs5, 0xa0(a1)\n" + " fld fs6, 0xa8(a1)\n" + " fld fs7, 0xb0(a1)\n" + " fld fs8, 0xb8(a1)\n" + " fld fs9, 0xc0(a1)\n" + " fld fs10, 0xc8(a1)\n" + " fld fs11, 0xd0(a1)\n" +#else +#error "Unsupported RISC-V FLEN" +#endif +#endif /* __riscv_flen */ + " ld s0, 0x00(a1)\n" + " ld s1, 0x08(a1)\n" + " ld s2, 0x10(a1)\n" + " ld s3, 0x18(a1)\n" + " ld s4, 0x20(a1)\n" + " ld s5, 0x28(a1)\n" + " ld s6, 0x30(a1)\n" + " ld s7, 0x38(a1)\n" + " ld s8, 0x40(a1)\n" + " ld s9, 0x48(a1)\n" + " ld s10, 0x50(a1)\n" + " ld s11, 0x58(a1)\n" + " ld ra, 0x60(a1)\n" + " ld a2, 0x68(a1)\n" /* pc */ + " ld sp, 0x70(a1)\n" + " jr a2\n" +#elif __riscv_xlen == 32 + " sw s0, 0x00(a0)\n" + " sw s1, 0x04(a0)\n" + " sw s2, 0x08(a0)\n" + " sw s3, 0x0c(a0)\n" + " sw s4, 0x10(a0)\n" + " sw s5, 0x14(a0)\n" + " sw s6, 0x18(a0)\n" + " sw s7, 0x1c(a0)\n" + " sw s8, 0x20(a0)\n" + " sw s9, 0x24(a0)\n" + " sw s10, 0x28(a0)\n" + " sw s11, 0x2c(a0)\n" + " sw ra, 0x30(a0)\n" + " sw ra, 0x34(a0)\n" /* pc */ + " sw sp, 0x38(a0)\n" +#ifdef __riscv_flen +#if __riscv_flen == 64 + " fsd fs0, 0x3c(a0)\n" + " fsd fs1, 0x44(a0)\n" + " fsd fs2, 0x4c(a0)\n" + " fsd fs3, 0x54(a0)\n" + " fsd fs4, 0x5c(a0)\n" + " fsd fs5, 0x64(a0)\n" + " fsd fs6, 0x6c(a0)\n" + " fsd fs7, 0x74(a0)\n" + " fsd fs8, 0x7c(a0)\n" + " fsd fs9, 0x84(a0)\n" + " fsd fs10, 0x8c(a0)\n" + " fsd fs11, 0x94(a0)\n" + " fld fs0, 0x3c(a1)\n" + " fld fs1, 0x44(a1)\n" + " fld fs2, 0x4c(a1)\n" + " fld fs3, 0x54(a1)\n" + " fld fs4, 0x5c(a1)\n" + " fld fs5, 0x64(a1)\n" + " fld fs6, 0x6c(a1)\n" + " fld fs7, 0x74(a1)\n" + " fld fs8, 0x7c(a1)\n" + " fld fs9, 0x84(a1)\n" + " fld fs10, 0x8c(a1)\n" + " fld fs11, 0x94(a1)\n" +#elif __riscv_flen == 32 + " fsw fs0, 0x3c(a0)\n" + " fsw fs1, 0x40(a0)\n" + " fsw fs2, 0x44(a0)\n" + " fsw fs3, 0x48(a0)\n" + " fsw fs4, 0x4c(a0)\n" + " fsw fs5, 0x50(a0)\n" + " fsw fs6, 0x54(a0)\n" + " fsw fs7, 0x58(a0)\n" + " fsw fs8, 0x5c(a0)\n" + " fsw fs9, 0x60(a0)\n" + " fsw fs10, 0x64(a0)\n" + " fsw fs11, 0x68(a0)\n" + " flw fs0, 0x3c(a1)\n" + " flw fs1, 0x40(a1)\n" + " flw fs2, 0x44(a1)\n" + " flw fs3, 0x48(a1)\n" + " flw fs4, 0x4c(a1)\n" + " flw fs5, 0x50(a1)\n" + " flw fs6, 0x54(a1)\n" + " flw fs7, 0x58(a1)\n" + " flw fs8, 0x5c(a1)\n" + " flw fs9, 0x60(a1)\n" + " flw fs10, 0x64(a1)\n" + " flw fs11, 0x68(a1)\n" +#else +#error "Unsupported RISC-V FLEN" +#endif +#endif /* __riscv_flen */ + " lw s0, 0x00(a1)\n" + " lw s1, 0x04(a1)\n" + " lw s2, 0x08(a1)\n" + " lw s3, 0x0c(a1)\n" + " lw s4, 0x10(a1)\n" + " lw s5, 0x14(a1)\n" + " lw s6, 0x18(a1)\n" + " lw s7, 0x1c(a1)\n" + " lw s8, 0x20(a1)\n" + " lw s9, 0x24(a1)\n" + " lw s10, 0x28(a1)\n" + " lw s11, 0x2c(a1)\n" + " lw ra, 0x30(a1)\n" + " lw a2, 0x34(a1)\n" /* pc */ + " lw sp, 0x38(a1)\n" + " jr a2\n" +#else +#error "Unsupported RISC-V XLEN" +#endif /* __riscv_xlen */ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, + void* stack_base, size_t stack_size, void *arg) +{ + ctx->s[0] = (void*)(arg); + ctx->s[1] = (void*)(llco_entry); + ctx->pc = (void*)(_llco_asm_entry); +#if __riscv_xlen == 64 + ctx->ra = (void*)(0xdeaddeaddeaddead); +#elif __riscv_xlen == 32 + ctx->ra = (void*)(0xdeaddead); +#endif + ctx->sp = (void*)((size_t)stack_base + stack_size); +} + +#endif // riscv + +//////////////////////////////////////////////////////////////////////////////// +// x86 +//////////////////////////////////////////////////////////////////////////////// +#if (defined(__i386) || defined(__i386__)) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,i386" + +struct llco_asmctx { + void *eip, *esp, *ebp, *ebx, *esi, *edi; +}; + +void _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( +#ifdef __DJGPP__ /* DOS compiler */ + "__llco_asm_switch:\n" +#else + ".text\n" + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch @function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#endif + " call 1f\n" + " 1:\n" + " popl %ecx\n" + " addl $(2f-1b), %ecx\n" + " movl 4(%esp), %eax\n" + " movl 8(%esp), %edx\n" + " movl %ecx, (%eax)\n" + " movl %esp, 4(%eax)\n" + " movl %ebp, 8(%eax)\n" + " movl %ebx, 12(%eax)\n" + " movl %esi, 16(%eax)\n" + " movl %edi, 20(%eax)\n" + " movl 20(%edx), %edi\n" + " movl 16(%edx), %esi\n" + " movl 12(%edx), %ebx\n" + " movl 8(%edx), %ebp\n" + " movl 4(%edx), %esp\n" + " jmp *(%edx)\n" + " 2:\n" + " ret\n" +#ifndef __DJGPP__ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +#endif +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, + void* stack_base, size_t stack_size, void *arg) +{ + void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - 16 - + 1*sizeof(size_t)); + stack_high_ptr[0] = (void*)(0xdeaddead); // Dummy return address. + stack_high_ptr[1] = (void*)(arg); + ctx->eip = (void*)(llco_entry); + ctx->esp = (void*)(stack_high_ptr); +} +#endif // __i386__ + +//////////////////////////////////////////////////////////////////////////////// +// x64 +//////////////////////////////////////////////////////////////////////////////// +#if (defined(__x86_64__) || defined(_M_X64)) && !defined(LLCO_NOASM) +#define LLCO_ASM +#define LLCO_READY +#define LLCO_METHOD "asm,x64" + +#ifdef _WIN32 + +struct llco_asmctx { + void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15, *rdi, *rsi; + void* xmm[20]; /* xmm6, xmm7, xmm8, xmm9, xmm10, xmm11, xmm12, xmm13, + xmm14, xmm15 */ + void* fiber_storage; + void* dealloc_stack; + void* stack_limit; + void* stack_base; +}; + +#if defined(__GNUC__) +#define LLCO_ASM_BLOB __attribute__((section(".text"))) +#elif defined(_MSC_VER) +#define LLCO_ASM_BLOB __declspec(allocate(".text")) +#pragma section(".text") +#endif + +LLCO_ASM_BLOB static unsigned char llco_wrap_main_code_entry[] = { + 0x4c,0x89,0xe9, // mov %r13,%rcx + 0x41,0xff,0xe4, // jmpq *%r12 + 0xc3, // retq + 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90 // nop +}; + +LLCO_ASM_BLOB static unsigned char llco_asm_switch_code[] = { + 0x48,0x8d,0x05,0x3e,0x01,0x00,0x00, // lea 0x13e(%rip),%rax + 0x48,0x89,0x01, // mov %rax,(%rcx) + 0x48,0x89,0x61,0x08, // mov %rsp,0x8(%rcx) + 0x48,0x89,0x69,0x10, // mov %rbp,0x10(%rcx) + 0x48,0x89,0x59,0x18, // mov %rbx,0x18(%rcx) + 0x4c,0x89,0x61,0x20, // mov %r12,0x20(%rcx) + 0x4c,0x89,0x69,0x28, // mov %r13,0x28(%rcx) + 0x4c,0x89,0x71,0x30, // mov %r14,0x30(%rcx) + 0x4c,0x89,0x79,0x38, // mov %r15,0x38(%rcx) + 0x48,0x89,0x79,0x40, // mov %rdi,0x40(%rcx) + 0x48,0x89,0x71,0x48, // mov %rsi,0x48(%rcx) + 0x0f,0x11,0x71,0x50, // movups %xmm6,0x50(%rcx) + 0x0f,0x11,0x79,0x60, // movups %xmm7,0x60(%rcx) + 0x44,0x0f,0x11,0x41,0x70, // movups %xmm8,0x70(%rcx) + 0x44,0x0f,0x11,0x89,0x80,0x00,0x00,0x00, // movups %xmm9,0x80(%rcx) + 0x44,0x0f,0x11,0x91,0x90,0x00,0x00,0x00, // movups %xmm10,0x90(%rcx) + 0x44,0x0f,0x11,0x99,0xa0,0x00,0x00,0x00, // movups %xmm11,0xa0(%rcx) + 0x44,0x0f,0x11,0xa1,0xb0,0x00,0x00,0x00, // movups %xmm12,0xb0(%rcx) + 0x44,0x0f,0x11,0xa9,0xc0,0x00,0x00,0x00, // movups %xmm13,0xc0(%rcx) + 0x44,0x0f,0x11,0xb1,0xd0,0x00,0x00,0x00, // movups %xmm14,0xd0(%rcx) + 0x44,0x0f,0x11,0xb9,0xe0,0x00,0x00,0x00, // movups %xmm15,0xe0(%rcx) + 0x65,0x4c,0x8b,0x14,0x25,0x30,0x00,0x00,0x00, // mov %gs:0x30,%r10 + 0x49,0x8b,0x42,0x20, // mov 0x20(%r10),%rax + 0x48,0x89,0x81,0xf0,0x00,0x00,0x00, // mov %rax,0xf0(%rcx) + 0x49,0x8b,0x82,0x78,0x14,0x00,0x00, // mov 0x1478(%r10),%rax + 0x48,0x89,0x81,0xf8,0x00,0x00,0x00, // mov %rax,0xf8(%rcx) + 0x49,0x8b,0x42,0x10, // mov 0x10(%r10),%rax + 0x48,0x89,0x81,0x00,0x01,0x00,0x00, // mov %rax,0x100(%rcx) + 0x49,0x8b,0x42,0x08, // mov 0x8(%r10),%rax + 0x48,0x89,0x81,0x08,0x01,0x00,0x00, // mov %rax,0x108(%rcx) + 0x48,0x8b,0x82,0x08,0x01,0x00,0x00, // mov 0x108(%rdx),%rax + 0x49,0x89,0x42,0x08, // mov %rax,0x8(%r10) + 0x48,0x8b,0x82,0x00,0x01, 0x00, 0x00, // mov 0x100(%rdx),%rax + 0x49,0x89,0x42,0x10, // mov %rax,0x10(%r10) + 0x48,0x8b,0x82,0xf8,0x00, 0x00, 0x00, // mov 0xf8(%rdx),%rax + 0x49,0x89,0x82,0x78,0x14, 0x00, 0x00, // mov %rax,0x1478(%r10) + 0x48,0x8b,0x82,0xf0,0x00, 0x00, 0x00, // mov 0xf0(%rdx),%rax + 0x49,0x89,0x42,0x20, // mov %rax,0x20(%r10) + 0x44,0x0f,0x10,0xba,0xe0,0x00,0x00,0x00, // movups 0xe0(%rdx),%xmm15 + 0x44,0x0f,0x10,0xb2,0xd0,0x00,0x00,0x00, // movups 0xd0(%rdx),%xmm14 + 0x44,0x0f,0x10,0xaa,0xc0,0x00,0x00,0x00, // movups 0xc0(%rdx),%xmm13 + 0x44,0x0f,0x10,0xa2,0xb0,0x00,0x00,0x00, // movups 0xb0(%rdx),%xmm12 + 0x44,0x0f,0x10,0x9a,0xa0,0x00,0x00,0x00, // movups 0xa0(%rdx),%xmm11 + 0x44,0x0f,0x10,0x92,0x90,0x00,0x00,0x00, // movups 0x90(%rdx),%xmm10 + 0x44,0x0f,0x10,0x8a,0x80,0x00,0x00,0x00, // movups 0x80(%rdx),%xmm9 + 0x44,0x0f,0x10,0x42,0x70, // movups 0x70(%rdx),%xmm8 + 0x0f,0x10,0x7a,0x60, // movups 0x60(%rdx),%xmm7 + 0x0f,0x10,0x72,0x50, // movups 0x50(%rdx),%xmm6 + 0x48,0x8b,0x72,0x48, // mov 0x48(%rdx),%rsi + 0x48,0x8b,0x7a,0x40, // mov 0x40(%rdx),%rdi + 0x4c,0x8b,0x7a,0x38, // mov 0x38(%rdx),%r15 + 0x4c,0x8b,0x72,0x30, // mov 0x30(%rdx),%r14 + 0x4c,0x8b,0x6a,0x28, // mov 0x28(%rdx),%r13 + 0x4c,0x8b,0x62,0x20, // mov 0x20(%rdx),%r12 + 0x48,0x8b,0x5a,0x18, // mov 0x18(%rdx),%rbx + 0x48,0x8b,0x6a,0x10, // mov 0x10(%rdx),%rbp + 0x48,0x8b,0x62,0x08, // mov 0x8(%rdx),%rsp + 0xff,0x22, // jmpq *(%rdx) + 0xc3, // retq + 0x90,0x90,0x90,0x90,0x90,0x90,0x90,0x90, // nop + 0x90,0x90, // nop +}; + +void (*_llco_asm_entry)(void) = + (void(*)(void))(void*)llco_wrap_main_code_entry; +void (*_llco_asm_switch)(struct llco_asmctx *from, + struct llco_asmctx *to) = (void(*)(struct llco_asmctx *from, + struct llco_asmctx *to))(void*)llco_asm_switch_code; + +static void llco_asmctx_make(struct llco_asmctx *ctx, + void* stack_base, size_t stack_size, void *arg) +{ + stack_size = stack_size - 32; // Reserve 32 bytes for the shadow space. + void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - + sizeof(size_t)); + stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); // Dummy return address. + ctx->rip = (void*)(_llco_asm_entry); + ctx->rsp = (void*)(stack_high_ptr); + ctx->r12 = (void*)(llco_entry); + ctx->r13 = (void*)(arg); + void* stack_top = (void*)((size_t)stack_base + stack_size); + ctx->stack_base = stack_top; + ctx->stack_limit = stack_base; + ctx->dealloc_stack = stack_base; +} + +#else + +struct llco_asmctx { + void *rip, *rsp, *rbp, *rbx, *r12, *r13, *r14, *r15; +}; + +void _llco_asm_entry(void); +int _llco_asm_switch(struct llco_asmctx *from, struct llco_asmctx *to); + +__asm__( + ".text\n" +#ifdef __MACH__ /* Mac OS X assembler */ + ".globl __llco_asm_entry\n" + "__llco_asm_entry:\n" +#else /* Linux assembler */ + ".globl _llco_asm_entry\n" + ".type _llco_asm_entry @function\n" + ".hidden _llco_asm_entry\n" + "_llco_asm_entry:\n" +#endif + " movq %r13, %rdi\n" + " jmpq *%r12\n" +#ifndef __MACH__ + ".size _llco_asm_entry, .-_llco_asm_entry\n" +#endif +); + +__asm__( + ".text\n" +#ifdef __MACH__ /* Mac OS assembler */ + ".globl __llco_asm_switch\n" + "__llco_asm_switch:\n" +#else /* Linux assembler */ + ".globl _llco_asm_switch\n" + ".type _llco_asm_switch @function\n" + ".hidden _llco_asm_switch\n" + "_llco_asm_switch:\n" +#endif + " leaq 0x3d(%rip), %rax\n" + " movq %rax, (%rdi)\n" + " movq %rsp, 8(%rdi)\n" + " movq %rbp, 16(%rdi)\n" + " movq %rbx, 24(%rdi)\n" + " movq %r12, 32(%rdi)\n" + " movq %r13, 40(%rdi)\n" + " movq %r14, 48(%rdi)\n" + " movq %r15, 56(%rdi)\n" + " movq 56(%rsi), %r15\n" + " movq 48(%rsi), %r14\n" + " movq 40(%rsi), %r13\n" + " movq 32(%rsi), %r12\n" + " movq 24(%rsi), %rbx\n" + " movq 16(%rsi), %rbp\n" + " movq 8(%rsi), %rsp\n" + " jmpq *(%rsi)\n" + " ret\n" +#ifndef __MACH__ + ".size _llco_asm_switch, .-_llco_asm_switch\n" +#endif +); + +static void llco_asmctx_make(struct llco_asmctx *ctx, + void* stack_base, size_t stack_size, void *arg) +{ + // Reserve 128 bytes for the Red Zone space (System V AMD64 ABI). + stack_size = stack_size - 128; + void** stack_high_ptr = (void**)((size_t)stack_base + stack_size - + sizeof(size_t)); + stack_high_ptr[0] = (void*)(0xdeaddeaddeaddead); // Dummy return address. + ctx->rip = (void*)(_llco_asm_entry); + ctx->rsp = (void*)(stack_high_ptr); + ctx->r12 = (void*)(llco_entry); + ctx->r13 = (void*)(arg); +} + +#endif +#endif // x64 + +// --- END ASM Code --- // + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +//////////////////////////////////////////////////////////////////////////////// +// ASM with stackjmp activated +//////////////////////////////////////////////////////////////////////////////// +#if defined(LLCO_READY) && defined(LLCO_STACKJMP) +LLCO_NOINLINE LLCO_NORETURN +static void llco_stackjmp(void *stack, size_t stack_size, + void(*entry)(void *arg)) +{ + struct llco_asmctx ctx = { 0 }; + llco_asmctx_make(&ctx, stack, stack_size, 0); + struct llco_asmctx ctx0 = { 0 }; + _llco_asm_switch(&ctx0, &ctx); + llco_exit(); +} +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Windows Fibers +//////////////////////////////////////////////////////////////////////////////// +#if defined(_WIN32) && !defined(LLCO_READY) +#define LLCO_WINDOWS +#define LLCO_READY +#define LLCO_METHOD "fibers,windows" + +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0400 +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include + +#error Windows fibers unsupported + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Webassembly Fibers +//////////////////////////////////////////////////////////////////////////////// +#if defined(__EMSCRIPTEN__) && !defined(LLCO_READY) +#define LLCO_WASM +#define LLCO_READY +#define LLCO_METHOD "fibers,emscripten" + +#include +#include + +#ifndef LLCO_ASYNCIFY_STACK_SIZE +#define LLCO_ASYNCIFY_STACK_SIZE 4096 +#endif + +static __thread char llco_main_stack[LLCO_ASYNCIFY_STACK_SIZE]; + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Ucontext +//////////////////////////////////////////////////////////////////////////////// +#if !defined(LLCO_READY) +#define LLCO_UCONTEXT +#define LLCO_READY +#define LLCO_METHOD "ucontext" +#ifndef LLCO_STACKJMP +#define LLCO_STACKJMP +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif +#define _XOPEN_SOURCE +#endif +#include + +static __thread ucontext_t stackjmp_ucallee; +static __thread int stackjmp_ucallee_gotten = 0; + +#if defined(__APPLE__) && defined(__aarch64__) && !defined(LLCO_NOSTACKADJUST) +// Here we ensure that the initial context switch will *not* page the +// entire stack into process memory before executing the entry point +// function. Which is a behavior that can be observed on Mac OS with +// Apple Silicon. This "trick" can be optionally removed at the expense +// of slower initial jumping into large stacks. +enum llco_stack_grows { DOWNWARDS, UPWARDS }; + +static enum llco_stack_grows llco_stack_grows0(int *addr0) { + int addr1; + return addr0 < &addr1 ? UPWARDS : DOWNWARDS; +} + +static enum llco_stack_grows llco_stack_grows(void) { + int addr0; + return llco_stack_grows0(&addr0); +} + +static void llco_adjust_ucontext_stack(ucontext_t *ucp) { + if (llco_stack_grows() == UPWARDS) { + ucp->uc_stack.ss_sp = (char*)ucp->uc_stack.ss_sp+ucp->uc_stack.ss_size; + ucp->uc_stack.ss_size = 0; + } +} +#else +#define llco_adjust_ucontext_stack(ucp) +#endif + +// Ucontext always uses stackjmp with setjmp/longjmp, instead of swapcontext +// becuase it's much faster. +LLCO_NOINLINE LLCO_NORETURN +static void llco_stackjmp(void *stack, size_t stack_size, + void(*entry)(void *arg)) +{ + if (!stackjmp_ucallee_gotten) { + stackjmp_ucallee_gotten = 1; + getcontext(&stackjmp_ucallee); + } + stackjmp_ucallee.uc_stack.ss_sp = stack; + stackjmp_ucallee.uc_stack.ss_size = stack_size; + llco_adjust_ucontext_stack(&stackjmp_ucallee); + makecontext(&stackjmp_ucallee, (void(*)(void))entry, 0); + setcontext(&stackjmp_ucallee); + llco_exit(); +} + +#endif // Ucontext + +#if defined(LLCO_STACKJMP) +#include +#ifdef _WIN32 +// For reasons outside of my understanding, Windows does not allow for jumping +// between stacks using the setjmp/longjmp mechanism. +#error Windows stackjmp not supported +#endif +#endif + +//////////////////////////////////////////////////////////////////////////////// +// llco switching code +//////////////////////////////////////////////////////////////////////////////// + +#include + +struct llco { + struct llco_desc desc; +#if defined(LLCO_STACKJMP) + jmp_buf buf; +#elif defined(LLCO_ASM) + struct llco_asmctx ctx; +#elif defined(LLCO_WASM) + emscripten_fiber_t fiber; +#elif defined(LLCO_WINDOWS) + LPVOID fiber; +#endif +#ifdef LLCO_VALGRIND + int valgrind_stack_id; +#endif +#if defined(__GNUC__) + void *uw_stop_ip; // record of the last unwind ip. +#endif +}; + +#ifdef LLCO_VALGRIND +static __thread unsigned int llco_valgrind_stack_id = 0; +static __thread unsigned int llco_cleanup_valgrind_stack_id = 0; +#endif + +static __thread struct llco llco_thread = { 0 }; +static __thread struct llco *llco_cur = NULL; +static __thread struct llco_desc llco_desc; +static __thread volatile bool llco_cleanup_needed = false; +static __thread volatile struct llco_desc llco_cleanup_desc; +static __thread volatile bool llco_cleanup_active = false; + +#define llco_cleanup_guard() { \ + if (llco_cleanup_active) { \ + fprintf(stderr, "%s not available during cleanup\n", __func__); \ + abort(); \ + } \ +} + +static void llco_cleanup_last(void) { + if (llco_cleanup_needed) { + if (llco_cleanup_desc.cleanup) { + llco_cleanup_active = true; +#ifdef LLCO_VALGRIND + VALGRIND_STACK_DEREGISTER(llco_cleanup_valgrind_stack_id); +#endif + llco_cleanup_desc.cleanup(llco_cleanup_desc.stack, + llco_cleanup_desc.stack_size, llco_cleanup_desc.udata); + llco_cleanup_active = false; + } + llco_cleanup_needed = false; + } +} + +LLCO_NOINLINE +static void llco_entry_wrap(void *arg) { + llco_cleanup_last(); +#if defined(LLCO_WASM) + llco_cur = arg; + llco_cur->desc = llco_desc; +#else + (void)arg; + struct llco self = { .desc = llco_desc }; + llco_cur = &self; +#endif +#ifdef LLCO_VALGRIND + llco_cur->valgrind_stack_id = llco_valgrind_stack_id; +#endif +#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) + llco_cur->uw_stop_ip = __builtin_return_address(0); +#endif + llco_cur->desc.entry(llco_cur->desc.udata); +} + + +LLCO_NOINLINE LLCO_NORETURN +static void llco_entry(void *arg) { + llco_entry_wrap(arg); + llco_exit(); +} + +LLCO_NOINLINE +static void llco_switch1(struct llco *from, struct llco *to, + void *stack, size_t stack_size) +{ +#ifdef LLCO_VALGRIND + llco_valgrind_stack_id = VALGRIND_STACK_REGISTER(stack, stack + stack_size); +#endif +#if defined(LLCO_STACKJMP) + if (to) { + if (!_setjmp(from->buf)) { + _longjmp(to->buf, 1); + } + } else { + if (!_setjmp(from->buf)) { + llco_stackjmp(stack, stack_size, llco_entry); + } + } +#elif defined(LLCO_ASM) + if (to) { + _llco_asm_switch(&from->ctx, &to->ctx); + } else { + struct llco_asmctx ctx = { 0 }; + llco_asmctx_make(&ctx, stack, stack_size, 0); + _llco_asm_switch(&from->ctx, &ctx); + } +#elif defined(LLCO_WASM) + if (to) { + emscripten_fiber_swap(&from->fiber, &to->fiber); + } else { + if (from == &llco_thread) { + emscripten_fiber_init_from_current_context(&from->fiber, + llco_main_stack, LLCO_ASYNCIFY_STACK_SIZE); + } + stack_size -= LLCO_ASYNCIFY_STACK_SIZE; + char *astack = ((char*)stack) + stack_size; + size_t astack_size = LLCO_ASYNCIFY_STACK_SIZE - sizeof(struct llco); + struct llco *self = (void*)(astack + astack_size); + memset(self, 0, sizeof(struct llco)); + emscripten_fiber_init(&self->fiber, llco_entry, + self, stack, stack_size, astack, astack_size); + emscripten_fiber_swap(&from->fiber, &self->fiber); + } +#elif defined(LLCO_WINDOWS) + // Unsupported +#endif +} + +static void llco_switch0(struct llco_desc *desc, struct llco *co, + bool final) +{ + struct llco *from = llco_cur ? llco_cur : &llco_thread; + struct llco *to = desc ? NULL : co ? co : &llco_thread; + if (from != to) { + if (final) { + llco_cleanup_needed = true; + llco_cleanup_desc = from->desc; +#ifdef LLCO_VALGRIND + llco_cleanup_valgrind_stack_id = from->valgrind_stack_id; +#endif + } + if (desc) { + llco_desc = *desc; + llco_switch1(from, 0, desc->stack, desc->stack_size); + } else { + llco_cur = to; + llco_switch1(from, to, 0, 0); + } + llco_cleanup_last(); + } +} + + + +//////////////////////////////////////////////////////////////////////////////// +// Exported methods +//////////////////////////////////////////////////////////////////////////////// + +// Start a new coroutine. +LLCO_EXTERN +void llco_start(struct llco_desc *desc, bool final) { + if (!desc || desc->stack_size < LLCO_MINSTACKSIZE) { + fprintf(stderr, "stack too small\n"); + abort(); + } + llco_cleanup_guard(); + llco_switch0(desc, 0, final); +} + +// Switch to another coroutine. +LLCO_EXTERN +void llco_switch(struct llco *co, bool final) { +#if defined(LLCO_ASM) + // fast track context switch. Saves a few nanoseconds by checking the + // exception condition first. + if (!llco_cleanup_active && llco_cur && co && llco_cur != co && !final) { + struct llco *from = llco_cur; + llco_cur = co; + _llco_asm_switch(&from->ctx, &co->ctx); + llco_cleanup_last(); + return; + } +#endif + llco_cleanup_guard(); + llco_switch0(0, co, final); +} + +// Return the current coroutine or NULL if not currently running in a +// coroutine. +LLCO_EXTERN +struct llco *llco_current(void) { + llco_cleanup_guard(); + return llco_cur == &llco_thread ? 0 : llco_cur; +} + +// Returns a string that indicates which coroutine method is being used by +// the program. Such as "asm" or "ucontext", etc. +LLCO_EXTERN +const char *llco_method(void *caps) { + (void)caps; + return LLCO_METHOD +#ifdef LLCO_STACKJMP + ",stackjmp" +#endif + ; +} + +#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) && !defined(_WIN32) && \ + !defined(LLCO_NOUNWIND) + +#include +#include +#include + +struct llco_dlinfo { + const char *dli_fname; /* Pathname of shared object */ + void *dli_fbase; /* Base address of shared object */ + const char *dli_sname; /* Name of nearest symbol */ + void *dli_saddr; /* Address of nearest symbol */ +}; + +#ifdef __linux__ +int dladdr(const void *, void *); +#endif + +static void llco_getsymbol(struct _Unwind_Context *uwc, + struct llco_symbol *sym) +{ + memset(sym, 0, sizeof(struct llco_symbol)); + sym->cfa = (void*)_Unwind_GetCFA(uwc); + int ip_before; /* unused */ + sym->ip = (void*)_Unwind_GetIPInfo(uwc, &ip_before); + struct llco_dlinfo dlinfo = { 0 }; + if (sym->ip && dladdr(sym->ip, (void*)&dlinfo)) { + sym->fname = dlinfo.dli_fname; + sym->fbase = dlinfo.dli_fbase; + sym->sname = dlinfo.dli_sname; + sym->saddr = dlinfo.dli_saddr; + } +} + +struct llco_unwind_context { + void *udata; + void *start_ip; + bool started; + int nsymbols; + int nsymbols_actual; + struct llco_symbol last; + bool (*func)(struct llco_symbol *, void *); + void *unwind_addr; +}; + +static _Unwind_Reason_Code llco_func(struct _Unwind_Context *uwc, void *ptr) { + struct llco_unwind_context *ctx = ptr; + + struct llco *cur = llco_current(); + if (cur && !cur->uw_stop_ip) { + return _URC_END_OF_STACK; + } + struct llco_symbol sym; + llco_getsymbol(uwc, &sym); + if (ctx->start_ip && !ctx->started && sym.ip != ctx->start_ip) { + return _URC_NO_REASON; + } + ctx->started = true; + if (!sym.ip || (cur && sym.ip == cur->uw_stop_ip)) { + return _URC_END_OF_STACK; + } + ctx->nsymbols++; + if (!cur) { + ctx->nsymbols_actual++; + if (ctx->func && !ctx->func(&sym, ctx->udata)) { + return _URC_END_OF_STACK; + } + } else { + if (ctx->nsymbols > 1) { + ctx->nsymbols_actual++; + if (ctx->func && !ctx->func(&ctx->last, ctx->udata)) { + return _URC_END_OF_STACK; + } + } + ctx->last = sym; + } + return _URC_NO_REASON; +} + +LLCO_EXTERN +int llco_unwind(bool(*func)(struct llco_symbol *sym, void *udata), void *udata){ + struct llco_unwind_context ctx = { +#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) + .start_ip = __builtin_return_address(0), +#endif + .func = func, + .udata = udata + }; + _Unwind_Backtrace(llco_func, &ctx); + return ctx.nsymbols_actual; +} + +#else + +LLCO_EXTERN +int llco_unwind(bool(*func)(struct llco_symbol *sym, void *udata), void *udata){ + (void)func; (void)udata; + /* Unsupported */ + return 0; +} + +#endif +// END llco.c + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__linux__) +#include +static void sched_yield0(void) { + sched_yield(); +} +#else +#define sched_yield0() +#endif + +//////////////////////////////////////////////////////////////////////////////// +// aat.h +//////////////////////////////////////////////////////////////////////////////// +#ifdef SCO_NOAMALGA + +#include "deps/aat.h" + +#else + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +// BEGIN aat.h +// https://github.com/tidwall/aatree +// +// Copyright 2023 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Single header file for generating aat binary search trees. + +#ifndef AAT_H +#define AAT_H + +#define AAT_DEF(specifiers, prefix, type) \ +specifiers type *prefix##_insert(type **root, type *item); \ +specifiers type *prefix##_delete(type **root, type *key); \ +specifiers type *prefix##_search(type **root, type *key); \ +specifiers type *prefix##_delete_first(type **root); \ +specifiers type *prefix##_delete_last(type **root); \ +specifiers type *prefix##_first(type **root); \ +specifiers type *prefix##_last(type **root); \ +specifiers type *prefix##_iter(type **root, type *key); \ +specifiers type *prefix##_prev(type **root, type *item); \ +specifiers type *prefix##_next(type **root, type *item); \ + +#define AAT_FIELDS(type, left, right, level) \ +type *left; \ +type *right; \ +int level; \ + +#define AAT_IMPL(prefix, type, left, right, level, compare) \ +static void prefix##_clear(type *node) { \ + if (node) { \ + node->left = 0; \ + node->right = 0; \ + node->level = 0; \ + } \ +} \ + \ +static type *prefix##_skew(type *node) { \ + if (node && node->left && \ + node->left->level == node->level) \ + { \ + type *left_node = node->left; \ + node->left = left_node->right; \ + left_node->right = node; \ + node = left_node; \ + } \ + return node; \ +} \ + \ +static type *prefix##_split(type *node) { \ + if (node && node->right && node->right->right && \ + node->right->right->level == node->level) \ + { \ + type *right_node = node->right; \ + node->right = right_node->left; \ + right_node->left = node; \ + right_node->level++; \ + node = right_node; \ + } \ + return node; \ +} \ + \ +static type *prefix##_insert0(type *node, type *item, type **replaced) { \ + if (!node) { \ + item->left = 0; \ + item->right = 0; \ + item->level = 1; \ + node = item; \ + } else { \ + int cmp = compare(item, node); \ + if (cmp < 0) { \ + node->left = prefix##_insert0(node->left, item, replaced); \ + } else if (cmp > 0) { \ + node->right = prefix##_insert0(node->right, item, replaced); \ + } else { \ + *replaced = node; \ + item->left = node->left; \ + item->right = node->right; \ + item->level = node->level; \ + node = item; \ + } \ + } \ + node = prefix##_skew(node); \ + node = prefix##_split(node); \ + return node; \ +} \ + \ +type *prefix##_insert(type **root, type *item) { \ + type *replaced = 0; \ + *root = prefix##_insert0(*root, item, &replaced); \ + if (replaced != item) { \ + prefix##_clear(replaced); \ + } \ + return replaced; \ +} \ + \ +static type *prefix##_decrease_level(type *node) { \ + if (node->left || node->right) { \ + int new_level = 0; \ + if (node->left && node->right) { \ + if (node->left->level < node->right->level) { \ + new_level = node->left->level; \ + } else { \ + new_level = node->right->level; \ + } \ + } \ + new_level++; \ + if (new_level < node->level) { \ + node->level = new_level; \ + if (node->right && new_level < node->right->level) { \ + node->right->level = new_level; \ + } \ + } \ + } \ + return node; \ +} \ + \ +static type *prefix##_delete_fixup(type *node) { \ + node = prefix##_decrease_level(node); \ + node = prefix##_skew(node); \ + node->right = prefix##_skew(node->right); \ + if (node->right && node->right->right) { \ + node->right->right = prefix##_skew(node->right->right); \ + } \ + node = prefix##_split(node); \ + node->right = prefix##_split(node->right); \ + return node; \ +} \ + \ +static type *prefix##_delete_first0(type *node, \ + type **deleted) \ +{ \ + if (node) { \ + if (!node->left) { \ + *deleted = node; \ + if (node->right) { \ + node = node->right; \ + } else { \ + node = 0; \ + } \ + } else { \ + node->left = prefix##_delete_first0(node->left, deleted); \ + node = prefix##_delete_fixup(node); \ + } \ + } \ + return node; \ +} \ + \ +static type *prefix##_delete_last0(type *node, \ + type **deleted) \ +{ \ + if (node) { \ + if (!node->right) { \ + *deleted = node; \ + if (node->left) { \ + node = node->left; \ + } else { \ + node = 0; \ + } \ + } else { \ + node->right = prefix##_delete_last0(node->right, deleted); \ + node = prefix##_delete_fixup(node); \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_delete_first(type **root) { \ + type *deleted = 0; \ + *root = prefix##_delete_first0(*root, &deleted); \ + prefix##_clear(deleted); \ + return deleted; \ +} \ + \ +type *prefix##_delete_last(type **root) { \ + type *deleted = 0; \ + *root = prefix##_delete_last0(*root, &deleted); \ + prefix##_clear(deleted); \ + return deleted; \ +} \ + \ +static type *prefix##_delete0(type *node, \ + type *key, type **deleted) \ +{ \ + if (node) { \ + int cmp = compare(key, node); \ + if (cmp < 0) { \ + node->left = prefix##_delete0(node->left, key, deleted); \ + } else if (cmp > 0) { \ + node->right = prefix##_delete0(node->right, key, deleted); \ + } else { \ + *deleted = node; \ + if (!node->left && !node->right) { \ + node = 0; \ + } else { \ + type *leaf_deleted = 0; \ + if (!node->left) { \ + node->right = prefix##_delete_first0(node->right, \ + &leaf_deleted); \ + } else { \ + node->left = prefix##_delete_last0(node->left, \ + &leaf_deleted); \ + } \ + leaf_deleted->left = node->left; \ + leaf_deleted->right = node->right; \ + leaf_deleted->level = node->level; \ + node = leaf_deleted; \ + } \ + } \ + if (node) { \ + node = prefix##_delete_fixup(node); \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_delete(type **root, type *key) { \ + type *deleted = 0; \ + *root = prefix##_delete0(*root, key, &deleted); \ + prefix##_clear(deleted); \ + return deleted; \ +} \ + \ +type *prefix##_search(type **root, type *key) { \ + type *found = 0; \ + type *node = *root; \ + while (node) { \ + int cmp = compare(key, node); \ + if (cmp < 0) { \ + node = node->left; \ + } else if (cmp > 0) { \ + node = node->right; \ + } else { \ + found = node; \ + node = 0; \ + } \ + } \ + return found; \ +} \ + \ +type *prefix##_first(type **root) { \ + type *node = *root; \ + if (node) { \ + while (node->left) { \ + node = node->left; \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_last(type **root) { \ + type *node = *root; \ + if (node) { \ + while (node->right) { \ + node = node->right; \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_iter(type **root, type *key) { \ + type *found = 0; \ + type *node = *root; \ + while (node) { \ + int cmp = compare(key, node); \ + if (cmp < 0) { \ + found = node; \ + node = node->left; \ + } else if (cmp > 0) { \ + node = node->right; \ + } else { \ + found = node; \ + node = 0; \ + } \ + } \ + return found; \ +} \ + \ +static type *prefix##_parent(type **root, \ + type *item) \ +{ \ + type *parent = 0; \ + type *node = *root; \ + while (node) { \ + int cmp = compare(item, node); \ + if (cmp < 0) { \ + parent = node; \ + node = node->left; \ + } else if (cmp > 0) { \ + parent = node; \ + node = node->right; \ + } else { \ + node = 0; \ + } \ + } \ + return parent; \ +} \ + \ +type *prefix##_next(type **root, type *node) { \ + if (node) { \ + if (node->right) { \ + node = node->right; \ + while (node->left) { \ + node = node->left; \ + } \ + } else { \ + type *parent = prefix##_parent(root, node); \ + while (parent && parent->left != node) { \ + node = parent; \ + parent = prefix##_parent(root, parent); \ + } \ + node = parent; \ + } \ + } \ + return node; \ +} \ + \ +type *prefix##_prev(type **root, type *node) { \ + if (node) { \ + if (node->left) { \ + node = node->left; \ + while (node->right) { \ + node = node->right; \ + } \ + } else { \ + type *parent = prefix##_parent(root, node); \ + while (parent && parent->right != node) { \ + node = parent; \ + parent = prefix##_parent(root, parent); \ + } \ + node = parent; \ + } \ + } \ + return node; \ +} \ + +#endif // AAT_H +// END aat.h + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif + + +//////////////////////////////////////////////////////////////////////////////// +// Platform independent code below +//////////////////////////////////////////////////////////////////////////////// + +struct sco_link { + struct sco *prev; + struct sco *next; +}; + +struct sco { + union { + // Linked list + struct { + struct sco *prev; + struct sco *next; + }; + // Binary tree (AA-tree) + struct { + AAT_FIELDS(struct sco, left, right, level) + }; + }; + int64_t id; + void *udata; + struct llco *llco; +}; + +static int sco_compare(struct sco *a, struct sco *b) { + return a->id < b->id ? -1: a->id > b->id; +} + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +AAT_DEF(static, sco_aat, struct sco) +AAT_IMPL(sco_aat, struct sco, left, right, level, sco_compare) + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +// https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html +// hash u64 using mix13 +static uint64_t sco_mix13(uint64_t key) { + key ^= (key >> 30); + key *= UINT64_C(0xbf58476d1ce4e5b9); + key ^= (key >> 27); + key *= UINT64_C(0x94d049bb133111eb); + key ^= (key >> 31); + return key; +} + + +//////////////////////////////////////////////////////////////////////////////// +// sco_map - A hashmap-style structure that stores sco types using multiple +// binary search trees (aa-tree) in hashed shards. This allows for the map to +// grow evenly, without allocations, and performing much faster than using a +// single BST. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef SCO_NSHARDS +#define SCO_NSHARDS 512 +#endif + +struct sco_map { + struct sco *roots[SCO_NSHARDS]; + int count; +}; + +#define scp_map_getaat(sco) \ + (&map->roots[sco_mix13((sco)->id) & (SCO_NSHARDS-1)]) + +static struct sco *sco_map_insert(struct sco_map *map, struct sco *sco) { + struct sco *prev = sco_aat_insert(scp_map_getaat(sco), sco); + if (!prev) { + map->count++; + } + return prev; +} + +static struct sco *sco_map_delete(struct sco_map *map, struct sco *key){ + struct sco *prev = sco_aat_delete(scp_map_getaat(key), key); + if (prev) { + map->count--; + } + return prev; +} + +struct sco_list { + struct sco_link head; + struct sco_link tail; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Global and thread-local variables. +//////////////////////////////////////////////////////////////////////////////// + +static __thread bool sco_initialized = false; +static __thread size_t sco_nrunners = 0; +static __thread struct sco_list sco_runners = { 0 }; +static __thread size_t sco_nyielders = 0; +static __thread struct sco_list sco_yielders = { 0 }; +static __thread struct sco *sco_cur = NULL; +static __thread struct sco_map sco_paused = { 0 }; +static __thread size_t sco_npaused = 0; +static __thread bool sco_exit_to_main_requested = false; +static __thread void(*sco_user_entry)(void *udata); + +static atomic_int_fast64_t sco_next_id = 0; +static atomic_bool sco_locker = 0; +static struct sco_map sco_detached = { 0 }; +static size_t sco_ndetached = 0; + +static void sco_lock(void) { + bool expected = false; + while(!atomic_compare_exchange_weak(&sco_locker, &expected, true)) { + expected = false; + sched_yield0(); + } +} + +static void sco_unlock(void) { + atomic_store(&sco_locker, false); +} + +static void sco_list_init(struct sco_list *list) { + list->head.prev = NULL; + list->head.next = (struct sco*)&list->tail; + list->tail.prev = (struct sco*)&list->head; + list->tail.next = NULL; +} + +// Remove the coroutine from the runners or yielders list. +static void sco_remove_from_list(struct sco *co) { + co->prev->next = co->next; + co->next->prev = co->prev; + co->next = co; + co->prev = co; +} + +static void sco_init(void) { + if (!sco_initialized) { + sco_list_init(&sco_runners); + sco_list_init(&sco_yielders); + sco_initialized = true; + } +} + +static struct sco *sco_list_pop_front(struct sco_list *list) { + struct sco *co = NULL; + if (list->head.next != (struct sco*)&list->tail) { + co = list->head.next; + sco_remove_from_list(co); + } + return co; +} + +static void sco_list_push_back(struct sco_list *list, struct sco *co) { + sco_remove_from_list(co); + list->tail.prev->next = co; + co->prev = list->tail.prev; + co->next = (struct sco*)&list->tail; + list->tail.prev = co; +} + +static void sco_return_to_main(bool final) { + sco_cur = NULL; + sco_exit_to_main_requested = false; + llco_switch(0, final); +} + +static void sco_switch(bool resumed_from_main, bool final) { + if (sco_nrunners == 0) { + // No more runners. + if (sco_nyielders == 0 || sco_exit_to_main_requested || + (!resumed_from_main && sco_npaused > 0)) { + sco_return_to_main(final); + return; + } + // Convert the yielders to runners + sco_runners.head.next = sco_yielders.head.next; + sco_runners.head.next->prev = (struct sco*)&sco_runners.head; + sco_runners.tail.prev = sco_yielders.tail.prev; + sco_runners.tail.prev->next = (struct sco*)&sco_runners.tail; + sco_yielders.head.next = (struct sco*)&sco_yielders.tail; + sco_yielders.tail.prev = (struct sco*)&sco_yielders.head; + sco_nrunners = sco_nyielders; + sco_nyielders = 0; + } + sco_cur = sco_list_pop_front(&sco_runners); + sco_nrunners--; + llco_switch(sco_cur->llco, final); +} + +static void sco_entry(void *udata) { + // Initialize a new coroutine on the user's stack. + struct sco scostk = { 0 }; + struct sco *co = &scostk; + co->llco = llco_current(); + co->id = atomic_fetch_add(&sco_next_id, 1) + 1; + co->udata = udata; + co->prev = co; + co->next = co; + if (sco_cur) { + // Reschedule the coroutine that started this one + sco_list_push_back(&sco_yielders, co); + sco_list_push_back(&sco_yielders, sco_cur); + sco_nyielders += 2; + sco_switch(false, false); + } + sco_cur = co; + if (sco_user_entry) { + sco_user_entry(udata); + } + // This coroutine is finished. Switch to the next coroutine. + sco_switch(false, true); +} + +SCO_EXTERN +void sco_exit(void) { + if (sco_cur) { + sco_exit_to_main_requested = true; + sco_switch(false, true); + } +} + +SCO_EXTERN +void sco_start(struct sco_desc *desc) { + sco_init(); + struct llco_desc llco_desc = { + .entry = sco_entry, + .cleanup = desc->cleanup, + .stack = desc->stack, + .stack_size = desc->stack_size, + .udata = desc->udata, + }; + sco_user_entry = desc->entry; + llco_start(&llco_desc, false); +} + +SCO_EXTERN +int64_t sco_id(void) { + return sco_cur ? sco_cur->id : 0; +} + +SCO_EXTERN +void sco_yield(void) { + if (sco_cur) { + sco_list_push_back(&sco_yielders, sco_cur); + sco_nyielders++; + sco_switch(false, false); + } +} + +SCO_EXTERN +void sco_pause(void) { + if (sco_cur) { + sco_map_insert(&sco_paused, sco_cur); + sco_npaused++; + sco_switch(false, false); + } +} + +SCO_EXTERN +void sco_resume(int64_t id) { + sco_init(); + if (id == 0 && !sco_cur) { + // Resuming from main + sco_switch(true, false); + } else { + // Resuming from coroutine + struct sco *co = sco_map_delete(&sco_paused, &(struct sco){ .id = id }); + if (co) { + sco_npaused--; + co->prev = co; + co->next = co; + sco_list_push_back(&sco_yielders, co); + sco_nyielders++; + sco_yield(); + } + } +} + +SCO_EXTERN +void sco_detach(int64_t id) { + struct sco *co = sco_map_delete(&sco_paused, &(struct sco){ .id = id }); + if (co) { + sco_npaused--; + sco_lock(); + sco_map_insert(&sco_detached, co); + sco_ndetached++; + sco_unlock(); + } +} + +SCO_EXTERN +void sco_attach(int64_t id) { + sco_lock(); + struct sco *co = sco_map_delete(&sco_detached, &(struct sco){ .id = id }); + if (co) { + sco_ndetached--; + } + sco_unlock(); + if (co) { + sco_map_insert(&sco_paused, co); + sco_npaused++; + } +} + +SCO_EXTERN +void *sco_udata(void) { + return sco_cur ? sco_cur->udata : NULL; +} + +SCO_EXTERN +size_t sco_info_scheduled(void) { + return sco_nyielders; +} + +SCO_EXTERN +size_t sco_info_paused(void) { + return sco_npaused; +} + +SCO_EXTERN +size_t sco_info_running(void) { + size_t running = sco_nrunners; + if (sco_cur) { + // Count the current coroutine + running++; + } + return running; +} + +SCO_EXTERN +size_t sco_info_detached(void) { + sco_lock(); + size_t ndetached = sco_ndetached; + sco_unlock(); + return ndetached; +} + +// Returns true if there are any coroutines running, yielding, or paused. +SCO_EXTERN +bool sco_active(void) { + // Notice that detached coroutinues are not included. + return (sco_nyielders + sco_npaused + sco_nrunners + !!sco_cur) > 0; +} + +SCO_EXTERN +const char *sco_info_method(void) { + return llco_method(0); +} + +struct sco_unwind_context { + int nsymbols_actual; + bool started; + void *start_ip; + void *udata; + bool (*func)(struct sco_symbol*, void*); +}; + +static bool sco_unwind_step(struct llco_symbol *llco_sym, void *udata) { + struct sco_unwind_context *ctx = udata; + if (ctx->start_ip && !ctx->started && llco_sym->ip != ctx->start_ip) { + return true; + } + struct sco_symbol sym = { + .cfa = llco_sym->cfa, + .fbase = llco_sym->fbase, + .fname = llco_sym->fname, + .ip = llco_sym->ip, + .saddr = llco_sym->saddr, + .sname = llco_sym->sname, + }; + ctx->started = true; + ctx->nsymbols_actual++; + return !ctx->func || ctx->func(&sym, ctx->udata); +} + +// Unwinds the stack and returns the number of symbols +SCO_EXTERN +int sco_unwind(bool(*func)(struct sco_symbol *sym, void *udata), void *udata) { + struct sco_unwind_context ctx = { +#if defined(__GNUC__) && !defined(__EMSCRIPTEN__) + .start_ip = __builtin_return_address(0), +#endif + .func = func, + .udata = udata, + }; + llco_unwind(sco_unwind_step, &ctx); + return ctx.nsymbols_actual; +} +// END sco.c + +// BEGIN stack.c +// https://github.com/tidwall/stack +// +// Copyright 2024 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Coroutine stack allocator + +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + +#ifndef STACK_STATIC +#include "stack.h" +#else +#define STACK_API static +struct stack_opts { + size_t stacksz; + size_t defcap; + size_t maxcap; + size_t gapsz; + bool useguards; + bool nostackfreelist; + bool nopagerelease; + bool onlymalloc; +}; +struct stack { char _[32]; }; +struct stack_mgr { char _[320]; }; +#endif + +#ifndef STACK_API +#define STACK_API +#endif + +struct stack_group { + struct stack_group *prev; + struct stack_group *next; + size_t allocsz; + size_t stacksz; + size_t gapsz; + size_t pagesz; + bool guards; + char *stack0; + size_t cap; // max number of stacks + size_t pos; // index of next stack to use + size_t use; // how many are used +}; + +struct stack_freed { + struct stack_freed *prev; + struct stack_freed *next; + struct stack_group *group; +}; + +struct stack_mgr0 { + size_t pagesz; + size_t stacksz; + size_t defcap; + size_t maxcap; + size_t gapsz; + bool useguards; + bool nostackfreelist; + bool nopagerelease; + bool onlymalloc; + struct stack_group gendcaps[2]; + struct stack_group *group_head; + struct stack_group *group_tail; + struct stack_freed fendcaps[2]; + struct stack_freed *free_head; + struct stack_freed *free_tail; +}; + +struct stack0 { + void *addr; + size_t size; + struct stack_group *group; +}; + +static_assert(sizeof(struct stack) >= sizeof(struct stack0), ""); +static_assert(sizeof(struct stack_mgr) >= sizeof(struct stack_mgr0), ""); + +// Returns a size that is aligned to a boundary. +// The boundary must be a power of 2. +static size_t stack_align_size(size_t size, size_t boundary) { + return size < boundary ? boundary : + size&(boundary-1) ? size+boundary-(size&(boundary-1)) : + size; +} + +#ifndef _WIN32 + +// allocate memory using mmap. Used primarily for stack group memory. +static void *stack_mmap_alloc(size_t size) { + void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (addr == MAP_FAILED || addr == NULL) { + return NULL; + } + return addr; +} + +// free the stack group memory +static void stack_mmap_free(void *addr, size_t size) { + if (addr) { + munmap(addr, size); + } +} + +static struct stack_group *stack_group_new(size_t stacksz, size_t pagesz, + size_t cap, size_t gapsz, bool useguards) +{ + bool guards; + if (gapsz == 0) { + guards = false; + } else { + gapsz = stack_align_size(gapsz, pagesz); + guards = useguards; + } + // Calculate the allocation size of the group. + // A group allocation contains the group struct and all its stacks. + // Each stack is separated by an optional gap page, which can also act as + // the guard page. There will be one more gap pages than stack to ensure + // that each stack is sandwiched by gaps or guards, allowing for overflow + // padding or segv detection for stacks that grow up or down. + // For example, let's say the group has a capacity of 3. The memory layout + // will end up look something like this + // + // [ GUARD ][ Stack1 ][ GUARD ][ Stack2 ][ GUARD ][ Stack3 ][ GUARD ] + // + // where the entire group is a single mmap and each stack is sandwiched by + // guard pages. + size_t allocsz = stack_align_size(sizeof(struct stack_group), pagesz); + allocsz += gapsz; // add space for prefix gap/guard + size_t stack0 = allocsz; // offset of first stack + allocsz += (stacksz + gapsz) * cap; // add the remainder size + struct stack_group *group = stack_mmap_alloc(allocsz); + if (!group) { + return NULL; + } + memset(group, 0, sizeof(struct stack_group)); + group->allocsz = allocsz; + group->next = group; + group->prev = group; + group->guards = guards; + group->gapsz = gapsz; + group->stacksz = stacksz; + group->pagesz = pagesz; + group->stack0 = ((char*)group)+stack0; + group->cap = cap; + return group; +} + +static void stack_group_free(struct stack_group *group) { + stack_mmap_free(group, group->allocsz); +} + +static void stack_group_remove(struct stack_group *group) { + group->prev->next = group->next; + group->next->prev = group->prev; + group->next = NULL; + group->prev = NULL; +} + +static struct stack_group *stack_freed_remove(struct stack_freed *stack) { + stack->prev->next = stack->next; + stack->next->prev = stack->prev; + stack->next = NULL; + stack->prev = NULL; + struct stack_group *group = stack->group; + stack->group = NULL; + return group; +} + +// push a stack_group to the end of the manager group list. +static void stack_push_group(struct stack_mgr0 *mgr, struct stack_group *group) +{ + mgr->group_tail->prev->next = group; + group->prev = mgr->group_tail->prev; + group->next = mgr->group_tail; + mgr->group_tail->prev = group; +} + +static void stack_push_freed_stack(struct stack_mgr0 *mgr, + struct stack_freed *stack, struct stack_group *group) +{ + mgr->free_tail->prev->next = stack; + stack->prev = mgr->free_tail->prev; + stack->next = mgr->free_tail; + mgr->free_tail->prev = stack; + stack->group = group; +} +#endif + +// initialize a stack manager + +static void stack_mgr_init_(struct stack_mgr0 *mgr, struct stack_opts *opts) { +#ifdef _WIN32 + size_t pagesz = 4096; +#else + size_t pagesz = (size_t)sysconf(_SC_PAGESIZE); +#endif + size_t stacksz = opts && opts->stacksz ? opts->stacksz : 8388608; + stacksz = stack_align_size(stacksz, pagesz); + memset(mgr, 0, sizeof(struct stack_mgr0)); + mgr->stacksz = stacksz; + mgr->defcap = opts && opts->defcap ? opts->defcap : 4; + mgr->maxcap = opts && opts->maxcap ? opts->maxcap : 8192; + mgr->gapsz = opts && opts->gapsz ? opts->gapsz : 1048576; + mgr->useguards = opts && opts->useguards; + mgr->nostackfreelist = opts && opts->nostackfreelist; + mgr->nopagerelease = opts && opts->nopagerelease; + mgr->onlymalloc = opts && opts->onlymalloc; + mgr->pagesz = pagesz; + mgr->group_head = &mgr->gendcaps[0]; + mgr->group_tail = &mgr->gendcaps[1]; + mgr->group_head->next = mgr->group_tail; + mgr->group_tail->prev = mgr->group_head; + if (!mgr->nostackfreelist) { + mgr->free_head = &mgr->fendcaps[0]; + mgr->free_tail = &mgr->fendcaps[1]; + mgr->free_head->next = mgr->free_tail; + mgr->free_tail->prev = mgr->free_head; + } +#ifdef _WIN32 + mgr->onlymalloc = true; +#endif +} + +STACK_API +void stack_mgr_init(struct stack_mgr *mgr, struct stack_opts *opts) { + stack_mgr_init_((void*)mgr, opts); +} + + +static void stack_mgr_destroy_(struct stack_mgr0 *mgr) { +#ifndef _WIN32 + struct stack_group *group = mgr->group_head->next; + while (group != mgr->group_tail) { + struct stack_group *next = group->next; + stack_group_free(group); + group = next; + } +#endif + memset(mgr, 0, sizeof(struct stack_mgr0)); +} + +STACK_API +void stack_mgr_destroy(struct stack_mgr *mgr) { + stack_mgr_destroy_((void*)mgr); +} + +#ifndef _WIN32 +static void stack_release_group(struct stack_group *group, bool nofreelist) { + // Remove all stacks from free list, remove the group from the group list, + // and free group. + if (!nofreelist) { + struct stack_freed *stack; + for (size_t i = 0; i < group->pos; i++) { + stack = (void*)(group->stack0 + (group->stacksz+group->gapsz) * i); + stack_freed_remove(stack); + } + } + stack_group_remove(group); + stack_group_free(group); +} +#endif + +static int stack_get_(struct stack_mgr0 *mgr, struct stack0 *stack) { + if (mgr->onlymalloc) { + void *addr = malloc(mgr->stacksz); + if (!addr) { + return -1; + } + stack->addr = addr; + stack->size = mgr->stacksz; + stack->group = 0; + return 0; + } +#ifndef _WIN32 + struct stack_group *group; + if (!mgr->nostackfreelist) { + struct stack_freed *fstack = mgr->free_tail->prev; + if (fstack != mgr->free_head) { + group = stack_freed_remove(fstack); + group->use++; + stack->addr = fstack; + stack->size = mgr->stacksz; + stack->group = group; + return 0; + } + } + group = mgr->group_tail->prev; + if (group->pos == group->cap) { + size_t cap = group->cap ? group->cap * 2 : mgr->defcap; + if (cap > mgr->maxcap) { + cap = mgr->maxcap; + } + group = stack_group_new(mgr->stacksz, mgr->pagesz, cap, mgr->gapsz, + mgr->useguards); + if (!group) { + return -1; + } + stack_push_group(mgr, group); + } + char *addr = group->stack0 + (group->stacksz+group->gapsz) * group->pos; + if (group->guards) { + // Add page guards to the coroutine stack. + // A failure here usually means that there isn't enough system memory + // to split the a mmap'd virtual memory region into two. + // Linux assigns limits to how many distinct mapped regions a process + // may have. Typically around 64K. This means that when used with + // stack guards on Linux, you will probably be limited to about 64K + // concurrent threads and coroutines. To increase this limit, increase + // the value in the /proc/sys/vm/max_map_count, or disable page guards + // by setting 'guards' option to false. + int ret = 0; + if (addr == group->stack0) { + // Add the prefix guard page. + // This separates the group struct page and the first stack. + ret = mprotect(addr-group->gapsz, group->gapsz, PROT_NONE); + } + // Add the suffix guard page. + if (ret == 0) { + ret = mprotect(addr+group->stacksz, group->gapsz, PROT_NONE); + } + if (ret == -1) { + return -1; + } + } + group->pos++; + group->use++; + stack->addr = addr; + stack->size = mgr->stacksz; + stack->group = group; +#endif + return 0; +} + +STACK_API +int stack_get(struct stack_mgr *mgr, struct stack *stack) { + return stack_get_((void*)mgr, (void*)stack); +} + +static void stack_put_(struct stack_mgr0 *mgr, struct stack0 *stack) { + if (mgr->onlymalloc) { + free(stack->addr); + return; + } +#ifndef _WIN32 + void *addr = stack->addr; + struct stack_group *group = stack->group; + if (!mgr->nopagerelease){ + char *stack0 = addr; + size_t stacksz = group->stacksz; + if (!mgr->nostackfreelist) { + // The first page does not need to be released. + stack0 += group->pagesz; + stacksz -= group->pagesz; + } + if (stacksz > 0) { + // Re-mmap the pages that encompass the stack. The MAP_FIXED option + // releases the pages back to the operating system. Yet the entire + // stack will still exists in the processes virtual memory. + mmap(stack0, stacksz, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + } + } + group->use--; + if (!mgr->nostackfreelist) { + // Add the stack to the stack freed linked list. + // This will cause the first page of the stack to being paged into + // process memory, thus using up at least page_size of data per linked + // stack. + stack_push_freed_stack(mgr, addr, group); + } + if (group->use == 0) { + // There are no more stacks in use for this group that belongs to the + // provided stack, and all other stacks have been used at least once in + // the past. + // The group should be fully released back to the operating system. + stack_release_group(group, mgr->nostackfreelist); + } +#endif +} + +STACK_API +void stack_put(struct stack_mgr *mgr, struct stack *stack) { + stack_put_((void*)mgr, (void*)stack); +} + +static size_t stack_size_(struct stack0 *stack) { + return stack->size; +} + +STACK_API +size_t stack_size(struct stack *stack) { + return stack_size_((void*)stack); +} + +static void *stack_addr_(struct stack0 *stack) { + return stack->addr; +} + +STACK_API +void *stack_addr(struct stack *stack) { + return stack_addr_((void*)stack); +} +// END stack.c + +#ifndef NECO_NOWORKER +// BEGIN worker.c +// https://github.com/tidwall/worker.c +// +// Copyright 2024 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +// Simple background worker for C + +#include +#include +#include +#include +#include + +#define WORKER_DEF_TIMEOUT INT64_C(1000000000) // one second +#define WORKER_DEF_MAX_THREADS 2 +#define WORKER_DEF_MAX_THREAD_ENTRIES 32 + +struct worker_opts { + int max_threads; // def: 2 + int max_thread_entries; // def: 32 + int64_t thread_timeout; // nanoseconds, def: 1 second + void*(*malloc)(size_t); // def: system malloc + void(*free)(void*); // def: system free +}; + +struct worker_entry { + void (*work)(void *udata); + void *udata; +}; + +struct worker_thread { + pthread_mutex_t mu; + pthread_cond_t cond; + pthread_t th; + int64_t timeout; + bool end; + int pos, len; + int nentries; + struct worker_entry *entries; +}; + +struct worker { + int nthreads; + struct worker_thread **threads; + void (*free)(void*); +}; + +void worker_free(struct worker *worker) { + if (worker) { + if (worker->threads) { + for (int i = 0; i < worker->nthreads; i++) { + struct worker_thread *thread = worker->threads[i]; + if (thread) { + pthread_mutex_lock(&thread->mu); + thread->end = true; + pthread_t th = thread->th; + pthread_cond_signal(&thread->cond); + pthread_mutex_unlock(&thread->mu); + if (th) { + pthread_join(th, 0); + } + worker->free(thread->entries); + worker->free(thread); + } + } + worker->free(worker->threads); + } + worker->free(worker); + } +} + +struct worker *worker_new(struct worker_opts *opts) { + // Load options + int nthreads = opts ? opts->max_threads : 0; + int nentries = opts ? opts->max_thread_entries : 0; + int64_t timeout = opts ? opts->thread_timeout : 0; + void*(*malloc_)(size_t) = opts ? opts->malloc : 0; + void(*free_)(void*) = opts ? opts->free : 0; + nthreads = nthreads <= 0 ? WORKER_DEF_MAX_THREADS : + nthreads > 65536 ? 65536 : nthreads; + nentries = nentries <= 0 ? WORKER_DEF_MAX_THREAD_ENTRIES : + nentries > 65536 ? 65536 : nentries; + timeout = timeout <= 0 ? WORKER_DEF_TIMEOUT : timeout; + malloc_ = malloc_ ? malloc_ : malloc; + free_ = free_ ? free_ : free; + + struct worker *worker = malloc_(sizeof(struct worker)); + if (!worker) { + return NULL; + } + memset(worker, 0, sizeof(struct worker)); + worker->free = free_; + worker->nthreads = nthreads; + worker->threads = malloc_(sizeof(struct worker_thread*) * nthreads); + if (!worker->threads) { + worker_free(worker); + return NULL; + } + memset(worker->threads, 0, sizeof(struct worker_thread*) * nthreads); + for (int i = 0; i < worker->nthreads; i++) { + struct worker_thread *thread = malloc_(sizeof(struct worker_thread)); + if (!thread) { + worker_free(worker); + return NULL; + } + memset(thread, 0, sizeof(struct worker_thread)); + worker->threads[i] = thread; + thread->timeout = timeout; + thread->nentries = nentries; + thread->entries = malloc_(sizeof(struct worker_entry) * nentries); + if (!thread->entries) { + worker_free(worker); + return NULL; + } + memset(thread->entries, 0, sizeof(struct worker_entry) * nentries); + pthread_mutex_init(&thread->mu, 0); + pthread_cond_init(&thread->cond, 0); + thread->nentries = nentries; + } + return worker; +} + +static void *worker_entry(void *arg) { + // printf("thread created\n"); + struct worker_thread *thread = arg; + pthread_mutex_lock(&thread->mu); + while (1) { + while (thread->len > 0) { + struct worker_entry entry = thread->entries[thread->pos]; + thread->pos++; + if (thread->pos == thread->nentries) { + thread->pos = 0; + } + thread->len--; + pthread_mutex_unlock(&thread->mu); + if (entry.work) { + entry.work(entry.udata); + } + pthread_mutex_lock(&thread->mu); + } + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += 1; + pthread_cond_timedwait(&thread->cond, &thread->mu, &ts); + if (thread->len == 0) { + thread->th = 0; + if (!thread->end) { + pthread_detach(thread->th); + } + thread->end = false; + break; + } + } + pthread_mutex_unlock(&thread->mu); + // printf("thread ended\n"); + return NULL; +} + +/// Submit work +/// @param worker the worker +/// @param pin pin to a thread or set to -1 for Round-robin selection +/// @param work the work to perform +/// @param udata any user data +/// @return true for success or false if no worker is available. +/// @return false for invalid arguments. Worker and work must no be null. +bool worker_submit(struct worker *worker, int64_t pin, void(*work)(void *udata), + void *udata) +{ + if (!worker || !work) { + return false; + } + static __thread uint32_t worker_next_index = 0; + if (pin < 0) { + pin = worker_next_index; + } + worker_next_index++; + struct worker_thread *thread = worker->threads[pin%worker->nthreads]; + bool submitted = false; + pthread_mutex_lock(&thread->mu); + if (thread->len < thread->nentries) { + int pos = thread->pos + thread->len; + if (pos >= thread->nentries) { + pos -= thread->nentries; + } + thread->entries[pos].work = work; + thread->entries[pos].udata = udata; + thread->len++; + if (!thread->th) { + int ret = pthread_create(&thread->th, 0, worker_entry, thread); + if (ret == -1) { + pthread_mutex_unlock(&thread->mu); + return false; + } + } + submitted = true; + pthread_cond_signal(&thread->cond); + } + pthread_mutex_unlock(&thread->mu); + return submitted; +} +// END worker.c +#endif // NECO_NOWORKER + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#include +#include +#endif +#include + +#include "neco.h" + +#if defined(__FreeBSD__) || defined(__APPLE__) +#include +#define NECO_POLL_KQUEUE +#elif defined(__linux__) +#include +#include +#define NECO_POLL_EPOLL +#elif defined(__EMSCRIPTEN__) || defined(_WIN32) +// #warning Webassembly has no polling +#define NECO_POLL_DISABLED +#else +// Only FreeBSD, Apple, and Linux +#error Platform not supported +#endif + +#ifdef _WIN32 +#ifndef SIGUSR1 +#define SIGUSR1 30 /* user defined signal 1 */ +#endif +#ifndef SIGUSR2 +#define SIGUSR2 31 /* user defined signal 2 */ +#endif +#endif + +#define CLAMP(x, min, max) ((x)<=(min)?(min):(x)>=(max)?(max):(x)) + +static_assert(sizeof(int) >= sizeof(uint32_t), "32-bit or higher required"); + +#if defined(__GNUC__) +#define noinline __attribute__ ((noinline)) +#define noreturn __attribute__ ((noreturn)) +#define aligned16 __attribute__((aligned(16))) +#else +#define noinline +#define noreturn +#define aligned16 +#endif + +#ifndef NECO_NOPOOL +#define POOL_ENABLED true +#else +#define POOL_ENABLED false +#endif + +#ifdef NECO_TESTING +#define TESTING_EXTERN(specifier) extern +#else +#define TESTING_EXTERN(specifier) specifier +#endif + +void sco_yield(void); + +TESTING_EXTERN(static) +const char *strsignal0(int signo) { + static __thread char buf[32]; + if (signo <= 0 || signo >= 32) { + snprintf(buf, sizeof(buf), "Unknown signal: %d", signo); + return buf; + } else { +#ifdef _WIN32 + snprintf(buf, sizeof(buf), "Signal: %d", signo); + return buf; + #else + return strsignal(signo); +#endif + } +} + +// https://zimbry.blogspot.com/2011/09/better-bit-mixing-improving-on.html +// hash u64 using mix13 +static uint64_t mix13(uint64_t key) { + key ^= (key >> 30); + key *= UINT64_C(0xbf58476d1ce4e5b9); + key ^= (key >> 27); + key *= UINT64_C(0x94d049bb133111eb); + key ^= (key >> 31); + return key; +} + +#ifdef NECO_TESTING +#include "tests/panic.h" +#else +// print a string, function, file, and line number. +#define pwhere(str) \ + fprintf(stderr, "%s, function %s, file %s, line %d.\n", \ + (str), __func__, __FILE__, __LINE__) + +// define an unreachable section of code. +// This will not signal like __builtin_unreachable() +#define unreachable() \ + pwhere("Unreachable"); \ + abort() + +// must is like assert but it cannot be disabled with -DNDEBUG +// This is primarily used for a syscall that, when provided valid paramaters, +// should never fail. Yet if it does fail we want the 411. +#define must(cond) \ + if (!(cond)) { \ + pwhere("Must failed: " #cond); \ + perror("System error:"); \ + abort(); \ + } \ + (void)0 + +#define panic(fmt, ...) \ + fprintf(stderr, "panic: " fmt "\n", __VA_ARGS__); \ + print_stacktrace(1, true); \ + fprintf(stderr, "\n"); \ + abort(); + +#endif + +#ifdef NECO_TESTING +#include "tests/signals.h" +#else +static bool bt_iscommon(const char *symbol) { + return symbol && ( + strstr(symbol, "coentry") || + strstr(symbol, "sco_entry") || + strstr(symbol, "print_stacktrace") || + strstr(symbol, "sighandler") || + strstr(symbol, "_sigtramp") + ); +} + +// system_out works like the system() but returns the contents to a newly +// allocated string. +// You must call free() on the output when you are done. +static char *system_out(const char *command) { + FILE *ls = popen(command, "r"); + if (!ls) { + return NULL; + } + size_t nout = 0; + char *out = NULL; + char buf[128]; + while (fgets(buf, sizeof(buf), ls) != 0) { + size_t nbuf = strlen(buf); + char *out2 = realloc(out, nout+nbuf+1); + if (!out2) { + free(out); + pclose(ls); + return NULL; + } + out = out2; + memcpy(out+nout, buf, nbuf); + nout += nbuf; + out[nout] = '\0'; + } + pclose(ls); + if (out == NULL) { + out = malloc(1); + if (out) { + out[0] = '\0'; + } + } + return out; +} + +struct bt_context { + int n; + int skip; + bool omit_commons; +}; + +static bool print_symbol(struct sco_symbol *sym, void *udata) { + struct bt_context *ctx = udata; + const char *fname = (char*)sym->fname; + size_t off1 = (size_t)sym->ip - (size_t)sym->fbase; + size_t off2 = off1; + const char *sname = sym->sname; + if (!sname) { + sname = fname; + off2 = (size_t)sym->ip - (size_t)sym->fbase; + } + char cmd[256]; + bool printed = false; + size_t n = 0; +#if defined(__linux__) + n = snprintf(cmd, sizeof(cmd), "addr2line -psfe '%s' %p", + fname, (void*)off1); +#elif !defined(__FreeBSD__) + n = snprintf(cmd, sizeof(cmd), "atos -o %s -l 0x%zx 0x%016zx", + fname, (size_t)sym->fbase, (size_t)sym->ip); +#endif + if (n > 0 && n < sizeof(cmd)-1) { + char *out = system_out(cmd); + if (out) { + if (!bt_iscommon(out)) { + if (ctx->skip == 0) { + fprintf(stderr, "0x%016zx: %s", (size_t)off2, out); + } else { + ctx->skip--; + } + } + free(out); + printed = true; + } + } + if (!printed) { + if (!bt_iscommon(sname)) { + if (ctx->skip == 0) { + fprintf(stderr, "0x%016zx: %s + %zu\n", + (size_t)off2, sname, off2); + } else { + ctx->skip--; + } + } + } + ctx->n++; + return true; +} + +static void print_stacktrace(int skip, bool omit_commons) { + fprintf(stderr, "\n------ STACK TRACE ------\n"); + struct bt_context ctx = { + .skip = skip, + .omit_commons = omit_commons, + }; + sco_unwind(print_symbol, &ctx); + // stacktrace all coroutines? +} + +noinline noreturn +static void sigexitnow(int signo) { + fprintf(stderr, "%s\n", signo == SIGINT ? "" : strsignal0(signo)); + _Exit(128+signo); +} +#endif + +// Private functions. See decl for descriptions. +int neco_errconv_from_sys(void); +void neco_errconv_to_sys(int); + +static bool env_paniconerror = false; +static int env_canceltype = NECO_CANCEL_ASYNC; +static int env_cancelstate = NECO_CANCEL_ENABLE; +static void *(*malloc_)(size_t) = NULL; +static void *(*realloc_)(void*, size_t) = NULL; +static void (*free_)(void*) = NULL; + + +/// Globally set the allocators for all Neco functions. +/// +/// _This should only be run once at program startup and before the first +/// neco_start function is called_. +/// @see GlobalFuncs +void neco_env_setallocator(void *(*malloc)(size_t), + void *(*realloc)(void*, size_t), void (*free)(void*)) +{ + malloc_ = malloc; + realloc_ = realloc; + free_ = free; +} + +void *neco_malloc(size_t nbytes) { + void *ptr = 0; + if (nbytes < SIZE_MAX) { + if (malloc_) { + ptr = malloc_(nbytes); + } else { + ptr = malloc(nbytes); + } + } + if (!ptr && nbytes > 0 && env_paniconerror) { + panic("%s", strerror(ENOMEM)); + } + return ptr; +} + +void *neco_realloc(void *ptr, size_t nbytes) { + void *ptr2 = 0; + if (nbytes < SIZE_MAX) { + if (!ptr) { + ptr2 = neco_malloc(nbytes); + } else if (realloc_) { + ptr2 = realloc_(ptr, nbytes); + } else { + ptr2 = realloc(ptr, nbytes); + } + } + if (!ptr2 && nbytes > 0 && env_paniconerror) { + panic("%s", strerror(ENOMEM)); + } + return ptr2; +} + +void neco_free(void *ptr) { + if (ptr) { + if (free_) { + free_(ptr); + } else { + free(ptr); + } + } +} + +/// Globally set the panic-on-error state for all coroutines. +/// +/// This will cause panics (instead of returning the error) for three errors: +/// `NECO_INVAL`, `NECO_PERM`, and `NECO_NOMEM`. +/// +/// _This should only be run once at program startup and before the first +/// neco_start function is called_. +/// @see GlobalFuncs +void neco_env_setpaniconerror(bool paniconerror) { + env_paniconerror = paniconerror; +} + +/// Globally set the canceltype for all coroutines. +/// +/// _This should only be run once at program startup and before the first +/// neco_start function is called_. +/// @see GlobalFuncs +void neco_env_setcanceltype(int type) { + env_canceltype = type; +} + +/// Globally set the cancelstate for all coroutines. +/// +/// _This should only be run once at program startup and before the first +/// neco_start function is called_. +/// @see GlobalFuncs +void neco_env_setcancelstate(int state) { + env_cancelstate = state; +} + +// return either a BSD kqueue or Linux epoll type. +static int evqueue(void) { +#if defined(NECO_POLL_EPOLL) + return epoll_create1(0); +#elif defined(NECO_POLL_KQUEUE) + return kqueue(); +#else + return -1; +#endif +} + +#define read0 read +#define recv0 recv +#define write0 write +#define send0 send +#define accept0 accept +#define connect0 connect +#define socket0 socket +#define bind0 bind +#define listen0 listen +#define setsockopt0 setsockopt +#define nanosleep0 nanosleep +#define fcntl0 fcntl +#define pthread_create0 pthread_create +#define pthread_detach0 pthread_detach +#define pipe0 pipe +#define malloc0 neco_malloc +#define realloc0 neco_realloc +#define free0 neco_free +#define stack_get0 stack_get +#define evqueue0 evqueue +#define kevent0 kevent +#define epoll_ctl0 epoll_ctl + +// Provide the ability to cause system calls to fail during testing. +// Not for production. +#ifdef NECO_TESTING +#include "tests/fail_counters.h" +#endif + +// Return monotonic nanoseconds of the CPU clock. +static int64_t getnow(void) { + struct timespec now = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &now); + return now.tv_sec * INT64_C(1000000000) + now.tv_nsec; +} + +enum evkind { + EVREAD = 1, // NECO_WAIT_READ + EVWRITE = 2, // NECO_WAIT_WRITE +}; + +static_assert(EVREAD == NECO_WAIT_READ, ""); +static_assert(EVWRITE == NECO_WAIT_WRITE, ""); + +struct cleanup { + void (*routine)(void *); + void *arg; + struct cleanup *next; +} aligned16; + +//////////////////////////////////////////////////////////////////////////////// +// colist - The standard queue type that is just a doubly linked list storing +// the highest priority coroutine at the head and lowest at the tail. +//////////////////////////////////////////////////////////////////////////////// + +struct coroutine; + +struct colink { + struct coroutine *prev; + struct coroutine *next; +} aligned16; + +struct colist { + struct colink head; + struct colink tail; +}; + +// Remove the coroutine from any list, eg. yielders, sigwaiters, or free. +static void remove_from_list(struct coroutine *co) { + struct colink *link = (void*)co; + ((struct colink*)link->prev)->next = link->next; + ((struct colink*)link->next)->prev = link->prev; + link->next = (struct coroutine*)link; + link->prev = (struct coroutine*)link; +} + +static void colist_init(struct colist *list) { + list->head.prev = NULL; + list->head.next = (struct coroutine*)&list->tail; + list->tail.prev = (struct coroutine*)&list->head; + list->tail.next = NULL; +} + +static void colist_push_back(struct colist *list, struct coroutine *co) { + remove_from_list(co); + struct colink *link = (void*)co; + ((struct colink*)list->tail.prev)->next = (struct coroutine*)link; + link->prev = list->tail.prev; + link->next = (struct coroutine*)&list->tail; + list->tail.prev = (struct coroutine*)link; +} + +static struct coroutine *colist_pop_front(struct colist *list) { + struct coroutine *co = list->head.next; + if (co == (struct coroutine*)&list->tail) { + return NULL; + } + remove_from_list(co); + return co; +} + +static bool colist_is_empty(struct colist *list) { + return list->head.next == (struct coroutine*)&list->tail; +} + +//////////////////////////////////////////////////////////////////////////////// +// coroutine - structure for a single coroutine object, state, and state +//////////////////////////////////////////////////////////////////////////////// + +enum cokind { COROUTINE, SELECTCASE }; + +struct coroutine { + struct coroutine *prev; + struct coroutine *next; + enum cokind kind; // always COROUTINE + + int64_t id; // coroutine id (sco_id()) + struct stack stack; // coroutine stack + int argc; // number of coroutine arguments + void **argv; // the coroutine arguments + void *aargv[4]; // preallocated arguments + void(*coroutine)(int,void**); // user coroutine function + + int64_t lastid; // identifer of the last started coroutine + int64_t starterid; // identifer of the starter coroutine + bool paused; // coroutine is paused + bool deadlined; // coroutine operation was deadlined + + struct cleanup *cleanup; // cancelation cleanup stack + + bool rlocked; + bool suspended; + + int64_t pool_ts; // timestamp when added to a pool + + char *cmsg; // channel message data from sender + bool cclosed; // channel closed by sender + + uint32_t sigwatch; // coroutine is watching these signals (mask) + uint32_t sigmask; // coroutine wait mask and result + + int canceltype; // cancelation type + int cancelstate; // cancelation state + bool canceled; // coroutine operation was cancelled + struct colist cancellist; // waiting coroutines + int ncancellist; // number of waiting coroutines + + struct colist joinlist; // waiting coroutines + int njoinlist; // number of waiting coroutines + + struct neco_chan *gen; // self generator (actually a channel) + + // For the rt->all comap, which stores all active coroutines + AAT_FIELDS(struct coroutine, all_left, all_right, all_level) + + // Deadline for pause. All paused will have this set to something. + int64_t deadline; + AAT_FIELDS(struct coroutine, dl_left, dl_right, dl_level) + + // File event node + int evfd; + enum evkind evkind; + AAT_FIELDS(struct coroutine, evleft, evright, evlevel) +} aligned16; + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +static int all_compare(struct coroutine *a, struct coroutine *b) { + // order by id + return a->id < b->id ? -1 : a->id > b->id; +} + +AAT_DEF(static, all, struct coroutine) +AAT_IMPL(all, struct coroutine, all_left, all_right, all_level, all_compare) + +static int dl_compare(struct coroutine *a, struct coroutine *b) { + // order by deadline, id + return + a->deadline < b->deadline ? -1 : a->deadline > b->deadline ? 1 : + a->id < b->id ? -1 : a->id > b->id; +} + +AAT_DEF(static, dlqueue, struct coroutine) +AAT_IMPL(dlqueue, struct coroutine, dl_left, dl_right, dl_level, dl_compare) + +static int evcompare(struct coroutine *a, struct coroutine *b) { + // order by evfd, evkind, id + return + a->evfd < b->evfd ? -1 : a->evfd > b->evfd ? 1 : + a->evkind < b->evkind ? -1 : a->evkind > b->evkind ? 1 : + a->id < b->id ? -1 : a->id > b->id; +} + +AAT_DEF(static, evaat, struct coroutine) +AAT_IMPL(evaat, struct coroutine, evleft, evright, evlevel, evcompare) + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +static __thread int lasterr = 0; + +/// Returns last known error from a Neco operation +/// +/// See [Neco errors](./API.md#errors) for a list. +int neco_lasterr(void) { + return lasterr; +} + +noinline +static void errhpnd(int ret) { + if (ret == -1) { + lasterr = neco_errconv_from_sys(); + } else { + lasterr = ret; + } + if (env_paniconerror) { + // The user has requested to panic on + // Only certain errors may cause a panic. + switch (lasterr) { + case NECO_INVAL: + case NECO_PERM: + case NECO_NOMEM: + panic("%s", neco_strerror(ret)); + default: + break; + } + } +} + +// error_guard is a small macro that is used in every public neco_* api +// function. It's called just before the function returns and ensures that +// the provided return value is stored to the "lasterr" thread-local variable +// and that if the error is a posix system error, such as EPERM, that it is +// correctly converted into a neco error code. +// If the user has requested to panic on errors by using the environment +// option `neco_setpaniconerror(true)` then this call will cause a panic if +// the provided return value is an qualifying error, such as NECO_NOMEM. +#define error_guard(ret) \ + lasterr = 0; \ + if (ret < 0) { \ + errhpnd(ret); \ + } \ + (void)0 + +// async_error_guard is an extension of error_guard that also provides async +// coroutine cancellation. +// When the provided return value is NECO_CANCELED _and_ the user canceltype +// is NECO_CANCEL_ASYNC, then the currently running coroutine will immediately +// be terminated. Any clean-up handlers established by neco_cleanup_push() that +// have not yet been popped, are popped and executed. +#define async_error_guard(ret) \ + error_guard(ret); \ + if (lasterr == NECO_CANCELED) { \ + struct coroutine *co = coself(); \ + if (co->canceltype == NECO_CANCEL_ASYNC) { \ + coexit(true); \ + } \ + } \ + (void)0 \ + +//////////////////////////////////////////////////////////////////////////////// +// comap - A hashmap-style structure that stores coroutines using multiple +// binary search trees (aa-tree) in hashed shards. This allows for the map to +// grow evenly, without allocations, and performing much faster than using a +// single BST. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef COMAP_NSHARDS +#define COMAP_NSHARDS 512 +#endif + +struct comap { + struct coroutine *roots[COMAP_NSHARDS]; + int count; +}; + +#define comap_getaat(co) \ + (&map->roots[mix13((co)->id) & (COMAP_NSHARDS-1)]) + +static struct coroutine *comap_insert(struct comap *map, struct coroutine *co) { + struct coroutine *prev = all_insert(comap_getaat(co), co); + map->count++; + return prev; +} + +static struct coroutine *comap_search(struct comap *map, struct coroutine *key){ + return all_search(comap_getaat(key), key); +} + +static struct coroutine *comap_delete(struct comap *map, struct coroutine *key){ + struct coroutine *prev = all_delete(comap_getaat(key), key); + map->count--; + return prev; +} + +//////////////////////////////////////////////////////////////////////////////// +// evmap - A hashmap-style structure that stores fd/events using multiple +// binary search trees (aa-tree) in hashed shards. This allows for the map to +// grow evenly, without allocations, and performing much faster than using a +// single BST. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef EVMAP_NSHARDS +#define EVMAP_NSHARDS 512 +#endif + +struct evmap { + struct coroutine *roots[EVMAP_NSHARDS]; + int count; +}; + +#define evmap_getaat(co) (&map->roots[mix13(co->evfd) & (EVMAP_NSHARDS-1)]) + +static struct coroutine *evmap_insert(struct evmap *map, struct coroutine *co) { + struct coroutine *prev = evaat_insert(evmap_getaat(co), co); + map->count++; + return prev; +} + +static struct coroutine *evmap_iter(struct evmap *map, struct coroutine *key){ + return evaat_iter(evmap_getaat(key), key); +} + +static struct coroutine *evmap_next(struct evmap *map, struct coroutine *key){ + return evaat_next(evmap_getaat(key), key); +} + +static struct coroutine *evmap_delete(struct evmap *map, struct coroutine *key){ + struct coroutine *prev = evaat_delete(evmap_getaat(key), key); + map->count--; + return prev; +} + + + +#ifndef NECO_TESTING +static +#endif +__thread int neco_gai_errno = 0; + +/// Get the last error from a neco_getaddrinfo() call. +/// +/// See the [man page](https://man.freebsd.org/cgi/man.cgi?query=gai_strerror) +/// for a list of errors. +int neco_gai_lasterr(void) { + return neco_gai_errno; +} + +// For atomically incrementing the runtime ID. +// The runtime ID is only used to protect channels from being accicentally +// used by another thread. +static atomic_int_fast64_t next_runtime_id = 1; + +// The neco runtime +struct runtime { + int64_t id; // unique runtime identifier + struct stack_mgr stkmgr; // stack memory manager + int64_t coid; // unique coroutine id incrementer + struct coroutine *costarter; // current coroutine starter + struct comap all; // all running coroutines. + struct coroutine *deadlines; // paused coroutines (aat root) + size_t ndeadlines; // total number of paused coroutines + size_t ntotal; // total number of coroutines ever created + size_t nsleepers; + size_t nlocked; + size_t nreceivers; + size_t nsenders; + size_t nwaitgroupers; // number of waitgroup blocked coroutines + size_t ncondwaiters; // number of cond blocked coroutines + size_t nworkers; // number of background workers + size_t nsuspended; // number of suspended coroutines + + struct evmap evwaiters; // coroutines waiting on events (aat root) + size_t nevwaiters; + + // list of coroutines waiting to be resumed by the scheduler + int nresumers; + struct colist resumers; + + // coroutine pool (reusables) + int npool; // number of coroutines in a pool + struct colist pool; // pool lists of coroutines for each size + + int qfd; // queue file descriptor (epoll or kqueue) + int64_t qfdcreated; // when the queue was created + + // zerochan pool (reusables) + struct neco_chan **zchanpool; // pool of zero sized channels + int zchanpoollen; // number of zero sized channels in pool + int zchanpoolcap; // capacity of pool + + struct colist sigwaiters; // signal waiting coroutines + size_t nsigwaiters; + uint32_t sigmask; // signal mask from handler + int sigwatchers[32]; // signals being watched, reference counted + bool mainthread; // running on the main thread + int sigqueue[32]; // queue of signals waiting for delivery +#ifndef _WIN32 + struct sigaction sigold[32]; // previous signal handlers, for restoring +#endif + char *sigstack; // stack for signals + int sigcrashed; + + int64_t rand_seed; // used for PRNG (non-crypto) +#ifdef __linux__ + void *libbsd_handle; // used for arc4random_buf + void (*arc4random_buf)(void *, size_t); +#endif + +#ifndef NECO_NOWORKER + struct worker *worker; + pthread_mutex_t iomu; + struct colist iolist; + size_t niowaiters; +#endif + + unsigned int burstcount; +}; + +#define RUNTIME_DEFAULTS (struct runtime) { 0 } + +static __thread struct runtime *rt = NULL; + +static void rt_release(void) { + free0(rt); + rt = NULL; +} + +// coself returns the currently running coroutine. +static struct coroutine *coself(void) { + return (struct coroutine*)sco_udata(); +} + +noinline +static void coexit(bool async); + +// Use coyield() instead of sco_yield() in neco so that an async cancelation +// can be detected +static void coyield(void) { + sco_yield(); + struct coroutine *co = coself(); + if (co->canceled && co->canceltype == NECO_CANCEL_ASYNC) { + coexit(true); + } +} + +// Schedule a coroutine to resume at the next pause phase. +// It's expected that the coroutine is currently paused. +// The difference between sched_resume(co) and sco_resume(co->id) is that with +// sched_resume() the current coroutine is not yielded by the call, allowing +// for multiple coroutines to be scheduled as a batch. While sco_resume() will +// immediately yield the current coroutine to the provided coroutine. +static void sched_resume(struct coroutine *co) { + colist_push_back(&rt->resumers, co); + rt->nresumers++; +} + +static void yield_for_sched_resume(void) { + // Two yields may be required. The first will resume the paused coroutines + // that are in the resumers queue. The second yield ensures that the + // current coroutine waits until _after_ the others have resumed. + if (rt->nresumers > 0) { + coyield(); + } + coyield(); +} + +static struct coroutine *evexists(int fd, enum evkind kind) { + struct coroutine *key = &(struct coroutine){ .evfd = fd, .evkind = kind }; + struct coroutine *iter = evmap_iter(&rt->evwaiters, key); + return iter && iter->evfd == fd && iter->evkind == kind ? iter : NULL; +} + +#if defined(_WIN32) +#include +static int is_main_thread(void) { + return IsGUIThread(false); +} +#elif defined(__linux__) || defined(__EMSCRIPTEN__) +int gettid(void); +static int is_main_thread(void) { + return getpid() == gettid(); +} +#else +int pthread_main_np(void); +static int is_main_thread(void) { + return pthread_main_np(); +} +#endif + +static int _is_main_thread_(void) { + if (!rt) { + return NECO_PERM; + } + return is_main_thread(); +} + +/// Test if coroutine is running on main thread. +/// @return 1 for true, 0 for false, or a negative value for error. +int neco_is_main_thread(void) { + int ret = _is_main_thread_(); + error_guard(ret); + return ret; +} + +static bool costackget(struct coroutine *co) { + return stack_get0(&rt->stkmgr, &co->stack) == 0; +} + +static void costackfree(struct coroutine *co) { + stack_put(&rt->stkmgr, &co->stack); +} + +static size_t costacksize(struct coroutine *co) { + return stack_size(&co->stack); +} + +static void *costackaddr(struct coroutine *co) { + return stack_addr(&co->stack); +} + +// Create a new coroutines with the provided coroutine function and stack size. +// Returns NULL if out of memory. +static struct coroutine *coroutine_new(void) { + struct coroutine *co = malloc0(sizeof(struct coroutine)); + if (!co) { + return NULL; + } + memset(co, 0, sizeof(struct coroutine)); + co->kind = COROUTINE; + if (!costackget(co)) { + free0(co); + return NULL; + } + co->next = co; + co->prev = co; + colist_init(&co->cancellist); + colist_init(&co->joinlist); + + // Return the coroutine in a non-running state. + return co; +} + +static void cofreeargs(struct coroutine *co) { + if (co->argv && co->argv != co->aargv) { + free0(co->argv); + } + co->argc = 0; + co->argv = NULL; +} + +static struct neco_chan *chan_fastmake(size_t data_size, size_t capacity, + bool as_generator); +static void chan_fastrelease(struct neco_chan *chan); +static void chan_fastretain(struct neco_chan *chan); + +static void coroutine_free(struct coroutine *co) { + if (co) { + cofreeargs(co); + costackfree(co); + free0(co); + } +} + + +#ifndef NECO_TESTING + +static __thread bool immediate_exit = false; +static __thread int immediate_exit_code = 0; + +void __neco_exit_prog(int code) { + immediate_exit = true; + immediate_exit_code = code; + neco_exit(); +} +#else +void __neco_exit_prog(int code) { + (void)code; + // Cannot exit program in testing +} +#endif + +static void cleanup(void *stack, size_t stack_size, void *udata) { + // The coroutine is finished. Cleanup resources + (void)stack; (void)stack_size; +#ifndef NECO_TESTING + if (immediate_exit) { + _Exit(immediate_exit_code); + } +#endif + struct coroutine *co = udata; +#ifndef NECO_NOPOOL + colist_push_back(&rt->pool, co); + rt->npool++; +#else + coroutine_free(co); +#endif +} + +static void coentry(void *udata) { + struct coroutine *co = udata; + co->id = sco_id(); + if (rt->costarter) { + rt->costarter->lastid = co->id; + co->starterid = rt->costarter->id; + } else { + co->starterid = 0; + } + rt->ntotal++; + comap_insert(&rt->all, co); + if (co->coroutine) { + co->coroutine(co->argc, co->argv); + } + coexit(false); +} + + +static int start(void(*coroutine)(int, void**), int argc, va_list *args, + void *argv[], neco_gen **gen, size_t gen_data_size) +{ + struct coroutine *co; +#ifndef NECO_NOPOOL + co = colist_pop_front(&rt->pool); + if (co) { + rt->npool--; + co->pool_ts = 0; + } else { + co = coroutine_new(); + } +#else + co = coroutine_new(); +#endif + if (!co) { + goto fail; + } + co->coroutine = coroutine; + co->canceltype = env_canceltype; + co->cancelstate = env_cancelstate; + + if (gen) { + co->gen = chan_fastmake(gen_data_size, 0, true); + if (!co->gen) { + goto fail; + } + chan_fastretain(co->gen); + *gen = (neco_gen*)co->gen; + } + + // set the arguments + if (argc <= (int)(sizeof(co->aargv)/sizeof(void*))) { + co->argv = co->aargv; + } else { + co->argv = malloc0((size_t)argc * sizeof(void*)); + if (!co->argv) { + goto fail; + } + } + co->argc = argc; + for (int i = 0; i < argc; i++) { + co->argv[i] = args ? va_arg(*args, void*) : argv[i]; + } + struct sco_desc desc = { + .stack = costackaddr(co), + .stack_size = costacksize(co), + .entry = coentry, + .cleanup = cleanup, + .udata = co, + }; + rt->costarter = coself(); + sco_start(&desc); + return NECO_OK; +fail: + if (co) { + if (co->gen) { + chan_fastrelease(co->gen); // once for fastmake + chan_fastrelease(co->gen); // once for fastretain + } + coroutine_free(co); + } + return NECO_NOMEM; +} + +#ifndef NECO_NOSIGNALS + +// These are signals that are allowed to be "watched" by a coroutine using +// the neco_signal_watch() and neco_signal_wait() operations. +static const int ALLOWED_SIGNALS[] = { + SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGPIPE, SIGUSR1, SIGUSR2, SIGALRM +}; + +// These are signals that trigger faults, such as a stack overflow or an +// invalid instruction. +// Notice that SIGABRT is intentionally not TRAPPED or ALLOWED. This is because +// it's been observed that trapping "Abort: 6" on Apple Silicon in Rosetta mode +// can lead to a dead lock that requires a "kill -9" to exit. +// Similar to issue: https://github.com/JuliaLang/julia/issues/42398 +static const int TRAPPED_SIGNALS[] = { + SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP +}; + +static void sigallowhandler(int signo) { + if (rt->sigwatchers[signo] == 0) { + // No coroutines are watching for this signal. Without an interested + // outside party, the program should exit immediately. + if (signo != SIGPIPE) { + sigexitnow(signo); + } + return; + } + // The Neco runtime has a signal watching for this specific signal. + // Queue the signal and let the runtime take over. + rt->sigmask |= (UINT32_C(1) << signo); + rt->sigqueue[signo]++; +} + +static void crash_and_exit(int signo) { +#ifndef NECO_TESTING + if (rt->mainthread) { + fprintf(stderr, "\n=== Crash on main thread ===\n"); + } else { + fprintf(stderr, "\n=== Crash on child thread ===\n"); + } + print_stacktrace(0, true); + fprintf(stderr, "\n"); +#endif + sigexitnow(signo); +} + +#ifdef NECO_TESTING +// During testing, allow for resetting the last crash. +void neco_reset_sigcrashed(void) { + if (rt) { + rt->sigcrashed = 0; + } +} +#endif + +static void sighandler(int signo, siginfo_t *info, void *ptr) { + (void)info; (void)ptr; + must(signo > 0 && signo < 32); + if (rt->sigcrashed) { + sigexitnow(rt->sigcrashed); + return; + } + for (size_t i = 0; i < sizeof(ALLOWED_SIGNALS)/sizeof(int); i++) { + if (signo == ALLOWED_SIGNALS[i]) { + sigallowhandler(signo); + return; + } + } + for (size_t i = 0; i < sizeof(TRAPPED_SIGNALS)/sizeof(int); i++) { + if (signo == TRAPPED_SIGNALS[i]) { + // Print the crash and exit. + rt->sigcrashed = signo; + crash_and_exit(signo); + return; + } + } +} +#endif + +// Set up a custom handler for certain signals +static int rt_handle_signals(void) { +#ifndef NECO_NOSIGNALS + if (!rt->mainthread) { + // Only handle signals on the main thread. + return NECO_OK; + } + static struct sigaction act = { 0 }; + sigemptyset(&act.sa_mask); + act.sa_sigaction = &sighandler; + act.sa_flags = SA_SIGINFO; + rt->sigstack = malloc0(NECO_SIGSTKSZ); + if (!rt->sigstack) { + return NECO_NOMEM; + } +#if NECO_SIGSTKSZ > 0 + act.sa_flags |= SA_ONSTACK; + stack_t ss = { + .ss_sp = rt->sigstack, + .ss_size = NECO_SIGSTKSZ, + .ss_flags = 0, + }; + must(sigaltstack(&ss, NULL) == 0); +#endif + for (size_t i = 0; i < sizeof(ALLOWED_SIGNALS)/sizeof(int); i++) { + int signo = ALLOWED_SIGNALS[i]; + must(sigaction(signo, &act, &rt->sigold[signo]) == 0); + } + for (size_t i = 0; i < sizeof(TRAPPED_SIGNALS)/sizeof(int); i++) { + int signo = TRAPPED_SIGNALS[i]; + must(sigaction(signo, &act, &rt->sigold[signo]) == 0); + } +#endif + return NECO_OK; +} + +// Restore the signal handlers +static void rt_restore_signal_handlers(void) { +#ifndef NECO_NOSIGNALS + if (!rt->mainthread) { + // Only handle signals on the main thread. + return; + } + if (!rt->sigstack) { + // Allocation failed during the initialization. + return; + } + free0(rt->sigstack); + for (size_t i = 0; i < sizeof(ALLOWED_SIGNALS)/sizeof(int); i++) { + int signo = ALLOWED_SIGNALS[i]; + must(sigaction(signo, &rt->sigold[signo], NULL) == 0); + } + for (size_t i = 0; i < sizeof(TRAPPED_SIGNALS)/sizeof(int); i++) { + int signo = TRAPPED_SIGNALS[i]; + must(sigaction(signo, &rt->sigold[signo], NULL) == 0); + } +#endif +} + +static void rt_sched_signal_step(void) { + for (int signo = 0; signo < 32; signo++) { + if (rt->sigqueue[signo] == 0) { + continue; + } else if (rt->sigwatchers[signo] == 0) { + // The signal is no longer being watched. + rt->sigqueue[signo]--; + if (rt->sigqueue[signo] == 0) { + rt->sigmask &= ~(UINT32_C(1) << signo); + } + if (signo != SIGPIPE) { + sigexitnow(signo); + } + } else { + struct coroutine *co = rt->sigwaiters.head.next; + while (co != (struct coroutine*)&rt->sigwaiters.tail) { + struct coroutine *next = co->next; + if (co->sigmask & (UINT32_C(1) << signo)) { + // A coroutine is waiting on this signal. + // Assign the signal to its sigmask and resume + // the coroutine. + co->sigmask = (uint32_t)signo; + rt->sigqueue[signo]--; + if (rt->sigqueue[signo] == 0) { + rt->sigmask &= ~(UINT32_C(1) << signo); + } + sco_resume(co->id); + next = (struct coroutine*)&rt->sigwaiters.tail; + } + co = next; + } + } + break; + } +} + +#define NEVENTS 16 + +static void rt_sched_event_step(int64_t timeout) { + (void)timeout; +#if defined(NECO_POLL_EPOLL) + struct epoll_event evs[NEVENTS]; + int timeout_ms = (int)(timeout/NECO_MILLISECOND); + int nevents = epoll_wait(rt->qfd, evs, NEVENTS, timeout_ms); +#elif defined(NECO_POLL_KQUEUE) + struct kevent evs[NEVENTS]; + struct timespec timeoutspec = { .tv_nsec = timeout }; + int nevents = kevent0(rt->qfd, NULL, 0, evs, NEVENTS, &timeoutspec); +#else + int nevents = 0; +#endif + // A sane kqueue/epoll instance can only ever return the number of + // events or EINTR when waiting on events. + // If the queue was interrupted, the error can be safely ignored because + // the sighandler() will responsibly manage the incoming signals, which + // are then dealt with by this function, right after the event loop. + must(nevents != -1 || errno == EINTR); + + // Consume each event. + for (int i = 0; i < nevents; i++) { +#if defined(NECO_POLL_EPOLL) + int fd = evs[i].data.fd; + bool read = evs[i].events & EPOLLIN; + bool write = evs[i].events & EPOLLOUT; +#elif defined(NECO_POLL_KQUEUE) + int fd = (int)evs[i].ident; + bool read = evs[i].filter == EVFILT_READ; + bool write = evs[i].filter == EVFILT_WRITE; +#else + int fd = 0; + bool read = false; + bool write = false; +#endif + // For linux, a single event may describe both a read and write status + // for a file descriptor so we have to break them apart and deal with + // each one independently. + while (read || write) { + enum evkind kind; + if (read) { + kind = EVREAD; + read = false; + } else { + kind = EVWRITE; + write = false; + } + // Now that we have an event type (read or write) and a file + // descriptor, it's time to wake up the coroutines that are waiting + // on that event. + struct coroutine *key = &(struct coroutine) { + .evfd = fd, + .evkind = kind, + }; + struct coroutine *co = evmap_iter(&rt->evwaiters, key); + while (co && co->evfd == fd && co->evkind == kind) { + sco_resume(co->id); + co = evmap_next(&rt->evwaiters, co); + } + } + } +} + +// Adds a and b, clamping overflows to INT64_MIN or INT64_MAX +TESTING_EXTERN(static) +int64_t i64_add_clamp(int64_t a, int64_t b) { + if (!((a ^ b) < 0)) { // Opposite signs can't overflow + if (a > 0) { + if (b > INT64_MAX - a) { + return INT64_MAX; + } + } else if (b < INT64_MIN - a) { + return INT64_MIN; + } + } + return a + b; +} + +#define MAX_TIMEOUT 500000000 // 500 ms + +// Handle paused couroutines. +static void rt_sched_paused_step(void) { + // Resume all the paused coroutines that are waiting for immediate + // attention. + struct coroutine *co = colist_pop_front(&rt->resumers); + while (co) { + sco_resume(co->id); + rt->nresumers--; + co = colist_pop_front(&rt->resumers); + } + + // Calculate the minimum timeout for this step. + int64_t timeout = MAX_TIMEOUT; + if (timeout > 0 && sco_info_scheduled() > 0) { + // No timeout when there is at least one yielding coroutine or + // when there are pending signals. + timeout = 0; + } + if (timeout > 0 && rt->sigmask) { + // There are pending signals. + // Do not wait to deliver them. + timeout = 0; + } + if (timeout > 0 && rt->ndeadlines > 0) { + // There's at least one deadline coroutine. Use the one with + // the minimum 'deadline' value to determine the timeout. + int64_t min_deadline = dlqueue_first(&rt->deadlines)->deadline; + int64_t timeout0 = i64_add_clamp(min_deadline, -getnow()); + if (timeout0 < timeout) { + timeout = timeout0; + } + } + timeout = CLAMP(timeout, 0, MAX_TIMEOUT); + +#ifndef NECO_NOWORKER + if (rt->niowaiters > 0) { + timeout = 0; + while (1) { + pthread_mutex_lock(&rt->iomu); + struct coroutine *co = colist_pop_front(&rt->iolist); + pthread_mutex_unlock(&rt->iomu); + if (!co) { + break; + } + sco_resume(co->id); + } + } +#endif + + if (rt->nevwaiters > 0) { + // Event waiters need their own logic. + rt_sched_event_step(timeout); + } else if (timeout > 0) { + // There are sleepers (or signal waiters), but no event waiters. + // Therefore no need for an event queue. We can just do a simple sleep + // using the timeout provided the sleeper with the minimum deadline. + // A signal will interrupt this sleeper if it's running on the main + // thread. + nanosleep(&(struct timespec){ + .tv_sec = timeout/1000000000, + .tv_nsec = timeout%1000000000, + }, 0); + } + + // Handle pending signals from the sighandler(). + if (rt->sigmask) { + // There's at least one signal pending. + rt_sched_signal_step(); + } + + // Check for deadliners and wake them up. + int64_t now = getnow(); + co = dlqueue_first(&rt->deadlines); + while (co && co->deadline < now) { + // Deadline has been reached. Resume the coroutine + co->deadlined = true; + sco_resume(co->id); + co = dlqueue_next(&rt->deadlines, co); + } +} + +// Resource collection step +static void rt_rc_step(void) { + int64_t now = getnow(); + if (rt->nevwaiters == 0 && rt->qfd > 0) { + if (now - rt->qfdcreated > NECO_MILLISECOND * 100) { + // Close the event queue file descriptor if it's no longer needed. + // When the queue goes unused for more than 100 ms it will + // automatically be closed. + close(rt->qfd); + rt->qfd = 0; + } + } + // Deal with coroutine pools. + if (rt->npool > 0) { + // First assign timestamps to newly pooled coroutines, then remove + // coroutines that have been at waiting in the pool more than 100 ms. + struct coroutine *co = rt->pool.tail.prev; + while (co != (struct coroutine*)&rt->pool.head && co->pool_ts == 0) { + co->pool_ts = now; + co = co->prev; + } + while (rt->pool.head.next != (struct coroutine*)&rt->pool.tail) { + co = (struct coroutine*)rt->pool.head.next; + if (now - co->pool_ts < NECO_MILLISECOND * 100) { + break; + } + remove_from_list(co); + rt->npool--; + coroutine_free(co); + } + } +} + +static int rt_scheduler(void) { + // The schdeduler is just a standard sco runtime loop. When thare are + // paused coroutines the paused step executes. That's where the real + // magic happens. + int ret = NECO_OK; + while (sco_active()) { + if (sco_info_paused() > 0) { + rt_sched_paused_step(); + } + rt_rc_step(); + sco_resume(0); + } + // Cleanup extra resources + if (rt->qfd) { + close(rt->qfd); + } + struct coroutine *co = colist_pop_front(&rt->pool); + while (co) { + coroutine_free(co); + co = colist_pop_front(&rt->pool); + } + return ret; +} + +static void rt_freezchanpool(void) { + for (int i = 0; i < rt->zchanpoollen; i++) { + free0(rt->zchanpool[i]); + } + if (rt->zchanpool) { + free0(rt->zchanpool); + } +} + +static struct stack_opts stack_opts_make(void) { + return (struct stack_opts) { + .stacksz = NECO_STACKSIZE, + .defcap = NECO_DEFCAP, + .maxcap = NECO_MAXCAP, + .gapsz = NECO_GAPSIZE, +#ifdef NECO_USEGUARDS + .useguards = true, +#endif +#ifdef NECO_NOSTACKFREELIST + .nostackfreelist = true, +#endif +#ifdef NECO_USEHEAPSTACK + .onlymalloc = true, +#endif + }; +} + +static void init_networking(void) { +#ifdef _WIN32 + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 2); + WSAStartup(wVersionRequested, &wsaData); +#endif +} + +static void rt_release_dlhandles(void) { +#if __linux__ + if (rt->libbsd_handle) { + dlclose(rt->libbsd_handle); + } +#endif +} + +static int run(void(*coroutine)(int, void**), int nargs, va_list *args, + void *argv[]) +{ + init_networking(); + rt = malloc0(sizeof(struct runtime)); + if (!rt) { + return NECO_NOMEM; + } + *rt = RUNTIME_DEFAULTS; + rt->mainthread = is_main_thread(); + rt->id = atomic_fetch_add(&next_runtime_id, 1); + + + struct stack_opts sopts = stack_opts_make(); + stack_mgr_init(&rt->stkmgr, &sopts); + + colist_init(&rt->sigwaiters); + colist_init(&rt->pool); + colist_init(&rt->resumers); + + // Initialize the signal handlers + int ret = rt_handle_signals(); + if (ret != NECO_OK) { + goto fail; + } + +#ifndef NECO_NOWORKER + struct worker_opts wopts = { + .max_threads = NECO_MAXWORKER, + .max_thread_entries = NECO_MAXRINGSIZE, + .malloc = malloc0, + .free = free0, + }; + rt->worker = worker_new(&wopts); + if (!rt->worker) { + ret = NECO_NOMEM; + goto fail; + } + pthread_mutex_init(&rt->iomu, 0); + colist_init(&rt->iolist); +#endif + + + // Start the main coroutine. Actually, it's just queued to run first. + ret = start(coroutine, nargs, args, argv, 0, 0); + if (ret != NECO_OK) { + goto fail; + } + + // Run the scheduler + ret = rt_scheduler(); + +fail: + stack_mgr_destroy(&rt->stkmgr); + rt_freezchanpool(); + rt_restore_signal_handlers(); + rt_release_dlhandles(); +#ifndef NECO_NOWORKER + worker_free(rt->worker); +#endif + rt_release(); + return ret; +} + +static int startv(void(*coroutine)(int argc, void *argv[]), int argc, + va_list *args, void *argv[], neco_gen **gen, size_t gen_data_size) +{ + if (!coroutine || argc < 0) { + return NECO_INVAL; + } + int ret; + if (!rt) { + ret = run(coroutine, argc, args, argv); + } else { + ret = start(coroutine, argc, args, argv, gen, gen_data_size); + } + return ret; +} + +/// Starts a new coroutine. +/// +/// If this is the first coroutine started for the program (or thread) then +/// this will also create a neco runtime scheduler which blocks until the +/// provided coroutine and all of its subsequent child coroutines finish. +/// +/// **Example** +/// +/// ``` +/// // Here we'll start a coroutine that prints "hello world". +/// +/// void coroutine(int argc, void *argv[]) { +/// char *msg = argv[0]; +/// printf("%s\n", msg); +/// } +/// +/// neco_start(coroutine, 1, "hello world"); +/// ``` +/// +/// @param coroutine The coroutine that will soon run +/// @param argc Number of arguments +/// @param ... Arguments passed to the coroutine +/// @return NECO_OK Success +/// @return NECO_NOMEM The system lacked the necessary resources +/// @return NECO_INVAL An invalid parameter was provided +int neco_start(void(*coroutine)(int argc, void *argv[]), int argc, ...) { + va_list args; + va_start(args, argc); + int ret = startv(coroutine, argc, &args, 0, 0, 0); + va_end(args); + error_guard(ret); + return ret; +} + + +/// Starts a new coroutine using an array for arguments. +/// @see neco_start +int neco_startv(void(*coroutine)(int argc, void *argv[]), int argc, + void *argv[]) +{ + int ret = startv(coroutine, argc, 0, argv, 0, 0); + error_guard(ret); + return ret; +} + +static int yield(void) { + if (!rt) { + return NECO_PERM; + } + coyield(); + return NECO_OK; +} + +/// Cause the calling coroutine to relinquish the CPU. +/// The coroutine is moved to the end of the queue. +/// @return NECO_OK Success +/// @return NECO_PERM Operation called outside of a coroutine +int neco_yield(void) { + int ret = yield(); + error_guard(ret); + return ret; +} + +// cofind returns the coroutine belonging to the provided identifier. +static struct coroutine *cofind(int64_t id) { + struct coroutine *co = NULL; + if (rt) { + co = comap_search(&rt->all, &(struct coroutine){ .id = id }); + } + return co; +} + +// pause the currently running coroutine with the provided deadline. +static void copause(int64_t deadline) { + struct coroutine *co = coself(); + // Cannot pause an already canceled or deadlined coroutine. + if (!co->canceled && !co->deadlined) { + co->deadline = deadline; + if (co->deadline < INT64_MAX) { + dlqueue_insert(&rt->deadlines, co); + rt->ndeadlines++; + } + co->paused = true; + sco_pause(); + co->paused = false; + if (co->deadline < INT64_MAX) { + dlqueue_delete(&rt->deadlines, co); + rt->ndeadlines--; + } + co->deadline = 0; + } +} + +static int sleep0(int64_t deadline) { + struct coroutine *co = coself(); + rt->nsleepers++; + copause(deadline); + rt->nsleepers--; + int ret = co->canceled ? NECO_CANCELED : NECO_OK; + co->canceled = false; + co->deadlined = false; + return ret; +} + +static int sleep_dl(int64_t deadline) { + if (!rt) { + return NECO_PERM; + } else if (getnow() > deadline) { + return NECO_TIMEDOUT; + } + return sleep0(deadline); +} + +/// Same as neco_sleep() but with a deadline parameter. +int neco_sleep_dl(int64_t deadline) { + int ret = sleep_dl(deadline); + async_error_guard(ret); + return ret; +} + +/// Causes the calling coroutine to sleep until the number of specified +/// nanoseconds have elapsed. +/// @param nanosecs duration nanoseconds +/// @return NECO_OK Coroutine slept until nanosecs elapsed +/// @return NECO_TIMEDOUT nanosecs is a negative number +/// @return NECO_CANCELED Operation canceled +/// @return NECO_PERM Operation called outside of a coroutine +/// @see neco_sleep_dl() +int neco_sleep(int64_t nanosecs) { + return neco_sleep_dl(i64_add_clamp(getnow(), nanosecs)); +} + +static int64_t getid(void) { + if (!rt) { + return NECO_PERM; + } + return sco_id(); +} + +/// Returns the identifier for the currently running coroutine. +/// +/// This value is guaranteed to be unique for the duration of the program. +/// @return The coroutine identifier +/// @return NECO_PERM Operation called outside of a coroutine +int64_t neco_getid(void) { + int64_t ret = getid(); + error_guard(ret); + return ret; +} + +static int64_t lastid(void) { + if (!rt) { + return NECO_PERM; + } + return coself()->lastid; +} + +/// Returns the identifier for the coroutine started by the current coroutine. +/// +/// For example, here a coroutine is started and its identifer is then +/// retreived. +/// +/// ``` +/// neco_start(coroutine, 0); +/// int64_t id = neco_lastid(); +/// ``` +/// +/// @return A coroutine identifier, or zero if the current coroutine has not +/// yet started any coroutines. +/// @return NECO_PERM Operation called outside of a coroutine +int64_t neco_lastid(void) { + int64_t ret = lastid(); + error_guard(ret); + return ret; +} + +static int64_t starterid(void) { + if (!rt) { + return NECO_PERM; + } + return coself()->starterid; +} + +// checkdl checks if the coroutine has been canceled or timedout, resets the +// co->canceled and co->deadlined flags to false, and returns the appropriate +// error code. This is typically used from *_dl operations. +static int checkdl(struct coroutine *co, int64_t deadline) { + if (!co->canceled && !co->deadlined && deadline == INT64_MAX) { + // Most cases. + return NECO_OK; + } + bool canceled = co->canceled; + bool deadlined = co->deadlined; + co->canceled = false; + co->deadlined = false; + if (!canceled && !deadlined && deadline < INT64_MAX && getnow() > deadline){ + deadlined = true; + } + return canceled ? NECO_CANCELED : deadlined ? NECO_TIMEDOUT : NECO_OK; +} + +/// Get the identifier for the coroutine that started the current coroutine. +/// +/// ``` +/// void child_coroutine(int argc, void *argv[]) { +/// int parent_id = neco_starterid(); +/// // The parent_id is equal as the neco_getid() from the parent_coroutine +/// // below. +/// } +/// +/// void parent_coroutine(int argc, void *argv[]) { +/// int id = neco_getid(); +/// neco_start(child_coroutine, 0); +/// } +/// ``` +/// @return A coroutine identifier, or zero if the coroutine is the first +/// coroutine started. +int64_t neco_starterid(void) { + int64_t ret = starterid(); + error_guard(ret); + return ret; +} + +static int cancel_dl(int64_t id, int64_t deadline) { + struct coroutine *co = coself(); + if (!co) { + return NECO_PERM; + } + struct coroutine *cotarg; + while (1) { + cotarg = cofind(id); + if (!cotarg) { + return NECO_NOTFOUND; + } + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + return ret; + } + if (cotarg->cancelstate == NECO_CANCEL_ENABLE) { + // Coroutine was found and its cancel state is enabled. + // Set the cancel flag and wake it up. + cotarg->canceled = true; + sco_resume(id); + coyield(); + return NECO_OK; + } + // Target coroutine is blocking their cancel state. + // Wait for it to be released. + colist_push_back(&cotarg->cancellist, co); + cotarg->ncancellist++; + copause(deadline); + remove_from_list(co); + cotarg->ncancellist--; + } +} + +int neco_cancel_dl(int64_t id, int64_t deadline) { + int ret = cancel_dl(id, deadline); + async_error_guard(ret); + return ret; +} + +int neco_cancel(int64_t id) { + return neco_cancel_dl(id, INT64_MAX); +} + +//////////////////////////////////////////////////////////////////////////////// +// signals +//////////////////////////////////////////////////////////////////////////////// + +static int signal_watch(int signo) { + if (signo < 1 || signo > 31) { + return NECO_INVAL; + } + struct coroutine *co = coself(); + if (!co || !rt->mainthread) { + return NECO_PERM; + } + if ((co->sigwatch & (UINT32_C(1) << signo)) == 0) { + co->sigwatch |= UINT32_C(1) << signo; + rt->sigwatchers[signo]++; + } + return NECO_OK; +} + + +/// Have the current coroutine watch for a signal. +/// +/// This can be used to intercept or ignore signals. +/// +/// Signals that can be watched: SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGPIPE, +/// SIGUSR1, SIGUSR2, SIGALRM +/// +/// Signals that _can not_ be watched: SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP +/// +/// **Example** +/// +/// ``` +/// // Set up this coroutine to watich for the SIGINT (Ctrl-C) and SIGQUIT +/// // (Ctrl-\) signals. +/// neco_signal_watch(SIGINT); +/// neco_signal_watch(SIGQUIT); +/// +/// printf("Waiting for Ctrl-C or Ctrl-\\ signals...\n"); +/// int sig = neco_signal_wait(); +/// if (sig == SIGINT) { +/// printf("\nReceived Ctrl-C\n"); +/// } else if (sig == SIGQUIT) { +/// printf("\nReceived Ctrl-\\\n"); +/// } +/// +/// // The neco_signal_unwatch is used to stop watching. +/// neco_signal_unwatch(SIGINT); +/// neco_signal_unwatch(SIGQUIT); +/// ``` +/// +/// @param signo The signal number +/// @return NECO_OK Success +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @see Signals +int neco_signal_watch(int signo) { + int ret = signal_watch(signo); + error_guard(ret); + return ret; +} + +static int signal_unwatch(int signo) { + if (signo < 1 || signo > 31) { + return NECO_INVAL; + } + struct coroutine *co = coself(); + if (!co || !rt->mainthread) { + return NECO_PERM; + } + if ((co->sigwatch & (UINT32_C(1) << signo)) != 0) { + co->sigwatch &= ~(UINT32_C(1) << signo); + rt->sigwatchers[signo]--; + } + return NECO_OK; +} + +/// Stop watching for a siganl to arrive +/// @param signo The signal number +/// @return NECO_OK on success or an error +int neco_signal_unwatch(int signo) { + int ret = signal_unwatch(signo); + error_guard(ret); + return ret; +} + +static int signal_wait_dl(int64_t deadline) { + struct coroutine *co = coself(); + if (!rt || !rt->mainthread) { + return NECO_PERM; + } + if (co->sigwatch == 0) { + return NECO_NOSIGWATCH; + } + if (co->canceled) { + co->canceled = false; + return NECO_CANCELED; + } + co->sigmask = co->sigwatch; + colist_push_back(&rt->sigwaiters, co); + rt->nsigwaiters++; + copause(deadline); + remove_from_list(co); + rt->nsigwaiters--; + int signo = (int)co->sigmask; + co->sigmask = 0; + int ret = checkdl(co, INT64_MAX); + return ret == NECO_OK ? signo : ret; +} + +/// Same as neco_signal_wait() but with a deadline parameter. +int neco_signal_wait_dl(int64_t deadline) { + int ret = signal_wait_dl(deadline); + async_error_guard(ret); + return ret; +} + +/// Wait for a signal to arrive. +/// @return A signal number or an error. +/// @return NECO_PERM +/// @return NECO_NOSIGWATCH if not currently watching for signals. +/// @return NECO_CANCELED +/// @see Signals +/// @see neco_signal_wait_dl() +int neco_signal_wait(void) { + return neco_signal_wait_dl(INT64_MAX); +} + +static int64_t now0(void) { + if (!rt) { + return NECO_PERM; + } else { + return getnow(); + } +} + +/// Get the current time. +/// +/// This operation calls gettime(CLOCK_MONOTONIC) to retreive a monotonically +/// increasing value that is not affected by discontinuous jumps in the system +/// time. +/// +/// This value IS NOT the same as the local system time; for that the user +/// should call gettime(CLOCK_REALTIME). +/// +/// The main purpose of this function to work with operations that use +/// deadlines, i.e. functions with the `*_dl()` suffix. +/// +/// **Example** +/// +/// ``` +/// // Receive a message from a channel using a deadline of one second from now. +/// int ret = neco_chan_recv_dl(ch, &msg, neco_now() + NECO_SECOND); +/// if (ret == NECO_TIMEDOUT) { +/// // The operation timed out +/// } +/// ``` +/// +/// @return On success, the current time as nanoseconds. +/// @return NECO_PERM Operation called outside of a coroutine +/// @see Time +int64_t neco_now(void) { + int64_t now = now0(); + error_guard(now); + return now; +} + +#if defined(NECO_TESTING) +// Allow for tests to set a fail flag for testing event queue failures. +__thread bool neco_fail_cowait = false; +#endif + +#if defined(NECO_POLL_EPOLL) +static int wait_dl_addevent(struct coroutine *co, int fd, enum evkind kind) { + (void)co; + struct epoll_event ev = { 0 }; + ev.data.fd = fd; + if (kind == EVREAD) { + ev.events = EPOLLIN | EPOLLONESHOT; + ev.events |= evexists(fd, EVWRITE) ? EPOLLOUT : 0; + } else { + ev.events = EPOLLOUT | EPOLLONESHOT; + ev.events |= evexists(fd, EVREAD) ? EPOLLIN : 0; + } + int ret = epoll_ctl0(rt->qfd, EPOLL_CTL_MOD, fd, &ev); + if (ret == -1) { + ret = epoll_ctl0(rt->qfd, EPOLL_CTL_ADD, fd, &ev); + } + return ret; +} +static void wait_dl_delevent(struct coroutine *co, int fd, enum evkind kind) { + // Using oneshot. Nothing to do. + (void)co; (void)fd; (void)kind; +} + +#elif defined(NECO_POLL_KQUEUE) +static int wait_dl_addevent(struct coroutine *co, int fd, enum evkind kind) { + (void)co; + int ret = 0; + if (!evexists(fd, kind)) { + struct kevent ev = { + .ident = (uintptr_t)fd, + .flags = EV_ADD | EV_ONESHOT, + .filter = kind == EVREAD ? EVFILT_READ : EVFILT_WRITE, + }; + ret = kevent0(rt->qfd, &ev, 1, NULL, 0, NULL); + } + return ret; +} + +static void wait_dl_delevent(struct coroutine *co, int fd, enum evkind kind) { + // Using oneshot. Nothing to do. + (void)co; (void)fd; (void)kind; +} +#endif + +// wait_dl makes the current coroutine wait for the file descriptor to be +// available for reading or writing. +static int wait_dl(int fd, enum evkind kind, int64_t deadline) { + if (kind != EVREAD && kind != EVWRITE) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } + struct coroutine *co = coself(); +#if !defined(NECO_POLL_EPOLL) && !defined(NECO_POLL_KQUEUE) + // Windows and Emscripten only yield, until support for events queues + // are enabled. + (void)fd; (void)deadline; + (void)evmap_insert; (void)evmap_delete; (void)evexists; + + sco_yield(); +#else + if (rt->qfd == 0) { + // The scheduler currently does not have an event queue for handling + // file events. Create one now. This new queue will be shared for the + // entirety of the scheduler. It will be automatically freed when it's + // no longer needed, by the scheduler. + rt->qfd = evqueue0(); + if (rt->qfd == -1) { + // Error creating the event queue. This is usually due to a system + // that is low on resources or is limiting the number of file + // descriptors allowed by a program, e.g. ulimit. + rt->qfd = 0; + return -1; + } + // The queue was successfully created. + rt->qfdcreated = getnow(); + } + + int ret = wait_dl_addevent(co, fd, kind); + if (ret == -1) { + return -1; + } + + co->evfd = fd; + co->evkind = kind; + + // Add this coroutine, chaining it to the distinct fd/kind. + // This creates a unique record in the evwaiters list + evmap_insert(&rt->evwaiters, co); + rt->nevwaiters++; + + // Now wait for the scheduler to wake this coroutine up again. + copause(deadline); + + // Delete from evwaiters. + evmap_delete(&rt->evwaiters, co); + rt->nevwaiters--; + + co->evfd = 0; + co->evkind = 0; + + wait_dl_delevent(co, fd, kind); +#endif + return checkdl(co, INT64_MAX); +} + +/// Same as neco_wait() but with a deadline parameter. +int neco_wait_dl(int fd, int mode, int64_t deadline) { + int ret = wait_dl(fd, mode, deadline); + async_error_guard(ret); + return ret; +} + +/// Wait for a file descriptor to be ready for reading or writing. +/// +/// Normally one should use neco_read() and neco_write() to read and write +/// data. But there may be times when you need more involved logic or to use +/// alternative functions such as `recvmsg()` or `sendmsg()`. +/// +/// ``` +/// while (1) { +/// int n = recvmsg(sockfd, msg, MSG_DONTWAIT); +/// if (n == -1) { +/// if (errno == EAGAIN) { +/// // The socket is not ready for reading. +/// neco_wait(sockfd, NECO_WAIT_READ); +/// continue; +/// } +/// // Socket error. +/// return; +/// } +/// // Message received. +/// break; +/// } +/// ``` +/// +/// @param fd The file descriptor +/// @param mode NECO_WAIT_READ or NECO_WAIT_WRITE +/// @return NECO_OK Success +/// @return NECO_NOMEM The system lacked the necessary resources +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_ERROR Check errno for more info +/// @see Posix2 +int neco_wait(int fd, int mode) { + return neco_wait_dl(fd, mode, INT64_MAX); +} + +// cowait makes the current coroutine wait for the file descriptor to be +// available for reading or writing. Any kind of error encountered by this +// operation will cause the coroutine to be rescheduled, with the error +// gracefully being ignored, while leaving the file, coroutine, and scheduler +// in the state that it was prior to calling the function. +// Alternatively, the wait_dl can be used if the error is needed. +static void cowait(int fd, enum evkind kind, int64_t deadline) { + int ret = wait_dl(fd, kind, deadline); + if (ret == NECO_CANCELED) { + // The cancel flag should be set to true if the wait was canceled, + // because the caller is responsible for handling deadlines and + // checking the canceled flag upon return. + struct coroutine *co = coself(); + co->canceled = true; + } + if (ret != NECO_OK) { + coyield(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Networking code +//////////////////////////////////////////////////////////////////////////////// + +#ifndef NECO_NOWORKER + +struct ioread { + int fd; + void *data; + size_t count; + ssize_t res; + struct runtime *rt; + struct coroutine *co; +} aligned16; + +static void ioread(void *udata) { + struct ioread *info = udata; + info->res = read0(info->fd, info->data, info->count); + if (info->res == -1) { + info->res = -errno; + } + pthread_mutex_lock(&info->rt->iomu); + colist_push_back(&info->rt->iolist, info->co); + pthread_mutex_unlock(&info->rt->iomu); +} + +static ssize_t read1(int fd, void *data, size_t nbytes) { + ssize_t n; + bool nowork = false; +#ifdef NECO_TESTING + if (neco_fail_read_counter > 0) { + nowork = true; + } +#endif + if (!nowork) { + nowork = true; + struct coroutine *co = coself(); + struct ioread info = { + .fd = fd, + .data = data, + .count = nbytes, + .co = co, + .rt = rt, + }; +#ifdef NECO_USEROUNDROBINPIN + int64_t pin = -1; +#else + int64_t pin = co->id; +#endif + if (worker_submit(rt->worker, pin, ioread, &info)) { + rt->niowaiters++; + sco_pause(); + rt->niowaiters--; + n = info.res; + if (n < 0) { + errno = -n; + n = -1; + } + nowork = false; + } + } + if (nowork) { + n = read0(fd, data, nbytes); + } + return n; +} +#else +#define read1 read0 +#endif + +static ssize_t read_dl(int fd, void *data, size_t nbytes, int64_t deadline) { + struct coroutine *co = coself(); + if (!co) { + errno = EPERM; + return -1; + } + while (1) { + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + errno = ret == NECO_CANCELED ? ECANCELED : ETIMEDOUT; + return -1; + } +#if NECO_BURST < 0 + cowait(fd, EVREAD, deadline); +#endif + ssize_t n = read1(fd, data, nbytes); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) { +#if NECO_BURST >= 0 + if (rt->burstcount == NECO_BURST) { + rt->burstcount = 0; + cowait(fd, EVREAD, deadline); + } else { + rt->burstcount++; + sco_yield(); + } +#endif + // continue; + } else { + return -1; + } + } else { + return n; + } + } +} + +/// Same as neco_read() but with a deadline parameter. +ssize_t neco_read_dl(int fd, void *buf, size_t count, int64_t deadline) { + ssize_t ret = read_dl(fd, buf, count, deadline); + async_error_guard(ret); + return ret; +} + +/// Read from a file descriptor. +/// +/// This operation attempts to read up to count from file descriptor fd +/// into the buffer starting at buf. +/// +/// This is a Posix wrapper function for the purpose of running in a Neco +/// coroutine. It's expected that the provided file descriptor is in +/// non-blocking state. +/// +/// @return On success, the number of bytes read is returned (zero indicates +/// end of file) +/// @return On error, value -1 (NECO_ERROR) is returned, and errno is set to +/// indicate the error. +/// @see Posix +/// @see neco_setnonblock() +/// @see https://www.man7.org/linux/man-pages/man2/read.2.html +ssize_t neco_read(int fd, void *buf, size_t count) { + return neco_read_dl(fd, buf, count, INT64_MAX); +} + +#ifdef NECO_TESTING +#define write1 write0 +// Allow for testing partial writes +__thread int neco_partial_write = 0; +static ssize_t write2(int fd, const void *data, size_t nbytes) { + bool fail = false; + if (neco_partial_write) { + neco_partial_write--; + nbytes = nbytes > 128 ? 128 : nbytes; + // nbytes /= 2; + fail = neco_partial_write == 0; + } + if (fail) { + errno = EIO; + return -1; + } + return write1(fd, data, nbytes); +} +#else +// In production, a write to a broken stdout or stderr pipe should end the +// program immediately. +inline +static ssize_t write1(int fd, const void *data, size_t nbytes) { + ssize_t n = write0(fd, data, nbytes); + if (n == -1 && errno == EPIPE && (fd == 1 || fd == 2)) { + // Broken pipe on stdout or stderr + _Exit(128+EPIPE); + } + return n; +} +#define write2 write1 +#endif + +#ifndef NECO_NOWORKER +struct iowrite { + int fd; + const void *data; + size_t count; + ssize_t res; + struct runtime *rt; + struct coroutine *co; +} aligned16; + +static void iowrite(void *udata) { + struct iowrite *info = udata; + info->res = write2(info->fd, info->data, info->count); + if (info->res == -1) { + info->res = -errno; + } + pthread_mutex_lock(&info->rt->iomu); + colist_push_back(&info->rt->iolist, info->co); + pthread_mutex_unlock(&info->rt->iomu); +} + +static ssize_t write3(int fd, const void *data, size_t nbytes) { + bool nowork = false; + ssize_t n; +#ifdef NECO_TESTING + if (neco_fail_write_counter > 0 || neco_partial_write > 0) { + nowork = true; + } +#endif + if (!nowork) { + nowork = true; + struct coroutine *co = coself(); + struct iowrite info = { + .fd = fd, + .data = data, + .count = nbytes, + .co = co, + .rt = rt, + }; +#ifdef NECO_USEROUNDROBINPIN + int64_t pin = -1; +#else + int64_t pin = co->id; +#endif + if (worker_submit(rt->worker, pin, iowrite, &info)) { + rt->niowaiters++; + sco_pause(); + rt->niowaiters--; + n = info.res; + if (n < 0) { + errno = -n; + n = -1; + } + nowork = false; + } + } + if (nowork) { + n = write2(fd, data, nbytes); + } + return n; +} +#else +#define write3 write2 +#endif + +static ssize_t write_dl(int fd, const void *data, size_t nbytes, + int64_t deadline) +{ + struct coroutine *co = coself(); + if (!co) { + errno = EPERM; + return -1; + } + ssize_t written = 0; + while (1) { + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + errno = ret == NECO_CANCELED ? ECANCELED : ETIMEDOUT; + return -1; + } + // size_t maxnbytes = CLAMP(nbytes, 0, 8096); + // ssize_t n = write2(fd, data, maxnbytes); + ssize_t n = write3(fd, data, nbytes); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) { + cowait(fd, EVWRITE, deadline); + } else if (written == 0) { + return -1; + } else { + // There was an error but data has also been written. It's + // important to notify the caller about the amount of data + // written. The caller will need to then check to see if the + // return value is less than nbytes, and if so check + // the system errno. + return written; + } + } else if (n > 0) { + nbytes -= (size_t)n; + written += (size_t)n; + data = (char*)data + n; + } + if (nbytes == 0) { + break; + } + if (n >= 0) { + // Some data was written but there's more yet. + // Avoiding starving the other coroutines. + coyield(); + } + } + return written; +} + +/// Same as neco_write() but with a deadline parameter. +ssize_t neco_write_dl(int fd, const void *buf, size_t count, + int64_t deadline) +{ + ssize_t ret = write_dl(fd, buf, count, deadline); + async_error_guard(ret); + if (ret >= 0 && (size_t)ret < count) { + lasterr = NECO_PARTIALWRITE; + } + return ret; +} + +/// Write to a file descriptor. +/// +/// This operation attempts to write all bytes in the buffer starting at buf +/// to the file referred to by the file descriptor fd. +/// +/// This is a Posix wrapper function for the purpose of running in a Neco +/// coroutine. It's expected that the provided file descriptor is in +/// non-blocking state. +/// +/// One difference from the Posix version is that this function will attempt to +/// write _all_ bytes in buffer. The programmer, at their discretion, may +/// considered it as an error when fewer than count is returned. If so, the +/// neco_lasterr() will return the NECO_PARTIALWRITE. +/// +/// @return On success, the number of bytes written is returned. +/// @return On error, value -1 (NECO_ERROR) is returned, and errno is set to +/// indicate the error. +/// @see Posix +/// @see neco_setnonblock() +/// @see https://www.man7.org/linux/man-pages/man2/write.2.html +ssize_t neco_write(int fd, const void *buf, size_t count) { + return neco_write_dl(fd, buf, count, INT64_MAX); +} + +#ifdef _WIN32 + +static int wsa_err_to_errno(int wsaerr) { + switch (wsaerr) { + case WSAEINTR: return EINTR; + case WSAEBADF: return EBADF; + case WSAEACCES: return EACCES; + case WSAEFAULT: return EFAULT; + case WSAEINVAL: return EINVAL; + case WSAEMFILE: return EMFILE; + case WSAEWOULDBLOCK: return EAGAIN; + case WSAEINPROGRESS: return EINPROGRESS; + case WSAEALREADY: return EALREADY; + case WSAENOTSOCK: return ENOTSOCK; + case WSAEDESTADDRREQ: return EDESTADDRREQ; + case WSAEMSGSIZE: return EMSGSIZE; + case WSAEPROTOTYPE: return EPROTOTYPE; + case WSAENOPROTOOPT: return ENOPROTOOPT; + case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; + case WSAEOPNOTSUPP: return EOPNOTSUPP; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; + case WSAENETDOWN: return ENETDOWN; + case WSAENETUNREACH: return ENETUNREACH; + case WSAENETRESET: return ENETRESET; + case WSAECONNABORTED: return ECONNABORTED; + case WSAECONNRESET: return ECONNRESET; + case WSAENOBUFS: return ENOBUFS; + case WSAEISCONN: return EISCONN; + case WSAENOTCONN: return ENOTCONN; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAELOOP: return ELOOP; + case WSAENAMETOOLONG: return ENAMETOOLONG; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + case WSAENOTEMPTY: return ENOTEMPTY; + case WSAECANCELLED: return ECANCELED; + case WSA_E_CANCELLED: return ECANCELED; + } + return wsaerr; +} + +int accept1(int sock, struct sockaddr *addr, socklen_t *len) { + int fd = accept0(sock, addr, len); + if (fd == -1) { + errno = wsa_err_to_errno(WSAGetLastError()); + } + return fd; +} +#else +#define accept1 accept0 +#endif + +static int accept_dl(int sockfd, struct sockaddr *addr, socklen_t *addrlen, + int64_t deadline) +{ + struct coroutine *co = coself(); + if (!co) { + errno = EPERM; + return -1; + } + while (1) { + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + errno = ret == NECO_CANCELED ? ECANCELED : ETIMEDOUT; + return -1; + } + int fd = accept1(sockfd, addr, addrlen); + if (fd == -1) { + if (errno == EINTR || errno == EAGAIN) { + cowait(sockfd, EVREAD, deadline); + } else { + return -1; + } + } else { + if (neco_setnonblock(fd, true, 0) == -1) { + close(fd); + return -1; + } + return fd; + } + } +} + +/// Same as neco_accept() but with a deadline parameter. +int neco_accept_dl(int sockfd, struct sockaddr *addr, socklen_t *addrlen, + int64_t deadline) +{ + int ret = accept_dl(sockfd, addr, addrlen, deadline); + async_error_guard(ret); + return ret; +} + +/// Accept a connection on a socket. +/// +/// While in a coroutine, this function should be used instead of the standard +/// accept() to avoid blocking other coroutines from running concurrently. +/// +/// The the accepted file descriptor is returned in non-blocking mode. +/// +/// @param sockfd Socket file descriptor +/// @param addr Socket address out +/// @param addrlen Socket address length out +/// @return On success, file descriptor (non-blocking) +/// @return On error, value -1 (NECO_ERROR) is returned, and errno is set to +/// indicate the error. +/// @see Posix +int neco_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + return neco_accept_dl(sockfd, addr, addrlen, INT64_MAX); +} + +static int connect_dl(int fd, const struct sockaddr *addr, socklen_t addrlen, + int64_t deadline) +{ + struct coroutine *co = coself(); + if (!co) { + errno = EPERM; + return -1; + } + bool inprog = false; + while (1) { + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + errno = ret == NECO_CANCELED ? ECANCELED : ETIMEDOUT; + return -1; + } + errno = 0; + ret = connect0(fd, addr, addrlen); + if (ret == -1) { + switch (errno) { + case EISCONN: + // The socket is already connected. + // This could be because we just tried a moment ago and + // received an EINPROGRESS. In that case return success, + // otherwise return an error. + ret = inprog ? 0 : -1; + break; + case EAGAIN: + // Only unix domain sockets can get this error. + // Linux manual does not describe why this can happen or + // what to do when it does. FreeBSD says it means that there + // are not enough ports available on the system to assign to + // the file descriptor. We can (maybe??) try again. But + // probably best to return an error that is different than + // EAGAIN. + errno = ECONNREFUSED; + break; + case EINPROGRESS: + // EINPROGRESS means the connection is in progress and may + // take a while. We are allowed to add this file descriptor + // to an event queue and wait for the connection to be ready. + // This can be done by waiting on a write event. + // printf("D\n"); + inprog = true; + cowait(fd, EVWRITE, deadline); + continue; + case EINTR: // Interrupted. Just try again. + case ENOMEM: // Mac OS can sometimes return ENOMEM (bug) + continue; + } + } + return ret; + } +} + +/// Same as neco_connect() but with a deadline parameter. +int neco_connect_dl(int sockfd, const struct sockaddr *addr, socklen_t addrlen, + int64_t deadline) +{ + int ret = connect_dl(sockfd, addr, addrlen, deadline); + async_error_guard(ret); + return ret; +} + +/// Connects the socket referred to by the file descriptor sockfd to the +/// address specified by addr. +/// +/// While in a coroutine, this function should be used instead of the standard +/// connect() to avoid blocking other coroutines from running concurrently. +/// +/// @param sockfd Socket file descriptor +/// @param addr Socket address out +/// @param addrlen Socket address length out +/// @return NECO_OK Success +/// @return On error, value -1 (NECO_ERROR) is returned, and errno is set to +/// indicate the error. +int neco_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + return neco_connect_dl(sockfd, addr, addrlen, INT64_MAX); +} + +void neco_errconv_to_sys(int err) { + switch (err) { + case NECO_OK: + errno = 0; + break; + case NECO_INVAL: + errno = EINVAL; + break; + case NECO_PERM: + errno = EPERM; + break; + case NECO_NOMEM: + errno = ENOMEM; + break; + case NECO_CANCELED: + errno = ECANCELED; + break; + case NECO_TIMEDOUT: + errno = ETIMEDOUT; + break; + } +} + +int neco_errconv_from_sys(void) { + switch (errno) { + case EINVAL: + return NECO_INVAL; + case EPERM: + return NECO_PERM; + case ENOMEM: + return NECO_NOMEM; + case ECANCELED: + return NECO_CANCELED; + case ETIMEDOUT: + return NECO_TIMEDOUT; + default: + return NECO_ERROR; + } +} + +const char *neco_shortstrerror(int code) { + switch (code) { + case NECO_OK: + return "NECO_OK"; + case NECO_ERROR: + return "NECO_ERROR"; + case NECO_INVAL: + return "NECO_INVAL"; + case NECO_PERM: + return "NECO_PERM"; + case NECO_NOMEM: + return "NECO_NOMEM"; + case NECO_NOTFOUND: + return "NECO_NOTFOUND"; + case NECO_NOSIGWATCH: + return "NECO_NOSIGWATCH"; + case NECO_CLOSED: + return "NECO_CLOSED"; + case NECO_EMPTY: + return "NECO_EMPTY"; + case NECO_TIMEDOUT: + return "NECO_TIMEDOUT"; + case NECO_CANCELED: + return "NECO_CANCELED"; + case NECO_BUSY: + return "NECO_BUSY"; + case NECO_NEGWAITGRP: + return "NECO_NEGWAITGRP"; + case NECO_GAIERROR: + return "NECO_GAIERROR"; + case NECO_UNREADFAIL: + return "NECO_UNREADFAIL"; + case NECO_PARTIALWRITE: + return "NECO_PARTIALWRITE"; + case NECO_NOTGENERATOR: + return "NECO_NOTGENERATOR"; + case NECO_NOTSUSPENDED: + return "NECO_NOTSUSPENDED"; + default: + return "UNKNOWN"; + } +} + +/// Returns a string representation of an error code. +/// @see Errors +const char *neco_strerror(ssize_t errcode) { + switch (errcode) { + case NECO_OK: + return "Success"; + case NECO_ERROR: + return strerror(errno); + case NECO_INVAL: + return strerror(EINVAL); + case NECO_PERM: + return strerror(EPERM); + case NECO_NOMEM: + return strerror(ENOMEM); + case NECO_NOTFOUND: + return "No such coroutine"; + case NECO_NOSIGWATCH: + return "Not watching on a signal"; + case NECO_CLOSED: + return "Channel closed"; + case NECO_EMPTY: + return "Channel empty"; + case NECO_TIMEDOUT: + return strerror(ETIMEDOUT); + case NECO_CANCELED: + return strerror(ECANCELED); + case NECO_BUSY: + return strerror(EBUSY); + case NECO_NEGWAITGRP: + return "Negative waitgroup counter"; + case NECO_GAIERROR: + if (neco_gai_errno == EAI_SYSTEM) { + return strerror(errno); + } else { + return gai_strerror(neco_gai_errno); + } + case NECO_UNREADFAIL: + return "Failed to unread byte"; + case NECO_PARTIALWRITE: + return "Failed to write all bytes"; + case NECO_NOTGENERATOR: + return "Coroutine is not a generator"; + case NECO_NOTSUSPENDED: + return "Coroutine is not suspended"; + default: { + static __thread char udmsg[32]; + snprintf(udmsg, sizeof(udmsg)-1, "Undefined error: %zd", errcode); + return udmsg; + } + } +} + +static int getstats(neco_stats *stats) { + if (!stats) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } + *stats = (neco_stats) { + .coroutines = rt->all.count, + .sleepers = rt->nsleepers, + .evwaiters = rt->nevwaiters, + .sigwaiters = rt->nsigwaiters, + .senders = rt->nsenders, + .receivers = rt->nreceivers, + .locked = rt->nlocked, + .waitgroupers = rt->nwaitgroupers, + .condwaiters = rt->ncondwaiters, + .suspended = rt->nsuspended, + }; + return NECO_OK; +} + +/// Returns various stats for the current Neco runtime. +/// +/// ```c +/// // Print the number of active coroutines +/// neco_stats stats; +/// neco_getstats(&stats); +/// printf("%zu\n", stats.coroutines); +/// ``` +/// +/// Other stats include: +/// +/// ```c +/// coroutines +/// sleepers +/// evwaiters +/// sigwaiters +/// senders +/// receivers +/// locked +/// waitgroupers +/// condwaiters +/// suspended +/// ``` + +int neco_getstats(neco_stats *stats) { + int ret = getstats(stats); + error_guard(ret); + return ret; +} + +//////////////////////////////////////////////////////////////////////////////// +// channels +//////////////////////////////////////////////////////////////////////////////// + +struct neco_chan { + int64_t rtid; // runtime id. for runtime/thread isolation + int rc; // reference counter + bool sclosed; // sender closed + bool rclosed; // receiver closed + bool qrecv; // queue has all receivers, otherwise all senders + bool lok; // used for the select-case 'closed' result + struct colist queue; // waiting coroutines. Either senders or receivers + int msgsize; // size of each message + int bufcap; // max number of messages in ring buffer + int buflen; // number of messages in ring buffer + int bufpos; // position of first message in ring buffer + char data[]; // message ring buffer + one extra entry for 'lmsg' +}; + +// coselectcase pretends to be a coroutine for the purpose of multiplexing +// select-case channels into a single coroutine. +// It's required that this structure is 16-byte aligned. +struct coselectcase { + struct coroutine *prev; + struct coroutine *next; + enum cokind kind; + + struct neco_chan *chan; + struct coroutine *co; + void *data; + bool *ok; + int idx; + int *ret_idx; +} aligned16; + +// returns the message slot +static char *cbufslot(struct neco_chan *chan, int index) { + return chan->data + (chan->msgsize * index); +} + +// push a message to the back by copying from data +static void cbuf_push(struct neco_chan *chan, void *data) { + int pos = chan->bufpos + chan->buflen; + if (pos >= chan->bufcap) { + pos -= chan->bufcap; + } + if (chan->msgsize > 0) { + memcpy(cbufslot(chan, pos), data, (size_t)chan->msgsize); + } + chan->buflen++; +} + +// pop a message from the front and copy to data +static void cbuf_pop(struct neco_chan *chan, void *data) { + if (chan->msgsize) { + memcpy(data, cbufslot(chan, chan->bufpos), (size_t)chan->msgsize); + } + chan->bufpos++; + if (chan->bufpos == chan->bufcap) { + chan->bufpos = 0; + } + chan->buflen--; +} + +static struct neco_chan *chan_fastmake(size_t data_size, size_t capacity, + bool as_generator) +{ + struct neco_chan *chan; + // Generators do not need buffered capacity of any size because they can't + // use the neco_select, which requires at least one buffered slot for the + // neco_case operation to store the pending data. + size_t ring_size = as_generator ? 0 : data_size * (capacity+1); + if (POOL_ENABLED && ring_size == 0 && rt->zchanpoollen > 0) { + chan = rt->zchanpool[--rt->zchanpoollen]; + } else { + size_t memsize = sizeof(struct neco_chan) + ring_size; + chan = malloc0(memsize); + if (!chan) { + return NULL; + } + } + // Zero the struct memory space, not the data space. + memset(chan, 0, sizeof(struct neco_chan)); + chan->rtid = rt->id; + chan->msgsize = (int)data_size; + chan->bufcap = (int)capacity; + colist_init(&chan->queue); + return chan; +} + +static int chan_make(struct neco_chan **chan, size_t data_size, size_t capacity) +{ + if (!chan || data_size > INT_MAX || capacity > INT_MAX) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } + *chan = chan_fastmake(data_size, capacity, 0); + if (!*chan) { + return NECO_NOMEM; + } + return NECO_OK; +} + +/// Creates a new channel for sharing messages with other coroutines. +/// +/// **Example** +/// +/// ``` +/// void coroutine(int argc, void *argv[]) { +/// neco_chan *ch = argv[0]; +/// +/// // Send a message +/// neco_chan_send(ch, &(int){ 1 }); +/// +/// // Release the channel +/// neco_chan_release(ch); +/// } +/// +/// int neco_start(int argc, char *argv[]) { +/// neco_chan *ch; +/// neco_chan_make(&ch, sizeof(int), 0); +/// +/// // Retain a reference of the channel and provide it to a newly started +/// // coroutine. +/// neco_chan_retain(ch); +/// neco_start(coroutine, 1, ch); +/// +/// // Receive a message +/// int msg; +/// neco_chan_recv(ch, &msg); +/// printf("%d\n", msg); // prints '1' +/// +/// // Always release the channel when you are done +/// neco_chan_release(ch); +/// } +/// ``` +/// +/// @param chan Channel +/// @param data_size Data size of messages +/// @param capacity Buffer capacity +/// @return NECO_OK Success +/// @return NECO_NOMEM The system lacked the necessary resources +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @note The caller is responsible for freeing with neco_chan_release() +/// @note data_size and capacity cannot be greater than INT_MAX +int neco_chan_make(struct neco_chan **chan, size_t data_size, size_t capacity) { + int ret = chan_make(chan, data_size, capacity); + error_guard(ret); + return ret; +} + +static void chan_fastretain(struct neco_chan *chan) { + chan->rc++; +} + +static int chan_retain(struct neco_chan *chan) { + if (!chan) { + return NECO_INVAL; + } else if (!rt || rt->id != chan->rtid) { + return NECO_PERM; + } + chan_fastretain(chan); + return NECO_OK; +} + +/// Retain a reference of the channel so it can be shared with other coroutines. +/// +/// This is needed for avoiding use-after-free bugs. +/// +/// See neco_chan_make() for an example. +/// +/// @param chan The channel +/// @return NECO_OK Success +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @note The caller is responsible for releasing the reference with +/// neco_chan_release() +/// @see Channels +/// @see neco_chan_release() +int neco_chan_retain(struct neco_chan *chan) { + int ret = chan_retain(chan); + error_guard(ret); + return ret; +} + +static bool zchanpush(struct neco_chan *chan) { + if (rt->zchanpoollen == rt->zchanpoolcap) { + if (rt->zchanpoolcap == 256) { + return false; + } + int cap = rt->zchanpoolcap == 0 ? 16 : rt->zchanpoolcap * 2; + void *zchanpool2 = realloc0(rt->zchanpool, + sizeof(struct neco_chan) * (size_t)cap); + if (!zchanpool2) { + return false; + } + rt->zchanpoolcap = cap; + rt->zchanpool = zchanpool2; + } + rt->zchanpool[rt->zchanpoollen++] = chan; + return true; +} + +static void chan_fastrelease(struct neco_chan *chan) { + chan->rc--; + if (chan->rc < 0) { + if (!POOL_ENABLED || chan->msgsize > 0 || !zchanpush(chan)) { + free0(chan); + } + } +} + +static int chan_release(struct neco_chan *chan) { + if (!chan) { + return NECO_INVAL; + } else if (!rt || rt->id != chan->rtid) { + return NECO_PERM; + } + chan_fastrelease(chan); + return NECO_OK; +} + +/// Release a reference to a channel +/// +/// See neco_chan_make() for an example. +/// +/// @param chan The channel +/// @return NECO_OK Success +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @see Channels +/// @see neco_chan_retain() +int neco_chan_release(struct neco_chan *chan) { + int ret = chan_release(chan); + error_guard(ret); + return ret; +} + +static int chan_send0(struct neco_chan *chan, void *data, bool broadcast, + int64_t deadline) +{ + if (!chan) { + return NECO_INVAL; + } else if (!rt || chan->rtid != rt->id) { + return NECO_PERM; + } else if (chan->sclosed) { + return NECO_CLOSED; + } + struct coroutine *co = coself(); + if (co->canceled && !broadcast) { + co->canceled = false; + return NECO_CANCELED; + } + int sent = 0; + while (!colist_is_empty(&chan->queue) && chan->qrecv) { + // A receiver is currently waiting for a message. + // Pop it from the queue. + struct coroutine *recv = colist_pop_front(&chan->queue); + if (recv->kind == SELECTCASE) { + // The receiver is a select-case. + struct coselectcase *cocase = (struct coselectcase *)recv; + if (*cocase->ret_idx != -1) { + // This select-case has already been handled + continue; + } + // Set the far stack index pointer and exchange the select-case + // with the real coroutine. + *cocase->ret_idx = cocase->idx; + recv = cocase->co; + recv->cmsg = cocase->data; + *cocase->ok = true; + } + // Directly copy the message to the receiver's 'data' argument. + if (chan->msgsize > 0) { + memcpy(recv->cmsg, data, (size_t)chan->msgsize); + } + if (!broadcast) { + // Resume receiver immediately. + sco_resume(recv->id); + return NECO_OK; + } else { + // Schedule the reciever. + sched_resume(recv); + sent++; + } + } + if (broadcast) { + yield_for_sched_resume(); + return sent; + } + + if (chan->buflen < chan->bufcap) { + // There room to write to the ring buffer. + // Add this message and return immediately. + cbuf_push(chan, data); + return NECO_OK; + } + + // Push this sender to queue + colist_push_back(&chan->queue, co); + chan->qrecv = false; + co->cmsg = data; + + // Wait for a receiver to consume this message. + rt->nsenders++; + copause(deadline); + rt->nsenders--; + remove_from_list(co); + + co->cmsg = NULL; + return checkdl(co, INT64_MAX); +} + +/// Same as neco_chan_send() but with a deadline parameter. +int neco_chan_send_dl(neco_chan *chan, void *data, int64_t deadline) { + int ret = chan_send0(chan, data, false, deadline); + async_error_guard(ret); + return ret; +} + +/// Send a message +/// +/// See neco_chan_make() for an example. +/// +/// @return NECO_OK Success +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_CANCELED Operation canceled +/// @return NECO_CLOSED Channel closed +/// @see Channels +/// @see neco_chan_recv() +int neco_chan_send(struct neco_chan *chan, void *data) { + return neco_chan_send_dl(chan, data, INT64_MAX); +} + +/// Sends message to all receiving channels. +/// @return The number of channels that received the message +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_CLOSED Channel closed +/// @note This operation cannot be canceled and does not timeout +/// @see Channels +/// @see neco_chan_recv() +int neco_chan_broadcast(struct neco_chan *chan, void *data) { + int ret = chan_send0(chan, data, true, INT64_MAX); + error_guard(ret); + return ret; +} + +static int chan_tryrecv0(struct neco_chan *chan, void *data, bool try, + int64_t deadline) +{ + if (!chan) { + return NECO_INVAL; + } else if (!rt || chan->rtid != rt->id) { + return NECO_PERM; + } else if (chan->rclosed) { + return NECO_CLOSED; + } + struct coroutine *co = coself(); + if (co->canceled) { + co->canceled = false; + return NECO_CANCELED; + } + if (chan->buflen > 0) { + // Take from the buffer + cbuf_pop(chan, data); + struct coroutine *send = NULL; + if (!colist_is_empty(&chan->queue)) { + // There's a sender waiting to send a message. + // Put the sender's message in the buffer and wake it up. + send = colist_pop_front(&chan->queue); + cbuf_push(chan, send->cmsg); + } + if (chan->sclosed && colist_is_empty(&chan->queue) && + chan->buflen == 0) + { + // The channel was closed and there are no more messages. + // Close the receiving side too + chan->rclosed = true; + } + if (send) { + sco_resume(send->id); + } + return NECO_OK; + } + + if (!colist_is_empty(&chan->queue) && !chan->qrecv) { + // A sender is currently waiting to send a message. + // This message must be consumed immediately. + struct coroutine *send = colist_pop_front(&chan->queue); + if (chan->msgsize) { + memcpy(data, send->cmsg, (size_t)chan->msgsize); + } + if (chan->sclosed && colist_is_empty(&chan->queue) && + chan->buflen == 0) + { + // The channel was closed and there are no more messages. + // Now close the receiving side too. + chan->rclosed = true; + } + sco_resume(send->id); + return NECO_OK; + } + if (try) { + return NECO_EMPTY; + } + // Push the receiver to the queue. + colist_push_back(&chan->queue, co); + chan->qrecv = true; + // Assign the 'cmsg' message slot so that the sender knows where to copy + // the message. + co->cmsg = data; + // Set the channel closed flag that *may* be changed by the sender. + co->cclosed = false; + // Wait for a sender. + rt->nreceivers++; + copause(deadline); + rt->nreceivers--; + remove_from_list(co); + // The sender has already copied its message to 'data'. + co->cmsg = NULL; + int ret = checkdl(co, INT64_MAX); + if (ret != NECO_OK) { + return ret; + } + if (co->cclosed) { + // The channel was closed for receiving while this coroutine was + // waiting on a new message. + if (chan->msgsize) { + memset(data, 0, (size_t)chan->msgsize); + } + co->cclosed = false; + return NECO_CLOSED; + } else { + return NECO_OK; + } +} + +/// Same as neco_chan_recv() but with a deadline parameter. +int neco_chan_recv_dl(struct neco_chan *chan, void *data, int64_t deadline) { + int ret = chan_tryrecv0(chan, data, false, deadline); + async_error_guard(ret); + return ret; +} + +/// Receive a message +/// +/// See neco_chan_make() for an example. +/// +/// @param chan channel +/// @param data data pointer +/// @return NECO_OK Success +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_CANCELED Operation canceled +/// @return NECO_CLOSED Channel closed +/// @see Channels +/// @see neco_chan_send() +int neco_chan_recv(struct neco_chan *chan, void *data) { + return neco_chan_recv_dl(chan, data, INT64_MAX); +} + +/// Receive a message, but do not wait if the message is not available. +/// @param chan channel +/// @param data data pointer +/// @return NECO_OK Success +/// @return NECO_EMPTY No message available +/// @return NECO_CLOSED Channel closed +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_INVAL An invalid parameter was provided +/// @see Channels +/// @see neco_chan_recv() +int neco_chan_tryrecv(struct neco_chan *chan, void *data) { + int ret = chan_tryrecv0(chan, data, true, INT64_MAX); + async_error_guard(ret); + return ret; +} + +static int chan_close(struct neco_chan *chan) { + if (!chan) { + return NECO_INVAL; + } else if (!rt || chan->rtid != rt->id) { + return NECO_PERM; + } else if (chan->sclosed) { + return NECO_CLOSED; + } + chan->sclosed = true; + if (chan->buflen > 0 || (!colist_is_empty(&chan->queue) && !chan->qrecv)) { + // There are currently messages in the buffer or senders still waiting + // to send messages. Do not close the receiver side yet. + return NECO_OK; + } + // No buffered messages or senders waiting. + // Close the receiver side and wake up all receivers. + while (!colist_is_empty(&chan->queue)) { + struct coroutine *recv = colist_pop_front(&chan->queue); + if (recv->kind == SELECTCASE) { + // The receiver is a select-case. + struct coselectcase *cocase = (struct coselectcase *)recv; + if (*cocase->ret_idx != -1) { + // This select-case has already been handled + continue; + } + // Set the far stack index pointer and exchange the select-case + // with the real coroutine. + *cocase->ret_idx = cocase->idx; + recv = cocase->co; + *cocase->ok = false; + } + recv->cclosed = true; + sched_resume(recv); + } + chan->rclosed = true; + chan->qrecv = false; + yield_for_sched_resume(); + return NECO_OK; +} + +/// Close a channel for sending. +/// @param chan channel +/// @return NECO_OK Success +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_CLOSED Channel already closed +int neco_chan_close(struct neco_chan *chan) { + int ret = chan_close(chan); + error_guard(ret); + return ret; +} + +static int chan_select(int ncases, struct coselectcase *cases, int *ret_idx, + int64_t deadline, bool try) +{ + // Check that the channels are valid before continuing. + for (int i = 0; i < ncases; i++) { + struct neco_chan *chan = cases[i].chan; + if (!chan) { + return NECO_INVAL; + } else if (chan->rtid != rt->id) { + return NECO_PERM; + } + } + + struct coroutine *co = coself(); + + if (co->canceled) { + co->canceled = false; + return NECO_CANCELED; + } + + // Scan each channel and see if there are any messages waiting in their + // queue or if any are closed. + // If so then receive that channel immediately. + for (int i = 0; i < ncases; i++) { + struct neco_chan *chan = cases[i].chan; + if ((!colist_is_empty(&chan->queue) && !chan->qrecv) || + chan->buflen > 0 || chan->rclosed) + { + int ret = neco_chan_recv(cases[i].chan, cases[i].data); + *cases[i].ok = ret == NECO_OK; + return i; + } + } + + if (try) { + return NECO_EMPTY; + } + + // Push all cases into their repsective channel queue. + for (int i = 0; i < ncases; i++) { + colist_push_back(&cases[i].chan->queue, (struct coroutine*)&cases[i]); + cases[i].chan->qrecv = true; + } + + // Wait for a sender to wake us up + rt->nreceivers++; + copause(deadline); + rt->nreceivers--; + + // Remove all cases + for (int i = 0; i < ncases; i++) { + remove_from_list((struct coroutine*)&cases[i]); + } + int ret = checkdl(co, INT64_MAX); + return ret == NECO_OK ? *ret_idx : ret; +} + +static int chan_selectv_dl(int ncases, va_list *args, struct neco_chan **chans, + int64_t deadline, bool try) +{ + if (ncases < 0) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } + + // Allocate space for storing each select-case struct. + // These are used in place of a coroutine for the channel receiver queues. + bool must_free; + struct coselectcase stack_cases[8]; + struct coselectcase *cases; + if (ncases > 8) { + cases = malloc0((size_t)ncases * sizeof(struct coselectcase)); + if (!cases) { + return NECO_NOMEM; + } + must_free = true; + } else { + cases = stack_cases; + must_free = false; + } + struct coroutine *co = coself(); + int ret_idx = -1; + + // Copy the select-case arguments into the array. + for (int i = 0; i < ncases; i++) { + struct neco_chan *chan; + chan = args ? va_arg(*args, struct neco_chan*) : chans[i]; + cases[i] = (struct coselectcase){ + .chan = chan, + .kind = SELECTCASE, + .idx = i, + .ret_idx = &ret_idx, + .co = co, + .data = chan ? cbufslot(chan, chan->bufcap) : 0, + .ok = chan ? &chan->lok : 0, + }; + cases[i].next = (struct coroutine*)&cases[i]; + cases[i].prev = (struct coroutine*)&cases[i]; + } + int ret = chan_select(ncases, cases, &ret_idx, deadline, try); + if (must_free) { + free0(cases); + } + return ret; +} + +/// Same as neco_chan_selectv() but with a deadline parameter. +int neco_chan_selectv_dl(int nchans, struct neco_chan *chans[], + int64_t deadline) +{ + int ret = chan_selectv_dl(nchans, 0, chans, deadline, false); + async_error_guard(ret); + return ret; +} + +/// Same as neco_chan_select() but using an array for arguments. +int neco_chan_selectv(int nchans, struct neco_chan *chans[]) { + return neco_chan_selectv_dl(nchans, chans, INT64_MAX); +} + +/// Same as neco_chan_select() but with a deadline parameter. +int neco_chan_select_dl(int64_t deadline, int nchans, ...) { + va_list args; + va_start(args, nchans); + int ret = chan_selectv_dl(nchans, &args, 0, deadline, false); + va_end(args); + async_error_guard(ret); + return ret; +} + +/// Wait on multiple channel operations at the same time. +/// +/// **Example** +/// +/// ``` +/// +/// // Let's say we have two channels 'c1' and 'c2' that both transmit 'char *' +/// // messages. +/// +/// // Use neco_chan_select() to wait on both channels. +/// +/// char *msg; +/// int idx = neco_chan_select(2, c1, c2); +/// switch (idx) { +/// case 0: +/// neco_chan_case(c1, &msg); +/// break; +/// case 1: +/// neco_chan_case(c2, &msg); +/// break; +/// default: +/// // Error occured. The return value 'idx' is the error +/// } +/// +/// printf("%s\n", msg); +/// ``` +/// +/// @param nchans Number of channels +/// @param ... The channels +/// @return The index of channel with an available message +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_NOMEM The system lacked the necessary resources +/// @return NECO_CANCELED Operation canceled +/// @see Channels +int neco_chan_select(int nchans, ...) { + va_list args; + va_start(args, nchans); + int ret = chan_selectv_dl(nchans, &args, 0, INT64_MAX, false); + va_end(args); + async_error_guard(ret); + return ret; +} + +/// Same as neco_chan_select() but does not wait if a message is not available +/// @return NECO_EMPTY No message available +/// @see Channels +/// @see neco_chan_select() +int neco_chan_tryselect(int nchans, ...) { + va_list args; + va_start(args, nchans); + int ret = chan_selectv_dl(nchans, &args, 0, INT64_MAX, true); + va_end(args); + async_error_guard(ret); + return ret; +} + +/// Same as neco_chan_tryselect() but uses an array for arguments. +int neco_chan_tryselectv(int nchans, struct neco_chan *chans[]) { + int ret = chan_selectv_dl(nchans, 0, chans, 0, true); + async_error_guard(ret); + return ret; +} + +static int chan_case(struct neco_chan *chan, void *data) { + if (!chan) { + return NECO_INVAL; + } else if (!rt || chan->rtid != rt->id) { + return NECO_PERM; + } else if (!chan->lok) { + return NECO_CLOSED; + } + if (chan->msgsize) { + memcpy(data, cbufslot(chan, chan->bufcap), (size_t)chan->msgsize); + } + return NECO_OK; +} + +/// Receive the message after a successful neco_chan_select(). +/// See neco_chan_select() for an example. +/// @param chan The channel +/// @param data The data +/// @return NECO_OK Success +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_CLOSED Channel closed +int neco_chan_case(struct neco_chan *chan, void *data) { + int ret = chan_case(chan, data); + error_guard(ret); + return ret; +} + +struct getaddrinfo_args { + atomic_int returned; + char *node; + char *service; + struct addrinfo *hints; + struct addrinfo *res; + int fds[2]; + int ret; + int errnum; +#ifdef __FreeBSD__ + void *dlhandle; +#endif +}; + +static void gai_args_free(struct getaddrinfo_args *args) { + if (args) { + free0(args->service); + free0(args->node); + free0(args->hints); + freeaddrinfo(args->res); + if (args->fds[0]) { + must(close(args->fds[0]) == 0); + } + if (args->fds[1]) { + must(close(args->fds[1]) == 0); + } +#ifdef __FreeBSD__ + if (args->dlhandle) { + dlclose(args->dlhandle); + } +#endif + free0(args); + } +} + +static struct getaddrinfo_args *gai_args_new(const char *node, + const char *service, const struct addrinfo *hints) +{ + struct getaddrinfo_args *args = malloc0(sizeof(struct getaddrinfo_args)); + if (!args) { + return NULL; + } + memset(args, 0, sizeof(struct getaddrinfo_args)); + if (node) { + size_t nnode = strlen(node); + args->node = malloc0(nnode+1); + if (!args->node) { + gai_args_free(args); + return NULL; + } + memcpy(args->node, node, nnode+1); + } + if (service) { + size_t nservice = strlen(service); + args->service = malloc0(nservice+1); + if (!args->service) { + gai_args_free(args); + return NULL; + } + memcpy(args->service, service, nservice+1); + } + if (hints) { + args->hints = malloc0(sizeof(struct addrinfo)); + if (!args->hints) { + gai_args_free(args); + return NULL; + } + memset(args->hints, 0, sizeof(struct addrinfo)); + args->hints->ai_flags = hints->ai_flags; + args->hints->ai_family = hints->ai_family; + args->hints->ai_socktype = hints->ai_socktype; + args->hints->ai_protocol = hints->ai_protocol; + args->hints->ai_next = NULL; + } + return args; +} + +static atomic_int getaddrinfo_th_counter = 0; + +#ifdef NECO_TESTING +int neco_getaddrinfo_nthreads(void) { + return atomic_load(&getaddrinfo_th_counter); +} +#endif + +static void *getaddrinfo_th(void *v) { + struct getaddrinfo_args *a = v; + a->ret = getaddrinfo(a->node, a->service, a->hints, &a->res); + a->errnum = errno; + must(write(a->fds[1], &(int){1}, sizeof(int)) == sizeof(int)); + while (!atomic_load(&a->returned)) { + sched_yield(); + } + gai_args_free(a); + atomic_fetch_sub(&getaddrinfo_th_counter, 1); + return NULL; +} + +static bool is_ip_address(const char *addr) { + bool ok = false; + if (addr) { + struct in6_addr result; + ok = inet_pton(AF_INET, addr, &result) == 1 || + inet_pton(AF_INET6, addr, &result) == 1; + } + return ok; +} + +static void cleanup_gai(void *ptr) { + // This notifies the gai thread that it should return. The thread itself + // will do the actual cleanup of resources. + struct getaddrinfo_args *args = ptr; + atomic_store(&args->returned, 1); +} + +#ifdef _WIN32 +int pipe1(int fds[2]) { + return _pipe(fds, 64, _O_BINARY); +} +#else +#define pipe1 pipe0 +#endif + +static int getaddrinfo_dl(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res, int64_t deadline) +{ + struct coroutine *co = coself(); + if (!co) { + errno = EPERM; + return EAI_SYSTEM; + } + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + errno = ret == NECO_CANCELED ? ECANCELED : ETIMEDOUT; + return EAI_SYSTEM; + } + if (is_ip_address(node)) { + // This is a simple address. Since there's no DNS lookup involved we + // can use the standard getaddrinfo function without worrying about + // much delay. + return getaddrinfo(node, service, hints, res); + } + + // DNS lookup is probably needed. This may cause network usage and who + // knows how long it will take to return. Here we'll use a background + // thread to do the work and we'll wait by using a local pipe. + struct getaddrinfo_args *args = gai_args_new(node, service, hints); + if (!args) { + return EAI_MEMORY; + } + + // Create a pipe to communicate with the thread. This allows us to use the + // async neco_read() operation, which includes builtin deadline and cancel + // support. Using a traditional pthread mutexes or cond variables are not + // an option. + if (pipe1(args->fds) == -1) { + gai_args_free(args); + return EAI_SYSTEM; + } +#ifndef _WIN32 + if (neco_setnonblock(args->fds[0], true, 0) == -1) { + gai_args_free(args); + return EAI_SYSTEM; + } +#endif + + pthread_t th; +#ifdef __FreeBSD__ + // The pthread functions are not included in libc for FreeBSD, dynamically + // load the function instead. + int (*pthread_create)(pthread_t*,const pthread_attr_t*,void*(*)(void*), + void*); + int (*pthread_detach)(pthread_t); + args->dlhandle = dlopen("/usr/lib/libpthread.so", RTLD_LAZY); + if (!args->dlhandle) { + gai_args_free(args); + return EAI_SYSTEM; + } + pthread_create = (int(*)(pthread_t*,const pthread_attr_t*,void*(*)(void*), + void*))dlsym(args->dlhandle, "pthread_create"); + pthread_detach = (int(*)(pthread_t)) + dlsym(args->dlhandle, "pthread_detach"); + if (!pthread_create || !pthread_detach) { + gai_args_free(args); + return EAI_SYSTEM; + } +#endif + atomic_fetch_add(&getaddrinfo_th_counter, 1); + ret = pthread_create0(&th, 0, getaddrinfo_th, args); + if (ret != 0) { + errno = ret; + gai_args_free(args); + atomic_fetch_sub(&getaddrinfo_th_counter, 1); + return EAI_SYSTEM; + } + must(pthread_detach0(th) == 0); + + // At this point the args are owned by the thread and all cleanup will + // happen there. The thread will complete the getaddrinfo operation. + // Then we'll steal the result and return it to the caller. + neco_cleanup_push(cleanup_gai, args); + int ready = 0; + ssize_t n = neco_read_dl(args->fds[0], &ready, sizeof(int), deadline); + ret = EAI_SYSTEM; + if (n != -1) { + must(ready == 1 && n == sizeof(int)); + *res = args->res; + args->res = NULL; + ret = args->ret; + errno = args->errnum; + coyield(); + } + neco_cleanup_pop(1); + return ret; +} + +/// Same as neco_getaddrinfo() but with a deadline parameter. +int neco_getaddrinfo_dl(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res, int64_t deadline) +{ + int ret = getaddrinfo_dl(node, service, hints, res, deadline); + int err; + if (ret == 0) { + err = NECO_OK; + } else if (ret == EAI_SYSTEM) { + err = NECO_ERROR; + } else { + err = NECO_GAIERROR; + } + async_error_guard(err); + return ret; +} + +/// The getaddrinfo() function is used to get a list of addresses and port +/// numbers for node (hostname) and service. +/// +/// This is functionally identical to the Posix getaddrinfo function with the +/// exception that it does not block, allowing for usage in a Neco coroutine. +/// +/// @return On success, 0 is returned +/// @return On error, a nonzero error code defined by the system. See the link +/// below for a list. +/// @see Posix +/// @see https://www.man7.org/linux/man-pages/man3/getaddrinfo.3.html +int neco_getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) +{ + return neco_getaddrinfo_dl(node, service, hints, res, INT64_MAX); +} + +// Returns a host and port. +// The host will need to be freed by the caller. +static int parse_tcp_addr(const char *addr, char **host, const char **port) { + size_t naddr = strlen(addr); + if (naddr == 0) { + return NECO_INVAL; + } + const char *colon = NULL; + for (size_t i = naddr-1; ; i--) { + if (addr[i] == ':') { + colon = addr+i; + break; + } + if (i == 0) { + break; + } + } + if (!colon) { + return NECO_INVAL; + } + *port = colon+1; + naddr = (size_t)(colon-addr); + if (addr[0] == '[' && addr[naddr-1] == ']') { + addr++; + naddr -= 2; + } + *host = malloc0(naddr+1); + if (!*host) { + return NECO_NOMEM; + } + memcpy(*host, addr, naddr); + (*host)[naddr] = '\0'; + return NECO_OK; +} + +int neco_errconv_from_gai(int errnum) { + switch (errnum) { + case EAI_MEMORY: + return NECO_NOMEM; + case EAI_SYSTEM: + return neco_errconv_from_sys(); + default: + neco_gai_errno = errnum; + return NECO_GAIERROR; + } +} + +static void cleanup_free_host(void *host) { + free0(host); +} + +// Returns NECO errors +static int getaddrinfo_from_tcp_addr_dl(const char *addr, int tcp_vers, + struct addrinfo **res, int64_t deadline) +{ + char *host = NULL; + const char *port = 0; + int ret = parse_tcp_addr(addr, &host, &port); + if (ret != NECO_OK) { + return ret; + } + struct addrinfo hints = { 0 }; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_family = tcp_vers; + struct addrinfo *ainfo = NULL; + const char *vhost = host; + if (vhost[0] == '\0') { + if (tcp_vers == AF_INET6) { + vhost = "::"; + } else { + vhost = "0.0.0.0"; + } + } + neco_cleanup_push(cleanup_free_host, host); + ret = neco_getaddrinfo_dl(vhost, port, &hints, &ainfo, deadline); + neco_cleanup_pop(1); + if (ret != 0) { + return neco_errconv_from_gai(ret); + } + // It has been observed on Linux that a getaddrinfo can successfully return + // but with an empty result. Let's check for that case and ensure an error + // is returned to the caller. + neco_gai_errno = EAI_FAIL; + ret = NECO_GAIERROR; + *res = 0; + if (ainfo) { + *res = ainfo; + ret = NECO_OK; + neco_gai_errno = EAI_FAIL; + } + return ret; +} + + + +static int getaddrinfo_from_tcp_addr_dl(const char *addr, int tcp_vers, + struct addrinfo **res, int64_t deadline); + +int neco_errconv_from_sys(void); + +static int setnonblock(int fd, bool nonblock, bool *oldnonblock) { +#ifdef _WIN32 + // There's no way to detect if a socket is in non-blocking mode in Windows. + return ioctlsocket(fd, FIONBIO, &(unsigned long){ nonblock }); +#else + int flags = fcntl0(fd, F_GETFL, 0); + if (flags == -1) { + return -1; + } + if (oldnonblock) { + *oldnonblock = (flags & O_NONBLOCK) == O_NONBLOCK; + } + if (nonblock) { + flags |= O_NONBLOCK; + } else { + flags &= ~O_NONBLOCK; + } + return fcntl0(fd, F_SETFL, flags); +#endif +} + +/// Change the non-blocking state for a file descriptor. +/// @see Posix2 +int neco_setnonblock(int fd, bool nonblock, bool *oldnonblock) { + int ret = setnonblock(fd, nonblock, oldnonblock); + error_guard(ret); + return ret; +} + +static void cleanup_close_socket(void *arg) { + close(*(int*)arg); +} + +static int dial_connect_dl(int domain, int type, int protocol, + const struct sockaddr *addr, socklen_t addrlen, int64_t deadline) +{ + int fd = socket0(domain, type, protocol); + if (fd == -1) { + return -1; + } + if (neco_setnonblock(fd, true, 0) == -1) { + close(fd); + return -1; + } + int ret; + neco_cleanup_push(cleanup_close_socket, &fd); + ret = neco_connect_dl(fd, addr, addrlen, deadline); + if (ret == 0) { + ret = fd; + fd = -1; + } + neco_cleanup_pop(1); + return ret; +} + +static void cleanup_addrinfo(void *arg) { + struct addrinfo *ainfo = arg; + freeaddrinfo(ainfo); +} + +static int dial_tcp_dl(const char *addr, int tcp_vers, int64_t deadline) { + struct addrinfo *ainfo; + int ret = getaddrinfo_from_tcp_addr_dl(addr, tcp_vers, &ainfo, deadline); + if (ret != NECO_OK) { + return ret; + } + int fd; + neco_cleanup_push(cleanup_addrinfo, ainfo); + struct addrinfo *ai = ainfo; + do { + fd = dial_connect_dl(ai->ai_family, ai->ai_socktype, ai->ai_protocol, + ai->ai_addr, ai->ai_addrlen, deadline); + if (fd != -1) { + break; + } + ai = ai->ai_next; + } while (ai); + if (fd == -1) { + fd = neco_errconv_from_sys(); + } + neco_cleanup_pop(1); + return fd; +} + +static int dial_unix_dl(const char *addr, int64_t deadline) { + (void)addr; (void)deadline; +#ifdef _WIN32 + return NECO_PERM; +#else + struct sockaddr_un unaddr = { .sun_family = AF_UNIX }; + size_t naddr = strlen(addr); + if (naddr > sizeof(unaddr.sun_path) - 1) { + return NECO_INVAL; + } + strncpy(unaddr.sun_path, addr, sizeof(unaddr.sun_path) - 1); + int fd = dial_connect_dl(AF_UNIX, SOCK_STREAM, 0, (void*)&unaddr, + sizeof(struct sockaddr_un), deadline); + if (fd == -1) { + fd = neco_errconv_from_sys(); + } + return fd; +#endif +} + +static int dial_dl(const char *network, const char *address, int64_t deadline) { + if (neco_getid() <= 0) { + return NECO_PERM; + } else if (!network || !address) { + return NECO_INVAL; + } else if (strcmp(network, "tcp") == 0) { + return dial_tcp_dl(address, 0, deadline); + } else if (strcmp(network, "tcp4") == 0) { + return dial_tcp_dl(address, AF_INET, deadline); + } else if (strcmp(network, "tcp6") == 0) { + return dial_tcp_dl(address, AF_INET6, deadline); + } else if (strcmp(network, "unix") == 0) { + return dial_unix_dl(address, deadline); + } else { + return NECO_INVAL; + } +} + +/// Same as neco_dial() but with a deadline parameter. +int neco_dial_dl(const char *network, const char *address, int64_t deadline) { + int ret = dial_dl(network, address, deadline); + error_guard(ret); + return ret; +} + +/// Connect to a remote server. +/// +/// **Example** +/// +/// ```c +/// int fd = neco_dial("tcp", "google.com:80"); +/// if (fd < 0) { +/// // .. error, do something with it. +/// } +/// // Connected to google.com. Use neco_read(), neco_write(), or create a +/// // stream using neco_stream_make(fd). +/// close(fd); +/// ``` +/// @param network must be "tcp", "tcp4", "tcp6", or "unix". +/// @param address the address to dial +/// @return On success, file descriptor (non-blocking) +/// @return On error, Neco error +/// @see Networking +/// @see neco_serve() +int neco_dial(const char *network, const char *address) { + return neco_dial_dl(network, address, INT64_MAX); +} + +static int listen_tcp_dl(const char *addr, int tcp_vers, int64_t deadline) { + struct addrinfo *ainfo; + int ret = getaddrinfo_from_tcp_addr_dl(addr, tcp_vers, &ainfo, deadline); + if (ret != NECO_OK) { + return ret; + } + int fd = socket0(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol); + bool ok = fd != -1 && setsockopt0(fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, + sizeof(int)) != -1; + ok = ok && bind0(fd, ainfo->ai_addr, ainfo->ai_addrlen) != -1; + freeaddrinfo(ainfo); + ok = ok && listen0(fd, SOMAXCONN) != -1; + ok = ok && neco_setnonblock(fd, true, 0) != -1; + if (!ok) { + if (fd != -1) { + close(fd); + fd = -1; + } + } + return fd; +} + +static int listen_unix_dl(const char *addr, int64_t deadline) { + (void)addr; (void)deadline; +#ifdef _WIN32 + return NECO_PERM; +#else + int fd = socket0(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + return NECO_ERROR; + } + struct sockaddr_un unaddr; + memset(&unaddr, 0, sizeof(struct sockaddr_un)); + unaddr.sun_family = AF_UNIX; + strncpy(unaddr.sun_path, addr, sizeof(unaddr.sun_path) - 1); + if (bind0(fd, (void*)&unaddr, sizeof(struct sockaddr_un)) == -1) { + close(fd); + return NECO_ERROR; + } + if (listen0(fd, SOMAXCONN) == -1) { + close(fd); + return NECO_ERROR; + } + if (neco_setnonblock(fd, true, 0) == -1) { + close(fd); + return NECO_ERROR; + } + return fd; +#endif +} + +static int serve_dl(const char *network, const char *address, int64_t deadline) +{ + if (!network || !address) { + return NECO_INVAL; + } else if (neco_getid() <= 0) { + return NECO_PERM; + } else if (strcmp(network, "tcp") == 0) { + return listen_tcp_dl(address, 0, deadline); + } else if (strcmp(network, "tcp4") == 0) { + return listen_tcp_dl(address, AF_INET, deadline); + } else if (strcmp(network, "tcp6") == 0) { + return listen_tcp_dl(address, AF_INET6, deadline); + } else if (strcmp(network, "unix") == 0) { + return listen_unix_dl(address, deadline); + } else { + return NECO_INVAL; + } +} + +/// Same as neco_serve() but with a deadline parameter. +int neco_serve_dl(const char *network, const char *address, int64_t deadline) { + int ret = serve_dl(network, address, deadline); + async_error_guard(ret); + return ret; +} + + +/// Listen on a local network address. +/// +/// **Example** +/// +/// ```c +/// int servefd = neco_serve("tcp", "127.0.0.1:8080"); +/// if (servefd < 0) { +/// // .. error, do something with it. +/// } +/// while (1) { +/// int fd = neco_accept(servefd, 0, 0); +/// // client accepted +/// } +/// +/// close(servefd); +/// ``` +/// @param network must be "tcp", "tcp4", "tcp6", or "unix". +/// @param address the address to serve on +/// @return On success, file descriptor (non-blocking) +/// @return On error, Neco error +/// @see Networking +/// @see neco_dial() +int neco_serve(const char *network, const char *address) { + return neco_serve_dl(network, address, INT64_MAX); +} + +//////////////////////////////////////////////////////////////////////////////// +// sync +//////////////////////////////////////////////////////////////////////////////// + +struct neco_mutex { + int64_t rtid; // runtime id + bool locked; // mutex is locked (read or write) + int rlocked; // read lock counter + struct colist queue; // coroutine doubly linked list +}; + + +static_assert(sizeof(neco_mutex) >= sizeof(struct neco_mutex), ""); + +static int mutex_init(neco_mutex *mutex) { + struct neco_mutex *mu = (void*)mutex; + if (!mu) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } + memset(mu, 0, sizeof(struct neco_mutex)); + mu->rtid = rt->id; + colist_init(&mu->queue); + return NECO_OK; +} + +int neco_mutex_init(neco_mutex *mutex) { + int ret = mutex_init(mutex); + error_guard(ret); + return ret; +} + +inline +static int check_mutex(struct coroutine *co, struct neco_mutex *mu) { + if (!mu) { + return NECO_INVAL; + } else if (!co) { + return NECO_PERM; + } else if (mu->rtid == 0) { + return neco_mutex_init((neco_mutex*)mu); + } else if (rt->id != mu->rtid) { + return NECO_PERM; + } + return NECO_OK; +} + +inline +static int mutex_trylock(struct coroutine *co, struct neco_mutex *mu, + bool tryonly, int64_t deadline) +{ + if (!tryonly) { + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + return ret; + } + } + if (mu->locked) { + return NECO_BUSY; + } + mu->locked = true; + return NECO_OK; +} + +inline +static int mutex_tryrdlock(struct coroutine *co, struct neco_mutex *mu, + bool tryonly, int64_t deadline) +{ + if (!tryonly) { + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + return ret; + } + } + if (!colist_is_empty(&mu->queue) || (mu->rlocked == 0 && mu->locked)) { + return NECO_BUSY; + } + mu->rlocked++; + mu->locked = true; + return NECO_OK; +} + +int neco_mutex_trylock(neco_mutex *mutex) { + struct coroutine *co = coself(); + struct neco_mutex *mu = (void*)mutex; + int ret = check_mutex(co, mu); + if (ret != NECO_OK) { + return ret; + } + ret = mutex_trylock(co, mu, true, 0); + async_error_guard(ret); + return ret; +} + +int neco_mutex_tryrdlock(neco_mutex *mutex) { + struct coroutine *co = coself(); + struct neco_mutex *mu = (void*)mutex; + int ret = check_mutex(co, mu); + if (ret != NECO_OK) { + return ret; + } + ret = mutex_tryrdlock(co, mu, true, 0); + async_error_guard(ret); + return ret; +} + +noinline +static int finish_lock(struct coroutine *co, struct neco_mutex *mu, + bool rlocked, int64_t deadline) +{ + co->rlocked = rlocked; + colist_push_back(&mu->queue, co); + rt->nlocked++; + copause(deadline); + rt->nlocked--; + remove_from_list(co); + co->rlocked = false; + return checkdl(co, INT64_MAX); +} + +static int mutex_lock_dl(struct coroutine *co, struct neco_mutex *mu, + int64_t deadline) +{ + int ret = mutex_trylock(co, mu, false, deadline); + if (ret == NECO_BUSY) { + // Another coroutine is holding this lock. + ret = finish_lock(co, mu, false, deadline); + } + return ret; +} + +int neco_mutex_lock_dl(neco_mutex *mutex, int64_t deadline) { + struct coroutine *co = coself(); + struct neco_mutex *mu = (void*)mutex; + int ret = check_mutex(co, mu); + if (ret != NECO_OK) { + return ret; + } + ret = mutex_lock_dl(co, mu, deadline); + async_error_guard(ret); + return ret; +} + +int neco_mutex_lock(neco_mutex *mutex) { + return neco_mutex_lock_dl(mutex, INT64_MAX); +} + +int neco_mutex_rdlock_dl(neco_mutex *mutex, int64_t deadline) { + struct coroutine *co = coself(); + struct neco_mutex *mu = (void*)mutex; + int ret = check_mutex(co, mu); + if (ret != NECO_OK) { + return ret; + } + ret = mutex_tryrdlock(co, mu, false, deadline); + if (ret == NECO_BUSY) { + // Another coroutine is holding this lock. + ret = finish_lock(co, mu, true, deadline); + } + async_error_guard(ret); + return ret; +} + +int neco_mutex_rdlock(neco_mutex *mutex) { + return neco_mutex_rdlock_dl(mutex, INT64_MAX); +} + +static void mutex_fastunlock(struct neco_mutex *mu) { + if (!mu->locked) { + return; + } + if (mu->rlocked > 0) { + // This lock is currently being used by a reader. + mu->rlocked--; + if (mu->rlocked > 0) { + // There are still more readers using this lock. + return; + } + } + if (colist_is_empty(&mu->queue)) { + // There are no more coroutines in the queue. + mu->locked = false; + return; + } + // Choose next coroutine to take the lock. + while (1) { + struct coroutine *co = colist_pop_front(&mu->queue); + // Schedule the coroutines to resume. + sched_resume(co); + if (co->rlocked) { + // The popped coroutine was a reader. + mu->rlocked++; + if (!colist_is_empty(&mu->queue) && mu->queue.head.next->rlocked) { + // The next in queue is also reader. + // Allow it to continue too. + continue; + } + } + break; + } + yield_for_sched_resume(); +} + +static int mutex_unlock(neco_mutex *mutex) { + struct coroutine *co = coself(); + struct neco_mutex *mu = (void*)mutex; + int ret = check_mutex(co, mu); + if (ret != NECO_OK) { + return ret; + } + mutex_fastunlock(mu); + coyield(); + return NECO_OK; +} + +int neco_mutex_unlock(neco_mutex *mutex) { + int ret = mutex_unlock(mutex); + error_guard(ret); + return ret; +} + +inline +static int mutex_fastlock(struct coroutine *co, struct neco_mutex *mu, + int64_t deadline) +{ + if (!mu->locked) { + mu->locked = true; + return NECO_OK; + } + int ret = mutex_lock_dl(co, mu, deadline); + async_error_guard(ret); + return ret; +} + +#ifdef NECO_TESTING +// An optimistic lock routine for testing only +int neco_mutex_fastlock(neco_mutex *mutex, int64_t deadline) { + struct coroutine *co = coself(); + return mutex_fastlock(co, (struct neco_mutex *)mutex, deadline); +} +#endif + +static int mutex_destroy(neco_mutex *mutex) { + struct coroutine *co = coself(); + struct neco_mutex *mu = (void*)mutex; + int ret = check_mutex(co, mu); + if (ret != NECO_OK) { + return ret; + } + if (mu->locked) { + return NECO_BUSY; + } + memset(mu, 0, sizeof(struct neco_mutex)); + return NECO_OK; +} + +int neco_mutex_destroy(neco_mutex *mutex) { + int ret = mutex_destroy(mutex); + error_guard(ret); + return ret; +} + +struct neco_waitgroup { + int64_t rtid; + int count; + struct colist queue; +}; + +static_assert(sizeof(neco_waitgroup) >= sizeof(struct neco_waitgroup), ""); + +inline +static int check_waitgroup(struct neco_waitgroup *wg) { + if (!wg) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } else if (wg->rtid == 0) { + return neco_waitgroup_init((neco_waitgroup*)wg); + } else if (rt->id != wg->rtid) { + return NECO_PERM; + } + return NECO_OK; +} + +static int waitgroup_init(neco_waitgroup *waitgroup) { + struct neco_waitgroup *wg = (void*)waitgroup; + if (!wg) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } + memset(wg, 0, sizeof(struct neco_waitgroup)); + wg->rtid = rt->id; + colist_init(&wg->queue); + return NECO_OK; +} + + +int neco_waitgroup_init(neco_waitgroup *waitgroup) { + int ret = waitgroup_init(waitgroup); + error_guard(ret); + return ret; +} + +static int waitgroup_add(neco_waitgroup *waitgroup, int delta) { + struct neco_waitgroup *wg = (void*)waitgroup; + int ret = check_waitgroup(wg); + if (ret != NECO_OK) { + return ret; + } + int waiters = wg->count + delta; + if (waiters < 0) { + return NECO_NEGWAITGRP; + } + wg->count = waiters; + return NECO_OK; +} + + +int neco_waitgroup_add(neco_waitgroup *waitgroup, int delta) { + int ret = waitgroup_add(waitgroup, delta); + error_guard(ret); + return ret; +} + +static int waitgroup_done(neco_waitgroup *waitgroup) { + struct neco_waitgroup *wg = (void*)waitgroup; + int ret = check_waitgroup(wg); + if (ret != NECO_OK) { + return ret; + } + if (wg->count == 0) { + return NECO_NEGWAITGRP; + } + wg->count--; + if (wg->count == 0 && !colist_is_empty(&wg->queue)) { + struct coroutine *co = colist_pop_front(&wg->queue); + if (colist_is_empty(&wg->queue)) { + // Only one waiter. Do a quick switch. + sco_resume(co->id); + } else { + // Many waiters. Batch them together. + do { + sched_resume(co); + co = colist_pop_front(&wg->queue); + } while (co); + yield_for_sched_resume(); + } + } + return NECO_OK; +} + +int neco_waitgroup_done(neco_waitgroup *waitgroup) { + int ret = waitgroup_done(waitgroup); + error_guard(ret); + return ret; +} + +static int waitgroup_wait_dl(neco_waitgroup *waitgroup, int64_t deadline) { + struct neco_waitgroup *wg = (void*)waitgroup; + int ret = check_waitgroup(wg); + if (ret != NECO_OK) { + return ret; + } + struct coroutine *co = coself(); + ret = checkdl(co, deadline); + if (ret != NECO_OK) { + return ret; + } + if (wg->count == 0) { + // It's probably a good idea to yield to another coroutine. + coyield(); + return NECO_OK; + } + // The coroutine was added to the deadliner queue. We can safely + // yield and wait to be woken at some point in the future. + colist_push_back(&wg->queue, co); + rt->nwaitgroupers++; + copause(deadline); + rt->nwaitgroupers--; + remove_from_list(co); + return checkdl(co, INT64_MAX); +} + +int neco_waitgroup_wait_dl(neco_waitgroup *waitgroup, int64_t deadline) { + int ret = waitgroup_wait_dl(waitgroup, deadline); + async_error_guard(ret); + return ret; +} + +int neco_waitgroup_wait(neco_waitgroup *waitgroup) { + return neco_waitgroup_wait_dl(waitgroup, INT64_MAX); +} + +static int waitgroup_destroy(neco_waitgroup *waitgroup) { + struct neco_waitgroup *wg = (void*)waitgroup; + int ret = check_waitgroup(wg); + if (ret != NECO_OK) { + return ret; + } + memset(wg, 0, sizeof(struct neco_waitgroup)); + return NECO_OK; +} + +int neco_waitgroup_destroy(neco_waitgroup *waitgroup) { + int ret = waitgroup_destroy(waitgroup); + error_guard(ret); + return ret; +} + +struct neco_cond { + int64_t rtid; // runtime id + struct colist queue; // coroutine doubly linked list +}; + +static_assert(sizeof(neco_cond) >= sizeof(struct neco_cond), ""); + +static int cond_init0(struct neco_cond *cv) { + memset(cv, 0, sizeof(struct neco_cond)); + cv->rtid = rt->id; + colist_init(&cv->queue); + return NECO_OK; +} + +static int cond_init(neco_cond *cond) { + struct neco_cond *cv = (void*)cond; + if (!cv) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } + return cond_init0((struct neco_cond*)cond); +} + +int neco_cond_init(neco_cond *cond) { + int ret = cond_init(cond); + error_guard(ret); + return ret; +} + +inline +static int check_cond(struct neco_cond *cv) { + if (!cv) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } else if (cv->rtid == 0) { + return cond_init0(cv); + } else if (rt->id != cv->rtid) { + return NECO_PERM; + } + return NECO_OK; +} + +static int cond_destroy(neco_cond *cond) { + struct neco_cond *cv = (void*)cond; + int ret = check_cond(cv); + if (ret != NECO_OK) { + return ret; + } + memset(cv, 0, sizeof(struct neco_cond)); + return NECO_OK; +} + +int neco_cond_destroy(neco_cond *cond) { + int ret = cond_destroy(cond); + error_guard(ret); + return ret; +} + +static int cond_signal(neco_cond *cond) { + struct neco_cond *cvar = (void*)cond; + int ret = check_cond(cvar); + if (ret != NECO_OK) { + return ret; + } + struct coroutine *co = colist_pop_front(&cvar->queue); + if (co) { + sco_resume(co->id); + } + return NECO_OK; +} + +int neco_cond_signal(neco_cond *cond) { + int ret = cond_signal(cond); + error_guard(ret); + return ret; +} + +static int cond_broadcast(neco_cond *cond) { + struct neco_cond *cvar = (void*)cond; + int ret = check_cond(cvar); + if (ret != NECO_OK) { + return ret; + } + struct coroutine *co = colist_pop_front(&cvar->queue); + while (co) { + sched_resume(co); + co = colist_pop_front(&cvar->queue); + } + yield_for_sched_resume(); + return NECO_OK; +} + +int neco_cond_broadcast(neco_cond *cond) { + int ret = cond_broadcast(cond); + error_guard(ret); + return ret; +} + +static int cond_wait_dl(neco_cond *cond, neco_mutex *mutex, int64_t deadline) { + struct neco_cond *cvar = (struct neco_cond*)cond; + int ret = check_cond(cvar); + if (ret != NECO_OK) { + return ret; + } + struct coroutine *co = coself(); + struct neco_mutex *mu = (struct neco_mutex*)mutex; + ret = check_mutex(co, mu); + if (ret != NECO_OK) { + return ret; + } + if (co->canceled) { + co->canceled = false; + return NECO_CANCELED; + } + mutex_fastunlock(mu); + // The coroutine was added to the deadliner queue. We can safely + // yield and wait to be woken at some point in the future. + colist_push_back(&cvar->queue, co); + rt->ncondwaiters++; + copause(deadline); + rt->ncondwaiters--; + remove_from_list(co); + ret = checkdl(co, INT64_MAX); + // Must relock. + while (mutex_fastlock(co, mu, INT64_MAX) != NECO_OK) { } + return ret; +} + +int neco_cond_wait_dl(neco_cond *cond, neco_mutex *mutex, int64_t deadline) { + int ret = cond_wait_dl(cond, mutex, deadline); + async_error_guard(ret); + return ret; +} + +int neco_cond_wait(neco_cond *cond, neco_mutex *mutex) { + return neco_cond_wait_dl(cond, mutex, INT64_MAX); +} + +// Returns a string that indicates which coroutine method is being used by +// the program. Such as "asm,aarch64" or "ucontext", etc. +const char *neco_switch_method(void) { + return sco_info_method(); +} + +static int setcanceltype(int type, int *oldtype) { + if (type != NECO_CANCEL_ASYNC && type != NECO_CANCEL_INLINE) { + return NECO_INVAL; + } + struct coroutine *co = coself(); + if (!co) { + return NECO_PERM; + } + if (oldtype) { + *oldtype = co->canceltype; + } + co->canceltype = type; + return NECO_OK; +} + +int neco_setcanceltype(int type, int *oldtype) { + int ret = setcanceltype(type, oldtype); + error_guard(ret); + return ret; +} + +static int setcancelstate(int state, int *oldstate) { + if (state != NECO_CANCEL_ENABLE && state != NECO_CANCEL_DISABLE) { + return NECO_INVAL; + } + struct coroutine *co = coself(); + if (!co) { + return NECO_PERM; + } + if (oldstate) { + *oldstate = co->cancelstate; + } + co->cancelstate = state; + return NECO_OK; +} + +int neco_setcancelstate(int state, int *oldstate) { + int ret = setcancelstate(state, oldstate); + error_guard(ret); + return ret; +} + +static void cleanup_push(struct cleanup *handler, void (*routine)(void *), + void *arg) +{ + struct coroutine *co = coself(); + handler->routine = routine; + handler->arg = arg; + handler->next = co->cleanup; + co->cleanup = handler; +} + +static void cleanup_pop(int execute) { + struct coroutine *co = coself(); + struct cleanup *handler = co->cleanup; + co->cleanup = handler->next; + if (execute && handler->routine) { + handler->routine(handler->arg); + } +} + +static_assert(sizeof(struct cleanup) <= 32, ""); + +void __neco_c0(void *cl, void (*routine)(void *), void *arg) { + cleanup_push(cl, routine, arg); +} + +void __neco_c1(int execute) { + cleanup_pop(execute); +} + +// coexit is performed at the exit of every coroutine. +// Most general coroutine cleanup is performed here because this operation +// always runs from inside of the coroutine context/stack and during a standard +// runtime scheduler step. +// The async flag indicates that the coroutine is being exited before the entry +// function has been completed. In this case the cleanup push/pop stack stack +// will immediately be unrolled and the coroutine will cease execution. +noinline +static void coexit(bool async) { + // The current coroutine _must_ exit. + struct coroutine *co = coself(); + + if (async) { + // Run the cleanup stack + while (co->cleanup) { + cleanup_pop(1); + } + } + + // Delete from map + comap_delete(&rt->all, co); + + // Notify all cancel waiters (if any) + bool sched = false; + struct coroutine *cowaiter = colist_pop_front(&co->cancellist); + while (cowaiter) { + sched_resume(cowaiter); + sched = true; + cowaiter = colist_pop_front(&co->cancellist); + } + + // Notify all join waiters (if any) + cowaiter = colist_pop_front(&co->joinlist); + while (cowaiter) { + sched_resume(cowaiter); + sched = true; + cowaiter = colist_pop_front(&co->joinlist); + } + + // Close the generator + if (co->gen) { + chan_close((void*)co->gen); + chan_fastrelease((void*)co->gen); + co->gen = 0; + } + + // Free the call arguments + cofreeargs(co); + + if (sched) { + yield_for_sched_resume(); + } + + if (async) { + sco_exit(); + } +} + +static void co_pipe(int argc, void *argv[]) { + must(argc == 3); + char *path = argv[0]; + int64_t secret = *(int64_t*)argv[1]; + int *fd_out = argv[2]; + int fd = neco_dial("unix", path); + neco_write(fd, &secret, 8); + *fd_out = fd; +} + +static int _pipe_(int pipefd[2]) { + if (!pipefd) { + errno = EINVAL; + return -1; + } + if (!rt) { + errno = EPERM; + return -1; + } + int oldstate = 0; + neco_setcancelstate(NECO_CANCEL_DISABLE, &oldstate); + int fd0 = -2; + int fd1 = -2; + int ret = -1; + int64_t secret; + uint64_t tmpkey; + neco_rand(&secret, 8, NECO_PRNG); + neco_rand(&tmpkey, 8, NECO_PRNG); + char path[PATH_MAX]; + snprintf(path, sizeof(path), "/tmp/neco.%" PRIu64 ".sock", tmpkey); + int ln = neco_serve("unix", path); + if (ln > 0) { + // State a coroutine to connect to the listener. + int nret = neco_start(co_pipe, 3, path, &secret, &fd0); + neco_errconv_to_sys(nret); + if (nret == NECO_OK) { + int64_t dl = neco_now() + NECO_SECOND * 5; + fd1 = neco_accept_dl(ln, 0, 0, dl); + neco_errconv_to_sys(fd1); + if (fd1 > 0) { + // Other side of pipe is connected. + // Waiting for the handshake. + int64_t data; + ssize_t n = neco_read_dl(fd1, &data, 8, dl); + neco_errconv_to_sys(n); + if (n == 8 && data == secret) { + // Handshake good. + ret = 0; + } + } + } + close(ln); + unlink(path); + } + int perrno = errno; + neco_yield(); + if (ret == 0) { + pipefd[0] = fd0; + pipefd[1] = fd1; + fd0 = -1; + fd1 = -1; + } + close(fd0); + close(fd1); + neco_setcancelstate(oldstate, 0); + errno = perrno; + return ret; +} + +/// Create a bidirection data channel for communicating between threads. +/// @param pipefd The array pipefd is used to return two file descriptors +// referring to the ends of the pipe. +/// @return On success, zero is returned. On error, -1 is returned, errno is +/// set to indicate the error, and pipefd is left unchanged. +/// @note This is the POSIX equivalent of `pipe()` with the following changes: +/// The original `pipe()` function only creates a unidirectional data channel +/// while this one is bidirectional, meaning that both file descriptors can +/// be used by different threads for both reading and writing, similar to a +/// socket. Also, the file descriptors are returned in non-blocking state. +int neco_pipe(int pipefd[2]) { + int ret = _pipe_(pipefd); + error_guard(ret); + return ret; +} + +static int join_dl(int64_t id, int64_t deadline) { + struct coroutine *co = coself(); + if (!co) { + return NECO_PERM; + } + struct coroutine *cotarg = cofind(id); + if (!cotarg) { + return NECO_OK; + } + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + return ret; + } + if (cotarg == co) { + return NECO_PERM; + } + colist_push_back(&cotarg->joinlist, co); + cotarg->njoinlist++; + copause(deadline); + remove_from_list(co); + cotarg->njoinlist--; + return checkdl(co, INT64_MAX); +} + +/// Same as neco_join() but with a deadline parameter. +int neco_join_dl(int64_t id, int64_t deadline) { + int ret = join_dl(id, deadline); + async_error_guard(ret); + return ret; +} + +/// Wait for a coroutine to terminate. +/// If that coroutine has already terminated or is not found, then this +/// operation returns immediately. +/// +/// **Example** +/// +/// ``` +/// // Start a new coroutine +/// neco_start(coroutine, 0); +/// +/// // Get the identifier of the new coroutine. +/// int64_t id = neco_lastid(); +/// +/// // Wait until the coroutine has terminated. +/// neco_join(id); +/// ``` +/// +/// @param id Coroutine identifier +/// @return NECO_OK Success +/// @return NECO_CANCELED Operation canceled +/// @return NECO_PERM Operation called outside of a coroutine +/// @see neco_join_dl() +int neco_join(int64_t id) { + return neco_join_dl(id, INT64_MAX); +} + +#define DEFAULT_BUFFER_SIZE 4096 + +struct bufrd { + size_t len; + size_t pos; + char *data; +}; + +struct bufwr { + size_t len; + char *data; +}; + +struct neco_stream { + int fd; + int64_t rtid; + bool buffered; + + struct bufrd rd; + struct bufwr wr; + size_t cap; + char data[]; +}; + +typedef struct neco_stream neco_stream; + +static int stream_make_buffered_size(neco_stream **stream, int fd, bool buffered, + size_t buffer_size) +{ + if (!stream || fd < 0) { + return NECO_INVAL; + } else if (!rt) { + return NECO_PERM; + } + size_t memsize; + if (buffered) { + buffer_size = buffer_size == 0 ? DEFAULT_BUFFER_SIZE : buffer_size; + memsize = sizeof(neco_stream) + buffer_size; + } else { + memsize = offsetof(neco_stream, rd); + } + *stream = malloc0(memsize); + if (!*stream) { + return NECO_NOMEM; + } + memset(*stream, 0, memsize); + (*stream)->rtid = rt->id; + (*stream)->fd = fd; + (*stream)->buffered = buffered; + if (buffered) { + (*stream)->cap = buffer_size; + } + return NECO_OK; +} + +int neco_stream_make_buffered_size(neco_stream **stream, int fd, + size_t buffer_size) +{ + int ret = stream_make_buffered_size(stream, fd, true, buffer_size); + error_guard(ret); + return ret; +} + +int neco_stream_make_buffered(neco_stream **stream, int fd) { + int ret = stream_make_buffered_size(stream, fd, true, 0); + error_guard(ret); + return ret; +} + +int neco_stream_make(neco_stream **stream, int fd) { + int ret = stream_make_buffered_size(stream, fd, false, 0); + error_guard(ret); + return ret; +} + +static int stream_release(neco_stream *stream) { + if (!stream) { + return NECO_INVAL; + } else if (!rt || stream->rtid != rt->id) { + return NECO_PERM; + } + if (stream->buffered) { + if (stream->rd.data && stream->rd.data != stream->data) { + free0(stream->rd.data); + } + if (stream->wr.data && stream->wr.data != stream->data) { + free0(stream->wr.data); + } + } + free0(stream); + return NECO_OK; +} + +int neco_stream_release(neco_stream *stream) { + int ret = stream_release(stream); + error_guard(ret); + return ret; +} + +static bool ensure_rd_data(neco_stream *stream) { + if (!stream->rd.data) { + if (!stream->wr.data) { + stream->rd.data = stream->data; + } else { + stream->rd.data = malloc0(stream->cap); + if (!stream->rd.data) { + return false; + } + } + } + return true; +} + +static bool ensure_wr_data(neco_stream *stream) { + if (!stream->wr.data) { + if (!stream->rd.data) { + stream->wr.data = stream->data; + } else { + stream->wr.data = malloc0(stream->cap); + if (!stream->wr.data) { + return false; + } + } + } + return true; +} + +static ssize_t stream_read_dl(neco_stream *stream, void *data, size_t nbytes, + int64_t deadline) +{ + if (!stream) { + return NECO_INVAL; + } else if (!rt || stream->rtid != rt->id) { + return NECO_PERM; + } + if (!stream->buffered) { + return neco_read_dl(stream->fd, data, nbytes, deadline); + } + if (!ensure_rd_data(stream)) { + return NECO_NOMEM; + } + if (stream->rd.len == 0) { + ssize_t n = neco_read_dl(stream->fd, stream->rd.data, stream->cap, + deadline); + if (n == -1) { + return neco_errconv_from_sys(); + } + stream->rd.len = (size_t)n; + stream->rd.pos = 0; + } + nbytes = stream->rd.len < nbytes ? stream->rd.len : nbytes; + memcpy(data, stream->rd.data + stream->rd.pos, nbytes); + stream->rd.pos += nbytes; + stream->rd.len -= nbytes; + return (ssize_t)nbytes; +} + +ssize_t neco_stream_read_dl(neco_stream *stream, void *data, size_t nbytes, + int64_t deadline) +{ + ssize_t ret = stream_read_dl(stream, data, nbytes, deadline); + if (ret == 0 && nbytes > 0 && neco_lasterr() == NECO_OK) { + ret = NECO_EOF; + } + error_guard(ret); + return ret; +} + +ssize_t neco_stream_read(neco_stream *stream, void *data, size_t nbytes) { + return neco_stream_read_dl(stream, data, nbytes, INT64_MAX); +} + +static ssize_t stream_readfull_dl(neco_stream *stream, void *data, + size_t nbytes, int64_t deadline) +{ + ssize_t nread = 0; + do { + ssize_t n = neco_stream_read_dl(stream, data, nbytes, deadline); + if (n <= 0) { + if (nread == 0) { + nread = n; + } + return nread; + } else { + data = (char*)data + n; + nbytes -= (size_t)n; + nread += n; + } + } while (nbytes > 0); + return nread; +} + +ssize_t neco_stream_readfull_dl(neco_stream *stream, void *data, size_t nbytes, + int64_t deadline) +{ + ssize_t ret = stream_readfull_dl(stream, data, nbytes, deadline); + error_guard(ret); + return ret; +} + +ssize_t neco_stream_readfull(neco_stream *stream, void *data, size_t nbytes) { + return neco_stream_readfull_dl(stream, data, nbytes, INT64_MAX); +} + +static ssize_t stream_buffered_read_size(neco_stream *stream) { + if (!stream) { + return NECO_INVAL; + } else if (!rt || stream->rtid != rt->id) { + return NECO_PERM; + } else if (!stream->buffered) { + return 0; + } + return (ssize_t)stream->rd.len; +} + +ssize_t neco_stream_buffered_read_size(neco_stream *stream) { + ssize_t ret = stream_buffered_read_size(stream); + error_guard(ret); + return ret; +} + +/// Same as neco_stream_read_byte() but with a deadline parameter. +int neco_stream_read_byte_dl(neco_stream *stream, int64_t deadline) { + unsigned char byte; + if (rt && stream && stream->rtid == rt->id && stream->buffered && + stream->rd.len > 0 && checkdl(coself(), deadline) == NECO_OK) + { + // fast-track read byte + byte = (unsigned char)stream->rd.data[stream->rd.pos]; + stream->rd.pos++; + stream->rd.len--; + } else { + ssize_t ret = neco_stream_read_dl(stream, &byte, 1, deadline); + if (ret != 1) { + return ret; + } + } + return byte; +} + +/// Read and returns a single byte. If no byte is available, returns an error. +int neco_stream_read_byte(neco_stream *stream) { + return neco_stream_read_byte_dl(stream, INT64_MAX); +} + +static int stream_unread_byte(neco_stream *stream) { + if (!stream) { + return NECO_INVAL; + } else if (!rt || stream->rtid != rt->id) { + return NECO_PERM; + } else if (!stream->buffered || stream->rd.pos == 0) { + return NECO_UNREADFAIL; + } + stream->rd.pos--; + stream->rd.len++; + return NECO_OK; +} + +/// Unread the last byte. Only the most recently read byte can be unread. +int neco_stream_unread_byte(neco_stream *stream) { + int ret = stream_unread_byte(stream); + error_guard(ret); + return ret; +} + +static int stream_flush_dl(neco_stream *stream, int64_t deadline) { + if (!stream) { + return NECO_INVAL; + } else if (!rt || stream->rtid != rt->id) { + return NECO_PERM; + } else if (!stream->buffered) { + // Not buffered. Only need to check the deadline. + return checkdl(coself(), deadline); + } + ssize_t n = neco_write_dl(stream->fd, stream->wr.data, stream->wr.len, + deadline); + if (n <= 0) { + if (n == 0 && stream->wr.len == 0) { + return NECO_OK; + } else { + return neco_errconv_from_sys(); + } + } + if ((size_t)n < stream->wr.len) { + // parital write. + memmove(stream->wr.data, stream->wr.data+n, stream->wr.len-(size_t)n); + stream->wr.len -= (size_t)n; + return NECO_PARTIALWRITE; + } else { + stream->wr.len = 0; + return NECO_OK; + } +} + +/// Same as neco_stream_flush() but with a deadline parameter. +int neco_stream_flush_dl(neco_stream *stream, int64_t deadline) { + int ret = stream_flush_dl(stream, deadline); + async_error_guard(ret); + return ret; +} + +/// Flush writes any buffered data to the underlying file descriptor. +int neco_stream_flush(neco_stream *stream) { + return neco_stream_flush_dl(stream, INT64_MAX); +} + +static ssize_t stream_write_dl(neco_stream *stream, const void *data, + size_t nbytes, int64_t deadline) +{ + if (!stream) { + return NECO_INVAL; + } else if (!rt || stream->rtid != rt->id) { + return NECO_PERM; + } + if (!stream->buffered) { + return neco_write_dl(stream->fd, data, nbytes, deadline); + } + if (!ensure_wr_data(stream)) { + return NECO_NOMEM; + } + ssize_t nwritten = 0; + while (nbytes > 0) { + if (stream->wr.len == stream->cap) { + // Buffer is full. + int ret = neco_stream_flush_dl(stream, deadline); + if (ret != NECO_OK) { + if (nwritten == 0) { + nwritten = ret; + } + break; + } + } + // Copy data into the buffer + size_t n = stream->cap - stream->wr.len; + n = n < nbytes ? n : nbytes; + memcpy(stream->wr.data+stream->wr.len, data, n); + stream->wr.len += n; + data = (char*)data + n; + nbytes -= n; + nwritten += n; + } + return nwritten; +} + +ssize_t neco_stream_write_dl(neco_stream *stream, const void *data, + size_t nbytes, int64_t deadline) +{ + ssize_t ret = stream_write_dl(stream, data, nbytes, deadline); + async_error_guard(ret); + return ret; +} + +ssize_t neco_stream_write(neco_stream *stream, const void *data, size_t nbytes){ + return neco_stream_write_dl(stream, data, nbytes, INT64_MAX); +} + +static ssize_t stream_buffered_write_size(neco_stream *stream) { + if (!stream) { + return NECO_INVAL; + } else if (!rt || stream->rtid != rt->id) { + return NECO_PERM; + } else if (!stream->buffered) { + return 0; + } + return (ssize_t)stream->wr.len; +} + +ssize_t neco_stream_buffered_write_size(neco_stream *stream) { + ssize_t ret = stream_buffered_write_size(stream); + error_guard(ret); + return ret; +} + +static int stream_close_dl(neco_stream *stream, int64_t deadline) { + if (!stream) { + return NECO_INVAL; + } else if (!rt || stream->rtid != rt->id) { + return NECO_PERM; + } + int ret = NECO_OK; + if (stream->buffered && (deadline < INT64_MAX || stream->wr.len > 0)) { + ret = stream_flush_dl(stream, deadline); + } + close(stream->fd); + stream_release(stream); + return ret; +} + +/// Close a stream with a deadline. +/// A deadline is provided to accomodate for buffered streams that may need to +/// flush bytes on close +int neco_stream_close_dl(neco_stream *stream, int64_t deadline) { + int ret = stream_close_dl(stream, deadline); + error_guard(ret); + return ret; +} + +/// Close a stream +int neco_stream_close(neco_stream *stream) { + return neco_stream_close_dl(stream, INT64_MAX); +} + +// pcg-family random number generator + +static int64_t rincr(int64_t seed) { + return (int64_t)((uint64_t)(seed)*6364136223846793005 + 1); +} + +static uint32_t rgen(int64_t seed) { + uint64_t state = (uint64_t)seed; + uint32_t xorshifted = (uint32_t)(((state >> 18) ^ state) >> 27); + uint32_t rot = (uint32_t)(state >> 59); + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); +} + +static uint32_t rnext(int64_t *seed) { + *seed = rincr(rincr(*seed)); // twice called intentionally + uint32_t value = rgen(*seed); + return value; +} + +static void pcgrandom_buf(void *data, size_t nbytes, int64_t *seed) { + while (nbytes >= 4) { + uint32_t value = rnext(seed); + ((char*)data)[0] = ((char*)&value)[0]; + ((char*)data)[1] = ((char*)&value)[1]; + ((char*)data)[2] = ((char*)&value)[2]; + ((char*)data)[3] = ((char*)&value)[3]; + data = (char*)data + 4; + nbytes -= 4; + } + if (nbytes > 0) { + uint32_t value = rnext(seed); + for (size_t i = 0; i < nbytes; i++) { + ((char*)data)[i] = ((char*)&value)[i]; + } + } +} + +static int rand_setseed(int64_t seed, int64_t *oldseed) { + if (!rt) { + return NECO_PERM; + } + if (oldseed) { + *oldseed = rt->rand_seed; + } + rt->rand_seed = seed; + return NECO_OK; +} + +/// Set the random seed for the Neco pseudorandom number generator. +/// +/// The provided seed is only used for the (non-crypto) NECO_PRNG and is +/// ignored for NECO_CPRNG. +/// +/// @param seed +/// @param oldseed[out] The previous seed +/// @return NECO_OK Success +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @see Random +int neco_rand_setseed(int64_t seed, int64_t *oldseed) { + int ret = rand_setseed(seed, oldseed); + error_guard(ret); + return ret; +} + +static int rand_dl(void *data, size_t nbytes, int attr, int64_t deadline) { + if (attr != NECO_CSPRNG && attr != NECO_PRNG) { + return NECO_INVAL; + } + if (!rt) { + return NECO_PERM; + } + void (*arc4random_buf_l)(void *, size_t) = 0; + if (attr == NECO_CSPRNG) { +#ifdef __linux__ + // arc4random_buf should be available in libbsd for Linux. + if (!rt->arc4random_buf) { + if (!rt->libbsd_handle) { + rt->libbsd_handle = dlopen("libbsd.so.0", RTLD_LAZY); + must(rt->libbsd_handle); + } + rt->arc4random_buf = (void(*)(void*,size_t)) + dlsym(rt->libbsd_handle, "arc4random_buf"); + must(rt->arc4random_buf); + } + arc4random_buf_l = rt->arc4random_buf; +#elif defined(__APPLE__) || defined(__FreeBSD__) + arc4random_buf_l = arc4random_buf; +#endif + } + struct coroutine *co = coself(); + while (1) { + int ret = checkdl(co, deadline); + if (ret != NECO_OK) { + return ret; + } + if (nbytes == 0) { + break; + } + size_t partsz = nbytes < 256 ? nbytes : 256; + if (arc4random_buf_l) { + arc4random_buf_l(data, partsz); + } else { + pcgrandom_buf(data, partsz, &rt->rand_seed); + } + nbytes -= partsz; + data = (char*)data + partsz; + if (nbytes == 0) { + break; + } + coyield(); + } + return NECO_OK; +} + +/// Same as neco_rand() but with a deadline parameter. +int neco_rand_dl(void *data, size_t nbytes, int attr, int64_t deadline) { + int ret = rand_dl(data, nbytes, attr, deadline); + async_error_guard(ret); + return ret; +} + +/// Generator random bytes +/// +/// This operation can generate cryptographically secure data by +/// providing the NECO_CSPRNG option or non-crypto secure data with +/// NECO_PRNG. +/// +/// Non-crypto secure data use the [pcg-family](https://www.pcg-random.org) +/// random number generator. +/// +/// @param data buffer for storing random bytes +/// @param nbytes number of bytes to generate +/// @param attr NECO_PRNG or NECO_CSPRNG +/// @return NECO_OK Success +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_CANCELED Operation canceled +int neco_rand(void *data, size_t nbytes, int attr) { + return neco_rand_dl(data, nbytes, attr, INT64_MAX); +} + +/// Terminate the current coroutine. +/// +/// Any clean-up handlers established by neco_cleanup_push() that +/// have not yet been popped, are popped (in the reverse of the order +/// in which they were pushed) and executed. +/// +/// Calling this from outside of a coroutine context does nothing and will be +/// treated effectivley as a no-op. +void neco_exit(void) { + if (rt) { + coexit(true); + } +} + +struct neco_gen { + // A generator is actually a channel in disguise. + int _; +}; + +static int gen_start_chk(neco_gen **gen, size_t data_size) { + if (!gen || data_size > INT_MAX) { + return NECO_INVAL; + } + if (!rt) { + return NECO_PERM; + } + return NECO_OK; +} + +/// Start a generator coroutine +/// +/// **Example** +/// +/// ``` +/// void coroutine(int argc, void *argv[]) { +/// // Yield each int to the caller, one at a time. +/// for (int i = 0; i < 10; i++) { +/// neco_gen_yield(&i); +/// } +/// } +/// +/// int neco_main(int argc, char *argv[]) { +/// +/// // Create a new generator coroutine that is used to send ints. +/// neco_gen *gen; +/// neco_gen_start(&gen, sizeof(int), coroutine, 0); +/// +/// // Iterate over each int until the generator is closed. +/// int i; +/// while (neco_gen_next(gen, &i) != NECO_CLOSED) { +/// printf("%d\n", i); +/// } +/// +/// // This coroutine no longer needs the generator. +/// neco_gen_release(gen); +/// return 0; +/// } +/// ``` +/// +/// @param[out] gen Generator object +/// @param data_size Data size of messages +/// @param coroutine Generator coroutine +/// @param argc Number of arguments +/// @return NECO_OK Success +/// @return NECO_NOMEM The system lacked the necessary resources +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @note The caller is responsible for freeing the generator object with +/// with neco_gen_release(). +int neco_gen_start(neco_gen **gen, size_t data_size, + void(*coroutine)(int argc, void *argv[]), int argc, ...) +{ + int ret = gen_start_chk(gen, data_size); + if (ret == NECO_OK) { + va_list args; + va_start(args, argc); + ret = startv(coroutine, argc, &args, 0, (void*)gen, data_size); + va_end(args); + } + error_guard(ret); + return ret; +} + +/// Same as neco_gen_start() but using an array for arguments. +int neco_gen_startv(neco_gen **gen, size_t data_size, + void(*coroutine)(int argc, void *argv[]), int argc, void *argv[]) +{ + int ret = gen_start_chk(gen, data_size); + if (ret == NECO_OK) { + ret = startv(coroutine, argc, 0, argv, (void*)gen, data_size); + } + error_guard(ret); + return ret; +} + +/// Retain a reference of the generator so it can be shared with other +/// coroutines. +/// +/// This is needed for avoiding use-after-free bugs. +/// +/// See neco_gen_start() for an example. +/// +/// @param gen The generator +/// @return NECO_OK Success +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @note The caller is responsible for releasing the reference with +/// neco_gen_release() +/// @see Generators +/// @see neco_gen_release() +int neco_gen_retain(neco_gen *gen) { + return neco_chan_retain((void*)gen); +} + +/// Release a reference to a generator +/// +/// See neco_gen_start() for an example. +/// +/// @param gen The generator +/// @return NECO_OK Success +/// @return NECO_INVAL An invalid parameter was provided +/// @return NECO_PERM Operation called outside of a coroutine +/// @see Generators +/// @see neco_gen_retain() +int neco_gen_release(neco_gen *gen) { + return neco_chan_release((void*)gen); +} + +/// Same as neco_gen_yield() but with a deadline parameter. +int neco_gen_yield_dl(void *data, int64_t deadline) { + struct coroutine *co = coself(); + if (!co) { + return NECO_PERM; + } + if (!co->gen) { + return NECO_NOTGENERATOR; + } + return neco_chan_send_dl((void*)co->gen, data, deadline); +} + +/// Send a value to the generator for the next iteration. +/// +/// See neco_gen_start() for an example. +int neco_gen_yield(void *data) { + return neco_gen_yield_dl(data, INT64_MAX); +} + +/// Same as neco_gen_next() but with a deadline parameter. +int neco_gen_next_dl(neco_gen *gen, void *data, int64_t deadline) { + return neco_chan_recv_dl((void*)gen, data, deadline); +} + +/// Receive the next value from a generator. +/// +/// See neco_gen_start() for an example. +int neco_gen_next(neco_gen *gen, void *data) { + return neco_gen_next_dl(gen, data, INT64_MAX); +} + +/// Close the generator. +/// @param gen Generator +/// @return NECO_OK Success +int neco_gen_close(neco_gen *gen) { + return neco_chan_close((void*)gen); +} + +/// Test a neco error code. +/// @return the provided value, unchanged. +int neco_testcode(int errcode) { + int ret = errcode; + error_guard(ret); + return ret; +} + +/// Stop normal execution of the current coroutine, print stack trace, and exit +/// the program immediately. +int neco_panic(const char *fmt, ...) { + char buf[256]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + panic("%s", buf); + return NECO_OK; +} + +static int suspend_dl(int64_t deadline) { + struct coroutine *co = coself(); + if (!co) { + return NECO_PERM; + } + + co->suspended = true; + rt->nsuspended++; + copause(deadline); + rt->nsuspended--; + co->suspended = false; + + return checkdl(co, INT64_MAX); +} + +/// Same as neco_suspend() but with a deadline parameter. +int neco_suspend_dl(int64_t deadline) { + int ret = suspend_dl(deadline); + async_error_guard(ret); + return ret; +} + +/// Suspend the current coroutine. +/// @return NECO_OK Success +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_CANCELED Operation canceled +/// @see neco_resume +/// @see neco_suspend_dl +int neco_suspend(void) { + return neco_suspend_dl(INT64_MAX); +} + +static int resume(int64_t id) { + if (!rt) { + return NECO_PERM; + } + struct coroutine *co = cofind(id); + if (!co) { + return NECO_NOTFOUND; + } + if (!co->suspended) { + return NECO_NOTSUSPENDED; + } + sco_resume(co->id); + return NECO_OK; +} + +/// Resume a suspended roroutine +/// @return NECO_OK Success +/// @return NECO_PERM Operation called outside of a coroutine +/// @return NECO_NOTFOUND Coroutine not found +/// @return NECO_NOTSUSPENDED Coroutine not suspended +/// @see neco_suspend +int neco_resume(int64_t id) { + int ret = resume(id); + error_guard(ret); + return ret; +} + +#ifndef NECO_NOWORKER +struct iowork { + void(*work)(void *udata); + void *udata; + struct coroutine *co; + struct runtime *rt; +}; + +static void iowork(void *udata) { + struct iowork *info = udata; + info->work(info->udata); + pthread_mutex_lock(&info->rt->iomu); + colist_push_back(&info->rt->iolist, info->co); + pthread_mutex_unlock(&info->rt->iomu); +} +#endif + +static int workfn(int64_t pin, void(*work)(void *udata), void *udata) { + struct coroutine *co = coself(); + if (!work) { + return NECO_INVAL; + } + if (!co) { + return NECO_PERM; + } +#ifdef NECO_NOWORKER + // Run in foreground + work(udata); +#else + // Run in background + struct iowork info = { .work = work, .udata = udata, .co = co, .rt = rt }; + rt->niowaiters++; + while (!worker_submit(rt->worker, pin, iowork, &info)) { + sco_yield(); + } + sco_pause(); + rt->niowaiters--; +#endif + return NECO_OK; +} + +/// Perform work in a background thread and wait until the work is done. +/// +/// This operation cannot be canceled and cannot timeout. It's the responibilty +/// of the caller to figure out a mechanism for doing those things from inside +/// of the work function. +/// +/// The work function will not be inside of a Neco context, thus all `neco_*` +/// functions will fail if called from inside of the work function. +/// +/// @param pin pin to a thread, or use -1 for round robin selection. +/// @param work the work, must not be null +/// @param udata any user data +/// @return NECO_OK Success +/// @return NECO_NOMEM The system lacked the necessary resources +/// @return NECO_INVAL An invalid parameter was provided +/// @note There is no way to cancel or timeout this operation +/// @note There is no way to cancel or timeout this operation +int neco_work(int64_t pin, void(*work)(void *udata), void *udata) { + int ret = workfn(pin, work, udata); + error_guard(ret); + return ret; +} diff --git a/neco.h b/neco.h new file mode 100644 index 0000000..2cb8182 --- /dev/null +++ b/neco.h @@ -0,0 +1,452 @@ +// https://github.com/tidwall/neco +// +// Copyright 2024 Joshua J Baker. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. +// +// Neco -- Coroutine library for C + +#ifndef NECO_H +#define NECO_H + +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif + +//////////////////////////////////////////////////////////////////////////////// +// basic operations +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup BasicOperations Basic operations +/// Neco provides standard operations for starting a coroutine, sleeping, +/// suspending, resuming, yielding to another coroutine, joining/waiting for +/// child coroutines, and exiting a running coroutine. +/// @{ +int neco_start(void(*coroutine)(int argc, void *argv[]), int argc, ...); +int neco_startv(void(*coroutine)(int argc, void *argv[]), int argc, void *argv[]); +int neco_yield(void); +int neco_sleep(int64_t nanosecs); +int neco_sleep_dl(int64_t deadline); +int neco_join(int64_t id); +int neco_join_dl(int64_t id, int64_t deadline); +int neco_suspend(void); +int neco_suspend_dl(int64_t deadline); +int neco_resume(int64_t id); +void neco_exit(void); +int64_t neco_getid(void); +int64_t neco_lastid(void); +int64_t neco_starterid(void); +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// channels +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Channels Channels +/// Channels allow for sending and receiving values between coroutines. +/// By default, sends and receives will block until the other side is ready. +/// This allows the coroutines to synchronize without using locks or condition +/// variables. +/// @{ +typedef struct neco_chan neco_chan; + +int neco_chan_make(neco_chan **chan, size_t data_size, size_t capacity); +int neco_chan_retain(neco_chan *chan); +int neco_chan_release(neco_chan *chan); +int neco_chan_send(neco_chan *chan, void *data); +int neco_chan_send_dl(neco_chan *chan, void *data, int64_t deadline); +int neco_chan_broadcast(neco_chan *chan, void *data); +int neco_chan_recv(neco_chan *chan, void *data); +int neco_chan_recv_dl(neco_chan *chan, void *data, int64_t deadline); +int neco_chan_tryrecv(neco_chan *chan, void *data); +int neco_chan_close(neco_chan *chan); +int neco_chan_select(int nchans, ...); +int neco_chan_select_dl(int64_t deadline, int nchans, ...); +int neco_chan_selectv(int nchans, neco_chan *chans[]); +int neco_chan_selectv_dl(int nchans, neco_chan *chans[], int64_t deadline); +int neco_chan_tryselect(int nchans, ...); +int neco_chan_tryselectv(int nchans, neco_chan *chans[]); +int neco_chan_case(neco_chan *chan, void *data); +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// generators +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Generators Generators +/// A generator is a specialized iterator-bound coroutine that can produce a +/// sequence of values to be iterated over. +/// @{ +typedef struct neco_gen neco_gen; + +int neco_gen_start(neco_gen **gen, size_t data_size, void(*coroutine)(int argc, void *argv[]), int argc, ...); +int neco_gen_startv(neco_gen **gen, size_t data_size, void(*coroutine)(int argc, void *argv[]), int argc, void *argv[]); +int neco_gen_retain(neco_gen *gen); +int neco_gen_release(neco_gen *gen); +int neco_gen_yield(void *data); +int neco_gen_yield_dl(void *data, int64_t deadline); +int neco_gen_next(neco_gen *gen, void *data); +int neco_gen_next_dl(neco_gen *gen, void *data, int64_t deadline); +int neco_gen_close(neco_gen *gen); +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// synchonization mechanisms +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Mutexes Mutexes +/// A mutex is synchronization mechanism that blocks access to variables by +/// multiple coroutines at once. This enforces exclusive access by a coroutine +/// to a variable or set of variables and helps to avoid data inconsistencies +/// due to race conditions. +/// @{ +typedef struct { char _[48]; } neco_mutex; + +#define NECO_MUTEX_INITIALIZER { 0 } + +int neco_mutex_init(neco_mutex *mutex); +int neco_mutex_lock(neco_mutex *mutex); +int neco_mutex_lock_dl(neco_mutex *mutex, int64_t deadline); +int neco_mutex_trylock(neco_mutex *mutex); +int neco_mutex_unlock(neco_mutex *mutex); +int neco_mutex_rdlock(neco_mutex *mutex); +int neco_mutex_rdlock_dl(neco_mutex *mutex, int64_t deadline); +int neco_mutex_tryrdlock(neco_mutex *mutex); +/// @} + +/// @defgroup WaitGroups WaitGroups +/// A WaitGroup waits for a multiple coroutines to finish. +/// The main coroutine calls neco_waitgroup_add() to set the number of +/// coroutines to wait for. Then each of the coroutines runs and calls +/// neco_waitgroup_done() when complete. +/// At the same time, neco_waitgroup_wait() can be used to block until all +/// coroutines are completed. +/// @{ +typedef struct { char _[48]; } neco_waitgroup; + +#define NECO_WAITGROUP_INITIALIZER { 0 } + +int neco_waitgroup_init(neco_waitgroup *waitgroup); +int neco_waitgroup_add(neco_waitgroup *waitgroup, int delta); +int neco_waitgroup_done(neco_waitgroup *waitgroup); +int neco_waitgroup_wait(neco_waitgroup *waitgroup); +int neco_waitgroup_wait_dl(neco_waitgroup *waitgroup, int64_t deadline); +/// @} + +/// @defgroup CondVar Condition variables +/// A condition variable is a synchronization mechanism that allows coroutines +/// to suspend execution until some condition is true. +/// @{ +typedef struct { char _[48]; } neco_cond; +#define NECO_COND_INITIALIZER { 0 } + +int neco_cond_init(neco_cond *cond); +int neco_cond_signal(neco_cond *cond); +int neco_cond_broadcast(neco_cond *cond); +int neco_cond_wait(neco_cond *cond, neco_mutex *mutex); +int neco_cond_wait_dl(neco_cond *cond, neco_mutex *mutex, int64_t deadline); +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// file descriptors +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Posix Posix wrappers +/// Functions that work like their Posix counterpart but do not block, allowing +/// for usage in a Neco coroutine. +/// @{ + +// wrappers for various posix operations. +ssize_t neco_read(int fd, void *data, size_t nbytes); +ssize_t neco_read_dl(int fd, void *data, size_t nbytes, int64_t deadline); +ssize_t neco_write(int fd, const void *data, size_t nbytes); +ssize_t neco_write_dl(int fd, const void *data, size_t nbytes, int64_t deadline); +int neco_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +int neco_accept_dl(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int64_t deadline); +int neco_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int neco_connect_dl(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int64_t deadline); +int neco_getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res); +int neco_getaddrinfo_dl(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res, int64_t deadline); +/// @} + +/// @defgroup Posix2 File descriptor helpers +/// Functions for working with file descriptors. +/// @{ + +// utility for enabling non-blocking on existing file descriptors +int neco_setnonblock(int fd, bool nonblock, bool *oldnonblock); + +// wait for a file descriptor to be readable or writeable. +#define NECO_WAIT_READ 1 +#define NECO_WAIT_WRITE 2 + +int neco_wait(int fd, int mode); +int neco_wait_dl(int fd, int mode, int64_t deadline); + +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// networking +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Networking Networking utilities +/// @{ + +int neco_serve(const char *network, const char *address); +int neco_serve_dl(const char *network, const char *address, int64_t deadline); +int neco_dial(const char *network, const char *address); +int neco_dial_dl(const char *network, const char *address, int64_t deadline); + +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// cancellation +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Cancelation Cancelation +/// @{ + +int neco_cancel(int64_t id); +int neco_cancel_dl(int64_t id, int64_t deadline); + +#define NECO_CANCEL_ASYNC 1 +#define NECO_CANCEL_INLINE 2 +#define NECO_CANCEL_ENABLE 3 +#define NECO_CANCEL_DISABLE 4 + +int neco_setcanceltype(int type, int *oldtype); +int neco_setcancelstate(int state, int *oldstate); + +#define neco_cleanup_push(routine, arg) {__neco_c0(&(char[32]){0},routine,arg); +#define neco_cleanup_pop(execute) __neco_c1(execute);} + +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// random number generator +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Random Random number generator +/// @{ + +#define NECO_CSPRNG 0 // Cryptographically secure pseudorandom number generator +#define NECO_PRNG 1 // Pseudorandom number generator (non-crypto, faster) + +int neco_rand_setseed(int64_t seed, int64_t *oldseed); +int neco_rand(void *data, size_t nbytes, int attr); +int neco_rand_dl(void *data, size_t nbytes, int attr, int64_t deadline); + +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// signal handling +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Signals Signals +/// Allows for signals, such as SIGINT (Ctrl-C), to be intercepted or ignored. +/// @{ + +int neco_signal_watch(int signo); +int neco_signal_wait(void); +int neco_signal_wait_dl(int64_t deadline); +int neco_signal_unwatch(int signo); + +/// @} + + +//////////////////////////////////////////////////////////////////////////////// +// background worker +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Worker Background worker +/// Run arbritary code in a background worker thread +/// @{ + +int neco_work(int64_t pin, void(*work)(void *udata), void *udata); + +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// Stats and information +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Stats Stats and information +/// @{ + +typedef struct neco_stats { + size_t coroutines; ///< Number of active coroutines + size_t sleepers; ///< Number of sleeping coroutines + size_t evwaiters; ///< Number of coroutines waiting on I/O events + size_t sigwaiters; ///< + size_t senders; ///< + size_t receivers; ///< + size_t locked; ///< + size_t waitgroupers; ///< + size_t condwaiters; ///< + size_t suspended; ///< + size_t workers; ///< Number of background worker threads +} neco_stats; + +int neco_getstats(neco_stats *stats); +int neco_is_main_thread(void); +const char *neco_switch_method(void); + +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// global behaviors +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup GlobalFuncs Global environment +/// @{ + +void neco_env_setallocator(void *(*malloc)(size_t), void *(*realloc)(void*, size_t), void (*free)(void*)); +void neco_env_setpaniconerror(bool paniconerror); +void neco_env_setcanceltype(int type); +void neco_env_setcancelstate(int state); + +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// time and duration +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Time Time +/// Functions for working with time. +/// +/// The following defines are available for convenience. +/// +/// ```c +/// #define NECO_NANOSECOND INT64_C(1) +/// #define NECO_MICROSECOND INT64_C(1000) +/// #define NECO_MILLISECOND INT64_C(1000000) +/// #define NECO_SECOND INT64_C(1000000000) +/// #define NECO_MINUTE INT64_C(60000000000) +/// #define NECO_HOUR INT64_C(3600000000000) +/// ``` +/// +/// @{ + +#define NECO_NANOSECOND INT64_C(1) +#define NECO_MICROSECOND INT64_C(1000) +#define NECO_MILLISECOND INT64_C(1000000) +#define NECO_SECOND INT64_C(1000000000) +#define NECO_MINUTE INT64_C(60000000000) +#define NECO_HOUR INT64_C(3600000000000) + +int64_t neco_now(void); + +///@} + +//////////////////////////////////////////////////////////////////////////////// +// errors +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup ErrorFuncs Error handling +/// Functions for working with [Neco errors](./API.md#neco-errors). +/// @{ + +#define NECO_OK 0 ///< Successful result (no error) +#define NECO_ERROR -1 ///< System error (check errno) +#define NECO_INVAL -2 ///< Invalid argument +#define NECO_PERM -3 ///< Operation not permitted +#define NECO_NOMEM -4 ///< Cannot allocate memory +#define NECO_EOF -5 ///< End of file or stream (neco_stream_*) +#define NECO_NOTFOUND -6 ///< No such coroutine (neco_cancel) +#define NECO_NOSIGWATCH -7 ///< Not watching on a signal +#define NECO_CLOSED -8 ///< Channel is closed +#define NECO_EMPTY -9 ///< Channel is empty (neco_chan_tryrecv) +#define NECO_TIMEDOUT -10 ///< Deadline has elapsed (neco_*_dl) +#define NECO_CANCELED -11 ///< Operation canceled (by neco_cancel) +#define NECO_BUSY -12 ///< Resource busy (mutex_trylock) +#define NECO_NEGWAITGRP -13 ///< Negative waitgroup counter +#define NECO_GAIERROR -14 ///< Error with getaddrinfo (check neco_gai_error) +#define NECO_UNREADFAIL -15 ///< Failed to unread byte (neco_stream_unread_byte) +#define NECO_PARTIALWRITE -16 ///< Failed to write all data (neco_stream_flush) +#define NECO_NOTGENERATOR -17 ///< Coroutine is not a generator (neco_gen_yield) +#define NECO_NOTSUSPENDED -18 ///< Coroutine is not suspended (neco_resume) + +const char *neco_strerror(ssize_t errcode); +int neco_lasterr(void); +int neco_gai_lasterr(void); +int neco_panic(const char *fmt, ...); + +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// streama +//////////////////////////////////////////////////////////////////////////////// + +/// @defgroup Streams Streams and Buffered I/O +/// Create a Neco stream from a file descriptor using neco_stream_make() or +/// a buffered stream using neco_stream_make_buffered(). +/// @{ + +typedef struct neco_stream neco_stream; + +int neco_stream_make(neco_stream **stream, int fd); +int neco_stream_make_buffered(neco_stream **stream, int fd); +int neco_stream_close(neco_stream *stream); +int neco_stream_close_dl(neco_stream *stream, int64_t deadline); +ssize_t neco_stream_read(neco_stream *stream, void *data, size_t nbytes); +ssize_t neco_stream_read_dl(neco_stream *stream, void *data, size_t nbytes, int64_t deadline); +ssize_t neco_stream_write(neco_stream *stream, const void *data, size_t nbytes); +ssize_t neco_stream_write_dl(neco_stream *stream, const void *data, size_t nbytes, int64_t deadline); +ssize_t neco_stream_readfull(neco_stream *stream, void *data, size_t nbytes); +ssize_t neco_stream_readfull_dl(neco_stream *stream, void *data, size_t nbytes, int64_t deadline); +int neco_stream_read_byte(neco_stream *stream); +int neco_stream_read_byte_dl(neco_stream *stream, int64_t deadline); +int neco_stream_unread_byte(neco_stream *stream); +int neco_stream_flush(neco_stream *stream); +int neco_stream_flush_dl(neco_stream *stream, int64_t deadline); +ssize_t neco_stream_buffered_read_size(neco_stream *stream); +ssize_t neco_stream_buffered_write_size(neco_stream *stream); + +/// @} + +//////////////////////////////////////////////////////////////////////////////// +// happy convenience macro +//////////////////////////////////////////////////////////////////////////////// + +// int neco_main(int argc, char *argv[]); + +#include +#include + +#define neco_main \ +static inline __neco_main0(int argc, char *argv[]); \ +static void _neco_main(int argc, void *argv[]) { \ + __neco_exit_prog(__neco_main0(*(int*)argv[0], *(char***)argv[1])); \ +} \ +int main(int argc, char *argv[]) { \ + neco_env_setpaniconerror(true); \ + neco_env_setcanceltype(NECO_CANCEL_ASYNC); \ + int ret = neco_start(_neco_main, 2, &argc, &argv); \ + fprintf(stderr, "neco_start: %s (code %d)\n", neco_strerror(ret), ret); \ + return -1; \ +}; \ +int static inline __neco_main0 + +//////////////////////////////////////////////////////////////////////////////// +// private functions, not to be call directly +//////////////////////////////////////////////////////////////////////////////// + +void __neco_c0(void*,void(*)(void*),void*); +void __neco_c1(int); +void __neco_exit_prog(int); + +//////////////////////////////////////////////////////////////////////////////// + +#ifndef EAI_SYSTEM +#define EAI_SYSTEM 11 +#endif + +#endif // NECO_H diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..8421771 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,26 @@ +# Testing + +Tests can be run from the project's root directory. + +```bash +tests/run.sh +``` + +This will run all tests using the system's default compiler. + +If [Clang](https://clang.llvm.org) is your compiler then you will also be +provided with memory address sanitizing and code coverage. + +If you need Valgrind you can provide `VALGRIND=1`. + +### Examples + +```bash +tests/run.sh # defaults +CC=clang-17 tests/run.sh # use alternative compiler +CC=emcc tests/run.sh # test WebAssembly using Emscripten +CC="zig cc" tests/run.sh # test with the Zig C compiler +CFLAGS="-O3" tests/run.sh # use custom cflags +NOSANS=1 tests/run.sh # do not use sanitizers +VALGRIND=1 tests/run.sh # use valgrind on all tests +``` diff --git a/tests/bench.c b/tests/bench.c new file mode 100644 index 0000000..b2b7e0b --- /dev/null +++ b/tests/bench.c @@ -0,0 +1,6 @@ +#include "tests.h" + +int main(int argc, char **argv) { + printf("No benchmarks available\n"); + return 0; +} \ No newline at end of file diff --git a/tests/fail_counters.h b/tests/fail_counters.h new file mode 100644 index 0000000..1c99a6d --- /dev/null +++ b/tests/fail_counters.h @@ -0,0 +1,112 @@ +#ifdef NECO_TESTING + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +#undef read0 +#undef recv0 +#undef write0 +#undef send0 +#undef accept0 +#undef connect0 +#undef socket0 +#undef bind0 +#undef listen0 +#undef setsockopt0 +#undef nanosleep0 +#undef fcntl0 +#undef evqueue0 +#undef pthread_create0 +#undef pthread_detach0 +#undef pipe0 +#undef malloc0 +#undef realloc0 +#undef stack_get0 + +#define FAIL_CALL(type, name0, name, ret, err, params, args) \ +__thread int neco_fail_ ## name ## _counter = 0; \ +__thread int neco_fail_ ## name ## _error = err; \ +static type name0 params { \ + type retout; \ + if (neco_fail_ ## name ## _counter == 1) { \ + errno = neco_fail_ ## name ## _error; \ + neco_fail_ ## name ## _error = err; \ + retout = ret; \ + } else { \ + retout = name args; \ + } \ + if (neco_fail_ ## name ## _counter > 0) { \ + neco_fail_ ## name ## _counter--; \ + } \ + return retout; \ +} +typedef void *voidp; +FAIL_CALL(ssize_t, recv0, recv, -1, EIO, + (int fd, void *data, size_t nbytes, int flags), + (fd, data, nbytes, flags)) +FAIL_CALL(ssize_t, read0, read, -1, EIO, + (int fd, void *data, size_t nbytes), + (fd, data, nbytes)) +FAIL_CALL(ssize_t, send0, send, -1, EIO, + (int fd, const void *data, size_t nbytes, int flags), + (fd, data, nbytes, flags)) +FAIL_CALL(ssize_t, write0, write, -1, EIO, + (int fd, const void *data, size_t nbytes), + (fd, data, nbytes)) +FAIL_CALL(int, accept0, accept, -1, EMFILE, + (int sockfd, struct sockaddr *addr, socklen_t *addrlen), + (sockfd, addr, addrlen)) +FAIL_CALL(int, connect0, connect, -1, ECONNREFUSED, + (int sockfd, const struct sockaddr *addr, socklen_t addrlen), + (sockfd, addr, addrlen)) +FAIL_CALL(int, socket0, socket, -1, EMFILE, + (int domain, int type, int protocol), + (domain, type, protocol)) +FAIL_CALL(int, bind0, bind, -1, EADDRINUSE, + (int sockfd, const struct sockaddr *addr, socklen_t addrlen), + (sockfd, addr, addrlen)) +FAIL_CALL(int, listen0, listen, -1, EADDRINUSE, + (int sockfd, int backlog), + (sockfd, backlog)) +FAIL_CALL(int, setsockopt0, setsockopt, -1, EBADF, + (int sockfd, int level, int optname, const void *optval, socklen_t optlen), + (sockfd, level, optname, optval, optlen)) +FAIL_CALL(int, nanosleep0, nanosleep, -1, EINTR, + (const struct timespec *req, struct timespec *rem), + (req, rem)) +FAIL_CALL(int, evqueue0, evqueue, -1, EBADF, + (void), + ()) +FAIL_CALL(int, pthread_create0, pthread_create, EPERM, EPERM, + (pthread_t *thread, const pthread_attr_t *attr, + void *(*start_routine)(void *arg), void *arg), + (thread, attr, start_routine, arg)) +FAIL_CALL(int, pthread_detach0, pthread_detach, EINVAL, EINVAL, + (pthread_t thread), + (thread)) +FAIL_CALL(voidp, malloc0, neco_malloc, NULL, ENOMEM, + (size_t nbytes), + (nbytes)) +FAIL_CALL(voidp, realloc0, neco_realloc, NULL, ENOMEM, + (void *ptr, size_t nbytes), + (ptr, nbytes)) +FAIL_CALL(int, stack_get0, stack_get, -1, ENOMEM, + (struct stack_mgr *mgr, struct stack *stack), + (mgr, stack)) + +#ifndef _WIN32 +FAIL_CALL(int, fcntl0, fcntl, -1, EBADF, + (int fd, int cmd, int arg), + (fd, cmd, arg)) +FAIL_CALL(int, pipe0, pipe, -1, EMFILE, + (int fds[2]), + (fds)) +#endif + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif diff --git a/tests/panic.h b/tests/panic.h new file mode 100644 index 0000000..5a525cf --- /dev/null +++ b/tests/panic.h @@ -0,0 +1,29 @@ +#ifdef NECO_TESTING + +__thread bool neco_last_panic = false; +#define panic(fmt, ...) \ + neco_last_panic = true; + +// print a string, function, file, and line number. +#define pwhere(str) \ + fprintf(stderr, "%s, function %s, file %s, line %d.\n", \ + (str), __func__, __FILE__, __LINE__) + +// define an unreachable section of code. +// This will not signal like __builtin_unreachable() +#define unreachable() \ + pwhere("Unreachable"); \ + abort() + +// must is like assert but it cannot be disabled with -DNDEBUG +// This is primarily used for a syscall that, when provided valid paramaters, +// should never fail. Yet if it does fail we want the 411. +#define must(cond) \ + if (!(cond)) { \ + pwhere("Must failed: " #cond); \ + perror("System error:"); \ + abort(); \ + } \ + (void)0 + +#endif diff --git a/tests/priv_funcs.h b/tests/priv_funcs.h new file mode 100644 index 0000000..6f1ebbc --- /dev/null +++ b/tests/priv_funcs.h @@ -0,0 +1,44 @@ +#ifdef NECO_TESTING + +void test_units_xx3(void) { + char data[] = "9eee31a1e38aec186198c742099ca26ade152235a522022a"; + uint64_t exp [] = { + 0xef46db3751d8e999, 0x3073eed68c0560e3, 0xc30c0e04a049d500, + 0x65684ed7172a9aab, 0x5d70724192470c5b, 0x5fba524e0a69edaa, + 0xf71c1a44887e43e1, 0xd2e66ac2e37498c3, 0x1a886c36a17db3f5, + 0x5784037bdd6ebcf8, 0xb9ef4fbef461e0ed, 0xe3069db7083b35d9, + 0x444015ec7d677317, 0x175cbcbac99b956d, 0xee241a9316896abc, + 0x97871dea5dfa15d5, 0xf766d4c3cf79dcf0, 0xbc84ae6c23db3bab, + 0xc534607cce3e89b2, 0x251097654bb75473, 0xdeb675823fc57e68, + 0xf706d921b1ef8077, 0xe167a86e0d5b2488, 0x597f1ce8bc96d5d6, + 0x1d9318a34a79339a, 0xa5493c035962815f, 0x6e0e55435b1d597e, + 0x2c375597755fb161, 0x31f688cc8cc53c7e, 0xc61af81f53d19a66, + 0x67e315810fe0f864, 0x433e6791508ecd01, 0x265bb8168c9cf315, + 0x0ef768f5bd9bdb81, 0xc1fa6b650b855ad5, 0xd45efccc80c8f911, + 0x704547f49c38ef28, 0x052b89cd32eb6959, 0xd6377bd64d0877dd, + 0x35649174351f9002, 0x32e423a14079407c, 0x6d9f7ae7b33e4e75, + 0x4b1312a97c446756, 0x55e56172079276d7, 0xdc5c69cc17b01ffa, + 0x6b62e86a3d457048, 0xedf90932412081b0, 0x24bd1679a4c82fe7, + }; + for (size_t i = 0; i < strlen(data); i++) { + assert(xxh3(data, i, i) == exp[i]); + } +} + +void test_units_align(void) { + for (int i = 0; i <= 16; i++) { + assert(align_size(i, 16) == 16); + } + for (int i = 17; i <= 32; i++) { + assert(align_size(i, 16) == 32); + } +} + +void test_units_mmap(void) { + void *mem = mmap_alloc(1); + assert(mem); + mmap_free(mem, 1); + assert(mmap_alloc(__SIZE_MAX__) == NULL); +} + +#endif // NECO_TEST_PRIVATE_FUNCTIONS diff --git a/tests/run.sh b/tests/run.sh new file mode 100755 index 0000000..f80f996 --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,245 @@ +#!/usr/bin/env bash + +# ./run.sh [] + +set -e +cd $(dirname "${BASH_SOURCE[0]}") + +OK=0 +finish() { + rm -fr *.o + rm -fr *.out + rm -fr *.test + rm -fr *.profraw + rm -fr *.dSYM + rm -fr *.profdata + rm -fr *.c.worker.js + rm -fr *.c.wasm + if [[ "$OK" != "1" ]]; then + echo "FAIL" + fi +} +trap finish EXIT + +echo_wrapped() { + # print an arguments list with line wrapping + line="" + for i in $(seq 1 "$#"); do + line2="$line ${!i}" + if [[ "${#line2}" -gt 70 ]]; then + echo "$line \\" + line=" " + fi + line="$line${!i} " + done + echo "$line" +} + +if [[ -f /proc/cpuinfo ]]; then + cpu="`cat /proc/cpuinfo | grep "model name" | uniq | cut -f3- -d ' ' | xargs`" +elif [[ "`which system_profiler`" != "" ]]; then + cpu="`system_profiler SPHardwareDataType | grep Chip | uniq | cut -f2- -d ':' | xargs`" +fi +if [[ "$CC" == "" ]]; then + CC=cc +fi +if [[ "$1" != "bench" ]]; then + CFLAGS="-O0 -g3 -Wall -Wextra -fstrict-aliasing $CFLAGS" + CCVERSHEAD="$($CC --version | head -n 1)" + if [[ "$CCVERSHEAD" == "" ]]; then + exit 1 + fi + + if [[ "$CCVERSHEAD" == *"clang"* ]]; then + CLANGVERS="$(echo "$CCVERSHEAD" | awk '{print $4}' | awk -F'[ .]+' '{print $1}')" + fi + + if [[ "$CC" == *"zig"* ]]; then + # echo Zig does not support asans + NOSANS=1 + fi + + # Use address sanitizer if possible + if [[ "$NOSANS" != "1" && "$CLANGVERS" -gt "13" ]]; then + CFLAGS="$CFLAGS -fno-omit-frame-pointer" + CFLAGS="$CFLAGS -fprofile-instr-generate" + CFLAGS="$CFLAGS -fcoverage-mapping" + CFLAGS="$CFLAGS -fsanitize=address" + CFLAGS="$CFLAGS -fsanitize=undefined" + if [[ "$1" == "fuzz" ]]; then + CFLAGS="$CFLAGS -fsanitize=fuzzer" + fi + CFLAGS="$CFLAGS -fno-inline" + CFLAGS="$CFLAGS -pedantic" + WITHSANS=1 + + if [[ "$(which llvm-cov-$CLANGVERS)" != "" && "$(which llvm-profdata-$CLANGVERS)" != "" ]]; then + LLVM_COV="llvm-cov-$CLANGVERS" + LLVM_PROFDATA="llvm-profdata-$CLANGVERS" + else + LLVM_COV="llvm-cov" + LLVM_PROFDATA="llvm-profdata" + fi + + if [[ "$(which $LLVM_PROFDATA)" != "" && "$(which $LLVM_COV)" != "" ]]; then + COV_VERS="$($LLVM_COV --version | awk '{print $4}' | awk -F'[ .]+' '{print $1}')" + if [[ "$COV_VERS" -gt "15" ]]; then + WITHCOV=1 + fi + fi + fi + CFLAGS=${CFLAGS:-"-O0 -g3 -Wall -Wextra -fstrict-aliasing"} +else + CFLAGS=${CFLAGS:-"-O3"} +fi +# CFLAGS="$CFLAGS -pthread" +CFLAGS="$CFLAGS -DNECO_TESTING -DNECO_BT_SOURCE_INFO" +if [[ "$VALGRIND" == "1" ]]; then + CFLAGS="$CFLAGS -DLLCO_VALGRIND" +fi +if [[ "$CC" == "emcc" ]]; then + # Running emscripten + CFLAGS="$CFLAGS -sASYNCIFY -sALLOW_MEMORY_GROWTH" + CFLAGS="$CFLAGS -Wno-limited-postlink-optimizations" + CFLAGS="$CFLAGS -Wno-unused-command-line-argument" + CFLAGS="$CFLAGS -Wno-pthreads-mem-growth" +elif [[ "`uname`" == *"_NT-"* ]]; then + # Running on Windows (MSYS) + LFLAGS2="$LFLAGS2 -lws2_32" +fi + +if [[ "$CC" == *"zig"* ]]; then + # Without -O3, 'zig cc' has quirks issues on Mac OS. + CFLAGS="$CFLAGS -O3" + # Stack unwinding is not supported yet + # https://github.com/ziglang/zig/issues/9046 + CFLAGS="$CFLAGS -DLLCO_NOUNWIND" +fi + + + +CC=${CC:-cc} +echo "CC: $CC" +echo "CFLAGS: $CFLAGS" +echo "OS: `uname`" +echo "CPU: $cpu" + +which "$CC" | true +cc2="$(readlink -f "`which "$CC" | true`" | true)" +if [[ "$cc2" == "" ]]; then + cc2="$CC" +fi +echo Compiler: $($cc2 --version | head -n 1) +if [[ "$NOSANS" == "1" ]]; then + echo "Sanitizers disabled" +fi +echo "Neco Commit: `git rev-parse --short HEAD 2>&1 || true`" + +if [[ "$1" == "bench" ]]; then + echo "BENCHMARKING..." + if [[ "$MARKDOWN" == "1" ]]; then + echo_wrapped $CC $CFLAGS ../neco.c bench.c + else + echo $CC $CFLAGS ../neco.c bench.c + fi + $CC $CFLAGS ../neco.c bench.c + ./a.out $@ + OK=1 +elif [[ "$1" == "fuzz" ]]; then + echo "FUZZING..." + echo $CC $CFLAGS ../neco.c fuzz.c + $CC $CFLAGS ../neco.c fuzz.c + MallocNanoZone=0 ./a.out corpus/ seeds/ # -jobs=8 # "${@:2}" +else + if [[ "$WITHCOV" == "1" ]]; then + echo "Code coverage: on" + else + echo "Code coverage: off" + fi + echo "For benchmarks: 'run.sh bench'" + echo "TESTING..." + DEPS_SRCS="../deps/sco.c ../deps/stack.c ../deps/worker.c" + DEPS_OBJS="sco.o stack.o worker.o" + rm -f neco.o $DEPS_OBJS + for f in *; do + if [[ "$f" != test_*.c ]]; then continue; fi + if [[ "$1" == test_* ]]; then + p=$1 + if [[ "$1" == test_*_* ]]; then + # fast track matching prefix with two underscores + suf=${1:5} + rest=${suf#*_} + idx=$((${#suf}-${#rest}-${#_}+4)) + p=${1:0:idx} + fi + if [[ "$f" != $p* ]]; then continue; fi + fi + + # Compile dependencies and neco.o + if [[ ! -f "neco.o" ]]; then + # Compile each dependency individually + DEPS_SRCS_ARR=($DEPS_SRCS) + for file in "${DEPS_SRCS_ARR[@]}"; do + $CC $CFLAGS -Wunused-function -c $file + done + if [[ "$AMALGA" == "1" ]]; then + echo "AMALGA=1" + $CC $CFLAGS -c ../neco.c + else + $CC $CFLAGS -DNECO_NOAMALGA -c ../neco.c + fi + fi + + # Compile the test + if [[ "$AMALGA" == "1" ]]; then + $CC $CFLAGS -o $f.test neco.o $LFLAGS2 $f -lm + else + $CC $CFLAGS -DNECO_NOAMALGA -o $f.test neco.o $DEPS_OBJS $LFLAGS2 $f -lm + fi + + # Run the program + if [[ "$WITHSANS" == "1" ]]; then + export MallocNanoZone=0 + export ASAN_OPTIONS=detect_stack_use_after_return=1 + fi + if [[ "$WITHCOV" == "1" ]]; then + export LLVM_PROFILE_FILE="$f.profraw" + fi + if [[ "$VALGRIND" == "1" ]]; then + valgrind --leak-check=yes ./$f.test $@ + elif [[ "$CC" == "emcc" ]]; then + node ./$f.test $@ + else + ./$f.test $@ + fi + + done + OK=1 + echo "OK" + + if [[ "$COVREGIONS" == "" ]]; then + COVREGIONS="false" + fi + + if [[ "$WITHCOV" == "1" ]]; then + $LLVM_PROFDATA merge *.profraw -o test.profdata + $LLVM_COV report *.test ../neco.c -ignore-filename-regex=.test. \ + -j=4 \ + -show-functions=true \ + -instr-profile=test.profdata > /tmp/test.cov.sum.txt + # echo coverage: $(cat /tmp/test.cov.sum.txt | grep TOTAL | awk '{ print $NF }') + echo covered: "$(cat /tmp/test.cov.sum.txt | grep TOTAL | awk '{ print $7; }') (lines)" + $LLVM_COV show *.test ../neco.c -ignore-filename-regex=.test. \ + -j=4 \ + -show-regions=true \ + -show-expansions=$COVREGIONS \ + -show-line-counts-or-regions=true \ + -instr-profile=test.profdata -format=html > /tmp/test.cov.html + echo "details: file:///tmp/test.cov.html" + echo "summary: file:///tmp/test.cov.sum.txt" + elif [[ "$WITHCOV" == "0" ]]; then + echo "code coverage not a available" + echo "install llvm-profdata and use clang for coverage" + fi + +fi diff --git a/tests/signals.h b/tests/signals.h new file mode 100644 index 0000000..9386d20 --- /dev/null +++ b/tests/signals.h @@ -0,0 +1,17 @@ +#ifdef NECO_TESTING + +#define print_stacktrace(a, b) + +// During testing, track the exit but do not actually exit. +__thread int neco_last_sigexitnow = 0; +static void sigexitnow(int signo) { + neco_last_sigexitnow = signo; + if (signo == SIGINT || signo == SIGABRT || signo == SIGSEGV || + signo == SIGBUS) + { + fprintf(stderr, "%s\n", signo == SIGINT ? "" : strsignal0(signo)); + _Exit(128+signo); + } +} + +#endif diff --git a/tests/test_basic.c b/tests/test_basic.c new file mode 100644 index 0000000..fd8d28f --- /dev/null +++ b/tests/test_basic.c @@ -0,0 +1,221 @@ +#include +#include "tests.h" + +static int64_t lastid = 0; + +void co_basic_startv(int argc, void *argv[]) { + assert(argc == 1); + lastid = neco_getid(); + int x = *(int*)argv[0]; + assert(x == 1977); +} + +void co_basic_start(int argc, void *argv[]) { + assert(argc == 1); + int *x = argv[0]; + *x = 1977; + +#ifdef IS_FAIL_TARGET + neco_fail_neco_malloc_counter = 1; + expect(neco_start(co_basic_start, 0), NECO_NOMEM); + neco_fail_stack_get_counter = 1; + expect(neco_start(co_basic_start, 0), NECO_NOMEM); + // This will trigger a NOMEM on number of arguments over 4 + neco_fail_neco_malloc_counter = 2; + expect(neco_start(co_basic_start, 5, 0, 0, 0, 0, 0), NECO_NOMEM); +#endif + + expect(neco_startv(co_basic_startv, 1, &(void*){x}), NECO_OK); + assert(neco_lastid() == lastid); + expect(neco_startv(co_basic_startv, 1, &(void*){x}), NECO_OK); + assert(neco_lastid() == lastid); + expect(neco_startv(co_basic_startv, 1, &(void*){x}), NECO_OK); + assert(neco_lastid() == lastid); +} + +void test_basic_start(void) { + int x = 0; + expect(neco_start(co_basic_start, 1, &x), NECO_OK); + assert(x == 1977); + + // These will fail +#ifdef IS_FAIL_TARGET + for (int i = 1; i <= 500; i++) { + neco_fail_neco_malloc_counter = i; + int ret = neco_start(co_basic_start, 1, &x); + if (ret == NECO_OK) { + break; + } + if (ret != NECO_NOMEM) { + fprintf(stderr, "expected NECO_NOMEM, got %s (iter %d)\n", + neco_strerror(ret), i); + assert(0); + } + } + + // neco_fail_neco_malloc_counter = 1; + // expect(neco_start(co_basic_start, 0), NECO_NOMEM); + // neco_fail_neco_malloc_counter = 2; + // expect(neco_start(co_basic_start, 0), NECO_NOMEM); + // neco_fail_neco_malloc_counter = 3; + // expect(neco_start(co_basic_start, 0), NECO_NOMEM); + // neco_fail_neco_malloc_counter = 4; + // expect(neco_start(co_basic_start, 0), NECO_NOMEM); + // neco_fail_neco_malloc_counter = 5; + // expect(neco_start(co_basic_start, 0), NECO_NOMEM); + // neco_fail_neco_malloc_counter = 6; + // expect(neco_start(co_basic_start, 0), NECO_NOMEM); + // neco_fail_neco_malloc_counter = 7; + // expect(neco_start(co_basic_start, 0), NECO_NOMEM); +#endif + + expect(neco_start(co_basic_start, -1), NECO_INVAL); + expect(neco_lastid(), NECO_PERM); + + +} + +void co_basic_stats(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + neco_stats stats; + expect(neco_getstats(&stats), NECO_OK); + assert(stats.coroutines == 1); +} + +void test_basic_stats(void) { + neco_stats stats; + expect(neco_getstats(&stats), NECO_PERM); + expect(neco_getstats(0), NECO_INVAL); + assert(neco_start(co_basic_stats, 0) == NECO_OK); +} + +void co_sched1(int argc, void *argv[]) { + assert(argc == 2); + char *a = argv[0]; + int *i = argv[1]; + a[(*i)++] = 'B'; + assert(neco_yield() == NECO_OK); + a[(*i)++] = 'D'; +} + +void co_sched2(int argc, void *argv[]) { + assert(argc == 2); + char *a = argv[0]; + int *i = argv[1]; + a[(*i)++] = 'E'; + assert(neco_yield() == NECO_OK); + a[(*i)++] = 'G'; +} + +void co_sched(int argc, void *argv[]) { + assert(argc == 2); + char *a = argv[0]; + int *i = argv[1]; + a[(*i)++] = 'A'; + assert(neco_start(co_sched1, 2, a, i) == NECO_OK); + a[(*i)++] = 'C'; + assert(neco_start(co_sched2, 2, a, i) == NECO_OK); + a[(*i)++] = 'F'; + assert(neco_yield() == NECO_OK); + a[(*i)++] = 'H'; +} + +void test_basic_sched(void) { + expect(neco_yield(), NECO_PERM); + + // Tests the scheduling order. + char a[10]; + int i = 0; + assert(neco_start(co_sched, 2, a, &i) == NECO_OK); + a[i++] = '\0'; + char exp[] = "ABCDEFGH"; + if (strcmp(a, exp) != 0) { + printf("expected '%s' got '%s'\n", exp, a); + abort(); + } +} + +void co_sleep(int argc, void *argv[]) { + (void)argc; (void)argv; + expect(neco_sleep(-1), NECO_TIMEDOUT); + expect(neco_cancel(neco_getid()), NECO_OK); + expect(neco_sleep(INT64_MAX), NECO_CANCELED); + expect(neco_sleep(INT64_MIN), NECO_TIMEDOUT); +} + +void co_sleep_rand_child(int argc, void *argv[]) { + assert(argc == 1); + int64_t dl = *(int64_t*)argv[0]; + int nsecs = (int64_t)(rand_double() * (double)(NECO_SECOND/10)); + expect(neco_sleep(nsecs), NECO_OK); + expect(neco_sleep_dl(dl), NECO_OK); +} + +void co_sleep_rand(int argc, void *argv[]) { + (void)argc; (void)argv; + int N = 100; + int64_t dl = neco_now() + NECO_SECOND/2; + for (int i = 0; i < N; i++) { + expect(neco_start(co_sleep_rand_child, 1, &dl), NECO_OK); + } +} + +void test_basic_sleep(void) { + expect(neco_sleep(0), NECO_PERM); + expect(neco_start(co_sleep, 0), NECO_OK); + expect(neco_start(co_sleep_rand, 0), NECO_OK); +} + +void test_basic_misc(void) { + assert(neco_starterid() == NECO_PERM); + expect(neco_is_main_thread(), NECO_PERM); + expect(neco_now(), NECO_PERM); + assert(strlen(neco_switch_method()) > 0); +} + +static int val = 0; + +void rt1(void *arg) { + (*(int*)arg)++; +} + +void co_exit(int argc, void *argv[]) { + (void)argc; (void)argv; + val++; + neco_cleanup_push(rt1, &val); + neco_exit(); + neco_cleanup_pop(1); + val++; +} + +void test_basic_exit(void) { + neco_exit(); // Noop + val = 0; + expect(neco_start(co_exit, 0), NECO_OK); + assert(val == 2); +} + +void test_basic_malloc(void) { + void *ptr = neco_malloc(100); + assert(ptr); + ptr = neco_realloc(ptr, 200); + assert(ptr); + neco_free(ptr); +} + +int main(int argc, char **argv) { + do_test(test_basic_start); + do_test(test_basic_stats); + do_test(test_basic_sched); + do_test(test_basic_sleep); + do_test(test_basic_exit); + do_test_(test_basic_malloc, false); + do_test(test_basic_misc); + + // The next lines test that neco_free and exit_prog operations can be + // reached from outside of a neco context + neco_free(neco_malloc(100)); + __neco_exit_prog(0); + +} diff --git a/tests/test_cancel.c b/tests/test_cancel.c new file mode 100644 index 0000000..fddc263 --- /dev/null +++ b/tests/test_cancel.c @@ -0,0 +1,109 @@ +#include "tests.h" + +static int total = 0; + +void cl0(void *arg) { + total += (size_t)arg; +} + +void co_basic_cleanup(int argc, void *argv[]) { + (void)argc; (void)argv; + neco_cleanup_push(cl0, (void*)1); + neco_cleanup_push(cl0, (void*)2); + neco_cleanup_push(cl0, (void*)4); + neco_cleanup_push(cl0, (void*)8); + // ... + neco_cleanup_pop(1); + neco_cleanup_pop(1); + neco_cleanup_pop(0); + neco_cleanup_pop(1); +} + +void test_cancel_cleanup(void) { + expect(neco_start(co_basic_cleanup, 0), NECO_OK); + assert(total == 13); +} + +void co_cancel(int argc, void *argv[]) { + assert(argc == 1); + bool sleep = *(bool*)argv[0]; + int oldtype; + expect(neco_setcanceltype(NECO_CANCEL_ASYNC, &oldtype), NECO_OK); + assert(oldtype == NECO_CANCEL_INLINE); + neco_cleanup_push(cl0, (void*)1); + neco_cleanup_push(cl0, (void*)2); + neco_cleanup_push(cl0, (void*)4); + neco_cleanup_push(cl0, (void*)8); + if (sleep) { + expect(neco_sleep(NECO_SECOND/10), NECO_OK); + } else { + expect(neco_yield(), NECO_OK); + } + // + neco_cleanup_pop(1); + neco_cleanup_pop(1); + neco_cleanup_pop(0); + neco_cleanup_pop(1); +} + +void co_cancel_starter(int argc, void *argv[]) { + (void)argc; (void)argv; + total = 0; + expect(neco_start(co_cancel, 1, argv[0]), NECO_OK); + expect(neco_cancel(neco_lastid()), NECO_OK); +} + +void test_cancel_early(void) { + expect(neco_start(co_cancel_starter, 1, &(bool){true}), NECO_OK); + expect(neco_start(co_cancel_starter, 1, &(bool){false}), NECO_OK); + assert(total == 15); +} + +void test_cancel_errors(void) { + expect(neco_cancel(-1), NECO_PERM); + expect(neco_setcanceltype(-1, 0), NECO_INVAL); + expect(neco_setcanceltype(NECO_CANCEL_ASYNC, 0), NECO_PERM); + expect(neco_setcanceltype(NECO_CANCEL_INLINE, 0), NECO_PERM); + + expect(neco_setcancelstate(-1, 0), NECO_INVAL); + expect(neco_setcancelstate(NECO_CANCEL_ENABLE, 0), NECO_PERM); + expect(neco_setcancelstate(NECO_CANCEL_DISABLE, 0), NECO_PERM); +} + +void co_cancel_block_child(int argc, void *argv[]) { + (void)argc; (void)argv; + expect(neco_setcancelstate(NECO_CANCEL_DISABLE, 0), NECO_OK); + neco_sleep(NECO_SECOND/4); + expect(neco_setcancelstate(NECO_CANCEL_ENABLE, 0), NECO_OK); +} + +void co_cancel_block(int argc, void *argv[]) { + (void)argc; (void)argv; + expect(neco_start(co_cancel_block_child, 0), NECO_OK); + int64_t start = neco_now(); + expect(neco_cancel(neco_lastid()), NECO_NOTFOUND); + assert(neco_now() - start >= NECO_SECOND/4); + + expect(neco_start(co_cancel_block_child, 0), NECO_OK); + start = neco_now(); + expect(neco_cancel_dl(neco_lastid(), 0), NECO_TIMEDOUT); + assert(neco_now() - start < NECO_SECOND/4); + + expect(neco_start(co_cancel_block_child, 0), NECO_OK); + start = neco_now(); + expect(neco_cancel(neco_getid()), NECO_OK); + expect(neco_cancel_dl(neco_lastid(), 0), NECO_CANCELED); + assert(neco_now() - start < NECO_SECOND/4); + +} + +void test_cancel_block(void) { + expect(neco_start(co_cancel_block, 0), NECO_OK); +} + +int main(int argc, char **argv) { + do_test(test_cancel_cleanup); + do_test(test_cancel_early); + do_test(test_cancel_errors); + do_test(test_cancel_block); +} diff --git a/tests/test_chan.c b/tests/test_chan.c new file mode 100644 index 0000000..35dfb66 --- /dev/null +++ b/tests/test_chan.c @@ -0,0 +1,511 @@ +#include +#include +#include "tests.h" + +void co_chan_order_send(int argc, void *argv[]) { + assert(argc == 2); + struct neco_chan *ch = argv[0]; + int i = (int)((intptr_t)argv[1]); + assert(neco_chan_send(ch, &i) == NECO_OK); +} + +void co_chan_order(int argc, void *argv[]) { + assert(argc == 2); + int i = *(int*)argv[0]; + int j = *(int*)argv[1]; + struct neco_chan *ch; + assert(neco_chan_make(&ch, sizeof(int), j) == NECO_OK); + for (int k = 0; k < i; k++) { + assert(neco_start(co_chan_order_send, 2, ch, k+1) == NECO_OK); + } + int x; + for (int k = 0; k < i; k++) { + assert(neco_chan_recv(ch, &x) == NECO_OK); + assert(x == k+1); + } + assert(neco_chan_release(ch) == NECO_OK); +} + +void test_chan_order(void) { + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + assert(neco_start(co_chan_order, 2, &i, &j) == NECO_OK); + } + } +} + +void co_chan_select_sender(int argc, void *argv[]) { + assert(argc == 5); + int i = (int)(intptr_t)argv[0]; + int x = (int)(intptr_t)argv[1]; + int N = (int)(intptr_t)argv[2]; + int *incr = argv[3]; + struct neco_chan *ch = argv[4]; + for (int j = 0; j < N; j++) { + x += incr[j]; + assert(neco_chan_send(ch, &x) == NECO_OK); + if ((i+j)%3 == 0) { + neco_sleep(1e9/10); + } + } +} + +void co_chan_select(int argc, void *argv[]) { + (void)argc; (void)argv; + int incr[] = { 8987, 8701, 1923, 4451, 8192, 4421, 1010, 3304, 6104 }; + int N = (int)(sizeof(incr)/sizeof(int)); + int M = 3; + struct neco_chan *ch0, *ch1, *ch2; + assert(neco_chan_make(&ch0, sizeof(int), 0) == NECO_OK); + assert(neco_chan_make(&ch1, sizeof(int), 0) == NECO_OK); + assert(neco_chan_make(&ch2, sizeof(int), 0) == NECO_OK); + assert(neco_start(co_chan_select_sender, 5, 0, 10000, N, incr, ch0) == NECO_OK); + assert(neco_start(co_chan_select_sender, 5, 1, 20000, N, incr, ch1) == NECO_OK); + assert(neco_start(co_chan_select_sender, 5, 2, 30000, N, incr, ch2) == NECO_OK); + int idx0 = 0; + int idx1 = 0; + int idx2 = 0; + int exp0 = 10000; + int exp1 = 20000; + int exp2 = 30000; + int data0, data1, data2; + bool ok0, ok1, ok2; + + for (int i = 0; i < N * M; i++) { + int idx = neco_chan_select(3, ch0, ch1, ch2); + switch (idx) { + case 0: + ok0 = neco_chan_case(ch0, &data0) == NECO_OK; + exp0 += incr[idx0++]; + assert(ok0 && data0 == exp0); + break; + case 1: + ok1 = neco_chan_case(ch1, &data1) == NECO_OK; + exp1 += incr[idx1++]; + assert(ok1 && data1 == exp1); + break; + case 2: + ok2 = neco_chan_case(ch2, &data2) == NECO_OK; + exp2 += incr[idx2++]; + assert(ok2 && data2 == exp2); + break; + } + } + assert(neco_chan_release(ch0) == NECO_OK); + assert(neco_chan_release(ch1) == NECO_OK); + assert(neco_chan_release(ch2) == NECO_OK); +} + +void co_chan_select_big_child(int argc, void *argv[]) { + assert(argc == 1); + struct neco_chan *ch = argv[0]; + expect(neco_chan_send(ch, &(int){8876}), NECO_OK); + expect(neco_chan_release(ch), NECO_OK); +} + +void co_chan_select_big_child_cancel(int argc, void *argv[]) { + assert(argc == 1); + int64_t id = *(int64_t*)argv[0]; + expect(neco_sleep(NECO_SECOND/4), NECO_OK); + expect(neco_cancel(id), NECO_OK); +} + + +void co_chan_select_big(int argc, void *argv[]) { + (void)argc; (void)argv; + struct neco_chan *chs[20]; + for (int i = 0; i < 20; i++) { + expect(neco_chan_make(&chs[i], sizeof(int), 0), NECO_OK); + } + + neco_fail_neco_malloc_counter = 1; + expect(neco_chan_selectv_dl(20, chs, neco_now()+NECO_SECOND/4), NECO_NOMEM); + expect(neco_chan_selectv_dl(20, chs, neco_now()+NECO_SECOND/4), NECO_TIMEDOUT); + + expect(neco_chan_retain(chs[12]), NECO_OK); + expect(neco_start(co_chan_select_big_child, 1, chs[12]), NECO_OK); + + assert(neco_chan_selectv(20, chs) == 12); + int x; + expect(neco_chan_case(chs[12], &x), NECO_OK); + assert(x == 8876); + + int64_t id = neco_getid(); + expect(neco_cancel(id), NECO_OK); + expect(neco_chan_selectv(20, chs), NECO_CANCELED); + + expect(neco_start(co_chan_select_big_child_cancel, 1, &id), NECO_OK); + expect(neco_chan_selectv(20, chs), NECO_CANCELED); + + for (int i = 0; i < 20; i++) { + expect(neco_chan_release(chs[i]), NECO_OK); + } +} + +void co_chan_select_fail_perm_0(int argc, void *argv[]) { + assert(argc == 1); + neco_chan **ch1 = argv[0]; + expect(neco_chan_make(ch1, 0, 0), NECO_OK); + expect(neco_sleep(NECO_SECOND/2), NECO_OK); + expect(neco_chan_release(*ch1), NECO_OK); +} + +void co_chan_select_fail_perm_1(int argc, void *argv[]) { + assert(argc == 1); + neco_chan **ch1 = argv[0]; + expect(neco_sleep(NECO_SECOND/4), NECO_OK); + expect(neco_chan_select(1, *ch1), NECO_PERM); + expect(neco_chan_select(1, NULL), NECO_INVAL); + return; +} + +void *th_chan_select_fail_perm_0(void *arg) { + assert(neco_start(co_chan_select_fail_perm_0, 1, arg) == NECO_OK); + return 0; +} + +void *th_chan_select_fail_perm_1(void *arg) { + assert(neco_start(co_chan_select_fail_perm_1, 1, arg) == NECO_OK); + return 0; +} + +void test_chan_select(void) { + assert(neco_start(co_chan_select, 0) == NECO_OK); + assert(neco_start(co_chan_select_big, 0) == NECO_OK); +#ifdef __EMSCRIPTEN__ + return; +#endif + neco_chan *ch1; + pthread_t th1, th2; + assert(pthread_create(&th1, 0, th_chan_select_fail_perm_0, &ch1) == 0); + assert(pthread_create(&th2, 0, th_chan_select_fail_perm_1, &ch1) == 0); + assert(pthread_join(th1, 0) == 0); + assert(pthread_join(th2, 0) == 0); + +} + +void co_chan_close_sender(int argc, void *argv[]) { + assert(argc == 1); + struct neco_chan *ch = argv[0]; + assert(neco_chan_send(ch, 0) == NECO_OK); + assert(neco_chan_close(ch) == NECO_OK); + assert(neco_chan_close(ch) == NECO_CLOSED); + assert(neco_chan_send(ch, 0) == NECO_CLOSED); +} + +void co_chan_close_sender_send(int argc, void *argv[]) { + assert(argc == 2); + struct neco_chan *ch = argv[0]; + int x = *(int*)argv[1]; + expect(neco_chan_send(ch, &x), NECO_OK); + expect(neco_chan_release(ch), NECO_OK); +} + +void co_chan_close_sender_recv(int argc, void *argv[]) { + assert(argc == 1); + struct neco_chan *ch = argv[0]; + int x; + expect(neco_chan_recv(ch, &x), NECO_OK); + assert(x == 1289); + expect(neco_chan_recv(ch, &x), NECO_OK); + assert(x == 2938); + expect(neco_chan_recv(ch, &x), NECO_CLOSED); + expect(neco_chan_release(ch), NECO_OK); +} + +void co_chan_close_send(int argc, void *argv[]) { + (void)argc; (void)argv; + struct neco_chan *ch; + assert(neco_chan_make(&ch, 0, 0) == NECO_OK); + assert(neco_start(co_chan_close_sender, 1, ch) == NECO_OK); + assert(neco_chan_recv(ch, 0) == NECO_OK); + assert(neco_chan_recv(ch, 0) == NECO_CLOSED); + assert(neco_chan_recv(ch, 0) == NECO_CLOSED); + assert(neco_chan_release(ch) == NECO_OK); + + /// + + assert(neco_chan_make(&ch, sizeof(int), 0) == NECO_OK); + assert(neco_chan_retain(ch) == NECO_OK); + assert(neco_start(co_chan_close_sender_send, 2, ch, &(int){ 1289 }) == NECO_OK); + assert(neco_chan_retain(ch) == NECO_OK); + assert(neco_start(co_chan_close_sender_send, 2, ch, &(int){ 2938 }) == NECO_OK); + assert(neco_chan_close(ch) == NECO_OK); + assert(neco_chan_retain(ch) == NECO_OK); + assert(neco_start(co_chan_close_sender_recv, 1, ch) == NECO_OK); + assert(neco_chan_release(ch) == NECO_OK); +} + +void test_chan_close_send(void) { + expect(neco_start(co_chan_close_send, 0), NECO_OK); +} + +void co_chan_close_recv_waiting(int argc, void *argv[]) { + assert(argc == 1); + struct neco_chan *ch = argv[0]; + expect(neco_sleep(NECO_SECOND/10), NECO_OK); + expect(neco_chan_close(ch), NECO_OK); + expect(neco_chan_release(ch), NECO_OK); +} + +void co_chan_close_recv(int argc, void *argv[]) { + (void)argc; (void)argv; + struct neco_chan *ch; + expect(neco_chan_make(&ch, sizeof(int), 0), NECO_OK); + expect(neco_chan_retain(ch), NECO_OK); + expect(neco_start(co_chan_close_recv_waiting, 1, ch), NECO_OK); + expect(neco_chan_recv(ch, &(int){1}), NECO_CLOSED); + expect(neco_chan_release(ch), NECO_OK); +} + +void test_chan_close_recv(void) { + expect(neco_start(co_chan_close_recv, 0), NECO_OK); +} + +void co_chan_close_select_waiting(int argc, void *argv[]) { + assert(argc == 1); + struct neco_chan *ch = argv[0]; + expect(neco_sleep(NECO_SECOND/10), NECO_OK); + expect(neco_chan_close(ch), NECO_OK); + expect(neco_chan_release(ch), NECO_OK); +} + +void co_chan_close_select(int argc, void *argv[]) { + (void)argc; (void)argv; + struct neco_chan *ch; + expect(neco_chan_make(&ch, sizeof(int), 0), NECO_OK); + expect(neco_chan_retain(ch), NECO_OK); + expect(neco_start(co_chan_close_select_waiting, 1, ch), NECO_OK); + // select-case with the same channel twice is intentional. + assert(neco_chan_select(2, ch, ch) == 0); + expect(neco_chan_case(ch, &(int){1}), NECO_CLOSED); + expect(neco_chan_release(ch), NECO_OK); +} + +void test_chan_close_select(void) { + expect(neco_start(co_chan_close_select, 0), NECO_OK); +} + +void co_chan_broadcast_recv(int argc, void *argv[]) { + assert(argc == 1); + struct neco_chan *ch = argv[0]; + int x; + assert(neco_chan_recv(ch, &x) == NECO_OK); + assert(x == 9182); + assert(neco_chan_send(ch, &(int){5555}) == NECO_OK); + assert(neco_chan_release(ch) == NECO_OK); +} + +void co_chan_broadcast(int argc, void *argv[]) { + (void)argc; (void)argv; + struct neco_chan *ch; + assert(neco_chan_make(&ch, sizeof(int), 0) == NECO_OK); + int N = 10; + for (int i = 0; i < N; i++) { + assert(neco_chan_retain(ch) == NECO_OK); + assert(neco_start(co_chan_broadcast_recv, 1, ch) == NECO_OK); + } + int sent = neco_chan_broadcast(ch, &(int){9182}); + assert(sent == 10); + for (int i = 0; i < N; i++) { + int x; + assert(neco_chan_recv(ch, &x) == NECO_OK); + assert(x == 5555); + } + assert(neco_chan_release(ch) == NECO_OK); +} + +void test_chan_broadcast(void) { + assert(neco_start(co_chan_broadcast, 0) == NECO_OK); +} + +void co_chan_deadline1(int argc, void *argv[]) { + assert(argc == 2); + struct neco_chan *ch = argv[0]; + int other_id = *(int*)argv[1]; + int id = neco_getid(); + + int x = 0; + expect(neco_chan_recv_dl(ch, &x, neco_now() + NECO_SECOND/8), NECO_OK); + assert(x == 9183); + expect(neco_cancel(other_id), NECO_OK); + expect(neco_chan_send(ch, &id), NECO_OK); + expect(neco_chan_send(ch, &id), NECO_CANCELED); + expect(neco_chan_release(ch), NECO_OK); +} + +void co_chan_deadline(int argc, void *argv[]) { + (void)argc; (void)argv; + int id = neco_getid(); + struct neco_chan *ch; + expect(neco_chan_make(&ch, sizeof(int), 0), NECO_OK); + expect(neco_chan_send_dl(ch, &(int){9182}, neco_now() + NECO_SECOND/8), NECO_TIMEDOUT); + int x = 0; + expect(neco_chan_recv_dl(ch, &x, neco_now() + NECO_SECOND/8), NECO_TIMEDOUT); + expect(neco_chan_select_dl(neco_now() + NECO_SECOND/8, 1, ch), NECO_TIMEDOUT); + expect(neco_chan_select_dl(neco_now() + NECO_SECOND/8, 0), NECO_TIMEDOUT); + + expect(neco_chan_retain(ch), NECO_OK); + expect(neco_start(co_chan_deadline1, 2, ch, &id), NECO_OK); + expect(neco_chan_send_dl(ch, &(int){9183}, neco_now() + NECO_SECOND/8), NECO_OK); + expect(neco_chan_recv(ch, &x), NECO_CANCELED); + int other_id; + expect(neco_chan_recv(ch, &other_id), NECO_OK); + expect(neco_cancel(other_id), NECO_OK); + expect(neco_chan_release(ch), NECO_OK); +} + +void test_chan_deadline(void) { + expect(neco_start(co_chan_deadline, 0), NECO_OK); +} + +void co_chan_cancel1(int argc, void *argv[]) { + assert(argc == 2); + struct neco_chan *ch = argv[0]; + int other_id = *(int*)argv[1]; + expect(neco_sleep(NECO_SECOND/10), NECO_OK); + expect(neco_cancel(other_id), NECO_OK); + expect(neco_chan_release(ch), NECO_OK); +} + +void co_chan_cancel(int argc, void *argv[]) { + (void)argc; (void)argv; + int id = neco_getid(); + struct neco_chan *ch; + expect(neco_chan_make(&ch, sizeof(int), 0), NECO_OK); + expect(neco_chan_retain(ch), NECO_OK); + expect(neco_start(co_chan_cancel1, 2, ch, &id), NECO_OK); + expect(neco_chan_send(ch, &(int){1}), NECO_CANCELED); + expect(neco_chan_retain(ch), NECO_OK); + expect(neco_start(co_chan_cancel1, 2, ch, &id), NECO_OK); + expect(neco_chan_recv(ch, &(int){1}), NECO_CANCELED); + expect(neco_chan_release(ch), NECO_OK); +} + +void test_chan_cancel(void) { + expect(neco_start(co_chan_cancel, 0), NECO_OK); +} + +void co_chan_fail(int argc, void *argv[]) { + (void)argc; (void)argv; + expect(neco_chan_make(0, 0, 0), NECO_INVAL); + neco_chan *ch; + expect(neco_chan_make(&ch, SIZE_MAX, 0), NECO_INVAL); + neco_fail_neco_malloc_counter = 1; + expect(neco_chan_make(&ch, 10, 0), NECO_NOMEM); + expect(neco_chan_release(0), NECO_INVAL); + + neco_fail_neco_malloc_counter = 1; + expect(neco_chan_make(&ch, 0, 0), NECO_NOMEM); + + expect(neco_chan_make(&ch, 0, 0), NECO_OK); + neco_fail_neco_realloc_counter = 1; + + expect(neco_chan_close(ch), NECO_OK); + expect(neco_chan_case(ch, 0), NECO_CLOSED); + expect(neco_chan_release(ch), NECO_OK); + expect(neco_chan_select(-1), NECO_INVAL); + + expect(neco_chan_make(&ch, (size_t)INT_MAX+1, 0), NECO_INVAL); + expect(neco_chan_make(&ch, 0, (size_t)INT_MAX+1), NECO_INVAL); +} + + +void test_chan_fail(void) { + neco_chan *ch; + expect(neco_chan_make(&ch, 0, 0), NECO_PERM); + expect(neco_chan_retain(0), NECO_INVAL); + expect(neco_chan_retain((neco_chan*)1), NECO_PERM); + expect(neco_chan_release((neco_chan*)1), NECO_PERM); + expect(neco_chan_send(0, 0), NECO_INVAL); + expect(neco_chan_send((neco_chan*)1, 0), NECO_PERM); + expect(neco_chan_recv(0, 0), NECO_INVAL); + expect(neco_chan_recv((neco_chan*)1, 0), NECO_PERM); + expect(neco_chan_close(0), NECO_INVAL); + expect(neco_chan_close((neco_chan*)1), NECO_PERM); + expect(neco_chan_case(0, 0), NECO_INVAL); + expect(neco_chan_case((neco_chan*)1, 0), NECO_PERM); + expect(neco_start(co_chan_fail, 0), NECO_OK); + expect(neco_chan_select(1), NECO_PERM); +} + +void co_chan_zchanpool(int argc, void *argv[]) { + (void)argc; (void)argv; + int N = 500; + neco_chan **chs = malloc(sizeof(neco_chan*)*N); + assert(chs); + for (int i = 0; i < N/2; i++) { + expect(neco_chan_make(&chs[i], 0, 0), NECO_OK); + } + for (int i = 0; i < N/4; i++) { + expect(neco_chan_release(chs[i]), NECO_OK); + } + for (int i = N/2; i < N; i++) { + expect(neco_chan_make(&chs[i], 0, 0), NECO_OK); + } + for (int i = N/4; i < N; i++) { + expect(neco_chan_release(chs[i]), NECO_OK); + } + free(chs); +} + +void test_chan_zchanpool(void) { + expect(neco_start(co_chan_zchanpool, 0), NECO_OK); +} + +void co_chan_tryrecv(int argc, void *argv[]) { + (void)argc; (void)argv; + neco_chan *ch; + int x; + expect(neco_chan_make(&ch, sizeof(int), 1), NECO_OK); + expect(neco_chan_tryrecv(ch, &x), NECO_EMPTY); + expect(neco_chan_send(ch, &(int){1}), NECO_OK); + expect(neco_chan_close(ch), NECO_OK); + expect(neco_chan_tryrecv(ch, &x), NECO_OK); + assert(x == 1); + expect(neco_chan_tryrecv(ch, &x), NECO_CLOSED); + expect(neco_chan_release(ch), NECO_OK); + + + expect(neco_chan_make(&ch, sizeof(int), 1), NECO_OK); + expect(neco_chan_tryrecv(ch, &x), NECO_EMPTY); + expect(neco_chan_send(ch, &(int){1}), NECO_OK); + expect(neco_chan_tryrecv(ch, &x), NECO_OK); + assert(x == 1); + expect(neco_chan_close(ch), NECO_OK); + expect(neco_chan_tryrecv(ch, &x), NECO_CLOSED); + expect(neco_chan_release(ch), NECO_OK); +} + +void test_chan_tryrecv(void) { + expect(neco_start(co_chan_tryrecv, 0), NECO_OK); +} + +void co_chan_tryselect(int argc, void *argv[]) { + (void)argc; (void)argv; + neco_chan *ch; + expect(neco_chan_make(&ch, sizeof(int), 1), NECO_OK); + expect(neco_chan_tryselect(1, ch), NECO_EMPTY); + expect(neco_chan_tryselectv(1, &(neco_chan*){ch}), NECO_EMPTY); + expect(neco_chan_release(ch), NECO_OK); +} + + +void test_chan_tryselect(void) { + expect(neco_start(co_chan_tryselect, 0), NECO_OK); +} + +int main(int argc, char **argv) { + do_test(test_chan_order); + do_test(test_chan_select); + do_test(test_chan_close_send); + do_test(test_chan_close_recv); + do_test(test_chan_close_select); + do_test(test_chan_tryrecv); + do_test(test_chan_tryselect); + do_test(test_chan_broadcast); + do_test(test_chan_deadline); + do_test(test_chan_cancel); + do_test(test_chan_zchanpool); + do_test(test_chan_fail); +} diff --git a/tests/test_errors.c b/tests/test_errors.c new file mode 100644 index 0000000..3ccda2b --- /dev/null +++ b/tests/test_errors.c @@ -0,0 +1,113 @@ +#include +#include "tests.h" + +void test_errors_short(void) { + assert(strcmp(neco_shortstrerror(NECO_OK), "NECO_OK") == 0); + assert(strcmp(neco_shortstrerror(NECO_ERROR), "NECO_ERROR") == 0); + assert(strcmp(neco_shortstrerror(NECO_INVAL), "NECO_INVAL") == 0); + assert(strcmp(neco_shortstrerror(NECO_PERM), "NECO_PERM") == 0); + assert(strcmp(neco_shortstrerror(NECO_NOMEM), "NECO_NOMEM") == 0); + assert(strcmp(neco_shortstrerror(NECO_NOSIGWATCH), "NECO_NOSIGWATCH") == 0); + assert(strcmp(neco_shortstrerror(NECO_CLOSED), "NECO_CLOSED") == 0); + assert(strcmp(neco_shortstrerror(NECO_EMPTY), "NECO_EMPTY") == 0); + assert(strcmp(neco_shortstrerror(NECO_TIMEDOUT), "NECO_TIMEDOUT") == 0); + assert(strcmp(neco_shortstrerror(NECO_CANCELED), "NECO_CANCELED") == 0); + assert(strcmp(neco_shortstrerror(NECO_BUSY), "NECO_BUSY") == 0); + assert(strcmp(neco_shortstrerror(NECO_NEGWAITGRP), "NECO_NEGWAITGRP") == 0); + assert(strcmp(neco_shortstrerror(NECO_GAIERROR), "NECO_GAIERROR") == 0); + assert(strcmp(neco_shortstrerror(NECO_NOTFOUND), "NECO_NOTFOUND") == 0); + assert(strcmp(neco_shortstrerror(NECO_UNREADFAIL), "NECO_UNREADFAIL") == 0); + assert(strcmp(neco_shortstrerror(NECO_PARTIALWRITE), "NECO_PARTIALWRITE") == 0); + assert(strcmp(neco_shortstrerror(NECO_NOTGENERATOR), "NECO_NOTGENERATOR") == 0); + assert(strcmp(neco_shortstrerror(NECO_NOTSUSPENDED), "NECO_NOTSUSPENDED") == 0); + assert(strcmp(neco_shortstrerror(1), "UNKNOWN") == 0); +} + +void test_errors_string(void) { + assert(strcmp(neco_strerror(-1909), "Undefined error: -1909") == 0); + assert(strcmp(neco_strerror(NECO_OK), "Success") == 0); + assert(strcmp(neco_strerror(NECO_INVAL), strerror(EINVAL)) == 0); + assert(strcmp(neco_strerror(NECO_PERM), strerror(EPERM)) == 0); + assert(strcmp(neco_strerror(NECO_NOMEM), strerror(ENOMEM)) == 0); + assert(strcmp(neco_strerror(NECO_NOSIGWATCH), "Not watching on a signal") == 0); + assert(strcmp(neco_strerror(NECO_CLOSED), "Channel closed") == 0); + assert(strcmp(neco_strerror(NECO_EMPTY), "Channel empty") == 0); + assert(strcmp(neco_strerror(NECO_TIMEDOUT), strerror(ETIMEDOUT)) == 0); + assert(strcmp(neco_strerror(NECO_CANCELED), strerror(ECANCELED)) == 0); + assert(strcmp(neco_strerror(NECO_BUSY), strerror(EBUSY)) == 0); + assert(strcmp(neco_strerror(NECO_NOTFOUND), "No such coroutine") == 0); + assert(strcmp(neco_strerror(NECO_NEGWAITGRP), "Negative waitgroup counter") == 0); + assert(strcmp(neco_strerror(NECO_UNREADFAIL), "Failed to unread byte") == 0); + assert(strcmp(neco_strerror(NECO_PARTIALWRITE), "Failed to write all bytes") == 0); + assert(strcmp(neco_strerror(NECO_NOTGENERATOR), "Coroutine is not a generator") == 0); + assert(strcmp(neco_strerror(NECO_NOTSUSPENDED), "Coroutine is not suspended") == 0); + errno = ENOMEM; + assert(strcmp(neco_strerror(NECO_ERROR), strerror(ENOMEM)) == 0); + neco_gai_errno = EAI_SOCKTYPE; + assert(strcmp(neco_strerror(NECO_GAIERROR), gai_strerror(EAI_SOCKTYPE)) == 0); + neco_gai_errno = EAI_SYSTEM; + assert(strcmp(neco_strerror(NECO_GAIERROR), strerror(ENOMEM)) == 0); +} + +void test_errors_conv(void) { + errno = EPERM; + assert(neco_errconv_from_sys() == NECO_PERM); + errno = EINVAL; + assert(neco_errconv_from_sys() == NECO_INVAL); + errno = ENOMEM; + assert(neco_errconv_from_sys() == NECO_NOMEM); + errno = ECANCELED; + assert(neco_errconv_from_sys() == NECO_CANCELED); + errno = ETIMEDOUT; + assert(neco_errconv_from_sys() == NECO_TIMEDOUT); + errno = EAGAIN; + assert(neco_errconv_from_sys() == NECO_ERROR); + assert(neco_errconv_from_gai(EAI_MEMORY) == NECO_NOMEM); + errno = EAGAIN; + assert(neco_errconv_from_gai(EAI_SYSTEM) == NECO_ERROR); + errno = ETIMEDOUT; + assert(neco_errconv_from_gai(EAI_SYSTEM) == NECO_TIMEDOUT); + neco_gai_errno = EAI_FAIL; + assert(neco_errconv_from_gai(EAI_SOCKTYPE) == NECO_GAIERROR); + assert(neco_gai_errno == EAI_SOCKTYPE); + assert(neco_gai_lasterr() == neco_gai_errno); + + + neco_errconv_to_sys(NECO_OK); + assert(errno == 0); + + neco_errconv_to_sys(NECO_INVAL); + assert(errno == EINVAL); + neco_errconv_to_sys(NECO_PERM); + assert(errno == EPERM); + neco_errconv_to_sys(NECO_NOMEM); + assert(errno == ENOMEM); + neco_errconv_to_sys(NECO_CANCELED); + assert(errno == ECANCELED); + neco_errconv_to_sys(NECO_TIMEDOUT); + assert(errno == ETIMEDOUT); +} + +void test_errors_code(void) { + errno = ETIMEDOUT; + assert(neco_testcode(-1) == NECO_ERROR && errno == ETIMEDOUT); + assert(neco_lasterr() == NECO_TIMEDOUT); + + + assert(!neco_last_panic); + neco_env_setpaniconerror(true); + errno = EINVAL; + assert(neco_testcode(-1) == NECO_ERROR && errno == EINVAL); + assert(neco_lasterr() == NECO_INVAL); + neco_env_setpaniconerror(false); + assert(neco_last_panic); + neco_last_panic = false; +} + + +int main(int argc, char **argv) { + do_test(test_errors_short); + do_test(test_errors_string); + do_test(test_errors_conv); + do_test(test_errors_code); +} diff --git a/tests/test_gen.c b/tests/test_gen.c new file mode 100644 index 0000000..1dd9109 --- /dev/null +++ b/tests/test_gen.c @@ -0,0 +1,69 @@ +#include "tests.h" + +void co_gen_yielder(int argc, void *argv[]) { + (void)argc; (void)argv; + expect(neco_gen_yield(&(int){9}), NECO_OK); + expect(neco_gen_yield(&(int){4}), NECO_OK); + expect(neco_sleep(NECO_SECOND/10), NECO_OK); + expect(neco_gen_yield(&(int){3}), NECO_OK); + expect(neco_gen_yield(&(int){7}), NECO_CLOSED); + +} + +void co_gen_yield(int argc, void *argv[]) { + assert(argc == 1); + bool startv = *(bool*)argv[0]; + + expect(neco_gen_yield(0), NECO_NOTGENERATOR); + + neco_gen *gen; + if (startv) { + expect(neco_gen_startv(&gen, sizeof(int), co_gen_yielder, 0, 0), NECO_OK); + } else { + expect(neco_gen_start(&gen, sizeof(int), co_gen_yielder, 0), NECO_OK); + } + expect(neco_gen_retain(gen), NECO_OK); + int x; + expect(neco_gen_next(gen, &x), NECO_OK); + assert(x == 9); + expect(neco_gen_next(gen, &x), NECO_OK); + assert(x == 4); + expect(neco_gen_next_dl(gen, &x, neco_now()+NECO_SECOND/20), NECO_TIMEDOUT); + expect(neco_gen_next(gen, &x), NECO_OK); + assert(x == 3); + expect(neco_gen_close(gen), NECO_OK); + expect(neco_gen_release(gen), NECO_OK); // for gen_start + expect(neco_gen_release(gen), NECO_OK); // for gen_retain +} + +void test_gen_yield(void) { + expect(neco_start(co_gen_yield, 1, &(bool){true}), NECO_OK); + expect(neco_start(co_gen_yield, 1, &(bool){false}), NECO_OK); +} + +void co_gen_fail_yielder(int argc, void *argv[]) { + (void)argc; (void)argv; + +} + +void co_gen_fail(int argc, void *argv[]) { + (void)argc; (void)argv; + neco_gen *gen; + neco_fail_neco_malloc_counter = 2; + expect(neco_gen_start(&gen, 0, co_gen_fail_yielder, 0), NECO_NOMEM); + neco_fail_neco_malloc_counter = 3; + expect(neco_gen_start(&gen, 0, co_gen_fail_yielder, 5, 0, 0, 0, 0, 0), NECO_NOMEM); +} + +void test_gen_fail(void) { + expect(neco_gen_start(0, 0, 0, 0), NECO_INVAL); + expect(neco_gen_start((void*)1, SIZE_MAX, 0, 0), NECO_INVAL); + expect(neco_gen_start((void*)1, 0, 0, 0), NECO_PERM); + expect(neco_gen_yield(0), NECO_PERM); + expect(neco_start(co_gen_fail, 0), NECO_OK); +} + +int main(int argc, char **argv) { + do_test(test_gen_fail); + do_test(test_gen_yield); +} diff --git a/tests/test_join.c b/tests/test_join.c new file mode 100644 index 0000000..95c4dfd --- /dev/null +++ b/tests/test_join.c @@ -0,0 +1,78 @@ +#include +#include "tests.h" + +static int value = 0; + +void co_join_child(int argc, void *argv[]) { + (void)argc; (void)argv; + value = 9918; + expect(neco_sleep(NECO_SECOND/10), NECO_OK); + value = 1899; +} + +void co_join_canceler(int argc, void *argv[]) { + (void)argc; (void)argv; + expect(neco_sleep(NECO_SECOND/20), NECO_OK); + expect(neco_cancel(neco_starterid()), NECO_OK); +} + +void co_join(int argc, void *argv[]) { + (void)argc; (void)argv; + assert(neco_starterid() == 0); + + expect(neco_join(neco_getid()), NECO_PERM); + + expect(neco_start(co_join_child, 0), NECO_OK); + assert(value == 9918); + expect(neco_join(neco_lastid()), NECO_OK); + assert(value == 1899); + + value = 0; + expect(neco_start(co_join_child, 0), NECO_OK); + assert(value == 9918); + expect(neco_join_dl(neco_lastid(), neco_now()+NECO_MILLISECOND), NECO_TIMEDOUT); + assert(value == 9918); + + value = 0; + expect(neco_start(co_join_child, 0), NECO_OK); + assert(value == 9918); + expect(neco_join_dl(neco_lastid(), 1), NECO_TIMEDOUT); + assert(value == 9918); + + value = 0; + expect(neco_start(co_join_child, 0), NECO_OK); + assert(value == 9918); + expect(neco_join_dl(neco_lastid(), 1), NECO_TIMEDOUT); + assert(value == 9918); + + value = 0; + expect(neco_start(co_join_child, 0), NECO_OK); + assert(value == 9918); + expect(neco_join_dl(-1, 1), NECO_OK); + assert(value == 9918); + + value = 0; + expect(neco_start(co_join_child, 0), NECO_OK); + assert(value == 9918); + expect(neco_cancel(neco_getid()), NECO_OK); + expect(neco_join_dl(neco_lastid(), 1), NECO_CANCELED); + assert(value == 9918); + + value = 0; + expect(neco_start(co_join_child, 0), NECO_OK); + assert(value == 9918); + expect(neco_start(co_join_canceler, 0), NECO_OK); + expect(neco_join(neco_lastid()), NECO_CANCELED); + assert(value == 9918); + +} + +void test_join(void) { + expect(neco_join(0), NECO_PERM); + expect(neco_start(co_join, 0), NECO_OK); +} + + +int main(int argc, char **argv) { + do_test(test_join); +} diff --git a/tests/test_misc.c b/tests/test_misc.c new file mode 100644 index 0000000..a1e659d --- /dev/null +++ b/tests/test_misc.c @@ -0,0 +1,33 @@ +#include +#include "tests.h" + +void *neco_malloc(size_t nbytes); +void *neco_realloc(void *ptr, size_t nbytes); +void neco_free(void *ptr); + + +int64_t i64_add_clamp(int64_t a, int64_t b); + +void test_misc_units(void) { + assert(i64_add_clamp(0, INT64_MIN) == INT64_MIN); + assert(i64_add_clamp(0, INT64_MAX) == INT64_MAX); + assert(i64_add_clamp(1, INT64_MIN) == INT64_MIN+1); + assert(i64_add_clamp(1, INT64_MAX) == INT64_MAX); + assert(i64_add_clamp(-1, INT64_MIN) == INT64_MIN); + assert(i64_add_clamp(-1, INT64_MAX) == INT64_MAX-1); +} + +int main(int argc, char **argv) { + // test outside of the "do" + + neco_env_setallocator(0,0,0); + void *ptr = neco_malloc(100); + assert(ptr); + ptr = neco_realloc(ptr, 200); + assert(ptr); + neco_free(ptr); + neco_free(0); + + + do_test(test_misc_units); +} diff --git a/tests/test_net.c b/tests/test_net.c new file mode 100644 index 0000000..f12746f --- /dev/null +++ b/tests/test_net.c @@ -0,0 +1,571 @@ +#include "tests.h" + +#if defined(_WIN32) +DISABLED("test_net", "Windows") +#elif defined(__EMSCRIPTEN__) +DISABLED("test_net", "Emscripten") +#else + +#include +#include +#include +#include + +void co_net_serve_client(int argc, void *argv[]) { + assert(argc == 2); + char *proto = argv[0]; + char *addr = argv[1]; + int fd = neco_dial(proto, addr); + assert(fd > 0); + int ret = neco_write(fd, "+PING\r\n", 7); + assert(ret == 7); + char buf[256]; + ssize_t n = neco_read(fd, buf, sizeof(buf)); + assert(n == 7); + buf[n] = '\0'; + assert(strcmp(buf, "+PONG\r\n") == 0); + + + assert(neco_read_dl(fd, buf, sizeof(buf), 1) == NECO_ERROR && errno == ETIMEDOUT); + expect(neco_cancel(neco_getid()), NECO_OK); + assert(neco_read(fd, buf, sizeof(buf)) == NECO_ERROR && errno == ECANCELED); + + close(fd); +} + +void co_net_serve_server(int argc, void *argv[]) { + assert(argc == 1); + int fd = *(int*)argv[0]; + char buf[256]; + ssize_t n = neco_read(fd, buf, sizeof(buf)); + assert(n == 7); + buf[n] = '\0'; + assert(strcmp(buf, "+PING\r\n") == 0); + assert(neco_write(fd, "+PONG\r\n", 7) == 7); + close(fd); +} + +void co_net_serve(int argc, void *argv[]) { + assert(argc == 4); + char *proto_sv = argv[0]; + char *addr_sv = argv[1]; + char *proto_cl = argv[2]; + char *addr_cl = argv[3]; + if (strcmp(proto_sv, "unix") == 0) { + unlink(addr_sv); + } + int sockfd = neco_serve(proto_sv, addr_sv); + assert(sockfd > 0); + int fd2 = neco_serve(proto_sv, addr_sv); + assert(fd2 == -1 && errno == EADDRINUSE); + int N = 10; + for (int i = 0; i < N; i++) { + expect(neco_start(co_net_serve_client, 2, proto_cl, addr_cl), NECO_OK); + } + int i = 0; + while (i < N) { + neco_fail_evqueue_counter = 1; + int fd = neco_accept(sockfd, 0, 0); + neco_fail_evqueue_counter = 0; + assert(fd > 0); + expect(neco_start(co_net_serve_server, 1, &fd), NECO_OK); + i++; + } + assert(close(sockfd) == 0); +} + +void test_net_unix(void) { + expect(neco_start(co_net_serve, 4, + "unix", "socket", "unix", "socket"), NECO_OK); + expect(neco_start(co_net_serve, 4, + "unix", "socket", "unix", "socket"), NECO_OK); +} + +void test_net_tcp_auto(void) { + expect(neco_start(co_net_serve, 4, + "tcp", "localhost:19770", "tcp", "localhost:19770"), NECO_OK); +} + +void test_net_tcp_mix40(void) { + expect(neco_start(co_net_serve, 4, + "tcp4", "localhost:19770", "tcp", "localhost:19770"), NECO_OK); +} +void test_net_tcp_mix60(void) { + expect(neco_start(co_net_serve, 4, + "tcp6", "[::]:19770", "tcp", "localhost:19770"), NECO_OK); +} +void test_net_tcp_mix44(void) { + expect(neco_start(co_net_serve, 4, + "tcp4", "localhost:19770", "tcp4", "localhost:19770"), NECO_OK); +} +void test_net_tcp_mix66(void) { + expect(neco_start(co_net_serve, 4, + "tcp6", "localhost:19770", "tcp6", "localhost:19770"), NECO_OK); +} + +void test_net_tcp_emptyhost(void) { + expect(neco_start(co_net_serve, 4, + "tcp", ":19770", "tcp", ":19770"), NECO_OK); +} + +void co_net_cancel_child(int argc, void **argv) { + assert(argc == 1); + int64_t id = *(int64_t*)argv[0]; + expect(neco_cancel(id), NECO_OK); // Cancel Accept 1 + expect(neco_cancel(id), NECO_OK); // Cancel Accept 2 +} + +void co_net_cancel(int argc, void **argv) { + assert(argc == 0); + (void)argv; + unlink("socket"); + int sockfd = neco_serve("unix", "socket"); + assert(sockfd > 0); + // Test for deadline + int fd = neco_accept_dl(sockfd, 0, 0, neco_now() + NECO_SECOND / 4); + assert(fd == -1 && errno == ETIMEDOUT); + + int64_t id = neco_getid(); + assert(id > 0); + expect(neco_start(co_net_cancel_child, 1, &id), NECO_OK); + fd = neco_accept(sockfd, 0, 0); // Accept 1 + assert(fd == -1 && errno == ECANCELED); + fd = neco_accept(sockfd, 0, 0); // Accept 2 + assert(fd == -1 && errno == ECANCELED); + close(sockfd); +} + +void test_net_cancel(void) { + // Test for starting a socket reader and writer, performing deadlines and + // canceling. + expect(neco_start(co_net_cancel, 0), NECO_OK); +} + +void co_net_getaddrinfo(int argc, void **argv) { + assert(argc == 0); + (void)argv; + + struct addrinfo hints = { 0 }; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_family = AF_INET; + struct addrinfo *ainfo = NULL; + + int ret; + + // This will succeed + ret = neco_getaddrinfo_dl("localhost", "19770", &hints, &ainfo, neco_now()+NECO_HOUR); + assert(ret == 0); + freeaddrinfo(ainfo); + + // This will timeout + ret = neco_getaddrinfo_dl("localhost", "19770", &hints, &ainfo, neco_now()-1); + assert(ret == EAI_SYSTEM && errno == ETIMEDOUT); + // expect(neco_sleep(NECO_SECOND/4), NECO_OK); + + // This will succeed + ret = neco_getaddrinfo_dl("localhost", "19770", &hints, &ainfo, neco_now()+NECO_HOUR); + assert(ret == 0); + freeaddrinfo(ainfo); + + // This will timeout + ret = neco_getaddrinfo_dl("127.0.0.1", "19770", &hints, &ainfo, neco_now()-1); + assert(ret == EAI_SYSTEM && errno == ETIMEDOUT); + // expect(neco_sleep(NECO_SECOND/4), NECO_OK); + + // This will timeout + ret = neco_getaddrinfo_dl("google.com", "19770", &hints, &ainfo, neco_now()-1); + assert(ret == EAI_SYSTEM && errno == ETIMEDOUT); + + // This will succeed + ret = neco_getaddrinfo("127.0.0.1", "19770", &hints, &ainfo); + assert(ret == 0); + freeaddrinfo(ainfo); + + // This will cancel + int64_t id = neco_getid(); + expect(neco_cancel(id), NECO_OK); + ret = neco_getaddrinfo_dl("google.com", "19770", &hints, &ainfo, neco_now()-1); + assert(ret == EAI_SYSTEM && errno == ECANCELED); + + // This will cancel + expect(neco_cancel(id), NECO_OK); + ret = neco_getaddrinfo_dl("localhost", "19770", &hints, &ainfo, neco_now()-1); + assert(ret == EAI_SYSTEM && errno == ECANCELED); + + // This will succeed + int fd = neco_serve("tcp6", ":19970"); + assert(fd > 0); + close(fd); + +} + +void test_net_getaddrinfo(void) { + struct addrinfo hints = { 0 }; + struct addrinfo *ainfo = NULL; + int ret = neco_getaddrinfo("localhost", "9999", &hints, &ainfo); + assert(ret == EAI_SYSTEM && errno == EPERM); + expect(neco_start(co_net_getaddrinfo, 0), NECO_OK); +} + +void co_net_dial_client(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + int fd; + fd = neco_dial_dl("tcp", "127.0.0.1:19760", neco_now()+NECO_SECOND/4); + assert(fd == -1 && errno == ECONNREFUSED); + fd = neco_dial_dl("tcp", "127.0.0.1:19770", neco_now()-1); + assert(fd == NECO_TIMEDOUT); + fd = neco_dial_dl("tcp", "127.0.0.1:19770", neco_now()+NECO_SECOND); + assert(fd > 0); + close(fd); +} + +void co_net_dial(int argc, void **argv) { + assert(argc == 0); + (void)argv; + int sockfd, fd; + expect(neco_dial(0, 0), NECO_INVAL); + expect(neco_dial("", ""), NECO_INVAL); + expect(neco_dial("unix", + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000" + ), NECO_INVAL); + + fd = neco_dial("unix", "/"); + assert(fd == -1 && (errno == ENOTSOCK || errno == EACCES)); + fd = neco_dial("tcp", "127.0.0.1:19760"); + assert(fd == -1 && errno == ECONNREFUSED); + fd = neco_dial_dl("tcp", "127.0.0.1:19760", neco_now()+NECO_SECOND/4); + assert(fd == -1 && errno == ECONNREFUSED); + fd = neco_dial_dl("tcp", "127.0.0.1:19760", neco_now()-1); + assert(fd == NECO_TIMEDOUT); + sockfd = neco_serve("tcp", "127.0.0.1:19770"); + assert(sockfd > 0); + expect(neco_start(co_net_dial_client, 0), NECO_OK); + struct sockaddr_in addr = { 0 }; + socklen_t addrlen = sizeof(struct sockaddr_in); + fd = neco_accept(sockfd, (struct sockaddr*)&addr, &addrlen); + assert(fd > 0); + assert(strcmp(inet_ntoa(addr.sin_addr), "127.0.0.1") == 0); + assert(addr.sin_port > 0); + close(fd); + close(sockfd); +} + +void test_net_dial(void) { + expect(neco_start(co_net_dial, 0), NECO_OK); +} + +void co_net_dial_fail(int argc, void **argv) { + assert(argc == 0); + (void)argv; + + int ret; + + neco_fail_socket_counter = 1; + ret = neco_dial("unix", "socket"); + assert(ret == NECO_ERROR && errno == EMFILE); + + neco_fail_fcntl_counter = 1; + ret = neco_dial("unix", "socket"); + assert(ret == NECO_ERROR && errno == EBADF); + +} + +void test_net_dial_fail(void) { + expect(neco_dial(0, 0), NECO_PERM); + expect(neco_read(0, 0, 0), NECO_ERROR, NECO_PERM); + expect(neco_write(0, 0, 0), NECO_ERROR, NECO_PERM); + expect(neco_start(co_net_dial_fail, 0), NECO_OK); +} + +void co_net_getaddrinfo_fail(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + struct addrinfo hints = { 0 }; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_family = AF_INET; + + struct addrinfo *ainfo = NULL; + int ret; + ret = neco_getaddrinfo("", "9999", &hints, &ainfo); + +#ifdef __APPLE__ + // Apple does not allow empty node strings + assert(ret == 0); + freeaddrinfo(ainfo); +#else + assert(ret != 0); +#endif + + ret = neco_getaddrinfo(0, "9999", &hints, &ainfo); + assert(ret == 0); + freeaddrinfo(ainfo); + + ret = neco_getaddrinfo("i1o2293405", "9999", &hints, &ainfo); + assert(ret != 0); + + // cause NOMEM errors at six different positions + for (int i = 1; i <= 4; i++) { + neco_fail_neco_malloc_counter = i; + ret = neco_getaddrinfo("i1o2293405", "9999", &hints, &ainfo); + assert(ret == EAI_MEMORY); + } + + neco_fail_pipe_counter = 1; + ret = neco_getaddrinfo("i1o2293405", "9999", &hints, &ainfo); + assert(ret == EAI_SYSTEM && errno == EMFILE); + + neco_fail_fcntl_counter = 1; + ret = neco_getaddrinfo("i1o2293405", "9999", &hints, &ainfo); + assert(ret == EAI_SYSTEM && errno == EBADF); + + neco_fail_pthread_create_counter = 1; + ret = neco_getaddrinfo("i1o2293405", "9999", &hints, &ainfo); + assert(ret == EAI_SYSTEM && errno == EPERM); + + neco_fail_read_counter = 1; + ret = neco_getaddrinfo("i1o2293405", "9999", &hints, &ainfo); + assert(ret == EAI_SYSTEM && errno == EIO); + + + expect(neco_sleep(NECO_SECOND/4), NECO_OK); +} + +void test_net_getaddrinfo_fail(void) { + expect(neco_start(co_net_getaddrinfo_fail, 0), NECO_OK); +} + +void co_net_serve_fail(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + int fd; + + expect(neco_serve("tcp2", "localhost:80"), NECO_INVAL); + + unlink("socket"); + neco_fail_socket_counter = 1; + fd = neco_serve("unix", "socket"); + assert(fd == -1 && errno == EMFILE); + + unlink("socket"); + neco_fail_listen_counter = 1; + fd = neco_serve("unix", "socket"); + assert(fd == -1 && errno == EADDRINUSE); + + unlink("socket"); + neco_fail_fcntl_counter = 1; + fd = neco_serve("unix", "socket"); + assert(fd == -1 && errno == EBADF); + + neco_fail_neco_malloc_counter = 1; + fd = neco_serve("tcp", "localhost:80"); + assert(fd == NECO_NOMEM); + + neco_fail_socket_counter = 1; + fd = neco_serve("tcp", "localhost:80"); + assert(fd == -1 && errno == EMFILE); + + neco_fail_setsockopt_counter = 1; + fd = neco_serve("tcp", "localhost:80"); + assert(fd == -1 && errno == EBADF); + + neco_fail_bind_counter = 1; + fd = neco_serve("tcp", "localhost:19970"); + assert(fd == -1 && errno == EADDRINUSE); + + neco_fail_listen_counter = 1; + fd = neco_serve("tcp", "localhost:19970"); + assert(fd == -1 && errno == EADDRINUSE); + + neco_fail_fcntl_counter = 1; + fd = neco_serve("tcp", "localhost:19970"); + assert(fd == -1 && errno == EBADF); + + fd = neco_serve("tcp", ""); + assert(fd == NECO_INVAL); + + fd = neco_serve("tcp", "localhost"); + assert(fd == NECO_INVAL); + + + expect(neco_cancel(neco_getid()), NECO_OK); + fd = neco_serve("tcp", "127.0.0.1:19970"); + assert(fd == NECO_CANCELED); + + +} + +void test_net_serve_fail(void) { + expect(neco_serve(0, "localhost:80"), NECO_INVAL); + expect(neco_serve("tcp", 0), NECO_INVAL); + expect(neco_serve("tcp", "localhost:80"), NECO_PERM); + expect(neco_start(co_net_serve_fail, 0), NECO_OK); +} + +void co_net_autoclose_queue(int argc, void *argv[]) { + (void)argc; (void)argv; + // Should automatically close the qfd after 100 ms + int fd = neco_serve("tcp", "localhost:19970"); + assert(fd != -1); + expect(neco_accept_dl(fd,0,0, neco_now()+NECO_SECOND/4), NECO_ERROR, NECO_TIMEDOUT); + close(fd); + expect(neco_sleep(NECO_SECOND/4), NECO_OK); +} + +void test_net_autoclose_queue(void) { + expect(neco_start(co_net_autoclose_queue, 0), NECO_OK); +} + +void co_net_write_errors_reader(int argc, void *argv[]) { + assert(argc == 1); + neco_waitgroup *wg = argv[0]; + neco_sleep(NECO_MILLISECOND*100); + errno = 0; + int fd = neco_dial("unix", "socket"); + // assert(fd == -1); + // fd = neco_dial("unix", "socket"); + assert(fd > 0); + // printf("=====================>>>> !!!!\n"); + char data[50]; + // should be closed + assert(neco_read(fd, &data, sizeof(data)) == 0); + close(fd); + + fd = neco_dial("unix", "socket"); + // assert(fd == -1); + // fd = neco_dial("unix", "socket"); + assert(fd > 0); + // printf("=====================>>>> !!!!\n"); + + + assert(neco_read(fd, &data, sizeof(data)) == 5); + close(fd); + neco_waitgroup_done(wg); +} + +void co_net_write_errors(int argc, void *argv[]) { + (void)argc; (void)argv; + + unlink("socket"); + neco_fail_evqueue_counter = 1; + int sock = neco_serve("unix", "socket"); + assert(sock > 0); + neco_waitgroup wg = { 0 }; + neco_waitgroup_init(&wg); + neco_waitgroup_add(&wg, 1); + neco_start(co_net_write_errors_reader, 1, &wg); + + neco_sleep(NECO_SECOND/10); + neco_cancel(neco_getid()); + assert(neco_accept(sock, 0, 0) == -1 && errno == ECANCELED); + assert(neco_accept_dl(sock, 0, 0, 1) == -1 && errno == ETIMEDOUT); + + neco_fail_accept_counter = 1; + neco_fail_accept_error = EBADF; + assert(neco_accept(sock, 0, 0) == -1 && errno == EBADF); + // printf(">>> %d\n", neco_fail_fcntl_counter); + + neco_fail_fcntl_counter = 1; + neco_fail_fcntl_error = EBADF; + assert(neco_accept(sock, 0, 0) == -1 && errno == EBADF); + + int fd = neco_accept(sock, 0, 0); + // printf(">>> %d\n", neco_fail_fcntl_counter); + assert(fd > 0); +// printf("================\n"); + neco_cancel(neco_getid()); + assert(neco_write(fd, "hello", 5) == -1 && errno == ECANCELED); + assert(neco_write_dl(fd, "hello", 5, 1) == -1 && errno == ETIMEDOUT); + + neco_fail_write_counter = 1; + neco_fail_write_error = EBADF; + assert(neco_write(fd, "hello", 5) == -1 && errno == EBADF); + + // This will succeed even though a various errors occure because neco + // reschedules reads and writes on some common errors. + neco_fail_write_counter = 1; + neco_fail_write_error = EAGAIN; + neco_fail_cowait = true; + int ret = neco_write(fd, "hello", 5); + + assert(ret == 5); + neco_waitgroup_wait(&wg); + close(fd); + close(sock); +} + +void test_net_write_errors(void) { + assert(neco_write(0, 0, 0) == -1 && errno == EPERM); + assert(neco_accept(0, 0, 0) == -1 && errno == EPERM); + expect(neco_start(co_net_write_errors, 0), NECO_OK); +} + +void co_net_connect_errors(int argc, void *argv[]) { + (void)argc; (void)argv; + assert(neco_connect(99283, 0, 0) == -1); + assert(neco_connect_dl(99283, 0, 0, 1) == -1 && errno == ETIMEDOUT); + expect(neco_cancel(neco_getid()), NECO_OK); + assert(neco_connect_dl(99283, 0, 0, 1) == -1 && errno == ECANCELED); + + neco_fail_connect_counter = 1; + neco_fail_connect_error = EAGAIN; + assert(neco_connect(99283, 0, 0) == -1 && errno == ECONNREFUSED); + + neco_fail_connect_counter = 1; + neco_fail_connect_error = EISCONN; + assert(neco_connect(99283, 0, 0) == -1 && errno == EISCONN); + + neco_fail_connect_counter = 1; + neco_fail_connect_error = EINTR; + assert(neco_connect(99283, 0, 0) == -1); + +} + +void test_net_connect_errors(void) { + assert(neco_connect_dl(0, 0, 0, 0) == -1 && errno == EPERM); + expect(neco_start(co_net_connect_errors, 0), NECO_OK); +} + +void test_net_setnonblock(void) { + int fds[2]; + assert(pipe(fds) == 0); + bool prev; + expect(neco_setnonblock(fds[0], 1, &prev), NECO_OK); + assert(!prev); + expect(neco_setnonblock(fds[0], 1, &prev), NECO_OK); + assert(prev); + expect(neco_setnonblock(fds[0], 0, &prev), NECO_OK); + assert(prev); + expect(neco_setnonblock(fds[0], 0, &prev), NECO_OK); + assert(!prev); + close(fds[0]); + close(fds[1]); +} + +int main(int argc, char **argv) { + do_test(test_net_unix); + do_test(test_net_tcp_auto); + do_test(test_net_tcp_mix40); + do_test(test_net_tcp_mix60); + do_test(test_net_tcp_mix44); + do_test(test_net_tcp_mix66); + do_test(test_net_dial); + do_test(test_net_dial_fail); + do_test(test_net_tcp_emptyhost); + do_test(test_net_cancel); + do_test(test_net_getaddrinfo); + do_test(test_net_getaddrinfo_fail); + do_test(test_net_serve_fail); + do_test(test_net_autoclose_queue); + do_test(test_net_write_errors); + do_test(test_net_connect_errors); + do_test(test_net_setnonblock); +} +#endif diff --git a/tests/test_panic.c b/tests/test_panic.c new file mode 100644 index 0000000..9e5a14e --- /dev/null +++ b/tests/test_panic.c @@ -0,0 +1,32 @@ +#include "tests.h" + +extern void *neco_malloc(size_t nbytes); +extern void *neco_realloc(void *ptr, size_t nbytes); + +void co_test_panic(int argc, void *argv[]) { + (void)argc; (void)argv; + neco_last_panic = false; + neco_env_setpaniconerror(true); + + void *ptr = neco_malloc(SIZE_MAX); + assert(!ptr && neco_last_panic); + neco_last_panic = false; + neco_env_setpaniconerror(true); + ptr = neco_realloc(0, SIZE_MAX); + assert(!ptr && neco_last_panic); + + neco_env_setpaniconerror(false); + neco_last_panic = false; + + neco_panic("hello"); + assert(neco_last_panic); + neco_last_panic = false; +} + +void test_panic(void) { + expect(neco_start(co_test_panic, 0), NECO_OK); +} + +int main(int argc, char **argv) { + do_test(test_panic); +} diff --git a/tests/test_pipe.c b/tests/test_pipe.c new file mode 100644 index 0000000..a31f321 --- /dev/null +++ b/tests/test_pipe.c @@ -0,0 +1,63 @@ +#include "tests.h" + +#if defined(_WIN32) +DISABLED("test_pipe", "Windows") +#elif defined(__EMSCRIPTEN__) +DISABLED("test_pipe", "Emscripten") +#else + +#include + +void *th0_entry(void *arg) { + usleep(10000); + int fd = *(int*)arg; + assert(fd > 0); + assert(neco_setnonblock(fd, false, 0) == 0); + char data[64]; + assert(write(fd, "+PING\r\n", 7) == 7); + assert(read(fd, data, sizeof(data)) == 7); + assert(memcmp(data, "+PONG\r\n", 7) == 0); + close(fd); + return 0; +} + +void *th1_entry(void *arg) { + int fd = *(int*)arg; + assert(fd > 0); + assert(neco_setnonblock(fd, false, 0) == 0); + char data[64]; + assert(read(fd, data, sizeof(data)) == 7); + assert(memcmp(data, "+PING\r\n", 7) == 0); + assert(write(fd, "+PONG\r\n", 7) == 7); + close(fd); + return 0; +} + +void co_pipe(int argc, void *argv[]) { + (void)argc; (void)argv; + int fds[2]; + int r = neco_pipe(fds); + if (r == -1) { + perror("neco_pipe"); + } + expect(neco_pipe(fds), NECO_OK); + pthread_t th0, th1; + assert(pthread_create(&th0, 0, th0_entry, &fds[0]) == 0); + assert(pthread_create(&th1, 0, th1_entry, &fds[1]) == 0); + assert(pthread_join(th0, 0) == 0); + assert(pthread_join(th1, 0) == 0); +} + +void test_pipe(void) { + assert(neco_pipe(0) == -1 && errno == EINVAL); + int fds[2]; + assert(neco_pipe(fds) == -1 && errno == EPERM); + expect(neco_start(co_pipe, 0), NECO_OK); +} + + +int main(int argc, char **argv) { + do_test(test_pipe); +} + +#endif diff --git a/tests/test_rand.c b/tests/test_rand.c new file mode 100644 index 0000000..d968d5f --- /dev/null +++ b/tests/test_rand.c @@ -0,0 +1,73 @@ +#include +#include "tests.h" + +double entropy_calc(const char *data, size_t nbytes) { + uint64_t possible_bytes[256] = { 0 }; + for (size_t i = 0; i < nbytes; i++) { + unsigned char byte_idx = data[i]; + possible_bytes[byte_idx]++; + } + double entropy = 0; + for (size_t i = 0; i < 256; i++) { + uint64_t count = possible_bytes[i]; + if (count) { + double prob = (double)count / (double)nbytes; + entropy -= prob * log2(prob); + } + } + return entropy; +} + +void co_rand(int argc, void *argv[]) { + (void)argc; (void)argv; + int bufsz = 1003; + char *buf = malloc(bufsz); + assert(buf); + memset(buf, 0x11, bufsz); + assert(entropy_calc(buf, bufsz) < 0.0001); + expect(neco_rand(buf, bufsz, NECO_CSPRNG), NECO_OK); + assert(entropy_calc(buf, bufsz) > 7.5); + expect(neco_rand(buf, bufsz, NECO_CSPRNG), NECO_OK); + assert(entropy_calc(buf, bufsz) > 7.5); + + expect(neco_rand_setseed(9876, 0), NECO_OK); + expect(neco_rand(buf, bufsz, NECO_PRNG), NECO_OK); + double e1 = entropy_calc(buf, bufsz); + assert(e1 > 7.5); + + int64_t oldseed; + expect(neco_rand_setseed(4123, &oldseed), NECO_OK); + expect(neco_rand_setseed(oldseed, 0), NECO_OK); + expect(neco_rand(buf, bufsz, NECO_PRNG), NECO_OK); + double e2 = entropy_calc(buf, bufsz); + assert(e2 > 7.5 && e2 != e1); + + + + + + expect(neco_rand_setseed(oldseed, 0), NECO_OK); + expect(neco_rand(buf, bufsz, NECO_PRNG), NECO_OK); + double e3 = entropy_calc(buf, bufsz); + assert(e3 == e2); + + + + expect(neco_rand(0, 0, NECO_PRNG), NECO_OK); + expect(neco_cancel(neco_getid()), NECO_OK); + expect(neco_rand(0, 0, NECO_PRNG), NECO_CANCELED); + expect(neco_rand_dl(0, 0, NECO_PRNG, 1), NECO_TIMEDOUT); + + free(buf); +} + +void test_rand(void) { + expect(neco_rand(0, 0, -1), NECO_INVAL); + expect(neco_rand(0, 0, 0), NECO_PERM); + expect(neco_rand_setseed(0,0), NECO_PERM); + expect(neco_start(co_rand, 0), NECO_OK); +} + +int main(int argc, char **argv) { + do_test(test_rand); +} diff --git a/tests/test_signal.c b/tests/test_signal.c new file mode 100644 index 0000000..18e22fc --- /dev/null +++ b/tests/test_signal.c @@ -0,0 +1,222 @@ +#include "tests.h" + +#if defined(_WIN32) +DISABLED("test_signal", "Windows") +#elif defined(__EMSCRIPTEN__) +DISABLED("test_signal", "Emscripten") +#else + +#include +#include + +extern __thread int neco_last_sigexitnow; +void neco_reset_sigcrashed(void); +const char *strsignal0(int signo); + +void co_signal_basic_child(int argc, void *argv[]) { + assert(argc == 2); + int sigexpect = *(int*)(argv[0]); + struct neco_chan *ch = argv[1]; + // printf("co_signal: %d (%p)\n", sigexpect, neco_stack_bottom()); + neco_signal_watch(SIGUSR1); + neco_signal_watch(SIGUSR2); + while (1) { + int signo = neco_signal_wait(); + // printf(" SIGNAL %d (%p)\n", signo, neco_stack_bottom()); + if (signo == sigexpect) { + // printf(" ACCEPT (%p)\n", neco_stack_bottom()); + assert(neco_chan_send(ch, &signo) == NECO_OK); + break; + } else { + printf("???\n"); + assert(0); + } + } + neco_signal_unwatch(SIGUSR1); + neco_signal_unwatch(SIGUSR2); + // printf("co_signal: %d DONE\n", sigexpect); +} + +void co_signal_basic(int argc, void *argv[]) { + (void)argc; (void)argv; + // printf("co_signal\n"); + struct neco_chan *chsig; + assert(neco_chan_make(&chsig, sizeof(int), 0) == NECO_OK); + assert(chsig); + int sigex0 = SIGUSR1; + assert(neco_start(co_signal_basic_child, 2, &sigex0, chsig) == NECO_OK); + neco_yield(); + int sigex1 = SIGUSR2; + assert(neco_start(co_signal_basic_child, 2, &sigex1, chsig) == NECO_OK); + neco_yield(); + // printf("waiting on signal... (pid: %d)\n", getpid()); + raise(SIGUSR1); + raise(SIGUSR2); + raise(SIGUSR2); + int i = 0; + int j = 0; + while (1) { + int signo; + assert(neco_chan_recv(chsig, &signo) == NECO_OK); + i++; + j += signo; + if (j == SIGUSR1+SIGUSR2) { + assert(i == 2); + break; + } + } + assert(neco_chan_close(chsig) == NECO_OK); + assert(neco_chan_release(chsig) == NECO_OK); + + // Test out of range signal. + // neco_last_sigexitnow = 0; + // raise(33); + // neco_yield(); + // assert(neco_last_sigexitnow == 0); + + expect(neco_signal_watch(0), NECO_INVAL); + expect(neco_signal_unwatch(0), NECO_INVAL); + expect(neco_signal_watch(32), NECO_INVAL); + expect(neco_signal_unwatch(32), NECO_INVAL); + expect(neco_signal_wait(), NECO_NOSIGWATCH); + + expect(neco_signal_watch(SIGINT), NECO_OK); + expect(neco_cancel(neco_getid()), NECO_OK); + expect(neco_signal_wait(), NECO_CANCELED); + expect(neco_signal_unwatch(SIGINT), NECO_OK); + + +} + +void test_signal_basic(void) { + assert(strcmp(strsignal0(-1), "Unknown signal: -1") == 0); + assert(strcmp(strsignal0(33), "Unknown signal: 33") == 0); + assert(strlen(strsignal0(1)) > 0); + assert(neco_start(co_signal_basic, 0) == NECO_OK); + expect(neco_signal_watch(SIGINT), NECO_PERM); + expect(neco_signal_unwatch(SIGINT), NECO_PERM); + expect(neco_signal_wait(), NECO_PERM); +} + +void co_signal_deadline_canceler(int argc, void *argv[]) { + assert(argc == 1); + int64_t id = *(int64_t*)argv[0]; + expect(neco_yield(), NECO_OK); + raise(SIGUSR1); + raise(SIGUSR1); + expect(neco_sleep(NECO_SECOND/2), NECO_OK); + expect(neco_cancel(id), NECO_OK); +} + +void co_signal_deadline(int argc, void *argv[]) { + (void)argc; (void)argv; + int64_t id = neco_getid(); + expect(neco_start(co_signal_deadline_canceler, 1, &id), NECO_OK); + expect(neco_signal_watch(SIGUSR1), NECO_OK); + expect(neco_signal_wait_dl(neco_now() + NECO_SECOND/8), SIGUSR1); + expect(neco_signal_wait(), SIGUSR1); + expect(neco_signal_wait_dl(neco_now() + NECO_SECOND/8), NECO_TIMEDOUT); + expect(neco_signal_wait_dl(neco_now() + NECO_SECOND*2), NECO_CANCELED); + expect(neco_signal_unwatch(SIGUSR1), NECO_OK); +} + +void test_signal_deadline(void) { + expect(neco_start(co_signal_deadline, 0), NECO_OK); +} + +void co_signal_exit0(int argc, void *argv[]) { + (void)argc; (void)argv; + neco_sleep(NECO_SECOND/4); +} + +void co_signal_watch_no_wait(int argc, void *argv[]) { + (void)argc; (void)argv; + expect(neco_signal_watch(SIGUSR2), NECO_OK); + neco_sleep(NECO_SECOND/10); + expect(neco_signal_unwatch(SIGUSR2), NECO_OK); +} + + +void co_signal_exit(int argc, void *argv[]) { + (void)argc; (void)argv; + assert(neco_is_main_thread() == true); + // Simulates an exit on SIGINT and SIGUSR1 + + neco_last_sigexitnow = 0; + raise(SIGILL); + neco_yield(); + assert(neco_last_sigexitnow == SIGILL); + neco_reset_sigcrashed(); + + neco_last_sigexitnow = 0; + raise(SIGTERM); + neco_yield(); + assert(neco_last_sigexitnow == SIGTERM); + neco_reset_sigcrashed(); + + + neco_last_sigexitnow = 0; + raise(SIGUSR1); + neco_yield(); + assert(neco_last_sigexitnow == SIGUSR1); + neco_reset_sigcrashed(); + + + neco_last_sigexitnow = 0; + raise(SIGFPE); + neco_yield(); + assert(neco_last_sigexitnow == SIGFPE); + + // Without a reset, the last crash (SIGILL) will continue forward. + + neco_last_sigexitnow = 0; + raise(SIGPIPE); + neco_yield(); + assert(neco_last_sigexitnow == SIGFPE); + neco_reset_sigcrashed(); + + // Finally ensure that a watched signal eventually crashes when a child + // unwatches without waiting. + expect(neco_start(co_signal_watch_no_wait, 0), NECO_OK); + // printf("===============================\n"); + neco_last_sigexitnow = 0; + // printf("E\n"); + raise(SIGUSR2); + neco_sleep(NECO_SECOND/5); + assert(neco_last_sigexitnow == SIGUSR2); + // printf("===============================\n"); + + // printf("2\n"); +} + +void test_signal_exit(void) { + expect(neco_start(co_signal_exit, 0), NECO_OK); +} + +void co_signal_exit_thread(int argc, void *argv[]) { + (void)argc; (void)argv; + assert(neco_is_main_thread() == false); + expect(neco_signal_watch(SIGINT), NECO_PERM); + expect(neco_signal_unwatch(SIGINT), NECO_PERM); +} + +void *th_signal(void *arg) { + (void)arg; + expect(neco_start(co_signal_exit_thread, 0), NECO_OK); + return NULL; +} + +void test_signal_thread(void) { + pthread_t th; + assert(pthread_create(&th, 0, th_signal, 0) == 0); + assert(pthread_join(th, 0) == 0); +} + +int main(int argc, char **argv) { + do_test(test_signal_basic); + do_test(test_signal_deadline); + do_test(test_signal_exit); + do_test(test_signal_thread); +} + +#endif diff --git a/tests/test_sleep.c b/tests/test_sleep.c new file mode 100644 index 0000000..0950637 --- /dev/null +++ b/tests/test_sleep.c @@ -0,0 +1,59 @@ +#include +#include "tests.h" + +void co_sleep_cancel_child(int argc, void *argv[]) { + assert(argc == 1); + int64_t id = *(int64_t*)argv[0]; + expect(neco_cancel(-1111), NECO_NOTFOUND); + expect(neco_cancel(id), NECO_OK); + expect(neco_sleep(NECO_SECOND/4), NECO_OK); + expect(neco_cancel(id), NECO_OK); +} + +void co_sleep_cancel(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + int64_t id = neco_getid(); + assert(id > 0); + expect(neco_start(co_sleep_cancel_child, 1, &id), NECO_OK); + expect(neco_sleep(NECO_SECOND*10), NECO_CANCELED); + expect(neco_sleep(NECO_SECOND/10), NECO_OK); + expect(neco_sleep(NECO_SECOND*10), NECO_CANCELED); +} + +void test_sleep_cancel(void) { + expect(neco_cancel(0), NECO_PERM); + expect(neco_start(co_sleep_cancel, 0), NECO_OK); +} + + +void co_sleep_basic_child(int argc, void *argv[]) { + assert(argc == 1); + int *x = argv[0]; + (*x)++; + assert(neco_sleep(1e9/10) == NECO_OK); +} + +void co_sleep_basic(int argc, void *argv[]) { + assert(argc == 2); + int *x = argv[0]; + int *n = argv[1]; + for (int i = 0; i < *n; i++) { + assert(neco_start(co_sleep_basic_child, 1, x) == NECO_OK); + } +} + +void test_sleep_basic(void) { + int x = 0; + int n = 20; + double start = now(); + assert(neco_start(co_sleep_basic, 2, &x, &n) == NECO_OK); + assert(now() - start > 0.10); + assert(x == n); +} + + +int main(int argc, char **argv) { + do_test(test_sleep_basic); + do_test(test_sleep_cancel); +} diff --git a/tests/test_stream.c b/tests/test_stream.c new file mode 100644 index 0000000..d34da9a --- /dev/null +++ b/tests/test_stream.c @@ -0,0 +1,414 @@ +#include +#include "tests.h" + +#if defined(_WIN32) +DISABLED("test_stream", "Windows") +#elif defined(__EMSCRIPTEN__) +DISABLED("test_stream", "Emscripten") +#else + +void co_stream_other(int argc, void *argv[]) { + assert(argc == 2); + int fd = *(int*)argv[0]; + size_t bufsz = *(size_t*)argv[1]; + + neco_stream *buf; + expect(neco_stream_make_buffered_size(&buf, fd, bufsz), NECO_OK); + + assert(neco_stream_write(buf, "hello1", 6) == 6); + expect(neco_stream_flush(buf), NECO_OK); + expect(neco_stream_flush(buf), NECO_OK); + + char data[6]; + assert(neco_stream_read(buf, data, sizeof(data)) == 6); + assert(memcmp(data, "jello1", 6) == 0); + + assert(neco_stream_write(buf, "jello2", 6) == 6); + expect(neco_stream_flush(buf), NECO_OK); + + + neco_stream_release(buf); + + close(fd); +} + +void co_stream(int argc, void *argv[]) { + assert(argc == 1); + size_t bufsz = *(size_t*)argv[0]; + + int fds[2]; + expect(neco_pipe(fds), NECO_OK); + int fd = fds[0]; + expect(neco_start(co_stream_other, 2, &fds[1], &bufsz), NECO_OK); + + + neco_stream *buf; + neco_fail_neco_malloc_counter = 1; + expect(neco_stream_make_buffered(&buf, fd), NECO_NOMEM); + + expect(neco_stream_make_buffered_size(&buf, fd, bufsz), NECO_OK); + + char data[6]; + + assert(neco_stream_read(buf, data, 0) == 0); + + assert(neco_stream_read(buf, data, sizeof(data)) == 6); + assert(memcmp(data, "hello1", 6) == 0); + + assert(neco_stream_buffered_read_size(buf) == 0); + expect(neco_stream_unread_byte(buf), NECO_OK); // 1 + assert(neco_stream_buffered_read_size(buf) == 1); + expect(neco_stream_unread_byte(buf), NECO_OK); // o + assert(neco_stream_buffered_read_size(buf) == 2); + expect(neco_stream_unread_byte(buf), NECO_OK); // l + assert(neco_stream_buffered_read_size(buf) == 3); + expect(neco_stream_unread_byte(buf), NECO_OK); // l + assert(neco_stream_buffered_read_size(buf) == 4); + expect(neco_stream_unread_byte(buf), NECO_OK); // e + assert(neco_stream_buffered_read_size(buf) == 5); + expect(neco_stream_unread_byte(buf), NECO_OK); // h + assert(neco_stream_buffered_read_size(buf) == 6); + expect(neco_stream_unread_byte(buf), NECO_UNREADFAIL); + + // close(fd); + + assert(neco_stream_read_byte(buf) == 'h'); + assert(neco_stream_read_byte(buf) == 'e'); + assert(neco_stream_read_byte(buf) == 'l'); + assert(neco_stream_read_byte(buf) == 'l'); + assert(neco_stream_read_byte(buf) == 'o'); + assert(neco_stream_read_byte(buf) == '1'); + + assert(neco_stream_write(buf, "", 0) == 0); + assert(neco_stream_write(buf, "jello1", 6) == 6); + assert(neco_stream_flush(buf) == NECO_OK); + + assert(neco_stream_read_byte(buf) == 'j'); + assert(neco_stream_read_byte(buf) == 'e'); + assert(neco_stream_read_byte(buf) == 'l'); + assert(neco_stream_read_byte(buf) == 'l'); + assert(neco_stream_read_byte(buf) == 'o'); + assert(neco_stream_read_byte(buf) == '2'); + + close(fd); + + int ret = neco_stream_read_byte(buf); + assert(ret == NECO_ERROR && errno == EBADF); + + ret = neco_stream_read_byte(buf); + assert(ret == NECO_ERROR && errno == EBADF); + + ret = neco_stream_read_byte(buf); + assert(ret == NECO_ERROR && errno == EBADF); + + ret = neco_stream_read(buf, data, 0); + assert(ret == NECO_ERROR && errno == EBADF); + + + ret = neco_stream_write(buf, "bad", 3); + assert(ret == 3); + assert(neco_stream_buffered_write_size(buf) == 3); + ret = neco_stream_flush(buf); + assert(ret == NECO_ERROR && errno == EBADF); + ret = neco_stream_flush(buf); + assert(ret == NECO_ERROR && errno == EBADF); + + + + + + + // printf("%d\n", neco_stream_unread_byte(buf)); + + // assert(neco_stream_unread_byte(buf) == '1'); + // assert(neco_stream_unread_byte(buf) == 'o'); + // assert(neco_stream_unread_byte(buf) == 'l'); + // assert(neco_stream_unread_byte(buf) == 'l'); + // assert(neco_stream_unread_byte(buf) == 'e'); + // assert(neco_stream_unread_byte(buf) == 'h'); + + + + neco_stream_release(buf); + + +} + +void test_stream_basic(void) { + + // Test for basic errors + expect(neco_stream_make_buffered_size(0, 1, 16), NECO_INVAL); + expect(neco_stream_make_buffered_size((void*)16, 1, 16), NECO_PERM); + expect(neco_stream_make_buffered_size((void*)16, -1, 16), NECO_INVAL); + expect(neco_stream_make_buffered_size(0, 1, 16), NECO_INVAL); + expect(neco_stream_make_buffered_size((void*)16, 1, 16), NECO_PERM); + expect(neco_stream_make_buffered_size((void*)16, -1, 16), NECO_INVAL); + expect(neco_stream_flush(0), NECO_INVAL); + expect(neco_stream_flush((void*)1), NECO_PERM); + expect(neco_stream_read_byte(0), NECO_INVAL); + expect(neco_stream_read_byte((void*)1), NECO_PERM); + expect(neco_stream_unread_byte(0), NECO_INVAL); + expect(neco_stream_unread_byte((void*)1), NECO_PERM); + expect(neco_stream_buffered_read_size(0), NECO_INVAL); + expect(neco_stream_buffered_read_size((void*)1), NECO_PERM); + expect(neco_stream_release(0), NECO_INVAL); + expect(neco_stream_release((void*)1), NECO_PERM); + expect(neco_stream_buffered_write_size(0), NECO_INVAL); + expect(neco_stream_buffered_write_size((void*)1), NECO_PERM); + expect(neco_stream_read(0, 0, 0), NECO_INVAL); + expect(neco_stream_read((void*)1, 0, 0), NECO_PERM); + expect(neco_stream_write(0, 0, 0), NECO_INVAL); + expect(neco_stream_write((void*)1, 0, 0), NECO_PERM); + + // Coroutine tests + expect(neco_start(co_stream, 1, &(size_t){0}), NECO_OK); + expect(neco_start(co_stream, 1, &(size_t){16}), NECO_OK); +} + +void co_stream_big_other(int argc, void *argv[]) { + assert(argc == 3); + int fd = *(int*)argv[0]; + int N = *(int*)argv[1]; + char *rdata0 = argv[2]; + char *rdata = xmalloc(N); + assert(rdata); + neco_stream *buf; + expect(neco_stream_make_buffered(&buf, fd), NECO_OK); + ssize_t n = neco_stream_readfull(buf, rdata, N); + assert(n == N); + assert(memcmp(rdata, rdata0, N) == 0); + ssize_t t = neco_stream_buffered_read_size(buf); + neco_stream_release(buf); + while (1) { + n = neco_read(fd, rdata, N); + if (n <= 0) { + break; + } + t += n; + } + assert(t == N); + close(fd); + xfree(rdata); +} + +void co_stream_big(int argc, void *argv[]) { + (void)argc; (void)argv; + + int N = 123456; + char *rdata = xmalloc(N); + assert(rdata); + expect(neco_rand(rdata, N, 0), NECO_OK); + + int fds[2]; + expect(neco_pipe(fds), NECO_OK); + int fd = fds[0]; + neco_stream *buf; + expect(neco_stream_make_buffered(&buf, fd), NECO_OK); + expect(neco_start(co_stream_big_other, 3, &fds[1], &N, rdata), NECO_OK); + int64_t other_id = neco_lastid(); + ssize_t n = neco_stream_write(buf, rdata, N); + assert(n == N); + expect(neco_stream_flush(buf), NECO_OK); + neco_stream_release(buf); + n = neco_write(fd, rdata, N); + assert(n == N); + close(fd); + neco_join(other_id); + xfree(rdata); +} + +void test_stream_big(void) { + expect(neco_start(co_stream_big, 0), NECO_OK); +} + +void co_stream_partial_other(int argc, void *argv[]) { + assert(argc == 3); + int fd = *(int*)argv[0]; + int N = *(int*)argv[1]; + // char *rdata0 = argv[2]; + char *rdata = xmalloc(N); + assert(rdata); + // neco_stream *buf; + // expect(neco_stream_make_buffered(&buf, fd), NECO_OK); + // ssize_t n = neco_stream_readfull(buf, rdata, N); + // assert(n == N); + // assert(memcmp(rdata, rdata0, N) == 0); + // ssize_t t = neco_stream_buffered_read_size(buf); + while (1) { + ssize_t n = neco_read(fd, rdata, N); + if (n <= 0) { + break; + } + } + + + neco_stream *buf; + expect(neco_stream_make_buffered(&buf, fd), NECO_OK); + ssize_t n = neco_stream_readfull(buf, rdata, N); + assert(n == NECO_EOF); // buffer closed + neco_stream_release(buf); + + + + // assert(t == N); + close(fd); + xfree(rdata); +} + +void co_stream_partial_write(int argc, void *argv[]) { + (void)argc; (void)argv; + + int N = 123456; + char *rdata = xmalloc(N); + assert(rdata); + expect(neco_rand(rdata, N, 0), NECO_OK); + + + int fds[2]; + expect(neco_pipe(fds), NECO_OK); + int fd = fds[0]; + expect(neco_start(co_stream_partial_other, 3, &fds[1], &N, rdata), NECO_OK); + int64_t other_id = neco_lastid(); + + neco_partial_write = 2; + ssize_t n = neco_write(fd, rdata, N); + assert(n < N); + assert(neco_lasterr() == NECO_PARTIALWRITE); + + // assert(n == N); + + neco_stream *buf; + expect(neco_stream_make_buffered(&buf, fd), NECO_OK); + expect(neco_stream_write(buf, "hello", 5), 5); + neco_fail_neco_malloc_counter = 1; + expect(neco_stream_read(buf, rdata, N), NECO_NOMEM); + neco_stream_release(buf); + + + expect(neco_stream_make_buffered(&buf, fd), NECO_OK); + expect(neco_stream_read_dl(buf, rdata, N, 1), NECO_TIMEDOUT); + neco_fail_neco_malloc_counter = 1; + expect(neco_stream_write(buf, rdata, N), NECO_NOMEM); + neco_stream_release(buf); + + + + + expect(neco_stream_make_buffered(&buf, fd), NECO_OK); + expect(neco_stream_write(buf, rdata, 500), 500); + neco_partial_write = 2; + int ret = neco_stream_flush(buf); + assert(ret == NECO_PARTIALWRITE); + neco_stream_release(buf); + + + expect(neco_stream_make_buffered(&buf, fd), NECO_OK); + expect(neco_stream_write(buf, rdata, 4096), 4096); + neco_partial_write = 1; + ret = neco_stream_write(buf, "1", 1); // this forces an error + assert(ret == -1 && errno == EIO); + neco_stream_release(buf); + + + + // neco_partial_write = 2; + // n = neco_stream_write(buf, rdata, N); + // assert(n < N); + // int ret = neco_stream_flush(buf); + // printf("%d\n", ret); + // assert(neco_lasterr() == NECO_PARTIALWRITE); + + + close(fd); + neco_join(other_id); + xfree(rdata); +} + +void test_stream_partial_write(void) { + expect(neco_start(co_stream_partial_write, 0), NECO_OK); +} + +void co_stream_nonbuffered_client(int argc, void *argv[]) { + (void)argc; (void)argv; + int fd = neco_dial("tcp", "127.0.0.1:9182"); + assert(fd > 0); + neco_stream *stream; + expect(neco_stream_make(&stream, fd), NECO_OK); + + assert(neco_stream_write(stream, "hello\n", 6) == 6); + assert(neco_stream_buffered_write_size(stream) == 0); + + expect(neco_stream_close(stream), NECO_OK); +} + +void co_stream_nonbuffered(int argc, void *argv[]) { + (void)argc; (void)argv; + int servefd = neco_serve("tcp", "127.0.0.1:9182"); + assert(servefd > 0); + expect(neco_start(co_stream_nonbuffered_client, 0), NECO_OK); + int fd = neco_accept(servefd, 0, 0); + assert(fd > 0); + neco_stream *stream; + expect(neco_stream_make(&stream, fd), NECO_OK); + + char buf[100]; + assert(neco_stream_read(stream, buf, sizeof(buf)) == 6); + assert(memcmp(buf, "hello\n", 6) == 0); + assert(neco_stream_buffered_read_size(stream) == 0); + + expect(neco_stream_flush(stream), NECO_OK); + expect(neco_stream_close(stream), NECO_OK); + close(servefd); +} + +void test_stream_nonbuffered(void) { + expect(neco_stream_close(0), NECO_INVAL); + expect(neco_stream_close((void*)1), NECO_PERM); + expect(neco_start(co_stream_nonbuffered, 0), NECO_OK); +} + +void co_stream_buffered_client(int argc, void *argv[]) { + (void)argc; (void)argv; + int fd = neco_dial("tcp", "127.0.0.1:9182"); + assert(fd > 0); + neco_stream *stream; + expect(neco_stream_make_buffered(&stream, fd), NECO_OK); + + assert(neco_stream_write(stream, "hello\n", 6) == 6); + assert(neco_stream_buffered_write_size(stream) == 6); + + expect(neco_stream_close(stream), NECO_OK); +} + +void co_stream_buffered(int argc, void *argv[]) { + (void)argc; (void)argv; + int servefd = neco_serve("tcp", "127.0.0.1:9182"); + assert(servefd > 0); + expect(neco_start(co_stream_buffered_client, 0), NECO_OK); + int fd = neco_accept(servefd, 0, 0); + assert(fd > 0); + neco_stream *stream; + expect(neco_stream_make_buffered(&stream, fd), NECO_OK); + + char buf[100]; + assert(neco_stream_read(stream, buf, sizeof(buf)) == 6); + assert(memcmp(buf, "hello\n", 6) == 0); + assert(neco_stream_buffered_read_size(stream) == 0); + + expect(neco_stream_close(stream), NECO_OK); + close(servefd); +} + + +void test_stream_buffered(void) { + expect(neco_start(co_stream_buffered, 0), NECO_OK); +} + +int main(int argc, char **argv) { + do_test(test_stream_basic); + do_test(test_stream_big); + do_test(test_stream_partial_write); + do_test(test_stream_buffered); + do_test(test_stream_nonbuffered); +} +#endif diff --git a/tests/test_suspend.c b/tests/test_suspend.c new file mode 100644 index 0000000..29f72d8 --- /dev/null +++ b/tests/test_suspend.c @@ -0,0 +1,57 @@ +#include "tests.h" + +int nvals = 0; +static char vals[100] = { 0 }; + +void co_suspend_child(int argc, void *argv[]) { + (void)argc; (void)argv; + vals[nvals++] = 'B'; + expect(neco_suspend(), NECO_OK); + vals[nvals++] = 'D'; + expect(neco_yield(), NECO_OK); + vals[nvals++] = 'F'; + expect(neco_resume(neco_starterid()), NECO_OK); + vals[nvals++] = 'H'; +} + + +void co_suspend(int argc, void *argv[]) { + (void)argc; (void)argv; + vals[nvals++] = 'A'; + expect(neco_start(co_suspend_child, 0), NECO_OK); + vals[nvals++] = 'C'; + expect(neco_resume(neco_lastid()), NECO_OK); + vals[nvals++] = 'E'; + expect(neco_suspend(), NECO_OK); + vals[nvals++] = 'G'; + + expect(neco_resume(neco_lastid()), NECO_NOTSUSPENDED); + expect(neco_resume(-1), NECO_NOTFOUND); +} + +void test_suspend(void) { + expect(neco_suspend(), NECO_PERM); + expect(neco_resume(0), NECO_PERM); + expect(neco_start(co_suspend, 0), NECO_OK); + bool ok = true; + char c = 'A'; + for (int i = 0 ; i < nvals; i++, c++) { + if (vals[i] != c) { + ok = false; + break; + } + } + if (!ok) { + printf("Out of order: ["); + for (int i = 0 ; i < nvals; i++) { + printf("%c ", vals[i]); + } + printf("]\n"); + assert(ok); + } +} + + +int main(int argc, char **argv) { + do_test(test_suspend); +} diff --git a/tests/test_sync.c b/tests/test_sync.c new file mode 100644 index 0000000..8aa7436 --- /dev/null +++ b/tests/test_sync.c @@ -0,0 +1,589 @@ +#include +#include "tests.h" + +int _neco_mutex_stats(neco_mutex *mutex); + +void co_sync_mutex_child(int argc, void *argv[]) { + assert(argc == 4); + int i = *(int*)argv[0]; + neco_mutex *mu = argv[1]; + neco_chan *ch = argv[2]; + assert(neco_chan_retain(ch) == NECO_OK); + int *x = argv[3]; + switch (i) { + case 0: + neco_sleep(NECO_MILLISECOND * 500); + break; + case 1: + neco_sleep(NECO_MILLISECOND * 200); + break; + case 2: + neco_sleep(NECO_MILLISECOND * 300); + break; + case 3: + neco_sleep(NECO_MILLISECOND * 100); + break; + case 4: + neco_sleep(NECO_MILLISECOND * 400); + break; + } + assert(neco_mutex_lock(mu) == NECO_OK); + int px; + switch (i) { + case 0: + assert(*x == 455069); + px = 233912; + break; + case 1: + assert(*x == 817263); + px = 712934; + break; + case 2: + assert(*x == 712934); + px = 102993; + break; + case 3: + assert(*x == 918273); + px = 817263; + break; + case 4: + assert(*x == 102993); + px = 455069; + break; + } + *x = 0; + neco_sleep(NECO_MILLISECOND); + assert(*x == 0); + *x = px; + assert(neco_mutex_unlock(mu) == NECO_OK); + assert(neco_chan_send(ch, 0) == NECO_OK); + assert(neco_chan_release(ch) == NECO_OK); +} + +void co_sync_mutex(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + int x = 881772; + neco_mutex mu; + assert(neco_mutex_init(&mu) == NECO_OK); + + neco_chan *ch; + assert(neco_chan_make(&ch, 0, 0) == NECO_OK); + for (int i = 0; i < 5; i++) { + assert(neco_start(co_sync_mutex_child, 4, &i, &mu, ch, &x) == NECO_OK); + } + assert(neco_mutex_lock(&mu) == NECO_OK); + assert(x == 881772); + neco_sleep(NECO_MILLISECOND * 600); + x = 918273; + assert(neco_mutex_unlock(&mu) == NECO_OK); + // joiner + for (int i = 0; i < 5; i++) { + assert(neco_chan_recv(ch, 0) == NECO_OK); + } + assert(neco_mutex_lock(&mu) == NECO_OK); + assert(x == 233912); + expect(neco_mutex_unlock(&mu), NECO_OK); + expect(neco_mutex_unlock(&mu), NECO_OK); + expect(neco_chan_release(ch), NECO_OK); +} + +void test_sync_mutex(void) { + assert(neco_start(co_sync_mutex, 0) == NECO_OK); +} + +void co_sync_waitgroup_child(int argc, void *argv[]) { + assert(argc == 2); + neco_waitgroup *wg = argv[0]; + int *x = argv[1]; + *x = *x + 1; + neco_sleep(NECO_MILLISECOND*(*x)); + expect(neco_waitgroup_done(wg), NECO_OK); +} + +void co_sync_waitgroup_multi_waiter(int argc, void *argv[]) { + assert(argc == 1); + neco_waitgroup *wg = argv[0]; + expect(neco_waitgroup_wait(wg), NECO_OK); +} + +void co_sync_waitgroup_multi_done(int argc, void *argv[]) { + assert(argc == 1); + neco_waitgroup *wg = argv[0]; + expect(neco_sleep(NECO_SECOND), NECO_OK); + expect(neco_waitgroup_done(wg), NECO_OK); +} + +void co_sync_waitgroup(int argc, void *argv[]) { + assert(argc == 1); + bool multi = *(bool*)argv[0]; + neco_waitgroup wg; + int N = 5; + expect(neco_waitgroup_init(&wg), NECO_OK); + expect(neco_waitgroup_add(&wg, N), NECO_OK); + int x = 0; + for (int i = 0; i < N; i++) { + expect(neco_start(co_sync_waitgroup_child, 2, &wg, &x), NECO_OK); + } + expect(neco_waitgroup_wait(&wg), NECO_OK); + expect(neco_waitgroup_add(&wg, -1), NECO_NEGWAITGRP); + expect(neco_waitgroup_done(&wg), NECO_NEGWAITGRP); + expect(neco_waitgroup_destroy(&wg), NECO_OK); + assert(x == N); + + // Should work after destroy because it's automatically reinitialized upon + // neco_waitgroup_add() or neco_waitgroup_wait(). + if (multi) { + expect(neco_waitgroup_add(&wg, 1), NECO_OK); + expect(neco_start(co_sync_waitgroup_multi_done, 1, &wg), NECO_OK); + expect(neco_start(co_sync_waitgroup_multi_waiter, 1, &wg), NECO_OK); + } + expect(neco_waitgroup_wait(&wg), NECO_OK); +} + +void test_sync_waitgroup_one(void) { + expect(neco_start(co_sync_waitgroup, 1, &(bool){false}), NECO_OK); +} + +void test_sync_waitgroup_multi(void) { + expect(neco_start(co_sync_waitgroup, 1, &(bool){true}), NECO_OK); +} + +void co_sync_waitgroup_cancel_child(int argc, void **argv) { + assert(argc == 1); + int64_t id = *(int64_t*)argv[0]; + expect(neco_sleep(NECO_SECOND/8), NECO_OK); + expect(neco_cancel(id), NECO_OK); + expect(neco_sleep(NECO_SECOND/8), NECO_OK); +} + +void co_sync_waitgroup_cancel(int argc, void **argv) { + assert(argc == 0); + (void)argv; + neco_waitgroup wg; + expect(neco_waitgroup_init(&wg), NECO_OK); + int64_t id = neco_getid(); + expect(neco_cancel(id), NECO_OK); + expect(neco_waitgroup_wait(&wg), NECO_CANCELED); + expect(neco_waitgroup_add(&wg, 1), NECO_OK); + expect(neco_start(co_sync_waitgroup_cancel_child, 1, &id), NECO_OK); + expect(neco_waitgroup_wait(&wg), NECO_CANCELED); + expect(neco_waitgroup_wait_dl(&wg, neco_now()+1), NECO_TIMEDOUT); + expect(neco_waitgroup_done(&wg), NECO_OK); + expect(neco_waitgroup_wait_dl(&wg, neco_now()-1), NECO_TIMEDOUT); +} + +void test_sync_waitgroup_cancel(void) { + expect(neco_start(co_sync_waitgroup_cancel, 0), NECO_OK); +} + +void co_sync_waitgroup_fail(int argc, void **argv) { + assert(argc == 0); + (void)argv; + neco_waitgroup wg; + expect(neco_waitgroup_init(&wg), NECO_OK); + expect(neco_waitgroup_add(&wg, 1), NECO_OK); + + // poison the waitgroup + memset(&wg, 63, sizeof(neco_waitgroup)); + expect(neco_waitgroup_add(&wg, 1), NECO_PERM); + memset(&wg, 0, sizeof(neco_waitgroup)); + +} + +void test_sync_waitgroup_fail(void) { + expect(neco_waitgroup_init(0), NECO_INVAL); + expect(neco_waitgroup_destroy(0), NECO_INVAL); + expect(neco_waitgroup_done(0), NECO_INVAL); + expect(neco_waitgroup_add(0, 0), NECO_INVAL); + expect(neco_waitgroup_wait(0), NECO_INVAL); + + neco_waitgroup wg; + expect(neco_waitgroup_init(&wg), NECO_PERM); + expect(neco_waitgroup_wait(&wg), NECO_PERM); + + expect(neco_start(co_sync_waitgroup_fail, 0), NECO_OK); +} + +void co_sync_cond_signal_child(int argc, void *argv[]) { + assert(argc == 2); + neco_cond *cond = argv[0]; + neco_mutex *mutex = argv[1]; + assert(!!mutex); + expect(neco_sleep(NECO_SECOND/4), NECO_OK); + expect(neco_cond_signal(cond), NECO_OK); +} + +void co_sync_cond_signal(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + neco_cond cond; + expect(neco_cond_init(&cond), NECO_OK); + neco_mutex mutex; + expect(neco_mutex_init(&mutex), NECO_OK); + expect(neco_start(co_sync_cond_signal_child, 2, &cond, &mutex), NECO_OK); + expect(neco_mutex_lock(&mutex), NECO_OK); + expect(neco_cond_wait(&cond, &mutex), NECO_OK); + expect(neco_mutex_unlock(&mutex), NECO_OK); + expect(neco_cond_destroy(&cond), NECO_OK); + expect(neco_mutex_destroy(&mutex), NECO_OK); +} + +void test_sync_cond_signal(void) { + expect(neco_start(co_sync_cond_signal, 0), NECO_OK); +} + +void co_sync_cond_signal_cancel_child(int argc, void **argv) { + assert(argc == 2); + (void)argv; + int64_t id = *(int64_t*)argv[0]; + bool delay = *(bool*)argv[1]; + + if (delay) { + expect(neco_sleep(NECO_SECOND/8), NECO_OK); + } + expect(neco_cancel(id), NECO_OK); +} + +void co_sync_cond_signal_cancel(int argc, void **argv) { + assert(argc == 0) + (void)argv; + + neco_cond cond = { 0 }; + neco_mutex mutex = { 0 }; + int64_t id = neco_getid(); + bool delay; + + expect(neco_mutex_lock(&mutex), NECO_OK); + delay = false; + expect(neco_start(co_sync_cond_signal_cancel_child, 2, &id, &delay), NECO_OK); + // This gets canceled right away + expect(neco_cond_wait(&cond, &mutex), NECO_CANCELED); + expect(neco_mutex_unlock(&mutex), NECO_OK); + + expect(neco_mutex_lock(&mutex), NECO_OK); + delay = true; + expect(neco_start(co_sync_cond_signal_cancel_child, 2, &id, &delay), NECO_OK); + // This gets canceled after slight delay + expect(neco_cond_wait(&cond, &mutex), NECO_CANCELED); + expect(neco_mutex_unlock(&mutex), NECO_OK); +} + +void test_sync_cond_signal_cancel(void) { + expect(neco_start(co_sync_cond_signal_cancel, 0), NECO_OK); +} + +void co_sync_cond_broadcast_child(int argc, void *argv[]) { + assert(argc == 3); + neco_cond *cond = argv[0]; + neco_mutex *mutex = argv[1]; + int *x = argv[2]; + expect(neco_mutex_lock(mutex), NECO_OK); + expect(neco_cond_wait(cond, mutex), NECO_OK); + (*x)++; + expect(neco_mutex_unlock(mutex), NECO_OK); +} + +void co_sync_cond_broadcast(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + neco_cond cond; + expect(neco_cond_init(&cond), NECO_OK); + neco_mutex mutex; + expect(neco_mutex_init(&mutex), NECO_OK); + int N = 10; + int x = 0; + for (int i = 0; i < N; i++) { + expect(neco_start(co_sync_cond_broadcast_child, 3, &cond, &mutex, &x), NECO_OK); + } + expect(neco_sleep(NECO_SECOND/4), NECO_OK); + expect(neco_cond_broadcast(&cond), NECO_OK); + while (1) { + expect(neco_mutex_lock(&mutex), NECO_OK); + bool done = x == N; + expect(neco_mutex_unlock(&mutex), NECO_OK); + if (done) { + break; + } + } + expect(neco_cond_destroy(&cond), NECO_OK); + expect(neco_mutex_destroy(&mutex), NECO_OK); +} + +void test_sync_cond_broadcast(void) { + expect(neco_start(co_sync_cond_broadcast, 0), NECO_OK); +} + +void co_sync_cond_deadline_child(int argc, void *argv[]) { + assert(argc == 2); + neco_cond *cond = argv[0]; + neco_mutex *mutex = argv[1]; + assert(!!mutex); + neco_sleep(NECO_SECOND/2); + expect(neco_cond_signal(cond), NECO_OK); +} + +void co_sync_cond_deadline(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + neco_cond cond; + expect(neco_cond_init(&cond), NECO_OK); + neco_mutex mutex; + expect(neco_mutex_init(&mutex), NECO_OK); + expect(neco_start(co_sync_cond_deadline_child, 2, &cond, &mutex), NECO_OK); + expect(neco_mutex_lock(&mutex), NECO_OK); + expect(neco_cond_wait_dl(&cond, &mutex, neco_now()+NECO_SECOND/8), NECO_TIMEDOUT); + expect(neco_cond_wait_dl(&cond, &mutex, neco_now()+NECO_SECOND), NECO_OK); + expect(neco_mutex_unlock(&mutex), NECO_OK); + expect(neco_cond_destroy(&cond), NECO_OK); + expect(neco_mutex_destroy(&mutex), NECO_OK); +} + +void test_sync_cond_deadline(void) { + expect(neco_start(co_sync_cond_deadline, 0), NECO_OK); +} + +void co_sync_mutex_rw_order_child2(int argc, void *argv[]) { + assert(argc == 2); + neco_mutex *mu = argv[0]; + struct order *order = argv[1]; + order_add(order, 17); + expect(neco_mutex_rdlock(mu), NECO_OK); + order_add(order, 23); + expect(neco_mutex_unlock(mu), NECO_OK); + order_add(order, 26); +} + +void co_sync_mutex_rw_order_child3(int argc, void *argv[]) { + assert(argc == 2); + neco_mutex *mu = argv[0]; + struct order *order = argv[1]; + order_add(order, 19); + expect(neco_mutex_trylock(mu), NECO_BUSY); + order_add(order, 20); + expect(neco_mutex_rdlock(mu), NECO_OK); + order_add(order, 24); + expect(neco_mutex_unlock(mu), NECO_OK); + order_add(order, 27); +} + +void co_sync_mutex_rw_order_child(int argc, void *argv[]) { + assert(argc == 2); + neco_mutex *mu = argv[0]; + struct order *order = argv[1]; + order_add(order, 6); + expect(neco_mutex_trylock(mu), NECO_BUSY); + order_add(order, 7); + expect(neco_mutex_tryrdlock(mu), NECO_OK); + order_add(order, 8); + expect(neco_mutex_unlock(mu), NECO_OK); + order_add(order, 10); + expect(neco_mutex_lock(mu), NECO_OK); + order_add(order, 13); + expect(neco_yield(), NECO_OK); + order_add(order, 14); + expect(neco_start(co_sync_mutex_rw_order_child2, 2, mu, order), NECO_OK); + order_add(order, 18); + expect(neco_start(co_sync_mutex_rw_order_child3, 2, mu, order), NECO_OK); + order_add(order, 21); + expect(neco_mutex_unlock(mu), NECO_OK); + order_add(order, 28); +} + +void co_sync_mutex_rw_order(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + // order_print = true; + struct order order = { 0 }; + neco_mutex _mutex; + neco_mutex *mu = &_mutex; + order_add(&order, 1); + expect(neco_mutex_init(mu), NECO_OK); + order_add(&order, 2); + expect(neco_mutex_rdlock(mu), NECO_OK); + order_add(&order, 3); + expect(neco_mutex_rdlock(mu), NECO_OK); + order_add(&order, 4); + expect(neco_mutex_rdlock(mu), NECO_OK); + order_add(&order, 5); + expect(neco_start(co_sync_mutex_rw_order_child, 2, mu, &order), NECO_OK); + order_add(&order, 9); + expect(neco_mutex_unlock(mu), NECO_OK); + order_add(&order, 11); + expect(neco_mutex_unlock(mu), NECO_OK); + order_add(&order, 12); + expect(neco_mutex_unlock(mu), NECO_OK); + order_add(&order, 15); + expect(neco_mutex_tryrdlock(mu), NECO_BUSY); + order_add(&order, 16); + expect(neco_mutex_rdlock(mu), NECO_OK); + order_add(&order, 22); + expect(neco_mutex_unlock(mu), NECO_OK); + order_add(&order, 25); + expect(neco_yield(), NECO_OK); + order_add(&order, 29); + expect(neco_mutex_destroy(mu), NECO_OK); + order_check(&order); +} + +void test_sync_mutex_rw_order(void) { + expect(neco_start(co_sync_mutex_rw_order, 0), NECO_OK); +} + +void co_sync_mutex_deadline_child(int argc, void *argv[]) { + assert(argc == 2); + int64_t id = *(int64_t*)argv[0]; + neco_mutex *mutex = argv[1]; + assert(!!mutex); + expect(neco_sleep(NECO_SECOND/4), NECO_OK); + expect(neco_cancel(id), NECO_OK); +} + +void co_sync_mutex_deadline(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + int64_t id = neco_getid(); + neco_mutex mutex; + expect(neco_mutex_init(&mutex), NECO_OK); + expect(neco_mutex_lock(&mutex), NECO_OK); + expect(neco_start(co_sync_mutex_deadline_child, 2, &id, &mutex), NECO_OK); + expect(neco_mutex_lock_dl(&mutex, neco_now()+NECO_SECOND/8), NECO_TIMEDOUT); + expect(neco_cancel(id), NECO_OK); + expect(neco_mutex_lock_dl(&mutex, neco_now()+NECO_HOUR), NECO_CANCELED); + expect(neco_mutex_lock_dl(&mutex, neco_now()+NECO_HOUR), NECO_CANCELED); + expect(neco_cancel(id), NECO_OK); + expect(neco_mutex_rdlock_dl(&mutex, neco_now()-1), NECO_CANCELED); + expect(neco_mutex_rdlock_dl(&mutex, neco_now()-1), NECO_TIMEDOUT); +} + +void test_sync_mutex_deadline(void) { + expect(neco_start(co_sync_mutex_deadline, 0), NECO_OK); +} + +void co_sync_mutex_fail(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + neco_mutex mutex; + expect(neco_mutex_init(0), NECO_INVAL); + expect(neco_mutex_init(&mutex), NECO_OK); + expect(neco_mutex_lock(&mutex), NECO_OK); + expect(neco_mutex_destroy(&mutex), NECO_BUSY); + expect(neco_mutex_fastlock(&mutex, neco_now() - 1), NECO_TIMEDOUT); +} + +void test_sync_mutex_fail(void) { + expect(neco_mutex_destroy(0), NECO_INVAL); + neco_mutex mutex; + expect(neco_mutex_init(&mutex), NECO_PERM); + expect(neco_mutex_lock(&mutex), NECO_PERM); + expect(neco_mutex_trylock(&mutex), NECO_PERM); + expect(neco_mutex_rdlock(&mutex), NECO_PERM); + expect(neco_mutex_tryrdlock(&mutex), NECO_PERM); + expect(neco_mutex_unlock(&mutex), NECO_PERM); + expect(neco_mutex_destroy(&mutex), NECO_PERM); + expect(neco_start(co_sync_mutex_fail, 0), NECO_OK); +} + + +void co_sync_mutex_rw_reader(int argc, void *argv[]) { + assert(argc == 4); + neco_mutex *mu = argv[0]; + int *x = argv[1]; + int *N = argv[2]; + int *rds = argv[3]; + expect(neco_mutex_trylock(mu), NECO_BUSY); + expect(neco_mutex_lock(mu), NECO_OK); + (*x)++; + expect(neco_sleep(NECO_MILLISECOND), NECO_OK); + expect(neco_mutex_unlock(mu), NECO_OK); + expect(neco_sleep(NECO_MILLISECOND), NECO_OK); + expect(neco_mutex_tryrdlock(mu), NECO_BUSY); + expect(neco_mutex_rdlock(mu), NECO_OK); + (*rds)++; + assert(*x == *N*10); + expect(neco_sleep(NECO_MILLISECOND*100), NECO_OK); + expect(neco_mutex_unlock(mu), NECO_OK); +} + +void co_sync_mutex_rw(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + // struct order order = { 0 }; + neco_mutex mutex; + neco_mutex *mu = &mutex; + expect(neco_mutex_init(mu), NECO_OK); + int x = 0; + int N = 20; + int rds = 0; + expect(neco_mutex_lock(mu), NECO_OK); + for (int i = 0 ; i < N; i++) { + expect(neco_start(co_sync_mutex_rw_reader, 4, &mutex, &x, &N, &rds), NECO_OK); + } + assert(x == 0); + expect(neco_mutex_unlock(mu), NECO_OK); + expect(neco_mutex_lock(mu), NECO_OK); + assert(x == N); + expect(neco_sleep(NECO_MILLISECOND*100), NECO_OK); + x *= 10; + expect(neco_mutex_unlock(mu), NECO_OK); + expect(neco_sleep(NECO_MILLISECOND*200), NECO_OK); + assert(rds == 20); + expect(neco_mutex_destroy(mu), NECO_OK); +} + +void test_sync_mutex_rw(void) { + expect(neco_start(co_sync_mutex_rw, 0), NECO_OK); +} + +void co_sync_cond_fail(int argc, void *argv[]) { + assert(argc == 0); + (void)argv; + + neco_cond cond; + neco_mutex mutex; + + expect(neco_cond_init(&cond), NECO_OK); + expect(neco_cond_wait(0, 0), NECO_INVAL); + expect(neco_cond_wait(&cond, 0), NECO_INVAL); + + // poison the mutex + memset(&mutex, 63, sizeof(neco_mutex)); + expect(neco_cond_wait(&cond, &mutex), NECO_PERM); + memset(&mutex, 0, sizeof(neco_mutex)); + + // poison the cond + memset(&cond, 63, sizeof(neco_cond)); + expect(neco_cond_wait(&cond, &mutex), NECO_PERM); + memset(&cond, 0, sizeof(neco_cond)); + + expect(neco_cond_signal(0), NECO_INVAL); + expect(neco_cond_broadcast(0), NECO_INVAL); + +} + +void test_sync_cond_fail(void) { + neco_cond cond; + neco_mutex mutex; + expect(neco_cond_init(0), NECO_INVAL); + expect(neco_cond_init(&cond), NECO_PERM); + expect(neco_cond_wait(&cond, &mutex), NECO_PERM); + expect(neco_cond_destroy(0), NECO_INVAL); + expect(neco_start(co_sync_cond_fail, 0), NECO_OK); +} + +int main(int argc, char **argv) { + do_test(test_sync_mutex); + do_test(test_sync_mutex_rw); + do_test(test_sync_mutex_rw_order); + do_test(test_sync_mutex_deadline); + do_test(test_sync_mutex_fail); + do_test(test_sync_waitgroup_one); + do_test(test_sync_waitgroup_multi); + do_test(test_sync_waitgroup_cancel); + do_test(test_sync_waitgroup_fail); + do_test(test_sync_cond_signal); + do_test(test_sync_cond_signal_cancel); + do_test(test_sync_cond_broadcast); + do_test(test_sync_cond_deadline); + do_test(test_sync_cond_fail); +} diff --git a/tests/test_wait.c b/tests/test_wait.c new file mode 100644 index 0000000..624e8d9 --- /dev/null +++ b/tests/test_wait.c @@ -0,0 +1,35 @@ +#include "tests.h" + +#if defined(_WIN32) +DISABLED("test_net", "Windows") +#elif defined(__EMSCRIPTEN__) +DISABLED("test_net", "Emscripten") +#else + +void co_wait_fd(int argc, void *argv[]) { + (void)argc; + (void)argv; + int fds[2]; + assert(pipe(fds) == 0); + expect(neco_wait_dl(fds[0], NECO_WAIT_READ, neco_now()+NECO_MILLISECOND), NECO_TIMEDOUT); + expect(neco_wait(fds[1], NECO_WAIT_WRITE), NECO_OK); + int ret = neco_wait(-10, NECO_WAIT_WRITE); + assert(ret == NECO_ERROR && errno == EBADF); + assert(write(fds[1], "hiya", 4) == 4); + expect(neco_wait(fds[0], NECO_WAIT_READ), NECO_OK); + char buf[16]; + assert(read(fds[0], buf, sizeof(buf)) == 4 && memcmp(buf, "hiya", 4) == 0); + close(fds[0]); + close(fds[1]); +} + +void test_wait_fd(void) { + expect(neco_wait(1, 0), NECO_INVAL); + expect(neco_wait(1, NECO_WAIT_READ), NECO_PERM); + expect(neco_start(co_wait_fd, 0), NECO_OK); +} + +int main(int argc, char **argv) { + do_test(test_wait_fd); +} +#endif diff --git a/tests/test_work.c b/tests/test_work.c new file mode 100644 index 0000000..541b99e --- /dev/null +++ b/tests/test_work.c @@ -0,0 +1,31 @@ +#include "tests.h" + +void work(void *udata) { + (*(int*)udata)++; + usleep(1000); +} + +void co_work_runner(int argc, void *argv[]) { + assert(argc == 1); + int i = *(int*)argv[0]; + int x = i; + neco_work(-1, work, &x); + assert(x == i+1); +} + +void co_work(int argc, void *argv[]) { + (void)argc; (void)argv; + for (int i = 0; i < 1000; i++) { + expect(neco_start(co_work_runner, 1, &i), NECO_OK); + } +} + +void test_work(void) { + expect(neco_work(0, 0, 0), NECO_INVAL); + expect(neco_work(0, work, 0), NECO_PERM); + expect(neco_start(co_work, 0), NECO_OK); +} + +int main(int argc, char **argv) { + do_test(test_work); +} diff --git a/tests/tests.h b/tests/tests.h new file mode 100644 index 0000000..db50335 --- /dev/null +++ b/tests/tests.h @@ -0,0 +1,354 @@ +#ifndef TESTS_H +#define TESTS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../neco.h" + +#ifdef _WIN32 +#include +#endif + +#if !defined(_WIN32) && !defined(__EMSCRIPTEN__) +// Everything but Windows and use Emscripten can use fail counters +#define IS_FAIL_TARGET +#endif + + +#ifndef NECO_TESTING +#error NECO_TESTING not defined +#endif + +#if defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#endif + +atomic_int total_allocs = 0; +atomic_int total_mem = 0; + +static bool rand_alloc_fail = false; +// 1 in 10 chance malloc or realloc will fail. +static int rand_alloc_fail_odds = 10; + +struct alloc_prefix { + size_t size; +} __attribute__((aligned(16))); + +static void *xmalloc(size_t size) { + if (rand_alloc_fail && rand()%rand_alloc_fail_odds == 0) { + return NULL; + } + void *mem = malloc(16+size); + assert(mem); + *(uint64_t*)mem = size; + atomic_fetch_add(&total_allocs, 1); + atomic_fetch_add(&total_mem, size); + return (char*)mem+16; +} + +static void xfree(void *ptr) { + if (ptr) { + size_t size = *(uint64_t*)((char*)ptr-16); + atomic_fetch_sub(&total_mem, size); + atomic_fetch_sub(&total_allocs, 1); + free((char*)ptr-16); + } +} + +static void *xrealloc(void *ptr, size_t size) { + if (!ptr) { + return xmalloc(size); + } + if (rand_alloc_fail && rand()%rand_alloc_fail_odds == 0) { + return NULL; + } + size_t psize = *(uint64_t*)((char*)ptr-16); + void *mem = realloc((char*)ptr-16, 16+size); + assert(mem); + *(uint64_t*)mem = size; + total_mem -= psize; + total_mem += size; + return (char*)mem+16; +} + +void init_test_allocator(bool random_failures) { + rand_alloc_fail = random_failures; + neco_env_setallocator(xmalloc, xrealloc, xfree); +} + +void cleanup_test_allocator(void) { + if (total_allocs > 0 || total_mem > 0) { + fprintf(stderr, "test failed: %d unfreed allocations, %d bytes\n", + (int)total_allocs, (int)total_mem); + exit(1); + } + neco_env_setallocator(NULL, NULL, NULL); +} + +static bool nameless_tests = false; +static void wait_for_threads(void); + +uint64_t mkrandseed(void) { + uint64_t seed = 0; +#ifdef _WIN32 + assert(RtlGenRandom(&seed, sizeof(uint64_t))); +#else + FILE *urandom = fopen("/dev/urandom", "r"); + assert(urandom); + assert(fread(&seed, sizeof(uint64_t), 1, urandom)); + fclose(urandom); +#endif + return seed; +} + +void seedrand(void) { + uint64_t seed = mkrandseed(); + srand(seed); +} + +double rand_double(void) { + return (double)rand()/((double)(RAND_MAX)+1); +} + +static int64_t getnow(void) { + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) { + fprintf(stderr, "clock_gettime: %s\n", strerror(errno)); + exit(EXIT_FAILURE); + } + return now.tv_sec * INT64_C(1000000000) + now.tv_nsec; +} + +#define do_test_(name,for_neco) { \ + if (argc < 2 || strstr(#name, argv[1])) { \ + if (!nameless_tests) printf("%s\n", #name); \ + seedrand(); \ + if (for_neco) { \ + neco_env_setcanceltype(NECO_CANCEL_INLINE); \ + neco_env_setcancelstate(NECO_CANCEL_ENABLE); \ + init_test_allocator(false); \ + } \ + name(); \ + wait_for_threads(); \ + if (for_neco) { \ + cleanup_test_allocator(); \ + neco_env_setcanceltype(NECO_CANCEL_ASYNC); \ + neco_env_setcancelstate(NECO_CANCEL_ENABLE); \ + } \ + } \ +} + +#define do_test(name) \ + do_test_(name, true) + + + + +double now(void) { + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + return (now.tv_sec*1e9 + now.tv_nsec) / 1e9; +} + +char *readfile(const char *path, long *len) { + FILE *f = fopen(path, "rb+"); + assert(f); + assert(fseek(f, 0, 2) == 0); + size_t size = ftell(f); + rewind(f); + char *mem = (char *)malloc(size+1); + assert(mem); + assert(fread(mem, size, 1, f) == 1); + mem[size] = '\0'; + assert(fclose(f) == 0); + if (len) *len = size; + return (char*)mem; +} + +char *readtestfile(const char *name, long *len) { + char path[128]; + snprintf(path, sizeof(path), "testfiles/%s", name); + return readfile(path, len); +} + +char *commaize(unsigned int n) { + char s1[64]; + char *s2 = (char *)malloc(64); + assert(s2); + memset(s2, 0, sizeof(64)); + snprintf(s1, sizeof(s1), "%d", n); + int i = strlen(s1)-1; + int j = 0; + while (i >= 0) { + if (j%3 == 0 && j != 0) { + memmove(s2+1, s2, strlen(s2)+1); + s2[0] = ','; + } + memmove(s2+1, s2, strlen(s2)+1); + s2[0] = s1[i]; + i--; + j++; + } + return s2; +} + +// private or undocumented functions (exported) +const char *neco_shortstrerror(int code); +int neco_errconv_from_sys(void); +void neco_errconv_to_sys(int err); +int neco_errconv_from_gai(int errnum); +int neco_getaddrinfo_nthreads(void); +int neco_mutex_fastlock(neco_mutex *mutex, int64_t deadline); +void neco_setcanceled(void); +int neco_pipe(int pipefd[2]); +int neco_testcode(int errcode); +void *neco_malloc(size_t size); +void *neco_realloc(void *ptr, size_t size); +void neco_free(void *ptr); +int neco_stream_release(neco_stream *stream); +int neco_stream_make_buffered_size(neco_stream **buf, int fd, size_t buffer_size); +int neco_mutex_destroy(neco_mutex *mutex); +int neco_waitgroup_destroy(neco_waitgroup *waitgroup); +int neco_cond_destroy(neco_cond *cond); + +#define FAIL_EXTERN(name) \ +extern __thread volatile int neco_fail_ ## name ## _counter; \ +extern __thread volatile int neco_fail_ ## name ## _error; + +FAIL_EXTERN(read) +FAIL_EXTERN(write) +FAIL_EXTERN(accept) +FAIL_EXTERN(connect) +FAIL_EXTERN(socket) +FAIL_EXTERN(bind) +FAIL_EXTERN(listen) +FAIL_EXTERN(setsockopt) +FAIL_EXTERN(nanosleep) +FAIL_EXTERN(fcntl) +FAIL_EXTERN(evqueue) +FAIL_EXTERN(pthread_create) +FAIL_EXTERN(pthread_detach) +FAIL_EXTERN(pipe) +FAIL_EXTERN(neco_malloc) +FAIL_EXTERN(neco_realloc) +FAIL_EXTERN(stack_get) + +extern __thread bool neco_fail_cowait; +extern __thread bool neco_last_panic; +extern __thread bool neco_connect_dl_canceled; +extern __thread int neco_partial_write; + +#define expect(op, ...) { \ + int args[] = {__VA_ARGS__,0,0}; \ + int nargs = (sizeof((int[]){__VA_ARGS__})/sizeof(int)); \ + int ret = (op); \ + if (nargs > 0 && ret != (args[0])) { \ + fprintf(stderr, "FAIL expected '%s' got '%s', function %s, file %s, line %d.\n", \ + neco_shortstrerror((args[0])), neco_shortstrerror(ret), \ + __func__, __FILE__, __LINE__); \ + _Exit(1); \ + } \ + if (nargs == 1 && args[0] == NECO_ERROR) { \ + assert(!"need extra arg"); \ + } \ + if (nargs > 1 && neco_lasterr() != (args[1])) { \ + fprintf(stderr, "FAIL `neco_lasterr` expected '%s' got '%s', function %s, file %s, line %d.\n", \ + neco_shortstrerror((args[1])), neco_shortstrerror(neco_lasterr()), \ + __func__, __FILE__, __LINE__); \ + _Exit(1); \ + } \ +} + +#ifdef assert +#undef assert +#endif + +#define assert(cond) { \ + if (!(cond)) { \ + fprintf(stderr, "FAIL assertion '%s', function %s, file %s, line %d.\n", \ + #cond, __func__, __FILE__, __LINE__); \ + _Exit(1); \ + } \ +} + +static bool order_print = false; + +struct order { + int vals[256]; + int count; +}; + +static void order_add(struct order *order, int val) { + if (order_print) { + printf("%d\n", val); + } + order->vals[order->count++] = val; +} + +#define order_check(order) { \ + for (int i = 1; i < (order)->count; i++) { \ + if ((order)->vals[i-1] != (order)->vals[i]-1) { \ + fprintf(stderr, "Out of order: [ "); \ + for (int j = 0; j < (order)->count; j++) { \ + fprintf(stderr, "%d ", (order)->vals[j]); \ + } \ + fprintf(stderr, "]\n"); \ + assert(!"bad order"); \ + } \ + } \ +} + +void wait_for_threads(void) { + double start = now(); + while (neco_getaddrinfo_nthreads() > 0) { + if (now() - start > 5e9) { + fprintf(stderr, "All threads did not stop in a reasonable amount of time\n"); + abort(); + } + sched_yield(); + } +} + +void neco_print_stats(void) { + neco_stats stats; + int ret = neco_getstats(&stats); + if (ret != NECO_OK) { + printf("%s\n", neco_strerror(ret)); + return; + } + printf("coroutines: %zu\n", stats.coroutines); + printf("sleepers: %zu\n", stats.sleepers); + printf("evwaiters: %zu\n", stats.evwaiters); + printf("sigwaiters: %zu\n", stats.sigwaiters); + printf("senders: %zu\n", stats.senders); + printf("receivers: %zu\n", stats.receivers); + printf("locked: %zu\n", stats.locked); + printf("waitgroupers: %zu\n", stats.waitgroupers); + printf("condwaiters: %zu\n", stats.condwaiters); +} + +#define DISABLED(test, platform) \ + int main(void) { \ + (void)nameless_tests; \ + fprintf(stderr, test " \e[31mdisabled for " platform "\e[0m\n"); \ + return 0; \ + } + +extern __thread int neco_gai_errno; + +#if defined(__GNUC__) +#pragma GCC diagnostic pop +#endif + +#endif // TESTS_H