 Hi, hello and welcome to the second lecture in the program analysis course. So in this second lecture we will look into operational semantics. The lecture will be composed of five parts and this is the very first of these five parts. So let me start by looking at the big picture. So in the last lecture we have looked into what programming languages actually are because programming languages obviously are at the foundation of program analysis and we had looked into how one can define the syntax of a language for example using a grammar. We've also looked at different representations of programs written in these languages like abstract syntax trees or control flow graphs and what we want to do in this lecture today is to look at how we can assign meaning to programs written in a language. So this meaning or semantics can be defined and we'll use operational semantics as a way to define the meaning of a programming language. All of this will be done in the context of imperative languages. There are other kinds of languages for example functional languages which we will not look at here in the course but instead we focus on operational semantics for imperative languages. In a sense these operational semantics are a formal foundation for specifying languages and also for describing program analyses which is why they are relevant for this course. Just to warn you so this lecture will be probably the most theoretical of the course. If you like it great if you do not like it that much stay tuned because things will get a little bit more applied and practical after this lecture on operational semantics. So here's a rough plan for the lecture. So we'll start and this is what I'll do in this first part of the lecture with a motivation why do we actually need operational semantics and then I'll give you some preliminaries basically formal concepts that we'll need in order to describe operational semantics. Then we'll describe the syntax of the language that we'll use in this lecture which is called SIP. It's a simple imperative programming language. So this is a toy language but nevertheless interesting to look at in particular for the purpose of formally defining the semantics of a language and then we look at three different ways how we can actually describe the behavior of a program in this language. The first of which is an abstract machine and then we look at two forms of structural operational semantics namely small step semantics and big step semantics. So let's get started with a few preliminaries that we'll need in order to define the semantics of a language. Before looking into what operational semantics actually are let's start with a question that you should always ask and that is the question of why do we actually need this. So the question is both about why do we need operational semantics and why do we actually need to specify the semantics of a language at all. And to answer that question let me just show you a small piece of C code and then I'll ask you what the meaning of this code is. So in this piece of code we'll define an integer variable i and assign a value to it let's say five and then we're calling a function let's call it f and we are passing two arguments namely i plus plus and i minus minus. And now my question for you and that's something you should think about maybe while pausing the video for a few seconds is what are the arguments that are actually passed to this function f. So if you think about what the meaning of this piece of code is there at least two options that may come to your mind. One option is that the arguments are five and five and this is what you'll get if the two arguments are evaluated from left to right. So that means at first the first argument is evaluated which is i plus plus the meaning of the postfix plus plus operator is to first return the value and then increment it so it will return five and then assign six to i and then the second argument is evaluated and because this is a prefix operator it means that we'll first decrement i so instead of six it's now five again and then pass the result of that decrement operation so the value five to the function f and as a result the arguments are five and five. But there's also another option which is option two which would be four and four and this is what you'll get if the arguments are evaluated from right to left. So if you first evaluate the value of the right argument then we first decrement i and return the value of this decrement the result of this decrement operation which is then four and now i has the value four then we move on to evaluating the left argument which we first return so the value four again and then increment it again so after that i will again have value five but the values that we give to function f in this second option would be four and four. Now you can argue which of these two options makes more sense and their arguments in favor of both of those. The funny thing is that both of these two options are actually possible in C so now you may wonder why this is the case and the answer in short is that C has unspecified semantics at least for some parts of the language and this is actually one of these parts. So the language despite being used by millions of programmers and many many lines of code has some parts of its meaning not really defined which essentially means whoever's implementing this language can decide whether the arguments are evaluated from left to right or from right to left and you may even change your mind a couple of times while running a program if you want. In practice what this means is that the compiler that is compiling a piece of C code is deciding what the semantics of this piece of code is and if you want to try this out you can try to compile this small snippet of code shown here with different C compilers and you'll see that actually you'll get different behavior depending on what compiler you're using. Now of course this is not ideal because as a programmer you want to know what your program is really doing when it's going to execute so what you really want from a language is that all or at least let's say almost all behavior should be clearly specified and one way to actually do this is to formally write down the semantics of the language for example using operational semantics. Now operational semantics which we'll focus on here in this course is just one way of specifying the semantics of programs just for the sake of completeness and to show you that this is not the only way let's have a look at different ways of specifying the semantics of programs. So first of all you can specify semantics both in a static and a dynamic way. The static semantics do not really say exactly what happens at runtime but they somehow constrain the possible executions that the program can have and even without executing the code will give you some guarantees about some aspects of this code and one way that I'm sure everybody is familiar with to do this is to have a type system which describes which type each variable and maybe each function has and without executing the code if the type system shows that the program is type correct you know that this variable will always have a value of these types so in a sense a type system is a way to statically assign some of the semantics to a program. And then we'll also have so-called dynamic semantics which come in in different forms so one of them is called denotational semantics we won't go into this so this is just so that you've heard it and maybe can put it into context if you have heard about it before. Another form are axiomatic semantics and finally there are also operational semantics and this is actually what we want to focus on here. So now what are all these semantics useful for? Well they're useful for a couple of things you've seen the C example on the previous slide but in general they are useful for at least four scenarios. One of them is for language design so if someone sits down and designs a language and for every programming language someone has done this at some point then of course that person wants to think about what is the meaning of the programs that you can write in this language and in order to really clearly define what the meaning of the program of these programs will be you somehow have to write it down and a typical way to do this is to specify the semantics using one of the formalisms that you see here. A second use case is for language implementations so if you are the person to implement say a compiler or maybe a virtual machine then of course you need to know what the program what a program written in a specific language is supposed to do because otherwise how can you implement its behavior. Then specifying the semantics is obviously very important for programming because if the programmer doesn't know what the program is supposed to do then how the hell can you actually decide what kind of program to write and finally and this is the reason why we are talking about all of this here in the course knowing the semantics of a language is obviously also very important for program analysis because a program analysis after all reasons about the behavior of a program and the only way to do this is if the behavior that the program will have is actually well defined through some kind of semantics. Alright so now I've hopefully convinced you that it's a good idea to define the semantics of a language and before we can do this we need to go through a couple of preliminaries which are basically kinds of formalisms that we'll use in order to define the semantics of a programming language. So let's get started with the first one which will be about so-called transition systems. So what is a transition system? So in essence it's a way to describe that you have states of a system and that you can transition from one state to another according to some well-defined rules and we'll use these transition systems to describe the states of a program and describe what happens when different parts of the program are actually executed. So these transition systems consist of two things. One is a set called config which is a set of so-called configurations or states. So states and configurations are just two terms that I'll use interchangeably. And then the second ingredient of a transition system is a binary relation. So a relation between two things that I'll denote with an arrow and this arrow is a binary relation over pairs of elements of this config set which basically means that it's describing how to get from one configuration to another configuration. And this binary relation therefore is called the transition relation. So using this transition relation I can for example say that I'm in some configuration C and then can transition to another configuration C prime and then this is called a transition or if you want to use the other terminology you can also say this is a change of state and what we'll use this for is to essentially describe a step of the computation. We'll assume that our transition relation is deterministic and this means basically that whenever we are in some state and we can transition to another state then there's only one other state we can transition to. So it's always well defined what the next state will be if there's a next state. So more formally this means if we have a state C and we can transition to a state C1 and there happens to be another transition from C to a state C2 then this implies that C1 must be equal to C2 so there are no two different states we can transition to from state C. And to add one more piece of notation here in addition to this arrow notation for a single transition we'll also use the arrow with a little star and this denotes the reflexive transitive closure of our transition relation and what that means is the following. So reflexive just means that for any state or configuration we can go to the same state again using this reflexive transitive closure. So reflexive just means you can stay in the same state and transitive means essentially that if you go from one to the other and from the other to yet another then you can also go directly from the first to the third. So more formally that means for all configurations C, C prime and C double prime it holds that if you can go from C to C prime and you can go from C prime to C double prime then this implies that you can actually also go directly using this transitive closure from C to C double prime. All right so now transition systems are one of the things we'll use here. Let's move on to a second point and this is about rule induction. So what is a rule induction? It's essentially a way to define a set by first specifying some basic elements of the set and then defining rules that you can use to generate more elements of the set and this allows you to describe a possibly infinite set using a short well a small number of basic elements and a small number of rules. So what we do here is we'll define a set which is then called the inductive set with two things. The first is a finite set of basic elements. So these are elements that we just enumerate and say hey these elements need to be part of our set and they are typically called axioms. And then the second ingredient for rule induction is that we have a finite list of rules or finite set of rules to be more precise and what these rules do is to specify how we can generate more elements given elements that we already have in the set and the way this is done is that we need to say well if we already have this and this and this then we can conclude to have yet another element and one way to write this down is to use this bar notation where we have some conclusion which we can take if some set of hypothesis from here called h1 to hn is true and you can also write down the axioms in a similar way where you basically say without having any hypothesis so without assuming anything we can conclude something for example that some element t is part of our set. So just to explain this notation a little more everything you have on top of the bar is the hypothesis in this case this is mt and in the case of the rules that we specify there needs to be something and then what we have at the bottom of the bar is the conclusion so in this case it's an element t of our set and here it's some conclusion c. Now this may sound pretty abstract so let's look at a concrete example to make this a little bit more precise or concrete so as one example let's just look at the set of natural numbers it's a set that everybody certainly knows and actually this can be specified using rule induction using just one axiom and one rule where we say we have one axiom that says zero is part of the set without any hypothesis so it's just an axiom you can always say zero is part of the set of natural numbers and then we'll also have one rule that is saying if n is in our set then n plus one should also be in the set and having this axiom which gives us the starting point zero and this rule which just adds more and more elements to the set we have defined the set of natural numbers. Now the natural numbers are not a particularly interesting set and you wonder why we need all this formalism just to describe natural numbers so let's have a look at a second example which is about evaluating expressions for example arithmetic expressions that you may have in the programming language so in this second example we are also defining a set and we'll use this set to define the meaning of expressions so for example let's say we have an expression that looks like this and this is just one possible syntax to write down an expression that adds three and four then what we want to do here in order to define the meaning of such expressions is to is to define a set and this set consists of pairs of ast so abstract syntax trees that describe a particular expression and the values that these expressions have once you evaluate them and now because there are infinitely many expressions of course we cannot really write down this entire set in an naive way but just enumerating all its elements but instead we'll use this rule induction idea to define some axioms and some rules which then defines this entire set to explain the meaning of all possible expressions so the notation that we use to write down these these elements of the set is the following we'll say that an expression evaluates to a value n using this down arrow notation okay so what this means is that the expression e evaluates to the number n which just means this expression has the number n okay now looking back at the arithmetic expressions that we have defined in the previous lecture let's write down axioms and rules to actually specify what these expressions mean so for the axioms one thing we'll need to do is to define the meaning of very simple expressions that just consist of individual numbers so we can do this for example by saying that an expression that just consists of one obviously has the value one and an expression that just consists of two should have the value two and because we do not want to do this for all possible numbers because there are infinitely many we can actually summarize all these axioms into a so-called axiom scheme by just saying that for any number n this number is just evaluated to the value that this number has so to n now this is still pretty boring because it's just about numbers that obviously have the value that they do have now to really define what more complex expressions mean we need to look into complex expressions which are built from operators and we do this by defining some rules for example a rule like the following which at the end will allow us to say what the meaning of applying the plus operator to an expression e1 and e2 is and we can do this assuming that we know two other things so we have two hypotheses here one is that we assume to know that expression e1 is evaluating to the value n1 and expression e2 is evaluating to the value n2 and if we know this we can conclude that this entire expression plus of e1 and e2 evaluates to some value n if n is actually the result of adding n1 and o2 now this only defines the meaning of plus so we would have to write more of these rules for other operations for example for minus and if our language contains also multiplication and division and so on then also for those but again we can use a scheme here in this case now a rule scheme where we just say that for any operator it holds that if we know that some expression e1 evaluates to n1 and some expression e2 evaluates to n2 then we can conclude that op of e1 and e2 evaluates to n if n is the result of applying this operator op to n1 and e2 and this now covers all operations that we may have in our language rule induction now gives us a way to specify the elements of such a set so we can for example use it to specify the meaning of all possible expressions now sometimes we may want to just prove that one element is actually in our inductive set for example to show that a particular expression has a particular meaning and the way we do this is by writing down a proof tree which is actually showing using the axioms and rules in our inductively defined set that a particular element is part of the set so what this proof tree is doing is show that an element is in an inductive set just getting back to our two examples let's again have a look at the natural numbers where let's say we want to show that two is a natural number so at the very end we want to conclude that two is a natural number and now if you look back at the axioms and rules that we have we can just use these two axioms and rules that you see down here right um and let's do this to show that two is a natural number well we can conclude that two is a natural number if you can show that one is a natural number and in order to show that one is a natural number we need to show that zero is a natural number and this happens to be an axiom so we can just conclude this without showing any further hypothesis. Let's also have a look at a second example which is again about our arithmetic expressions and here let's assume that we want to show the result of evaluating a particular expression. Let's say this expression is minus of the result of plus of three and four and one and we would like to show that this actually evaluates to six. Now here's again a moment where I'll invite you to stop the video and try to write down the proof tree on your own because only by trying and possibly failing you can really check if you understand these concepts. Now let me show you the solution so what we want to conclude at the very end so this is what we write down at the bottom is what we also have there at the top so we want to show that minus apply to plus of three and four and one indeed evaluates to six and in order to do this we now need to look at the different ingredients of this expression so on the one hand we have this plus of three and four which evaluates to something and in order to show that this overall expression evaluates to seven to six sorry we will have to show that this evaluates to seven and then on the other side of the minus operation we have one which we want to show to evaluate to one. Now let's start on the right hand side so one does evaluate to one this was one of the axioms that we have described using this axiom scheme here and looking at the other side we now need to further decompose our expression here by basically showing what the meaning of three is namely that it evaluates to three which happens to be another axiom we can use and showing that the value of four evaluates to four which is yet another axiom so overall by applying these three axioms and using two rules we can show that this complex expression is actually evaluating to the value six based on the inductive set that we have defined just on the previous slide. All right and this is already the end of the first part of this lecture on operational semantics so we've now looked into some preliminaries which we'll then use in the next parts of this lecture to define the operational semantics of a programming language thank you very much for watching and see you next time