CS2210: Compiler Construction. Runtime Environment

Similar documents
CS2210: Compiler Construction. Runtime Environment

Code Generation. Lecture 19

Lecture Outline. Topic 1: Basic Code Generation. Code Generation. Lecture 12. Topic 2: Code Generation for Objects. Simulating a Stack Machine

Code Generation. Lecture 12

Code Generation. The Main Idea of Today s Lecture. We can emit stack-machine-style code for expressions via recursion. Lecture Outline.

We can emit stack-machine-style code for expressions via recursion

Automatic Memory Management

Runtime. The optimized program is ready to run What sorts of facilities are available at runtime

Run-Time Environments/Garbage Collection

Acknowledgements These slides are based on Kathryn McKinley s slides on garbage collection as well as E Christopher Lewis s slides

G Programming Languages - Fall 2012

Code Generation & Parameter Passing

The remote testing experiment. It works! Code Generation. Lecture 12. Remote testing. From the cs164 newsgroup

Programming Language Implementation

Lecture 13: Garbage Collection

Run-time Environments

Calling Conventions. Hakim Weatherspoon CS 3410, Spring 2012 Computer Science Cornell University. See P&H 2.8 and 2.12

Run-time Environments

G Programming Languages - Fall 2012

One-Slide Summary. Lecture Outine. Automatic Memory Management #1. Why Automatic Memory Management? Garbage Collection.

Code Generation II. Code generation for OO languages. Object layout Dynamic dispatch. Parameter-passing mechanisms Allocating temporaries in the AR

CMSC 330: Organization of Programming Languages. Memory Management and Garbage Collection

CMSC 330: Organization of Programming Languages

Example. program sort; var a : array[0..10] of integer; procedure readarray; : function partition (y, z :integer) :integer; var i, j,x, v :integer; :

Memory Allocation. Static Allocation. Dynamic Allocation. Dynamic Storage Allocation. CS 414: Operating Systems Spring 2008

CS61C : Machine Structures

CS 536 Introduction to Programming Languages and Compilers Charles N. Fischer Lecture 11

Prof. Kavita Bala and Prof. Hakim Weatherspoon CS 3410, Spring 2014 Computer Science Cornell University. See P&H 2.8 and 2.12, and A.

CMSC 330: Organization of Programming Languages

Compilers. 8. Run-time Support. Laszlo Böszörmenyi Compilers Run-time - 1

Concepts Introduced in Chapter 7

Garbage Collection (1)

Run-time Environments. Lecture 13. Prof. Alex Aiken Original Slides (Modified by Prof. Vijay Ganesh) Lecture 13

Garbage Collection. Steven R. Bagley

Name, Scope, and Binding. Outline [1]

Exploiting the Behavior of Generational Garbage Collector

Manual Allocation. CS 1622: Garbage Collection. Example 1. Memory Leaks. Example 3. Example 2 11/26/2012. Jonathan Misurda

Algorithms for Dynamic Memory Management (236780) Lecture 4. Lecturer: Erez Petrank

Sustainable Memory Use Allocation & (Implicit) Deallocation (mostly in Java)

Advanced Programming & C++ Language

Managed runtimes & garbage collection. CSE 6341 Some slides by Kathryn McKinley

G Programming Languages Spring 2010 Lecture 4. Robert Grimm, New York University

Managed runtimes & garbage collection

Code Generation Super Lectures

COMP 303 Computer Architecture Lecture 3. Comp 303 Computer Architecture

Binding and Storage. COMP 524: Programming Language Concepts Björn B. Brandenburg. The University of North Carolina at Chapel Hill

CS 345. Garbage Collection. Vitaly Shmatikov. slide 1

ACM Trivia Bowl. Thursday April 3 rd (two days from now) 7pm OLS 001 Snacks and drinks provided All are welcome! I will be there.

Lecture Notes on Garbage Collection

CSE 504. Expression evaluation. Expression Evaluation, Runtime Environments. One possible semantics: Problem:

Anne Bracy CS 3410 Computer Science Cornell University

Run Time Environments

Compiler Construction D7011E

MIPS Functions and the Runtime Stack

CS 4120 Lecture 37 Memory Management 28 November 2011 Lecturer: Andrew Myers

COL728 Minor2 Exam Compiler Design Sem II, Answer all 5 questions Max. Marks: 20

Do-While Example. In C++ In assembly language. do { z--; while (a == b); z = b; loop: addi $s2, $s2, -1 beq $s0, $s1, loop or $s2, $s1, $zero

CA Compiler Construction

Garbage Collection. Akim D le, Etienne Renault, Roland Levillain. May 15, CCMP2 Garbage Collection May 15, / 35

Lecture Outine. Why Automatic Memory Management? Garbage Collection. Automatic Memory Management. Three Techniques. Lecture 18

Lecture 15 Garbage Collection

CS 61c: Great Ideas in Computer Architecture

Code Generation. Lecture 30

Opera&ng Systems CMPSCI 377 Garbage Collec&on. Emery Berger and Mark Corner University of Massachuse9s Amherst

Lecture 13: Complex Types and Garbage Collection

Machine Language Instructions Introduction. Instructions Words of a language understood by machine. Instruction set Vocabulary of the machine

Chapter 2. Instructions: Language of the Computer. Adapted by Paulo Lopes

Garbage Collection. Hwansoo Han

Chapter 2. Computer Abstractions and Technology. Lesson 4: MIPS (cont )

CS 241 Honors Memory

Branch Addressing. Jump Addressing. Target Addressing Example. The University of Adelaide, School of Computer Science 28 September 2015

Programming Languages Third Edition. Chapter 10 Control II Procedures and Environments

CSE Lecture In Class Example Handout

CS577 Modern Language Processors. Spring 2018 Lecture Garbage Collection

Garbage Collection. Vyacheslav Egorov

Lecture Outline. Code Generation. Lecture 30. Example of a Stack Machine Program. Stack Machines

Garbage Collection Techniques

Run-time Environments - 3

Anne Bracy CS 3410 Computer Science Cornell University

CS 316: Procedure Calls/Pipelining

CSE Lecture In Class Example Handout

CSE P 501 Compilers. Memory Management and Garbage Collec<on Hal Perkins Winter UW CSE P 501 Winter 2016 W-1

See P&H 2.8 and 2.12, and A.5-6. Prof. Hakim Weatherspoon CS 3410, Spring 2015 Computer Science Cornell University

Chapter 2A Instructions: Language of the Computer

Project. there are a couple of 3 person teams. a new drop with new type checking is coming. regroup or see me or forever hold your peace

Compiler Construction

Parallel GC. (Chapter 14) Eleanor Ainy December 16 th 2014

Design Issues. Subroutines and Control Abstraction. Subroutines and Control Abstraction. CSC 4101: Programming Languages 1. Textbook, Chapter 8

Midterm II CS164, Spring 2006

Robust Memory Management Schemes

Compilers and computer architecture: A realistic compiler to MIPS

Automatic Garbage Collection

MIPS Functions and Instruction Formats

Run-time Environment

Lecture Notes on Advanced Garbage Collection

Compiler Construction

Motivation for Dynamic Memory. Dynamic Memory Allocation. Stack Organization. Stack Discussion. Questions answered in this lecture:

Dynamic Storage Allocation

Agenda. CSE P 501 Compilers. Java Implementation Overview. JVM Architecture. JVM Runtime Data Areas (1) JVM Data Types. CSE P 501 Su04 T-1

Hakim Weatherspoon CS 3410 Computer Science Cornell University

Transcription:

: The environment in which the program executes in at runtime Includes HW: Main memory, CPU,... Includes OS: Environment variables,... Includes Runtime Libraries: C Runtime Library (libc),... When a program is invoked The OS allocates memory for the program Program code and data is loaded into the main memory Program initializes runtime environment Program jumps to entry point main() All program binaries include two parts: " Code implementing semantics of program Runtime code

Runtime Code CS2210: Compiler Construction Runtime code: any code not implementing semantics Code to manage runtime environment Manage main memory storage (e.g. heap/stack) Manage CPU register storage Manage multiple CPUs (for languages with threading) Code to implement language execution model Code to pass function arguments according to model Code to do dynamic type checking (if applicable) Code to ensure security (if applicable) May even include compiler itself! (just-in-time compiler) Some runtime codes are pre-fabricated libraries E.g. Heap data management, threading library... Some generated by compiler, interleaved in program code E.g. Stack data management, register management, argument passing, type checking,...

Runtime Code for Memory Management 3 types of data that need to be stored in memory 1. Data with static lifetimes (duration of program) (E.g. global variables, static local variables, program code) 2. Data with scoped lifetimes (within given scope) (E.g. local variables, function parameters) 3. Data with arbitrary lifetimes (on-demand alloc/free) (E.g. malloc()/free(), new/delete) 1. and 2. are called named memory Has either variable or function name associated with data For code gen, compiler must know address for each name Compiler must lay out named memory at compile time Compiler must also generate memory management code 3. is called unnamed memory Pointers may point to data, but data itself is anonymous Can be managed by runtime library, not involving compiler

3 Types of Memory Management Static data management: static-lifetime data Stores data at fixed locations laid out at compile-time Laid out data forms an executable image file Contains program code, as well as initial values for vars File copied verbatim to memory at program launch Stack data management: scoped-lifetime data Stores data in memory area managed like a stack Data pushed / popped when scope entered / exited Memory allocated only for the scope where data is valid Compiler generates runtime code to manage stack Heap data management: arbitrary-lifetime data Store data in area that allows on-demand alloc/free Typically managed by memory management runtime library

Example Memory Layout Example layout of program memory at runtime... static data code of g() code of f() code of main() global data segment code heap data data stack data 0xBFFFFFF

Managing Activation Records Activation Record (AR): data required for function call Includes local variables, function parameters... Lifetime of data is scope of function For static data, no choice but to use static management For AR data, We have a choice: static or stack ARs can be laid out statically as part of image by compiler ARs can be pushed / popped at runtime as part of stack We will study an example of each Classic Fortran: stores ARs in static memory C: stores ARs in stack memory

Classic Fortran: ARs in Static Memory ARs for each function is laid out sequentially in memory FUNCTION F1(...)... END FUNCTION F2(...)... END... FUNCTION FN(...) A =...... END......... F1 s AR F2 s AR A... FN s AR

Classic Fortran: ARs in Static Memory ARs for each function is laid out sequentially in memory FUNCTION F1(...)... END FUNCTION F2(...)... END... FUNCTION FN(...) A =...... END Global Base: // A can be translated to addr: // Global Base + offset......... offset F1 s AR F2 s AR A... FN s AR Both Global Base and offset are known to the compiler

Storing ARs in Static Memory is Wasteful Nested and disjoint lifetimes between 2 ARs P1 P2 P1 P2 P1 P1 P1 P2 P2 Nested lifetime P1() { P2; Disjoint lifetime P1(); P2(); What is the minimum memory required to execute above? Nested lifetime: P1 s AR + P2 s AR (P1 s AR must be maintained during call to P2) Disjoint lifetime: Max(P1 s AR, P2 s AR) (P1 s memory is invalid during P2 s call and can be reused) Static management reserves memory for all functions even though many of them have disjoint lifetimes

C: ARs in Stack Memory Allocation strategy Manage ARs like a stack in memory: On function entry: AR instance allocated at top of stack On function return: AR instance removed from top of stack Hardware support Stack pointer ($SP) register $SP stores address of top of the stack Allocation/de-allocation can be done by moving $SP Frame pointer ($FP) register $FP stores base address of current AR Variable addresses translated to an offset from $FP

C: ARs in Stack Memory How does it work? class C { int g() { return 1; int f(int x) { int y; if (x==2) y = 1; else y = x + f(x-1); 2... return y; code of g() code of f() code of main() global data segment heap int main() { f(3); main s AR fp main

C: ARs in Stack Memory How does it work? class C { int g() { return 1; int f(int x) { int y; if (x==2) y = 1; else y = x + f(x-1); 2... return y; code of g() code of f() code of main() global data segment heap int main() { f(3); main s AR fp main

C: ARs in Stack Memory How does it work? class C { int g() { return 1; int f(int x) { int y; if (x==2) y = 1; else y = x + f(x-1); 2... return y; code of g() code of f() code of main() global data segment heap int main() { f(3); y x=3 (result) main s AR fp main

C: ARs in Stack Memory How does it work? class C { int g() { return 1; int f(int x) { int y; if (x==2) y = 1; else y = x + f(x-1); 2... return y; code of g() code of f() code of main() global data segment heap int main() { f(3); y location (1) x=3 (result) main s AR fp main

C: ARs in Stack Memory How does it work? class C { int g() { return 1; int f(int x) { int y; if (x==2) y = 1; else y = x + f(x-1); 2... return y; code of g() code of f() code of main() global data segment heap int main() { f(3); y location (1) fp main fpf (3) x=3 (result) main s AR

C: ARs in Stack Memory How does it work? class C { int g() { return 1; int f(int x) { int y; if (x==2) y = 1; else y = x + f(x-1); 2... return y; int main() { f(3); code of g() code of f() code of main() global data segment heap tmp=x-1 y location (1) fp main fpf (3) x=3 (result) main s AR

C: ARs in Stack Memory How does it work? class C { int g() { return 1; int f(int x) { int y; if (x==2) y = 1; else y = x + f(x-1); 2... return y; int main() { f(3); code of g() code of f() code of main() global data segment heap tmp=x-1 y location (1) fp main fpf (3) x=3 (result) main s AR

C: ARs in Stack Memory How does it work? class C { int g() { return 1; int f(int x) { int y; if (x==2) y = 1; else y = x + f(x-1); 2... return y; int main() { f(3); code of g() code of f() code of main() global data segment heap y location (2) fp f (3) fpf (2) x=2 (result) tmp=x-1 y location (1) fp main x=3 (result) main s AR

C: ARs in Stack Memory Variables translated to addr: $FP + var offset within AR class C { int g() { return 1; int f() { int y; if (x==2) y = 1; else y = x + f(x-1); 2... return y; int main() { f(3); code of g() code of f() code of main() global data segment heap y location (2) fp f (3) fpf (2) x=2 (result) tmp=x-1 y location (1) fp main fpf (3) x=3 (result) main s AR fp main

Stack vs. Static Memory Management for ARs Stack Memory Advantages: Efficient use of memory by allocating ARs only when active Supports recursive function calls (Allows more than one instance of a function in memory) Static Memory Advantages: Can calculate memory usage at compile time (Useful for embedded devices and accelerator devices that have limited memory to store ARs) Vast majority of compilers use stack memory for ARs, and that s what we will assume going forward

Contents of Activation Record (AR) Layout of an AR for a function Temporaries Local variables Saved Caller/Callee Register Values Saved Return Address at Caller Saved Caller s AR Frame Pointer Parameters Return Value

Calling Convention Calling convention: Rules on how caller / callee interact Caller s responsibility Before call Evaluate arguments and save them in callee s AR Save $FP in callee s AR; update to base of new callee s AR Save return address in callee s AR and jump to callee Callee s responsibility On return Restore saved caller s FP into $FP Jump to saved caller return address Includes rules on sharing CPU registers Some registers are saved/restored to/from stack by caller (A subset called caller-saved registers) Some registers are saved/restored to/from stack by callee (A subset called callee-saved registers)

Calling Convention AR layout is also part of calling convention Designed for easy access Parts of callee s AR are accessed by caller (RA, parameters) Place them at bottom of AR where caller can find them easily (If at top, location will differ depending on number of variables and temporaries in callee s AR, something compiler generating caller doesn t necessarily know) Designed for execution speed E.g. first 4 arguments in MIPS typically passed in registers (Register accesses are faster than stack accesses) Who decides on the calling convention? Entirely up to the compiler writer When linking modules generated by different compilers, care must be taken that same conventions are followed (e.g. Java Native Interface allows calls from Java to C)

Machine Code Generation Using Runtime Code to Manage Memory and Registers

Translating IR to Machine Code We use symbol names in 3-address code (IR) e.g. add a, b, c When generating machine code Symbols have to be translated to memory addresses For static data (global variables): Allocated in global data segment Statically known: global_base + offset For stack data (local variables): Using relative addressing from $FP i.e. $FP + offset $FP Generate code to update at function call / return offset statically known (from the symbol table)

Translating IR to Machine Code Now we have built the symbol table, we are finally ready to translate IR (AST in our case) to binary code In your projects, you will only generate very inefficient code Inefficient use of registers and stack memory Inefficient use of instruction types No compiler optimizations to minimize time and space If you had an intermediate low-level IR (e.g. 3-address code), you would have been able to do a much better job :( But we will learn what things are possible next chapter: compiler optimizations

Generating MIPS Assembly Machine code generation is machine ISA dependent In this course, we will use the MIPS architecture RISC (Reduced Instruction Set Computer) machine ALU instruction use registers for operands and results load/store are the only instruction to access memory 32 general purpose registers $0 is always zero, $a0,...,$a4 are for arguments $sp saves stack pointer, $fp saves frame pointer 32 bits per word

Some MIPS Instruction Examples lw R1, offset(r2) sw R1, offset(r2) add R1, R2, R3 addiu R1, R2, imm move R1, R2 li R1, imm # load one word from offset + R2 to R1 # store R1 to offset+r2 # R1 R2 + R3 # R1 R2 + imm, overflow unchecked # R1 R2 # R1 imm

Code Generation Considerations We used to stored values in unlimited temporary variables, but CPU registers are limited must reuse registers Must save / restore registers when reusing them E.g. suppose you store results of expressions in $t0 When generating E E1 + E2, E1 will first store result into $t0 E2 will next store result into $t0, overwriting E1 s result Must save $t0 somewhere before generating E2 Registers are saved on and restored from the stack Note: $sp: stack pointer register, pointing to top of stack Saving a register $t0 on the stack: addiu $sp, $sp, -4 # Allocate (push) a word on the stack sw $t0, 0($sp) # Store $t0 on the top of the stack Restoring a value from stack to register $t0: lw $t0, 0($sp) # Load word from top of stack to $t0 addiu $sp, $sp, 4 # Free (pop) word from stack

Code Generation for Expressions cgen(e1+e2): # stores result in $t0 cgen(e1) # push $t0 on stack addiu $sp, $sp, -4 sw $t0, 0($sp) # stores result in $t0 cgen(e2) # pop value of e1 to $t1 lw $t1, 0($sp) addiu $sp, $sp, 4 # performs addition add $t0, $t1, $t0 cgen(if (e1==e2) then e3 else e4): cgen(e1) # push $t0 on stack addiu $sp, $sp, -4 sw $t0, 0($sp) cgen(e2) # pop value of e1 to $t1 lw $t1, 0($sp) addiu $sp, $sp, 4 # performs comparison beq $t0, $t1, Tpath Fpath: cgen(e4) b End Tpath: cgen(e3) End:...

Code Generation for Function Call cgen(f(e1))= # push arguments cgen(e1) addiu $sp, $sp, -4 sw $t0, 0($sp) # push old FP addiu $sp, $sp, -4 sw $fp, 0($sp) # push return address addiu $sp, $sp, -4 sw $ra, 0($sp) # begin new AR in stack move $fp, $sp j fentry cgen(def(f(...):e)= fentry: cgen(e) # remove AR from stack move $sp, $fp # pop return address lw $ra, 0($sp) addiu $sp, $sp, 4 # pop old FP lw $fp, 0($sp) addiu $sp, $sp, 4 # jump to return address jr $ra

Code Generation for Variables Local variables are referenced from an offset from $fp In example, $fp is pointing to the return address Since the stack pointer changes when intermediate results are saved, local variables do not have fixed offset to $sp new $sp new $fp... Temporaries Local Variables Return Address Old $fp X1 X2... Xn first local variable: -4($fp) argument X1: +8($fp)

Buffer Overflow Attacks (BOAs) BOA is a major type of security threat Code example int foo() { int a[4]; int i=0; while (1) { a[i] = getc(); if (a[i] ==. ) break; i++; void main() { foo(); low address stack grow direction high address a[0] a[1] a[2] a[3] return address f() main() array grow direction

When Return Address is Overwritten What happens when foo() finishes its execution foo:... # pop return address lw $ra, 0($sp) addiu $sp, $sp, 4 # jump to return address jr $ra When provided a malicious input string?...... 00 10 00 00 (20Bytes) (addr of malicious code) Where is the malicious code stored? Attacker can store code in array itself Attacker can use snippets of pre-existing code

How to Defend BOA Attacks? Array bounds checks Software approaches Java: runtime compiler inserts check for every array access C: AddressSanitizer compiler pass to insert checks on memory accesses and malloc/frees (works on LLVM, GCC) Binary: Valgrind dynamic binary instrumentation tool that inserts bounds checks at runtime Hardware-assisted approaches HW: Intel MPX (Memory Protection Extensions) allows SW to set array bounds; CPU checks every array access Canary word checks Push a randomized word on stack before return address Check that canary word hasn t changed before returning BOA must overwrite canary before reaching return address

How to Defend BOA Attacks? (cont d) Control flow integrity checks Compiler does call graph analysis to record legal return targets at the end of each function body Compare return address to legal targets before returning Address Space Layout Randomization (ASLR) Randomize layout of stack, heap, code in program memory space on each execution of program Non-Executable Memory (NX) Mark pages allocated for stack as non-executable (NX) CPU raises exception when trying to execute NX page Prevents attack code from stored in array on stack Many other defending techniques

Translating Parameters Till now, we know how to translate Globals Locals

Translating Parameters Till now, we know how to translate Globals Locals How about parameters and arguments? int func1(int a, int b) {......... z = z + func1(x, y); Parameters a, b the names used when a function is declared Arguments x, y the expressions passed when a function is called

Calling Convention Call by value Semantic: Copy value of argument to parameter Caller copies argument value to parameter location (Stack location or register depending on calling convention) Call by reference Semantic: Parameter references same location as arg Updates to parameters is reflected on argument Argument must be a location, not merely a value Caller copies pointer to argument to parameter Callee accesses argument through pointer Call by name Semantic: Arg evaluated whenever parameter accessed Whenever a parameter is accessed in callee, argument expression evaluated in the environment of caller Arguments are not evaluated immediately on function call Good for efficiency: unused arguments not evaluated Caller copies address of evaluation code to parameter

Call by Value CS2210: Compiler Construction int a = 10; int b = 6; int f(int x, int y) { x = a + 5; a = a + 10; y = x + 7; void main() { f(a,b); printf( a=%d,b=%d,a,b); a b x y =10 =6

Call by Value CS2210: Compiler Construction int a = 10; int b = 6; int f(int x, int y) { x = a + 5; a = a + 10; y = x + 7; void main() { f(a,b); printf( a=%d,b=%d,a,b); a b x y =10 =6 =10 =6

Call by Value CS2210: Compiler Construction int a = 10; int b = 6; int f(int x, int y) { x = a + 5; a = a + 10; y = x + 7; void main() { f(a,b); printf( a=%d,b=%d,a,b); a b x y =10 =6 =15 =6

Call by Value CS2210: Compiler Construction int a = 10; int b = 6; int f(int x, int y) { x = a + 5; a = a + 10; y = x + 7; void main() { f(a,b); printf( a=%d,b=%d,a,b); a b x y =20 =6 =15 =6

Call by Value CS2210: Compiler Construction int a = 10; int b = 6; int f(int x, int y) { x = a + 5; a = a + 10; y = x + 7; void main() { f(a,b); printf( a=%d,b=%d,a,b); a b x y =20 =6 =15 =22

Call by Reference CS2210: Compiler Construction int a = 10; int b = 6; int f(int &x, int &y) { x = a + 5; a = a + 10; y = x + 7; void main() { f(a,b); printf( a=%d,b=%d,a,b); a b x y =10 =6

Call by Reference CS2210: Compiler Construction int a = 10; int b = 6; int f(int &x, int &y) { x = a + 5; a = a + 10; y = x + 7; void main() { f(a,b); printf( a=%d,b=%d,a,b); a b x y =15 =6

Call by Reference CS2210: Compiler Construction int a = 10; int b = 6; int f(int &x, int &y) { x = a + 5; a = a + 10; y = x + 7; void main() { f(a,b); printf( a=%d,b=%d,a,b); a b x y =25 =6

Call by Reference CS2210: Compiler Construction int a = 10; int b = 6; int f(int &x, int &y) { x = a + 5; a = a + 10; y = x + 7; void main() { f(a,b); printf( a=%d,b=%d,a,b); a b x y =25 =32

Call by Name CS2210: Compiler Construction Rule Goal: lazy evaluation of arguments Premise 1: arguments are often complex expressions Premise 2: arguments often go unused inside callee Only evaluate arguments when they are used (in the environment of caller) Implementation: Code called thunk generated inside caller for evaluation Pass address of thunk to callee Callee jumps to thunk whenever parameter value is needed How to recreate environment of caller: On thunk entry, update $FP to that of caller On thunk exit, update $FP to that of callee Used in functional languages such as Haskell Expressions computed only when needed for I/O output

Call by Name: Thunk Evaluation int f(int x, int y) { int b=3; if (x>0) x = y; void main() { int a=1; int b=1; f(a, b*(b-1)*(b-2)); $SP $FP b=3 x=thunk y=thunk Evaluate b*(b-1)*(b-2) using this b? f() a=1 b=1 main()

Call by Name: Thunk Evaluation int f(int x, int y) { int b=3; if (x>0) x = y; void main() { int a=1; int b=1; f(a, b*(b-1)*(b-2)); $SP $FP b=3 x=thunk y=thunk Evaluate b*(b-1)*(b-2) using this b? f() a=1 b=1 main()

Call by Name: Thunk Evaluation int f(int x, int y) { int b=3; if (x>0) x = y; void main() { int a=1; int b=1; f(a, b*(b-1)*(b-2)); $SP temporaries for b(b-1)(b-2) b=3 x=thunk y=thunk restored main() env. f() a=1 b=1 main() No, evaluate using this b! $FP

Runtime Management of Classes and Objects Objects are like structures in C Objects are laid out in contiguous memory Each member variable is stored at a fixed offset in object Unlike structures, objects have member methods Two types of member methods: Nonvirtual member methods: cannot be overridden Parent obj = new Child(); obj.nonvirtual(); // Parent::nonvirtual() called Method called depends on (static) reference type Compiler can decide call targets statically Virtual member methods: can be overridden by child class Parent obj = new Child(); obj.virtual(); // Child::virtual() called Method called depends on (runtime) type of object Need to call different targets depending on runtime type

Static Dispatch and Dynamic Dispatch Dispatch: to send to a particular place for a purpose In CS: to jump to a (particular) function Static Dispatch: selects call target at compile time Nonvirtual methods implemented using static dispatch Implication for code generation: Can hard code function address into binary Dynamic Dispatch: selects call target at runtime Virtual methods implemented using dynamic dispatch Implication for code generation: Must generate code to select correct call target How? At compile time, generate a dispatch table for each class, containing call targets for all virtual methods for that class At runtime, each object maintains a pointer to the dispatch table for its runtime type (class)

Object Layout CS2210: Compiler Construction class tag dispatch ptr attribute 1 attribute 2... attribute n Class tag is an integer to identify this class for purposes of dynamic type checking Dispatch ptr is a pointer to the dispatch table Member variables are allocated in subsequent slots

Inheritance and Subclasses Invariant the offset of a member variable or member method is the same in a class and all of its subclasses For example, if A3 A2 A1 A1 s method table f1() f2() class tag dispatch ptr A1 s attr + A2 s attr + A3 s attr A1 object A2 object A3 object A2 s method table f1() f2 () f3() A3 s method table f1() f2 () f3 ()

Inheritance and Subclasses Member variable access Generate code using offset for reference type (class) Object may be of child type, but will still have same offset Member method call Generate code to load call target from dispatch table using offset for reference type Again, object may be of child type, but still same offset No inheritance in our project No dynamic dispatching Statically bind a function call to its address

Heap Memory Management Garbage Collection

Heap Management Heap data Lives beyond the lifetime of the procedure that creates it TreeNode* createtree() { { TreeNode* p = (TreeNode*)malloc(sizeof(TreeNode)); return p; Cannot reclaim memory automatically using a stack Problem: when and how do we reclaim that memory? Two approaches Manual memory management Programmer inserts deallocation calls. E.g. free(p) Automatic memory management Runtime code automatically reclaims memory when it determines that data is no longer needed

Why Manual Memory Management? Manual memory management is typically more efficient Programmers know when data is no longer needed With automatic management, runtime must somehow detect when data is no longer needed and recycle it Performance overhead / poor response time: Program must be paused during detection phase Program will be unresponsive during that time Memory overhead: Detection can be done every so often (Typically only when program runs out of memory) Runtime may keep around data longer than necessary Results in larger memory footprint

Why Automatic Memory Management? Leads to fewer bugs With manual management, programmers may forget to free unused memory memory leak free memory too early dangling pointer access free memory twice (double free) Symptoms: slowly increasing memory footprint, nondeterministic data corruption or crashes... Memory bugs are extremely hard to find and fix Plenty of research on memory bug detection tools... AddressSanitizer, Valgrind, Intel MPX... But tools have limitations Too much overhead to be used during production runs Most runtime detection tools have limited coverage

Why Automatic Memory Management? Leads to a more secure system Secure languages run on virtual machines (VMs) (E.g. Java, JavaScript, PHP...) Virtual machine: a secure runtime environment sandboxed from underlying environment, implemented by runtime code E.g. JavaScript app running on web browser is sandboxed from rest of device (can t manipulate your camera) Must guarantee security against even malicious code Control of memory management essential for security Or, app can manipulate memory used by sandboxing code Nullifies all security guarantees of sandboxing Runtime must keep track of where each piece of data is (and what it is used for) Impossible to do with manual memory management

Implementation: Automatic and Manual Common functionality in both automatic and manual Runtime code maintains used/unused spaces in heap (e.g. linked together in the form of a list) On allocation: memory of given size found in unused space and moved to used space On free: given memory moved from used to unused space Only in automatic memory management Compiler inserted calls to memory management routines Memory management library Routines to perform detection of unused memory Routines to perform freeing of unused memory We will focus on automatic memory management

The Difficulty: Detection How to determine an object will no longer be used In general, impossible to tell exactly After all, how can you predict the future? Requires knowledge of program beyond what compiler has But can tell when it can no longer be used Axiom: an object can only be used if it can be referenced Corollary 1: if an object cannot be referenced, the object can no longer be used Object obj = new Object();... obj = NULL; // above object can no longer be used Heap allocated objects are called nameless objects Rest (including references) are called named objects Corollary 2: nameless objects are unusable unless reachable from a named object

Reachable Objects and Garbage Named objects can be global variables in global data segment local variables in stack memory registers An object x is reachable iff A named object contains a reference to x, or A reachable object y contains a reference to x An unreachable object is referred to as garbage Garbage can no longer be used and can be reclaimed This reclamation is process is called garbage collection

Two Garbage Collection Schemes Reference Counting Maintains a counter inside each object that counts the number of references to object When counter becomes 0, the object is no longer reachable Immediately reclaim the unreachable object as garbage Tracing When the heap runs out of memory to allocate: 1. Stops the program and traces through all reachable objects starting from the named objects 2. Remaining objects are garbage and are reclaimed 3. Restarts the program

Scheme 1: Reference Counting Intuition: if an object is not referenced, it is unreachable If an object is referenced by only unreachable objects, that object is also unreachable Implementation: each object has a counter for the number of references pointing to that object Counter referred to as reference counter Compiler inserts code to update reference counters, whenever program modifies references

Implementation Details Initialization x = new Object(): x new Object(); rc(new object) 1; Reference assignment x = y. Assume: x references object X, and y references object Y x y; rc(y) rc(y) + 1; // rc(y): Y s reference counter rc(x) rc(x) - 1; if (rc(x)==0) then mark X as garbage to reclaim; for each object O referenced by X rc(o) rc(o) - 1; // X s reference no longer counts if (rc(o)==0) then mark O as garbage to reclaim; recursively repeat for O;

Reference Counting Example int a int *p 2... Heap int *q F1 s AR 2... main s AR nil free list

Reference Counting Example int a int *p 2... Heap int *q F1 s AR 2... main s AR nil free list

Reference Counting Example int a int *p 2... Heap int *q F1 s AR 3... main s AR 0... nil free list

Reference Counting Example int a int *p 2... Heap int *q F1 s AR 2... main s AR nil free list

Problem of Reference Counting RC cannot handle circular data structures int a int *p 2... Heap int *q F1 s AR 2... main s AR nil free list

Problem of Reference Counting RC cannot handle circular data structures int a int *p 2... Heap int *q F1 s AR 2... main s AR nil free list

Problem of Reference Counting RC cannot handle circular data structures int a int *p 2... Heap int *q F1 s AR 2... main s AR nil free list

Problem of Reference Counting RC cannot handle circular data structures int a int *p 2... Heap int *q F1 s AR main s AR 2... Garbage but cannot reclaimed nil free list

Discussion of Reference Counting Scheme Advantages: Relatively easy to implement Compiler only needs to insert RC manipulation code at reference assignments Good response time Garbage is collected whenever there is opportunity No need to pause program for long time responsive (Unless freeing a long chain of objects!)

Discussion of Reference Counting Scheme Advantages: Relatively easy to implement Compiler only needs to insert RC manipulation code at reference assignments Good response time Garbage is collected whenever there is opportunity No need to pause program for long time responsive (Unless freeing a long chain of objects!) Disadvantages: Cannot collect circular data structures (Must rely on tracing GC for these corner cases) Bad performance Manipulating RCs at each assignment is high overhead RC must be synchronized with multithreading even slower

Scheme 2: Tracing Start from named objects (also called root objects) If object is value: no further action If object is reference: follow reference If object is struct: go through each field Example: F1 s local variable references struct in heap int a int *p struct x *q F1 s AR main s AR

Scheme 2: Tracing Start from named objects (also called root objects) If object is value: no further action If object is reference: follow reference If object is struct: go through each field Example: F1 s local variable references struct in heap int a int *p struct x *q F1 s AR main s AR

Scheme 2: Tracing Start from named objects (also called root objects) If object is value: no further action If object is reference: follow reference If object is struct: go through each field Example: F1 s local variable references struct in heap int a int *p struct x *q F1 s AR main s AR

Scheme 2: Tracing Start from named objects (also called root objects) If object is value: no further action If object is reference: follow reference If object is struct: go through each field Example: F1 s local variable references struct in heap int a int *p struct x *q F1 s AR main s AR

Discussion of Tracing Scheme Advantages: Is guaranteed to collect even cyclic references Good performance Overhead proportional to traced (live) objects Garbage (dead) objects do not incur any overhead! Most objects have short lifetimes Dead by the time tracing GC runs

Discussion of Tracing Scheme Advantages: Is guaranteed to collect even cyclic references Good performance Overhead proportional to traced (live) objects Garbage (dead) objects do not incur any overhead! Most objects have short lifetimes Dead by the time tracing GC runs Disadvantages: Bad response time: Requires pausing program Prone to heap thrashing Thrashing: frequent GCs to collect small amounts of garbage If heap does not have extra headroom beyond working set GC becomes very frequent Most objects are now live (bad performance)

Flavors of Tracing Collection To move or not to move objects? Garbage collection can leave holes inside heap Objects can be moved during GC to "compress" holes Mark-and-Sweep: example of non-moving GC Semispace: example of moving GC To collect at once or incrementally? Tracing entire heap can lead to long pause times Possible to collect only a part of the heap at a time All-at-once: naive GC with no partitions Incremental: divides heap into multiple partitions Generational: divides heap into generations The two choices are orthogonal to each other

Flavor 1: Mark-and-Sweep When it is about to run out of memory, GC stalls program execution and executes two phases Mark phase: traces through all reachable objects starting from root objects Sweep phase: reclaims unreachable objects Implementation Each object has an extra mark bit Set mark bit to 0 on object allocation Mark phase: Do a depth-first traversal starting from root objects Set mark bit to 1 for all reachable objects while tracing Sweep phase: Scan entire heap from beginning to end Reclaim all objects with mark bit 0 (unreachable objects) Reset mark bits to 0 for all remaining objects

Implementation Details mark() { todo = { all root objects ; while (todo!= NULL) { v one object in todo todo = todo - v; if (mark(v) == 0) { mark(v) = 1; extract all pointers pv1, pv2,..., pvn from v; todo = todo {pv1, pv2,..., pvn sweep() { p bottom(heap); while (p!=top(heap)) { if (mark(p)==1) mark(p) 0; else add p with sizeof(p) to freelist; p p + sizeof(p);

Mark-and-Sweep Example int a int *p 0... 0... 0... 0... Heap int *q 0... F1 s AR main s AR 0... nil 0... nil free list

Mark-and-Sweep Example int a int *p 0... Heap int *q 0... F1 s AR main s AR nil 0... nil free list

Mark-and-Sweep Example int a int *p int *q F1 s AR nil 0... 0... Heap main s AR nil free list

Mark-and-Sweep Example int a int *p int *q F1 s AR 0... 0... 0... nil 0... 0... 0... Heap main s AR nil free list

Discussion of Mark-and-Sweep Collection Advantages: No need to move objects simpler implementation GC time is shorter since objects stay in place

Discussion of Mark-and-Sweep Collection Advantages: No need to move objects simpler implementation GC time is shorter since objects stay in place Disadvantages (Due to Fragmentation): Slow Allocation: free space fragmented across heap Must search through free list to find right size Search process can become costly with long lists Loss of Locality: objects allocated together spatially apart Objects allocated together temporally may be spatially apart Objects allocated together tend to be accessed together Loss of locality can cause CPU cache misses, page faults

Flavor 2: Semispace Goal: Eliminate fragmentation Only use half of the heap space at one time During GC, move live objects to the other half Original half: called From Space Copied half: called To Space Once done, From Space becomes contiguous free space Problem: After moving objects, what happens to pointers? Reference variables will still be pointing to old locations After moving, all references will become dangling pointers Solution: Use forwarding pointers Definition: field in object pointing to moved location On object copy: Leave forwarding pointer in old copy pointing to new addr Update member variables of reference type to new copied locations if pointed object has a forwarding pointer

Flavor 2: Semispace Example int a int *p int *q F1 s AR nil 0... From Space 0... 0... To Space main s AR free list

Flavor 2: Semispace Example 1. Copy object q to To Space and leave forwarding pointer int a int *p int *q F1 s AR main s AR nil nil 0... From Space 0... 0... To Space free list

Flavor 2: Semispace Example 1. Copy object q to To Space and leave forwarding pointer int a int *p int *q F1 s AR main s AR nil nil 0... From Space 0... 0... To Space free list

Flavor 2: Semispace Example 1. Copy object q to To Space and leave forwarding pointer 2. Copy object p to To Space and leave forwarding pointer int a int *p int *q nil 0... 0... 0... From Space F1 s AR main s AR nil To Space free list

Flavor 2: Semispace Example 1. Copy object q to To Space and leave forwarding pointer 2. Copy object p to To Space and leave forwarding pointer int a int *p int *q nil 0... 0... 0... From Space F1 s AR main s AR nil To Space free list

Flavor 2: Semispace Example 1. Copy object q to To Space and leave forwarding pointer 2. Copy object p to To Space and leave forwarding pointer 3. Trace field in p to copy pointed to object, and update field int a int *p int *q nil 0... 0... 0... From Space F1 s AR main s AR nil To Space free list

Flavor 2: Semispace Example 1. Copy object q to To Space and leave forwarding pointer 2. Copy object p to To Space and leave forwarding pointer 3. Trace field in p to copy pointed to object, and update field 4. Trace field in new object through forwarding pointer, and update field int a int *p int *q nil 0... 0... 0... From Space F1 s AR main s AR nil To Space free list

Flavor 2: Semispace Example 1. Copy object q to To Space and leave forwarding pointer 2. Copy object p to To Space and leave forwarding pointer 3. Trace field in p to copy pointed to object, and update field 4. Trace field in new object through forwarding pointer, and update field int a int *p From Space int *q F1 s AR main s AR nil To Space free list

Discussion of Semispace Collection Advantages: No fragmentation fast allocation No free list required for allocation; only an alloc pointer within free space that gets moved along with each alloc Better locality faster program execution Objects are compacted on the heap (no holes) Objects are laid out contiguously in the order of allocation (Objects allocated together land on same cache line/page)

Discussion of Semispace Collection Advantages: No fragmentation fast allocation No free list required for allocation; only an alloc pointer within free space that gets moved along with each alloc Better locality faster program execution Objects are compacted on the heap (no holes) Objects are laid out contiguously in the order of allocation (Objects allocated together land on same cache line/page) Disadvantages: Only half of heap space usable more frequent GC Objects need to be copied longer GC

Flavors of Tracing Collection To move or not to move objects? " Garbage collection can leave holes inside heap Objects can be moved during GC to "compress" holes Mark-and-Sweep: example of non-moving GC Semispace: example of moving GC To collect at once or incrementally? Tracing entire heap can lead to long pause times Possible to collect only a part of the heap at a time All-at-once: naive GC with no partitions Incremental: divides heap into multiple partitions Generational: divides heap into generations The two choices are orthogonal to each other

Flavor 3: Incremental Goal: Eliminate long pause times Divide heap into partitions and collect one at a time Need to pause only during the collection of a partition Problem: What to do about cross-partition pointers? While tracing, pointer may cross a partition boundary Path to live objects in this partition may cross arbitrary number of partitions before returning to this partition How to collect one partition without tracing entire heap? Solution: Use write barriers Definition: code that keeps track of cross-partition pointers On each reference update in program: Detect if pointer crosses partitions by comparing source/target addresses to partition ranges If so, add pointer to root set of target partition Conservatively assumes cross-partition pointers are live

Flavor 3: Incremental Example int a int *p nil From space#1 0... From space#2 int *q F1 s AR 0... 0... 0... To space#1 To space#2 main s AR free list

Flavor 3: Incremental Example 1. Write barrier adds cross-partition pointers to root sets during execution int a int *p nil From space#1 writer barrier From space#2 int *q F1 s AR 0... 0... To space#1 To space#2 main s AR free list

Flavor 3: Incremental Example 1. Write barrier adds cross-partition pointers to root sets during execution 2. When collecting partition 1, pointers from partition 2 are also traced int a int *p nil From space#1 writer barrier From space#2 int *q F1 s AR main s AR 0... 0... To space#1 nil To space#2 free list

Flavor 3: Incremental Example 1. Write barrier adds cross-partition pointers to root sets during execution 2. When collecting partition 1, pointers from partition 2 are also traced 3. Garbage in partition 1 not collected due to conservatism of write barrier (also garbage in partition 2 will not be collected later on) From space#1 From space#2 int a int *p int *q F1 s AR main s AR To space#1 nil writer barrier 0... To space#2 free list

Discussion of Incremental Collection Advantages: Good response time: due to smaller partitions Disadvantages: Overhead of write barriers: must check for cross-partition pointers on every pointer modification Conservatism of write barriers Cross-partition pointers could be from garbage objects If cycles cross partitions will never be collected (Need periodic full heap GC to remove these)

Flavor 4: Generational Goal: Make garbage collection efficient Incremental GC reduces pause times but not total GC time Improve on incremental GC to also shorten total GC time Insight: GC time is proportional to number of live objects Since only live objects are traversed during tracing Since only live objects are copied (for Semispace GC) Not proportional to amount of garbage, nor size of heap! GC is efficient when only a small percentage of heap is live Problem: Minimize percentage of live heap objects Wait as long as you can before GC to allow objects to die But cannot wait forever since GC is forced when heap is full Generational Hypothesis (empirical observation) 90% of objects die very young (locals, temporaries) 10% of objects live for a long time (global data structure)

Flavor 4: Generational Solution Divide heap into two partitions: young / old generations Young generation: contains short-lived objects Old generation: contains long-lived objects Young gen GC is frequent (90% are short-lived objects) Old gen GC is infrequent (only 10% are long-lived objects) Most GC happens in young gen where GC is efficient (In young gen GC most objects are dead, per hypothesis) Stages in an object s life 1. Object initially allocated to young generation 2. If object dies young (likely), collected in next young gen GC 3. If object survives several young gen GCs, move to old gen 4. Object eventually dies and is collected by next old gen GC

Flavor 4: Generational Example

Discussion of Generational Collection Advantages: Better response time: most GCs are young gen GCs Better efficiency: again, most GCs are young gen GCs Disadvantages: Same problem with write barriers as incremental Infrequently, old generation needs collection With young generation to collect cross-partition cycles Will lead to long pause times Techniques exist to shorten: parallel GC, concurrent GC (But beyond the scope of this lecture)

Comparison of Different GC algorithms Based on the following publication S. M. Blackburn, P. Cheng, and K. S. McKinley, "Myths and Realities: The Performance Impact of Garbage Collection," in SIGMETRICS International Conference on Measurement and Modeling of Computer Systems, 2004 Studied 6 configurations on Java benchmarks: RefCount: reference counting Semispace: moves objects from from to to space MarkSweep: adds objects to free list without moving GenRC: young = Semispace, old = Reference Counting GenCopy: young = Semispace, old = Semispace GenMS: young = Semispace, old = MarkSweep Young: always semispace to speed up program execution Guarantees contiguous free space speeds up alloc Guarantees contiguous object alloc improves locality

Comparison of Different GC algorithms (I) Total GC time, normalized to fastest time: All Heap thrashing observed at close to minumum heap sizes All Generational versions faster due to generation hypothesis All GenMS faster than GenCopy due to less object copying All RefCount slow due to constant RC updates (but better response times not shown here)

Comparison of Different GC algorithms (II) Mutator (program execution) time, normalized to fastest time: All Semispace faster than MarkSweep due to fast alloc/locality All Generational typically captures most benefits of Semispace (g) But generational sometimes slow due to write barriers (h) But GenMS sometimes slower than GenCopy due to worse locality if heap access often go to fragmented old gen

Comparison of Different GC algorithms (III) Total time (GC + Mutator), normalized to fastest time All Small heap MarkSweep faster due to faster GC All Large heap Semispace faster due to faster Mutator (a),(c) Typically GenMS is used for best of both worlds (b) Sometimes GenCopy is better if frequent old gen accesses (compacted old gen provides better locality)