CppCast - Coroutines

Episode Date: July 19, 2017

Rob and Jason are joined by Gor Nishanov to talk about the C++ Coroutines proposal. Gor Nishanov is a Principal Software Design Engineer on the Microsoft C++ team. He works on design and stand...ardization of C++ Coroutines, and on asynchronous programming models. Prior to joining C++ team, Gor was working on distributed systems in Windows Clustering team. News Verdigris: Qt without moc Trip report: Summer ISO C++ standards meeting A C++ review community Future Ruminations Gor Nishanov @gornishanov Gor Nishanov's GitHub Links CppCon 2015: Gor Nishanov "C++ Coroutines - a negative overhead abstraction" CppCon 2016: Gor Nishanov "C++ Coroutines: Under the covers" Wandbox: Coroutines with Ranges Compiler Explorer: Coroutines clang demo Sponsors Backtrace Hosts @robwirving @lefticus

Transcript
Discussion (0)
Starting point is 00:00:00 This episode of CppCast is sponsored by Backtrace, the turnkey debugging platform that helps you spend less time debugging and more time building. Get to the root cause quickly with detailed information at your fingertips. Start your free trial at backtrace.io slash cppcast. CppCast is also sponsored by CppCon, the annual week-long face-to-face gathering for the entire C++ community. Get your ticket today. Episode 110 of CppCast with guest Gore Nishina recorded July 19th, 2017. In this episode, we talk about some of the news out of the Toronto ISO C++ meeting. And we talk to Gore Nishina,
Starting point is 00:00:54 Principal Engineer at Microsoft. Gore tells us about the C++ Coroutines proposal. to C++ Coaching's Potokal. Welcome to episode 110 of CppCast, the only podcast for C++ developers by C++ developers. I'm your host, Rob Irving, joined by my co-host, Jason Turner. Jason, how are you doing today? I'm doing pretty good, Rob. How are you doing? Doing good. How'd your training go?
Starting point is 00:01:44 I think it went well. I got some great feedback, and maybe I'll see which one I'm going to plan to do next in the future here. Okay, sounds good. Well, at the top of every episode I'd like to read a piece of feedback and we got a lot of feedback last week. So, Jason, normally we get pretty good feedback on Twitter and Facebook and everything and we did get
Starting point is 00:01:59 at least one listener saying, you know, he always listens to CppCast on his way to brunch and he said he enjoyed this week's topic about cute without what makes cute bad for C++ devs and said it was enlightening as hell but we got some other mixed feedback as well
Starting point is 00:02:16 and I think we kind of stumbled on a little religious war with cute devs there were a couple who I guess, were upset that we didn't press the Copper Spice developers on some of the changes they were making. And a couple different listeners pointed us to this article
Starting point is 00:02:38 that does a comparison between Qt, Copper Spice, and this other library that you can use with Qt, which is called Vertigris. Okay. Yeah, so I'll put a link to that in the show notes. And if you are interested in reading about another library that works with Qt, it looks like this one changes the way... It gets rid of mock, but it still creates the meta object generation at compile time. So it handles the mock problem a little bit differently
Starting point is 00:03:11 than Copper's Pice, is my understanding. So if you're interested in that, we'll put this link in the show notes, and maybe we'll talk more about that topic in the future. Yeah. Well, we'd love to hear your thoughts about the show as well. You can always reach out to us on Facebook, Twitter, or email us at feedback at cpcast.com.
Starting point is 00:03:31 And don't forget to leave us a review on iTunes. Joining us today is Gor Nishinov. Gor is a principal software design engineer on the Microsoft C++ team. He works on design and standardization of C++ coroutines and on asynchronous programming models. Prior to joining the C++ team, Gore is working on distributed systems in the Windows clustering team. Gore, welcome to the show. Hey guys, great to be with you. So, you know, I guess, how did you end up on the compiler team?
Starting point is 00:04:01 Well, if I track down what I've been doing, I was always jumping between the languages things and doing some production at some point, getting frustrated with the tools that I'm using and going back. So I think initially in Russia, I was on the cross compiler team that were doing software compilers partially for space industry. Then after the Soviet Union fell apart, I started doing random software, like, for example,
Starting point is 00:04:34 Ink It Forward WordPerfect. You know, back in the 90s, we had pen computers, and Microsoft had Pen API. So you can take a WordPerfect document and then scribble on top of it with your pen, and then you can send this augmented document.
Starting point is 00:04:50 Then I went back to school, RPI, at Rensselaer Polytechnic Institute where Alex Stepanov and Maser collaborated on the STL. It was all infused with C++, so in 1996 I thought I learned how to use C++ properly.
Starting point is 00:05:07 So I went back into the product team. So it was a Windows NT team. And after applying C++ extensively for 15 years, I got frustrated. So I decided to go back. I mean, future, stoop future is what triggered me. That, like, it's so wrong. I have to go and start working on that. Oh, well, you know, now I need to know
Starting point is 00:05:31 if we should wait until the actual interview portion so you can tell us all the ways that std future is wrong, or if you want to tell us now mild and calm. But that's what prompted me, at least. Yeah, the asynchronous programming and tools that are not sufficient enough in C++ that I decided to go and try to help. Well, I will bring up StudeFuture in a little bit, but I've got to ask, while we're still talking about your bio, what was it like working in the space industry and compilers involved and that kind of thing? I was young, I didn't know. I simply was a writing compiler front-end, was modular, and also worked on the register allocator in our
Starting point is 00:06:19 middle, or close to the back-end. But then there were people who actually middle or close to the back end. But then there were people who actually communicated and were sending the data. I never interacted directly with the space industry. Okay. So what year was that that you were working on that, if you don't mind?
Starting point is 00:06:40 Early 90s, maybe end of 89, 90, 91. Okay. I was just thinking about automatic register allocation and how the register keyword has never really been terribly useful in my opinion. Well. Was it useful back then in the late 80s, early 90s? Or were you still just doing what you wanted to do?
Starting point is 00:07:04 I was doing a modular front-end. Okay. So modular, modular, modular. It did not have any register keyword. It's the register allocator that is doing lowering
Starting point is 00:07:19 after you're done with the syntactic sugar. Right. Okay, well, Gore, we've got a couple news articles to talk about. after you're done with the syntactic sugar. Right. Okay, well, Gore, we've got a couple news articles to talk about. We'd love to get your thoughts on these, and then we'll dig more into coroutines, okay? Sure.
Starting point is 00:07:34 Okay. So this first one is Herb Sutter's trip report from the Toronto ISO C++ meeting that just finished up a couple days ago. And it sounds like this was a pretty eventful meeting. The big headline that came out of it is that the concepts TS has been merged into the draft of C++ 20. Gore, did you make it to this one? Oh, yes, yes.
Starting point is 00:07:58 It was a very exciting meeting. So concept, for example, for a while there was a big struggle to get them in but people were disagreeing about some of the natural syntax for them and they wanted to have more to type and eventually
Starting point is 00:08:16 deciding to separate the natural syntax from the rest of the proposal. Yeah, and as of now... It started moving forward, and Andrew Sutton and Richard Smith and Core was frantically carving out the proposal,
Starting point is 00:08:39 because I think it's relatively unusual that such huge change was done on the working paper during the week. So they carved, then they healed the wounds, they re-reviewed the code, and it was approved for merging into C++20, or at least the working paper that targets C++20. Then, you know from the article and from the other reports is that we got four TSS out. We got three techno-specifications,
Starting point is 00:09:15 network and coroutines, and ranges, and finally modules moved into the PDTS stage, which is preliminary draft techno-specifications. So a lot of stuff was moved during this moved into the PDTS stage, which is Preliminary Draft Techno Specification. So a lot of stuff was moved during this meeting. It was fantastic.
Starting point is 00:09:31 So for our listeners who are familiar with concepts, if I understand correctly, in the proposal there was three different ways with which a concept could be introduced, and now we've removed some of them or something? Yeah, we removed two. So essentially, there is a full syntax, which looks like a template syntax, but additionally, you can say requires,
Starting point is 00:09:57 and you put concept names in places where you had type name or a class, right, in the template introducer. But additionally, there was a natural syntax or function-like syntax where you can simply write a function and instead of saying int, you can say a number
Starting point is 00:10:15 or something like that, and it will be essentially a template function with restricted template parameter to just the number concept. So that was removed. And then there is another version of introducers where you have some relationship between the concepts which was slightly more complicated but still less
Starting point is 00:10:39 cumbersome than the full template syntax. It was also removed. So we right now have only one, the biggest one, and then people agreed that they really want the short syntax as well. It'll just take a little bit longer to get there. Okay, so you think the short syntax will make it in after it stays in the TS
Starting point is 00:10:57 a bit longer and maybe has some of the language changed a bit? Oh, I really hope so. I think people initially want to have big in-your-face syntax, but after using it for a while, they get tired of it. They're like, okay, I know it's a concept. Okay, okay, let me just write it simply. So I think that that will happen. Just from the usage, people would start using with the heavyweight syntax, and they would want a lighter syntax at some point. Was there anything else, maybe not related to coroutines,
Starting point is 00:11:30 that you wanted to call out from Toronto that was big news? Oh, yes. There were a few little things. One of them I liked was designated initializers. Have you seen those? Yeah, I did see that. Yes. I was curious about the syntax. How do you do the dot member name
Starting point is 00:11:49 instead of just member name equals whatever value? Well, it came from C. So there were three design goals for Genesic-leaded initializers. And one of them improved compatibility with C because if you look at Linux kernel, it's all over the place. So everybody's using this dot field name notation.
Starting point is 00:12:11 So because of that goal, we went with exactly the same syntax as C. But it is so much better than C. Really. Let's see what you guys think.
Starting point is 00:12:27 So in C, these designators, they're optional. So you can use on half of your members. You can say a name,.a,.x. And then others, you just put numbers or whatever values are. In Z++, you either use normal initialization or you use designated initializers. You cannot do half and half. So if you
Starting point is 00:12:54 choose to say.x equals 5, you cannot just say 3, 4, 4y. If you go that way, you go all the way. Another cool difference from C is that in C you can do them in random order. That's one. And second, evaluation order of the expressions that you put there
Starting point is 00:13:15 is unspecified. Well, in C++ we're saying they must be in the order of declarations. And the order is left to right. I think it's wonderful. Okay, okay. So we're thinking about the fact that objects are going to be initialized in the order that they're declared inside the struct, regardless of how we did the initialization, designated initializers, right? But you're enforcing it by saying the designated initializers
Starting point is 00:13:48 must be in the same order they're declared, correct? So, okay, then why don't we go and fix class initializer lists so that they are required to be in the order that they're declared also? Well, one step at a time, but I think we put a foot in the door with this one. So essentially, these initializers, I really love them in a way how they cleaned up the same facility which came from C. And there are other little tweaks
Starting point is 00:14:20 like the one I described, which are very nice. Okay. That's interesting. I'm going to have to play with that. Do you know if any compilers currently support that syntax? Clang. Clang does? Okay. I assumed it had been tested in some compilers already. Well, yeah, the proposal came with Richard Smith and Chandler
Starting point is 00:14:39 in the list of authors, so I assume that they actually implemented before proposing right okay okay this next thing we have to talk about is another proposal coming out of meeting C++ and this is for a C++ review community and I thought this was
Starting point is 00:14:59 a pretty interesting idea basically if you want to you know if you make a library and you want to get some feedback on it, there's a new subreddit set up, which is cpp underscore review. And you can make a post about your library there. And this isn't starting until August. We'll have to see how it actually works. But you'll be able to go on and look at this person's library, submit feedback, and hopefully the library authors get some good feedback. And at the end of the process, you'll maybe get a meeting C++ review badge for your library
Starting point is 00:15:35 to show that you were successfully reviewed. I thought that was a neat idea. Yeah, I agree it's a neat idea because the boost process is big and difficult. Not everyone wants to be a part of Boost. So just having some way to say that, yes, this library meets some minimum set of standards or something, it's a good idea. Yeah, and not every library should be a part of Boost. You might just have a very small library that just doesn't fit in there.
Starting point is 00:16:04 Or a very big library that you don't want to try to go through the effort. Right. Well, I'm wondering how people will be able to donate time to do those things. For example, in open source community like Clan,
Starting point is 00:16:22 you do it by barter, right? You review somebody else's patches they review yours or maybe you bribe them with gifts and other offerings or something like that uh but uh since uh since this certification process is not geared to any particular project uh i am just curious how it will work out i I mean, it's a great idea. I just, I want to see how it goes. Sure. Right. Yeah. Hopefully some people are interested in, you know, giving their time to it. Okay. And then we were talking about futures earlier. Sean Parent wrote this article on, titled Future Ruminations. And he's kind of going into a deep dive about his thoughts
Starting point is 00:17:08 about futures and the future of futures. And since we talked with you, Gore, about how that's how you got involved in asynchronous programming, I was wondering if you had any thoughts on this. Oh, it's a wonderful article. There are a lot of good things there about cleaning up the interface to the future,
Starting point is 00:17:30 treating it as the handle to work and consolation, which is very important. But I'm not sure whether futures will be something that we will be actually using in the future. So I do not see the future for the future. But I'm trying to avoid puns, but they just happen.
Starting point is 00:17:54 With the future, it's very easy. But essentially, you can have several ways of writing your asynchronous code. One is using callbacks, right? And that is not very convenient, at least for some people. They hate writing state machines. Others probably would use coroutines, which are the same state machine simply written in an imperative fashion. So if you use coroutines, you don't need full functional futures. You just need some kind of ability to explain what happens after you resume the coroutine, and we can talk about it later. So composition of your code using future with dot, and other operations, I'm not sure there will be use cases for that.
Starting point is 00:18:50 That's some pretty strong words. Well, yes, because there is a much more powerful construct like a reactive string. I'm not sure whether you have seen RxCPP or reactive extensions in Java, JavaScript. But essentially, those are streams of values that appear in time. So imagine the timeline, right, going to the right. And then you have zero or more values appearing at some point in time.
Starting point is 00:19:22 And then in the end, your sequence either never terminates, so values keep coming, or it terminates with an error, or it terminates because there is no more things. So for those reactive streams, there is algebra. There are hundreds of operations, and you have extremely expressive declarative programming model where you're describing what you want to do and you can see you know programs which is very difficult to write normally takes five six lines to
Starting point is 00:19:58 write with those reactive strings so I think that this will be the two extremes for people who want to write declaratively, reactive streams would be something that people would compose in a similar way how ranges TS works. With ranges, you have those little combiners and you compose multiple actions that transform your streams or iterables. And in the end, you get something else. But it's a nice, it's a high-level description. So you don't deal with implementation details. You just say how you compose those operations. And similarly, with reactive asynchronous streams,
Starting point is 00:20:37 there are actions you can do, and there are a lot of them. So I think in the future, we will see those two extremes. If you want simply to write as in code and express your code procedurally as the procedural function do that, do that. If something else, do this or else
Starting point is 00:20:55 and then sometimes wait on a bunch of activities and then do that so that will be expressed like a normal function but with co-await as the coroutine. And on the other end, we will have declarative programming with reactive stream. Future just not expressive enough.
Starting point is 00:21:15 It is a workaround for the absence of coroutines. So I do not think that futures will be something that we will be using. So the reactive streams, are they dependent on coroutines also, or is this a completely separate proposal? It's a completely separate proposal, and they can interact with coroutines, but they're independent. So usually a coroutine would be on the consuming side. For example, in coroutines we have for a wait statement, which allows you to iterate on a synchronous stream.
Starting point is 00:21:54 So that was put there with the noise that reactive streams will come. So the coroutine could be on the consumption side of the reactive stream or on production side of the reactive stream. Or you can even have an element where you co-await something and then yield. So you will talk about that maybe
Starting point is 00:22:17 in the coroutine part of the discussion. But you can mix a weight and yield in the same coroutine. And the result is a synchronous stream because we can suspend and then yield values, but in a synchronous fashion. We do not do it in a block in a synchronous fashion. So coroutines will interact with those asynchronous streams,
Starting point is 00:22:39 but those two programming models, procedural and declaratives, they are both very useful because they can express different things in a nice, concise fashion. And those are the two things that will, I think, remain. I'm not sure what is the future for the future. Again, no pun intended. It's just that, how else would you say it?
Starting point is 00:23:01 Right. Okay, so since we're talking about await and yield and everything, for listeners who haven't heard or aren't too familiar with the coroutines proposal, could you give us an overview? Sure. So at the high level,
Starting point is 00:23:16 coroutines are functions which get two more operations. So normal functions, they have two operations, call and return. That's all you can do. There is a call syntax, you know, f, parens, and the new pass some parameters, and then inside of the function body, you have a return statement that you can provide a value to return back to whomever called you.
Starting point is 00:23:40 So coroutines get two more. They have a point where a function execution can be suspended and control returns back to its caller. And then there is a way to resume the coroutine. And how you resume it depends on the semantic of the coroutine. So in C++, unlike in other languages, we decided to make coroutines open-ended. The language does not define
Starting point is 00:24:06 the semantic. I mean, eventually in the library we will get coroutine types that have some defined semantic, but as it stands today, coroutines are just a language facility which allows you to annotate where in the function
Starting point is 00:24:21 you can suspend and give an ability, a glue, to add in library abstractions that will explain what happens when coroutine is suspended and resumed. For example, the simplest one would be a generator. It's a coroutine which produces
Starting point is 00:24:43 potentially infinite lazy sequence. So from the outside it looks like an iterable. So you say generator of int, f, and then whatever is inside. So you will use it the same way like you use any
Starting point is 00:24:59 iterable that has begin and end. You can go to your range-based for, you can give it to any standard algorithm, which takes begin and end. With ranges, it's even easier. You just pass this coroutine, which is an input range, to any algorithm which expects
Starting point is 00:25:16 range. And then, whenever your algorithm does plus-plus on an iterator, it jumps back into the coroutine and continues from the point where it was stopped. So it is a way to write a state machine, but in a procedural fashion. So you write your code and say,
Starting point is 00:25:35 co-yield some expression. When you reach co-yield expression, that value that is being yielded will become available to an iterator which consumes the result of the coroutine at star, at the reference iteration on the iterator. And when you do plus plus, you jump back into the coroutine right at the point after it was yielding something. And it'll continue.
Starting point is 00:26:06 So this is... I'm sorry, I thought there was some question. No, go ahead. Okay. And this is similar to what we have in Python, I think now in JavaScript, in C Sharp, in Dart, and in many other languages. So it's not unique in that respect.
Starting point is 00:26:26 What is unique in C++ is how efficient they are and that they are fully open. You decide what does it mean to yield. We don't have a prescribed way, prescribed generator. You can plug in your own if you dislike what we offer in the standard and right now just to give an ability to people to experiment and come up with the awesome generators that we will standardize in the future
Starting point is 00:26:54 we don't even offer you a generator but it takes only 15 lines to write so maybe 50 if you include the iterators and a lot of comments. So this is one use case. Another would be an asynchronous task. So it's before you use callbacks or maybe future.then is that you are saying,
Starting point is 00:27:17 I'm going to call the sync API. And when it continues, and when, for example, I'm trying to connect to a socket. And once the connection is established, you will resume the coroutine from that point. So it allows you to write code as if it was synchronous. The only exception is that at those points where your synchronous code would block, you would use co-await to signify that you want, at this point, to suspend the coroutine.
Starting point is 00:27:48 And then, in co-await mechanics, there is a way to explain to the compiler how to resume the coroutine once the task is finished. And then, so far, we looked at two different cases. A simple asynchronous task and a generator. But you can mix and match, because it's you who defined the semantic of those. So you can say, if your function contains both yields and a weight, then the abstraction it represents would be an asynchronous stream,
Starting point is 00:28:19 which can produce values, but it can suspend in between producing the values. Interesting. I wanted to interrupt this discussion for just a moment to bring you a word from our sponsors. Backtrace is a debugging platform that improves software quality, reliability, and support by bringing deep introspection and automation throughout the software error lifecycle. Spend less time debugging and reduce your mean time to resolution by using the first and only platform to combine symbolic debugging, error aggregation, and state analysis. At the time of error, Bactres jumps into action, capturing detailed dumps of application and environmental state.
Starting point is 00:28:55 Bactres then performs automated analysis on process memory and executable code to classify errors and highlight important signals such as heap corruption, malware, and much more. This data is aggregated and archived in a centralized object store, providing your team a single system to investigate errors across your environments. Join industry leaders like Fastly, Message Systems, and AppNexus that use Backtrace to modernize their debugging infrastructure. It's free to try, minutes to set up, fully featured with no commitment necessary. Check them out at backtrace.io slash cppcast. Now, I want to clear up something, if that's all right. You've mentioned ranges several times, and I think that you're saying, like,
Starting point is 00:29:35 just a normal begin-end iterator pair like we're used to currently, but how does this interact with the ranges TS proposal? I mean, the ranges TS that's presumably coming up also. It interacts wonderfully. Casey Carter written adapters, which allows to plug in coroutines into ranges. And it's even available on a wand box. So I can send you a link so you can see how Coroutine plays with ranges. Because
Starting point is 00:30:08 I think in classification that Casey did, Coroutine is an input range, because you can only read from it. You can never go back. And essentially, whichever
Starting point is 00:30:24 algorithm can work over the input ranges, you can give to that algorithm the coroutine itself. And then the algorithms would pull the values out of the range, and every time you pull, you jump inside of the coroutine and continue
Starting point is 00:30:39 from the latest yield point. So we'll definitely have to share that link in the show notes if you can give us one. Yeah, sure thing. Definitely something our listeners would want to see. So what's the current status of the coroutines proposal? Was there any big news for coroutines out of Toronto?
Starting point is 00:30:58 Well, it was published as a technical specification. We have now two implementations, one in Clang, another in MSVC. And Clang we missed 4.1 by just, I think, a week. So it will be in whatever the release comes after 4.1. But hey, if you can build your clang from trunk, go ahead. And Eric Fizili here already added the library support, so we are complete there.
Starting point is 00:31:31 You need to grab both the clang and the library that comes with it. But if you don't want a library, that's fine too, because it's just one little file that is required for coroutines to work. So there is this little file that defines the glue which links language support with coroutine abstractions that you want to provide.
Starting point is 00:32:00 And it's a very small file which links those two together. You can just cut and paste it. You don't have to even have an entire library. So have you been involved in both Visual Studio and the Clang implementations of coroutines? Yes. Okay. Yes. Especially in Clang and all of EM
Starting point is 00:32:19 because there, yes, I did probably most of the work. In MSVC, I had help. Okay. I thought I had heard that in LLBM. Hold on. Richard Smith provided initial implementation of the parsing of the coroutines, but then he got busy with something else, so I plugged in the back end
Starting point is 00:32:45 it's where the heart of the coroutine is and added whatever was missing in the front end to work with the back end implementation and the good part is that now LLVM itself has coroutines which is in the back end thus people in Rust and in Python and other languages which target LLVM IR can get the same benefit
Starting point is 00:33:11 or as efficient coroutines within reason in their language as C++. Yeah, that's what I thought that I had heard, and you just confirmed that it's actually an LLVM IR thing. It's primitive in LLVM, I guess, is to coroutine. Yes. So there is a bunch of intrinsics, and you can describe your coroutine in pure LLVM, and then LLVM will actually turn your function into a coroutine.
Starting point is 00:33:42 And the reasoning behind is that we wanted coroutines to be very efficient. And initially I thought I could completely implement coroutines in Clang because it has this wonderful AST and you can party on it and rewrite, change it, but
Starting point is 00:33:58 after some experimentation, we ended up roughly with the same decomposition into backend and frontend as in MSVC, where coroutine is fully represented in LLVM IR as a function, and it travels through the optimization pipeline as a function. And the reason for that is optimizers are very good at optimizing functions. They are not very good at optimizing state machines. So essentially, we describe coroutine as a normal function.
Starting point is 00:34:32 It is optimized like a normal function. But just before it is eligible for inlining into its colors, then we do the transformation of a coroutine into a state machine so we delayed as far as we could the transformation so that we get most inline optimization opportunities as possible Can you speak to how efficiently MSVC is also able to do this?
Starting point is 00:35:06 I am not going to talk about that. Because, well, the reason is our initial implementation of coroutines in MSVC, the purpose was to verify the glue, to verify the model. So we focused just to make it work. So that was the prototype, and Clang got all of the benefit of knowing how the original
Starting point is 00:35:29 implementation was done. No, it's not a prototype. It's a production version. It's just that we optimized for different things. We were trying to prove the overall design,
Starting point is 00:35:46 but it's still usable. It's just not as efficient as we would like to yet. But in LLVM, we went into a different approach. We just said, okay, let's see, can we get it optimized the way we like? And that was the focus because the rest of the coroutine design was already verified and embedded in MSVC,
Starting point is 00:36:09 so we focused just purely on the optimization opportunities. Okay, so we've brought up this optimization and all this several times now. So how much overhead is there in using coroutines and in adding the subtraction? Depending on the scenario, it could be zero. For example, in CppCon last year, I showed that generator optimizes into a constant. If you are iterating over generator over a fixed range.
Starting point is 00:36:51 And even though, in general case, coroutines involve heap allocation, for common cases we eliminate those heap allocations. So the rule is very simple. As long as the lifetime of a coroutine is fully enclosed in the lifetime of its caller, we place the state of the coroutine onto the caller's frame. So thus, if you have a generator, you know, 4x over some kind of fancy generator, then you loop over it. That entire coroutine is purely on the stack. If you use structured programming, and by that I mean that you break your big function into smaller functions,
Starting point is 00:37:39 even though you don't even call them more than once, it's the same thing. Now we're going with asynchronous tasks. So if your asynchronous task is broken into subtasks, which are broken into further subtasks, and you simply call a wait on that subtask, again, lifetime of the subtask is fully enclosed in the lifetime of a task. So it becomes simply a state, it's is local variable on the caller side
Starting point is 00:38:08 and then all of the inlining works like normal so for a whole bunch of scenarios we are making them zero overhead okay since you have coroutines in MSVC now
Starting point is 00:38:24 do you know if anyone's internally using coroutines in MSVC now, do you know if anyone's internally using coroutines? Yes. Has it changed how they're programming? Are programmers at Microsoft really enjoying them? Yes, yes, yes. We had several teams using them in production. We also have external developers using coroutines, for example. You know Tidal?
Starting point is 00:38:48 There is a music service Tidal. Some kind of music streaming something. And the unofficial Tidal streaming app is written with coroutines. And you can see on GitHub and search for Coia Wait, and you will find them. And inside, we have several teams using the coroutines and also modern CPP. I think you talked with Kenny Kerr some time ago. Well, it's all based on coroutines. Not only on the use side, but also on the production side.
Starting point is 00:39:23 So to generate the projection, he uses generators. But on the consumption side, when people write async code, well, entire WinRT is asynchronous. So without coil weight, it's extremely painful to work with it. So it does sound fair to say that this is proven and you're using it. And so are you expecting
Starting point is 00:39:52 that we'll see it merged into C++20 like Concepts was soon? We have merged criterias and one of the part of the merged criteria, one implementation not tainted by me. So one coroutine implementation,
Starting point is 00:40:09 which is done from the technical specification wording as opposed to, you know, from talking to Gore, what does it mean? And to a degree, EDG might count as that Edison design group. But ideally, we would love GCC to implement it too so we can compare and see who is faster.
Starting point is 00:40:32 That, I think, is an overlooked... We have the tendency to complain about not all the compilers being up to conformance in particular categories, whatever. But I think it's one of the key strengths of C++, personally, that people complain about I think it's one of the key strengths of C++ personally
Starting point is 00:40:46 that people complain about without realizing it's a strength that having these competing implementations verifies that the spec makes sense and verifies that we have solid compilers. Right. So I think to streamline TS process, last meeting in Kona,
Starting point is 00:41:06 there was a discussion of having a merge criteria. And once merge criteria are met, it's nearly automatic merge process. Not sure whether it will work in this way, but maybe. So in the case of a coroutine, criterias are one independent implementation, a bunch of tests so that people can verify implementation against the tests, and adapters so that whatever is already in the working paper interacts nicely with coroutines. So
Starting point is 00:41:43 if we have future.then in the working paper of the standard, then we'll have adapters that will work with coroutines. And we will throw in a generator just in case. Does the spec make any overhead guarantees? Or maybe to phrase it a different way, is the test suite that you're working on going to have anything that tests the overhead of the implementation? I think we will add one just for the kicks
Starting point is 00:42:14 because it is a bragging right. We cannot demand it to be that fast or that good, but it would be fun to compare compilers. Right. It strikes me as one of the few core language features that at least because, maybe just because I have a hard time wrapping my mind around what exactly it's doing,
Starting point is 00:42:39 that is difficult to actually demonstrate why it's a zero overhead. If that makes sense. Well, just go to Godbolt and see what's going on. Well, yes, always can do that. Yes. You've mentioned the coroutine-specific keywords a couple times, like co-wait and co-yield.
Starting point is 00:43:00 Is the co- underscore going to be the final language? Because I know if we watch one of your videos from two years ago at CppCon, you were just saying await and yield. Yes, I fought valiantly. But I couldn't. It essentially started with yield. Yield was the problematic keyword. Okay. And some people felt that return is too confusing.
Starting point is 00:43:30 And then for consistency, how about we'll add core everywhere? And now there is actually a benefit of having a core return because unlike other languages like C Sharp, Dart, and others that have similar await and yield keywords, they all require some kind of tag on a function. For example, in C Sharp, it's async. In Dart, it's sync star. In Rust, they're talking about fn star or something like that. So that allows them to give a different
Starting point is 00:44:07 meaning to await or yield keyword within the coroutine because they know the context. In C++ coroutine is just a function that has correturn, co-yield or co-await. You don't have anything extra on the function signature or a function definition. And that is part of the design because we wanted to have seamless interaction with the rest of the language. We did not want to have a different corin convention or change the type system.
Starting point is 00:44:38 So from the outside, coroutine is a normal function. It is undistinguishable from any other function. It's only the implementation. Oh yeah, yes. For example, assume that dot then in some form will appear in the future. In the future.
Starting point is 00:44:58 And your function says std future of int f blah semicolon. You have no idea whether it's simply rendered as a coroutine or as a normal
Starting point is 00:45:14 traditional function. It's simply implementation detail. You can flip back and forth. So why did I go there? Oh, I know, I know, I know. I was going for explanation why correturn is actually useful. Sometimes you just want to write
Starting point is 00:45:30 a do-nothing coroutine, essentially, that it never suspends. Maybe you just started typing it. So you can say correturn semicolon, and boom, your function is a coroutine. Or maybe correturn space 42. So, thus, function is a coroutine. Or maybe correturn space 42. So thus you have a coroutine
Starting point is 00:45:49 and it doesn't require extra tag. So that particular usage of correturn made me less annoyed with co underscore. Okay, so what is the difference between a function that always returns the same value and a coroutine that always returns the same value?
Starting point is 00:46:08 I mean, like, if you just did co-return 42, like you were just saying. Well, the function will be identical. Because coroutine is simply, no, that way around. Function is a special case of coroutine. Okay. So coroutine can do everything that a function can do, but better. It can do more things.
Starting point is 00:46:29 But when it is doing exactly the same thing as a function, it is a function. Okay. And that is something that I am trying to, let me see, break consensus. So coroutines were absolutely awesome 60 years ago, in 58, because they were understood
Starting point is 00:46:50 as a better function, as more general form of a function. But then after our goal 60, stacks came around, and block-scoped variable came around. And those were hard to implement without extensive compiler support.
Starting point is 00:47:07 So people took an easy route. They implemented coroutines on top of user-mode scheduled threads. And that is a much heavier abstraction than a function. And at some point, people start calling user-mode scheduled threads or fibers. They start calling them coroutines. Then they start disambiguating them with, oh, it's a stackful coroutine, and whatever is not a thread, it is a stackless coroutine. But essentially, those are two different things with very different costs of usage costs.
Starting point is 00:47:52 And right now with compiler-supported coroutines, we're bringing those good coroutines of the old, which are generalization of functions back. And usage of coroutines as a cheaper thread is less. Okay, so if you today, when you're writing code, do you just use coroutine, coreturn, you know, like, do you take
Starting point is 00:48:17 advantage of the fact that coroutines are a better function and just use them everywhere? No, because usually because there is potential suspension involved, you probably have a different type that we use normally.
Starting point is 00:48:33 So if it's a generator, you likely use some generator type and you want to yield. So if it's future, if you used to write a function that returns a future, you can switch to using coroutines trivially.
Starting point is 00:48:51 But if your function never suspends, why do you want to return a future of int or some equivalent of future of int as opposed to plain int? Right, okay. You were talking about threads a moment ago. Is there any interesting interactions between coroutines and threads that we should know about?
Starting point is 00:49:13 Well, if you implement an asynchronous coroutine, the one with square weight, which is trying to represent some async activity, well, it's your choice or the choice of the library writer whether you will be resumed on the same thread some machine activity, well, it's your choice or the choice of the library writer whether you will be resumed on the same thread as the one that got suspended or a new thread. And you have to be careful there. And some people prefer to be resumed on the same thread and that can be encoded in the
Starting point is 00:49:40 await adapters, which tells the compiler what to do when await happens. But it might be surprising. TLS is one of the things which might switch when you go to a different thread. So if you read something from TLS, when you resume slightly later, you will be reading from a TLS for a different thread. So for our listeners who are familiar, you're referring to thread local storage. Yes. So you're saying if you have a thread local object in your coroutine, you access that, somehow return it, whatever,
Starting point is 00:50:15 and then the next time your coroutine is resumed, it's from a different thread, you're going to now have a different local state that you weren't expecting to see, effectively. Yes, you will be consuming the thread local storage from whatever thread is currently running. Interesting. Which is actually wonderful. Which is wonderful, because
Starting point is 00:50:34 let me tell you what happens when it's the other way around. So, like, you know there are fibers, there is boost coroutine, so compilers, in some architectures, thread local access is expensive. So they would go and cache an address of a TLS page in some of the registers, which are preserved between function calls. And then imagine that
Starting point is 00:51:05 you suspended that function in a stackful manner, which essentially means you have no idea that you got suspended, because some function 10 levels down didn't suspend. And then you resume the coroutine
Starting point is 00:51:21 on a different thread. So now that register points potentially into some other memory. So it is actually very dangerous not to do what happens in the C++ coroutines with regard to thread local storage. Right. So essentially with things like boost coroutine, if you resume your stackful coroutine on a different thread, it is undefined behavior if you touch thread local. Like you should not touch thread local at all. Okay.
Starting point is 00:51:58 So I just want to make sure I understood one thing. If you're running a GUI application and you call a coroutine and you want to make sure you resume on the UI thread, is that something you can do? Yes. And what does that calling syntax look like? It's all the same. It's call await
Starting point is 00:52:16 on something. And since it is the library that describes to the compiler what happens when you call await, you can do things like you capture the current execution context, which threads you are running, and then when you resume the coroutine, you will simply say resume on that thread.
Starting point is 00:52:35 But it depends on your environment. So coroutines were made to work with arbitrary runtimes. So as long as you can express that in your runtime, you can teach coroutines to interact gracefully with it. So coroutines come naked. It's just a compiler feature. But by design, it was meant to plug in to whatever asynchronous runtime you have
Starting point is 00:53:04 or UI runtime. So do you think that coroutines are going to fundamentally change the way we interact with C++? Only if you do async stuff, maybe UI. And it will be easier to write iterables. So, like lazy sequences, it's another use case. But it's mostly, I think, those two. Asynchronous programming and lazy sequences.
Starting point is 00:53:35 But otherwise, you'll be doing exactly the same way like before. Well, maybe one more. Coroutines also allow you to write clean code without exceptions, because every await point can be customized to work like if error go to end. So, for example, if you have expected of t type, which expressed either a value or an error, you can make so that when you co-await on expected of t, if it contains a value make so that when you co-await unexpected of t, if it contains a value, the result of the co-await expression
Starting point is 00:54:10 will be just t, whatever that value is. But if it is an error, it will propagate as the return value of the entire function, of the entire coroutine. So this is another use case. Interesting. Yeah, it was, I think, of entire core routine. So this is another use case.
Starting point is 00:54:28 Yeah, it was, I think, we had five design goals. Scalable, efficient, interact with C gracefully, open-ended, meaning customizable to whatever on time. And the last one was exceptions are optional. Interesting. Definitely some people who would be interested in that.
Starting point is 00:54:46 Well, like driver writers we didn't want to make coroutines tied to the exceptions in any way we can interact with exceptions just fine but you don't have to and because of that property you can actually express something like
Starting point is 00:55:02 exception like nice clean control flow because all of the error propagation will be handled under the covers by the library machinery that you explain to the compiler what happens when you go and wait on expected of T
Starting point is 00:55:17 or maybe an optional of T or whatever you want. Okay. Well, Gor, I think we may have gone over everything. Is there anything else you wanted to bring up before we let you go? Well, one thing. Rob is actually doing the intro to the CppCast live
Starting point is 00:55:38 because I thought it's always the recording because it was so, so, so identical every time. And now I'm seeing it live. It is incredible. Well, Gore, thank you so much for your time today. It's been great having you on. Thank you, guys. Thanks for joining us.
Starting point is 00:55:57 Thanks so much for listening in as we chat about C++. I'd love to hear what you think of the podcast. Please let me know if we're discussing the stuff you're interested in. Or if you have a suggestion for a topic, I'd love to hear what you think of the podcast. Please let me know if we're discussing the stuff you're interested in. Or if you have a suggestion for a topic, I'd love to hear about that too. You can email all your thoughts to feedback at cppcast.com. I'd also appreciate if you like CppCast on Facebook and follow CppCast on Twitter. You can also follow me at Rob W. Irving and Jason at Leftkiss on Twitter. And of course, you can find all that info and the show notes on the podcast website
Starting point is 00:56:25 at cppcast.com. Theme music for this episode is provided by podcastthemes.com.

There aren't comments yet for this episode. Click on any sentence in the transcript to leave a comment.