implementing object oriented languages implementing
play

Implementing Object-Oriented Languages Implementing instance - PDF document

Implementing Object-Oriented Languages Implementing instance variable access Key features: Key problem: subtype polymorphism inheritance (possibly multiple) Solution: prefixing subtyping & subtype polymorphism layout of


  1. Implementing Object-Oriented Languages Implementing instance variable access Key features: Key problem: subtype polymorphism • inheritance (possibly multiple) Solution: prefixing • subtyping & subtype polymorphism • layout of subclass has layout of superclass as a prefix • message passing, dynamic binding, run-time type testing • code that accesses a superclass will access the superclass part of any subclass properly, transparently + access is just a load or store at a constant offset Subtype polymorphism is the key problem class Point { x • support uniform representation of data int x; (analogous to boxing for polymorphic data) int y; y • store the class of each object at a fixed offset } • organize layout of data to make instance variable access and method lookup & invocation fast class ColorPoint x extends Point { • code compiled expecting an instance of a superclass y Color color; still works if run on an instance of a subclass } • multiple inheritance complicates this color • perform static analysis to bound polymorphism // OK: subclass polymorphism • perform transformations to reduce polymorphism Point p = new ColorPoint(3,4,Blue); // OK: x and y have same offsets in all Point subclasses int manhattan_distance = p.x + p.y; Craig Chambers 232 CSE 501 Craig Chambers 233 CSE 501 Implementing dynamic dispatching (virtual functions) Virtual function tables How to find the right method to invoke for a dynamically Observation: in Option 3, all instances of a given class will have dispatched message rcvr.Message(arg1, ...) ? identical method addresses Option 1: search inheritance hierarchy, Option 4: factor out class-invariant parts into shared object starting from run-time class of rcvr • instance variables whose values are common across all − very slow, penalizes deep inheritance hierarchies instances of a class (e.g. method addresses) are moved out to a separate object • historically called a virtual function table (vtbl) Option 2: use a hash table • each instance contains a single pointer to the vtbl • can act like a cache on the front of Option 1 • combine with (or replace) class pointer − still significantly slower than a direct procedure call • layout of subclass’s vtbl has layout of superclass’s vtbl as a • but used in early Smalltalk systems! prefix Option 3: store method addresses in the receiver objects, + dynamic dispatching is fast & constant-time as if they were instance variables − but an extra load • each message/generic function declares an instance + no space cost in object variable • aside from vtbl/class pointer • each inheriting object stores an address in that instance variable • invocation = load + indirect jump! + good, constant-time invocation, independent of inheritance structure, overriding, ... − much bigger objects Craig Chambers 234 CSE 501 Craig Chambers 235 CSE 501

  2. Example of virtual function tables Multiple inheritance class Point { Problem: prefixing doesn’t work with multiple inheritance int x; int y; class Point { x void draw(); int x; int distance2origin(); int y; y } } table Point::draw draw class ColoredThing { color x ... d2o Color color; Point::d2o } y x color class ColorPoint class ColorPoint extends Point { extends Point, ? y x Color color; ColoredThing { color y } void draw(); void reverse_video(); } ColorPoint cp = new ColorPoint(3, 4, Blue); table ColorPt::draw draw Point p = cp; // OK x ... ColoredThing t = cp; // OK d2o ColorPoint cp2 = y r_v ColorPt::r_v new ColorPoint(p.x, p.y, t.color); // breaks color Craig Chambers 236 CSE 501 Craig Chambers 237 CSE 501 Some solutions Another solution Option 1: stick with single inheritance [e.g. Smalltalk] Option 4: embedding + pointer shifting [C++] − some examples really benefit from MI • concatenate superclass layouts, extend with subclass data • when upcasting to a superclass, shift pointer to point to where superclass is embedded Option 2: distinguish classes from interfaces [e.g. Java, C#] • downcasting does the reverse • only single inheritance below classes ⇒ if rcvr statically of class type, then can exploit • virtual function calls may need to shift rcvr pointers prefixing for its instance variable accesses and message • "trampolines" may get inserted sends • disallow instance variables in interfaces + gets back to constant-time access in most cases ⇒ no problems accessing them! • only messages to receivers of interface type are unresolved − very complicated, lots of little details ⇒ much smaller problem; can use e.g. hashing − some things (e.g. casting) may now have run-time cost − does poorly if using "virtual base classes", i.e., diamond- Option 3: compute offset of a field in rcvr by sending rcvr a shaped inheritance hierarchies message [Cecil/Vortex] − some sensible programs now disallowed • reduced problem to dynamic dispatching • e.g. casting through void*, downcasting from virtual base class • apply CHA etc. to optimize (all) dispatches − interior pointers may complicate GC, equality testing, ⇒ for fields whose offsets never change, debugging, etc. static binding + inlining reduces dispatches to constant Craig Chambers 238 CSE 501 Craig Chambers 239 CSE 501

  3. Example Example of virtual function tables class Point { class Point { class ColoredThing { x int x; int x; Color color; y int y; int y; void reverse_video(); } void draw(); } int d2o(); } class ColoredThing { color Color color; table } table draw r_v cp x d2o color x p class ColorPoint y extends Point, y ColoredThing { t color } class ColorPoint extends Point, ColoredThing { void draw(); ColorPoint cp = new ColorPoint(3, 4, Blue); } Point p = cp; // OK p table draw ColorPt::draw ColoredThing t = cp; // OK: adds 8 to cp x // now this works: d2o ColorPoint cp2 = y this = this + 12; r_v new ColorPoint(p.x, p.y, t.color); jump CT::r_v; t table // this works, too: ColorPoint cp3 = r_v color (ColorPoint) t; // subtracts 8 from t Craig Chambers 240 CSE 501 Craig Chambers 241 CSE 501 Limitations of table-based techniques Dynamic table-based implementations Table-based techniques only work well when: Standard implementation: global hash table in runtime system • indexed by class × msg • have static type information to use to map message/ • filled dynamically as program runs instance variable names to offsets in tables/objects • can be flushed after reflective operations • not true in dynamically typed languages + reasonable space cost + incremental • cannot extend classes with new operations except via − fair average-case dispatch time, poor worst-case time subclassing • not true in languages with open classes (e.g. MultiJava [Clifton et al. 00]) or multiple dispatching (e.g. CLOS, Dylan, Cecil) Refinement: hash table per message name • each call site knows statically which table to consult • cannot modify classes dynamically + faster dispatching • not true in fully reflective languages (e.g. Smalltalk, Self, CLOS) • memory loads and indirect jumps are inexpensive • may not be true with heavily pipelined hardware Craig Chambers 242 CSE 501 Craig Chambers 243 CSE 501

  4. Inline caching Example of inline caching Give each dynamically-dispatched call site its own Initially: small method lookup cache + call site knows its message name ... + cache is isolated from other call sites call Lookup msg: “draw” class: Trick: use machine call instruction itself as a one-element cache ... • initially: call runtime system’s Lookup routine • Lookup routine patches call instruction to branch to invoked method • record receiver class • next time through, jump directly to expected target method After caching target method: • method checks whether current receiver class is same as last receiver class ... ColorPoint::draw() • if so, then cache hit (90-95% frequency, for Smalltalk) if cache.class ≠ call Lookup • if not, then call Lookup and rebind cache self.class then msg: “draw” class: CPt call Lookup + fast dispatch sequence if cache hit ( ≈ 4 instructions plus call) ... regular code ... ... + hardware call prefetching works well − exploits self-modifying code − low performance if not a cache hit [Deutsch & Schiffman 84] Craig Chambers 244 CSE 501 Craig Chambers 245 CSE 501 Polymorphic inline caching (PIC) Example of polymorphic inline caching Idea: support a multi-element cache by generating a After a few receiver classes: call-site-specific dispatcher stub + fast dispatching even if several classes are common ... − still slow performance if many classes equally common call Lookup − some space cost msg: “draw” ... Foreshadowing: dispatching stubs record dynamic profile data of which receiver classes occur at which call sites switch (self.class) { ColorPt::draw() case ColorPt: case ColorPt3D: case Point: [Hölzle et al. 91] Point::draw() default: call Lookup } Craig Chambers 246 CSE 501 Craig Chambers 247 CSE 501

Recommend


More recommend