1 / 44

Modern Compiler Design

Modern Compiler Design. T8 – Semantic Analysis: Type Checking . Mooly Sagiv and Greta Yorsh School of Computer Science Tel-Aviv University gretay@post.tau.ac.il http://www.cs.tau.ac.il/~gretay. Example. String. String. “drive” + “drink”. string. (in Java ). ERROR. in Cool ?. Int.

ita
Download Presentation

Modern Compiler Design

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Modern Compiler Design T8 – Semantic Analysis: Type Checking Mooly Sagiv and Greta Yorsh School of Computer Science Tel-Aviv University gretay@post.tau.ac.il http://www.cs.tau.ac.il/~gretay

  2. Example String String “drive” + “drink” string (in Java ) ERROR in Cool ? Int String 12 + “Rumster” ERROR

  3. Executable code exe COOL code txt Today Goals • Implementing Type Checking for Cool • Cool Type Rules Coming up: MIPS tutorial LexicalAnalysis Syntax Analysis Parsing AST SymbolTableetc. Inter.Rep. (IR) Code Gen.

  4. Inheritance Graph • What do we put in it? • all predefined classes: Int, String, Bool, Object, IO • all user-defined classes • Why do we put it there ? • to be able to check that something is a type • to be able to compare types (for type checking) • Implementation • mapping class names to nodes in the graph • node points to its superclass node

  5. Major Semantic Tasks • Inheritance graph construction and checking • Symbol table construction • construct symbol table for features using inheritance tree • assign enclosing scope for each AST node • Scope checking • check program using symbol tables • Check for Main class and main method • Type checking • uses inheritance graph and symbol tables • assign type for each AST node

  6. Semantic Checks • Scope rules • identifiers are defined • no multiple definition of same identifier • local variables are defined before used • … • Type rules • which types can be combined with certain operator • assignment of expression to variable • formal and actual parameters of a method call • …

  7. Notation for Inference Rules • By tradition inference rules are written Hypothesis1  Hypothesis2 ...  Hypothesisn  Conclusion •  means “it is provable that ...” • Cool type rules have hypothesis and conclusion O,M,C  e: T [ NAME] conditions type environment

  8. Type Checking - Implementation • Single traversal over AST • Types passed up the tree • Type environment passed down the tree

  9. globals Symbol kind Foo class Foo Symbol kind test Int->Int x Int test Symbol c Int O(x) = Int int_const object int_const val=32 name=x val=45 O,M,C  x: Int O,M,C int_const : Int Example … lt plus neg lookup(x) : Int : Int (45 + x) < (~32)

  10. O,M,C  E1 : Int O,M,C  E2 : Int O,M,C  E1 + E2 : Int O,M,C  E1 : Int O,M,C  ~E1 : Int O,M,C  E1 : Int O,M,C  E2 : Int O,M,C  E1 < E2 : Bool O(x) = Int int_const object int_const val=45 name=x val=32 O,M,C  x: Int O,M,C int_const : Int Example … lt : Bool : Int : Int plus neg : Int : Int : Int (45 + x) < (~32)

  11. Cool Types • How types are represented ? • AbstractSymbol • compare types by comparing AbstractSymbols • When are types are created? • during lexer/parsing • predefined types

  12. Cool Types • The types are • Class Names • SELF_TYPE • The user declares types for identifiers • The semantic analysis infers types for expressions • Every expression has a unique type • A language’s typesystem specifies which operations are valid for which types • The goal of type checking is to ensure that operations are used with the correct types • Enforces intended interpretation of values • Cool is statically typed: type checking during compilation

  13. Cool Types Environment O, M, K  e: T • gives types to free identifiers in the current scope • information about the formal parameters and return type of methods • the class in which an expression e appears • Why separate object/methods? • In Cool, the method and object identifiers live in different name spaces type environment

  14. Cool Types Environment O, M, K  e: T • O --- mapping Object Id’s to types • symbol table - current scope • O(x) = T • M --- mapping methods to method signatures • M(C, f) = (A, B, D) • means there is a method f(a:A, b:B): D defined in class C (or its ancestor) • K --- the class in which expression e appears • when SELF_TYPE is involved type environment

  15. O(x) = T O,M,C  x: T O,M,C int_const : Int Type rules O, M, K  e1 : Int O, M, K  e2 : Int O, M, K  e1 + e2 : Int • Rules give templates describing how to type expressions • By filling the templates, we can produce complete typings for expressions

  16. O,M,C  E1 : Int O,M,C  E2 : Int O,M,C  E1 + E2 : Int 1 is an integer O,M,C  1 : Int 2 is an integer O,M,C  2: Int [Int] [Int] [Add] O,M,C 1+2: Int O,M,C int_const : Int Example 1 +2 • Templates: • Inference:

  17. Object IO D E C A B Subtyping • Define a relation  on classes • X  X • X  Y if X inherits from Y • X  Z if X  Y and Y  Z A  C B  Object E  D and D  E

  18. Least Upper Bounds • Z is the least upper bound of X and Y lub(X, Y)=Z • X  Z and Y  Z Z is upper bound • X  Z’ and Y  Z’  Z  Z’Z is the least upper bound

  19. lub(A,B) = C lub(C,D) = D lub(C,E) = Object In Cool, the least upper bound of two types is their least common ancestor in the inheritance tree Object IO D E C A B Least Upper Bounds

  20. Dynamic and Static Types in Cool • A variable of static type A can hold the value of static type B if B  A class A { ... } class B inherits A { ... } class Main ( x: A  new A ; ... x  new B ; } static type of x is A dynamic type of x is A dynamic type of x is B

  21. Dynamic and Static Types • The dynamic type of an object is the class that is used in the new expression • a runtime notion • even languages that are statically typed have dynamic types • The static type of an expression captures all the dynamic types that the expression could have • a compile-time notion

  22. Static vs. Dynamic Typing • Static typing proponents say: • Static checking catches many programming errors • Prove properties of your code • Avoids the overhead of runtime type checks • C, Java, Cool, ML • Dynamic typing proponents say: • Static type systems are restrictive • Rapid prototyping difficult with type systems • Complicates the programming language and the compiler • Compiler optimizations can hide costs • machine code • In practice, most code is written in statically typed languages with escape mechanisms • Unsafe casts in C, Java • union in C

  23. Soundness • A type system is sound if  e. dynamic_type(e)  static_type(e) • whenever the inferred type of e is T ( denoted by O,M,C  e : T ) • Then e evaluates to a value of type  T in all executions of the program • We only want sound rules • But some sound rules are better than others

  24. O (x) = T0 O,M,K  e1 : T1 T1 T0 O,M,K  x  e1 : T1 [Assign] A Assign class A { foo() : A { … } }; class B inherits A { }; ... let x:B in x (new B).foo(); let x:A in x (new B).foo(); let x:Object in x (new B).foo(); ERROR OK OK

  25. O,M,K  e0: T O(T/x)  e1 : T1 O,M,K  let x: Te0 in e1 : T1 O,M,K  e0: T O(T0/x)  e1 : T1 T  T0 O,M,K  let x: T0 e0 in e1 : T1 Let Rule with Initialization • Both rules are sound but more programs typecheck with the second one • uses subtyping Weak rule class A { foo():C { … } }; class B inherits A { }; ... let x:A  new B in x.foo();

  26. O,M,K  e0 :Bool O,M,K  e1: T1 O,M,K  e2 : T2 O,M,K  if e0 then e1 else e2 fi : lub(T1, T2) Object IO D E C A B If-Then-Else foo(a:A, b:B, c:C, e:E) : D { if (a < b) then e else c fi } lub(E,C) = Object Is Object  D ? ERROR

  27. Object IO D E IO C A B Case O,M,K  e : T O[X/x],M,K  e1: E O[Y/y],M,K  e2: F O[Z/z],M,K  e3: G O,M,K  case e of x: X =>e1; y:Y =>e2 ; z:Z=>e3 esac : lub(E,F,G) foo(d:D) : D { case d of x : IO => let a:A(new A) in x; y : E => (new B); z : C => z; esac }; lub(IO,B,C) = Object and Object  D ERROR

  28. Object IO D E A C A B Case O,M,K  e : T O[X/x],M,K  e1: E O[Y/y],M,K  e2: F O[Z/z],M,K  e3: G O,M,K  case e of x: X =>e1; y:Y =>e2 ; z:Z=>e3 esac : lub(E,F,G) foo(d:D) : D { case d of x : IO => let a:A(new A) in a; y : E => (new B); z : C => z; esac }; lub(A,B,C) = C and C  D OK

  29. SELF_TYPE Example • What can be a dynamic type of object returned by foo() ? • any subtype of A class A { foo() : A { self } ; }; class B inherits A { ... }; class Main { B x  (new B).foo(); }; class A { foo() : SELF_TYPE { self } ; }; class B inherits A { ... }; class Main { B x  (new B).foo(); }; ERROR OK

  30. SELF_TYPE • Research idea • Helps type checker to accept more correct programs C,M,K  (new A).foo() : A C,M,K  (new B).foo() : B • SELF_TYPE is NOT a dynamictype • Meaning of SELF_TYPE depends on where it appears textually

  31. Where can SELF_TYPE appear ? • Parser checks that SELF_TYPE appears only where a type is expected • How ? • But SELF_TYPE is not allowed everywhere a type can appear

  32. Where can SELF_TYPE appear ? • class T1 inherits T2 { … } • T1, T2 cannot be SELF_TYPE • x : SELF_TYPE • attribute • let • case • new SELF_TYPE • creates an object of the same type as self • foo@T(e) • T cannot be SELF_TYPE • foo(x:T1):T2 {…} • only T2 can be SELF_TYPE

  33. new Example class A { foo() : A { new SELF_TYPE }; }; class B inherits A { … } … (new A).foo(); (new B).foo(); creates A object creates B object

  34. Subtyping for SELF_TYPE • SELF_TYPEc SELF_TYPEc • SELF_TYPEc C • It is always safe to replace SELF_TYPEc with C • SELF_TYPEc  T if C  T • T  SELF_TYPEc is always false • because SELF_TYPEc can denote any subtype of C

  35. lub(T,T’) for SELF_TYPE • lub(SELF_TYPEc, SELF_TYPEc) = SELF_TYPEc • lub(T, SELF_TYPEc) = lub(T,C) • the best we can do

  36. New Rules O, M, K  self : SELF_TYPEk O, M, K  new SELF_TYPE : SELF_TYPEk

  37. Other rules • A use of SELF_TYPE refers to any subtype of the current class • Except in dispatch • because the method return type of SELF_TYPE might have nothing to do with the current class

  38. O, M  c : C O, M  a : A O, M  b : B M(C, foo) = (A1, B1,SELF_TYPE) A  A1 , B  B1 O, M, K  c.foo(a, b): C Dispatch O, M  c : C O, M  a : A O, M  b : B M(C, foo) = (A1, B1, D1) A  A1 , B  B1, D1  SELF_TYPE O, M, K  c.foo(a, b) : D1 • which class is used to find the declaration of foo() ?

  39. Example class A { foo() : A { self }; }; class B inherits A { … } … (new A).foo(); (new B).foo(); returns A object returns B object

  40. Static Dispatch O, M, K  c : C O, M, K  a : A O, M, K  b : B M(C, f) = (A1, B1, D1) A  A1, B  B1, C  C1, D1  SELF_TYPE O, M, K  c.f@C1(a, b): D1 • if we dispatch a method returning SELF_TYPE in class C1, do we get back C1 ? • No. SELF_TYPE is the type of self, which may be a subtype of the class in which the method appears O, M, K  c : C O, M, K  a : A O, M, K  b : B M(C, f) = (A1, B1,SELF_TYPE) A  A1, B  B1, C  C1 O, M, K  c@C1.f@(a, b): C

  41. SELF_TYPE Example class A { deligate : B; callMe() : ??? { deligate.callMe(); } ; }; class B { callMe() : SELF_TYPE { self }; }; class Main { A a (new A).callMe(); }; SELF_TYPE ERROR

  42. Error Handling • Error detection is easy • Error recovery: what type is assigned to an expression with no legitimate type ? • influences type of enclosing expressions • cascading errors let y : Int  x + 2 in y + 3 • Better solution: special type No_Type • inheritance graph can be cyclic

  43. Summary • Type is a set of values • Type rules are logical rules of inference • Soundness of (static) type systems • for every expression e, for every value v of e at runtime v values_of(static_type(e)) • static_type(e) may actually describe more values • can reject correct programs • Becomes more complicated with subtyping (inheritance)

  44. Major Semantic Tasks • Inheritance graph construction and checking • Symbol table construction • construct symbol table for features using inheritance tree • assign enclosing scope for each AST node • Scope checking • check program using symbol tables • Check for Main class and main method • Type checking • uses inheritance graph and symbol tables • assign type for each AST node • Remaining Semantic Checks • checks that require type + symbol table information combined • Disclaimer: This is a naïve phase ordering. Phases could be mixed, combined, optimized, re-ordered etc.

More Related