[63c2bca] | 1 | Proposal for virtual functionality
|
---|
| 2 |
|
---|
[d49bfa8] | 3 | There are two types of virtual inheritance in this proposal, relaxed
|
---|
[c72f9fd] | 4 | (implicit) and strict (explicit). Relaxed is the simpler case that uses the
|
---|
[d49bfa8] | 5 | existing trait system with the addition of trait references and vtables.
|
---|
[c72f9fd] | 6 | Strict adds some constraints and requires some additional notation but allows
|
---|
[d49bfa8] | 7 | for down-casting.
|
---|
| 8 |
|
---|
| 9 | Relaxed Virtual Inheritance:
|
---|
| 10 |
|
---|
[63c2bca] | 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 |
|
---|
[c72f9fd] | 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:
|
---|
[63c2bca] | 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;
|
---|
[5a0735ac] | 46 | case line : draw(&w->line); break;
|
---|
[63c2bca] | 47 | default : handle_error(); break;
|
---|
| 48 | }
|
---|
| 49 | }
|
---|
| 50 |
|
---|
[c72f9fd] | 51 | While this code will work as implemented, adding any new widgets or any new
|
---|
[d49bfa8] | 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.
|
---|
[63c2bca] | 55 |
|
---|
[d49bfa8] | 56 | Using trait references to implement the above gives the following :
|
---|
[63c2bca] | 57 |
|
---|
[d49bfa8] | 58 | trait drawable objects[10];
|
---|
[63c2bca] | 59 | fill_objects(objects);
|
---|
| 60 |
|
---|
| 61 | while(running) {
|
---|
[d49bfa8] | 62 | for(drawable object : objects) {
|
---|
[63c2bca] | 63 | draw(object);
|
---|
| 64 | }
|
---|
| 65 | }
|
---|
| 66 |
|
---|
[d49bfa8] | 67 | The keyword trait is optional (by the same rules as the struct keyword). This
|
---|
[e1e4aa9] | 68 | is not currently supported in CFA and the lookup is not possible to implement
|
---|
[d49bfa8] | 69 | statically. Therefore we need to add a new feature to handle having dynamic
|
---|
| 70 | lookups like this.
|
---|
[63c2bca] | 71 |
|
---|
[d49bfa8] | 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.
|
---|
[63c2bca] | 75 |
|
---|
[d49bfa8] | 76 | For instance specifying that the drawable trait reference looks up the type
|
---|
[e1e4aa9] | 77 | of the first argument to find the implementation would be :
|
---|
[63c2bca] | 78 |
|
---|
| 79 | trait drawable(otype T) {
|
---|
| 80 | void draw(virtual T* );
|
---|
| 81 | };
|
---|
| 82 |
|
---|
[d49bfa8] | 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
|
---|
[c72f9fd] | 85 | would have to be explicitly given, or a strong convention would have to be
|
---|
[e1e4aa9] | 86 | enforced (e.g. implementation of trait functions is always drawn from the
|
---|
[c72f9fd] | 87 | first polymorphic parameter).
|
---|
[63c2bca] | 88 |
|
---|
[50fb7df] | 89 | Instances of a trait are created by wrapping an existing instance of a type
|
---|
| 90 | that implements that trait. This wrapper includes all the function pointers
|
---|
| 91 | and other values required to preform the dynamic look-up. These are chosen by
|
---|
| 92 | the normal look-up rules at the point of abstraction.
|
---|
[63c2bca] | 93 |
|
---|
[d49bfa8] | 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
|
---|
[c72f9fd] | 98 | is also restricted, initially forbidden, see extension.
|
---|
[63c2bca] | 99 |
|
---|
[50fb7df] | 100 | Ownership of the underlying structure is also a bit of a trick. Considering
|
---|
| 101 | the use cases for trait object, it is probably best to have the underlying
|
---|
| 102 | object be heap allocated and owned by the trait object.
|
---|
| 103 |
|
---|
[c72f9fd] | 104 | Extension: Multi-parameter Virtual Traits:
|
---|
[63c2bca] | 105 |
|
---|
[e1e4aa9] | 106 | This implementation can be extended to traits with multiple parameters if
|
---|
[d49bfa8] | 107 | one is called out as being the virtual trait. For example :
|
---|
[63c2bca] | 108 |
|
---|
[d49bfa8] | 109 | trait iterator(otype T, dtype Item) {
|
---|
| 110 | Maybe(Item) next(virtual T *);
|
---|
| 111 | }
|
---|
[63c2bca] | 112 |
|
---|
[d49bfa8] | 113 | iterator(int) generators[10];
|
---|
[63c2bca] | 114 |
|
---|
[d49bfa8] | 115 | Which creates a collection of iterators that produce integers, regardless of
|
---|
| 116 | how those iterators are implemented. This may require a note that this trait
|
---|
| 117 | is virtual on T and not Item, but noting it on the functions may be enough.
|
---|
[da81e1d0] | 118 |
|
---|
[63c2bca] | 119 |
|
---|
[d49bfa8] | 120 | Strict Virtual Inheritance:
|
---|
[63c2bca] | 121 |
|
---|
[d49bfa8] | 122 | One powerful feature relaxed virtual does not support is the idea of down
|
---|
| 123 | casting. Once something has been converted into a trait reference there is
|
---|
| 124 | very little we can do to recover and of the type information, only the trait's
|
---|
| 125 | required function implementations are kept.
|
---|
[63c2bca] | 126 |
|
---|
[d49bfa8] | 127 | To allow down casting strict virtual requires that all traits and structures
|
---|
[e1e4aa9] | 128 | involved be organized into a tree. Each trait or struct must have a unique
|
---|
[d49bfa8] | 129 | position on this tree (no multiple inheritance).
|
---|
[63c2bca] | 130 |
|
---|
[d49bfa8] | 131 | This is declared as follows :
|
---|
[63c2bca] | 132 |
|
---|
[d49bfa8] | 133 | trait error(otype T) virtual {
|
---|
| 134 | const char * msg(T *);
|
---|
[63c2bca] | 135 | }
|
---|
| 136 |
|
---|
[d49bfa8] | 137 | trait io_error(otype T) virtual error {
|
---|
| 138 | FILE * src(T *);
|
---|
[63c2bca] | 139 | }
|
---|
| 140 |
|
---|
[d49bfa8] | 141 | struct eof_error virtual io_error {
|
---|
| 142 | FILE * fd;
|
---|
| 143 | };
|
---|
| 144 |
|
---|
| 145 | So the trait error is the head of a new tree and io_error is a child of it.
|
---|
| 146 |
|
---|
[c72f9fd] | 147 | Also the parent trait is implicitly part of the assertions of the children,
|
---|
[d49bfa8] | 148 | so all children implement the same operations as the parent. By the unique
|
---|
[c72f9fd] | 149 | path down the tree, we can also uniquely order them so that a prefix of a
|
---|
| 150 | child's vtable has the same format as its parent's.
|
---|
[d49bfa8] | 151 |
|
---|
| 152 | This gives us an important extra feature, runtime checking of the parent-child
|
---|
[c3b96677] | 153 | relationship with virtual cast, where a pointer (and maybe a reference) to
|
---|
| 154 | a virtual type can be cast to another virtual cast. However the cast is
|
---|
| 155 | dynamicly check and only occurs if the underlying type is a child of the type
|
---|
| 156 | being cast to. Otherwise null is returned.
|
---|
| 157 |
|
---|
| 158 | (virtual TYPE)EXPR
|
---|
| 159 |
|
---|
| 160 | As an extention, the TYPE may be ommitted if it can be determained from
|
---|
| 161 | context, for instance if the cast occurs on the right hand side of an
|
---|
| 162 | assignment.
|
---|
[d49bfa8] | 163 |
|
---|
[50fb7df] | 164 | Function look-up follows the same rules as relaxed (behavioural) inheritance.
|
---|
| 165 | Traits can be upcast and down cast without losing information unless the
|
---|
| 166 | trait is cast down to a structure. Here there are two options.
|
---|
| 167 |
|
---|
| 168 | Abstraction Time Binding: The more efficient and consistant with other parts
|
---|
| 169 | of CFA. Only the trait types use dynamic look-up, if converveted back into a
|
---|
| 170 | structure the normal static look-up rules find the function at compile time.
|
---|
| 171 | Casting down to a structure type can then result in the loss of a set of
|
---|
| 172 | bindings.
|
---|
| 173 | Construction Time Binding: For more consistant handling of the virtual
|
---|
| 174 | structs, they are always considered wrapped. Functions are bound to the
|
---|
| 175 | instance the moment it is constructed and remain unchanged throughout its
|
---|
| 176 | lifetime, so down casting does not lose information.
|
---|
| 177 |
|
---|
| 178 | (We will have to decide between one of these two.)
|
---|
| 179 |
|
---|
[e1e4aa9] | 180 | Extension: Multiple Parents
|
---|
[d49bfa8] | 181 |
|
---|
| 182 | Although each trait/struct must have a unique position on each tree, it could
|
---|
| 183 | have positions on multiple trees. All this requires is the ability to give
|
---|
| 184 | multiple parents, as here :
|
---|
| 185 |
|
---|
| 186 | trait region(otype T) virtual drawable, collider;
|
---|
[63c2bca] | 187 |
|
---|
[d49bfa8] | 188 | The restriction being, the parents must come from different trees. This
|
---|
| 189 | object (and all of its children) can be cast to either tree. This is handled
|
---|
[e1e4aa9] | 190 | by generating a separate vtable for each tree the structure is in.
|
---|
[63c2bca] | 191 |
|
---|
[e1e4aa9] | 192 | Extension: Multi-parameter Strict Virtual
|
---|
[d49bfa8] | 193 |
|
---|
[e1e4aa9] | 194 | If a trait has multiple parameters then one must be called out to be the one
|
---|
| 195 | we generate separate vtables for, as in :
|
---|
[d49bfa8] | 196 |
|
---|
| 197 | trait example(otype T, otype U) virtual(T) ...
|
---|
| 198 |
|
---|
[e1e4aa9] | 199 | This can generate a separate vtable for each U for which all the T+U
|
---|
| 200 | implementations are provided. These are then separate nodes in the tree (or
|
---|
| 201 | the root of different trees) as if each was created individually. Providing a
|
---|
| 202 | single unique instance of these nodes would be the most difficult aspect of
|
---|
| 203 | this extension, possibly intractable, though with sufficient hoisting and
|
---|
[c72f9fd] | 204 | link-once duplication it may be possible.
|
---|
[d49bfa8] | 205 |
|
---|
| 206 | Example:
|
---|
| 207 |
|
---|
| 208 | trait argument(otype T) virtual {
|
---|
| 209 | char short_name(virtual T *);
|
---|
| 210 | bool is_set(virtual T *);
|
---|
[63c2bca] | 211 | };
|
---|
| 212 |
|
---|
[d49bfa8] | 213 | trait value_argument(otype T, otype U) virtual(T) argument {
|
---|
| 214 | U get_value(virtual T *);
|
---|
| 215 | };
|
---|
| 216 |
|
---|
[e1e4aa9] | 217 | Extension: Structural Inheritance
|
---|
[d49bfa8] | 218 |
|
---|
| 219 | Currently traits must be the internal nodes and structs the leaf nodes.
|
---|
[e1e4aa9] | 220 | Structs could be made internal nodes as well, in which case the child structs
|
---|
[c72f9fd] | 221 | would likely structurally inherit the fields of their parents.
|
---|
[d49bfa8] | 222 |
|
---|
| 223 |
|
---|
| 224 | Storing the Virtual Lookup Table (vtable):
|
---|
| 225 |
|
---|
[c72f9fd] | 226 | We have so far been silent on how the vtable is created, stored and accessed.
|
---|
[50fb7df] | 227 | The vtables for the two types might be handled slightly differently and then
|
---|
| 228 | there is also the hierarchy data for virtual casts.
|
---|
| 229 |
|
---|
| 230 | The hierarchy data is simple conceptually. A single (exactly one copy) pointer
|
---|
| 231 | for each type can act as the identity for it. The value of the pointer is
|
---|
| 232 | its parent type, with the root pointer being NULL. Additional meta-data
|
---|
| 233 | can accompany the parent pointer, such as a string name or the vtable fields.
|
---|
| 234 |
|
---|
| 235 | They types of each vtable can be constructed from the definitions of the
|
---|
| 236 | traits (or internal nodes). The stand alone/base vtable is the same for both
|
---|
| 237 | kinds of inheritance. It may be argumented differently however (include parent
|
---|
| 238 | /this pointer in hierachal inheritance).
|
---|
| 239 |
|
---|
| 240 | Creation of the actual vtable is tricky. For classical single implementation
|
---|
| 241 | semantics we would assemble the functions and create one vtable at compile
|
---|
| 242 | time. However, not only does this not give CFA-like behaviour, it is
|
---|
| 243 | impossible generally because types can satify assertions in different ways at
|
---|
| 244 | different times and stop satifying them. A special set of harder rules could
|
---|
| 245 | be used, instead we have decided to try creating multiple vtables for each
|
---|
| 246 | type. The different vtables will all implement the same type but not always
|
---|
| 247 | in the same way.
|
---|
| 248 |
|
---|
| 249 | Storage has some issues from creation. If the contents of every vtable could
|
---|
| 250 | be determained at compile time they could all be created and stored
|
---|
| 251 | statically. However since thunks can be constructed on the stack and become
|
---|
| 252 | the best match, that isn't always possible. Those will have to be stored in
|
---|
| 253 | dynamic memory. Which means that all vtables must be stored dynamically or
|
---|
| 254 | there must be a way to determain which ones to free when the trait object is
|
---|
| 255 | destroyed.
|
---|
[d49bfa8] | 256 |
|
---|
| 257 | Access has two main options:
|
---|
| 258 |
|
---|
| 259 | The first is through the use of fat pointers, or a tuple of pointers. When the
|
---|
| 260 | object is converted to a trait reference, the pointers to its vtables are
|
---|
| 261 | stored along side it.
|
---|
| 262 |
|
---|
[e1e4aa9] | 263 | This allows for compatibility with existing structures (such as those imported
|
---|
[d49bfa8] | 264 | from C) and is the default storage method unless a different one is given.
|
---|
| 265 |
|
---|
| 266 | The other is by inlining the vtable pointer as "intrusive vtables". This adds
|
---|
[e1e4aa9] | 267 | a field to the structure to the vtable. The trait reference then has a single
|
---|
[d49bfa8] | 268 | pointer to this field, the vtable includes an offset to find the beginning of
|
---|
| 269 | the structure again.
|
---|
| 270 |
|
---|
| 271 | This is used if you specify a vtable field in the structure. If given in the
|
---|
| 272 | trait the vtable pointer in the trait reference can then become a single
|
---|
| 273 | pointer to the vtable field and use that to recover the original object
|
---|
[e1e4aa9] | 274 | pointer as well as retrieve all operations.
|
---|
[d49bfa8] | 275 |
|
---|
| 276 | trait drawable(otype T) {
|
---|
| 277 | vtable drawable;
|
---|
| 278 | };
|
---|
| 279 |
|
---|
| 280 | struct line {
|
---|
| 281 | vtable drawable;
|
---|
| 282 | vec2 start;
|
---|
| 283 | vec2 end;
|
---|
| 284 | };
|
---|
| 285 |
|
---|
| 286 | This inline code allows trait references to be converted to plain pointers
|
---|
| 287 | (although they still must be called specially). The vtable field may just be
|
---|
| 288 | an opaque block of memory or it may allow user access to the vtable. If so
|
---|
[e1e4aa9] | 289 | then there should be some way to retrieve the type of the vtable, which will be
|
---|
[d49bfa8] | 290 | autogenerated and often unique.
|
---|
| 291 |
|
---|
[c3b96677] | 292 | It may be worth looking into a way to force the vtable pointer to be in a
|
---|
| 293 | particular location, which would save the storage to store the offset and
|
---|
| 294 | maybe the offset operation itself (offset = 0). However it may not be worth
|
---|
| 295 | introducing a new language feature for.
|
---|
| 296 | As of writing, exceptions actually use this system.
|
---|
| 297 |
|
---|
[d49bfa8] | 298 |
|
---|
| 299 | Keyword Usage:
|
---|
| 300 |
|
---|
| 301 | It may be desirable to add fewer new keywords than discussed in this proposal.
|
---|
| 302 | It is possible that "virtual" could replace both "vtable" above with
|
---|
| 303 | unambiguous contextual meaning. However, for purposes of clarity in the design
|
---|
| 304 | discussion it is beneficial to keep the keywords for separate concepts distinct.
|
---|
| 305 |
|
---|
| 306 |
|
---|
| 307 | Trait References and Operations:
|
---|
[63c2bca] | 308 |
|
---|
[d49bfa8] | 309 | sizeof(drawable) will return the size of the trait object itself. However :
|
---|
[63c2bca] | 310 |
|
---|
[d49bfa8] | 311 | line a_line;
|
---|
| 312 | drawable widget = a_line;
|
---|
| 313 | sizeof(widget);
|
---|
[da81e1d0] | 314 |
|
---|
[d49bfa8] | 315 | Will instead return the sizeof the underlying object, although the trait must
|
---|
| 316 | require that its implementation is sized for there to be a meaningful value
|
---|
| 317 | to return. You may also get the size of the trait reference with
|
---|
[da81e1d0] | 318 |
|
---|
[d49bfa8] | 319 | sizeof(&widget);
|
---|
[da81e1d0] | 320 |
|
---|
[d49bfa8] | 321 | Calling free on a trait reference will free the memory for the object. It will
|
---|
| 322 | leave the vtables alone, as those are (always?) statically allocated.
|
---|
[c3b96677] | 323 |
|
---|
| 324 |
|
---|
| 325 | Special Traits:
|
---|
| 326 |
|
---|
| 327 | trait is_virtual_parent(dtype parent, dtype child) { ... };
|
---|
| 328 |
|
---|
| 329 | There are others but I believe this one to be the most important. The trait
|
---|
| 330 | holds if the parent type is a strict virtual ancestor (any number of levels)
|
---|
| 331 | of child. It will have to exist at least internally to check for upcasts and
|
---|
| 332 | it can also be used to optimize virtual casts into upcasts. Or a null value or
|
---|
| 333 | error if the cast would never succeed. Exporting it to a special trait allows
|
---|
| 334 | users to express that requirement in their own polymorphic code.
|
---|
| 335 |
|
---|
| 336 |
|
---|
| 337 | Implementation:
|
---|
| 338 |
|
---|
| 339 | Before we can generate any of the nessasary code, the compiler has to get some
|
---|
| 340 | additional information about the code that it currently does not collect.
|
---|
| 341 |
|
---|
| 342 | First it should establish all child->parent links so that it may travel up the
|
---|
| 343 | hierarchy to grab the nessasary information, and create the actual parent
|
---|
| 344 | pointers in the strict virtual tables. It should also maintain the connections
|
---|
| 345 | between the virtual type (structure or trait), the vtable type and the vtable
|
---|
| 346 | instance (or default instance for relaxed virtual if multiple are allowed). To
|
---|
| 347 | this end a sub-node should be created with the nessasary pointers. Traits and
|
---|
| 348 | structs with virtual can create an instance and store all the nessasary data.
|
---|
| 349 |
|
---|
| 350 | With the hierarchy in place it can generate the vtable type for each type,
|
---|
| 351 | it will generally have a function pointer field for each type assertion in
|
---|
| 352 | some consistant order. Strict virtual will also have a pointer to the parent's
|
---|
| 353 | vtable and intrusive vtables will also have the offset to recover the original
|
---|
| 354 | pointer. Sized types will also carry the size.
|
---|
| 355 |
|
---|
| 356 | Wheither the vtable is intrusive or not should also be save so that the trait
|
---|
| 357 | object/reference/pointer knows if it has to store 1 or 2 pointers. A wrapper
|
---|
| 358 | function will have to be generated for each type assertion so that they may
|
---|
| 359 | be called on the trait type, these can probably be inlined.
|
---|
| 360 |
|
---|
| 361 | The virtual parameter will also have to be marked (implicately or explicately)
|
---|
| 362 | until code generation so that the wrapper functions know where to go to get
|
---|
| 363 | the vtable for the function look up. That could probably be added as a
|
---|
| 364 | storageclass, although one that is only valid on type assertions.
|
---|
| 365 |
|
---|
| 366 | The generated vtable will than have to have a vtable instance created and
|
---|
| 367 | filled with all the approprate values. Stricter matching may have to be used
|
---|
| 368 | to ensure that the functions used are stable. It will also have to use
|
---|
| 369 | ".gnu.linkonce" or equilant to ensure only one copy exists in the final code
|
---|
| 370 | base.
|
---|