Changes in / [957453d:8b28a52]


Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • doc/proposals/virtual.txt

    r957453d r8b28a52  
    11Proposal for virtual functionality
    2 
    3 There are two types of virtual inheritance in this proposal, relaxed
    4 (implicate) and strict (explicate). Relaxed is the simpler case that uses the
    5 existing trait system with the addition of trait references and vtables.
    6 Strict adds some constrants and requires some additional notation but allows
    7 for down-casting.
    8 
    9 Relaxed Virtual Inheritance:
    102
    113Imagine the following code :
     
    2820void draw(line*);
    2921
    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 :
     22While all the members of this simple UI support drawing creating a UI that easily
     23supports both these UI requires some tedious boiler-plate code :
    3224
    3325enum type_t { text, line };
     
    4941}
    5042
    51 While this code will work as indented, 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.
     43While this code will work as indented, adding any new widgets or any new widget behaviors
     44requires changing existing code to add the desired functionality. To ease this maintenance
     45effort required CFA introduces the concept of dynamic types, in a manner similar to C++.
    5546
    56 Using trait references to implement the above gives the following :
     47A simple usage of dynamic type with the previous example would look like :
    5748
    58 trait drawable objects[10];
     49drawable* objects[10];
    5950fill_objects(objects);
    6051
    6152while(running) {
    62       for(drawable object : objects) {
     53      for(drawable* object : objects) {
    6354            draw(object);
    6455      }
    6556}
    6657
    67 The keyword trait is optional (by the same rules as the struct keyword). This
    68 is not currently suported 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.
     58However, this is not currently do-able in the current CFA and furthermore is not
     59possible to implement statically. Therefore we need to add a new feature to handle
     60having dynamic types like this (That is types that are found dynamically not types
     61that change dynamically).
    7162
    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.
     63C++ uses inheritance and virtual functions to find the
     64desired type dynamically. CFA takes inspiration from this solution.
    7565
    76 For instance specifying that the drawable trait reference looks up the type
    77 of the first argument to find the implemenation would be :
     66What we really want to do is express the fact that calling draw() on a object
     67should find the dynamic type of the parameter before calling the routine, much like the
     68hand written example given above. We can express this by adding the virtual keyword on
     69the parameter of the constraints on our trait:
    7870
    7971trait drawable(otype T) {
     
    8173};
    8274
    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 explicately given, or a strong convention would have to be
    86 enforced.
     75This expresses the idea that drawable is similar to an abstract base class in C++ and
     76also gives meaning to trying to take a pointer of drawable. That is anything that can
     77be cast to a drawable pointer has the necessary information to call the draw routine on
     78that type. Before that drawable was only a abstract type while now it also points to a
     79piece of storage which specify which behavior the object will have at run time.
    8780
    88 Once a function in a trait has been marked as virtual it defines a new
    89 function that takes in that trait's reference and then dynamically calls the
    90 underlying type implemenation. Hence a trait reference becomes a kind of
    91 abstract type, cannot be directly instantiated but can still be used.
     81This storage needs to be allocate somewhere. C++ just adds an invisible pointer at
     82the beginning of the struct but we can do something more explicit for users, actually
     83have a visible special field :
    9284
    93 One of the limitations of this design is that it does not support double
    94 dispatching, which concretely means traits cannot have routines with more than
    95 one virtual parameter. The program must have a single table to look up the
    96 function on. Using trait references with traits with more than one parameter
    97 is also restricted, inistially forbidden, see extention.
     85struct text {
     86      char* text;
     87      vtable drawable;
     88};
    9889
    99 Extention: Multi-parameter Virtual Traits:
     90struct line{
     91      vtable drawable;
     92      vec2 start;
     93      vec2 end;
     94};
    10095
    101 This implementation can be extented to traits with multiple parameters if
    102 one is called out as being the virtual trait. For example :
     96With these semantics, adding a "vtable drawable" means that text pointers and line pointers are now
     97convertible to drawable pointers. This conversion will not necessarily be a type only change however, indeed,
     98the drawable pointer will point to the field "vtable drawable" not the head of the struct. However, since all
     99the types are known at compile time, converting pointers becomes a simple offset operations.
    103100
    104 trait iterator(otype T, dtype Item) {
    105         Maybe(Item) next(virtual T *);
     101The vtable field contains a pointer to a vtable which contains all the information needed for the caller
     102to find the function pointer of the desired behavior.
     103
     104One of the limitations of this design is that it does not support double dispatching, which
     105concretely means traits cannot have routines with more than one virtual parameter. This design
     106would have many ambiguities if it did support multiple virtual parameter. A futher limitation is
     107that traits over more than one type cannot have vtables meaningfully defined for them, as the
     108particular vtable to use would be a function of the other type(s) the trait is defined over.
     109
     110It is worth noting that the function pointers in these vtables are bound at object construction, rather than
     111function call-site, as in Cforall's existing polymorphic functions. As such, it is possible that two objects
     112with the same static type would have a different vtable (consider what happens if draw(line*) is overridden
     113between the definitions of two line objects). Given that the virtual drawable* erases static types though,
     114this should not be confusing in practice. A more distressing possibility is that of creating an object that
     115outlives the scope of one of the functions in its vtable. This is certainly a possible bug, but it is of a
     116type that C programmers are familiar with, and should be able to avoid by the usual methods.
     117
     118Extensibility.
     119
     120One of the obvious critics of this implementation is that it lacks extensibility for classes
     121that cannot be modified (ex: Linux C headers). However this solution can be extended to
     122allow more extensibility by adding "Fat pointers".
     123
     124Indeed, users could already "solve" this issue by writing their own fat pointers as such:
     125
     126trait MyContext(otype T) {
     127      void* get_stack(virtual T*)
     128};
     129
     130void* get_stack(ucontext_t *context);
     131
     132struct fat_ucontext_t {
     133      vtable MyContext;
     134      ucontext_t *context;
    106135}
    107136
    108 iterator(int) generators[10];
    109 
    110 Which creates a collection of iterators that produce integers, regardless of
    111 how those iterators are implemented. This may require a note that this trait
    112 is virtual on T and not Item, but noting it on the functions may be enough.
    113 
    114 
    115 Strict Virtual Inheritance:
    116 
    117 One powerful feature relaxed virtual does not support is the idea of down
    118 casting. Once something has been converted into a trait reference there is
    119 very little we can do to recover and of the type information, only the trait's
    120 required function implementations are kept.
    121 
    122 To allow down casting strict virtual requires that all traits and structures
    123 involved be orginized into a tree. Each trait or struct must have a unique
    124 position on this tree (no multiple inheritance).
    125 
    126 This is declared as follows :
    127 
    128 trait error(otype T) virtual {
    129         const char * msg(T *);
     137//Tedious forwarding routine
     138void* get_stack(fat_ucontext_t *ptr) {
     139      return get_stack(ptr->context);
    130140}
    131141
    132 trait io_error(otype T) virtual error {
    133         FILE * src(T *);
    134 }
     142However, users would have to write all the virtual methods they want to override and make
     143them all simply forward to the existing method that takes the corresponding POCO(Plain Old C Object).
    135144
    136 struct eof_error virtual io_error {
    137         FILE * fd;
     145The alternative we propose is to use language level fat pointers :
     146
     147trait MyContext(otype T) {
     148      void* get_stack(virtual T*)
    138149};
    139150
    140 So the trait error is the head of a new tree and io_error is a child of it.
     151void* get_stack(ucontext_t *context);
    141152
    142 Also the parent trait is implicately part of the assertions of the children,
    143 so all children implement the same operations as the parent. By the unique
    144 path down the tree, we can also uniquely order them so that prefixs of a
    145 child's vtable has the same format as the parent's.
     153//The type vptr(ucontext_t) all
     154vptr(ucontext_t) context;
    146155
    147 This gives us an important extra feature, runtime checking of the parent-child
    148 relationship with a C++ dynamic_cast like operation. Allowing checked
    149 convertions from trait references to more particular references, which works
    150 if the underlying type is, or is a child of, the new trait type.
     156These behave exactly as the previous example but all the forwarding routines are automatically generated.
    151157
    152 Extention: Multiple Parents
     158Bikeshedding.
    153159
    154 Although each trait/struct must have a unique position on each tree, it could
    155 have positions on multiple trees. All this requires is the ability to give
    156 multiple parents, as here :
     160It may be desirable to add fewer new keywords than discussed in this proposal; it is possible that "virtual"
     161could replace both "vtable" and "vptr" above with unambiguous contextual meaning. However, for purposes of
     162clarity in the design discussion it is beneficial to keep the keywords for separate concepts distinct.
    157163
    158 trait region(otype T) virtual drawable, collider;
    159 
    160 The restriction being, the parents must come from different trees. This
    161 object (and all of its children) can be cast to either tree. This is handled
    162 by generating a seperate vtable for each tree the structure is in.
    163 
    164 Extention: Multi-parameter Strict Virtual
    165 
    166 If a trait has multiple parameters than one must be called out to be the one
    167 we generate seperate vtables for, as in :
    168 
    169 trait example(otype T, otype U) virtual(T) ...
    170 
    171 This can generate a seperate vtable for each U for which all the T+U
    172 implementations are provided. These are then seperate nodes in the tree (or
    173 the root of different trees) as if each was created individually.
    174 
    175 Example:
    176 
    177 trait argument(otype T) virtual {
    178         char short_name(virtual T *);
    179         bool is_set(virtual T *);
    180 };
    181 
    182 trait value_argument(otype T, otype U) virtual(T) argument {
    183         U get_value(virtual T *);
    184 };
    185 
    186 Extention: Structural Inheritance
    187 
    188 Currently traits must be the internal nodes and structs the leaf nodes.
    189 Structs could be made internal nodes as well, and might specify structural
    190 inheritance.
    191 
    192 
    193 Storing the Virtual Lookup Table (vtable):
    194 
    195 We have so fare been silent on how the vtable is created, stored and accessed.
    196 
    197 Creation happens at compile time. Function pointers are found by using the
    198 same best match rules as elsewhere (additional rules for defaults from the
    199 parent may or may not be reqired). For strict virtual this must happen at the
    200 global scope and forbiding static functions, to ensure that a single unique
    201 vtable is created. Similarly, there may have to be stricter matching rules
    202 for the functions that go into the vtable, possibly requiring an exact match.
    203 Relaxed virtual could relax both restrictions, if we allow different vtable
    204 at different conversion (struct to trait reference) sites. If it is allowed
    205 local functions being bound to a vtable could cause issues when they go out
    206 of scope, however this should follow the lifetime rules most C programs
    207 already follow implicately.
    208 
    209 Most vtables should be stored statically, the only exception being some of
    210 the relaxed vtables that could have local function pointers. These may be able
    211 to be stack allocated. All vtables should be immutable and require no manual
    212 cleanup.
    213 
    214 Access has two main options:
    215 
    216 The first is through the use of fat pointers, or a tuple of pointers. When the
    217 object is converted to a trait reference, the pointers to its vtables are
    218 stored along side it.
    219 
    220 This allows for compatability with existing structures (such as those imported
    221 from C) and is the default storage method unless a different one is given.
    222 
    223 The other is by inlining the vtable pointer as "intrusive vtables". This adds
    224 a field to the structure to the vtable. The trait reference than has a single
    225 pointer to this field, the vtable includes an offset to find the beginning of
    226 the structure again.
    227 
    228 This is used if you specify a vtable field in the structure. If given in the
    229 trait the vtable pointer in the trait reference can then become a single
    230 pointer to the vtable field and use that to recover the original object
    231 pointer as well as retrive all operations.
    232 
    233 trait drawable(otype T) {
    234         vtable drawable;
    235 };
    236 
    237 struct line {
    238         vtable drawable;
    239         vec2 start;
    240         vec2 end;
    241 };
    242 
    243 This inline code allows trait references to be converted to plain pointers
    244 (although they still must be called specially). The vtable field may just be
    245 an opaque block of memory or it may allow user access to the vtable. If so
    246 than there should be some way to retrive the type of the vtable, which will be
    247 autogenerated and often unique.
    248 
    249 
    250 Keyword Usage:
    251 
    252 It may be desirable to add fewer new keywords than discussed in this proposal.
    253 It is possible that "virtual" could replace both "vtable" above with
    254 unambiguous contextual meaning. However, for purposes of clarity in the design
    255 discussion it is beneficial to keep the keywords for separate concepts distinct.
    256 
    257 
    258 Trait References and Operations:
    259 
    260 sizeof(drawable) will return the size of the trait object itself. However :
    261 
    262 line a_line;
    263 drawable widget = a_line;
    264 sizeof(widget);
    265 
    266 Will instead return the sizeof the underlying object, although the trait must
    267 require that its implementation is sized for there to be a meaningful value
    268 to return. You may also get the size of the trait reference with
    269 
    270 sizeof(&widget);
    271 
    272 Calling free on a trait reference will free the memory for the object. It will
    273 leave the vtables alone, as those are (always?) statically allocated.
Note: See TracChangeset for help on using the changeset viewer.