theory of compilation 236360 erez petrank lecture 8: runtime. 1
TRANSCRIPT
1
Theory of Compilation 236360
Erez Petrank
Lecture 8: Runtime.
Runtime Environment
• Code generated by the compiler to handle stuff that the programmer does not wish to handle.
• For example: file handling, memory management, synchronization (create threads, implement locks, etc.), runtime stack (activation records), dynamic optimization, debugging, etc.
• You can think of all those as library functions, but they are always there by languages definition. – No need to link them in.
• The more complex items nowadays are dynamic memory management, JIT optimization, and management of parallelism.
• We will talk about activation records and present an introduction to memory management. – Threads and synchronization are discussed in the
operating systems course.
3
Microsoft CLR
(image source: wikipedia)
4
Java Virtual Machine (JVM)
Language On JVM
Erlang Erjang
JavaScript Rhino (?)
Pascal Free Pascal
PHP Quercus
Python Jython
REXX NetRexx
Ruby Jruby
Tcl Jacl
…
(image source: wikipedia)
5
JavaScript Engine (Mozilla SpiderMonkey)
• SpiderMonkey is a fast interpreter • SpiderMonkey contains two JavaScript Just-In-Time (JIT)
compilers, a garbage collector, code implementing the basic behavior of JavaScript values…
• SpiderMonkey's interpreter is mainly a single, tremendously long function that steps through the bytecode one instruction at a time, using a switch statement (or faster alternative, depending on the compiler) to jump to the appropriate chunk of code for the current instruction.
(source: https://developer.mozilla.org/En/SpiderMonkey/Internals)
6
Activation Records and Function Call
7
Motivation
• Functions are invoked frequently; it is important to understand what goes on during the execution.
• Handling functions has a significant impact on efficiency.
8
Support of Procedures
• new computing environment – at least temporary memory for local variables
• passing information into the new environment– parameters
• transfer of control to/from procedure• handling return values
9
Design Decisions
• scoping rules– static scoping vs. dynamic scoping
• caller/callee conventions– parameters– who saves register values?
• allocating space for local variables
10
Static (lexical) Scopingmain ( ){
int a = 0 ;int b = 0 ;{
int b = 1 ;{
int a = 2 ;printf (“%d %d\n”, a, b)
}{
int b = 3 ;printf (“%d %d\n”, a, b) ;
}printf (“%d %d\n”, a, b) ;
}printf (“%d %d\n”, a, b) ;
}
B0
B1
B3B3
B2
Declaration Scopes
a=0 B0,B1,B3
b=0 B0
b=1 B1,B2
a=2 B2
b=3 B3
In most modern languages (C, Java), a name refers to its (closest) enclosing scope
known at compile time
11
Dynamic Scoping
• Each identifier is associated with its latest definition in the run.
• To find the relevant definition, search in the local function first, then search in the function that called the local function, then search in the function that called that function, and so on.
12
Dynamic Scoping Implementation
• Each identifier is associated with a global stack of bindings
• When entering scope where identifier is declared– push declaration on identifier stack
• When exiting scope where identifier is declared– pop identifier stack
• Evaluating the identifier in any context binds to the current top of stack
• Determined at runtime• Not always possible to type-check or discover
access to non-initialized variables at compile-time.
13
Example
• what value is returned from main?• static scoping?• dynamic scoping?
int x = 42;
int f() { return x; } int g() { int x = 1; return f(); }int main() { return g(); }
14
Static Scoping Properties
• Java, C++, etc.
• Identifier binding is known at compile time• Address of the variable is known at compile time• Assigning addresses to variables is part of code
generation• No runtime errors of “access to undefined
variable”• Can check types of variables
A Naïve Implementation Using a Table
var x: int;
int foo(): int { return x; }
int bar(): int { var x: int; x = 1;return foo();
}int main() { x = 0;
print bar(); }
Identifier Address
x (global) 432234
x (in bar) 432238
A problem with the naïve implementation: Recursion (direct or indirect)
What is the address of ‘a’ (of fib), in the following example?
procedure fib(n: int) } var a: int;var b: int;if (n == 2) return 1;if (n ≤ 1) return 0;a = fib(n – 1);b = fib(n – 2);return a + b;
}procedure main() { print fib(5);
}• Note that this problem does not exist for dynamic scoping.
17
Activation Record (or Frame)
• A separate space for each procedure invocation
• Managed at runtime– code for managing it generated by the compiler
• Desired properties – efficient allocation and deallocation
• procedures are called frequently
– variable size • different procedures may require different memory sizes
18
Memory Layout
stack grows down (towards lower addresses)
heap grows up (towards higher
addresses)heap
code
static data
stack
19
Activation Record (frame)parameter k
parameter 1
access link
return information
dynamic link
registers & misc
local variablestemporaries
next frame would be here
…
administrativepart
high addresses
lowaddresses
framepointer
stackpointer
incoming parameters
stack grows down
20
Runtime Stack
• Stack of activation records• Call = push new activation record• Return = pop activation record• Only one “active” activation record – top of stack• Recursion handled implicitly.
21
Runtime Stack
• SP – stack pointer – top of current frame
• FP – frame pointer – base of current frame– Sometimes called BP
(base pointer)
• Intel has 8 registers: EAX, EBX, ECX, EDX, ESI, EDI, ESP (stack pointer), EBP (base pointer).
• 2 out of 8 registers are used to manage the stack!
Current frame
… …
Previous frame
SP
FPstack grows down
Pentium Runtime Stack
22
Pentium stack registers
Pentium stack and call/ret instructions
Register Usage
ESP Stack pointer
EBP Base pointer
Instruction Usage
push, pusha,… push on runtime stack
pop,popa,… pop
call transfer control to called routine
return transfer control back to caller
23
Call Sequences
• The processor does not save the content of registers on procedure calls
• So who will? – Caller saves and restores registers– Callee saves and restores registers– But can also have both save/restore some registers
Call Sequences
24
call
calle
r
calle
e
return
calle
r
Caller push code
Callee push code (prologue)
Function codeCallee pop code
(epilogue)
Caller pop code
Push caller-saved registersPush actual parameters (in reverse order)
push return addressJump to call address
Push current base-pointerbp = spPush local variablesPush callee-save registers
Pop callee-save registersPop callee activation recordPop old base-pointer
pop return addressJump to address
Pop parametersPop caller-saved registers
Call Sequences
25
call
calle
rca
llee
return
calle
r
add $8, %esppop %ecx
Push caller-saved registersPush actual parameters (in reverse order)
push return addressJump to call address
Push current base-pointerbp = spPush local variablesPush callee-save registers
Pop callee-save registersPop callee activation recordPop old base-pointer
pop return addressJump to address
Pop parametersPop caller-save registersPop parameters
push %ecxpush $21push $42call _foo
Function code
push %ebpmov %esp, %ebpsub $8, %esppush %ebx
pop %ebxmov %ebp, %esppop %ebpret
call
26
“To Callee-save or to Caller-save?”
• Which of them implies more efficiency? • Callee-saved registers need only be saved when
callee modifies their value. • Caller saving:
– calling a recursive routine, we want to save before the call.
– assumes nothing on callee (library, unknown code).
• Typically, heuristics and conventions are followed.
27
Accessing Stack Variables
• Use offset from FP (%ebp)• Remember –
stack grows downwards• Above FP = parameters• Below FP = locals• Examples
– %ebp + 4 = return address– %ebp + 8 = first parameter– %ebp – 4 = first local
… …
SP
FP
Return address
Local 1…
Local n
Previous fp
Param n…
param1FP+8
FP-4
28
Factorial – fact(int n)fact:pushl %ebp # save frame pointermovl %esp,%ebp # ebp=esppushl %ebx # save ebxmovl 8(%ebp),%ebx # ebx = ncmpl $1,%ebx # n = 1 ?jle .lresult # then doneleal -1(%ebx),%eax # eax = n-1pushl %eax # call fact # fact(n-1)imull %ebx,%eax # eax=retv*njmp .lreturn # .lresult:movl $1,%eax # retv.lreturn:movl -4(%ebp),%ebx # restore ebxmovl %ebp,%esp # restore esppopl %ebp # restore ebp
ESP
EBP
Return address
old %ebx
Previous fp
nEBP+8
EBP-4 old %ebp
old %eax
(stack in intermediate point)
(disclaimer: real compiler can do better than that)
29
Windows Exploit(s) Buffer Overflow
void foo (char *x) { char buf[2]; strcpy(buf, x); } int main (int argc, char *argv[])
{ foo(argv[1]); }
./a.out abracadabraSegmentation fault Stack grows
this way
Memory addresses
Previous frame
Return address
Saved FP
char* x
buf[2]
…
ab
ra
ca
da
br
30
Runtime checks
• generate code for checking attempted illegal operations– Null pointer check
• MoveField, MoveArray, ArrayLength, VirtualCall• Reference arguments to library functions should not be null
– Array bounds check– Array allocation size check– Division by zero– …
• If check fails jump to error handler code that prints a message and gracefully exists program
31
Null pointer check
# null pointer check cmp $0,%eax je labelNPE
labelNPE: push $strNPE # error message call __println push $1 # error code call __exit
Single generated handler for entire program
32
Array bounds check # array bounds check # ecx = index mov -4(%eax),%ebx # ebx = length cmp %ebx,%ecx jle labelABE # ebx <= ecx ? cmp $0,%ecx jl labelABE # ecx < 0 ?
labelABE: push $strABE # error message call __println push $1 # error code call __exit
Single generated handler for entire program
33
Array allocation size check
# array size check cmp $0,%eax # eax == array size jle labelASE # eax <= 0 ?
labelASE: push $strASE # error message call __println push $1 # error code call __exit
Single generated handler for entire program
34
Runtime Checks
• Improve reliability and security• But: costly!
• Java implements them. • C doesn’t.
35
Nested Procedures
• For example – Pascal, Javascript• any routine can have sub-routines• any sub-routine can access anything that is
defined in its containing scopes or inside the sub-routine itself
36
Example: Nested Procedures
program p;var x: Integer;procedure a
var y: Integer;procedure b begin…b… end;
function cvar z: Integer;procedure d begin…d… end;
begin…c…end;
begin…a… end;
begin…p… end.
possible call sequence:
pa a c b c d
What is the address of variable “y” in procedure d?
37
nested procedures• Can call a sibling, ancestor, and ancestor’s siblings. • But can use variables only of ancestors!
• B can call C as it is defined in the ancestor A.• But if C updates y, it updates A’s y.
Procedure A; var y: int; Procedure B;
var y: real;begin …. end;
Procedure C;begin y:=5; end;
38
nested procedures• When “c” uses variables from “a”, which “a” is it?• how do we find the right activation record at runtime?• Goal: find the closest activation record of a given nesting
level. • if routine of level k uses variables of the same level, it uses
its own variables.• if it uses variables of level
j < k then it must be the last routine called at level j
• If a procedure is last at level j on the stack, then it must be an ancestor of the current routine
P
a
b
P
Pc
Pd
a Pa
Pc
Pd
39
Finding the Relevant Variable
• problem: a routine may need to access variables of another routine that contains it statically
• solution: the access link in the activation record• The access link points to the last activation record of the
nesting level above it– in our example, access link of d points to activation records of
c
• Access links are created at runtime• number of links to be traversed is known at compile time
• Cost while accessing a variable: traversing pointers from one nesting level to the other.
• Cost while entering a routine: walk the stack to find the closest routine with one lower nesting level.
40
access links
program p;var x: Integer;procedure a var y: Integer; procedure b begin…b…
end; procedure c
var z: Integer; procedure d begin…d… end;
begin…c…end; begin…a… end;begin…p… end.
a
a
c
b
c
d
y
y
z
z
possible call sequence:pa a c b c d
a
b
P
c c
d
a
41
Efficient management of the access links
• We maintain an array: the display array. • Size: max nesting level, • Content: D[i] holds a pointer to the closest
activation record with nesting level i. • When we need to access a variable in a
containing method, we can access it easily using the display array.
Managing the display array
• When a routine of nesting level i is called:
• Save the value of D[i] in the activation record (for restoration on exit)
• Update D[i] to point at the new activation record.
• On returning from the routine, restore the previous value of D[i].
access link
q(1, 9)
sd [ 1 ]
d [ 2 ]
Managing the display array
• When a routine of nesting level i is called:
• Save the value of D[i] in the activation record (for restoration on exit)
• Update D[i] to point at the new activation record.
• On returning from the routine, restore the previous value of D[i].
saved d [ 2 ]
q(1, 3)
saved d [ 2 ]
q(1, 9)
sd [ 1 ]
d [ 2 ]
Managing the display array
• When a routine of nesting level i is called:
• Save the value of D[i] in the activation record (for restoration on exit)
• Update D[i] to point at the new activation record.
• On returning from the routine, restore the previous value of D[i].
saved d [ 3 ]
p(1, 3)
saved d [ 2 ]
q(1, 3)
saved d [ 2 ]
q(1, 9)
sd [ 1 ]
d [ 2 ]
d [ 3 ]
Managing the display array
• When a routine of nesting level i is called:
• Save the value of D[i] in the activation record (for restoration on exit)
• Update D[i] to point at the new activation record.
• On returning from the routine, restore the previous value of D[i].
saved d [ 2 ]
e(1, 3)
saved d [ 3 ]
p(1, 3)
saved d [ 2 ]
q(1, 3)
saved d [ 2 ]
q(1, 9)
sd [ 1 ]
d [ 2 ]
d [ 3 ]
46
Cost of finding a Variable
• Without the Display: – Accessing a variable: traversing pointers from one nesting level to the
other. – Entering a routine: walk the stack to find the closest routine with one
lower nesting level.
• Using the Display: – Accessing a variable: constant. (check the Display.) – Entering/exiting a routine: constant. (update the Display using the
current frame.)
• Runtime costs !
47
Activation Records: Summary
• compile time memory management for procedure data
• works well for data with well-scoped lifetime– deallocation when procedure returns
48
Dynamic Memory Management: Introduction
There is a course about this topic: 236780 “Algorithms for dynamic memory management”
49
Static and Dynamic Variables
• Static variables are defined in a method and are allocated on the runtime stack, as explained in the first part of this lecture.
• Sometimes there is a need for allocation during the run. – E.g., when managing a linked-list whose size is not
predetermined.
• This is dynamic allocation. • In C, “malloc” allocates a space and “delete” says that the
program will not use this space anymore.
Ptr = malloc (256 bytes);/* Use ptr */Free (Ptr);
50
Dynamic Memory Allocation
• In Java, “new” allocates an object for a given class. – President obama = new President
• But there is no instruction for manually deleting the object.
• Objects reclaimed by a garbage collector when the program “does not need it” anymore.
course c = new course(236360);c.class = “TAUB 2”;Faculty.add(c);
51
Manual Vs. Automatic Memory Management
• A manual memory management lets the programmer decide when objects are deleted.
• A memory manager that lets a garbage collector delete objects is called automatic.
• Manual memory management creates severe debugging problems– Memory leaks,– Dangling pointers.
• In large projects where objects are shared between various components it is sometimes difficult to tell when an object is not needed anymore.
• Considered the BIG debugging problem of the 80’s• What is the main debugging problem today?
52
Automatic Memory Reclamantion
• When the system “knows” the object will not be used anymore, it reclaims its space.
• Telling whether an object will be used after a given line of code is undecidable.
• Therefore, a conservative approximation is used. • An object is reclaimed when the program has “no way of
accessing it”. • Formally, when it is unreachable by a path of pointers from
the “root” pointers, to which the program has direct access. – Local variables, pointers on stack, global (class) pointers, JNI
pointers, etc.
• It is also possible to use code analysis to be more accurate sometimes.
What’s good about automatic “garbage collection”?
© Erez Petrank 53
• Software engineering: – Relieves users of the book-keeping burden. – Stronger reliability, fewer bugs, faster debugging. – Code understandable and reliable. (Less interaction
between modules.)
• Security (Java):– Program never gets a pointer to “play with”.
54
Importance
• Memory is the bottleneck in modern computation. – Time & energy (and space).
• Optimal allocation (even if all accesses are known in advance to the allocator) is NP-Complete (to even approximate).
• Must be done right for a program to run efficiently.
• Must be done right to ensure reliability.
GC and languages
© Erez Petrank 55
• Sometimes it’s built in:– LISP, Java, C#.– The user cannot free an object.
• Sometimes it’s an added feature:– C, C++.– User can choose to free objects or not. The collector
frees all objects not freed by the user.
• Most modern languages are supported by garbage collection.
© Erez Petrank 56
Most modern languages rely on GC
Source: “The Garbage Collection Handbook” by Richard Jones, Anthony Hosking, and Eliot Moss.
61
7
What’s bad about automatic “garbage collection”?
© Erez Petrank 57
• It has a cost: – Old Lisp systems 40%.– Today’s Java program (if the collection is done “right”)
5-15%.
• Considered a major factor determining program efficiency.
• Techniques have evolved since the 60’s. We will only go over basic techniques.
58
Garbage Collection Efficiency
• Overall collection overheads (program throughput).
• Pauses in program run. • Space overhead. • Cache Locality (efficiency and energy).
59
Three classical algorithms
• Reference counting• Mark and sweep (and mark-compact)• Copying.
• The last two are also called tracing algorithms because they go over (trace) all reachable objects.
Reference counting
60
• Recall that we would like to know if an object is reachable from the roots.
• Associate a reference count field with each object: how many pointers reference this object.
• When nothing points to an object, it can be deleted.
• Very simple, used in many systems.
Basic Reference Counting
61
• Each object has an RC field, new objects get o.RC:=1.
• When p that points to o1 is modified to point to o2 we execute: o1.RC--, o2.RC++.
• if then o1.RC==0:– Delete o1.
– Decrement o.RC for all “children” of o1.
– Recursively delete objects whose RC is decremented to 0.
o1 o2
p
A Problem: Cycles
62
• The Reference counting algorithm does not reclaim cycles!
• Solution 1: ignore cycles, they do not appear frequently in modern programs.
• Solution 2: run tracing algorithms (that can reclaim cycles) infrequently.
• Solution 3: designated algorithms for cycle collection.
• Another problem for the naïve algorithm: requires a lot of synchronization in parallel programs.
• Advanced versions solve that.
The Mark-and-Sweep Algorithm
63
• Mark phase:– Start from roots and traverse all objects reachable by
a path of pointers. – Mark all traversed objects.
• Sweep phase:– Go over all objects in the heap. – Reclaim objects that are not marked.
The Mark-Sweep algorithm
64
• Traverse live objects & mark black. • White objects can be reclaimed.
sta
ckHeap
registers
Roots
Note! This is not the heap data structure!
Triggering
65
New(A)=if free_list is empty
mark_sweep() if free_list is empty
return (“out-of-memory”)pointer = allocate(A)return (pointer)
Garbage collection is triggered by allocation.
Basic Algorithm
66
mark_sweep()= for Ptr in Roots
mark(Ptr)sweep()
mark(Obj)=if mark_bit(Obj) == unmarked
mark_bit(Obj)=markedfor C in Children(Obj)
mark(C)
Sweep()=p = Heap_bottomwhile (p < Heap_top)
if (mark_bit(p) == unmarked) then free(p)else mark_bit(p) = unmarked; p=p+size(p)
Properties of Mark & Sweep
67
• Most popular method today (at a more advanced form). • Simple.• Does not move objects, and so heap may fragment. • Complexity:
Mark phase: live objects (dominant phase) Sweep phase: heap size.
• Termination: each pointer traversed once. • Various engineering tricks are used to improve
performance.
• During the run objects are allocated and reclaimed. • Gradually, the heap gets fragmented. • When space is too fragmented to allocate, a compaction
algorithm is used. • Move all live objects to the beginning of the heap and
update all pointers to reference the new locations. • Compaction is considered very costly and we usually
attempt to run it infrequently, or only partially.
Mark-Compact
68
The Heap
69
An Example: The Compressor
• A simplistic presentation of the Compressor: • Go over the heap and compute for each live object where it
moves to – To the address that is the sum of live space before it in the
heap. – Save the new locations in a separate table.
• Go over the heap and for each object: – Move it to its new location– Update all its pointers.
• Why can’t we do it all in a single heap pass? • (In the full algorithm: succinct table, execute the first pass
very quickly, and parallelization.)
70
Mark Compact
• Important parameters of a compaction algorithm:– Keep order of objects?– Use extra space for compactor data structures?– How many heap passes? – Can it run in parallel on a multi-processor?
• We do not elaborate in this intro.
Copying garbage collection
71
• Heap partitioned into two.• Part 1 takes all allocations.• Part 2 is reserved. • During GC, the collector traces all reachable
objects and copies them to the reserved part. • After copying the parts roles are reversed: • Allocation activity goes to part 2, which was
previously reserved. • Part 1, which was active, is reserved till next
collection.
1 2
Copying garbage collection
72
Part I Part II
Roots
A
D
C
B
E
The collection copies…
73
Part I Part II
Roots
A
D
C
B
E
A C
Roots are updated; Part I reclaimed.
74
Part I Part II
Roots
A C
Properties of Copying Collection
75
• Compaction for free• Major disadvantage: half of the heap is not
used. • “Touch” only the live objects
– Good when most objects are dead. – Usually most new objects are dead, and so there are
methods that use a small space for young objects and collect this space using copying garbage collection.
A very simplistic comparison
Copying
Mark & sweep
Reference Counting
Live objects
Size of heap (live objects)
Pointer updates + dead objects
Complexity
Half heap wasted
Bit/object + stack for DFS
Count/object + stack for DFS
Space overhead
For free Additional work Additional work
Compaction
long long Mostly short Pause time
Cycle collection
More issues
© Erez Petrank 77
Generational Garbage Collection
“The weak generational hypothesis”: most objects die young.
Using the hypothesis: separate objects according to their ages and collect
the area of the young objects more frequently.
© Erez Petrank 78
More Specifically,
• The heap is divided into two or more areas (generations).
• Objects allocated in 1st (youngest) generation. • The youngest generation is collected frequently. • Objects that survive in the young generation
“long enough” are promoted to the old generation.
© Erez Petrank 79
• Short pauses: the young generation is kept small and so most pauses are short.
• Efficiency: collection efforts are concentrated where many dead objects exists.
• Locality: • Collector: mostly concentrated on a small part of
the heap• Program: allocates (and mostly uses) young
objects in a small part of the memory.
Advantages
© Erez Petrank 80
Mark-Sweep or Copying ?
• Copying is good when live space is small (time) and heap is small (space).
• A popular choice: – Copying for the (small) young generation.– Mark-and-sweep for the full collection.
• A small waste in space, high efficiency.
© Erez Petrank 81
Inter-Generational Pointers
• Pointers from objects in the old generation to objects in the young generation may witnesses the liveness of an object.
• We don’t want to trace the old generation to see if the pointing objects are alive. – Why?
• The solution: – “Keep a list” of all inter-generational pointers.– Assume (conservatively) the parent (old) object is
alive. – Treat these pointers as additional roots.
• “Typically”: most pointers are from young to old (few from old to young).
• When collecting the old generation, collect the entire heap.
© Erez Petrank 82
Inter-Generational Pointers
• Inter-generational pointers are created: – When objects are promoted to old generation– When pointers are modified in the old generation.
• The first can be monitored by the collector during promotion.
• The second requires a write barrier.
© Erez Petrank 83
Write Barrier
Update(object y, field f, object x) { if y is in old space and x is in young
remember y.f->x ; y.f := x;}
x f
young old
Reference counting also had a write-barrier:
update(object y, field f, object x) {x.RC++; // increment new referent
y.f^.RC--; // decrement old referentif (y.f^.RC == 0) collect(y.f); y.f := x;
}
Y
84
Memory Management with Parallel Processors
• Stop the world• Parallel (stop-the-world) GC• Concurrent GC
• Trade-offs in pauses and throughput• Difference in complexity• Choose between parallel (longer pauses) and
concurrent (lower throughput)
© Erez Petrank 85
Terminology
Stop-the-World
Parallel
Concurrent
On-the-Fly
programGC
© Erez Petrank 86
Concurrent GC• Problem: Long pauses disturb the user.An important measure for the collection: length
of pauses.
• Can we just run the program while the collector runs on a different thread?
• Not so simple!• The heap changes while we collect. • For example,
– we look at an object B, but before we have a chance to mark its children, the program changes them.
© Erez Petrank 87
Example
A
B
C
A
B
C
A
B
C
GC marks A’s children and makes A black.
Time
Programmodifies pointer.
Collector fails to trace C.
Problem: a new un-nonticed object becomes a child of an already scanned object.
88
Solution with a write-barrier
• The program notifies the collector that changes happen so that it can mark objects conservatively.
• E.g.: the program registers objects that gain pointers.
• update(object y, field f, object x) {notifyCollector(x); // record new referent y.f := x;
}
• Collector can then assume all recordedobjects are alive (and trace their descendants as live).
Y
XZ
89
Modern Memory Management
• Handle parallel platforms.• Real-time. • Cache consciousness. • Handle new platforms (GPU, PCM, …) • Hardware assistance.
90
Summary: Dynamic Memory Management
• Compiler generates code for allocation of objects and garbage collection when necessary.
• Reference-Counting, Mark-Sweep, Mark-Compact, Copying. • Generations
– inter-generational pointers, write barrier.
• Concurrent garbage collection
91
Runtime Summary• Runtime:
– services that are always there: function calls, memory management, threads, etc.
– We discussed function calls• scoping rules• activation records• caller/callee conventions• Nested procedures (and the display array)
– Memory Management• mark-sweep• copying• reference counting• compaction
92
OO Issues
93
Representing Data at Runtime
• Source language types– int, boolean, string, object types
• Target language types– Single bytes, integers, address representation
• Compiler should map source types to some combination of target types– Implement source types using target types
94
Basic Types
• int, boolean, string, void• Arithmetic operations
– Addition, subtraction, multiplication, division, remainder
• Can be mapped directly to target language types and operations
95
Pointer Types
• Represent addresses of source language data structures
• Usually implemented as an unsigned integer• Pointer dereferencing – retrieves pointed value
• May produce an error– Null pointer dereference – when is this error triggered?
96
Object Types
• An object is a record with built in methods and some additional features.
• Basic operations– Field selection + read/write
• computing address of field, dereferencing address
– Copying• copy block (not Java) or field-by-field copying
– Method invocation• Identifying method to be called, calling it
• How does it look at runtime?
97
Object Types
class Foo { int x; int y;
void rise() {…} void shine() {…}}
x
y
rise
shine
Compile time information
Runtime memory layout for object of class Foo
DispacthVectorPtr
98
Field Selection
x
y
rise
shine
Compile time information
Runtime memory layout for object of class Foo
DispacthVectorPtrFoo f;int q;
q = f.x;
MOV f, %EBXMOV 4(%EBX), %EAXMOV %EAX, q
base pointer
field offset
from base pointer
99
Object Types - Inheritance
x
y
rise
shine
Compile time information
Runtime memory layout for object of class Bar
twinkle
z
DispacthVectorPtr
class Foo { int x; int y;
void rise() {…} void shine() {…}}
class Bar extends Foo{ int z; void twinkle() {…}}
100
Object Types - Polymorphism
class Foo { … void rise() {…} void shine() {…}}
x
y
Runtime memory layout for object of class Bar
class Bar extends Foo{ …}
z
class Main { void main() { Foo f = new Bar(); f.rise();}
f
Pointer to Bar
Pointer to Foo inside Bar
DVPtr
101
Static & Dynamic Binding
• Which “rise” should is main() using? • Static binding: f is of type Foo and therefore it always refers
to Foo’s rise. • Dynamic binding: f points to a Bar object now, so it refers
to Bar’s rise.
class Foo { … void rise() {…} void shine() {…}}
class Bar extends Foo{ void rise() {…}}
class Main { void main() { Foo f = new Bar(); f.rise();}
102
Typically, Dynamic Binding is used
• Finding the right method implementation at runtime according to object type
• Using the Dispatch Vector (a.k.a. Dispatch Table)
class Foo { … void rise() {…} void shine() {…}}
class Bar extends Foo{ void rise() {…}}
class Main { void main() { Foo f = new Bar(); f.rise();}
103
Dispatch Vectors in Depth
• Vector contains addresses of methods• Indexed by method-id number• A method signature has the same id number for all subclasses
class Main { void main() { Foo f = new Bar(); f.rise();}
class Foo { … void rise() {…} void shine() {…}}
01
class Bar extends Foo{ void rise() {…}}
0
xyz
fPointer to Bar
Pointer to Foo inside BarDVPtr
shine
rise
shinerise
Dispatchvector for Bar
Methodcode
using Bar’s
dispatch table
104
Dispatch Vectors in Depthclass Main { void main() { Foo f = new Foo(); f.rise();}
class Foo { … void rise() {…} void shine() {…}}
01
class Bar extends Foo{ void rise() {…}}
0
xy
f
Pointer to Foo
DVPtrshine
rise
shinerise
using Foo’s
dispatch table
Dispatchvector for Foo
Methodcode
Multiple Inheritance
105
supertyping convert_ptr_to_E_to_ptr_to_C(e) = e convert_ptr_to_E_to_ptr_to_D(e) = e + sizeof (class C)
subtyping convert_ptr_to_C_to_ptr_to_E(e) = e convert_ptr_to_D_to_ptr_to_E(e) = e - sizeof (class C)
class C { field c1; field c2; void m1() {…} void m2() {…}}class D { field d1; void m3() {…} void m4() {…}}class E extends C,D{ field e1; void m2() {…} void m4() {…} void m5() {…}}
c1c2
DVPtr
Pointer to E
Pointer to C inside EDVPtr
m2_C_E
m1_C_C
E-Object layout
Dispatchvector
d1e1
m4_D_E
m3_D_D
m5_E_EPointer to D inside E
106
Runtime checks
• generate code for checking attempted illegal operations– Null pointer check– Array bounds check– Array allocation size check– Division by zero– …
• If check fails jump to error handler code that prints a message and gracefully exists program
107
Null pointer check
# null pointer check cmp $0,%eax je labelNPE
labelNPE: push $strNPE # error message call __println push $1 # error code call __exit
Single generated handler for entire program
108
Array bounds check
# array bounds check mov -4(%eax),%ebx # ebx = length# ecx holds index cmp %ecx,%ebx jle labelABE # ebx <= ecx ? cmp $0,%ecx jl labelABE # ecx < 0 ?
labelABE: push $strABE # error message call __println push $1 # error code call __exit
Single generated handler for entire program
109
Array allocation size check
# array size check cmp $0,%eax # eax == array size jle labelASE # eax <= 0 ?
labelASE: push $strASE # error message call __println push $1 # error code call __exit
Single generated handler for entire program
110
Recap• Lexical analysis
– regular expressions identify tokens (“words”)
• Syntax analysis– context-free grammars identify the structure of the program
(“sentences”)
• Contextual (semantic) analysis– type checking defined via typing judgements– can be encoded via attribute grammars– Syntax directed translation
• Intermediate representation – many possible IRs; generation of intermediate representation;
3AC; backpatching
• Runtime: – services that are always there: function calls, memory
management, threads, etc.
111
Journey inside a compiler
LexicalAnalysi
s
Syntax Analysi
s
Sem.Analysi
s
Inter.Rep.
Code Gen.
float position;
float initial;
float rate;
position = initial + rate * 60
<float> <ID,position> <;> <float> <ID,initial> <;> <float> <ID,rate> <;> <ID,1> <=> <ID,2> <+> <ID,3> <*> <60>
TokenStream
112
Journey inside a compiler
LexicalAnalysi
s
Syntax Analysi
s
Sem.Analysi
s
Inter.Rep.
Code Gen.
<ID,1> <=> <ID,2> <+> <ID,3> <*> <60>
60
<id,1>
=
<id,3>
<id,2>
+
*
AST
id symbol type data
1 position float …
2 initial float …
3 rate float …
symbol table
S ID = EE ID | E + E | E * E | NUM
113
Journey inside a compiler
LexicalAnalysi
s
Syntax Analysi
s
Sem.Analysi
s
Inter.Rep.
Code Gen.
60
=
<id,3>
<id,2>
+
*
<id,1>
inttofloat
60
<id,1>
=
<id,3>
<id,2>
+
*
AST AST
coercion: automatic conversion from int to floatinserted by the compiler
id symbol type
1 position float
2 initial float
3 rate float
symbol table
114
Journey inside a compiler
LexicalAnalysi
s
Syntax Analysi
s
Sem.Analysi
s
Inter.Rep.
Code Gen.
t1 = inttofloat(60)t2 = id3 * t1t3 = id2 + t2id1 = t3
3AC
60
=
<id,3>
<id,2>
+
*
<id,1>
inttofloat
production semantic rule
S id = E S.code := E. code || gen(id.var ‘:=‘ E.var)
E E1 op E2 E.var := freshVar(); E.code = E1.code || E2.code || gen(E.var ‘:=‘ E1.var ‘op’ E2.var)
E inttofloat(num) E.var := freshVar(); E.code = gen(E.var ‘:=‘ inttofloat(num))
E id E.var := id.var; E.code = ‘’
t1 = inttofloat(60)t2 = id3 * t1
t3 = id2 * t2id1 = t3
(for brevity, bubbles show only code generated by the node and not all accumulated “code” attribute)
note the structure:translate E1translate E2
handle operator
115
Journey inside a compiler
Inter.Rep.
Code Gen.
LexicalAnalysi
s
Syntax Analysi
s
Sem.Analysi
s
3AC Optimized
t1 = inttofloat(60)t2 = id3 * t1t3 = id2 + t2id1 = t3
t1 = id3 * 60.0id1 = id2 + t1
value known at compile timecan generate code with converted value
eliminated temporary t3
116
Journey inside a compiler
Inter.Rep.
Code Gen.
LexicalAnalysi
s
Syntax Analysi
s
Sem.Analysi
s
Optimized
t1 = id3 * 60.0id1 = id2 + t1
Code Gen
LDF R2, id3MULF R2, R2, #60.0LDF R1, id2ADDF R1,R1,R2STF id1,R1
117
Problem 3.8 from [Appel]
A simple left-recursive grammar: E E + id E id
A simple right-recursive grammar accepting the same language:
E id + E E id
Which has better behavior for shift-reduce parsing?
118
Answer
The stack never has more than three items on it. In general, withLR-parsing of left-recursive grammars, an input string of length O(n)requires only O(1) space on the stack.
E E + idE id
Input
id+id+id+id+id
id (reduce) E E + E + id (reduce) E E + E + id (reduce) E E + E + id (reduce) E E + E + id (reduce) E
stack
left recursive
119
Answer
The stack grows as large as the input string. In general, with LR-parsingof right-recursive grammars, an input string of length O(n) requires O(n) space on the stack.
E id + EE id
Input
id+id+id+id+id
id id + id + id id + id + id + id + id id + id + id id + id + id + id id + id + id + id + id + id + id + id + id (reduce) id + id + id + id + E (reduce) id + id + id + E (reduce) id + id + E (reduce) id + E (reduce) E
stack
right recursive