Code Generation Introduction. Compiler (scalac, gcc) Compiler (scalac, gcc) machine code (e.g. x86, arm, JVM) efficient to execute i=0 while (i < 10)

Slides:



Advertisements
Similar presentations
Chapter 16 Java Virtual Machine. To compile a java program in Simple.java, enter javac Simple.java javac outputs Simple.class, a file that contains bytecode.
Advertisements

Virtual Machines Matthew Dwyer 324E Nichols Hall
Semantics Static semantics Dynamic semantics attribute grammars
1 Lecture 10 Intermediate Representations. 2 front end »produces an intermediate representation (IR) for the program. optimizer »transforms the code in.
Intermediate Code Generation
Stacks & Their Applications COP Stacks  A stack is a data structure that stores information arranged like a stack.  We have seen stacks before.
1 Compiler Construction Intermediate Code Generation.
Compilation 2007 Code Generation Michael I. Schwartzbach BRICS, University of Aarhus.
1 1 Lecture 14 Java Virtual Machine Instructors: Fu-Chiung Cheng ( 鄭福炯 ) Associate Professor Computer Science & Engineering Tatung Institute of Technology.
Lab 9 Java Bytecode & The Jasmin Assembler
1 Chapter 7: Expressions Expressions are the fundamental means of specifying computations in a programming language To understand expression evaluation,
CS 536 Spring Code generation I Lecture 20.
JVM-1 Introduction to Java Virtual Machine. JVM-2 Outline Java Language, Java Virtual Machine and Java Platform Organization of Java Virtual Machine Garbage.
Compiler Construction Recap Rina Zviel-Girshin and Ohad Shacham School of Computer Science Tel-Aviv University.
Chapter 16 Java Virtual Machine. To compile a java program in Simple.java, enter javac Simple.java javac outputs Simple.class, a file that contains bytecode.
4/6/08Prof. Hilfinger CS164 Lecture 291 Code Generation Lecture 29 (based on slides by R. Bodik)
Compiler design Computer Science Rensselaer Polytechnic Lecture 1.
The Stack and Queue Types Lecture 10 Hartmut Kaiser
Code Generation Introduction. Compiler (scalac, gcc) Compiler (scalac, gcc) machine code (e.g. x86, arm, JVM) efficient to execute i=0 while (i < 10)
David Evans CS201j: Engineering Software University of Virginia Computer Science Lecture 18: 0xCAFEBABE (Java Byte Codes)
Compiler Construction Lecture 17 Mapping Variables to Memory.
2.2 A Simple Syntax-Directed Translator Syntax-Directed Translation 2.4 Parsing 2.5 A Translator for Simple Expressions 2.6 Lexical Analysis.
Syntax & Semantic Introduction Organization of Language Description Abstract Syntax Formal Syntax The Way of Writing Grammars Formal Semantic.
1 CSC 222: Computer Programming II Spring 2005 Stacks and recursion  stack ADT  push, pop, peek, empty, size  ArrayList-based implementation, java.util.Stack.
1 October 1, October 1, 2015October 1, 2015October 1, 2015 Azusa, CA Sheldon X. Liang Ph. D. Computer Science at Azusa Pacific University Azusa.
Java Bytecode What is a.class file anyway? Dan Fleck George Mason University Fall 2007.
Java Programming Introduction & Concepts. Introduction to Java Developed at Sun Microsystems by James Gosling in 1991 Object Oriented Free Compiled and.
1 Introduction to JVM Based on material produced by Bill Venners.
Chapter 8 Intermediate Code Zhang Jing, Wang HaiLing College of Computer Science & Technology Harbin Engineering University.
Syntax Directed Translation Compiler Design Lecture (03/16//98) Computer Science Rensselaer Polytechnic.
Code Generation Compiler Baojian Hua
Compiler Chapter# 5 Intermediate code generation.
Interpretation Environments and Evaluation. CS 354 Spring Translation Stages Lexical analysis (scanning) Parsing –Recognizing –Building parse tree.
Unit-1 Introduction Prepared by: Prof. Harish I Rathod
Prof. Fateman CS 164 Lecture 281 Virtual Machine Structure Lecture 28.
Introduction CPSC 388 Ellen Walker Hiram College.
Programming Languages
More on MIPS programs n SPIM does not support everything supported by a general MIPS assembler. For example, –.end doesn’t work Use j $ra –.macro doesn’t.
1 A Simple Syntax-Directed Translator CS308 Compiler Theory.
CHP-3 STACKS.
More Examples of Abstract Interpretation Register Allocation.
Code Generation CPSC 388 Ellen Walker Hiram College.
Chap. 10, Intermediate Representations J. H. Wang Dec. 27, 2011.
CSCE 314 Programming Languages
Review on Program Challenge CSc3210 Yuan Long.
Recap: Printing Trees into Bytecodes To evaluate e 1 *e 2 interpreter –evaluates e 1 –evaluates e 2 –combines the result using * Compiler for e 1 *e 2.
RealTimeSystems Lab Jong-Koo, Lim
CS 536 © CS 536 Spring Introduction to Programming Languages and Compilers Charles N. Fischer Lecture 15.
The Java Virtual Machine (JVM)
A Simple Syntax-Directed Translator
Constructing Precedence Table
Compilers Principles, Techniques, & Tools Taught by Jing Zhang
CS216: Program and Data Representation
ASTs, Grammars, Parsing, Tree traversals
Computer Architecture and Organization Miles Murdocca and Vincent Heuring Chapter 4 – The Instruction Set Architecture.
Code Generation: Introduction
Java Byte Codes (0xCAFEBABE) cs205: engineering software
CSE401 Introduction to Compiler Construction
Lecture 30 (based on slides by R. Bodik)
Lecture 19: 0xCAFEBABE (Java Byte Codes) CS201j: Engineering Software
Byte Code Verification
ASTs, Grammars, Parsing, Tree traversals
Compiler Design 21. Intermediate Code Generation
CS 153: Concepts of Compiler Design November 6 Class Meeting
A Closer Look at Instruction Set Architectures Chapter 5
Course Overview PART I: overview material PART II: inside a compiler
Compiler Design 21. Intermediate Code Generation
A Tour of Language Implementation
CMPE 152: Compiler Design April 16 Class Meeting
CSc 453 Interpreters & Interpretation
Presentation transcript:

Code Generation Introduction

Compiler (scalac, gcc) Compiler (scalac, gcc) machine code (e.g. x86, arm, JVM) efficient to execute i=0 while (i < 10) { a[i] = 7*i+3 i = i + 1 } i=0 while (i < 10) { a[i] = 7*i+3 i = i + 1 } source code (e.g. Scala, Java,C) easy to write mov R1,#0 mov R2,#40 mov R3,#3 jmp +12 mov (a+R1),R3 add R1, R1, #4 add R3, R3, #7 cmp R1, R2 blt -16 mov R1,#0 mov R2,#40 mov R3,#3 jmp +12 mov (a+R1),R3 add R1, R1, #4 add R3, R3, #7 cmp R1, R2 blt -16 i = 0 LF w h i l e i = 0 LF w h i l e i = 0 while ( i < 10 ) i = 0 while ( i < 10 ) lexer characters words trees data-flow graphs parser assign while i 0 + * 3 7i assign a[i] < i 10 code gen optimizer type check idea

Example: gcc #include int main() { int i = 0; int j = 0; while (i < 10) { printf("%d\n", j); i = i + 1; j = j + 2*i+1; } jmp.L2.L3: movl -8(%ebp), %eax movl %eax, 4(%esp) movl $.LC0, (%esp) call printf addl $1, -12(%ebp) movl -12(%ebp), %eax addl %eax, %eax addl -8(%ebp), %eax addl $1, %eax movl %eax, -8(%ebp).L2: cmpl $9, -12(%ebp) jle.L3 gcc test.c -S

LLVM: Another Interesting C Compiler

Your Compiler Your Compiler Java Virtual Machine (JVM) Bytecode i=0 while (i < 10) { a[i] = 7*i+3 i = i + 1 } i=0 while (i < 10) { a[i] = 7*i+3 i = i + 1 } source code simplified Java-like language 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 Your Project i = 0 LF w h i l e i = 0 LF w h i l e i = 0 while ( i < 10 ) i = 0 while ( i < 10 ) lexer characters words trees parser assign while i 0 + * 3 7i assign a[i] < i 10 code gen type check

javac example while (i < 10) { System.out.println(j); i = i + 1; j = j + 2*i+1; } 4: iload_1 5: bipush 10 7: if_icmpge 32 10: getstatic #2; //System.out 13: iload_2 14: invokevirtual #3; //println 17: iload_1 18: iconst_1 19: iadd 20: istore_1 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4 32: return javac Test.java javap –c Test Phase after type checking: emits such bytecode instructions Guess what each JVM instruction for the highlighted expression does.

Stack Machine: High-Level Machine Code... 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4... program counter instruction sequence: local variable memory (slots): top of stack stack Let us step through

Operands are consumed from stack and put back onto stack... 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4... instruction sequence: memory: top of stack stack 8

Operands are consumed from stack and put back onto stack... 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4... instruction sequence: memory: top of stack stack 8 2

Operands are consumed from stack and put back onto stack... 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4... instruction sequence: memory: top of stack stack 8 2 3

Operands are consumed from stack and put back onto stack... 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4... instruction sequence: memory: top of stack stack 8 6

Operands are consumed from stack and put back onto stack... 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4... instruction sequence: memory: top of stack stack 14

Operands are consumed from stack and put back onto stack... 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4... instruction sequence: memory: top of stack stack 14 1

Operands are consumed from stack and put back onto stack... 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4... instruction sequence: memory: top of stack stack 15

Operands are consumed from stack and put back onto stack... 21: iload_2 22: iconst_2 23: iload_1 24: imul 25: iadd 26: iconst_1 27: iadd 28: istore_2 29: goto 4... instruction sequence: memory: top of stack stack

Instructions in JVM Separate for each type, including –integer types (iadd, imul, iload, istore, bipush) –reference types (aload, astore) Why are they separate? –Memory safety! –Each reference points to a valid allocated object Conditionals and jumps Further high-level operations –array operations –object method and field access

Stack Machine Simulator var code : Array[Instruction] var pc : Int // program counter var local : Array[Int] // for local variables var operand : Array[Int] // operand stack var top : Int while (true) step def step = code(pc) match { case Iadd() => operand(top - 1) = operand(top - 1) + operand(top) top = top - 1 // two consumed, one produced case Imul() => operand(top - 1) = operand(top - 1) * operand(top) top = top - 1 // two consumed, one produced top stack 8 6

Stack Machine Simulator: Moving Data case Bipush(c) => operand(top + 1) = c // put given constant 'c' onto stack top = top + 1 case Iload(n) => operand(top + 1) = local(n) // from memory onto stack top = top + 1 case Istore(n) => local(n) = operand(top) // from stack into memory top = top - 1 // consumed } if (notJump(code(n))) pc = pc + 1 // by default go to next instructions

Actual Java Virtual Machine JVM Instruction DescriptionJVM Instruction Description from JavaTech bookJavaTech Official documentation: html/index.html Use: javac -g *.java to compile javap -c -l ClassName to explore

Selected Instructions bipush X Like iconst, but for arbitrarily large X

Example: Twice class Expr1 { public static int twice(int x) { return x*2; } } javac -g Expr1.java; javap -c -l Expr1 public static int twice(int); Code: 0: iload_0 // load int from var 0 to top of stack 1: iconst_2 // push 2 on top of stack 2: imul // replace two topmost elements with their product 3: ireturn // return top of stack }

Example: Area class Expr2 { public static int cubeArea(int a, int b, int c) { return (a*b + b*c + a*c) * 2; } javac -g Expr2.java; javap -c -l Expr2 public static int cubeArea(int, int, int); Code: 0: iload_0 1: iload_1 2: imul 3: iload_1 4: iload_2 5: imul 6: iadd 7: iload_0 8: iload_2 9: imul 10: iadd 11: iconst_2 12: imul 13: ireturn } LocalVariableTable: Start Length Slot Name Signature a I b I c I

What Instructions Operate on operands that are part of instruction itself, following their op code (unique number for instruction - iconst) operand stack - used for computation (iadd) memory managed by the garbage collector (loading and storing fields) constant pool - used to store ‘big’ values instead of in instruction stream –e.g. string constants, method and field names –mess!

CAFEBABE Library to make bytecode generation easy and fun! Named after magic code appearing in.class files when displayed in hexadecimal: More on that in the labs!

Towards Compiling Expressions: Prefix, Infix, and Postfix Notation

Overview of Prefix, Infix, Postfix Let f be a binary operation, e 1 e 2 two expressions We can denote application f(e 1,e 2 ) as follows –in prefix notationf e 1 e 2 –in infix notatione 1 f e 2 –in postfix notatione 1 e 2 f Suppose that each operator (like f) has a known number of arguments. For nested expressions –infix requires parentheses in general –prefix and postfix do not require any parantheses!

Expressions in Different Notation For infix, assume * binds stronger than + There is no need for priorities or parens in the other notations arg.list +(x,y) +(*(x,y),z)+(x,*(y,z))*(x,+(y,z)) prefix + x y + * x y z+ x * y z* x + y z infix x + y x*y + zx + y*zx*(y + z) postfix x y + x y * z +x y z * +x y z + * Infix is the only problematic notation and leads to ambiguity Why is it used in math? Amgiuity reminds us of algebraic laws: x + y looks same from left and from right (commutative) x + y + z parse trees mathematically equivalent (associative)

Convert into Prefix and Postfix prefix infix ( ( x + y ) + z ) + u x + (y + (z + u )) postfix draw the trees: Terminology: prefix = Polish notation (attributed to Jan Lukasiewicz from Poland) postfix = Reverse Polish notation (RPN) Is the sequence of characters in postfix opposite to one in prefix if we have binary operations? What if we have only unary operations?

Compare Notation and Trees arg.list +(x,y) +(*(x,y),z)+(x,*(y,z))*(x,+(y,z)) prefix + x y + * x y z+ x * y z* x + y z infix x + y x*y + zx + y*zx*(y + z) postfix x y + x y * z +x y z * +x y z + * draw ASTs for each expression How would you pretty print AST into a given form?

Simple Expressions and Tokens sealed abstract class Expr case class Var(varID: String) extends Expr case class Plus(lhs: Expr, rhs: Expr) extends Expr case class Times(lhs: Expr, rhs: Expr) extends Expr sealed abstract class Token case class ID(str : String) extends Token case class Add extends Token case class Mul extends Token case class O extends Token // ( case class C extends Token // )

Printing Trees into Lists of Tokens def prefix(e : Expr) : List[Token] = e match { case Var(id) => List(ID(id)) case Plus(e1,e2) => List(Add()) ::: prefix(e1) ::: prefix(e2) case Times(e1,e2) => List(Mul()) ::: prefix(e1) ::: prefix(e2) } def infix(e : Expr) : List[Token] = e match { // should emit parantheses! case Var(id) => List(ID(id)) case Plus(e1,e2) => List(O())::: infix(e1) ::: List(Add()) ::: infix(e2) :::List(C()) case Times(e1,e2) => List(O())::: infix(e1) ::: List(Mul()) ::: infix(e2) :::List(C()) } def postfix(e : Expr) : List[Token] = e match { case Var(id) => List(ID(id)) case Plus(e1,e2) => postfix(e1) ::: postfix(e2) ::: List(Add()) case Times(e1,e2) => postfix(e1) ::: postfix(e2) ::: List(Mul()) }

LISP: Language with Prefix Notation 1958 – pioneering language Syntax was meant to be abstract syntax Treats all operators as user-defined ones, so syntax does not assume the number of arguments is known –use parantheses in prefix notation: f(x,y) as (f x y) (defun factorial (n) (if (<= n 1) 1 (* n (factorial (- n 1)))))

PostScript: Language using Postfix.ps are ASCII files given to PostScript- compliant printers Each file is a program whose execution prints the desired pages ogramming%20languagehttp://en.wikipedia.org/wiki/PostScript%20pr ogramming%20language PostScript language tutorial and cookbook Adobe Systems Incorporated Reading, MA : Addison Wesley, 1985 ISBN (pbk.)

A PostScript Program /inch {72 mul} def /wedge { newpath 0 0 moveto 1 0 translate 15 rotate 0 15 sin translate sin arc closepath } def gsave 3.75 inch 7.25 inch translate 1 inch 1 inch scale wedge 0.02 setlinewidth stroke grestore gsave 4.25 inch 4.25 inch translate 1.75 inch 1.75 inch scale 0.02 setlinewidth { 12 div setgray gsave wedge gsave fill grestore 0 setgray stroke grestore 30 rotate } for grestore showpage

If we send it to printer (or run GhostView viewer gv) we get 4.25 inch 4.25 inch translate 1.75 inch 1.75 inch scale 0.02 setlinewidth { 12 div setgray gsave wedge gsave fill grestore 0 setgray stroke grestore 30 rotate } for grestore showpage

Why postfix? Can evaluate it using stack def postEval(env : Map[String,Int], pexpr : Array[Token]) : Int = { // no recursion! var stack : Array[Int] = new Array[Int](512) var top : Int = 0; var pos : Int = 0 while (pos < pexpr.length) { pexpr(pos) match { case ID(v) => top = top + 1 stack(top) = env(v) case Add() => stack(top - 1) = stack(top - 1) + stack(top) top = top - 1 case Mul() => stack(top - 1) = stack(top - 1) * stack(top) top = top - 1 } pos = pos + 1 } stack(top) } x -> 3, y -> 4, z -> 5 infix: x*(y+z) postfix: x y z + * Run ‘postfix’ for this env

Evaluating Infix Needs Recursion The recursive interpreter: def infixEval(env : Map[String,Int], expr : Expr) : Int = expr match { case Var(id) => env(id) case Plus(e1,e2) => infix(env,e1) + infix(env,e2) case Times(e1,e2) => infix(env,e1) * infix(env,e2) } Maximal stack depth in interpreter = expression height

Compiling Expressions Evaluating postfix expressions is like running a stack-based virtual machine on compiled code Compiling expressions for stack machine is like translating expressions into postfix form

Expression, Tree, Postfix, Code infix: x*(y+z) postfix: x y z + * bytecode: iload 1x iload 2y iload 3z iadd+ imul* * x y z +

Show Tree, Postfix, Code infix: (x*y + y*z + x*z)*2 tree: postfix: bytecode:

“Printing” Trees into Bytecodes To evaluate e 1 *e 2 interpreter –evaluates e 1 –evaluates e 2 –combines the result using * Compiler for e 1 *e 2 emits: –code for e 1 that leaves result on the stack, followed by –code for e 2 that leaves result on the stack, followed by –arithmetic instruction that takes values from the stack and leaves the result on the stack def compile(e : Expr) : List[Bytecode] = e match { // ~ postfix printer case Var(id) => List(ILoad(slotFor(id))) case Plus(e1,e2) => compile(e1) ::: compile(e2) ::: List(IAdd()) case Times(e1,e2) => compile(e1) ::: compile(e2) ::: List(IMul()) }

Local Variables For int: instructions iload and istore from JVM Spec.JVM Spec Assigning indices (called slots) to local variables using function slotOf : VarSymbol  {0,1,2,3,…} How to compute the indices? –assign them in the order in which they appear in the tree def compile(e : Expr) : List[Bytecode] = e match { case Var(id) => List(ILoad(slotFor(id))) … } def compileStmt(s : Statmt) : List[Bytecpde] = s match { case Assign(id,e) => compile(e) ::: List(IStore(slotFor(id))) … }

Global Variables and Fields Static Variables Consult JVM Spec and seeJVM Spec getstatic putstatic as well as the section on Runtime Constant Pool. Instance Variables (Fields) To access these we use getfield putfield In lower-level machines x.f is evaluated as mem[x + offset(f)] With inheritance, offset(f) must be consistent despite the fact that we do not know exactly what the run-time class of x will be this can be very tricky to implement efficiently with multiple inheritance general approach: do a run-time test on the dynamic type of an object to decide how to interpret field or method access

Factorial Example class Factorial { public int fact(int num){ int num_aux; if (num < 1) num_aux = 1 ; else num_aux= num*(this.fact(num-1)); return num_aux; } public int fact(int); Code: 0: iload_1 1: iconst_1 2: if_icmpge 10 5: iconst_1 6: istore_2 7: goto 20 10: iload_1 11: aload_0 12: iload_1 13: iconst_1 14: isub 15: invokevirtual #2; //Met. fact:(I)I 18: imul 19: istore_2 20: iload_2 21: ireturn aload_0 refers to receiver object (0 th argument), since ‘fact’ is not static

Correctness If we execute the compiled code, the result is the same as running the interpreter. exec(env,compile(expr)) == interpret(env,expr) interpret : Env x Expr -> Int compile : Expr -> List[Bytecode] exec : Env x List[Bytecode] -> Int Assume 'env' in both cases maps var names to values. With much more work, can prove correctness of entire compiler: CompCert - A C Compiler whose Correctness has been Formally Verified

exec(env,compile(expr)) == interpret(env,expr) Attempted proof by induction: exec(env,compile(Times(e1,e2))) == exec(env,compile(e1) ::: compile(e2) ::: List(`*`)) == We need to know something about behavior of intermediate executions. exec : Env x List[Bytecode] -> Int run : Env x List[Bytecode] x List[Int] -> List[Int] // stack as argument and result exec(env,bcodes) == run(env,bcodes,List()).head

run(env,bcodes,stack) = newStack Executing sequence of instructions run : Env x List[Bytecode] x List[Int] -> List[Int] Stack grows to the right, top of the stack is last element Byte codes are consumed from left Definition of run is such that run (env,`*` :: L, S ::: List(x1, x2)) == run(env,L, S:::List(x1*x2)) run (env,`+` :: L, S ::: List(x1, x2)) == run(env,L, S:::List(x1+x2)) run(env,ILoad(n) :: L, S) == run(env,L, S:::List(env(n))) By induction one shows: run (env,L1 ::: L2,S) == run(env,L2, run(env,L1,S)) execute instructions L1, then execute L2 on the result

New correctness condition exec : Env x List[Bytecode] -> Int run : Env x List[Bytecode] x List[Int] -> List[Int] Old condition: exec(env,compile(expr)) == interpret(env,expr) New condition: run(env,compile(expr),S) == S:::List(interpret(env,expr)) for short (T - table), [x] denotes List(x) run(T,C(e),S) == S:::[I(T,e)]

Proof of one case: run(T,C(e),S) == S:::[I(T,e)] run(T,C(Times(e1,e2)),S) == run(T,C(e1):::C(e2):::[`*`],S) == run(T,[`*`], run(T,e2, run(T,e1,S) )) == run(T,[`*`], run(T,e2, S:::[I(T,e1)]) ) == run(T,[`*`], S:::[I(T,e1),I(T,e2)]) == S:::[I(T,e1) * I(T,e2)] == S:::[I(T,Times(e1,e2)]