Keep type-checking information across a variable boundary, when function signatures are handled internally

134 Views Asked by At

I am required to use C17 for this problem.

I'm trying to design a coroutine system in standard C. This means no ucontext (as it's not standard) and no longjmp (as according to the standard downwards jumps are UB). I want this system to also be multithread compatible, which means I have to not destroy stack variables that are depended on by spawned fibers (more about this in the code example).

Here's a small example of what I would expect an example test program to look like. (This specific program is meant to test that the runtime system does not deadlock in the case that two fibers that would deadlock if executed sequentially are spawned concurrently.)

static yield_t countdown_await(runtime_t* rt, size_t entrypoint) {
    RUNTIME_ARGUMENTS(rt, countdownlatch_t*, cdl);
    switch(entrypoint) {
        case 0: goto START;
        case 1: goto POST_AWAIT;
    }

    START:
        countdownlatch_countdown(rt);
        return COUNTDOWNLATCH_YIELD_AWAIT(rt, cdl, 1);
    POST_AWAIT:
        return YIELD_RETURN(rt);
}

static yield_t test_single_thread_concurrency(runtime_t* rt, size_t entrypoint) {
    RUNTIME_ARGUMENTS(rt, int, argc, char**, argv);
    RUNTIME_LOCAL(rt, countdownlatch_t*, cdl);
    RUNTIME_LOCAL(rt, fiber_t*, a);
    RUNTIME_LOCAL(rt, fiber_t*, b);
    switch(entrypoint) {
        case 0: goto START;
        case 1: goto POST_JOIN_A;
        case 2: goto POST_JOIN_B;
    }

    START:
        *cdl = countdownlatch_new(rt, 2);
        *a = RUNTIME_START(rt, countdown_await, *cdl);
        *b = RUNTIME_START(rt, countdown_await, *cdl);

        return RUNTIME_YIELD_JOIN(rt, *a, 1);
    POST_JOIN_A:
        return RUNTIME_YIELD_JOIN(rt, *b, 2);

    POST_JOIN_B:
        return YIELD_RETURN(rt, 0); // exit code
}

int main(int argc, char* argv[]) {
    return RUNTIME_MAIN(test_single_thread_concurrency, argc, argv);
}

I face the following problems with this syntax and implementation:

  • RUNTIME_MAIN does not have a mechanism to check that test_single_thread_concurrency accepts arguments int argc and char** argv
  • RUNTIME_MAIN does not have a mechanism to check that test_single_thread_concurrency returns an int exit code
  • RUNTIME_YIELD_JOIN does not have a mechanism to check that a and b are void-resulting fibers
  • RUNTIME_START does not have a mechanism to check countdown_await's arguments or its return type

Which means, in order to avoid UB, I've put a large mental burden on the programmer to remember the arguments and return types of each function, and refactoring tools no longer work. (Most of these macros would be implemented via various calls with sizeofs inside and casting to void* or void (*)(void).)

I'm fine with introducing a ton of macros that completely change how code is written in order to accomplish this. I just want to make sure it is impossible (or at the very least, really difficult) to write code that includes type mismatches, while retaining the ability to

  • call a coroutine function on the current fiber
  • start a new fiber via a coroutine function, with arguments passed to this fiber able to point to data on the current stack (originally, I envisioned this to be handled by the runtime_t creating reference-counted stacks that link to each-other)
  • join a fiber and obtain its result

I know of the existence of _Generic, but if it can be used to solve this problem, I have so far been unable to figure out the solution.

I am required to use C17 for this project (or I would have another language, such as ATS2, generate C code from a more strongly-typed language).

0

There are 0 best solutions below