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

Поділитися
Вставка
  • Опубліковано 28 лип 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
    UA-cam Channel Managed by Digital Medium Ltd events.digital-medium.co.uk
    *--*
  • Наука та технологія

КОМЕНТАРІ • 29

  • @PedroOliveira-sl6nw
    @PedroOliveira-sl6nw 6 місяців тому +1

    Finally a good use of coroutines

  • @tomerdbz
    @tomerdbz 2 роки тому +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 2 роки тому +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 2 роки тому

      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.

  • @jhbonarius
    @jhbonarius 2 роки тому +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 2 роки тому +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 2 роки тому +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 2 роки тому

      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 2 роки тому

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

    • @shushens
      @shushens 2 роки тому

      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.

  • @shri314
    @shri314 2 роки тому +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.

  • @TheBlaizard
    @TheBlaizard 2 роки тому

    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 2 роки тому +3

    That was a great talk, congratulations!!

    • @CppCon
      @CppCon  2 роки тому

      Thank you!

  • @ericcurtin412
    @ericcurtin412 2 роки тому +2

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

  • @etunimenisukunimeni1302
    @etunimenisukunimeni1302 2 роки тому

    "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.

  • @aaronr.9644
    @aaronr.9644 2 роки тому

    Very interesting talk!

    • @CppCon
      @CppCon  2 роки тому

      Glad you enjoyed it

  • @yaroslavpanych2067
    @yaroslavpanych2067 2 роки тому

    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 2 роки тому +1

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

    • @elijahshadbolt7334
      @elijahshadbolt7334 2 роки тому

      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 2 роки тому

      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.

  • @goswinvonbrederlow6602
    @goswinvonbrederlow6602 2 роки тому

    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 2 роки тому

      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)

  • @mieszkowaw
    @mieszkowaw 2 роки тому +2

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

    • @jhbonarius
      @jhbonarius 2 роки тому

      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

  • @Dziaji
    @Dziaji 2 роки тому +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 2 роки тому

      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 2 роки тому

      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.