| [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 | 
 | 
|---|