 Everyone, let's do two minutes, project five questions, one more four questions. Although maybe we can say one more four questions maybe until afterwards we can go over some examples or something. But project five questions? Is that a good project question? Maybe. Let's stick with normal, let's prioritize, right? Normal project five questions. We're all done? This, laughter. Let's go with the opposite. Haven't started. Haven't started yet. That's bad things. Yes, we should start. It's going to get a little trickier than you think. And you have to think about it. You have to actually think about things. So that's why starting early is good. What's tricky? What should we not do? What should you not do? Yes. Why would I answer that? You should not wait to start it. Yes. Yes, if you start, because you can start thinking about it you can kind of off source and be like, hey, I think I did switch statements correctly but it's not actually working properly. And then I'll help you figure out why it didn't work. Cool. OK, front and back. I guess on the switch statements. So I imagine that you have to do some kind of recursive help because you don't want to have a case of difference. Say that again. You don't have to show them how many cases. Correct. Probably have to do some kind of recursive. Let's see. You can do it like that? Yeah, you're parsing. You could parse it recursively. For a switch statement, you don't know how many there is going to be, right? Is your parse statement list recursive? I mean, is it? That's more of a question. Because you can do that iteratively more recursively. So it really is up to you. You can do that either way. For those, I'd probably do that actually iteratively. Can I just find them now? Yeah, because you don't need to set up the if structure correctly, right? So you're going to make sure for every case statement that it's properly set up. Yeah. When you say iteratively, do you mean like when you start a switch statement, you'll go through until you hit like default or like right, right? And then you will store the statement? Yes. So you would create that because you don't know exactly how many case statements you're going to, right? So you could just like have a parse case statement that you call every time you see a case statement, and that will return to something. But then your switch statement, how to put those all together basically. So in this case, I think iterating over, like you see the first case statement, you have to call parse body, right? Because you don't know how to parse it. The parse body does. But then when you get that back, you have to figure out how to stitch that into your structure that you're going to return from the switch statement. OK, we'll say project five bonus questions and hover questions for after. So we are at the table here. So we should be able to get through runtime and start some landing calculus, which will be super fun. Everybody gets terrified because it has the word calculus in its title. The lambda? It's just a Greek letter. What if it was called alpha calculus? Would that be any scarier? That would be a lot scarier. Much scarier? What about omega? I feel like I could do it. You could do that. You're like, omega's. What if it was like an umlaut or something? Umlaut. Oh, that's right. What is like an o with an umlaut over it? I guess you're German. Isn't that right? I took German. Right? All right. Well, I'll just change it the way you say it. OK, NEA in Spanish, right? NEA, NEA calculus. That could be a thing, right? Is that any scarier than lambda calculus? No, that's actually the biggest calculus percent. Lambda sounds kind of goofy too. OK, so what we're trying to understand here is, oh, I'm not there. OK, yeah. OK, so we want to see, we want to somehow support local functions, right? So we've been trying to think about, OK, how can we actually, well, writing, so it seems kind of pretty easy how we could write local functions as a programmer from the programmers perspective. What we're trying to think about, right, is how can the compiler actually support local functions, right? And so let's look at an example of kind of what we want, what we would want out of this to support. So here we have some function foo, and inside it, it's declaring a local variable x, and then foo declares a local function bar, and inside there, that actually declares a local function baz. And it's going to set x is equal to x plus 1, and if x is less than 10, we want to call bar. So can we do this with the scoping rules? Because bar, so where's the name bar valid? So if we go with our normal C style rules, right, if we have a function declared in the global scope, that name's valid there inside the function and after that function, right, in that scope, in this case it's the global scope. So here bar, right, bar is available to be called inside a bar, right, and it can also be called from foo, but can another function call this function bar? Sure. So inside bar. Outside, yeah, outside foo. If we have another function main. Oh, no. It's local. Right, it's local, just like local variables, right, you can't just reach into a function and take out its local variable. Just like here you can't reach in and take out that, or call that local function. Then we have bar calls baz. So what's going to happen here? So I guess let's first see, we set x equal to 0, then we call bar, and then we print out the value of x. So what's this program going to execute? Let's initialize here. Should we print out 11? I think 10, yeah. But the question, the really interesting thing, right, is trying to think about what's the call stack here look like. So function foo is called. The first thing that happens is it sets x to 0. Then it calls bar. So we call function bar, and bar defines a local function baz. And then from bar we then call baz. And then we increment x. So x was 0, now it's 1. Is 1 less than 10? That's easy math. Then we call bar, which calls baz. Does it declare a separate instance that declared the same one? Is it a declaration? Yes. Describe a little more what you mean. So if you call multiple instances of a function, and each one of those instances is on the call stack, you'll have a local instance of some variable in that function. Does the same thing happen for a local function? Ah, so that is part of the question. I mean, it's actually part of how is this implemented, right? It kind of goes back there. So with the first call of bar. So let's think about this, right, baz. Is the code of baz going to change? So if you think about it, there's global functions. We have a function called itself multiple times. There's not different copies of the code of that function, right? It's not a global function. But it is not a global function. But what is the fact that this is local? What does this actually change from putting this in a global scope? So what are the two, there's two main differences here. It exists once. It can only exist once. In declaration ones. Okay, yes. It can only be declared once with that name. Yes. And runtime, it will have its own function frame. Correct. Right? Exactly. Okay. So it's going to have its, so, but when we invoke these functions on the local scope, they're still going to have their own function frames, right? They have their own frames and everything. So what about, so we said that you can't call bar from outside foo. So what does that mean applies to this local foo? The scope, right? Because the scope of bar only exists inside foo. So local functions A affect scope. Does that have anything to do with having different copies of the code? No, right? We kind of saw that we could have the compiler just actually rewrite that local foo, local bar to a global bar, right? And that would be semantically the same. But what is happening inside Baz here? What is key to having this Baz as a local function? It's constancy every single time bar is called. What does that matter for the execution of this program? Does Baz have any local variables? No. No. So, but local regular functions that aren't local still have local variables, right? And those just live on the function frame and they're separate copies. So you don't need different code to do different local variables. But what is Baz doing here? This is a global variable? Oh, sorry. Global, not global, but local in the scope. Yeah, so it's kind of crazy, right? It's changing a variable that's outside its scope and that is not global. It's changing another function's local variable. Where does this x live in foo's function frame? Exactly. In foo's function frame, which is somewhere on the call stack, and is it a fixed difference from Baz to foo? No. No, right? Because every invocation of Baz is going to reference this same variable x. So does it keep track of how far away it is? Yes, so kind of. So we'll see that. But hopefully that shows that we don't actually need different instances of Baz. We don't need different code of Baz, as long as we can make sure that we're accessing the correct x. So does it use, so knows where it is in the function frame? Does it use the base planer to then calculate all the stack? Yes. So it knows they pass in, every function gets, what we're going to see in a second, but they get a link pointer that points them to their lexical parent. Because here, Baz's lexical parent is bar, that's scope, and bar's lexical parent is foo. So I know by accessing those links, I can properly get to the variable x. Whereas I can't just do that at runtime, because I don't know how many functions there are between Baz and bar and foo. We'll see an example of this, but everybody's seeing what's going on here, how this function is executing, right? So we were here, we had one, we called bar, we're inside bar, we call Baz, x is now two, we checked two is less than 10, we call bar, which calls Baz, two is three, we keep doing this until x is going to be 10, so this check will not pass. And then we have this huge call stack, right? But 10 instances of bars and Baz, they will all eventually return, and finally we will print out the value of x, which is 10. So it's less than, it's the key to the question about 11 versus 10. Okay, so we compiled this code in assuming a compiler that actually supports local functions. GCC does not support this, this is not going to work, but assuming that we did, but other languages do, right? So we're looking at C because we've been looking at C and because you've been using it in your project, but other languages, Python supports this, JavaScript, it kind of supports it, yeah, it definitely does, it does. It gets a little more complicated though, yeah? Is there a C compiler that actually supports this? At this point you're not C anymore, right? You've extended the C language to be something different with local functions. So is there a modification to GCC or one of the other compilers or a completely different compiler that supports this C plus this thing? Probably. GCC is open source, you can make it support it. So is that LVM or Clang or whatever, right? I'm sure somebody's done it. That would be kind of a cool class project actually. Okay, so if we do this, it will output 10. So the question is, does the C-deckle calling convention that we've been studying, can it actually support this? It could be that the calling convention supports it, but C didn't want to add that overhead to that complexity so they just didn't do it, right? The calling convention is separate from the language itself, right? So let's kind of look at this, right? So we're in here, so this is just the call stack, right? So here it's not the actual stack here, we're just abstracting this to see these are the function frames that are on the stack, right? So we first are calling foo, so foo is on the stack. Then foo calls bar, so bar is on the stack. And then bar calls baz, so baz is on the stack. We increment x, now the question is, how does this baz, right, this instance of baz, where on this stack does x live? We're talking about normal C-deckle. Where does it want to go, where does x live? In foo, right, it needs to access foo's function to change the value of x, right? It needs to access foo's function frame, right? So let's say it does that. Then we call bar, then we call baz, right? And here we can maybe say, well, just go to whoever called you, right? So we actually have, we saw that every function of location saves the parent's, the parent caller's base pointer, right? And that's in a fixed location. So a function can actually go up using the base pointer to see who called it. And this is actually exactly how anybody, oh, I'm gonna get really sad when I ask this question, so maybe I shouldn't, but anybody used GDP to look at the stack trace. If you do like stack trace or ST, it shows you all the function calls that are up to your breakpoint. I got some of your nodding your head. This is exactly how GCC does that, is it looks at the base pointer that's saved for every function, and it can see exactly what functions are being called there. So this is how it does it, right? So you can think, well, yeah, we could do that, right? We just go up to access x in there because the compiler knows the offset where x is from foo's base pointer, right? So as long as I get foo's base pointer, I can change x. So we say, okay, that's fine. That'll support that. But now I call bar again, which then calls baz, which then needs to access x, right? But can I just go to up? I'd need, so what could I do? Let's talk about some, or some ways you could do this without doing the way we already talked about. So what do I need? What do I want to find on a stack? Access is variable x, right? What do I want to find? Function foo's, yeah, function foo's frame on the stack, right? But we looked at x86, we looked at cDecl, right? Does it actually know which frame belongs to who? No, right? Each function just has, I know the parent, I know who called, like, where to return to with the saved instruction pointer, and I have the saved base pointer of my parent, right? That's all the information I know. But what if we could change maybe cDecl calling convention to store the string of the name of the function that's called, and then we could use the base pointer to just look up and say, keep going until we see a function named foo on the stack, and then access x based on there, right? We can do that, you can actually do that, and that would work here. Because the whole point is, we don't know exactly from what we have with just these links here, we don't know how many to go up to find that foo. Because here we need to access foo here, and so we have to keep essentially crawling up the function frames until we got to that foo, but we have to know that we actually got to foo. I guess technically if you're being smart about it, you wouldn't use a string, you could give a function maybe just an integer, and as long as that's in a fixed location, then you can know exactly what it is, or maybe you hash the name or something if you want to avoid those things. But would that work? We may not work with. How would that work with cross-module compilation? I guess you could have a link or do it. Sorry. Okay. So this is where another way to do it, what we know is we know that bar's needs, bar's parents, we know that the lexical scope here is that bar when it accesses x, a baz accesses bar's parents scope. It's accessing x in foo. Lexically it's only two steps up in the scope, thinking about symbol tables and scopes. And for bar, if bar accesses x, it would know it only needed to go up one lexical scope. So the idea is we add an access link to the calling convention. So EVP, the saved base pointer, saves the caller's base pointer, the dynamic caller. But we want the base pointer of our lexical parent, not our caller's parent. We want a pointer that points to our lexical parent's... Yes, you definitely need both. Yes, you want both. This is what we want. This is what we have. This is what we want. So we want to add this, essentially. So we need to add another element to our calling convention. So we add an access link, and this would be our lexical link that would point to our parent. That way, a function can follow access links until it finds the correct scope. For instance, here, on the right-hand side, we have the base pointers, the base pointers. Each saved base pointer points to its parent. Now for here, every instance of VADS is going to have a pointer to its parent's lexical scope with the access link. So what's lexically here? What's VADS' parent's scope? Bar. So is this VADS going to point to this bar? No. No, why? Yeah, it's going to be the first bar on the stack, because there could be separate invocations. So that is where we do have lexical functions. So if every instance of bar set X to be zero, then only calls to VADS inside that bar would access that zero. So this VADS will have a link to bar, but what does bar have a link to? Foo. Foo, right? It has a link to its parent Foo. And similarly, this bar has a pointer to bar. This VADS, sorry, has a pointer to bar. And this bar has a pointer to foo. So this way we know, the compiler knows X is two parents, two hops away on the access link, right? So this means it can write code that says, when I want to access X, follow the access link once, and then follow that with the access link again. This way we wouldn't have to keep track of the names. Exactly, yes. We don't have to deal with names or anything. But we have to make sure that when we call a function, we're passing its parent's classical frame. And this way, we have the nice thing is when we're getting way down here, right? We have all these access links, or these, sorry, the base pointers, which point to our callers. And then we have all of these links so that every instance of VADS only needs to travel two links to get to X. Passing on this, it's kind of cool, right? So you have kind of like a crazy language feature that you're like, yeah, that makes sense. You know, like straightforward-ish. Right? But then how do you actually implement that? So the computer actually implements the semantics of what you want from the language perspective. This mess of errors makes sense. So this is how... This only applies to global functions, right? Yes. Or does it apply to global functions? It would not need... Global functions can only access local variables and global variables, right? You could maybe think of the global scope as one function frame, right? So you could maybe do this with, like, global variables on a global X scope. But then I think you would have issues with, like, cross-compilation. So is the... The access link is a thing that only gets trapped in a language where this is supported? Yes. Yes. But you can do it, and you can do it very efficiently, too. Right? So here, VADS and BAR, right, is a compilation question, right? The code for BAS and BAR is compiled and is the same, right, just like for any function. Like, it doesn't live in the code of foo or anything like that, right? But it just knows... So that when BAR calls BAS, it knows it needs to pass in its own... It's... Because BAS is lexical parent, right? BAR is the... the access link for BAS, so it passes that in when it calls BAS. And just like when BAR calls foo, or when foo calls BAR, right, it knows it's its lexical parent, so it knows to pass in its... its access link. Its base pointer has the access link. Cool. All right, I'm going to stop this, actually, and then split this lecture into two.