Intermediate Code Generation Part II Chapter 6: Type checking, Control Flow Slides adapted from : Robert van Engelen, Florida State University
Static versus Dynamic Checking Static checking: the compiler enforces programming language s static semantics Program properties that can be checked at compile time Dynamic semantics: checked at run time Compiler generates verification code to enforce programming language s dynamic semantics
Static Checking Typical examples of static checking are Type checks, overloading and coercion Flow-of-control checks Uniqueness checks
Type Checks, Overloading and Coercion int g(int), g(float); int f(float); int a, c[10], d; d = c+d; // FAIL: type check *d = a; // FAIL: type check a = g(d); // OK: function overloading a = f(d); // OK: coercion of d to float
Flow-of-Control Checks myfunc() { break; // ERROR } myfunc() { while (n) { if (i>10) break; // OK } } myfunc() { switch (a) { case 0: break; // OK case 1: } }
Uniqueness Checks { int i, j, i; // ERROR } myfunc(int a, int a) // ERROR { } struct myrec { int name; }; struct myrec // ERROR { int id; };
One-Pass versus Multi-Pass Static Checking One-pass compiler: static checking for C, Pascal, Fortran, and many other languages is performed in one pass while intermediate code is generated Influences design of a language: placement constraints Multi-pass compiler: static checking for Ada, Java, and C# is performed in a separate phase, sometimes by traversing the syntax tree multiple times
Type Systems A type system defines a set of types and rules to assign types to programming language constructs Informal type system rules, for example if both operands of addition are of type integer, then the result is of type integer Formal type system rules: Post system
Type Rules in Post System Notation An environment ρ maps elementary objects v to types τ, written ρ(v) = τ Example : ρ = { x, integer, y, integer, z, char, 1, integer, 2, integer }
Type Rules in Post System Notation A type judgment, written e : τ, is a statement where e is an expression τ is a type Truth value of type judgment depends on the environment If type judgment e : τ is provable in environment ρ, we write ρ e : τ
Type System Example ρ(v) = τ ρ v : τ If ρ(v) = τ then v : τ is provable ρ e 1 : integer ρ e 2 : integer ρ e 1 + e 2 : integer If e 1 : integer and e 2 : integer are provable in ρ, then e 1 + e 2 : integer is provable in ρ ρ(v) = τ ρ e : τ ρ v = e : void If ρ(v) = τ and ρ e : τ then v = e : void is provable in ρ
Type System Example Type checking = theorem proving Example ρ(x) = integer ρ ρ(y) = integer ρ y : integer ρ y + 2 : integer x = y + 2 : void ρ(2) = integer ρ 2 : integer
A Simple Language Example P D ; S D D ; D id : T T boolean char integer array [ num ] of T ^ T S id = E if E then S while E do S S ; S E true false literal num id E and E E + E E [ E ] E ^
Simple Language Example: Declarations T boolean { T.type = boolean } T char { T.type = char } T integer { T.type = integer } T array [ num ] of T 1 { T.type = array(1.. num.val, T 1.type) } T ^ T 1 { T.type = pointer(t 1 ) } Parametric types: type constructor
Simple Language Example: Declarations ρ(v) = T.type D id : T { addtype(id.entry, T.type) }
Simple Language Example: Checking Statements ρ(v) = τ ρ e : τ ρ v = e : void S id = E { S.type = if id.type == E.type then void else type_error } Note: the type of id is determined by scope s environment: id.type = lookup(id.entry)
Simple Language Example: Checking Statements ρ e : boolean ρ s : τ ρ if e then s : τ S if E then S 1 { S.type = if E.type == boolean then S 1.type else type_error }
Simple Language Example: Checking Statements ρ e : boolean ρ s : τ ρ while e do s : τ S while E do S 1 { S.type = if E.type == boolean then S 1.type else type_error }
Simple Language Example: Checking Statements ρ s 1 : void ρ s 2 : void ρ s 1 ; s 2 : void S S 1 ; S 2 { S.type = if S 1.type == void and S 2.type == void then void else type_error }
Simple Language Example: Checking Expressions E true { E.type = boolean } E false { E.type = boolean } E literal { E.type = char } E num { E.type = integer } E id { E.type = lookup(id.entry) }
Simple Language Example: Checking Expressions ρ e 1 : integer ρ e 2 : integer ρ e 1 + e 2 : integer E E 1 + E 2 { E.type = if E 1.type == integer and E 2.type == integer then integer else type_error }
Simple Language Example: Checking Expressions ρ e 1 : boolean ρ e 2 : boolean ρ e 1 and e 2 : boolean E E 1 and E 2 { E.type = if E 1.type == boolean and E 2.type == boolean then boolean else type_error }
Simple Language Example: Checking Expressions ρ e 1 : array(s, τ) ρ ρ e 1 [e 2 ] : τ e 2 : integer E E 1 [ E 2 ] { E.type = if E 1.type == array(s, t) and E 2.type == integer then t else type_error }
Simple Language Example: Checking Expressions ρ e 1 : pointer(τ) ρ e ^ : τ E E 1 ^ { E.type = if E 1.type == pointer(t) then t else type_error }
A Simple Language Example: Functions Introduce two new rules to treat function declaration/type and function call Example T T -> T E E ( E ) v : integer; odd : integer -> boolean; if odd(3) then v = 1;
Simple Language Example: Function Declarations T T 1 -> T 2 { T.type = function(t 1.type, T 2.type) } Parametric type: type constructor
Simple Language Example: Checking Function Invocations ρ e 1 : function(σ, τ) ρ e 1 (e 2 ) : τ ρ e 2 : σ E E 1 ( E 2 ) { E.type = if E 1.type == function(s, t) and E 2.type == s then t else type_error }
Type Conversion Type conversion coercion when implicitly performed by the compiler cast when explicitly specified by the programmer Both require a type system to check and infer types for (sub)expressions
Type Conversion Type conversion rules varies from language to language; we consider Java Widening rules preserve information Narrowing rules can lose information
Type Conversion: Widening double float long int short char byte
Type Conversion: Narrowing double float long int short byte char
Type Conversion Type max(type t 1, Type t 2 ) { // return least common ancestor } Addr widen(addr a, Type t, Type w) { if ( t == w ) return a; else if ( t = integer and w == float ) { temp = new Temp(); gen(temp = (float) a); return temp; else error; }
Type Conversion E E 1 + E 2 { E.type = max(e 1.type, E 2.type); a 1 = widen(e 1.addr, E 1.type, E.type); a 2 = widen(e 2.addr, E 2.type, E.type); E.addr = new Temp(); gen(e.addr = a 1 + a 2 ); }
[ ] Function Overloading
[ ] Polymorphic Function
[ ] Unification Algorithm