Theory of Compilation 236360 Erez Petrank Lecture 8: Runtime. 1.

Post on 18-Jan-2016

219 views 0 download

Transcript of Theory of Compilation 236360 Erez Petrank Lecture 8: Runtime. 1.

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