Jumping into statement expression raises error in clang 17 and later

I’m developing an asyc/await coroutine framework in C, it looks like:

static void _demo_task(task_t *t)
{
    switch (t->lc)
    {
    case 0:
        t->sub = run_async_task(_sub_task_func); // run async task

        // do something

        // await (join) task
        // int ret = JOIN(t->sub);
        int ret =
        ({
            t->lc = 1;
            return;
    case 1: // when sub task finish, it wakes this task and continue from here
            get_task_result(t->sub);
        });
    }
}

You can use “ret = JOIN()” to yield out and await sub task to finish. When it comes back, it jumps to “case 1:”, then sub task’s result is assigned to “ret”, just like the async/await routines in C# and javascript.

In clang 16 it works, but in clang 17 and later, jumping into statement expression is not allowed:

error: cannot jump from switch statement to this case label
note: jump enters a statement expression

Would it be possible to add a switch in later clang version to turn this off? Something like “-Wno-jump-into-statement-expr”, thanks you!

In many cases, such jumps lead to clang generating malformed IR, which will crash the compiler or generate nonsense assembly. See discussion at ⚙ D154696 [Clang] Diagnose jumps into statement expressions ⚙ D155342 [clang][JumpDiagnostics] ignore non-asm goto target scopes . We don’t want to allow that.

It might be possible to carve out a subset of jumps into statement expressions which are actually safe, but I’m not sure we could generalize it much beyond your exact example.

Thank you for the reply! We have been using this in production code for years, and for this specific routine it works fine.

Our implementation is stackless coroutine, “-Wconditional-uninitialized” is used to make sure there is no use of uninitialized stack variables when jumps taken.

Such jumps is used in macros like “JOIN()”, which mimic the async expression like “ret = await foo()” in C#. The macro just set the resume point and return, when comes back, grab the return value as the value of the whole macro.

Very appreciate if you would look into this, if you want some more examples, I’m glad to work with you, thank you!

I should note the same “such jumps lead to clang generating malformed IR” applied equally to GCC too. See Making sure you're not a bot! (and Making sure you're not a bot! ) and Making sure you're not a bot! (GCC 13 change for the C++ front-end, GCC’s C front-end was rejecting such jumps since GCC 4.0.0) which disallowed them for GCC.

But this is useful to mimic “await” or “co_await” in C, is it safe to use for this case?

You know GCC 4.0.0 was released over 20 years ago right? April 20, 2005.

Yes I know, they also discussed about deprecating the whole statement expression 20 years ago. Very appreciate if you could give some advice about how to implement co_await in C.

Everyone’s use case is valid, but I want to say I’d be strongly opposed to relaxing this error. It’s extremely difficult to support jumping into sub-expressions.

How would your async framework handle cases where you need to resume mid-function call argument evaluation? If I resume into the middle of an add expression, what is the value of the LHS of the subexpression that feeds into the ADD operation?

The semantics of these cases can be made extremely clear by avoiding statement expressions altogether, and using do { } while (false) or some other device instead.

3 Likes

C++ has co_await expression, it has the similar problem. Is it safe in C it’s safe to restrict the usage only in assignment like ret = ({ [some yield and resume]; [eval the async task return value]; }) ?