Parameter Passing Mechanisms CS308 Compiler Theory
2 Terms and Definitions Formal Parameters: specified (together with type) when procedure is declared (a.k.a. formals) Actual Parameters: values which are passed to a procedure at call site (a.k.a. actuals) l-value: storage location represented by an expression (e.g. a register, a memory location, etc) r-value: value contained at the storage location l- and r- refer to the “ left ” and “ right ” side of an assignment int factorial(int n) { if (n == 0) return 1; else return n * factorial(n - 1); } … factorial(42); actual formal CS308 Compiler Theory
3 Call-by-value Simplest possible approach: –a formal is treated the same as a local (i.e. storage is allocated on the stack or in a register) –the caller evaluates the actuals and passes the result to the callee Operations on the formals do not affect values in the stack frame of the caller, so the following will not work: void swap(int a, int b) { int temp; temp = a; a = b; b = temp; } CS308 Compiler Theory
4 Call-by-reference Also known as: call-by-address, call-by-location The location of the actual is passed, rather then its value: –if the actual is a variable (i.e. corresponds to an assignable location in memory) its address is passed –if the actual is an expression, the expression is evaluated to a temporary location and the address of that location is passed (the compiler will warn you since this is not what you usually want) Operations on the formals do affect the values in the caller, so procedure swap now works: void swap(int& a, int& b) { int temp; temp = a; a = b; b = temp; } CS308 Compiler Theory
5 Copy-restore Also known as: copy-in copy-out, value-result Combination of call-by-value and call-by-reference: –the actuals are evaluated before the call –the expressions having only r-values, –the expressions having l-values are passed by reference CS308 Compiler Theory
6 Call-by-Name The evaluation of actuals is delayed until their use in callee Can be thought of as in-line expansion (but isn ’ t!) Can be implemented by using parameterless evaluation procedures (sometimes called thunks) that are created for each actual: void foo(int a, int b) { … a … b … } … foo(1+2, 3); … int thunk_1() { return 1 + 2; } int thunk_2() { return 3; } void foo(proc f, proc g) { … p() … q() … } … foo(thunk_1, thunk_2); … CS308 Compiler Theory
7 Procedures as Parameters Some languages (such as C and Scheme) proved first-class procedure values: such values may be stored in a variable or returned by functions This creates many run-time issues: int main() { proc make_incrementer(int n) { int incrementer(int x) { return x + n; } return incrementer; } … proc my_incr = make_incrementer(42); int y = my_incr(5); } CS308 Compiler Theory
8 control link access link formal: n Call Scenario Consider what happens when we call main(): –main() ’ s stack frame is set up –call to make_incrementer() –return from make_incrementer() –PROBLEM: can ’ t call my_incr() because the stack frame in which n resides is destroyed!!! local: my_incr local: y control link access link formal: n CS308 Compiler Theory
9 Solutions Solution 1 (a la C): disallow nested procedures: –when we have no nested procedures, we never access non-static non-locals and so we need not preserve our caller ’ s stack frame Solution 2 (a la Pascal): disallow returning procedures from the scope where they are created: –when we call a procedure indirectly, we are guaranteed that the stack frames for scopes within which it is nested exist on stack –this requires eliminating assignment of procedures to local variables, but allows for passing procedures as parameters Solution 3 (a la Lisp/Scheme): use tree instead of stack for procedure ’ s local data –this creates a record for each procedure (called closure) when a procedure is defined (not when it is called) –allocating closures is costly; freeing requires a garbage collector (to be discussed later in the course) CS308 Compiler Theory
Implementing Object-Oriented Features CS308 Compiler Theory
11 Object-Oriented Features Overview Object-oriented features normally fall into two categories: –static-semantic features that involve purely compile-time activities: classes and access control: proper handling of scope rules, etc. overloading: this involves choosing a function based on information in addition to its name (all available during compilation) non-virtual functions: the compiler can, in effect, copy the definitions of functions from one class to another –run-time (dynamic semantic) features that require extra data structures and extra code at execution time virtual functions (requires dispatch tables that are updated at runtime) type dispatch (case statement in Cool) requires run-time type information When talking about object-oriented dispatch, it is useful to remember that this is always one of the arguments (first, actually) to the function being called: x.foo(…) = foo(x, …) CS308 Compiler Theory
12 Virtual Functions: Single Inheritance Here, the compiler has a couple of problems: –the parameter p to doIt() may be either Parent or Child ; the right incr() must be called and access to field x must work in either case –the incr() function inside Parent may be called with either Parent or Child as this, but that function must call the right limit() procedure class Parent { public: int x; virtual incr(int n) {x += n; limit();} virtual limit() {if (x>255) x = 255;} int otherStuff() {…} }; class Child : public Parent { public: int y; virtual incr(int n) {Parent::incr(); y += y; limit();} virtual limit() {if (x>y) x = y;} void stillMoreStuff() {…} }; class User { public: void doIt(Parent p) { p.incr(1); print(p.x); } … }; CS308 Compiler Theory
13 Virtual Functions: Single Inheritance (cont.) To handle these problems, we will need run-time support Old Truth: all problems in computer science can be solved by introducing a new layer of indirection! Note that the offsets for virtual functions is fixed at compile time Parent object is “ contained ” inside Child Parent Parent ’ s virtual table Child Child ’ s virtual table 0: virtual table 4: field x 0: virtual table 4: field x 8: field y 00: Parent::incr_ptr 04: Parent::limit_ptr 08: Parent::otherStuff_ptr 00: Child::incr_ptr 04: Child::limit_ptr 08: Parent::otherStuff_ptr 12: Child::stillMoreStuff_ptr 00: Child::incr_ptr 04: Child::limit_ptr 08: Parent::otherStuff_ptr 12: Child::stillMoreStuff_ptr CS308 Compiler Theory
14 Virtual Functions: Multiple Inheritance class A { public: int x; virtual f() {… x… f()…} }; class B { pubic: int y; virtual f() {… y… g()…} virtual g() {… y… f()…} }; class C : public A, public B { public: int z; virtual f() {… x… y… z… g() … A::f()…} void h() {…} }; int main() { C* pc = new C; A* pa = pc; B* pb = pc; // (*) all these call C::f pa->f(); pb->f(); pc->f(); } Class C inherits from both A and B –pa->x, pc->x all reference the same variable; pb->y, pc- >y all reference the same variable –The calls to f() inside A::f, B::g must all go to C::f during the execution of calls (*) –During the non-virtual call to A::f() access to x must work properly, and the call to f() inside A::f() must go to C::f() CS308 Compiler Theory
15 Virtual Functions: Multiple Inheritance (cont.) “ Classical ” approach to multiple inheritance: We layout C so that it “ contains ” A and B B* pb = pc adjusts the offset so that pb points into the “ middle ” of C Before calling a function, we first adjust this by the value in virtual table A A 0: A::f_ptr, adj = 0 B B 0: B::f_ptr, adj = 0 4: B::g_ptr, adj = 0 C C 0: C::f_ptr, adj = 0 4: C::h_ptr, adj = 0 0: C::f_ptr, adj = -8 4: B::g_ptr, adj = 0 00: virtual table 04: field x 00: virtual table 04: field y 00: virtual table 1 04: field x 08: virtual table 2 12: field y 16: field z CS308 Compiler Theory
16 Object-Oriented Features in COOL Fortunately, COOL only supports single inheritance Unfortunately, COOL has case (usually called typecase ) construct, so we must carry around type information at run-time According to COOL reader, objects are laid out as follows: 00: class tag 04: object size 08: virtual table 12: attributes … There ’ s also a garbage collector tag immediately preceding the object: you do not need to worry about it In your project, you will design the structure and use of virtual table yourself, the run-time system never looks at it CS308 Compiler Theory
17 Conclusions The BIG thing: programming language features immensely affect the design of the compiler in general and of the run-time system in particular Object-oriented features have huge impact on the complexity (and hence efficiency) of run-time: –going from single to multiple inheritance requires extra fetching and adding –another significant factor is function implementation features (recursion, access to non- static non-locals, dynamic-sized locals, procedures as parameters) CS308 Compiler Theory