| [63c2bca] | 1 | Proposal for virtual functionality | 
|---|
|  | 2 |  | 
|---|
|  | 3 | Imagine the following code : | 
|---|
|  | 4 |  | 
|---|
|  | 5 | trait drawable(otype T) { | 
|---|
|  | 6 | void draw(T* ); | 
|---|
|  | 7 | }; | 
|---|
|  | 8 |  | 
|---|
|  | 9 | struct text { | 
|---|
|  | 10 | char* text; | 
|---|
|  | 11 | }; | 
|---|
|  | 12 |  | 
|---|
|  | 13 | void draw(text*); | 
|---|
|  | 14 |  | 
|---|
|  | 15 | struct line{ | 
|---|
|  | 16 | vec2 start; | 
|---|
|  | 17 | vec2 end; | 
|---|
|  | 18 | }; | 
|---|
|  | 19 |  | 
|---|
|  | 20 | void draw(line*); | 
|---|
|  | 21 |  | 
|---|
|  | 22 | While all the members of this simple UI support drawing creating a UI that easily | 
|---|
|  | 23 | supports both these UI requires some tedious boiler-plate code : | 
|---|
|  | 24 |  | 
|---|
|  | 25 | enum type_t { text, line }; | 
|---|
|  | 26 |  | 
|---|
|  | 27 | struct widget { | 
|---|
|  | 28 | type_t type; | 
|---|
|  | 29 | union { | 
|---|
|  | 30 | text t; | 
|---|
|  | 31 | line l; | 
|---|
|  | 32 | }; | 
|---|
|  | 33 | }; | 
|---|
|  | 34 |  | 
|---|
|  | 35 | void draw(widget* w) { | 
|---|
|  | 36 | switch(w->type) { | 
|---|
|  | 37 | case text : draw(&w->text); break; | 
|---|
| [5a0735ac] | 38 | case line : draw(&w->line); break; | 
|---|
| [63c2bca] | 39 | default : handle_error(); break; | 
|---|
|  | 40 | } | 
|---|
|  | 41 | } | 
|---|
|  | 42 |  | 
|---|
|  | 43 | While this code will work as indented, adding any new widgets or any new widget behaviors | 
|---|
|  | 44 | requires changing existing code to add the desired functionality. To ease this maintenance | 
|---|
|  | 45 | effort required CFA introduces the concept of dynamic types, in a manner similar to C++. | 
|---|
|  | 46 |  | 
|---|
|  | 47 | A simple usage of dynamic type with the previous example would look like : | 
|---|
|  | 48 |  | 
|---|
|  | 49 | drawable* objects[10]; | 
|---|
|  | 50 | fill_objects(objects); | 
|---|
|  | 51 |  | 
|---|
|  | 52 | while(running) { | 
|---|
|  | 53 | for(drawable* object : objects) { | 
|---|
|  | 54 | draw(object); | 
|---|
|  | 55 | } | 
|---|
|  | 56 | } | 
|---|
|  | 57 |  | 
|---|
|  | 58 | However, this is not currently do-able in the current CFA and furthermore is not | 
|---|
|  | 59 | possible to implement statically. Therefore we need to add a new feature to handle | 
|---|
|  | 60 | having dynamic types like this (That is types that are found dynamically not types | 
|---|
|  | 61 | that change dynamically). | 
|---|
|  | 62 |  | 
|---|
|  | 63 | C++ uses inheritance and virtual functions to find the | 
|---|
|  | 64 | desired type dynamically. CFA takes inspiration from this solution. | 
|---|
|  | 65 |  | 
|---|
|  | 66 | What we really want to do is express the fact that calling draw() on a object | 
|---|
|  | 67 | should find the dynamic type of the parameter before calling the routine, much like the | 
|---|
|  | 68 | hand written example given above. We can express this by adding the virtual keyword on | 
|---|
|  | 69 | the parameter of the constraints on our trait: | 
|---|
|  | 70 |  | 
|---|
|  | 71 | trait drawable(otype T) { | 
|---|
|  | 72 | void draw(virtual T* ); | 
|---|
|  | 73 | }; | 
|---|
|  | 74 |  | 
|---|
|  | 75 | This expresses the idea that drawable is similar to an abstract base class in C++ and | 
|---|
|  | 76 | also gives meaning to trying to take a pointer of drawable. That is anything that can | 
|---|
|  | 77 | be cast to a drawable pointer has the necessary information to call the draw routine on | 
|---|
|  | 78 | that type. Before that drawable was only a abstract type while now it also points to a | 
|---|
|  | 79 | piece of storage which specify which behavior the object will have at run time. | 
|---|
|  | 80 |  | 
|---|
|  | 81 | This storage needs to be allocate somewhere. C++ just adds an invisible pointer at | 
|---|
|  | 82 | the beginning of the struct but we can do something more explicit for users, actually | 
|---|
|  | 83 | have a visible special field : | 
|---|
|  | 84 |  | 
|---|
|  | 85 | struct text { | 
|---|
|  | 86 | char* text; | 
|---|
|  | 87 | vtable drawable; | 
|---|
|  | 88 | }; | 
|---|
|  | 89 |  | 
|---|
|  | 90 | struct line{ | 
|---|
|  | 91 | vtable drawable; | 
|---|
|  | 92 | vec2 start; | 
|---|
|  | 93 | vec2 end; | 
|---|
|  | 94 | }; | 
|---|
|  | 95 |  | 
|---|
|  | 96 | With these semantics, adding a "vtable drawable" means that text pointers and line pointers are now | 
|---|
|  | 97 | convertible to drawable pointers. This conversion will not necessarily be a type only change however, indeed, | 
|---|
|  | 98 | the drawable pointer will point to the field "vtable drawable" not the head of the struct. However, since all | 
|---|
|  | 99 | the types are known at compile time, converting pointers becomes a simple offset operations. | 
|---|
|  | 100 |  | 
|---|
|  | 101 | The vtable field contains a pointer to a vtable which contains all the information needed for the caller | 
|---|
|  | 102 | to find the function pointer of the desired behavior. | 
|---|
|  | 103 |  | 
|---|
|  | 104 | One of the limitations of this design is that it does not support double dispatching, which | 
|---|
|  | 105 | concretely means traits cannot have routines with more than one virtual parameter. This design | 
|---|
| [ba5131d] | 106 | would have many ambiguities if it did support multiple virtual parameter. A futher limitation is | 
|---|
|  | 107 | that traits over more than one type cannot have vtables meaningfully defined for them, as the | 
|---|
|  | 108 | particular vtable to use would be a function of the other type(s) the trait is defined over. | 
|---|
| [63c2bca] | 109 |  | 
|---|
| [da81e1d0] | 110 | It is worth noting that the function pointers in these vtables are bound at object construction, rather than | 
|---|
|  | 111 | function call-site, as in Cforall's existing polymorphic functions. As such, it is possible that two objects | 
|---|
|  | 112 | with the same static type would have a different vtable (consider what happens if draw(line*) is overridden | 
|---|
|  | 113 | between the definitions of two line objects). Given that the virtual drawable* erases static types though, | 
|---|
|  | 114 | this should not be confusing in practice. A more distressing possibility is that of creating an object that | 
|---|
|  | 115 | outlives the scope of one of the functions in its vtable. This is certainly a possible bug, but it is of a | 
|---|
|  | 116 | type that C programmers are familiar with, and should be able to avoid by the usual methods. | 
|---|
|  | 117 |  | 
|---|
| [63c2bca] | 118 | Extensibility. | 
|---|
|  | 119 |  | 
|---|
|  | 120 | One of the obvious critics of this implementation is that it lacks extensibility for classes | 
|---|
|  | 121 | that cannot be modified (ex: Linux C headers). However this solution can be extended to | 
|---|
|  | 122 | allow more extensibility by adding "Fat pointers". | 
|---|
|  | 123 |  | 
|---|
|  | 124 | Indeed, users could already "solve" this issue by writing their own fat pointers as such: | 
|---|
|  | 125 |  | 
|---|
|  | 126 | trait MyContext(otype T) { | 
|---|
|  | 127 | void* get_stack(virtual T*) | 
|---|
|  | 128 | }; | 
|---|
|  | 129 |  | 
|---|
|  | 130 | void* get_stack(ucontext_t *context); | 
|---|
|  | 131 |  | 
|---|
|  | 132 | struct fat_ucontext_t { | 
|---|
|  | 133 | vtable MyContext; | 
|---|
|  | 134 | ucontext_t *context; | 
|---|
|  | 135 | } | 
|---|
|  | 136 |  | 
|---|
|  | 137 | //Tedious forwarding routine | 
|---|
|  | 138 | void* get_stack(fat_ucontext_t *ptr) { | 
|---|
|  | 139 | return get_stack(ptr->context); | 
|---|
|  | 140 | } | 
|---|
|  | 141 |  | 
|---|
|  | 142 | However, users would have to write all the virtual methods they want to override and make | 
|---|
|  | 143 | them all simply forward to the existing method that takes the corresponding POCO(Plain Old C Object). | 
|---|
|  | 144 |  | 
|---|
|  | 145 | The alternative we propose is to use language level fat pointers : | 
|---|
|  | 146 |  | 
|---|
|  | 147 | trait MyContext(otype T) { | 
|---|
|  | 148 | void* get_stack(virtual T*) | 
|---|
|  | 149 | }; | 
|---|
|  | 150 |  | 
|---|
|  | 151 | void* get_stack(ucontext_t *context); | 
|---|
|  | 152 |  | 
|---|
|  | 153 | //The type vptr(ucontext_t) all | 
|---|
|  | 154 | vptr(ucontext_t) context; | 
|---|
|  | 155 |  | 
|---|
|  | 156 | These behave exactly as the previous example but all the forwarding routines are automatically generated. | 
|---|
| [da81e1d0] | 157 |  | 
|---|
|  | 158 | Bikeshedding. | 
|---|
|  | 159 |  | 
|---|
|  | 160 | It may be desirable to add fewer new keywords than discussed in this proposal; it is possible that "virtual" | 
|---|
|  | 161 | could replace both "vtable" and "vptr" above with unambiguous contextual meaning. However, for purposes of | 
|---|
|  | 162 | clarity in the design discussion it is beneficial to keep the keywords for separate concepts distinct. | 
|---|
|  | 163 |  | 
|---|