Unit 4&5 Final
Unit 4&5 Final
SEMANTIC ANALYSIS
➢ Semantic Analysis computes additional information related to the
meaning of the program once the syntactic structure is known.
➢ In typed languages as C, semantic analysis involves adding information to the symbol
evaluated by a bottom-up, or PostOrder, traversal of the parse-tree.
• Example. The above arithmetic grammar is an example of an S-Attributed
Definition. The annotated parse-tree for the input 3*5+4n is:
L-attributed definition
Definition: A SDD its L-attributed if each inherited attribute of Xi in the RHS of A ! X1 :
INTRODUCTION
The front end translates a source program into an intermediate representation from which
the back end generates target code.
1. Retargeting is facilitated. That is, a compiler for a different machine can be created by
attaching a back end for the new machine to an existing front end.
INTERMEDIATE LANGUAGES
Syntax tree
Postfix notation
The semantic rules for generating three-address code from common programming language
constructs are similar to those for constructing syntax trees or for generating postfix notation.
Graphical Representations:
Syntax tree:
A syntax tree depicts the natural hierarchical structure of a source program. A dag
(Directed Acyclic Graph) gives the same information but in a more compact way because
common subexpressions are identified. A syntax tree and dag for the assignment statement a : =
b * - c + b * - c are as follows:
NOTES.PMR-INSIGNIA.ORG
assign assign
a + a +
* * *
c c c
Postfix notation:
Syntax-directed definition:
Syntax trees for assignment statements are produced by the syntax-directed definition.
Non-terminal S generates an assignment statement. The two binary operators + and * are
examples of the full operator set in a typical language. Operator associativities and precedences
are the usual ones, even though they have not been put into the grammar. This definition
constructs the tree from the input a : = b * - c + b* - c.
E ( E1 ) E.nptr : = E1.nptr
NOTES.PMR-INSIGNIA.ORG
The token id has an attribute place that points to the symbol-table entry for the identifier.
A symbol-table entry can be found from an attribute id.name, representing the lexeme associated
with that occurrence of id. If the lexical analyzer holds all lexemes in a single array of
characters, then attribute name might be the index of the first character of the lexeme.
Two representations of the syntax tree are as follows. In (a) each node is represented as a
record with a field for its operator and additional fields for pointers to its children. In (b), nodes
are allocated from an array of records and the index or position of the node serves as the pointer
to the node. All the nodes in the syntax tree can be visited by following pointers, starting from
the root at position 10.
aaaaaaaaaaaaa
assign 0 id b
1 id c
id a
2 uminus
2 1
3 * 0 2
+
4 id b
5 id c
* *
6 uminus 5
id b id b
7 * 4 6
bb
uminus uminus 8 + 3 7
9 id a
id c id c
10 assign 9 8
(a) (b)
Three-Address Code:
x : = y op z
where x, y and z are names, constants, or compiler-generated temporaries; op stands for any
operator, such as a fixed- or floating-point arithmetic operator, or a logical operator on boolean-
valued data. Thus a source language expression like x+ y*z might be translated into a sequence
t1 : = y * z
t2 : = x + t 1
NOTES.PMR-INSIGNIA.ORG
Advantages of three-address code:
The use of names for the intermediate values computed by a program allows three-
address code to be easily rearranged – unlike postfix notation.
Three-address code corresponding to the syntax tree and dag given above
t1 : = - c t 1 : = -c
t 2 : = b * t1 t2 : = b * t1
t3 : = - c t 5 : = t 2 + t2
t 4 : = b * t3 a : = t5
t5 : = t2 + t4
a : = t5
(a) Code for the syntax tree (b) Code for the dag
The reason for the term “three-address code” is that each statement usually contains three
addresses, two for the operands and one for the result.
4. The unconditional jump goto L. The three-address statement with label L is the next to be
executed.
5. Conditional jumps such as if x relop y goto L. This instruction applies a relational operator (
<, =, >=, etc. ) to x and y, and executes the statement with label L next if x stands in relation
NOTES.PMR-INSIGNIA.ORG
relop to y. If not, the three-address statement following if x relop y goto L is executed next,
as in the usual sequence.
6. param x and call p, n for procedure calls and return y, where y representing a returned value
is optional. For example,
param x1
param x2
...
param xn
call p,n
generated as part of a call of the procedure p(x 1, x2, …. ,xn ).
When three-address code is generated, temporary names are made up for the interior
nodes of a syntax tree. For example, id : = E consists of code to evaluate E into some temporary
t, followed by the assignment id.place : = t.
E E1 + E2 E.place := newtemp;
E.code := E1.code || E2.code || gen(E.place ‘:=’ E1.place ‘+’ E2.place)
E E1 * E2 E.place := newtemp;
E.code := E1.code || E2.code || gen(E.place ‘:=’ E1.place ‘*’ E2.place)
E - E1 E.place := newtemp;
E.code := E1.code || gen(E.place ‘:=’ ‘uminus’ E1.place)
E ( E1 ) E.place : = E1.place;
E.code : = E1.code
E id E.place : = id.place;
E.code : = ‘ ‘
NOTES.PMR-INSIGNIA.ORG
Semantic rules generating code for a while statement
S.begin:
E.code
S1.code
goto S.begin
S.after: ...
NOTES.PMR-INSIGNIA.ORG
Quadruples
Triples
Indirect triples
Quadruples:
A quadruple is a record structure with four fields, which are, op, arg1, arg2 and result.
The op field contains an internal code for the operator. The three-address statement x : =
y op z is represented by placing y in arg1, z in arg2 and x in result.
The contents of fields arg1, arg2 and result are normally pointers to the symbol-table
entries for the names represented by these fields. If so, temporary names must be entered
into the symbol table as they are created.
Triples:
To avoid entering temporary names into the symbol table, we might refer to a temporary
value by the position of the statement that computes it.
If we do so, three-address statements can be represented by records with only three fields:
op, arg1 and arg2.
The fields arg1 and arg2, for the arguments of op, are either pointers to the symbol table
or pointers into the triple structure ( for temporary values ).
Since three fields are used, this intermediate code format is known as triples.
NOTES.PMR-INSIGNIA.ORG
A ternary operation like x[i] : = y requires two entries in the triple structure as shown as below
while x : = y[i] is naturally represented as two operations.
Indirect Triples:
For example, let us use an array statement to list pointers to triples in the desired order.
Then the triples shown above might be represented as follows:
DECLARATIONS
NOTES.PMR-INSIGNIA.ORG
Declarations in a Procedure:
The syntax of languages such as C, Pascal and Fortran, allows all the declarations in a
single procedure to be processed as a group. In this case, a global variable, say offset, can keep
track of the next available relative address.
Before the first declaration is considered, offset is set to 0. As each new name is seen ,
that name is entered in the symbol table with offset equal to the current value of offset,
and offset is incremented by the width of the data object denoted by that name.
The procedure enter( name, type, offset ) creates a symbol-table entry for name, gives its
type type and relative address offset in its data area.
Attribute type represents a type expression constructed from the basic types integer and
real by applying the type constructors pointer and array. If type expressions are
represented by graphs, then attribute type might be a pointer to the node representing a
type expression.
The width of an array is obtained by multiplying the width of each element by the
number of elements in the array. The width of each pointer is assumed to be 4.
P D { offset : = 0 }
DD;D
NOTES.PMR-INSIGNIA.ORG
Keeping Track of Scope Information:
PD
D D ; D | id : T | proc id ; D ; S
One possible implementation of a symbol table is a linked list of entries for names.
A new symbol table is created when a procedure declaration D proc id D1;S is seen,
and entries for the declarations in D1 are created in the new table. The new table points back to
the symbol table of the enclosing procedure; the name represented by id itself is local to the
enclosing procedure. The only change from the treatment of variable declarations is that the
procedure enter is told which symbol table to make an entry in.
For example, consider the symbol tables for procedures readarray, exchange, and
quicksort pointing back to that for the containing procedure sort, consisting of the entire
program. Since partition is declared within quicksort, its table points to that of quicksort.
sort
nil header
a
x
readarray to readarray
exchange to exchange
quicksort
partition
header
i
j
NOTES.PMR-INSIGNIA.ORG
The semantic rules are defined in terms of the following operations:
1. mktable(previous) creates a new symbol table and returns a pointer to the new table. The
argument previous points to a previously created symbol table, presumably that for the
enclosing procedure.
2. enter(table, name, type, offset) creates a new entry for name name in the symbol table pointed
to by table. Again, enter places type type and relative address offset in fields within the entry.
3. addwidth(table, width) records the cumulative width of all the entries in table in the header
associated with this symbol table.
4. enterproc(table, name, newtable) creates a new entry for procedure name in the symbol table
pointed to by table. The argument newtable points to the symbol table for this procedure
name.
D D1 ; D2
The stack tblptr is used to contain pointers to the tables for sort, quicksort, and partition
when the declarations in partition are considered.
The top element of stack offset is the next available relative address for a local of the
current procedure.
A BC {actionA}
are done before actionA at the end of the production occurs. Hence, the action associated
with the marker M is the first to be done.
NOTES.PMR-INSIGNIA.ORG
The action for nonterminal M initializes stack tblptr with a symbol table for the
outermost scope, created by operation mktable(nil). The action also pushes relative
address 0 onto stack offset.
For each variable declaration id: T, an entry is created for id in the current symbol table.
The top of stack offset is incremented by T.width.
When the action on the right side of D proc id; ND1; S occurs, the width of all
declarations generated by D1 is on the top of stack offset; it is recorded using addwidth.
Stacks tblptr and offset are then popped.
At this point, the name of the enclosed procedure is entered into the symbol table of its
enclosing procedure.
ASSIGNMENT STATEMENTS
Suppose that the context in which an assignment appears is given by the following grammar.
PMD
Mɛ
D D ; D | id : T | proc id ; N D ; S
Nɛ
Nonterminal P becomes the new start symbol when these productions are added to those in the
translation scheme shown below.
S id : = E { p : = lookup ( id.name);
if p ≠ nil then
emit( p ‘ : =’ E.place)
else error }
E E1 + E 2 { E.place : = newtemp;
emit( E.place ‘: =’ E1.place ‘ + ‘ E2.place ) }
E E1 * E 2 { E.place : = newtemp;
emit( E.place ‘: =’ E1.place ‘ * ‘ E2.place ) }
E - E1 { E.place : = newtemp;
emit ( E.place ‘: =’ ‘uminus’ E1.place ) }
E ( E1 ) { E.place : = E1.place }
NOTES.PMR-INSIGNIA.ORG
E id { p : = lookup ( id.name);
if p ≠ nil then
E.place : = p
else error }
Temporaries can be reused by changing newtemp. The code generated by the rules for E
E1 + E2 has the general form:
evaluate E1 into t1
evaluate E2 into t2
t : = t 1 + t2
The lifetimes of these temporaries are nested like matching pairs of balanced parentheses.
statement value of c
0
$0 := a * b 1
$1 := c * d 2
$0 := $0 + $1 1
$1 := e * f 2
$0 := $0 - $1 1
x := $0 0
Elements of an array can be accessed quickly if the elements are stored in a block of
consecutive locations. If the width of each array element is w, then the ith element of array A
begins in location
base + ( i – low ) x w
where low is the lower bound on the subscript and base is the relative address of the storage
allocated for the array. That is, base is the relative address of A[low].
NOTES.PMR-INSIGNIA.ORG
292 Design and Implementation of Compiler
A
fter syntax tree have been constructed, the compiler must check whether the input program is type-
correct (called type checking and part of the semantic analysis). During type checking, a compiler
checks whether the use of names (such as variables, functions, type names) is consistent with their
definition in the program. Consequently, it is necessary to remember declarations so that we can detect
inconsistencies and misuses during type checking. This is the task of a symbol table. Note that a symbol table
is a compile-time data structure. It’s not used during run time by statically typed languages. Formally, a
symbol table maps names into declarations (called attributes), such as mapping the variable name x to its
type int. More specifically, a symbol table stores:
• For each type name, its type definition.
• For each variable name, its type. If the variable is an array, it also stores dimension information. It may
also store storage class, offset in activation record etc.
• For each constant name, its type and value.
• For each function and procedure, its formal parameter list and its output type. Each formal parameter
must have name, type, type of passing (by-reference or by-value), etc.
ARRAY all_symbol_table
ARRAY arr_lexeme
When lexical analyzer reads a letter, it starts saving letters, digits in a buffer ‘lex_bufffer’. The string collected
in lex_bufffer is then looked in the symbol table, using the lookup operation. Since the symbol table initialized
with entries for the keywords plus, minus, AND operator and some identifiers as shown in figure 9.1 the
lookup operation will find these entries if lex_buffer contains either div or mod. If there is no entry for the
string in lex_buffer, i.e., lookup return 0, then lex_buffer contains a lexeme for a new identifier. An entry for
the identifier is created using insert( ). After the insertion is made; ‘n’ is the index of the symbol-table entry
for the string in lex_buffer. This index is communicated to the parser by setting tokenval to n, and the token
in the token field of the entry is returned.
9.3.1 List
The simplest and easiest to implement data structure for symbol table is a linear list of records. We use single
array or collection of several arrays for this purpose to store name and their associated information. Now
names are added to end of array. End of array always marks by a point known as space. When we insert any
name in this list then searching is done in whole array from ‘space’ to beginning of array. If word is not found
in array then we create an entry at ‘space’ and increment ‘space’ by one or value of data type. At this time insert
( ), object look up ( ) operation are performed as major operation while begin_scope ( ) and end_scope( ) are
used in simple table as minor operation field as ‘token type’ attribute etc. In implementation of symbol table
first field always empty because when ‘object-lookup’ work then it will return ‘0’ to indicate no string in
symbol table.
Complexity: If any symbol table has ‘n’ names then for inserting any new name we must search list ‘n’ times
in worst case. So cost of searching is O(n) and if we want to insert ‘n’ name then cost of this insert is O(n^2)
in worst case.
a Integer 2
b Float 4
c Character 1
d Long 4
SPACE
SPACE
(a) (b)
In above figure (a) represent the simple list and (b) represent self organzing list in which Id1 is related to Id2
and Id3 is related to Id1.
A hash table, or a hash map, is a data structure that associates keys with values ‘Open hashing’ is a key that
is applied to hash table. In hashing –open, there is a property that no limit on number of entries that can be
made in table. Hash table consist an array ‘HESH’ and several buckets attached to array HESH according to
hash function. Main advantage of hash table is that we can insert or delete any number or name in O (n) time
if data are search linearly and there are ‘n’ memory location where data is stored. Using hash function any
name can be search in O(1) time. However, the rare worst-case lookup time can be as bad as O(n). A good
hash function is essential for good hash table performance. A poor choice of a hash function is likely to lead
to clustering, in which probability of keys mapping to the same hash bucket (i.e. a collision) occur. One
organization of a hash table that resolves conflicts is chaining. The idea is that we have list elements of type:
class Symbol {
public String key;
public Object binding;
public Symbol next;
public Symbol ( String k, Object v, Symbol r ) { key=k; binding=v;
next=r; }
}
296 Design and Implementation of Compiler
a \n
b c d \n
e \n
f g \n
(i) List
a Integer 2 byte
b Integer 2 byte
c Integer 2 byte
x Integer 2 byte
y Integer 2 byte
sum Integer 2 byte
Space
a b c \n
sum \n
u \n
x y \n
a x
y
b
c sum
To perform its task, the code generation interface will require the extraction of further information associated
with user-defined identifiers and best kept in the symbol table. In the case of constants we need to record the
associated values and in the case of variables we need to record the associated addresses and storage
demands (the elements of array variables will occupy a contiguous block of memory).
STACK INPUT ACTION
0 id + id * id $ GOTO ( I0 , id ) = s5 ; shift
0F3 + id * id $ GOTO ( I0 , F ) = 3
GOTO ( I3 , + ) = r4 ; reduce by T → F
0T2 + id * id $ GOTO ( I0 , T ) = 2
GOTO ( I2 , + ) = r2 ; reduce by E → T
0E1 + id * id $ GOTO ( I0 , E ) = 1
GOTO ( I1 , + ) = s6 ; shift
0 E 1 + 6 id 5 * id $ GOTO ( I5 , * ) = r6 ; reduce by F → id
0E1+6F3 * id $ GOTO ( I6 , F ) = 3
GOTO ( I3 , * ) = r4 ; reduce by T → F
0E1+6T9 * id $ GOTO ( I6 , T ) = 9
GOTO ( I9 , * ) = s7 ; shift
0 E 1 + 6 T 9 * 7 id 5 $ GOTO ( I5 , $ ) = r6 ; reduce by F → id
0 E 1 + 6 T 9 * 7 F 10 $ GOTO ( I7 , F ) = 10
GOTO ( I10 , $ ) = r3 ; reduce by T → T * F
0E1+6T9 $ GOTO ( I6 , T ) = 9
GOTO ( I9 , $ ) = r1 ; reduce by E → E + T
0E1 $ GOTO ( I0 , E ) = 1
GOTO ( I1 , $ ) = accept
TYPE CHECKING
A compiler must check that the source program follows both syntactic and semantic conventions
of the source language.
This checking, called static checking, detects and reports programming errors.
NOTES.PMR-INSIGNIA.ORG
2. Flow-of-control checks – Statements that cause flow of control to leave a construct must have
some place to which to transfer the flow of control. Example: An error occurs when an
enclosing statement, such as break, does not exist in switch statement.
A type checker verifies that the type of a construct matches that expected by its context.
For example : arithmetic operator mod in Pascal requires integer operands, so a type
checker verifies that the operands of mod have type integer.
Type information gathered by a type checker may be needed when code is generated.
TYPE SYSTEMS
The design of a type checker for a language is based on information about the syntactic
constructs in the language, the notion of types, and the rules for assigning types to language
constructs.
For example : “ if both operands of the arithmetic operators of +,- and * are of type integer, then
the result is of type integer ”
Type Expressions
A type expression is either a basic type or is formed by applying an operator called a type
constructor to other type expressions.
The sets of basic types and constructors depend on the language to be checked.
1. Basic types such as boolean, char, integer, real are type expressions.
A special basic type, type_error , will signal an error during type checking; void denoting
“the absence of a value” allows statements to be checked.
NOTES.PMR-INSIGNIA.ORG
Records : The difference between a record and a product is that the fields of a record have
names. The record type constructor will be applied to a tuple formed from field names and
field types.
For example:
type row = record
address: integer;
lexeme: array[1..15] of char
end;
var table: array[1...101] of row;
declares the type name row representing the type expression record((address X integer) X
(lexeme X array(1..15,char))) and the variable table to be an array of records of this type.
Pointers : If T is a type expression, then pointer(T) is a type expression denoting the type
“pointer to an object of type T”.
For example, var p: ↑ row declares variable p to have type pointer(row).
4. Type expressions may contain variables whose values are type expressions.
x pointer
Type systems
A type system is a collection of rules for assigning type expressions to the various parts of
a program.
Different type systems may be used by different compilers or processors of the same
language.
Checking done by a compiler is said to be static, while checking done when the target
program runs is termed dynamic.
Any check can be done dynamically, if the target code carries the type of an element
along with the value of that element.
NOTES.PMR-INSIGNIA.ORG
Sound type system
A sound type system eliminates the need for dynamic checking for type errors because it
allows us to determine statically that these errors cannot occur when the target program runs.
That is, if a sound type system assigns a type other than type_error to a program part, then type
errors cannot occur when the target code for the program part is run.
Error Recovery
Since type checking has the potential for catching errors in program, it is desirable for
type checker to recover from errors, so it can check the rest of the input.
Error handling has to be designed into the type system right from the start; the type
checking rules must be prepared to cope with errors.
Here, we specify a type checker for a simple language in which the type of each identifier
must be declared before the identifier is used. The type checker is a translation scheme that
synthesizes the type of each expression from the types of its subexpressions. The type checker
can handle arrays, pointers, statements and functions.
A Simple Language
P →D;E
D → D ; D | id : T
T → char | integer | array [ num ] of T | ↑ T
E → literal | num | id | E mod E | E [ E ] | E ↑
Translation scheme:
P→D;E
D→D;D
D → id : T { addtype (id.entry , T.type) }
T → char { T.type : = char }
T → integer { T.type : = integer }
T → ↑ T1 { T.type : = pointer(T1.type) }
T → array [ num ] of T1 { T.type : = array ( 1… num.val , T1.type) }
NOTES.PMR-INSIGNIA.ORG
Type checking of expressions
In the following rules, the attribute type for E gives the type expression assigned to the
expression generated by E.
The postfix operator ↑ yields the object pointed to by its operand. The type of E ↑ is the type t
of the object pointed to by the pointer E.
Statements do not have values; hence the basic type void can be assigned to them. If an error is
detected within a statement, then type_error is assigned.
1. Assignment statement:
S → id : = E { S.type : = if id.type = E.type then void
else type_error }
2. Conditional statement:
S → if E then S1 { S.type : = if E.type = boolean then S1.type
else type_error }
3. While statement:
S → while E do S1 { S.type : = if E.type = boolean then S1.type
else type_error }
NOTES.PMR-INSIGNIA.ORG
4. Sequence of statements:
S → S1 ; S2 { S.type : = if S1.type = void and
S1.type = void then void
else type_error }
Procedures:
A procedure definition is a declaration that associates an identifier with a statement. The
identifier is the procedure name, and the statement is the procedure body.
For example, the following is the definition of procedure named readarray :
procedure readarray;
var i : integer;
begin
for i : = 1 to 9 do read(a[i])
end;
When a procedure name appears within an executable statement, the procedure is said to be
called at that point.
Activation trees:
An activation tree is used to depict the way control enters and leaves activations. In an
activation tree,
1. Each node represents an activation of a procedure.
2. The root represents the activation of the main program.
3. The node for a is the parent of the node for b if and only if control flows from activation a to
b.
4. The node for a is to the left of the node for b if and only if the lifetime of a occurs before the
lifetime of b.
Control stack:
A control stack is used to keep track of live procedure activations. The idea is to push the
node for an activation onto the control stack as the activation begins and to pop the node
when the activation ends.
The contents of the control stack are related to paths to the root of the activation tree.
When node n is at the top of control stack, the stack contains the nodes along the path
from n to the root.
NOTES.PMR-INSIGNIA.ORG
The Scope of a Declaration:
A declaration is a syntactic construct that associates information with a name.
Declarations may be explicit, such as:
var i : integer ;
or they may be implicit. Example, any variable name starting with I is assumed to denote an
integer.
The portion of the program to which a declaration applies is called the scope of that declaration.
Binding of names:
Even if each name is declared once in a program, the same name may denote different
data objects at run time. “Data object” corresponds to a storage location that holds values.
The term environment refers to a function that maps a name to a storage location.
The term state refers to a function that maps a storage location to the value held there.
environment state
When an environment associates storage location s with a name x, we say that x is bound
to s. This association is referred to as a binding of x.
STORAGE ORGANISATION
The executing target program runs in its own logical address space in which each
program value has a location.
The management and organization of this logical address space is shared between the
complier, operating system and target machine. The operating system maps the logical
address into physical addresses, which are usually spread throughout memory.
Code
Static Data
Stack
free memory
Heap
NOTES.PMR-INSIGNIA.ORG
Run-time storage comes in blocks, where a byte is the smallest unit of addressable
memory. Four bytes form a machine word. Multibyte objects are stored in consecutive
bytes and given the address of first byte.
The storage layout for data objects is strongly influenced by the addressing constraints of
the target machine.
A character array of length 10 needs only enough bytes to hold 10 characters, a compiler
may allocate 12 bytes to get alignment, leaving 2 bytes unused.
This unused space due to alignment considerations is referred to as padding.
The size of some program objects may be known at run time and may be placed in an
area called static.
The dynamic areas used to maximize the utilization of space at run time are stack and
heap.
Activation records:
Procedure calls and returns are usually managed by a run time stack called the control
stack.
Each live activation has an activation record on the control stack, with the root of the
activation tree at the bottom, the latter activation has its record at the top of the stack.
The contents of the activation record vary with the language being implemented. The
diagram below shows the contents of activation record.
NOTES.PMR-INSIGNIA.ORG
Space for the return value of the called functions, if any. Again, not all called procedures
return a value, and if one does, we may prefer to place that value in a register for
efficiency.
The actual parameters used by the calling procedure. These are not placed in activation
record but rather in registers, when possible, for greater efficiency.
STATIC ALLOCATION
In static allocation, names are bound to storage as the program is compiled, so there is no
need for a run-time support package.
Since the bindings do not change at run-time, everytime a procedure is activated, its
names are bound to the same storage locations.
Therefore values of local names are retained across activations of a procedure. That is,
when control returns to a procedure the values of the locals are the same as they were
when control left the last time.
From the type of a name, the compiler decides the amount of storage for the name and
decides where the activation records go. At compile time, we can fill in the addresses at
which the target code can find the data it operates on.
All compilers for languages that use procedures, functions or methods as units of user-
defined actions manage at least part of their run-time memory as a stack.
Each time a procedure is called , space for its local variables is pushed onto a stack, and
when the procedure terminates, that space is popped off the stack.
Calling sequences:
Procedures called are implemented in what is called as calling sequence, which consists
of code that allocates an activation record on the stack and enters information into its
fields.
A return sequence is similar to code to restore the state of machine so the calling
procedure can continue its execution after the call.
The code in calling sequence is often divided between the calling procedure (caller) and
the procedure it calls (callee).
When designing calling sequences and the layout of activation records, the following
principles are helpful:
Values communicated between caller and callee are generally placed at the
beginning of the callee’s activation record, so they are as close as possible to the
caller’s activation record.
NOTES.PMR-INSIGNIA.ORG
Fixed length items are generally placed in the middle. Such items typically include
the control link, the access link, and the machine status fields.
Items whose size may not be known early enough are placed at the end of the
activation record. The most common example is dynamically sized array, where the
value of one of the callee’s parameters determines the length of the array.
We must locate the top-of-stack pointer judiciously. A common approach is to have
it point to the end of fixed-length fields in the activation record. Fixed-length data
can then be accessed by fixed offsets, known to the intermediate-code generator,
relative to the top-of-stack pointer.
...
Parameters and returned values
caller’s
control link
activation
links and saved status
record
The calling sequence and its division between caller and callee are as follows.
NOTES.PMR-INSIGNIA.ORG
Variable length data on stack:
The run-time memory management system must deal frequently with the allocation of
space for objects, the sizes of which are not known at the compile time, but which are
local to a procedure and thus may be allocated on the stack.
The reason to prefer placing objects on the stack is that we avoid the expense of garbage
collecting their space.
The same scheme works for objects of any type if they are local to the procedure called
and have a size that depends on the parameters of the call.
activation
control link
record for p
pointer to A
pointer to B
pointer to C
array A
arrays of p
array B
array C
arrays of q top
Procedure p has three local arrays, whose sizes cannot be determined at compile time.
The storage for these arrays is not part of the activation record for p.
Access to the data is through two pointers, top and top-sp. Here the top marks the actual
top of stack; it points the position at which the next activation record will begin.
The second top-sp is used to find local, fixed-length fields of the top activation record.
The code to reposition top and top-sp can be generated at compile time, in terms of sizes
that will become known at run time.
NOTES.PMR-INSIGNIA.ORG
HEAP ALLOCATION
Stack allocation strategy cannot be used if either of the following is possible :
1. The values of local names must be retained when an activation ends.
2. A called activation outlives the caller.
Heap allocation parcels out pieces of contiguous storage, as needed for activation records
or other objects.
Pieces may be deallocated in any order, so over the time the heap will consist of alternate
areas that are free and in use.
s Retained activation
s record for r
r q ( 1 , 9) control link
control link
q(1,9)
control link
The record for an activation of procedure r is retained when the activation ends.
Therefore, the record for the new activation q(1 , 9) cannot follow that for s physically.
If the retained activation record for r is deallocated, there will be free space in the heap
between the activation records for s and q.
NOTES.PMR-INSIGNIA.ORG