cpsc 213
play

CPSC 213 Switch Statements, Understanding Pointers - 2nd ed: 3.6.7, - PowerPoint PPT Presentation

Readings for Next Two Lectures Text CPSC 213 Switch Statements, Understanding Pointers - 2nd ed: 3.6.7, 3.10 - 1st ed: 3.6.6, 3.11 Introduction to Computer Systems Unit 1f Dynamic Control Flow Polymorphism and Switch Statements 1 2


  1. Readings for Next Two Lectures ‣ Text CPSC 213 •Switch Statements, Understanding Pointers - 2nd ed: 3.6.7, 3.10 - 1st ed: 3.6.6, 3.11 Introduction to Computer Systems Unit 1f Dynamic Control Flow Polymorphism and Switch Statements 1 2 Back to Procedure Calls ‣ Static Method Invocations and Procedure Calls •target method/procedure address is known statically ‣ in Java public class A { • static methods are class methods static void ping () {} } - invoked by naming the class, not an object Polymorphism public class Foo { static void foo () { A.ping (); } } ‣ in C void ping () {} •specify procedure name void foo () { ping (); } 3 4

  2. Polymorphism Polymorphic Dispatch ‣ Invoking a method on an object in Java ‣ Method address is determined dynamically • variable that stores the object has a static type • compiler can not hardcode target address in procedure call • object reference is dynamic and so is its type • instead, compiler generates code to lookup procedure address at runtime - object’s type must implement the type of the referring variable • address is stored in memory in the object’s class jump table - but object’s type may override methods of this base type ‣ Class Jump table ‣ Polymorphic Dispatch • every class is represented by class object • target method address depends on the type of the referenced object • the class object stores the class’s jump table • one call site can invoke different methods at different times • the jump table stores the address of every method implemented by the class • objects store a pointer to their class object class A { static void foo (A a) { void ping () {} Which ping gets called? a.ping (); ‣ Static and dynamic of method invocation void pong () {} a.pong (); } } • address of jump table is determined dynamically static void bar () { • method’s offset into jump table is determined statically foo (new A()); class B extends A { foo (new B()); void ping () {} } void wiff () {} } 5 6 Example of Java Dispatch Dynamic Jumps in C class A { void ping () {} void pong () {} } ‣ Function pointer Class A A.ping () {} class B extends A { •a variable that stores a pointer to a procedure ping void ping () {} pong void wiff () {} •declared A.pong () {} } - <return-type> (*<variable-name>)(<formal-argument-list>); Class B static void foo (A a) { •used to make dynamic call B.ping () {} a.ping (); ping a.pong (); - <variable-name> (<actual-argument-list>); pong } ‣ Example wiff B.wiff () {} static void bar () { foo (new A()); foo (new B()); } void ping () {} void foo () { an A a B void (*aFunc) (); foo (a) r5 r5 a a aFunc = ping; r[0] ← m[r[5]] # r0 = a calls ping aFunc (); r[1] ← m[r[0]] # r1 = a.class pc ← m[r[1]+0*4] # a.ping () } pc ← m[r[1]+1*4] # a.pong () Runtime Stack 7 8

  3. Simplified Polymorphism in C (SA-dynamic-call.c) •and B ... Declaration of B’s jump table and code ‣ Use a struct to store jump table struct B { void (*ping)(); •drawing on previous example of A ... void (*pong)(); Declaration of A’s jump table and code void (*wiff)(); }; struct A { void (*ping) (); void B_ping () { printf ("B_ping\n"); } void (*pong) (); void B_wiff () { printf ("B_wiff\n"); } }; void A_ping () { printf ("A_ping\n"); } Create an instance of B’s jump table void A_pong () { printf ("A_pong\n"); } struct B* new_B () { struct B* b = (struct B*) malloc (sizeof (struct B)); Create an instance of A’s jump table b->ping = B_ping; b->pong = A_pong; struct A* new_A () { b->wiff = B_wiff; struct A* a = (struct A*) malloc (sizeof (struct A)); return b; a->ping = A_ping; } a->pong = A_pong; return a; } 9 10 Dispatch Diagram for C (data layout) •invoking ping and pong on an A and a B ... void foo (struct A* a) { a->ping (); struct A void A_ping () {} A.ping () {} a->pong (); void A_pong () {} ping } void B_ping () {} pong void B_wiff () {} A.pong () {} void bar () { foo (new_A ()); foo ((struct A*) new_B ()); Struct B B.ping () {} } ping pong wiff B.wiff () {} struct A { struct B { void (*ping) (); void (*ping)(); void (*pong) (); void (*pong)(); }; void (*wiff)(); }; struct A* new_A () { struct A* a = (struct A*) malloc ( sizeof (struct A) ); struct B* new_B () { a->ping = A_ping; struct B* b = (struct B*) malloc ( sizeof (struct B) ); a->pong = A_pong; b->ping = B_ping; return a; b->pong = A_pong; } b->wiff = B_wiff; return b; } 11 12

  4. Dispatch Diagram for C (the dispatch) ISA for Polymorphic Dispatch void foo (struct A* a) { r[0] ← m[r[5]] # r0 = a pc ← m[r[1]+0*4] # a->ping () struct A void A_ping () {} a->ping (); A.ping () {} pc ← m[r[1]+1*4] # a->ping () void A_pong () {} a->pong (); ping void B_ping () {} } pong void B_wiff () {} A.pong () {} ‣ How do we compile Struct B B.ping () {} ping • a->ping () ? void foo (struct A* a) { pong ‣ Pseudo code a->ping (); B.wiff () {} wiff a->pong (); } • pc ← m[r[1]+0*4] void bar () { ‣ Current jumps supported by ISA foo (new_A ()); foo ( (struct A*) new_B ()); } Name Semantics Assembly Machine jump absolute pc ← a j a b--- aaaaaaaa indirect jump pc ← r[ t ] + ( o == pp *2) j o (r t ) ctpp foo (a) r5 r5 a ‣ We will benefit from a new instruction in the ISA a r[0] ← m[r[5]] # r0 = a pc ← m[r[1]+0*4] # a->ping () pc ← m[r[1]+1*4] # a->ping () • that jumps to an address that is stored in memory Runtime Stack 13 14 ‣ Double-indirect jump instruction (b+o) •jump to address stored in memory using base+offset addressing Name Semantics Assembly Machine jump absolute pc ← a j a b--- aaaaaaaa indirect jump pc ← r[ t ] + ( o == pp *2) j o (r t ) ctpp dbl-ind jump b+o pc ← m[r[ t ] + ( o == pp *2)] j * o (r t ) dtpp Switch Statements 15 16

  5. Switch Statement Human vs Compiler int i; void bar () { ‣ Benefits for humans int j; if (i==0) j=10; void foo () { else if (i==1) • the syntax models a common idiom: choosing one computation from a set switch (i) { j = 11; ‣ But, switch statements have interesting restrictions case 0: j=10; break; else if (i==2) case 1: j=11; break; j = 12; • case labels must be static , cardinal values case 2: j=12; break; else if (i==3) case 3: j=13; break; j = 13; - a cardinal value is a number that specifies a position relative to the beginning of an ordered set default: j=14; break; else } j = 14; - for example, integers are cardinal values, but strings are not } } • case labels must be compared for equality to a single dynamic expression ‣ Semantics the same as simplified nested if statements - some languages permit the expression to be an inequality ‣ Do these restrictions benefit humans? • where condition of each if tests the same variable • unless you leave the break the end of the case block • have you ever wanted to do something like this? ‣ So, why bother putting this in the language? switch (treeName) { switch (i,j) { • is it for humans, facilitate writing and reading of code? case "larch": case i>0: • is it for compilers, permitting a more efficient implementation? case "cedar": case i==0 & j>a: case "hemlock": case i<0 & j==a: ‣ Implementing switch statements } default: • we already know how to implement if statements; is there anything more to consider? } 17 18 Why Compilers like Switch Statements Happy Compilers mean Happy People switch (i) { label jumpTable[4] = { L0, L1, L2, L3 }; ‣ Notice what we have case 0: j=10; break; if (i >3) goto DEFAULT; case 1: j=11; break; • switch condition evaluates to a number goto jumpTable[i]; case 2: j=12; break; L0: j = 10; • each case arm has a distinct number case 3: j=13; break; goto CONT; ‣ And so, the implementation has a simplified form default: j=14; break; L1: j = 11; } goto CONT; • build a table with the address of every case arm, indexed by case value L2: j = 12; goto CONT; • switch by indexing into this table and jumping to matching case arm L3: j = 13; ‣ For example goto CONT; DEFAULT: switch (i) { label jumpTable[4] = { L0, L1, L2, L3 }; j = 14; case 0: j=10; break; if (i >3) goto DEFAULT; goto CONT; case 1: j=11; break; goto jumpTable[i]; CONT: case 2: j=12; break; L0: j = 10; case 3: j=13; break; goto CONT; ‣ Computation can be much more efficient default: j=14; break; L1: j = 11; if (i==0) } goto CONT; j=10; • compare the running time to if-based alternative L2: j = 12; else if (i==1) ‣ But, could it all go horribly wrong? goto CONT; j = 11; L3: j = 13; else if (i==2) • construct a switch statement where this implementation technique j = 12; goto CONT; is a really bad idea else if (i==3) DEFAULT: ‣ Guidelines for writing efficient switch statements j = 13; j = 14; else goto CONT; j = 14; CONT: 19 20

Recommend


More recommend