Using Coroutines to Implement C++ Exceptions for Freestanding Environments - Eyal Zedaka - CppCon 21

Sdílet
Vložit
  • čas přidán 31. 05. 2024
  • cppcon.org/
    github.com/CppCon/CppCon2021
    ---
    The current design of C++ exceptions lead to many reasons and excuses to disable them. They require outstanding unwinding libraries, ABI specific support, slow failure paths, metadata that increases binary size, RTTI (run time type information), and many more. Putting costs and overhead aside, it is often impractical, or holds significant implementation barriers, to implement the necessary support for C++ exceptions in every environment - kernel, custom OS, hypervisors, embedded, or an arbitrary freestanding environment with limited or no C++ runtime libraries.
    Writing code using exceptions is great though! The programmer gets to focus on the actual story of what the program is doing, and not worry too much about error propagation that happens automatically whenever an exception is thrown.
    So how do we avoid the manual error propagation that is usually followed by turning off exceptions? We use Macros(!), of course, to propagate the errors via return value, such as CHECK_ERROR(expression), RIGHT? Well... Not in this talk.
    In this session we are going to use the only tool I know of, that is both available in standard C++20, and gives us the ability to automatically propagate errors. If you read the title, you already know that this tool was not meant to be used for that purpose - that is C++20 Coroutines!
    We are going to not only run without exceptions, we are turning off RTTI as well, as we get inspired by this alternative method of throwing exceptions, observe some cool optimizations such as memory allocation elisions, analyze the assembly code of coroutines, and more. We will even get the link to the open source library that I wrote for this talk, so that you could try it yourself!
    This talk is for you if you want to get inspired on a unique usage of coroutines, not commonly seen before, or if you are working on kernel / freestanding code and like the use of exceptions. I hope that after this talk the audience plays with the thought that maybe in the future, exceptions could be implemented as a standard library feature, using a core language machinery such as coroutines, or some evolution or adaptation of it to this great use case.
    How to prepare for the talk - please be familiar with C++20 coroutines, just the basic understanding is enough.
    ---
    Eyal Zedaka
    Eyal Zedaka is a technical leader. He is a C++ knowledge center, lecturer in various C++ courses, and an author of C++ open source software in the areas of low level OS, and pure C++ frameworks. Eyal is now working at Magic Leap, an Augmented Reality (AR) device company, as manager of device security engineering, where he leads secure C++ development and vulnerability research throughout the platform. Eyal has served in designing a C++ freestanding framework for trusted execution environments, and providing consultation to embedded teams regarding C++ use in lower level areas. He had previously been a software security architect in charge of the Magic Leap platform security architecture, and a senior engineer in various low level and security C++ positions in the market for the last 9-10 years.
    ---
    Videos Filmed & Edited by Bash Films: www.BashFilms.com
    CZcams Channel Managed by Digital Medium Ltd events.digital-medium.co.uk
    *--*
  • Věda a technologie

Komentáře • 29

  • @PedroOliveira-sl6nw
    @PedroOliveira-sl6nw Před 4 měsíci +1

    Finally a good use of coroutines

  • @tomerdbz
    @tomerdbz Před 2 lety +2

    You’re a Wizard Zedaka! The way you optimized it truly feels like you’ve found an exploit. It’s clear you’re coming from security :)

  • @user-zm9gf4re4z
    @user-zm9gf4re4z Před 2 lety +4

    Good talk. Unfortunately special member functions (for example constructor) can't be a coroutine, so this approach can't replace "regular" exceptions. But IMO it is a better way of using expected-like types and optionals - co_await is so much better than UGLY_MACRO or "if" statements everywhere.

    • @eyalz7766
      @eyalz7766 Před 2 lety

      Thanks, regarding what you said, there are work arounds: for move operations it is most of the time ok as they are noexcept, for copy you could make an operator co_await in the class to act as a clone so you can do "a1 = co_await a2" or just use a clone function directly, then assume that assignment can never fail if you use copy-move-swap idiom. But that is just a thought I didn't try it.

  • @shri314
    @shri314 Před 2 lety +2

    Awesome Talk!! and creative use of coroutines.
    Thoughts:
    1. I think composing this with traditional coroutine (async i/o) will be complex if not impossible. It may be a co_await "overload".
    2. Also - one place where only regular exceptions can be used to report errors is the constructors.

  • @jhbonarius
    @jhbonarius Před 2 lety +9

    Isn't this just a (more complicated) implementation of std::expected? Or even more like returning std::variant, seeing the handling of the return value? Does using coroutines really add value here? You don't need to suspend or await anything in this example.

    • @johanneskalmbach5719
      @johanneskalmbach5719 Před 2 lety +2

      This mechanism allows an error to automatically propagate up the call stack until the appropriate try/catch block. Using variant/expected/error codes, you have
      to check if an error occured in every layer of the code. The only way I know of to do such a thing using std::expected etc. is by using a rather complicated macro,
      that stands at the place where the `co_await` keyword is used here:
      a)
      auto x = throwing_function(); // ordinary exceptions, convenient, but at a cost.
      b)
      auto expected = function_returning_expected();
      if (expected.holds_error()) return expected; // boilerplate manual propagation, could be shortened by macros, but not by other means, since `return` is required.
      c)
      auto x = co_await some_coroutine_from_this_talk(); // no boilerplate, other than the (keyword!) co_await, but not using exceptions.
      So as I understand it, the aim of this talk is to get the convenience of a) but the performance of b) by using c):)

    • @eyalz7766
      @eyalz7766 Před 2 lety +2

      Copying my reply from below:
      Exceptions propagate automatically whereas return codes do not (std::expected does not propagate the error automatically to the caller) - consider throwing safe_divide(x, y):
      std::cout

    • @elijahshadbolt7334
      @elijahshadbolt7334 Před 2 lety

      Yeah that dawned on me as soon as he changed the co_await to co_return. You can also use expected with actual coroutines that get resumed. I'd rather write `return my_throw(std::runtime_error("bad"));` than `co_yield std::runtime_error("bad");`

    • @elijahshadbolt7334
      @elijahshadbolt7334 Před 2 lety

      However, the coroutine approach propagates the exception without you having to check for error after returning to the call site.

    • @shushens
      @shushens Před 2 lety

      This is the best question IMHO. The way the talk is designed seems too much like a sales talk and not a talk made by an engineer for engineers.

  • @TheBlaizard
    @TheBlaizard Před 2 lety

    Very nice talk and interesting idea on how coroutines with expected-like type can be used, this is extremely powerful especially in embedded context.

  • @sagrel
    @sagrel Před 2 lety +3

    That was a great talk, congratulations!!

  • @ericcurtin412
    @ericcurtin412 Před 2 lety +2

    I started listening to this without watching and I thought it was Andrei Alexandrescu, he's a voice twin!

  • @etunimenisukunimeni1302

    "Any questions?"
    Oh, just ask how many! Not that I'd be able to tell you the actual number, because I didn't use unsigned and as a consequence my mental C++ runtime hit UB within the first 10 minutes.
    I suppose all of them can be reasonably summarised with a simple "Wat.", followed by "Are you a wizard?". I can sorta-kinda follow how you did it, but man, this is some seriously cool stuff, even if it's still a bit unpolished.

  • @goswinvonbrederlow6602

    To forward exceptions every function needs to be a coroutine and using co_await for the function calls. Isn't that adding a check of the return type and a copying/moving of the exception every time for non-trivial cases (and when not throwing an enum)? Isn't this breaking tail calls too? On another topic: Like giving a stack trace on throw you could also give one when entering a function and when returning. So with some simple define switches you could enable or disable tracing of all functions and tracing return values. No tracing of arguments or in/out parameters though, so not 100% perfect.

    • @eyalz7766
      @eyalz7766 Před 2 lety

      As you can see in the assembly, it is checking as much as it would have checked if you used plain return value based error handling. The co_await is being used as a smart "if error then return" and the error propagation itself is a copy of 2*(pointer size) bytes (the exit condition)

  • @yaroslavpanych2067
    @yaroslavpanych2067 Před 2 lety

    Hmm.. You still have check something in order to throw exception, so why in hell you say that exceptions are cleaner comparing to error checking?

    • @eyalz7766
      @eyalz7766 Před 2 lety +1

      Exceptions propagate automatically whereas return codes do not - consider throwing safe_divide(x, y):
      std::cout

    • @elijahshadbolt7334
      @elijahshadbolt7334 Před 2 lety

      It propagates the exception (like regular C++ exceptions), whereas returning error codes (or `expected`) requires you to check the return value for errors explicitly, at every nested function call.

    • @Solarbonite
      @Solarbonite Před 2 lety

      Exceptions are usually cleaner because you're not sure at which depth you or your clients are going to handle them.
      With error codes, in my experience it ends up becoming repeated error code checking at every level, which is annoying.

  • @Dziaji
    @Dziaji Před 2 lety +4

    Very bad explanation. He just started talking like we already knew this coroutine/exception paradigm. Then he blazed through slides without explaining anything.

    • @ILoveSoImAlive
      @ILoveSoImAlive Před 2 lety

      just work throu *java script promises*. its basicaly exactly the same and there are realy easy explanations for that. its just like syntactic shugar for call backs. on js side you will get enough overall information to have a good base for this talk.

    • @eyalz7766
      @eyalz7766 Před 2 lety

      The talk is kind of targeted towards people who have at least basic understanding of coroutines and exceptions. I am saying kind of because there are people that were satisfied with the short explanation of coroutines on the beginning but it's indeed hard to catch all of it that fast. Since this is a video, you can pause and read whatever you need in cppreference before you continue.

  • @mieszkowaw
    @mieszkowaw Před 2 lety +2

    So it's basically a C++ implementation of Rust's Result type

    • @jhbonarius
      @jhbonarius Před 2 lety

      No, that's what std::expected is going to be. This is similar, but uses a complex new feature... which imho doesn't add value here

  • @aaronr.9644
    @aaronr.9644 Před 2 lety

    Very interesting talk!

    • @CppCon
      @CppCon  Před 2 lety

      Glad you enjoyed it