Code Shape II Expressions & Assignment Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412.

Slides:



Advertisements
Similar presentations
Instruction Set Design
Advertisements

Optimizing Compilers for Modern Architectures Allen and Kennedy, Chapter 13 Compiling Array Assignments.
Intermediate Code Generation
1 Compiler Construction Intermediate Code Generation.
Program Representations. Representing programs Goals.
Introduction to Code Optimization Comp 412 Copyright 2010, Keith D. Cooper & Linda Torczon, all rights reserved. Students enrolled in Comp 412 at Rice.
Introduction to Code Generation Comp 412 Copyright 2010, Keith D. Cooper & Linda Torczon, all rights reserved. Students enrolled in Comp 412 at Rice University.
Code Shape, Part II Addressing Arrays, Aggregates, & Strings Comp 412 Copyright 2010, Keith D. Cooper & Linda Torczon, all rights reserved. Students enrolled.
Code Shape IV Procedure Calls & Dispatch Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412.
Intermediate Representations Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412 at Rice University.
Code Shape III Booleans, Relationals, & Control flow Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled.
1 Handling nested procedures Method 1 : static (access) links –Reference to the frame of the lexically enclosing procedure –Static chains of such links.
Code Shape II Expressions & Arrays Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved.
The Procedure Abstraction Part III: Allocating Storage & Establishing Addressability Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all.
1 Intermediate representation Goals: –encode knowledge about the program –facilitate analysis –facilitate retargeting –facilitate optimization scanning.
ISBN Chapter 6 Data Types: Structured types.
The Procedure Abstraction Part I: Basics Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412.
Instruction Selection, II Tree-pattern matching Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in.
Context-sensitive Analysis, II Ad-hoc syntax-directed translation, Symbol Tables, andTypes.
Introduction to Optimization Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved.
Wrapping Up Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved.
Introduction to Code Generation Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved.
Arrays Data Structures - structured data are data organized to show the relationship among the individual elements. It usually requires a collecting mechanism.
5.3 Machine-Independent Compiler Features
The Procedure Abstraction Part I: Basics Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412.
Fast, Effective Code Generation in a Just-In-Time Java Compiler Rejin P. James & Roshan C. Subudhi CSE Department USC, Columbia.
Operator Precedence First the contents of all parentheses are evaluated beginning with the innermost set of parenthesis. Second all multiplications, divisions,
IT253: Computer Organization Lecture 4: Instruction Set Architecture Tonga Institute of Higher Education.
CSC3315 (Spring 2009)1 CSC 3315 Programming Languages Hamid Harroud School of Science and Engineering, Akhawayn University
Runtime Environments Compiler Construction Chapter 7.
Chapter 12 Recursion, Complexity, and Searching and Sorting
Compiler Construction
Introduction to Parsing Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412 at Rice University.
The Procedure Abstraction, Part VI: Inheritance in OOLs Comp 412 Copyright 2010, Keith D. Cooper & Linda Torczon, all rights reserved. Students enrolled.
The Procedure Abstraction, Part V: Support for OOLs Comp 412 Copyright 2010, Keith D. Cooper & Linda Torczon, all rights reserved. Students enrolled in.
Compiler Chapter# 5 Intermediate code generation.
Copyright © 2005 Elsevier Chapter 8 :: Subroutines and Control Abstraction Programming Language Pragmatics Michael L. Scott.
1 Records Record aggregate of data elements –Possibly heterogeneous –Elements/slots are identified by names –Elements in same fixed order in all records.
Code Shape IV Procedure Calls & Dispatch Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412.
Algebraic Reassociation of Expressions Briggs & Cooper, “Effective Partial Redundancy Elimination,” Proceedings of the ACM SIGPLAN 1994 Conference on Programming.
Addressing Modes Chapter 6 S. Dandamudi To be used with S. Dandamudi, “Introduction to Assembly Language Programming,” Second Edition, Springer,
Introduction to Code Generation Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412 at Rice.
The Procedure Abstraction, Part IV Addressability Comp 412 Copyright 2010, Keith D. Cooper & Linda Torczon, all rights reserved. Students enrolled in Comp.
Chapter 7 Object Code Generation. Chapter 7 -- Object Code Generation2  Statements in 3AC are simple enough that it is usually no great problem to map.
Chapter# 6 Code generation.  The final phase in our compiler model is the code generator.  It takes as input the intermediate representation(IR) produced.
Boolean & Relational Values Control-flow Constructs Comp 412 Copyright 2010, Keith D. Cooper & Linda Torczon, all rights reserved. Students enrolled in.
Semantic Analysis II Type Checking EECS 483 – Lecture 12 University of Michigan Wednesday, October 18, 2006.
Procedures and Functions Procedures and Functions – subprograms – are named fragments of program they can be called from numerous places  within a main.
CS412/413 Introduction to Compilers and Translators April 2, 1999 Lecture 24: Introduction to Optimization.
Intermediate Code Generation CS 671 February 14, 2008.
Introduction to Optimization
The Procedure Abstraction Part IV: Allocating Storage & Establishing Addressability Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights.
Chapter 9 :: Subroutines and Control Abstraction
Introduction to Optimization
Intermediate Representations
Chap. 8 :: Subroutines and Control Abstraction
Chap. 8 :: Subroutines and Control Abstraction
Introduction to Code Generation
Wrapping Up Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412 at Rice University have explicit.
Chapter 6 Intermediate-Code Generation
The Procedure Abstraction Part V: Run-time Structures for OOLs
Code Shape III Booleans, Relationals, & Control flow
Instruction Selection, II Tree-pattern matching
Intermediate Representations
Code Shape IV Procedure Calls & Dispatch
Code Shape II Expressions & Assignment
Lecture 15: Code Generation - Instruction Selection
Introduction to Optimization
Algebraic Reassociation of Expressions COMP 512 Rice University Houston, Texas Fall 2003 P. Briggs & K.D. Cooper, “Effective Partial Redundancy Elimination,”
Procedure Linkages Standard procedure linkage Procedure has
Presentation transcript:

Code Shape II Expressions & Assignment Copyright 2003, Keith D. Cooper, Ken Kennedy & Linda Torczon, all rights reserved. Students enrolled in Comp 412 at Rice University have explicit permission to make copies of these materials for their personal use.

Code Shape My favorite example What if x is 2 and z is 3? What if y+z is evaluated earlier? The “best” shape for x+y+z depends on contextual knowledge  There may be several conflicting options x + y + z x + y  t1 t1+ z  t2 x + z  t1 t1+ y  t2 y + z  t1 t1+ z  t2  zyx   z yx   y zx   x zy Addition is commutative & associative for integers

Code Shape Another example -- the case statement Implement it as cascaded if-then-else statements  Cost depends on where your case actually occurs  O(number of cases) Implement it as a binary search  Need a dense set of conditions to search  Uniform (log n) cost Implement it as a jump table  Lookup address in a table & jump to it  Uniform (constant) cost Compiler must choose best implementation strategy No amount of massaging or transforming will convert one into another

Road Map for Class First, look at code shape Consider implementations for several language constructs Then, consider code generation Selection, scheduling, & allocation (order dictated by Lab 3) Look at modern algorithms & modern architectures Lab 3 will give you insight into scheduling  Solve a really hard problem  In Lab 1, allocation was over-simplified If we have time, introduce optimization Eliminating redundant computations (as with DAGs) Data-flow analysis, maybe SSA -form Start out with code shape for expressions …

Generating Code for Expressions The key code quality issue is holding values in registers When can a value be safely allocated to a register?  When only 1 name can reference its value  Pointers, parameters, aggregates & arrays all cause trouble When should a value be allocated to a register?  When it is both safe & profitable Encoding this knowledge into the IR Use code shape to make it known to every later phase Assign a virtual register to anything that can go into one Load or store the others at each reference I LOC has textual “memory tags” on loads, stores, & calls I LOC has a hierarchy of loads & stores ( see the digression ) Relies on a strong register allocator

Generating Code for Expressions The concept Use a simple treewalk evaluator Bury complexity in routines it calls > base(), offset(), & val() Implements expected behavior > Visits & evaluates children > Emits code for the op itself > Returns register with result Works for simple expressions Easily extended to other operators Does not handle control flow expr(node) { int result, t1, t2; switch (type(node)) { case , , ,  : t1  expr(left child(node)); t2  expr(right child(node)); result  NextRegister(); emit (op(node), t1, t2, result); break; case IDENTIFIER: t1  base(node); t2  offset(node); result  NextRegister(); emit (loadAO, t1, t2, result); break; case NUMBER: result  NextRegister(); emit (loadI, val(node), none, result); break; } return result; }

Generating Code for Expressions Example: Produces:  xy expr(node) { int result, t1, t2; switch (type(node)) { case , , ,  : t1  expr(left child(node)); t2  expr(right child(node)); result  NextRegister(); emit (op(node), t1, t2, result); break; case IDENTIFIER: t1  base(node); t2  offset(node); result  NextRegister(); emit (loadAO, t1, t2, result); break; case NUMBER: result  NextRegister(); emit (loadI, val(node), none, result); break; } return result; }

Generating Code for Expressions Example: Generates:   x y 2 expr(node) { int result, t1, t2; switch (type(node)) { case , , ,  : t1  expr(left child(node)); t2  expr(right child(node)); result  NextRegister(); emit (op(node), t1, t2, result); break; case IDENTIFIER: t1  base(node); t2  offset(node); result  NextRegister(); emit (loadAO, t1, t2, result); break; case NUMBER: result  NextRegister(); emit (loadI, val(node), none, result); break; } return result; }

Extending the Simple Treewalk Algorithm More complex cases for IDENTIFIER What about values in registers?  Modify the IDENTIFIER case  Already in a register  return the register name  Not in a register  load it as before, but record the fact  Choose names to avoid creating false dependences What about parameter values?  Many linkages pass the first several values in registers  Call-by-value  just a local variable with “funny” offset  Call-by-reference  needs an extra indirection What about function calls in expressions?  Generate the calling sequence & load the return value  Severely limits compiler’s ability to reorder operations

Extending the Simple Treewalk Algorithm Adding other operators Evaluate the operands, then perform the operation Complex operations may turn into library calls Handle assignment as an operator Mixed-type expressions Insert conversions as needed from conversion table Most languages have symmetric & rational conversion tables Typical Addition Table

Extending the Simple Treewalk Algorithm What about evaluation order? Can use commutativity & associativity to improve code This problem is truly hard What about order of evaluating operands? 1 st operand must be preserved while 2 nd is evaluated Takes an extra register for 2 nd operand Should evaluate more demanding operand expression first (Ershov in the 1950’s, Sethi in the 1970’s) Taken to its logical conclusion, this creates Sethi-Ullman scheme

Generating Code in the Parser Need to generate an initial IR form Chapter 4 talks about A ST s & I LOC Might generate an AST, use it for some high-level, near- source work (type checking, optimization), then traverse it and emit a lower-level IR similar to I LOC The big picture Recursive algorithm really works bottom-up  Actions on non-leaves occur after children are done Can encode same basic structure into ad-hoc SDT scheme  Identifiers load themselves & stack virtual register name  Operators emit appropriate code & stack resulting VR name  Assignment requires evaluation to an lvalue or an rvalue  Some modal behavior is unavoidable

Ad-hoc SDT versus a Recursive Treewalk Goal : Expr { $$ = $1; } ; Expr:Expr PLUS Term { t = NextRegister(); emit(add,$1,$3,t); $$ = t; } |Expr MINUS Term {…} |Term { $$ = $1; } ; Term:Term TIMES Factor { t = NextRegister(); emit(mult,$1,$3,t); $$ = t; }; |Term DIVIDES Factor {…} |Factor { $$ = $1; }; Factor:NUMBER { t = NextRegister(); emit(loadI,val($1),none, t ); $$ = t; } |ID { t1 = base($1); t2 = offset($1); t = NextRegister(); emit(loadAO,t1,t2,t); $$ = t; } expr(node) { int result, t1, t2; switch (type(node)) { case , , ,  : t1  expr(left child(node)); t2  expr(right child(node)); result  NextRegister(); emit (op(node), t1, t2, result); break; case IDENTIFIER: t1  base(node); t2  offset(node); result  NextRegister(); emit (loadAO, t1, t2, result); break; case NUMBER: result  NextRegister(); emit (loadI, val(node), none, result); break; } return result; }

Handling Assignment (just another operator) lhs  rhs Strategy Evaluate rhs to a value (an rvalue) Evaluate lhs to a location (an lvalue)  lvalue is a register  move rhs  lvalue is an address  store rhs If rvalue & lvalue have different types  Evaluate rvalue to its “natural” type  Convert that value to the type of *lvalue Unambiguous scalars go into registers Ambiguous scalars or aggregates go into memory Let hardware sort out the addresses !

Handling Assignment What if the compiler cannot determine the rhs’s type ? This is a property of the language & the specific program If type-safety is desired, compiler must insert a run-time check Add a tag field to the data items to hold type information Code for assignment becomes more complex evaluate rhs if type(lhs)  rhs.tag then convert rhs to type(lhs) or signal a run-time error lhs  rhs This is much more complex than if it knew the types

Handling Assignment Compile-time type-checking Goal is to eliminate both the check & the tag Determine, at compile time, the type of each subexpression Use compile-time types to determine if a run-time check is needed Optimization strategy If compiler knows the type, move the check to compile-time Unless tags are needed for garbage collection, eliminate them If check is needed, try to overlap it with other computation Can design the language so all checks are static

Handling Assignment (with reference counting) The problem with reference counting Must adjust the count on each pointer assignment Overhead is significant, relative to assignment Code for assignment becomes This adds 1 +, 1 -, 2 loads, & 2 stores With extra functional units & large caches, this may become either cheap or free … evaluate rhs lhs  count  lhs  count - 1 lhs  addr(rhs) rhs  count  rhs  count + 1 Plus a check for zero at the end

How does the compiler handle A [i,j] ? First, must agree on a storage scheme Row-major order (most languages) Lay out as a sequence of consecutive rows Rightmost subscript varies fastest A[1,1], A[1,2], A[1,3], A[2,1], A[2,2], A[2,3] Column-major order (Fortran) Lay out as a sequence of columns Leftmost subscript varies fastest A[1,1], A[2,1], A[1,2], A[2,2], A[1,3], A[2,3] Indirection vectors (Java) Vector of pointers to pointers to … to values Takes much more space, trades indirection for arithmetic Not amenable to analysis

Laying Out Arrays The Concept Row-major order Column-major order Indirection vectors 1,11,21,31,42,12,22,32,4 A 1,12,11,22,21,32,31,42,4 A 1,11,21,31,4 2,12,22,32,4 A 1,11,21,31,4 2,12,22,32,4 A These have distinct & different cache behavior

Computing an Array Address A[ i + ( i – low ) x sizeof(A[1]) In general: base(A) + ( i – low ) x sizeof(A[1]) Almost always a power of 2, known at compile-time  use a shift for speed

Computing an Array Address A[ i + ( i – low ) x sizeof(A[1]) In general: base(A) + ( i – low ) x sizeof(A[1]) Almost always a power of 2, known at compile-time  use a shift for speed int A[1:10]  low is 1 Make low 0 for faster access (saves a – )

Computing an Array Address A[ i + ( i – low ) x sizeof(A[1]) In general: base(A) + ( i – low ) x sizeof(A[1]) What about A[i 1,i 2 ] ? Row-major order, two + (( i 1 – low 1 ) x (high 2 – low 2 + 1) + i 2 – low 2 ) x sizeof(A[1]) Column-major order, two + (( i 2 – low 2 ) x (high 1 – low 1 + 1) + i 1 – low 1 ) x sizeof(A[1]) Indirection vectors, two dimensions *(A[i 1 ])[i 2 ] — where A[i 1 ] is, itself, a 1-d array reference This stuff looks expensive! Lots of implicit +, -, x ops

where w = sizeof(A[1,1]) Optimizing Address Calculation for A [i,j] In row-major + (i–low 1 )(high 2 –low 2 +1) x w + (j – low 2 ) x w Which can be factored + i x (high 2 –low 2 +1) x w + j x w – (low 1 x (high 2 –low 2 +1) x w) + (low 2 x w) If low i, high i, and w are known, the last term is a constant 0 – (low 1 x (high 2 –low 2 +1) x w + low 2 x w And len 2 as (high 2 -low 2 +1) Then, the address expression 0 + (i x len 2 + j ) x w Compile-time constants

Array References What about arrays as actual parameters? Whole arrays, as call-by-reference parameters Need dimension information  build a dope vector Store the values in the calling sequence Pass the address of the dope vector in the parameter slot Generate complete address polynomial at each reference Some improvement is possible Save len i and low i rather than low i and high i Pre-compute the fixed terms in prologue sequence What about call-by-value? Most c-b-v languages pass arrays by reference This is a language design low 1 high 1 low 2 high 2

Array References What about A[12] as an actual parameter? If corresponding parameter is a scalar, it’s easy Pass the address or value, as needed Must know about both formal & actual parameter Language definition must force this interpretation What is corresponding parameter is an array? Must know about both formal & actual parameter Meaning must be well-defined and understood Cross-procedural checking of conformability  Again, we’re treading on language design issues

Array References What about variable-sized arrays? Local arrays dimensioned by actual parameters Same set of problems as parameter arrays Requires dope vectors (or equivalent)  dope vector at fixed offset in activation record  Different access costs for textually similar references This presents a lot of opportunity for a good optimizer Common subexpressions in the address polynomial Contents of dope vector are fixed during each activation Should be able to recover much of the lost ground  Handle them like parameter arrays

Example: Array Address Calculations in a Loop DO J = 1, N A[I,J] = A[I,J] + B[I,J] END DO Naïve: Perform the address calculation twice DO J = 1, N R1 0 + (J x len 1 + I ) x floatsize R2 0 + (J x len 1 + I ) x floatsize MEM(R1) = MEM(R1) + MEM(R2) END DO

Example: Array Address Calculations in a Loop DO J = 1, N A[I,J] = A[I,J] + B[I,J] END DO Sophisticated: Move comon calculations out of loop R1 = I x floatsize c = len 1 x floatsize ! Compile-time constant R2 0 + R1 R3 0 + R1 DO J = 1, N a = J x c R4 = R2 + a R5 = R3 + a MEM(R4) = MEM(R4) + MEM(R5) END DO

Example: Array Address Calculations in a Loop DO J = 1, N A[I,J] = A[I,J] + B[I,J] END DO Very sophisticated: Convert multiply to add (Operator Strength Reduction) R1 = I x floatsize c = len 1 x floatsize ! Compile-time constant R2 0 + R1 ; R3 0 + R1 DO J = 1, N R2 = R2 + c R3 = R3 + c MEM(R2) = MEM(R2) + MEM(R3) END DO See, for example, Cooper, Simpson, & Vick, “Operator Strength Reduction”, ACM TOPLAS, Sept 2001