This is also tracked internally at Facebook by task T35565697.
Clang should optimize out the rethrow/catch at each coroutine level to avoid the overhead of the exception throw/unwind/catch/type-erase, since with coroutines we have the rethrow-point in the same function body as the catch, and so the compiler can see the coroutine handle. If these optimisations are implementable then this would mean we don't need any changes to the Coroutines TS design to make exception handling efficient.
The following use cases ought to be optimized:
Case 1A: For task<T>
types
The await_resume() method typically looks something like:
T await_resume() {
if (coro.promise().exception) {
std::rethrow_exception(coro.promise().exception);
}
return coro.promise().value;
}
And the awaiting coroutine's unhandled_exception()
method typically looks something like:
void unhandled_exception()
{
this->exception = std::current_exception();
}
And this occurs within the coroutine body that looks something like this:
try { ... }
catch (...) { promise.unhandled_exception(); }
final_suspend:
co_await promise.final_suspend();
So after inlining we would end up with the body of the coroutine looking something like this:
try { ... std::rethrow_exception(e); ... }
} catch (...) { this->exception = std::current_exception(); }
final_suspend:
co_await promise.final_suspend();
Is it possible to have the compiler optimise this code to something like the following, so that we avoid the overhead of throwing and catching an exception?
The compiler can see lexically the nearest enclosing try/catch so it seems like something that should be possible, but it may need some integration with the exception-handling runtime.
std::exception_ptr __e;
try {
...
__e = e;
++std::__unhandled_exceptions_counter;
goto handle_inline_exception;
...
} catch (...) {
this->exception = std::current_exception();
}
goto final_suspend;
handle_inline_exception:
--std::__unhandled_exceptions_counter;
this->exception = __e;
final_suspend:
co_await promise.final_suspend();
Case 1B: The task<T>
case where an awaitable directly throws an exception from await_resume()
rather than rethrowing an exception_ptr
eg. An await_resume()
method that looks like this:
void awaitable::await_resume() {
if (someCondition) throw some_exception{};
}
So that after inlining the coroutine body looks like this:
try {
...
throw some_exception{};
...
} catch (...) {
promise.exception = std::current_exception();
}
final_suspend:
co_await promise.final_suspend();
Ideally we would be able to short-circuit the exception handling mechanisms in these cases so that it optimised to something like this:
std::exception_ptr __e;
try {
...
__e = __runtime_make_exception_ptr(some_exception{});
++std::__unhandled_exceptions_counter;
goto handle_inline_exception;
...
} catch (...) {
promise.exception = std::current_exception();
}
goto final_suspend;
handle_inline_exception:
--std::__unhandled_exceptions_counter;
promise.exception = __e;
final_suspend:
co_await promise.final_suspend();
Case 2: For generator<T>
With a generator class we typically want the exception to propagate out of the coroutine to the caller of coroutine_handle<>::resume()
, ie. the unhandled_exception() method of the promise should look like this:
void unhandled_exception() { throw; }
So that the body of the generator coroutine would look like this (after some inlining):
try { ... }
catch (...) { throw; }
I'm assuming that the compiler should be able to optimize this to remove the try/catch altogether.
Does it already do this?