220 likes | 300 Views
Course Overview. PART I: overview material 1 Introduction 2 Language processors (tombstone diagrams, bootstrapping) 3 Architecture of a compiler PART II: inside a compiler 4 Syntax analysis 5 Contextual analysis 6 Runtime organization 7 Code generation PART III: conclusion
E N D
Course Overview PART I: overview material 1 Introduction 2 Language processors (tombstone diagrams, bootstrapping) 3Architecture of a compiler PART II: inside a compiler 4 Syntax analysis 5 Contextual analysis 6Runtime organization 7 Code generation PART III: conclusion • Interpretation 9 Review
Arguments We have already discussed how space on the stack is allocated for arguments to routines. We now discuss some specific issues about • passing by value versus passing by reference • passing of functional and procedural parameters
Arguments: by value or by reference Some programming languages allow two kinds of parameter passing to functions/procedures. Example: in Triangle (similar in Pascal or C++) Constant/value parameter Var/reference parameter let proc S(var n:Integer, i:Integer) ~ n:=n+i; var today: record y:integer, m:Integer, d:Integer end; in begin b := {y ~ 2002, m ~ 2, d ~ 22}; ! b is non-local S(var b.m, 6); end
Arguments: by value or by reference Value parameters: At the call site the argument is an expression. The evaluation of that expression leaves some value on the stack. This value is passed to the procedure/function. Typical instructions for putting a value parameter on the stack: LOADL 6 LOAD 3[L1] Var/reference parameters: Instead of passing a value on the stack, the address of a memory location is pushed. This implies a restriction that only “variable-like” things can be passed to a var parameter. In Triangle there is an explicit keyword var at the call-site, to signal passing a var parameter. In Pascal and C++ the reference is created implicitly (but the same restrictions apply). Typical instructions for putting a var parameter on the stack: LOADA 5[LB] LOADA 10[SB]
Functional / Procedural Parameters In Triangle (and Pascal) a limited version of higher-order programming is possible. One may pass functions/procedures as arguments to other functions/procedures. (Unlike Scheme, they are not first-class values!) Example 1: let func twice(func doIt(Integer x): Integer, Integer i) ~ doIt(doIt(i)); func double(Integer d): Integer ~ d*2; var twenty : Integer in begin twenty := twice(double,5); end A procedural/functional parameter is passed by pushing a “closure” on the stack. A closure consists of two parts: 1. the static link of the procedure/function 2. the address of the procedure/function
Functional / Procedural Parameters Example 2: (illustrates we can pass non-global functions as well) let func twice(func doIt(Integer x): Integer, Integer i) ~ doIt(doIt(i)); func cubed(Integer b): Integer ~ let func multiply(Integer x): Integer ~ x*b in twice(multiply, b); in begin ... ... cubed(4) ... end accesses lexically scoped var See pictures of stack beginning on next slide…
Functional / Procedural Parameters right before call to twice right after calling cubed SB arg b 4 LB arg b 4 linkdata LB linkdata arg #1 multiply closure multiply function code ST arg #2 4 ST static link function address
Functional / Procedural Parameters right after call to twice before first call to doIt arg b arg b 4 4 linkdata linkdata arg doIt arg doIt multiply closure multiply closure multiply multiply arg i arg i 4 4 LB LB linkdata linkdata ST arg #1 4 closure to call multiply closure multiply ST Note: to avoid clutter, dynamic links are not shown. Only some static links are shown.
Functional / Procedural Parameters execution of CALLI instruction (Call Indirect) before first call to doIt right after first call to doIt arg b arg b 4 4 linkdata linkdata arg doIt arg doIt multiply closure multiply closure multiply multiply arg i arg i 4 4 LB linkdata linkdata arg #1 arg x 4 4 LB closure to call multiply closure linkdata multiply ST ST
Functional / Procedural Parameters right after first call to doIt just before x*b in multiply arg b arg b 4 4 linkdata linkdata arg doIt arg doIt multiply closure multiply closure multiply multiply arg i arg i 4 4 linkdata linkdata arg x arg x 4 4 LB LB linkdata linkdata ST value of x LOAD [-1]LB 4 value of b LOAD [-1]L1 4 ST
Functional / Procedural Parameters just before return doIt/multiply just after return doIt/multiply arg b arg b 4 4 linkdata linkdata arg doIt arg doIt multiply closure multiply closure multiply multiply arg i arg i 4 4 LB linkdata linkdata value of doIt(4) arg x 4 16 LB ST linkdata value of x*b 16 ST
Functional / Procedural Parameters just before second call to doIt just before x*b in multiply arg b arg b 4 4 linkdata linkdata arg doIt arg doIt multiply closure multiply closure multiply multiply arg i arg i 4 4 LB linkdata linkdata arg #1 arg x 16 16 LB closure to call multiply closure linkdata multiply value of x LOAD [-1]LB ST 16 value of b LOAD [-1]L1 4 ST
Functional / Procedural Parameters just after return doIt/multiply = just before return from “twice” just after return from “twice” =just before return from cubed arg b 4 4 LB linkdata linkdata arg doIt arg doIt multiply closure multiply closure multiply multiply arg i value of twice(multiply,4) 4 64 LB ST linkdata value of doIt(16) 64 just after return from cubed ST value of cubed(4) 64 ST
Functional / Procedural Parameters Why does the stack allocation model for functional/procedural parameters work? More precisely, why can closure objects and the static frames they point to be completely in the stack? (In Scheme for example static environments are allocated in the heap.) It is sufficient to ensure that every closure object can never outlive the static frame to which it points. Points upward is OK! multiply closure Points downward would be BAD! multiply closure
Recursion How are recursive functions and procedures supported on a low-level machine? => Surprise! The stack memory allocation model already works! Example: let func fac(n:Integer) ~ if (n<=1) then 1 else n*fac(n-1); in begin putint(fac(6)); end • Why does it work? • Because every activation of a function gets its own activation record on the stack, with its own parameters, locals etc. • All procedures and functions are “reentrant”. Older languages (e.g. FORTRAN) that use static allocation for locals have problems with recursion.
fac(4) fac(4) fac(4) fac(3) fac(4) fac(3) fac(2) fac(3) fac(2) fac(1) fac(2) fac(3) Recursion: General Idea Why the stack allocation model works for recursion: Like other function/procedure calls, lifetimes of local variables and parameters for recursive calls behave like a stack. ? fac(4) fac(4) fac(3) fac(3) fac(2) fac(2) ? fac(1)
Recursion: In Detail let func fac(n:Integer) ~ if (n<=1) then 1 else n*fac(n-1); in begin putint(fac(6)); end right after entering fac right before recursive call to fac before call to fac SB SB SB arg 1 arg 1 arg n 6 6 6 ST LB LB link data link data ST value of n 6 arg: value of n-1 5 ST
Recursion right before recursive call to fac right before next recursive call to fac right before next recursive call to fac SB SB SB arg n arg n arg n 6 6 6 LB link data link data link data value of n value of n value of n 6 6 6 arg arg n arg n 5 5 5 ST LB link data link data value of n value of n 5 5 arg arg 4 4 ST LB link data value of n 4 arg 3 ST
Recursion Is the spaghetti of static and dynamic links getting confusing? Let’s zoom in on just a single activation of the fac procedure. The pattern is always the same: just before recursive call in fac to caller context (= previous LB) ? to lexical context (= SB) argument n LB link data n Intermediate results in the computation of n*fac(n-1); n-1 ST
Next step: multiply argument n=2 link data n=2 Recursion just before the return from the “deepest call”: n=1 after return from deepest call LB caller frame (what’s in here?) ? argument n=1 result=1 LB ST link data result = 1 ST
argument n=2 link data 2*fac(1)=2 Recursion just before the return from the “second deepest call”: n=2 (after return from deepest call and multiply) to caller context to lexical context (= SB) LB caller frame (what’s in here?) ? result Next step: return ST From here on down the stack is shrinking, multiplying each time with a bigger n
Recursion just before recursive call in fac after completing recursive call argument n argument n LB LB link data link data n n recurs. arg: n-1 fac(n-1) ST ST Calling a recursive function is just like calling any other function. After completion it just leaves its result on the top of the stack! A recursive call can happen in the midst of expression evaluation. Intermediate results, local variables, etc. simply remain on the stack, and computation proceeds when the recursive call is completed.