| 1 | Proposal for virtual functionality | 
|---|
| 2 |  | 
|---|
| 3 | There are two types of virtual inheritance in this proposal, relaxed | 
|---|
| 4 | (implicit) and strict (explicit). Relaxed is the simpler case that uses the | 
|---|
| 5 | existing trait system with the addition of trait references and vtables. | 
|---|
| 6 | Strict adds some constraints and requires some additional notation but allows | 
|---|
| 7 | for down-casting. | 
|---|
| 8 |  | 
|---|
| 9 | Relaxed Virtual Inheritance: | 
|---|
| 10 |  | 
|---|
| 11 | Imagine the following code : | 
|---|
| 12 |  | 
|---|
| 13 | trait drawable(otype T) { | 
|---|
| 14 | void draw(T* ); | 
|---|
| 15 | }; | 
|---|
| 16 |  | 
|---|
| 17 | struct text { | 
|---|
| 18 | char* text; | 
|---|
| 19 | }; | 
|---|
| 20 |  | 
|---|
| 21 | void draw(text*); | 
|---|
| 22 |  | 
|---|
| 23 | struct line{ | 
|---|
| 24 | vec2 start; | 
|---|
| 25 | vec2 end; | 
|---|
| 26 | }; | 
|---|
| 27 |  | 
|---|
| 28 | void draw(line*); | 
|---|
| 29 |  | 
|---|
| 30 | While all the members of this simple UI support drawing, creating a UI that | 
|---|
| 31 | easily supports both these UI requires some tedious boiler-plate code: | 
|---|
| 32 |  | 
|---|
| 33 | enum type_t { text, line }; | 
|---|
| 34 |  | 
|---|
| 35 | struct widget { | 
|---|
| 36 | type_t type; | 
|---|
| 37 | union { | 
|---|
| 38 | text t; | 
|---|
| 39 | line l; | 
|---|
| 40 | }; | 
|---|
| 41 | }; | 
|---|
| 42 |  | 
|---|
| 43 | void draw(widget* w) { | 
|---|
| 44 | switch(w->type) { | 
|---|
| 45 | case text : draw(&w->text); break; | 
|---|
| 46 | case line : draw(&w->line); break; | 
|---|
| 47 | default : handle_error(); break; | 
|---|
| 48 | } | 
|---|
| 49 | } | 
|---|
| 50 |  | 
|---|
| 51 | While this code will work as implemented, adding any new widgets or any new | 
|---|
| 52 | widget behaviors requires changing existing code to add the desired | 
|---|
| 53 | functionality. To ease this maintenance effort required CFA introduces the | 
|---|
| 54 | concept of trait references. | 
|---|
| 55 |  | 
|---|
| 56 | Using trait references to implement the above gives the following : | 
|---|
| 57 |  | 
|---|
| 58 | trait drawable objects[10]; | 
|---|
| 59 | fill_objects(objects); | 
|---|
| 60 |  | 
|---|
| 61 | while(running) { | 
|---|
| 62 | for(drawable object : objects) { | 
|---|
| 63 | draw(object); | 
|---|
| 64 | } | 
|---|
| 65 | } | 
|---|
| 66 |  | 
|---|
| 67 | The keyword trait is optional (by the same rules as the struct keyword). This | 
|---|
| 68 | is not currently supported in CFA and the lookup is not possible to implement | 
|---|
| 69 | statically. Therefore we need to add a new feature to handle having dynamic | 
|---|
| 70 | lookups like this. | 
|---|
| 71 |  | 
|---|
| 72 | What we really want to do is express the fact that calling draw() on a trait | 
|---|
| 73 | reference should find the underlying type of the given parameter and find how | 
|---|
| 74 | it implements the routine, as in the example with the enumeration and union. | 
|---|
| 75 |  | 
|---|
| 76 | For instance specifying that the drawable trait reference looks up the type | 
|---|
| 77 | of the first argument to find the implementation would be : | 
|---|
| 78 |  | 
|---|
| 79 | trait drawable(otype T) { | 
|---|
| 80 | void draw(virtual T* ); | 
|---|
| 81 | }; | 
|---|
| 82 |  | 
|---|
| 83 | This could be implied in simple cases like this one (single parameter on the | 
|---|
| 84 | trait and single generic parameter on the function). In more complex cases it | 
|---|
| 85 | would have to be explicitly given, or a strong convention would have to be | 
|---|
| 86 | enforced (e.g. implementation of trait functions is always drawn from the | 
|---|
| 87 | first polymorphic parameter). | 
|---|
| 88 |  | 
|---|
| 89 | Once a function in a trait has been marked as virtual it defines a new | 
|---|
| 90 | function that takes in that trait's reference and then dynamically calls the | 
|---|
| 91 | underlying type implementation. Hence a trait reference becomes a kind of | 
|---|
| 92 | abstract type, cannot be directly instantiated but can still be used. | 
|---|
| 93 |  | 
|---|
| 94 | One of the limitations of this design is that it does not support double | 
|---|
| 95 | dispatching, which concretely means traits cannot have routines with more than | 
|---|
| 96 | one virtual parameter. The program must have a single table to look up the | 
|---|
| 97 | function on. Using trait references with traits with more than one parameter | 
|---|
| 98 | is also restricted, initially forbidden, see extension. | 
|---|
| 99 |  | 
|---|
| 100 | Extension: Multi-parameter Virtual Traits: | 
|---|
| 101 |  | 
|---|
| 102 | This implementation can be extended to traits with multiple parameters if | 
|---|
| 103 | one is called out as being the virtual trait. For example : | 
|---|
| 104 |  | 
|---|
| 105 | trait iterator(otype T, dtype Item) { | 
|---|
| 106 | Maybe(Item) next(virtual T *); | 
|---|
| 107 | } | 
|---|
| 108 |  | 
|---|
| 109 | iterator(int) generators[10]; | 
|---|
| 110 |  | 
|---|
| 111 | Which creates a collection of iterators that produce integers, regardless of | 
|---|
| 112 | how those iterators are implemented. This may require a note that this trait | 
|---|
| 113 | is virtual on T and not Item, but noting it on the functions may be enough. | 
|---|
| 114 |  | 
|---|
| 115 |  | 
|---|
| 116 | Strict Virtual Inheritance: | 
|---|
| 117 |  | 
|---|
| 118 | One powerful feature relaxed virtual does not support is the idea of down | 
|---|
| 119 | casting. Once something has been converted into a trait reference there is | 
|---|
| 120 | very little we can do to recover and of the type information, only the trait's | 
|---|
| 121 | required function implementations are kept. | 
|---|
| 122 |  | 
|---|
| 123 | To allow down casting strict virtual requires that all traits and structures | 
|---|
| 124 | involved be organized into a tree. Each trait or struct must have a unique | 
|---|
| 125 | position on this tree (no multiple inheritance). | 
|---|
| 126 |  | 
|---|
| 127 | This is declared as follows : | 
|---|
| 128 |  | 
|---|
| 129 | trait error(otype T) virtual { | 
|---|
| 130 | const char * msg(T *); | 
|---|
| 131 | } | 
|---|
| 132 |  | 
|---|
| 133 | trait io_error(otype T) virtual error { | 
|---|
| 134 | FILE * src(T *); | 
|---|
| 135 | } | 
|---|
| 136 |  | 
|---|
| 137 | struct eof_error virtual io_error { | 
|---|
| 138 | FILE * fd; | 
|---|
| 139 | }; | 
|---|
| 140 |  | 
|---|
| 141 | So the trait error is the head of a new tree and io_error is a child of it. | 
|---|
| 142 |  | 
|---|
| 143 | Also the parent trait is implicitly part of the assertions of the children, | 
|---|
| 144 | so all children implement the same operations as the parent. By the unique | 
|---|
| 145 | path down the tree, we can also uniquely order them so that a prefix of a | 
|---|
| 146 | child's vtable has the same format as its parent's. | 
|---|
| 147 |  | 
|---|
| 148 | This gives us an important extra feature, runtime checking of the parent-child | 
|---|
| 149 | relationship with virtual cast, where a pointer (and maybe a reference) to | 
|---|
| 150 | a virtual type can be cast to another virtual cast. However the cast is | 
|---|
| 151 | dynamicly check and only occurs if the underlying type is a child of the type | 
|---|
| 152 | being cast to. Otherwise null is returned. | 
|---|
| 153 |  | 
|---|
| 154 | (virtual TYPE)EXPR | 
|---|
| 155 |  | 
|---|
| 156 | As an extention, the TYPE may be ommitted if it can be determained from | 
|---|
| 157 | context, for instance if the cast occurs on the right hand side of an | 
|---|
| 158 | assignment. | 
|---|
| 159 |  | 
|---|
| 160 | Extension: Multiple Parents | 
|---|
| 161 |  | 
|---|
| 162 | Although each trait/struct must have a unique position on each tree, it could | 
|---|
| 163 | have positions on multiple trees. All this requires is the ability to give | 
|---|
| 164 | multiple parents, as here : | 
|---|
| 165 |  | 
|---|
| 166 | trait region(otype T) virtual drawable, collider; | 
|---|
| 167 |  | 
|---|
| 168 | The restriction being, the parents must come from different trees. This | 
|---|
| 169 | object (and all of its children) can be cast to either tree. This is handled | 
|---|
| 170 | by generating a separate vtable for each tree the structure is in. | 
|---|
| 171 |  | 
|---|
| 172 | Extension: Multi-parameter Strict Virtual | 
|---|
| 173 |  | 
|---|
| 174 | If a trait has multiple parameters then one must be called out to be the one | 
|---|
| 175 | we generate separate vtables for, as in : | 
|---|
| 176 |  | 
|---|
| 177 | trait example(otype T, otype U) virtual(T) ... | 
|---|
| 178 |  | 
|---|
| 179 | This can generate a separate vtable for each U for which all the T+U | 
|---|
| 180 | implementations are provided. These are then separate nodes in the tree (or | 
|---|
| 181 | the root of different trees) as if each was created individually. Providing a | 
|---|
| 182 | single unique instance of these nodes would be the most difficult aspect of | 
|---|
| 183 | this extension, possibly intractable, though with sufficient hoisting and | 
|---|
| 184 | link-once duplication it may be possible. | 
|---|
| 185 |  | 
|---|
| 186 | Example: | 
|---|
| 187 |  | 
|---|
| 188 | trait argument(otype T) virtual { | 
|---|
| 189 | char short_name(virtual T *); | 
|---|
| 190 | bool is_set(virtual T *); | 
|---|
| 191 | }; | 
|---|
| 192 |  | 
|---|
| 193 | trait value_argument(otype T, otype U) virtual(T) argument { | 
|---|
| 194 | U get_value(virtual T *); | 
|---|
| 195 | }; | 
|---|
| 196 |  | 
|---|
| 197 | Extension: Structural Inheritance | 
|---|
| 198 |  | 
|---|
| 199 | Currently traits must be the internal nodes and structs the leaf nodes. | 
|---|
| 200 | Structs could be made internal nodes as well, in which case the child structs | 
|---|
| 201 | would likely structurally inherit the fields of their parents. | 
|---|
| 202 |  | 
|---|
| 203 |  | 
|---|
| 204 | Storing the Virtual Lookup Table (vtable): | 
|---|
| 205 |  | 
|---|
| 206 | We have so far been silent on how the vtable is created, stored and accessed. | 
|---|
| 207 |  | 
|---|
| 208 | Creation happens at compile time. Function pointers are found by using the | 
|---|
| 209 | same best match rules as elsewhere (additional rules for defaults from the | 
|---|
| 210 | parent may or may not be required). For strict virtual this must happen at the | 
|---|
| 211 | global scope and forbidding static functions, to ensure that a single unique | 
|---|
| 212 | vtable is created. Similarly, there may have to be stricter matching rules | 
|---|
| 213 | for the functions that go into the vtable, possibly requiring an exact match. | 
|---|
| 214 | Relaxed virtual could relax both restrictions, if we allow different vtable | 
|---|
| 215 | at different conversion (struct to trait reference) sites. If it is allowed | 
|---|
| 216 | local functions being bound to a vtable could cause issues when they go out | 
|---|
| 217 | of scope, however this should follow the lifetime rules most C programs | 
|---|
| 218 | already follow implicitly. | 
|---|
| 219 |  | 
|---|
| 220 | Most vtables should be stored statically, the only exception being some of | 
|---|
| 221 | the relaxed vtables that could have local function pointers. These may be able | 
|---|
| 222 | to be stack allocated. All vtables should be immutable and require no manual | 
|---|
| 223 | cleanup. | 
|---|
| 224 |  | 
|---|
| 225 | Access has two main options: | 
|---|
| 226 |  | 
|---|
| 227 | The first is through the use of fat pointers, or a tuple of pointers. When the | 
|---|
| 228 | object is converted to a trait reference, the pointers to its vtables are | 
|---|
| 229 | stored along side it. | 
|---|
| 230 |  | 
|---|
| 231 | This allows for compatibility with existing structures (such as those imported | 
|---|
| 232 | from C) and is the default storage method unless a different one is given. | 
|---|
| 233 |  | 
|---|
| 234 | The other is by inlining the vtable pointer as "intrusive vtables". This adds | 
|---|
| 235 | a field to the structure to the vtable. The trait reference then has a single | 
|---|
| 236 | pointer to this field, the vtable includes an offset to find the beginning of | 
|---|
| 237 | the structure again. | 
|---|
| 238 |  | 
|---|
| 239 | This is used if you specify a vtable field in the structure. If given in the | 
|---|
| 240 | trait the vtable pointer in the trait reference can then become a single | 
|---|
| 241 | pointer to the vtable field and use that to recover the original object | 
|---|
| 242 | pointer as well as retrieve all operations. | 
|---|
| 243 |  | 
|---|
| 244 | trait drawable(otype T) { | 
|---|
| 245 | vtable drawable; | 
|---|
| 246 | }; | 
|---|
| 247 |  | 
|---|
| 248 | struct line { | 
|---|
| 249 | vtable drawable; | 
|---|
| 250 | vec2 start; | 
|---|
| 251 | vec2 end; | 
|---|
| 252 | }; | 
|---|
| 253 |  | 
|---|
| 254 | This inline code allows trait references to be converted to plain pointers | 
|---|
| 255 | (although they still must be called specially). The vtable field may just be | 
|---|
| 256 | an opaque block of memory or it may allow user access to the vtable. If so | 
|---|
| 257 | then there should be some way to retrieve the type of the vtable, which will be | 
|---|
| 258 | autogenerated and often unique. | 
|---|
| 259 |  | 
|---|
| 260 | It may be worth looking into a way to force the vtable pointer to be in a | 
|---|
| 261 | particular location, which would save the storage to store the offset and | 
|---|
| 262 | maybe the offset operation itself (offset = 0). However it may not be worth | 
|---|
| 263 | introducing a new language feature for. | 
|---|
| 264 | As of writing, exceptions actually use this system. | 
|---|
| 265 |  | 
|---|
| 266 |  | 
|---|
| 267 | Keyword Usage: | 
|---|
| 268 |  | 
|---|
| 269 | It may be desirable to add fewer new keywords than discussed in this proposal. | 
|---|
| 270 | It is possible that "virtual" could replace both "vtable" above with | 
|---|
| 271 | unambiguous contextual meaning. However, for purposes of clarity in the design | 
|---|
| 272 | discussion it is beneficial to keep the keywords for separate concepts distinct. | 
|---|
| 273 |  | 
|---|
| 274 |  | 
|---|
| 275 | Trait References and Operations: | 
|---|
| 276 |  | 
|---|
| 277 | sizeof(drawable) will return the size of the trait object itself. However : | 
|---|
| 278 |  | 
|---|
| 279 | line a_line; | 
|---|
| 280 | drawable widget = a_line; | 
|---|
| 281 | sizeof(widget); | 
|---|
| 282 |  | 
|---|
| 283 | Will instead return the sizeof the underlying object, although the trait must | 
|---|
| 284 | require that its implementation is sized for there to be a meaningful value | 
|---|
| 285 | to return. You may also get the size of the trait reference with | 
|---|
| 286 |  | 
|---|
| 287 | sizeof(&widget); | 
|---|
| 288 |  | 
|---|
| 289 | Calling free on a trait reference will free the memory for the object. It will | 
|---|
| 290 | leave the vtables alone, as those are (always?) statically allocated. | 
|---|
| 291 |  | 
|---|
| 292 |  | 
|---|
| 293 | Special Traits: | 
|---|
| 294 |  | 
|---|
| 295 | trait is_virtual_parent(dtype parent, dtype child) { ... }; | 
|---|
| 296 |  | 
|---|
| 297 | There are others but I believe this one to be the most important. The trait | 
|---|
| 298 | holds if the parent type is a strict virtual ancestor (any number of levels) | 
|---|
| 299 | of child. It will have to exist at least internally to check for upcasts and | 
|---|
| 300 | it can also be used to optimize virtual casts into upcasts. Or a null value or | 
|---|
| 301 | error if the cast would never succeed. Exporting it to a special trait allows | 
|---|
| 302 | users to express that requirement in their own polymorphic code. | 
|---|
| 303 |  | 
|---|
| 304 |  | 
|---|
| 305 | Implementation: | 
|---|
| 306 |  | 
|---|
| 307 | Before we can generate any of the nessasary code, the compiler has to get some | 
|---|
| 308 | additional information about the code that it currently does not collect. | 
|---|
| 309 |  | 
|---|
| 310 | First it should establish all child->parent links so that it may travel up the | 
|---|
| 311 | hierarchy to grab the nessasary information, and create the actual parent | 
|---|
| 312 | pointers in the strict virtual tables. It should also maintain the connections | 
|---|
| 313 | between the virtual type (structure or trait), the vtable type and the vtable | 
|---|
| 314 | instance (or default instance for relaxed virtual if multiple are allowed). To | 
|---|
| 315 | this end a sub-node should be created with the nessasary pointers. Traits and | 
|---|
| 316 | structs with virtual can create an instance and store all the nessasary data. | 
|---|
| 317 |  | 
|---|
| 318 | With the hierarchy in place it can generate the vtable type for each type, | 
|---|
| 319 | it will generally have a function pointer field for each type assertion in | 
|---|
| 320 | some consistant order. Strict virtual will also have a pointer to the parent's | 
|---|
| 321 | vtable and intrusive vtables will also have the offset to recover the original | 
|---|
| 322 | pointer. Sized types will also carry the size. | 
|---|
| 323 |  | 
|---|
| 324 | Wheither the vtable is intrusive or not should also be save so that the trait | 
|---|
| 325 | object/reference/pointer knows if it has to store 1 or 2 pointers. A wrapper | 
|---|
| 326 | function will have to be generated for each type assertion so that they may | 
|---|
| 327 | be called on the trait type, these can probably be inlined. | 
|---|
| 328 |  | 
|---|
| 329 | The virtual parameter will also have to be marked (implicately or explicately) | 
|---|
| 330 | until code generation so that the wrapper functions know where to go to get | 
|---|
| 331 | the vtable for the function look up. That could probably be added as a | 
|---|
| 332 | storageclass, although one that is only valid on type assertions. | 
|---|
| 333 |  | 
|---|
| 334 | The generated vtable will than have to have a vtable instance created and | 
|---|
| 335 | filled with all the approprate values. Stricter matching may have to be used | 
|---|
| 336 | to ensure that the functions used are stable. It will also have to use | 
|---|
| 337 | ".gnu.linkonce" or equilant to ensure only one copy exists in the final code | 
|---|
| 338 | base. | 
|---|