Lecture08: Scope and Lexical Address Free and Bound Variables (EOPL 1.3.1) Given an expression E, does a particular variable reference x appear free or bound in that expression? Definition: A variable x occurs free in E iff there is some reference of x in E that is not bound by any declaration of x in E. Definition: A variable x occurs bound in E iff there is some reference of x in E that is bound by a declaration of x in E. Example: in ((lambda (x) x) y) variable x occurs bound but variable y occurs free Variables which are free may be bound in an enclosing context (lambda (y) ((lambda (x) x) y)) Rule: The value of an expression depends solely on the variables that occur free within the expression Therefore the semantics of any expression with no free variables is fixed Copyright Bill Havens 1
Occurs Free and Bound in Lambda Specific rules for Lambda Expressions Rule: A variable x occurs free in a lambda calculus expression E iff 1. E is a variable reference such that E is the same as x; or 2. E is of the form (lambda (y) E1 ), where y is different from x and x occurs free in E1 ; or 3. E is of the form (E1 E2 ) such that x occurs free in either E1 or E2. Rule: A variable x occurs bound in a lambda calculus expression E iff 1. E is of the form (lambda (y) E1 ), where x occurs bound in E1 or x and y are the same variable and y occurs free in E1 ; or 2. E is of the form (E1 E2 ) such that x occurs bound in either E1 or E2. From the above rules, we can write predicates for determining whether a specific variable occurs free (resp. occurs bound) in a lambda expression Copyright Bill Havens 2
Implementation Derived directly from case analysis of rule for occurs free? (define occurs-free? (lambda (var exp) (cond ((symbol? exp) (eqv? var exp)) ; case 1 ((eqv? (car exp) 'lambda) ; case 2 (and (not (eqv? (caadr exp) var)) ; ref var lambda var (occurs-free? var (caddr exp)))) ; var is free in body (else (or (occurs-free? var (car exp)) ; case 3 (occurs-free? var (cadr exp))))))) Is var x free in the following expressions? (lambda (x) (foo x)) (lambda (y) (foo x)) ((lambda (x) (list x)) x) (lambda (y) ((lambda (x) (list x)) y)) Copyright Bill Havens 3
Similar implementation for occurs-bound? Derived directly from rule for occurs-bound? (define occurs-bound? (lambda (var exp) (cond ((symbol? exp) #f) ; ((eqv? (car exp) 'lambda) ; case 1 (or (occurs-bound? var (caddr exp)) ; var occurs bound in body (and (eqv? (caadr exp) var) ; var = lambda var (occurs-free? var (caddr exp))))) ; var occurs free in body (else (or (occurs-bound? var (car exp)) ; case 2 (occurs-bound? var (cadr exp))))))) Is var x occur bound in the following expressions? (lambda (x) (foo x)) (lambda (y) (foo x)) ((lambda (x) (list x)) x) (lambda (y) ((lambda (x) (list x)) y)) Copyright Bill Havens 4
Introduction Scope and Blocks How do decide which variable declaration corresponds to each variable reference? Given by the scoping rules for the language Scheme uses static scoping (from Lecture07) The scope region associated with a variable declaration is called a block. Blocks can be nested hierarchically Inner blocks associated with the same variable name as an outer block can create scoping holes in the outer block. Called shadowing Copyright Bill Havens 5
Example in C: { // block 1 scope int x, y; // declaration of vars x and y x = 4; // reference to var x in block 1 { // block 2 scope int x, z; // declaration of vars x and z x = 3 // reference to var x in block 2 z = x + 1 // references to vars x and z in block 2 } // end of block2 scope y = x + 1 // reference to vars in block 1 } // end of block 1 scope Similar example in Scheme syntax (lambda (x y) ;; block 1 (lambda (x z) ;; block 2... )) Copyright Bill Havens 6
Nested Blocks Inner declarations take precedence over outer ones. (define x (lambda (x) (map (lambda (x) (+ x 1)) x))) Applying function x (x (1 2 3)) = (2 3 4) Why? Copyright Bill Havens 7
Introduction Lexical Address We need to translate source program syntax into an intermediate form suitable for compilation In particular, replace all references to identifier names with their address Definition: The lexical address of a variable reference gives the depth of the reference from the block in which the variable was declared and the position of the variable in that declaration. A lexical address has the form (v : d p) where v is the variable name, d is the depth and p is the position. Note: depth and position are zero-based (a la C) For example: (lambda (x y) ((lambda (a) (x (a y))) x)) can be transformed to show lexical addresses as follows... Copyright Bill Havens 8
Translation: (lambda (x y) ((lambda (a) ((x : 1 0) ((a : 0 0) (y : 1 1)))) (x : 0 0))) Note: this code is an intermediate representation and not executable Analysis: - First x is at depth 1 and position 0 - Variable a has depth 0 and position 0 - Second x has depth 0 and position 0 Replacing formal parameters Now the formal parameter names of the lambda expressions are redundant Can be replaced by a count of the number of parameters Example: (lambda 2 ((lambda 1 ((: 1 0) ((: 0 0) (: 1 1)))) (: 0 0))) Copyright Bill Havens 9
Introduction Run Time Environments How does the CPU know how to reference the right variable declarations at run time? Which memory addresses are associate with each variable reference? Problem is exacerbated by recursive function calls For statically scoped languages (eg- C, C++, Java, Scheme) a simple mechanism called a display is used. Reference: A. Aho & J.D. Ullman (1977) Principles of Compiler Design, Addision-Wesley (pp.67, 356). Copyright Bill Havens 10
Basic Idea Assume a stack based CPU Each procedure invocation creates an activation record for the procedure on the calling stack An activation record contains variously: - continuation for the caller (ie- return address) - memory for local and temporary variables in the procedure - previous top of the calling stack - other bookkeeping information Example: (lambda (x y) (foo x) (fie y)) Activation Record return address x: y: ptr to caller stack Activation records pushed onto calling stack when function called Copyright Bill Havens 11
Example from above enhanced ((lambda1 (x1 y1) ((lambda2 (a1) (x1 (a1 y1))) x1)) list 2) Calling stack (grows downward): 0 return address for caller 1 x1: list 2 y1: 2 lambda1 3 ptr to top of caller stack 4 return address for 5 a1: list 6 ptr = 3 TOP lambda1 Copyright Bill Havens 12
Dereferencing using Displays Now we need a mechanism for dereferencing variables which occur in the body of the lamba expressions. Based on Lexical Address of each variable reference Example again: (lambda 2 ((lambda 1 ((: 1 0) ((: 0 0) (: 1 1)))) (: 0 0))) Note that the lambda formal parameter now specifies how many memory locations to allocate on the calling stack (assumes reference variables here) Need a dispatch table which will map lexical addresses into memory locations on the calling stack The dispatch table is called the display (from Algol60) Format: The display is a vector of stack pointers which maps lexical addresses into memory locations on the calling stack. Maintained during procedure activation and return Copyright Bill Havens 13
Example of Display Calling Stack 0 return address for caller lambda1 1 x1: list 2 y1: 2 3 ptr to top of caller stack Display for lambda2 1 ptr = 1 0 ptr = 5 TOP 4 return address for lambda1 5 a1: list 6 ptr = 3 TOP How does the display provide variable dereferencing? Note that the display grows and shrinks like the calling stack Now we know how to efficiently implement lexically scoped block structured local variable dereferencing Copyright Bill Havens 14