SIMPLE (SINGLE) INHERITANCE AND VIRTUAL DISPATCH ================================================ class A { int x = 1; void f() {print(x);} } class B extends A { int y = 2; void f() {print(y);} void g() {print(x+y);} } // Key invariant: a B object can appear (dynamically) // wherever an A object is expected (statically). main { A a = new A(); B b = new B(); print(a.x); // prints 1 print(b.x + b.y); // prints 3 A a1 = b; a.f(); // prints 1 b.f(); // prints 2 a1.f(); // prints 2 b.g(); // prints 3 // a1.g(); -- ILLEGAL // a1.y; -- ILLEGAL } OBJECT/CLASS LAYOUT =================== Key trick: offsets of fields in objects, and methods in classes, are unchanged in subclasses. class A ---------------------- ---------------- a --> | class ptr -----|-----------> | f --------|----> code ---------------------- ---------------- for print(x) | int x | ---------------------- class B ---------------------- ---------------- b --> | class ptr -----|-----------> | f --------|----> code ---------------------- / ---------------- for print(y) | int x | / | g --------|----> code ---------------------- / ---------------- for print(x+y) | int y | / ---------------------- / / / ---------------------- / a1 --> | class ptr ------|--- ---------------------- | int x | ---------------------- | int y | ---------------------- EXECUTABLE PSEUDO-CODE ====================== (Like bytecode, but more explicit about addressing and offsets.) Method A.f(): // print(this.x); aload_0 // get "this" pointer getfield 1 // x is at offset 1 invokestatic print return Method B.f() // print(this.y); aload_0 // get "this" pointer getfield 2 // y is at offset 2 invokestatic print return Method B.g() // print (this.x + this.y); aload_0 // get "this" pointer getfield 1 // x is at offset 1 aload_0 // get "this" pointer getfield 2 // y is at offset 1 iadd invokestatic print return Method main // A a = new A(); new // create new instance of A astore_1 // store as local variable 1 (a) // B b = new B(); new // create new instance of B astore_2 // store as local variable 2 (b) // print (a.x); aload_1 // reference to a getfield 1 // x is at offset 1 invokestatic print // print (b.x + b.y); aload_2 // reference to b getfield 1 // x is at offset 1 aload_2 getfield 2 // y is at offset 2 iadd invokestatic print // A a1 = b; aload_2 // reference to b astore_3 // store as local variable 3 (a1) // a.f(); aload_1 // reference to a invokevirtual 0 // f is at offset 0 // b.f(); aload_2 // reference to b invokevirtual 0 // a1.f(); aload_3 // reference to a1 invokevirtual 0 // b.g(); aload_2 // reference to b invokevirtual 1 // g is at offset 1 Multiple Inheritance in Java ============================ Problem: can't assign uniform, compact offsets to functions in Interfaces, because different Classes can implement arbitrary combinations of Interfaces. Naive solution: - assign unique (non-compact) id's to interfaces - assign compact offsets to functions within each interface - for each pair (class, interface implemented by class) build one compact dispatch table, indexed by offset - class contains compact mapping from iface id's to dispatch tables, which must be searched at runtime. Somewhat faster (but somewhat larger): - instead of compact mapping, use a directly-indexed mapping Another idea: - abandon interface-based mappings - instead, assign a unique compact offset to every function, and dispatch directly via a table indexed by these offsets - functions that can never appear in the same object(s) can be assigned the same offset, in order to keep total number of offsets small (this is a graph coloring problem) - requires full knowledge of inheritance graph (not available in Java) - Jalapeno paper describes how to cope with clashes in the function offset assignments.