source: doc/theses/fangren_yu_MMath/performance.tex @ 262a864

Last change on this file since 262a864 was 9861ef2, checked in by Fangren Yu <f37yu@…>, 12 days ago

add description of closed trait types

  • Property mode set to 100644
File size: 12.0 KB
Line 
1\chapter{Future Work}
2
3These are some feature requests related to type system enhancement that come up during the development of \CFA language and library, but have not been implemented yet. A lot of the current implementations have to work around the limits of the language features and sometimes lead to inefficiency.
4
5\section{Closed trait types}
6
7\CFA as it currently is does not have any closed types, as new functions can be added at any time. It is also possible to locally declare a function,\footnote{Local functions are not a standard feature in C but supported by mainstream C compilers such as gcc, and allowed in \CFA too.} or a function pointer variable to make a type satisfy a certain trait temporarily and be used as such in this limited scope. However, the lack of closed types in such a "duck typing" scheme proposes two problems. For library implementors, it is common to not want the defined set of operations to be overwritten and cause the behavior of polymorphic invocations to change. For the compiler, it means caching and reusing the result of resolution is not reliable as newly introduced declarations can participate in assertion resolution, making a previously invalid expression valid, or the other way around by introducing ambiguity in assertions. Sometimes those interfaces are fairly complicated, for example the I/O library traits \textbf{istream} and \textbf{ostream} each has over 20 operations. Without the ability to store and reuse assertion resolution results, each time the compiler encounters an I/O operation in the source code (mainly the pipe operator \textbf{?|?} used to represent stream operations in \CFA) it has to resolve the same set of assertions again, causing a lot of repetitive work. Previous experiments have shown that the I/O assertions often account for over half of the number of assertions resolved in a \CFA translation unit. Introducing a way to eliminate the need of doing such repetitive assertion resolutions that are very unlikely to change by new overloads can therefore provide significant improvement to the performance of the compiler.
8
9The output stream trait in \CFA looks like this:
10
11\begin{cfa}
12forall( ostype & )
13trait basic_ostream {
14        // private
15        bool sepPrt$\$$( ostype & );                                                    // get separator state (on/off)
16   
17        void sepReset$\$$( ostype & );                                                  // set separator state to default state
18        void sepReset$\$$( ostype &, bool );                                    // set separator and default state
19        const char * sepGetCur$\$$( ostype & );                         // get current separator string
20        void sepSetCur$\$$( ostype &, const char [] );                  // set current separator string
21        bool getNL$\$$( ostype & );                                                     // get newline
22        bool setNL$\$$( ostype &, bool );                                               // set newline
23        bool getANL$\$$( ostype & );                                                    // get auto newline (on/off)
24        bool setANL$\$$( ostype &, bool );                                              // set auto newline (on/off), and return previous state
25        bool getPrt$\$$( ostype & );                                                    // get fmt called in output cascade
26        bool setPrt$\$$( ostype &, bool );                                              // set fmt called in output cascade
27        // public
28        void nlOn( ostype & );                                                          // turn auto-newline state on
29        void nlOff( ostype & );                                                         // turn auto-newline state off
30
31        void sep( ostype & );                                                           // turn separator state on
32        void nosep( ostype & );                                                         // turn separator state off
33        bool sepOn( ostype & );                                                         // set default state to on, and return previous state
34        bool sepOff( ostype & );                                                        // set default state to off, and return previous state
35        const char * sepGet( ostype & );                                        // get separator string
36        void sepSet( ostype &, const char [] );                         // set separator to string (15 character maximum)
37        const char * sepGetTuple( ostype & );                           // get tuple separator string
38        void sepSetTuple( ostype &, const char [] );            // set tuple separator to string (15 character maximum)
39
40        void ends( ostype & );                                                          // end of output statement
41        int fmt( ostype &, const char format[], ... ) __attribute__(( format(printf, 2, 3) ));
42}; // basic_ostream
43
44\end{cfa}
45
46and the overloaded output operator is declared as
47
48\begin{cfa}
49forall( ostype & | basic_ostream( ostype ) ) ostype & ?|?( ostype &, int ); // also defined for all other basic types
50\end{cfa}
51
52The \CFA polymorphic calling convention makes the output operator take all the trait functions above as implicit arguments, and they are repeatedly resolved every time an output operator is called. This creates both compile-time and run-time overheads. \CFA compilation takes the most amount of time in resolving assertions, and it is not uncommon to see in the current codebase and test suite that the majority of time spent in compiling a program is due to a series of I/O statements, while resolving those assertions for I/O operators could have been a once and done job. Repeatedly pushing tens of arguments onto the stack to call these operators at run-time can also have some impact on performance, although in the I/O case it is not as significant, since these operations involve system calls which are already slow.
53
54An addition of closed trait type will make a \CFA trait behave similarly to a Haskell type class and require an explicit instance declaration as well. The syntax for closed trait is not settled yet, but it may look something like the following:
55
56\begin{cfa}
57    forall(ostype &)
58    closed trait basic_ostream{
59        // all the above declarations
60    };
61
62    struct ofstream {
63        // ...
64    };
65
66    // implementation of trait functions for ofstream
67
68    __cfa_trait_instance(ofstream, basic_ostream);
69\end{cfa}
70
71At the point of instance declaration, all the required trait functions must be declared in scope, and they are inferred and specialized in the usual way. The list of inferred and specialized functions will then be saved into an array of function pointers, and polymorphic functions using the closed trait type can simply take the instance pointer, instead of a list of implicit parameters.
72
73\begin{cfa}
74    // original declaration
75    forall( ostype & | basic_ostream( ostype ) ) ostype & ?|?( ostype & os, int i);
76
77    // translated code with closed trait
78    void* ?|?(void* os, int i, void* _instance_basic_ostream);
79    // polymorphic pointer and reference types are erased
80
81    // call site
82    sout | 42;
83   
84    // generated code
85    ?|?(sout, 42, __cfa_instance_ofstream__basic_ostream /* generated by __cfa_trait_instance decl */);
86\end{cfa}
87
88Introducing closed trait types also comes with the additional benefit of the ability to achieve better encapsulation. In the above @basic_ostream@ example, the functions ending with dollar sign \$ are meant to be private (internal to the library) and the client code should not be able to see or call those functions directly. This is impossible with the current trait and assertion system, because the satisfying declarations must be visible at call site for the polymorphic call to resolve. With closed traits and instance types, it will be possible to forward declare a closed trait (which would not even make sense otherwise) and the instance pointer of a type implementing the trait without exposing any details to the client, since all the information necessary to call the trait functions are now captured in a single pointer to a table of functions built at compile time, and the type of that pointer is made opaque to the client.
89
90
91
92
93\section{Associated Types}
94
95The analysis presented in section 4.3 shows that if we mandate that all type parameters have to be bound before assertion resolution, the complexity of resolving assertions become much lower as every assertion parameter can be resolved independently. By fully utilizing information from higher up the expression tree for return value overloading, most of the type bindings can be resolved. However there are certain scenarios where we would like to have some intermediate types to be involved in certain operations, that are neither input nor output types.
96
97\CFA standard library provides a few polymorphic container types similar to those found in \CC standard template library. Naturally, we would like to have a harmonized iterator interface for different container types. The feature is still under development and has not been finalized, but for the purpose of this discussion, we will be using a similar approach to the \CC standard iterator contract. The equivalent type signatures can be given in \CFA trait as:
98
99\begin{cfa}
100    forall (Container, Iter, Elem) trait iterable {
101        Iter begin(Container);
102        Iter end(Container);
103        Elem& *?(Iter);
104        Iter ++?(Iter);
105        bool ?==?(Iter,Iter);
106    }
107\end{cfa}
108
109These are the exact operators required in \CC for the for-loop comprehension
110@for (Elem & e: container)@. The problem with this trait declaration is that the iterator type @Iter@ has to be explicitly given in the trait parameter list, but is intended to be deduced from the @begin@ and @end@ operations on the container type; and the same can be said for the element type. The iterable trait is meant to describe a property for the container type but involves two additional types, one for the iterator type and one for the element type. If we were to disallow unbound type parameters in assertions without adding any features to the type system, we will not be able to describe this iterable property in \CFA.
111
112To solve this problem, I propose adding associate type declarations in addition to the existing (free) @forall@ type parameters. In the body of a trait declaration, new types can be introduced as the return type of an expression involving the type parameters. Following the above example, the iterator contract could be rewritten to
113\begin{cfa}
114    forall (Container) trait iterable {
115        type Iter = begin(Container);
116        type Elem& = *?(Iter);
117        Iter end(Container);
118        Iter ++?(Iter);
119        bool ?==?(Iter,Iter);
120    }
121\end{cfa}
122
123This matches conceptually better that the iterable trait is about one type (the container) rather than about three types. The iterator type and element type can be viewed as properties of the container types, not independent type parameters.
124
125There remains one design decision to be made, that is whether the expressions that define associated types need to be uniquely resolved. If the associated type does not appear in parameter or return types, having such requirement seems to be reasonable, because the expression cost does not consider any conversions that occur in assertion parameter matching, which essentially means having multiple ways to resolve an associated type always results in ambiguity. A more interesting case would be that the associated type also appears in the return type, where both resolving the return type overload based on context and resolving the assertion that defines the associate type can help, and for the whole expression to resolve, they must agree on a common result. Moss gave the following example to illustrate \CFA assertion system's expressiveness that could be viewed as having an associated type as return, and can potentially vary based on the context:
126
127\begin{cfa}
128    forall(Ptr, Elem) trait pointer_like {
129        Elem& *?(Ptr); // Ptr can be dereferenced to Elem
130    };
131    struct list {
132        int value;
133        list* next; // may omit struct on type names
134    };
135    typedef list* list_iterator;
136    int& *?(list_iterator it) {
137        return it->value;
138    }
139\end{cfa}
140
141Note that the type @list*@ satisfies both @pointer_like(list*, int)@ and @pointer_like(list*, list)@ (the latter by the built-in pointer dereference operator) and the expression @*it@ could be either a @struct list@ or an @int@. Requiring associated types to be unique would make the @pointer_like@ trait inapplicable to @list*@ here, which is not desirable. I have not attempted to implement associated types in \CFA compiler, but based on the above discussions, one option could be to make associated type resolution and return type overloading coexist: when the associated type appears in the returns, we deduce it from the context and then verify the trait with ordinary assertion resolution; when it does not appear in the returns, we instead require the type to be uniquely determined by the expression that defines the associated type.
142
143\section{User-defined conversions}
Note: See TracBrowser for help on using the repository browser.