 Alright, it's 1.30 or 15 seconds still, so close enough round we got to count. So, good to see everybody, I missed you guys. A week and a half, I know, I know. So sad, so sad. We'll start off and we have questions on Homer3 and Hindley Milner type inference. Anybody? Somebody may be over there in a band's t-shirt. It's too specific. Can you generalize it? I don't know. You want to see, like, in the example, the PowerPoint that you put the video up, at the end there's a very large example of it going through the wall. You want to see, like, a table like that be over-placing, like, t-section, so it's kind of like all the other stuff in the class so far, right? If you end up with the right answer then, I don't really care, but that table helps you think through how you should be doing it, right? So that can help you work it out and if you mess up the final answer, right, we can go back and grade that and give you kind of partial credit to say, okay, correctly calculated the type of this node and this node and this node and this node, but, okay, you make one mistake here and act as gated through all the other. I know the video. Can you see any instance where there was an error? Were you going to say, like, I'm just not matching? Did that happen somewhere? Oh, I'll say yes or no. It can happen. So one of the examples, the earlier examples, has a type error, right, where we set, like, A is equal to 10, which meant that A has to be an integer and later on we used A as a function, right? So we had two conflicting types. We can't unify integer and function, right? So that's a type error. Okay. Any other questions or any buddies, friends, or neighbors want to volunteer them for a question? Yeah. Do you, yeah, you should mention why there's a type error, right? You want an explanation. So, like, at this node, you try this here, because of this constraint, A is an integer and because of this constraint, A is a function, you can't unify those, so it's a type error. Any other questions? Yeah. Can you speak up a little bit? So it means that if you have, that means that if you have, so do you see a function call, right? You know that you have, the first time you see a function call, you know you have some type of that function that is being called, right? So if later on that function is called again, now those parameters have to be the same type, you have to unify those parameters as well. So it has to satisfy both of those constraints. Project four questions? Yeah. So yeah, I was going through it again. So I saw that, like, the first half to, basically, I would call the first half, but yes. The first part. And then the second part is trying to determine the types. Right. And determine if there are certain types of errors that occurred. So should we go about implementing the parser completely and then worry about the type of data structures that we're going to implement to determine if the types are people who are not learning anything? Yeah. So the question is basically the first step of project four is to create, complete the parser. So you're given an incomplete parser. It's missing, I think, five different parsing cases. So the question is, should we do that first and then go to the typing or do the typing first? I'm sure if I keep in mind the data structures that we would save different... I would say no. I would do the parsing, get that 100% working so you know that that's working because test cases, right, are going to presumably have those structures in there. So you can't type those structures. You're going to fail. It doesn't matter who your type checking is, right? So yeah, you need to be able to have that, have the whole grammar so that that way as you're parsing you can keep track of whatever type information you need. When you have... When we have all of our inference types... Do we infer that the two types must then be the same or do we say that there's a type that's the same? If they're both gendered. Oh, then they must be the same. So yeah, if a type is being used, so that's all about propagating constraints, right? So you know when you see that that same type somewhere else being used or that same variable being used somewhere else, you know that those types have to be the same. You may not know exactly what it is yet until you continue doing your type checking, but yeah, you've got to make sure those are the same. Yeah? Will you use the same constraints that we have for the project, for the homework? More or less. Oh, the same constraints for the project? Yes, yeah. You can use that. Anything else? Project four? Yeah. If we had an operator type less than an operator then... So we say like both the types are the same type? No. So that's... A numeric type isn't really a type, right? It's a grouping of types. So that's in the project four description. That's kind of what that question was about, right? The project four description specifically states what the type constraint should be on relational operators. So in that case, the only thing that matters are the types are exactly the same and it returns a boolean. So you can use that same logic in homework three. All right. Anything else? Oh, let's get started then. It's been too long. I don't remember how to do this anymore. All right. Okay. So now we've looked at type checking. We've gone all the way through, if you remember, all the way back to the beginning of class, right? We started with, well, how do we define significant tokens amongst a series of bytes in our files and then how do we properly compose those tokens into a grammar so we can make sense that looks like a programming language. And we said, okay, now we know how to parse this grammar. We can write a parser for this grammar. We can create a parse tree. Then what we did before is we tried to say, okay, well, what are the semantics? What do all of these operators specifically mean in our parse tree? What does it mean when we have A plus B or even, I don't know, X is equal to assigned the value of 10 and the dereference and address of operators. So, and then we talked about type checking, right? So then how to say, okay, do the types in this program make sense? What does the type system look like? How can we actually check that? And then we looked at some really cool stuff on how to automatically infer the types of a program. So now we're going to kind of shift a little bit and try that. We're actually going to go pretty in depth here because we're going to talk about the runtime environment of the, of that the program is executing. And so we've talked, abstractly, about locations and names. So when we talk about semantics, what's the, what are the difference between locations and names? Oh no, it was three weeks ago. Yeah. A name is bound to a location. A name is bound to a location? Yeah. So what kind of the location represent what we're doing about circle diagrams? An address? Yeah, I'd say a location and memory. So a location had an address, right? So it's kind of was representing some locations, you know, in memory or somewhere that had some address. So what we're going to be concerned with now in this first part is how does the compiler actually implement locations and names, right? So the compiler is going to take in a program, generate the parse tree, type check that parse tree, and then it needs to find locations, right? It doesn't have these abstract box and circles like we've been talking about. It has a CPU with registers and memory, right? So we're going to look at how does the compiler map names to memory locations? How does it keep track of that? How is this kind of actually implemented in essence under the hood? And how do those, maybe some of those things then go back and help us think about how these languages and programming languages are implemented. So just to be clear from here on out we're going to assume static scoping and all of these examples. So we don't have to worry about dynamic scoping. So we know, it's going to say lane. I said yay. Okay, good. Much better than lane, I guess. Unless maybe you're like a really big fan of dynamic scoping. We could do that. Okay, so we're going to focus only on static scoping to be very familiar to what you're used to. Okay, so global variables. What are global variables? Somebody refresh our memory. So a variable that's ever deallocated in the scope of a program. So then, okay, so I guess I kind of being mentioned it, but. So the question we kind of want to ask is where can the compiler literally place or where can it reserve some location for this variable? So what are the options available to the compiler? You can say yeah. Stack. It could place it on the stack. Yeah, what's the stack though? What is it? Does your, if you look through your computer, is it like a stack somewhere? Yeah, I'm talking like physics, like maybe not exactly physical, but yeah. With the stack, it's in those registers that are, I mean, I guess it could put it in the cache. It could put it into the dynamic range. It could put it. Okay, so yeah, we have memory, so it kind of, I guess, depends on what we're talking about as our model of the processor and what the processor can see and can do. So it's got a few things, right? So what does the processor have that's, let's say, incredibly fast that it can work with? Not the cache. Registered. Yeah, so why not the cache? So we're going to see if you directly access the cache. It should be no. There could be weird ways to do it, but the cache is basically some bits that are closer to you and a lot faster actually on the CPU, and they cache some memory locations. So that way when you access them, you're accessing them really close to you locally on that CPU. So what's also very close to the CPU, I think somebody said, is the registers. Right? So there's, the compiler could decide to store global variables in the registers, right? It has the advantage that it's super fast, but maybe what's the downside there? What was that? Yeah, so we may need those registers. Those registers are very fast, so we want to use them for calculating. Somebody else said something over here. Yeah. Yeah, limited space. There's only a fixed amount of registers. I actually should have looked this up. You know, the register account on the x86 processors. I think it's like only six or something. For five or six general purpose registers. Somebody looks that up and then raises a hand. That'd be awesome. So we can place it in registers, right? On the chip. Where else could the compiler place the values? So we kind of talked about the stack, but what is the stack really? What was that? Virtual memory. What was that? Oh, virtual memory. So from the point of view of the program, the program actually has no idea that it's using virtual memory or not, right? So the virtual memory is a layer between the program and the actual hardware that's managed by BOS. To the program, the program gets all the memory. Yeah, the program can basically store it in memory. Eight registers. Awesome. Do you have a list of them? EBS? ECX? ESI? EDI? Wait, EDI? EDP? ESP. And then there's, is EIP in there? EIP. Okay, so yeah. Eight registers. There is the EIP, which is the instruction pointer, but if you as a program can't really control that, you control that all of a sudden, all that kind of stuff. Okay, awesome. That's good. It's going to come up in a bit. So we can store our global variables in memory. We can think of memory as this kind of big thing. We can store it on registers. Where else could we store? I don't know. I think crazy. What was that? Storage. What's storage? Hard drive. Yeah, you can store it on the hard drive, right? What else? Are you on the slides? I guess another way to put this would be somebody else's computer, right? So you could, yeah, you could store your global variables there, right? There's nothing that says it has to be local and it has to be in your process space or even on your computer, right? As long as your program can perform the computations. So we kind of mentioned it a little bit earlier. So what are the constraints on global variables specifically? So semantics wise, right? So what are the constraints on global variables? Or more, I guess more succinctly, or put another way, what are the semantics of global variables? That may influence where we put these things. Yeah. Yeah, so one thing is, because it's global, right, we said that it has to be accessible to every single scope or function in our program, right? Somebody else was saying something too. We don't want to share that out. Where's that coming from? There it goes. He's answering questions. Raise your hand. I look at it again. The sound all comes from everywhere. It's really crazy. Yeah, it can't be deallocated, so that's another thing. So not only it's valid throughout the life of the program, but that memory can't be deallocated. It has to always be accessible. So we talked about who can access that. Are there any limits on who can access or what code, what functions can access our global variables? Not really, right? That's the whole point. They're global, so literally anybody can access them. So, given these constraints, and I guess these four kind of varyingly different crazy options, where would you think would be a logical place to put these global variables? Don't want to argue for one side. Yeah. On what? At the beginning of the program, there's these four options. So which one did you choose? The disk. The disk. That would be really interesting. You could probably do something crazy like that. No, because the disk is, I can't remember the exact term, but the disk is like a thousand or a million times slower than memory, or than registers, which is... Yeah, you can do it in registers, but we talked about you have the problem that registers are being used constantly, right? So if we only have eight registers, but global variables, you know, if you have three global variables, well now we only have five registers to work with. And who knows, maybe we never actually even use those global variables that much. So storing those registers really doesn't get us a performance boost. What's one of the other options? Memory. Memory. Yeah, we want to store it in memory, but I guess we talked about it a little bit, right? So we have the stack, which we kind of envision as this thing that's constantly moving. So does that really make sense to put the global variables there? No, right? Why? Because there might be times when we're not able to access them. Yeah, depending on how the stack moves and changes, right? We want those global variables to be located at a specific location and to have that work. But the other way, the constraint is, anytime we access a global variable, no matter what function or where it is in our program, we need to know how to reference that memory location. And so if it's on a stack and the stack is constantly moving, changing, things are popping off of it, that may not be the best approach. So to answer this question, we're going to look at how GCC compiles a program with global variables to see kind of where it places the variables. So we have a super simple example. We have three global variables. We have a main function. We set A to be 10. So which A does this refer to? Global. Yeah, global A. Awesome, that was a test. B, it's referencing setting global B to be 100. Setting C to some random, I don't know, 10.45 to C as a flow, so we know we could do that. And then we add A and B together and store them back in A. Then we should return to zero. So, okay, we talked about, so compiler knows there's three global variables, right? So we know it has the name of A, right? So then well, essentially what the compiler does is decides, okay, this is where I'm going to put global variables. I'm going to save some memory in the program's address space. And so here, we're going to first go through it kind of symbolically kind of pseudo-coded. So the compiler says, okay, I'm going to store A at location A in address A, and I'm going to store B at location big B in memory. I'm going to store C at location big C. So when it compiles this code, what it's essentially going to do is say, okay, the memory at address big A is going to get the value 10, right? So everybody kind of see this syntax that's, you know, kind of weird, just pseudo-coded where it's saying, okay, we have all the memory. Whatever A is, A is just some compiler-defined memory address. Set that, copy the value 10 into that memory, those locations. And then copying this along, we do the same thing, right? We would set, okay, let's take 100, copy it in the memory location and specify by B. We do the same thing with C with that variable, and then we essentially say, sorry, this doesn't line up right, but I'd say memory A is equal to memory B and memory A is equal to memory A plus memory B, right? So that's essentially the computation here that's performed. And the return and stuff we're just going to ignore for now. Okay. But the compiler, right? The compiler really can't do these symbolic names. And the program that's executing has to know where do I get that memory address from. So when I compiled this program on CentOS 6.7, 32-bit, so it's x86 syntax. It decided, okay, I'm going to put A at location 80, 49, 63, 4 and B is going to be at 80, 49, 63, 8 and C is going to be at 80, 49, 63, 3, C and what did the 0x before these numbers mean? X. Yeah. So what base are they? 16. Yeah. So they're base 16 numbers. That's why you have a C in here. And so why are these all, so you can see these are four bytes or four addresses apart, right? You took the difference between B and A, right? It's four bytes, so why? So we're going to have that word size. It's a word size. Maybe. Yes and no. Yeah. It does have to be word aligned. It does have to be word aligned. There's also another reason though. Yeah. 64 bits. Four bytes is 32 bits, I believe. So this is specifically I compiled this for 32 bits. It's all X, A, 6. That's how you set it. Yeah. Size of int is 32 bits. Yeah. So size of int is 32. So actually it's 32 bits, so four bytes. So that's actually all those reasons are pretty much also valid. The mate and so if you think about it in memory, right, the compiler is just essentially saving these four bytes in between, let's say 34 to 38 for A, and in between 38 to 3C, 38 to 3C for B, and then the next four bytes after C for C. Yeah. So do they have to be one after another like this? No. Right? The compiler can do whatever it wants. It's clearly easier to do this, right? A lot less chances of things going wrong I don't know. It just makes it easier. Yeah. Also four bytes. On 32 bit float is four bytes. I did check that. And I believe a double would be eight bytes. Because it's 32 bits. Correct. So that's so the question is if we in our program took the address of A added one to it, which I believe should do pointer arithmetic and incremented by four bytes, the actual address by four bytes, we would be pointing at B and then we could with the star operator actually change that the value of B. I'm 99.99 percent sure that's probably outside the C specification. And so it'd tell you that it's undefined. So, you know, just like we said the compiler for alignment reasons or for performance reasons, right, could put those at different offsets from each other. I mean, I think it would compile. It would compile and it would work. It would work on this, yeah. Exactly. It would just be if it works exactly the same on every single system. I don't know. Is that okay? With stars, though, you have to actually send a type. Isn't that how you work? You work with a void star. You would send a type into a specific function and just use pointer arithmetic with the size of that type or something like that. And you would add... Yeah, you can do the same types of things with stars, but for this thing, specifically because you know they're actually laid out in this way, you could go back and you could use the address of A to essentially get to B and change the value of B without ever touching the address or a pointer to B or anything like that. But I think it would work in this instance, but it's not something that generally you could depend on. Okay, so now let's look at the actual XA6 code that this function generates. So everybody is definitely familiar with assembly, right? Yeah. Okay, you may not be exactly familiar with XA6 specifically, but that's okay because we're going to go through it slowly and the instructions here are not that complicated. They're kind of complicated. But before we get started, so assembly especially XA6 assembly, there's two different styles of syntax that people use. One is Intel syntax and one is AT&T and what that depends is, so here I have a move instruction where I'm moving you could essentially, the question is, am I moving from the thing on the right to the thing on the left or am I moving from the thing on the left to the thing on the right? So just to be clear we're using AT&T syntax and if you really, if you want to try this out yourself in the notes on the slides here I've put the command that I ran to get this assembly. So the syntax we're looking at here is the destination is always on the right, so from is on the left destination is on the right and we're moving the important thing here is what's the star mean? Sorry, what's the dollar sign here mean? No. So constant. So this is moving the constant 0xA so what's 0xA and base 10? 10. Yeah, so that's where this comes from, right? So we're moving the constant value 10 into 0x 8049638 634 and because there's no dollar sign in front of here, we know that this is a memory address and not a constant value. So this essentially says move 10, the value 10 into the memory location right here, which is essentially what we know as A, right? So this is what the compiler translate A is equal to 10 to. Questions on that? Okay, then the next line we're going to move 64 hex to 0x 8049638 So what's 0x64 in 100? Yeah, you could check I have to use like a hex calculator to do all these things just because I'm not at the leap level where I can add and subtract hex in my head. Okay, so now we're moving the constant value 100 into the memory address 8049638 which is we know is the memory address of B and so how did the compiler know to put these addresses here? Exactly, so yeah, so all these definitions at the top, so I will take one moment to just kind of step back and say there is, so we're talking about compile time, there's kind of two things there's compilation and then linking if you're compiling different object files and linking them all together so technically this is done at link time but it doesn't really matter, the point is the compiler decided hey, I'm going to put A at this memory address and I'm going to put B at this memory address and so now when I go to compile this code I know any place I'm setting A to B10 I know where A is, I know where the location of A is it's at memory address this memory address. Alright, so then we have the float, so now this is actually kind of interesting because the compiler generates two instructions here so the first thing that's going to happen is a move instruction, moving this hex value 41273333 into the register Eax so in the syntax that we're using the percent sign signifies a register so Eax is one of those eight registers that we talked about earlier and then it's going to move whatever is in Eax into memory 804963c which we know is the address of C so why did it do this so essentially what's the effect here yeah so what's the effect first so what is this value yeah, presumably I should check it but presumably 10.45 in IEEE floating point format which is a 32 bit format is this in hex I think you'd have to somebody can go verify but I'm pretty sure that's the case I think we're going to I was going to say how does it know that it's IEE floating point instead of just regular just confirmation I believe it's the C specification for a float I'm pretty sure it's the C specification that says all floats are 32 bit IEE floating point format I mean the C when you have the memory location at the very top of the float is there like a flag somewhere that represents at this memory address will always be IEE floating point values so the question is is there any guarantee so kind of the question is after the fact that we regenerate this assembly yeah because you just said floating before you said the float and you both have the same amount same size so what's the stopper in there for a whole integer value in there so what is the thing that's stopping us from doing that is EAX a float register? EAX is not a float register EAX is just a 32 bit register wouldn't type checking because that's a fine perfect yes it's a type system that actually prevents us from doing this exactly to your point to the CPU it's all just memory and they're all just bytes and bits right so it's only when you interpret them for something so like it's only when you're doing math on this memory address you have to actually use the floating the floating point operators to do the addition if you try to use the 32 bit or the integer operators to do addition, the two complement it would do it, it'll do it just fine because now at this point when we're in this assembly the program has no idea what's an int or a float or anything like that because all that information goes away and we actually compile it so that's the type information that makes sure that we can't write a program that does that even though the assembly code could do it and do weird stuff okay so essentially the effect is removing this value which is 10.45 moving it into EAX which is a register and then into the memory location so I don't know, anyone want to take a stab at why? I have theories, I actually don't know but yeah it could be, the compiler could be smart so although we can look at this and say well C is actually never used after this so this 10.4 so you know maybe it's a premature optimization or maybe it is an optimization I don't know, any other theories? yeah maybe it's a little more time to convert into a floating point yeah so probably so the main answer for most of these things when it comes down to why does the compiler do this, it's usually optimization so they usually try to do some, so remember x86 does not have so I think most people learn MIPS for assembly yeah so there I think they have it's a reduced instruction set every instruction is the same size so that all a certain number of bytes, the instructions but whereas in x86 instructions can be variable length so the compiler maybe through profiling or whatever realize that hey when I do this with these two instructions maybe the instructions are shorter together even though there's two of them then just copying that one the one value because in the instruction you have to define this whole so instruction has to have the value 41, 27, 33, 33 so the instruction has got to have four bytes at least and so I don't know it's a performance optimization but if you look at the maybe I don't know maybe we have time towards the end you can actually see the sizes of each of these instructions so it could be size or it could just be performance x86 has tons of instructions okay so what line are we at here 1, 2, 3, 4, 5 what have executed we've executed 3 so we're at 4, forgot to have 4 okay so now the code is going to do that computation A is equal to A plus B and so does everybody kind of see that these lines pretty much map identically to this abstract pseudo code we have here in the middle this is pretty much exactly what it's doing it's just saying hey copy this value into this memory address at this location copy this value into this memory address at this location alright so here it's very very similar so we say okay we know the address of A right we know A is located at 34 so we're going to move that into EDX this is a different register and then we're going to move 38 into EAX so now what's in EAX and what's in EDS so yeah what are those values 10 and 100 perfect okay so now presumably we have to do the addition right we're going to add them together this is one of these other things where we get in the weird compiler optimizations so it uses this assembly instruction called LEA which is load effective address this is so the way to read this so we know we're going to do something with the thing on the left that's in the parentheses and we're going to copy it into the register on the right we know that from the syntax so this one of those things we have to look up what this instruction means or the semantics of this instruction right this is basically another language we're learning essentially what this does I believe is takes EAX times it by the third parameter and adds it to EDX and then copies that result into EAX so this is essentially just adding EAX and EDX together and putting the results in EAX so specifically why it's doing this as opposed to another addition I think it's just crazy compiler optimizations but the effect is the same so have we finished this have we finished this instruction yet no cool so now we've done the calculation right but that value of A is sitting in the register it's not actually stored in memory yet so we need to actually move EAX into area 30 into location 34 so any questions on this? okay and this is pretty I don't know maybe clear maybe straightforward but we're going into detail so we want to see how these low level features of the processor affect kind of the language and everything going on and how we can actually implement the language features we want in this runtime environment so that's one of those you have to look at the semantics and the length so I think with a move L yeah I know things for long I think this saves it in that memory location so it's essentially point of view reference I know the memory location copy the value 10 directly to that memory location the L means long and I don't really remember why I don't know exactly why it's used here I'm just curious where did you get these memory or these so I have it in the notes on the slide I'll copy it into the slide when I repost it so there's tools you can run on your try to I think it's still recorded okay so we're here we looked at this let's do all this okay so any questions any other questions on this it's going to cause me to lose any more electors okay so you talk about global variables right so global variables pretty easy the compiler just decides where to stick it and it's a little bit more involved in that but the compiler essentially just says hey these are where they're going and then puts them there so what about local variables so what are the constraints on the local variables be allocated at the end of the scope yeah so local variables are deallocated at the end of their scope so that's one of them they're accessible within the scope so we don't really have to worry about anything outside of that scope accessing them so what would be I don't know what would be the easiest thing maybe I don't want to ask too many questions okay so one thing you can do you're writing a compiler, you're writing a language and you think well I basically just allocated essentially global memory for global variables why don't I just do that for functions too right I can reserve so I know how much space the local variables for each of the functions use right and I can reserve a lot of memory in that memory for those variables is this a good idea, bad idea yeah access, yeah so I could be over reserving or using memory in case I never call a function I'll still have allocated some memory for it yeah yeah so the question then is okay well what if I want to write a function let's say factorial and I'd say if n is 0 return 1 otherwise return factorial of n-1 times n so this work if this local variable or this parameter n if this is stored as a global variable why not yeah get overwritten right so if you only store this n in one global location every time you call this function you're overwriting that value and the function is going to try calling itself by an overwriting that value I'm going to keep on overwriting that value yeah and so you actually can't have recursive functions or even well so you can't have a recursive function you may not even be able to have you really in general can't have functions calling other functions because you have a function calling a function one of those other functions is then going to come back and call one of the functions that you already called then you clobber all the local variables so there's actually early versions of Fortran when you wrote didn't have functions as we know then it has procedures where procedure is just a bit of code and no ability to call other procedures so in this way they could do this this is how they did it, they just allocated global memory to use procedure and said boom we're done but in our languages we want recursion right we want the ability to call other functions and we want the ability to have those functions call themselves or call other functions and they call themselves and this beautiful beautiful recursion so we kind of talked about it but so then okay if we can't do like global static memory where might we want to put these local variables close it yeah put it on a stack or what I want to say is we kind of want so we think about global memory right as this defined here in this specific area right here's global memory and we can access it but what we really want is we want each function to have kind of their own private little memory we want each function to just be able to use the memory that it wants to use but have that be specific to each invocation of that function so that's where that comes in is the stack so the stack we've kind of been talking about it in kind of very abstract terms and now we're going to talk about it in super concrete terms about it exactly what it is and what we're talking about so the stack is essentially this is the kind of way I think about it is it's essentially scratch memory for the function most processors and assembly languages use some sort of a stack and so these are kind of the conventions so what are the what are the operations of a stack as a like a data type push and what and pop and what do they do more abstractly from a a queue is also can be thought of as like pushing and maybe popping the thing about pushing why does semantics different there you're still putting things in and taking things out yeah so a stack is last in first out so you are pushing things on to the stack and then when you pop something off the last thing that you put in is the thing that comes out where as a queue you're pushing a lot of things on you're taking the first one so it's a first in first out where a stack is last in first out so you just have to all of this that are lying here is pretty much just convention right so how the stack is actually implemented in memory on the processor could definitely vary but these are kind of things that would coalesce around so the way it works in x86 is the stack starts at a high memory addresses and it grows down so this could be you know when I use the word down I'm talking clearly visually down but once again remember this is just a convention right this is this is how I like to think about it you could completely flip it around and think that it grows up that's valid too but it's wrong to me no it just looks really weird to me I think of it like growing down moving like this way so the top is always constant and so the function each function can basically do whatever it wants to the stack you can use the stack as its own what I refer to as its scratch memory so it can push registers it can push values onto the stack it can pop values from the stack into registers and actually on x86 the assembly language explicitly supports these push and pop operations so the way this works is the register so we've heard about the eight registers one of those registers is ESB so we've talked about like EAX, EVX, ECX, EVX have you know what the E stands for what was it? extended me so it does stand I believe it does stand for extended but the semantics means that it's 32 bits so before they had a language that's very similar to x86 I actually don't know exactly what it's called but if you refer to like AX or BX or CX you're talking about the first usually the last 16 bits so half of a register because this is why all these things start with ECX so there's a specific register one of those eight registers its job is to hold the memory address of the top of the stack so ESPs holds the address of the top of the stack and so there's an instruction called push EAX which just like you think of a stack pushes that value onto the stack so you can do any register push it onto the stack and so the semantics here are at first and we'll kind of see this visually but at first decrements the stack pointer ESP so this is why we're going down so I start high values, high memory locations at the top until the stack grows to the bottom so we decrement it which means we're growing the stack down so we just created a location for EAX then we push we store the value of whatever's in EAX onto the stack of that location and pop works exactly identically but in reverse so it takes whatever the value is that ESP points to, the stack pointer points to puts in that register and increments the stack up so moves it up so let's look at an example okay so this is how why does it move doesn't ESP always point to the top of the stack so top of the stack I just realized is kind of missing over it points to the there's no other good term for it the bottom of the stack so it moves up to the previous scope don't even think about scope we're just talking about the stack all it is is a register so ESP holds the value you can push stuff you can pop stuff that's pretty much it I understand that what I'm saying is you added memory to the stack by decrementing ESP down correct and so to get that back we increment oh I understand what I'm saying I thought you're also pointing EAX to the current scope in order to figure out where in that part of the stack nope all we do is take whatever values so we're going to see in a second point to a certain value, take that value and then move the stack up for that's it I understand why you would move it up for because that's still the top of the stack alright so okay so we have remember like I've been talking about we have high memory so high memory addresses so what's the highest memory address on a 32 bit system all f's so however many s's it takes right so a bunch of f's so that's the top what would be the bottom zeros yeah perfect so I just kind of think of it as just and I kind of think of it as four four byte chunks but it's really just a bunch of bytes and so there's some stuff from where the current stack pointer is above us so let's say the stack pointer is here pointing at just this memory address it really doesn't matter because it could change throughout so after us so then the question is okay so so when I say top of the stack this is kind of what I mean is that the stack points to 10,000 packs so what's above this higher memory addresses but stuff that was put into the stack prior to yeah the stuff so legitimate stuff or older stuff or depending on when we're executing it's not our stuff so we shouldn't touch that but we know that it's valid stuff right we know what's above us could be legit is our legitimate values that somebody before us maybe was using what about what's after us perfectly fine exactly so garbage that right so that's the whole point is the stack grows down so we're gonna we can put whatever values we want onto the stack and we're gonna go right down you know so all that's basically after us we can consider as garbage we don't know what it is or where it is or whatever we don't care but we're gonna make sure we're done executing and kind of put the stack back where we found it right because otherwise we'll get to that in a second there's only one stack per machine in RAM that's kind of a global stack that you would keep on adding and pushing and pumping so here's the way to phrase it is every process has its own stack so different each process in your operating system each has its own stack they're all in memory because of virtual memory you don't have to worry about clobbering over each other or stepping over each other's toes okay so we're going to look at two super simple assembly instructions push dax pop ebx so the way I'm going to do this is I'm going to represent the register values in the lower the left eliciting and so there's as we learn there's eight values, eight register values but we only care about really three we have dax ebx okay yeah we have dax which has some value whatever we have ebx which has some value and we have esp so what's the value of esp yeah yeah so it's the address of the current value where the stack is pointing so it's pointing at 10,000 that's where it's pointing to so remember so there isn't actually an arrow or anything in memory or on the processor right this is just a visual representation of whatever is actually in esp so this is what controls where the stack is and where the stack is located okay so the first thing we're going to do is we're going to push dax um so this is the first instruction that's going to execute here so when we push dax what we're going to do first de-increment the pointer so we're going to decrement the es key essentially moving it down how far are we going to move it down how old of dax why because if we don't then there's not enough memory to store all dax right so dax is a fixed size right and so we're going to put whatever is we want to put whatever is in dax onto the stack so we have to make sure we're moving the stack pointer down enough to put whatever is in dax so from what I just told you dax is 32 bits so we're going to move it 4 bytes 4 bytes down so we're going to subtract 4 from 10,000 which is no we know it's in dax that's in dax so it's some hex value fffc which is then going to move the stack pointer where 1 or 2 sound like we're at the eye doctor 1 or 2 1 or 2 are they the same 1 1 or 2 1 or 2 2 why 2 we can use moving into memory that we can use there's a couple ways to remember right so one way would be well the stack is growing down so we can't actually go above 10,000 because somebody's using that memory right so we know intuitively we should be growing down we should be going down the other way to think about it is why know this value is fffc right and I know here's all zeros where's fffc going to be 2 yeah it's going to be this one here right so then what's this address assuming they're all 4 bytes 10,000 right 10,000 10,000 in hex okay great so we we successful okay now we haven't actually so now we're going to actually then so we did half of that right so we incremented the stack pointer for we started the stack pointer we moved the stack down 4 bytes then we're going to put eax in there in memory so now when we want to execute the pop instruction now we're going to do exactly the reverse of what we just did right so the first thing we did was decremented the stack pointer put the value so now going backwards we want to pop the value increment the stack pointer so we're going to then when we execute this we're going to take whatever's at fffc take that put it into ebx so it's going to be 0xa and then we're going to add what to esp4 and add 4 so we're going to increment esp by 4 which is going to move the pointer our stack pointer where up yeah so it's going to be right any questions on that very important before we go on yeah so where eax is right now and the stack is now considering garbage data exactly yes so kind of a little forward hinting if you recall we're looking at garbage values and we're looking at getting values that were deallocated what values they had and we saw that it changed depending on compilers and all that stuff that's because we essentially had the memory address of something on the stack and so through our function calls and through stack manipulation that value got overwritten so anyways we may go back to that we'll see so we talked about the stack so not only to get an intuitive understanding of what's going on but an actual physical real processor level understanding of what's going on at the assembly level so what is this all about why are we talking about this so the main idea is that for functions so we talked about it right the ideal place for functions to store their local variables would be on some scratch memory and it seems like a stack is a pretty good place to try and do that so the question is we want to use the stack to kind of allocate space for our local variables but can we use the stack pointer for this yeah so we could basically move the stack pointer down enough to allocate memory for our variables and then later in execution we can kind of refer to those values and use those values on the stack and then afterwards we can kind of make sure to bring the stack pointer back yeah that's definitely, we could definitely do that so what would maybe be a downside of that yeah you don't know where the scope of those local variables ends because you don't so if you move the ESP down then you have the top of the stack you don't know where that extra chunk ends that we just added so that's a very good point so we're going to definitely want to use the stack for our local variables but what else might we want to use the stack for so how many registers do we have 8 total right is every function going to use only 8 variables or less so what happens if we need to use more than 8 variables what about temporary variables right you can easily write an expression of 5, I don't know, a times b minus c plus 52 divided by 120 times whatever some other thing right you can easily create an expression where you need more than 8 temporary variables so that's why we want to store those yeah so we don't really want to store them in a global location right so we're going to have the same problem as recursion so we really would like to store those on the stack because it's our private little scratch area but then that leads us to a problem right so if we're changing the stack pointer but we we kind of said hey let's use this memory for these local variables with the stack pointers changing but how do I know how to get to every time I reference it is that why you move the sp back up almost so we want to move the sp back up when we're done so we want to leave the stack at the same location when we found it the main problem is as our function executes the stack pointer can change and ideally we want each of our so if you think about the global variables the global variables are kind of like so they're at fixed locations but we kind of saw you could think about it as well I maybe could have said hey let's start at this location and then each variable is an offset of that location so 1 is plus 4, 1 is plus 0 1 is plus 4, 1 is plus 8 so I may want to do the same thing here so I what I really want is some way to say hey somewhere on this stack that I've created for myself at offset 0 is variable A and at offset 4 is variable B and so that's where this concept of so instead of using the stack pointer which can change we're going to use this idea we use this idea of a frame pointer which points to the start of the function frame on the stack so you can think of the frame as being the part of the stack that your function is responsible for so that way you point that and we'll see exactly how this works you point that at the start of when your function starts executing and then you can use that as offsets and now the stack can grow as your function goes but you can always still use that to compute offsets so essentially what's going to happen is each local variable will be different offsets of the frame pointer and so in x86 it's a little bit confusing we'll use the term frame pointer or base pointer because it's in x86 it's considered the base pointer and it's stored in the register EBP so another interesting thing we thought we had eight registers now it turns out we use the stack pointer for important things and we're going to use this register EBP for other important things we really have a lot less registers than we think we do so let's look at kind of that similar example to our global variables but now bring those A, B and C variables into main scope so here we have our function main we have local variables now A, B and C A and B are ints these are floats we set A to 10, B to 100 C to 10.45 set A to B, A plus B and return 0 so now what does the compiler do to decide so in our pseudo code what does the compiler do for each of these variables move them into register we're going to ignore registers kind of for now because we want to think about so we definitely want to store these variables on the stack right so we kind of already said we want to use a base pointer and we just want to use essentially all of the variables are going to be offsets of that base pointer so that's all we're going to do is the compiler is going to define what these offsets are and it's going to say okay A is located at whatever is in EBP plus A B is going to be whatever is in EBP plus B C is going to be whatever is in EBP plus C then we set that memory location to be 10 this memory location to be 100 and this memory location to be 10.45 and then the final step is going to be this memory location so the big difference here from global variables right global variables have one static location but here instead of static locations we're going to use offsets from the base pointer does that kind of make sense okay so when I compiled this the compiler decided that it was going to put A at the base pointer minus C B at the base pointer minus 8 and C at the base pointer minus 4 and so this is how the assembly looks so it first so it first moves the stack pointer into the base pointer so why does it do this? to save where you're working for almost so to save so our function executes the stack is at a certain location and we want our base pointer to point to essentially the top of the stack when we start so that's what all we're doing here is saying the base pointer is whatever the stack pointer is right now so remember at this stack pointer point everything above us is garbage or everything above us is important and anything below us is garbage at this point so we'll see how this is used so we're saving that location then we're going to subtract what's OX10 in decimal for base 10 16 so we subtract 16 from the stack pointer so what does this in essence do to the stack pointer allocated so remember that memory is already there right so all we did was move the stack pointer down port down 16 bytes so where is EBP pointing to right now the original stack location so 16 up so EBP here and the stack pointer is now pointing to here so then what's EBP minus 4 where is that going to be so we have EBP at the top ESP at the bottom EBP minus 4 is that going to be above or below below is it going to be outside the stack no so essentially minus 4 and then minus 8 and then minus C which will be 12 right that's all in that space that I just you can think of allocated right because we're moving the stack down but so we're essentially creating room on the stack for our variables AB and C and now if any of that other code needs to push something on the stack or pop something it's fine it's not going to mess our offsets from our base pointer so these are pretty much the only things that are different from this code versus the other code the global variable so in this code we're doing the same thing we're going to move the value OXA which is 10 into mine so this is this syntax says take EBP and minus C from it so that's doing just this operation so move the value 10 into EBP minus C move the value 64X into EBP minus 8 what's EBP minus 8 B yeah it's the address of B and then we move that crazy value into EAX then we move EAX into EBP minus 4 and then we're going to move 0 8 EBPs what's EBP minus 8 EBP minus 8 is B so we move B into EAX so what's in the value of EAX in the EAX register right now 100 and then we're going to add EAX so when it's an ad like this it means you add EAX to whatever to EBP minus C and store that value into EBP minus C so this essentially this essentially takes EAX which was 100 adds it to what's in EBP minus C 10 8 right so yeah minus C is here the value is 0XA which is 10 we're just going to add that and then it's going to store it back in there so now at the memory address A which is EBP minus C we have the value 110 we're going to add that how did you know how did you know the offset the compiler decides the offsets so the compiler knows for local variables I'm going to use the base pointer and I'm going to assign each variable a different offset of the base pointer the compiler stores it when it looks at this function yeah so the compiler knows exactly how many variables I declared and it knows the base of all those variables so it can generate this subtract to create the space and it knows all the offsets that metadata the compiler uses it and then that offset is stored it has data itself it's the only data it is right here we're going to go into it we're going to go into it