410 likes | 419 Vues
Understand runtime memory management, symbol table stack, activation records, and access to local & nonlocal variables in compiler interpreters. Learn key concepts in interpreter design.
E N D
CMPE 152: Compiler DesignOctober 8 Class Meeting Department of Computer EngineeringSan Jose State UniversityFall 2019Instructor: Ron Mak www.cs.sjsu.edu/~mak
Chapter 12 The Interpreter • The back end executes the source program. • Uses the symbol tables and the parse treesbuilt by the front end parser. • One symbol table per main program, procedure, and function for that routine’s local identifiers. • The symbol table is pointed to by the routine name’s symbol table entry.
The Interpreter, cont’d • One parse tree per main program, procedure, and function. • For that routine’s compound statement. • Pointed to by the symbol table entryfor the routine’s name. • The interpreter never sees the original source program. • Only the symbol tables and parse trees.
Runtime Memory Management • The interpreter must manage the memorythat the source program uses during run time. • Up until now, we’ve used the hack of storing values computed during run time into the symbol table. • Why is this a bad idea? • This will fail miserably if the source program has recursive procedure and function calls.
Symbol Table Stack vs. Runtime Stack • The front end parser builds symbol tables and manages the symbol table stack as it parsesthe source program. • The parser pushes and pops symbol tablesas it enters and exits nested scopes. • The back end executor manages the runtime stack as it executes the source program. • The executor pushes and pops activation recordsas it calls and returns from procedures and functions. These are similar concepts, so don’t confuse them!
Runtime Activation Records • An activation record (AKA stack frame) maintains information about the currently executing routine • a procedure • a function • the main program itself
Runtime Activation Records, cont’d • In particular, an activation record contains the routine’s local memory. • values of local variables • values of formal parameters • This local memory is a memory map. • Key: The name of the local variable or formal parameter. • Value: The current value of the variable or parameter. • Local memory is a hash table!
Runtime Activation Records, cont’d PROGRAM main1; PROCEDURE proc2a; PROCEDURE proc3; BEGIN ... END; BEGIN {proc2a} proc3; END; PROCEDURE proc2b; BEGIN proc2a; END; BEGIN {main1} proc2b; END. In this example, the names of the routines indicate their nesting levels. AR: proc3 Call a routine: Push its activation record onto the runtime stack. Return from a routine: Pop off its activation record. AR: proc2a AR: proc2b AR: main1 RUNTIME STACK main1 proc2b proc2a proc3
PROGRAM main1; VAR j : integer; PROCEDURE proc2b; VAR j : integer; BEGIN j := 14; END; BEGIN {main1} j := 55; proc2b; END. Runtime Access to Local Variables Accessing local values is simple, because the currently executing routine’s activation record is on top of the runtime stack. AR: proc2b 14 j AR: main1 55 j main1 proc2b RUNTIME STACK
AR: proc3 AR: proc2a AR: proc2b m AR: main1 m j j i j Runtime Access to Nonlocal Variables PROGRAM main1; VAR i, j : integer; PROCEDURE proc2a; VAR m : integer; PROCEDURE proc3; VAR j : integer BEGIN j := i + m; END; BEGIN {proc2a} i := 11; m := j; proc3; END; PROCEDURE proc2b; VAR j, m : integer; BEGIN j := 14; m := 5; proc2a; END; BEGIN {main1} i := 33; j := 55; proc2b; END. • Each parse tree node for a variable contains the variable’s symbol table entry as its VALUE attribute. • Each symbol table entry has the variable’s nesting leveln. • To access the value of a variable at nesting level n, the value must come from the topmost activation record at level n. • Search the runtime stack from top to bottom for the topmost activation record at level n. 66 55 14 5 33 11 55 RUNTIME STACK main1 proc2b proc2a proc3
The Runtime Display • A vector called the runtime display makes it easier to access nonlocal values. • This has nothing to do with video displays!
The Runtime Display, cont’d • Elementnof the display always points to the topmost activation record at scope nesting level non the runtime stack. • The display must be updated as activation records are pushed onto and popped off the runtime stack.
The Runtime Display, cont’d • Whenever a new activation record at level n is pushed onto the stack, it links to the previous topmost activation record at level n. • This link helps to restore the runtime stack as activation records are popped off when returning from procedures and functions.
AR: proc3 AR: proc2a AR: proc2b m AR: main1 m j j j i Runtime Access to Nonlocal Variables PROGRAM main1; VAR i, j : integer; PROCEDURE proc2a; VAR m : integer; PROCEDURE proc3; VAR j : integer BEGIN j := i + m; END; BEGIN {proc2a} i := 11; m := j; proc3; END; PROCEDURE proc2b; VAR j, m : integer; BEGIN j := 14; m := 5; proc2a; END; BEGIN {main1} i := 33; j := 55; proc2b; END. 3 2 1 RUNTIME DISPLAY The runtime display allows faster access to nonlocal values. RUNTIME STACK main1 proc2b proc2a proc3
Recursive Calls PROGRAM main1 FUNCTION func2 FUNCTION func3 PROCEDURE proc2 PROCEDURE proc3 AR: proc2 AR: func3 AR: proc3 AR: func2 AR: proc3 3 2 AR: proc2 1 RUNTIME DISPLAY AR: main1 RUNTIME STACK proc2 proc3 proc3 main1 func2 func3 proc2
Allocating an Activation Record • The activation record for a routine (procedure, function, or the main program) needs one or more “data cells” to store the value of each of the routine’s local variables and formal parameters. TYPE arr = ARRAY[1..3] OF integer; ... PROCEDURE proc(i, j : integer; VAR x, y : real; VAR a : arr; b : arr); VAR k : integer; z : real; AR: proc i x k j y z a b
Allocating an Activation Record, cont’d TYPE arr = ARRAY[1..3] OF integer; ... PROCEDURE proc(i, j : integer; VAR x, y : real; VAR a : arr; b : arr); VAR k : integer; z : real; • Obtain the names and types of the local variables and formal parameters from the routine’s symbol table. AR: proc i x k j y z a b
Allocating an Activation Record, cont’d TYPE arr = ARRAY[1..3] OF integer; ... PROCEDURE proc(i, j : integer; VAR x, y : real; VAR a : arr; b : arr); VAR k : integer; z : real; • Whenever we call a procedure or function: • Create an activation record. • Push the activation record onto the runtime stack. • Allocate the memory map of data cells based on the symbol table. AR: proc i x k j y z a b No more storing runtime values in the symbol table!
index epsilon 10 -3.1 count delta 84 0.07 triplet 10 20 30 AR: main Passing Parameters During a Call PROGRAM main; TYPE arr = ARRAY[1..3] OF integer; VAR index, count : integer; epsilon, delta : real; triplet : arr; PROCEDURE proc(i, j : integer; VAR x, y : real; VAR a : arr; b : arr); ... BEGIN {main} ... proc(index + 2, count, epsilon, delta, triplet, triplet); END. • Value parameters: A copy of the value is passed. • VAR parameters: A reference to the actual parameter is passed. AR: proc i x 12 j y 84 a b 10 20 30 RUNTIME STACK
Executing Procedure and Function Calls Chapter 12
Class CallDeclaredExecutor • Method execute() • Create a new activation record based on the called routine’s symbol table. • Execute the actual parameter expressions. • Initialize the memory map of the new activation record. • The symbol table entry of the name of the called routine points to the routine’s symbol table. • Copy values of actual parameters passed by value. • Set pointers to actual parameters passed by reference.
Class CallDeclaredExecutorcont’d • Method execute() cont’d • Push the new activation recordonto the runtime stack. • Access the root of the called routine’s parse tree. • The symbol table entry of the name of the called routine points to the routine’s parse tree. • Execute the routine. • Pop the activation record off the runtime stack.
Class CallDeclaredExecutor • Method execute_actual_parms() • Obtain the formal parameter cell in the new activation record: wci::backend::interpreter::executors::CallDeclaredExecutor.cpp • Cell formalCell = newAr.getCell(formalId.getName());
Class CallDeclaredExecutor • Method execute_actual_parms() cont’d • Value parameter: wci::backend::interpreter::executors::CallDeclaredExecutor.cpp Object cell_value= expression_executor.execute(actual_node); assignment_executor.assign_value(actual_node, formal_id, formal_cell, formal_typespec, cell_value, value_typespec); Set a copy of the value of the actual parameter into the memory cell for the formal parameter.
Class CallDeclaredExecutor • Method executeActualParms() cont’d • VAR (reference) parameter: • Method ExpressionExecutor.executeVariable()executes the parse tree for an actual parameter and returns the reference to the value. wci::backend::interpreter::executors::CallDeclaredExecutor.cpp Cell *actual_cell= expression_executor.execute_variable(actual_node); formal_cell->set_value(actual_cell); Set a reference to the actual parameter into the memory cell for the formal parameter.
Class CellImpl • Since a memory cell can contain a value of any type or a reference, we implement it simply as a boost::any. wci::Object.h #include <boost/any.hpp> #define Object boost::any wci::backend::interpreter::memoryimpl::CellImpl.cpp Object CellImpl::get_value() const { return cell_value; } void CellImpl::set_value(Objectnew_value) { cell_value = new_value; }
Runtime Error Checking • Range error • Assign a value to a variable with a subrange type. • Verify the value is within range • Not less than the minimum value and not greater than the maximum value. • Method StatementExecutor.check_range() • Division by zero error • Before executing a division operation, check that the divisor’s value is not zero.
Pascal Interpreter • Now we can execute entire Pascal programs! • Demo
Midterm Review: Question 1 • The parser builds data structures consisting of symbol table entry objects (STEO) and type specification objects (TSO). • How are these structures used at compile time? • STEO: Store information about locally-declared identifiers. • TSO: Represent type information. • Both: Assign types to variables. • Both: Do type checking in statements and expressions.
Midterm Review: Question 1, cont’d • How does the interpreter use these structures at run time? • STEO: Create the memory map for each activation record that’s pushed onto the runtime stack. • STEO: Get the values of defined and enumeration constants. • TSO: Do runtime range checking for subranges. • TSO: Get the data types of array indexes and elements.
Midterm Review: Question 2 • How does the symbol table stack … • … allow a variable declared in an enclosing scope to be redeclared in the local scope? • Push a new symbol table onto the symbol table stack for the local scope. Enter the variable’s name into the local table. • … prevent a variable already declared in the local scope from being redeclared in the local scope? • First check the local symbol table to see whether the variable’s name is already entered into the table.
Midterm Review: Question 2, cont’d • How does the symbol table stack … • … allow two record types defined in the same procedure to declare fields with the same names? • Push a separate symbol table for each record type onto the symbol table stack when the parser is parsing the record type specification. Pop it off when the parser is done parsing the specification.
Midterm Review: Question 3 • Explain the role of synchronization sets during parsing. • Each synchronization set tells the parser what tokens to look for at a particular point of the source program. Some members of the set are tokens that are syntactically valid at that point, and other members are there to allow the parser to recover from syntax errors.
Midterm Review: Question 4 • Some programming languages use parentheses to enclose array subscripts and to enclose arguments to function calls. Suppose Pascal did the same. Consider the assignment statement:x := arr(i, 2*j)Describe how the Pascal parser could (or could not) determine whether it is parsing a call to function arr or an access to an element of array arr.
Midterm Review: Question 4, cont’d • By the time the parser is parsing the assignment statement, it would have already parsed the declaration of arr as either an array or a function. Therefore, when the parser encounters arr in the assignment statement, it looks in the symbol table to see how arr was declared. x := arr(i, 2*j)
Midterm Review: Question 5 • Suppose Pascal has an exponentiation operator **, so thatn**4represents the mathematical expression n4.Exponentiation has a higher operator precedence level than the multiplicative operators. Therefore,n**i*jis evaluated as (n**i)*j. Furthermore, exponentiation is right-associative, so that n**i**j**k is evaluated as n**(i**(j**k)).
Midterm Review: Question 5 • What modifications to the expression, simpleexpression, term, and factor syntax diagrams are required to accommodate the ** operator? Redraw only the diagrams that change. (Not all the diagrams may need to change, and you may need to add new diagrams.)
Midterm Review: Question 5, cont'd • Draw the parse tree for the assignment statementr := a**i*j**(n-1)**kYou may assume the existence of a node type for the ** operator. r := (a**i) * (j** ((n-1)**k)) := r * ** ** a i j ** - k n 1
AR: procD AR: procC AR: procB AR: procC AR: procB AR: procA AR: procE 4 AR: procE 3 AR: procD 2 1 AR: main RUNTIME STACK RUNTIME DISPLAY Midterm Review: Question 6 • Given the Pascal program structure and call chain: mainprocDprocEprocEprocAprocBprocCprocBprocCprocD PROGRAM main; PROCEDURE procA; PROCEDURE procB; PROCEDURE procC; PROCEDURE procD; PROCEDURE procE; Draw the runtime stack, activation records, and runtime display at the end of the call chain. Show the display pointers and the activation record links.