Changes in doc/papers/general/Paper.tex [28bc8c8:87555b7]
- File:
-
- 1 edited
-
doc/papers/general/Paper.tex (modified) (48 diffs)
Legend:
- Unmodified
- Added
- Removed
-
doc/papers/general/Paper.tex
r28bc8c8 r87555b7 267 267 int m = max( max, -max ); $\C{// uses (3) and (1) twice, by matching return type}$ 268 268 \end{cfa} 269 269 270 \CFA maximizes the ability to reuse names to aggressively address the naming problem. 270 271 In some cases, hundreds of names can be reduced to tens, resulting in a significant cognitive reduction. … … 285 286 286 287 287 \subsection{\texorpdfstring{\ LstKeywordStyle{forall} Functions}{forall Functions}}288 \subsection{\texorpdfstring{\protect\lstinline{forall} Functions}{forall Functions}} 288 289 \label{sec:poly-fns} 289 290 … … 435 436 One approach is to write bespoke data-structures for each context in which they are needed. 436 437 While this approach is flexible and supports integration with the C type-checker and tooling, it is also tedious and error-prone, especially for more complex data structures. 437 A second approach is to use @void *@ --based polymorphism, \eg the C standard-library functions @bsearch@ and @qsort@, which allow reuse of code with common functionality.438 A second approach is to use @void *@ based polymorphism, \eg the C standard-library functions @bsearch@ and @qsort@, which allow reuse of code with common functionality. 438 439 However, basing all polymorphism on @void *@ eliminates the type-checker's ability to ensure that argument types are properly matched, often requiring a number of extra function parameters, pointer indirection, and dynamic allocation that is not otherwise needed. 439 440 A third approach to generic code is to use preprocessor macros, which does allow the generated code to be both generic and type-checked, but errors may be difficult to interpret. … … 507 508 The offset arrays are statically generated where possible. 508 509 If a dynamic generic-type is declared to be passed or returned by value from a polymorphic function, the translator can safely assume the generic type is complete (\ie has a known layout) at any call-site, and the offset array is passed from the caller; 509 if the generic type is concrete at the call site, the elements of this offset array can even be statically generated using the C @offsetof@ macro. 510 As an example, the body of the second @value@ function is implemented like this:511 \begin{cfa} 512 _assign_T( _retval, p + _offsetof_pair[1]); $\C{// return *p.second}$513 \end{cfa} 514 @_assign_T@ is passed in as an implicit parameter from @otype T@, and takes two @T *@ (@void*@ in the generated code), a destination and a source; @_retval@ is the pointer to a caller-allocated buffer for the return value, the usual \CFA method to handle dynamically-sized return types.510 if the generic type is concrete at the call site, the elements of this offset array can even be statically generated using the C @offsetof@ macro. 511 As an example, the body of the second @value@ function is implemented as: 512 \begin{cfa} 513 _assign_T( _retval, p + _offsetof_pair[1] ); $\C{// return *p.second}$ 514 \end{cfa} 515 @_assign_T@ is passed in as an implicit parameter from @otype T@, and takes two @T *@ (@void *@ in the generated code), a destination and a source; @_retval@ is the pointer to a caller-allocated buffer for the return value, the usual \CFA method to handle dynamically-sized return types. 515 516 @_offsetof_pair@ is the offset array passed into @value@; this array is generated at the call site as: 516 517 \begin{cfa} 517 size_t _offsetof_pair[] = { offsetof( _pair_conc0, first), offsetof(_pair_conc0, second) }518 size_t _offsetof_pair[] = { offsetof( _pair_conc0, first ), offsetof( _pair_conc0, second ) } 518 519 \end{cfa} 519 520 … … 539 540 The most important such pattern is using @forall(dtype T) T *@ as a type-checked replacement for @void *@, \eg creating a lexicographic comparison for pairs of pointers used by @bsearch@ or @qsort@: 540 541 \begin{cfa} 541 forall( dtype T) int lexcmp( pair( T *, T * ) * a, pair( T *, T * ) * b, int (* cmp)( T *, T * ) ) {542 forall( dtype T ) int lexcmp( pair( T *, T * ) * a, pair( T *, T * ) * b, int (* cmp)( T *, T * ) ) { 542 543 return cmp( a->first, b->first ) ? : cmp( a->second, b->second ); 543 544 } 544 545 \end{cfa} 545 Since @pair( T *, T * )@ is a concrete type, there are no implicit parameters passed to @lexcmp@, so the generated code is identical to a function written in standard C using @void *@, yet the \CFA version is type-checked to ensure the fields of both pairs and the arguments to the comparison function match in type.546 Since @pair( T *, T * )@ is a concrete type, there are no implicit parameters passed to @lexcmp@, so the generated code is identical to a function written in standard C using @void *@, yet the \CFA version is type-checked to ensure the fields of both pairs and the arguments to the comparison function match in type. 546 547 547 548 Another useful pattern enabled by reused dtype-static type instantiations is zero-cost \newterm{tag-structures}. 548 549 Sometimes information is only used for type-checking and can be omitted at runtime, \eg: 549 550 \begin{cfa} 550 forall( dtype Unit) struct scalar { unsigned long value; };551 forall( dtype Unit ) struct scalar { unsigned long value; }; 551 552 struct metres {}; 552 553 struct litres {}; 553 554 554 forall( dtype U) scalar(U) ?+?( scalar(U) a, scalar(U) b ) {555 forall( dtype U) scalar(U) ?+?( scalar(U) a, scalar(U) b ) { 555 556 return (scalar(U)){ a.value + b.value }; 556 557 } … … 807 808 Due to the implicit flattening and structuring conversions involved in argument passing, @otype@ and @dtype@ parameters are restricted to matching only with non-tuple types, \eg: 808 809 \begin{cfa} 809 forall( otype T, dtype U) void f( T x, U * y );810 forall( otype T, dtype U ) void f( T x, U * y ); 810 811 f( [5, "hello"] ); 811 812 \end{cfa} … … 814 815 For example, a plus operator can be written to add two triples together. 815 816 \begin{cfa} 816 forall( otype T | { T ?+?( T, T ); }) [T, T, T] ?+?( [T, T, T] x, [T, T, T] y ) {817 forall( otype T | { T ?+?( T, T ); } ) [T, T, T] ?+?( [T, T, T] x, [T, T, T] y ) { 817 818 return [x.0 + y.0, x.1 + y.1, x.2 + y.2]; 818 819 } … … 825 826 \begin{cfa} 826 827 int f( [int, double], double ); 827 forall( otype T, otype U | { T f( T, U, U ); }) void g( T, U );828 forall( otype T, otype U | { T f( T, U, U ); } ) void g( T, U ); 828 829 g( 5, 10.21 ); 829 830 \end{cfa} 830 831 Hence, function parameter and return lists are flattened for the purposes of type unification allowing the example to pass expression resolution. 831 832 This relaxation is possible by extending the thunk scheme described by Bilson~\cite{Bilson03}. 832 Whenever a candidate's parameter structure does not exactly match the formal parameter's structure, a thunk is generated to specialize calls to the actual function:833 \begin{cfa}834 int _thunk( int _p0, double _p1, double _p2 ) { return f( [_p0, _p1], _p2 ); }835 \end{cfa}836 so the thunk provides flattening and structuring conversions to inferred functions, improving the compatibility of tuples and polymorphism.837 These thunks take advantage of gcc C nested-functions to produce closures that have the usual function-pointer signature WHAT DOES THIS MEAN???.833 % Whenever a candidate's parameter structure does not exactly match the formal parameter's structure, a thunk is generated to specialize calls to the actual function: 834 % \begin{cfa} 835 % int _thunk( int _p0, double _p1, double _p2 ) { return f( [_p0, _p1], _p2 ); } 836 % \end{cfa} 837 % so the thunk provides flattening and structuring conversions to inferred functions, improving the compatibility of tuples and polymorphism. 838 % These thunks are generated locally using gcc nested-functions, rather hositing them to the external scope, so they can easily access local state. 838 839 839 840 … … 852 853 \begin{cfa} 853 854 int sum$\(_0\)$() { return 0; } 854 forall( ttype Params | { int sum( Params ); } ) int sum$\(_1\)$( int x, Params rest ) {855 forall( ttype Params | { int sum( Params ); } ) int sum$\(_1\)$( int x, Params rest ) { 855 856 return x + sum( rest ); 856 857 } … … 865 866 \begin{cfa} 866 867 int sum( int x, int y ) { return x + y; } 867 forall( ttype Params | { int sum( int, Params ); } ) int sum( int x, int y, Params rest ) {868 forall( ttype Params | { int sum( int, Params ); } ) int sum( int x, int y, Params rest ) { 868 869 return sum( x + y, rest ); 869 870 } … … 871 872 One more step permits the summation of any summable type with all arguments of the same type: 872 873 \begin{cfa} 873 trait summable( otype T) {874 trait summable( otype T ) { 874 875 T ?+?( T, T ); 875 876 }; 876 forall( otype R | summable( R ) ) R sum( R x, R y ) {877 forall( otype R | summable( R ) ) R sum( R x, R y ) { 877 878 return x + y; 878 879 } 879 forall( otype R, ttype Params | summable(R) | { R sum(R, Params); } ) R sum(R x, R y, Params rest) {880 forall( otype R, ttype Params | summable(R) | { R sum(R, Params); } ) R sum(R x, R y, Params rest) { 880 881 return sum( x + y, rest ); 881 882 } … … 888 889 \begin{cfa} 889 890 struct S { int x, y; }; 890 forall( otype T, ttype Params | { void print(T); void print(Params); }) void print(T arg, Params rest) {891 forall( otype T, ttype Params | { void print(T); void print(Params); } ) void print(T arg, Params rest) { 891 892 print(arg); print(rest); 892 893 } … … 927 928 is transformed into: 928 929 \begin{cfa} 929 forall( dtype T0, dtype T1 | sized(T0) | sized(T1)) struct _tuple2 {930 forall( dtype T0, dtype T1 | sized(T0) | sized(T1) ) struct _tuple2 { 930 931 T0 field_0; $\C{// generated before the first 2-tuple}$ 931 932 T1 field_1; … … 933 934 _tuple2(int, int) f() { 934 935 _tuple2(double, double) x; 935 forall( dtype T0, dtype T1, dtype T2 | sized(T0) | sized(T1) | sized(T2)) struct _tuple3 {936 forall( dtype T0, dtype T1, dtype T2 | sized(T0) | sized(T1) | sized(T2) ) struct _tuple3 { 936 937 T0 field_0; $\C{// generated before the first 3-tuple}$ 937 938 T1 field_1; … … 941 942 } 942 943 \end{cfa} 943 \begin{sloppypar} 944 {\sloppy 944 945 Tuple expressions are then simply converted directly into compound literals, \eg @[5, 'x', 1.24]@ becomes @(_tuple3(int, char, double)){ 5, 'x', 1.24 }@. 945 \ end{sloppypar}946 \par}% 946 947 947 948 \begin{comment} … … 1004 1005 \section{Control Structures} 1005 1006 1006 \CFA identifies inconsistent, problematic, and missing control structures in C, and extends, modifies, and adds tocontrol structures to increase functionality and safety.1007 1008 1009 \subsection{\texorpdfstring{\ LstKeywordStyle{if} Statement}{if Statement}}1007 \CFA identifies inconsistent, problematic, and missing control structures in C, and extends, modifies, and adds control structures to increase functionality and safety. 1008 1009 1010 \subsection{\texorpdfstring{\protect\lstinline{if} Statement}{if Statement}} 1010 1011 1011 1012 The @if@ expression allows declarations, similar to @for@ declaration expression: … … 1019 1020 1020 1021 1021 \subsection{\texorpdfstring{\ LstKeywordStyle{switch} Statement}{switch Statement}}1022 \subsection{\texorpdfstring{\protect\lstinline{switch} Statement}{switch Statement}} 1022 1023 1023 1024 There are a number of deficiencies with the C @switch@ statements: enumerating @case@ lists, placement of @case@ clauses, scope of the switch body, and fall through between case clauses. … … 1039 1040 \lstMakeShortInline@% 1040 1041 \end{cquote} 1041 for a contiguous list:\footnote{gcc provides the same mechanism with awkward syntax, \lstinline@2 ... 42@, where spaces are required around the ellipse.}1042 for a contiguous list:\footnote{gcc has the same mechanism but awkward syntax, \lstinline@2 ...42@, because a space is required after a number, otherwise the period is a decimal point.} 1042 1043 \begin{cquote} 1043 1044 \lstDeleteShortInline@% … … 1090 1091 C @switch@ provides multiple entry points into the statement body, but once an entry point is selected, control continues across \emph{all} @case@ clauses until the end of the @switch@ body, called \newterm{fall through}; 1091 1092 @case@ clauses are made disjoint by the @break@ statement. 1092 While the ability to fall through \emph{is} a useful form of control flow, it does not match well with programmer intuition, resulting in many errors from missing @break@ statements. 1093 For backwards compatibility, \CFA provides a \emph{new} control structure, @choose@, which mimics @switch@, but reverses the meaning of fall through (see Figure~\ref{f:ChooseSwitchStatements}). 1094 1095 Collectively, these enhancements reduce programmer burden and increase readability and safety. 1093 While fall through \emph{is} a useful form of control flow, it does not match well with programmer intuition, resulting in errors from missing @break@ statements. 1094 For backwards compatibility, \CFA provides a \emph{new} control structure, @choose@, which mimics @switch@, but reverses the meaning of fall through (see Figure~\ref{f:ChooseSwitchStatements}), similar to Go. 1096 1095 1097 1096 \begin{figure} … … 1137 1136 \end{figure} 1138 1137 1139 \begin{comment} 1140 Forgotten @break@ statements at the end of @switch@ cases are a persistent sort of programmer error in C, and the @break@ statements themselves introduce visual clutter and an un-C-like keyword-based block delimiter. 1141 \CFA addresses this error by introducing a @choose@ statement, which works identically to a @switch@ except that its default end-of-case behaviour is to break rather than to fall through for all non-empty cases. 1142 Since empty cases like @case 7:@ in @case 7: case 11:@ still have fall-through semantics and explicit @break@ is still allowed at the end of a @choose@ case, many idiomatic uses of @switch@ in standard C can be converted to @choose@ statements by simply changing the keyword. 1143 Where fall-through is desired for a non-empty case, it can be specified with the new @fallthrough@ statement, making @choose@ equivalently powerful to @switch@, but more concise in the common case where most non-empty cases end with a @break@ statement, as in the example below: 1144 1145 \begin{cfa} 1146 choose( i ) { 1147 case 2: 1148 printf("even "); 1149 fallthrough; 1150 case 3: case 5: case 7: 1151 printf("small prime\n"); 1152 case 4,6,8,9: 1153 printf("small composite\n"); 1154 case 13~19: 1155 printf("teen\n"); 1156 default: 1157 printf("something else\n"); 1158 } 1159 \end{cfa} 1160 \end{comment} 1161 1162 1163 \subsection{\texorpdfstring{Labelled \LstKeywordStyle{continue} / \LstKeywordStyle{break}}{Labelled continue / break}} 1138 Finally, @fallthrough@ may appear in contexts other than terminating a @case@ clause, and have an explicit transfer label allowing separate cases but common final-code for a set of cases: 1139 \begin{cquote} 1140 \lstDeleteShortInline@% 1141 \begin{tabular}{@{}l@{\hspace{2\parindentlnth}}l@{}} 1142 \multicolumn{1}{c@{\hspace{2\parindentlnth}}}{\textbf{non-terminator}} & \multicolumn{1}{c}{\textbf{target label}} \\ 1143 \begin{cfa} 1144 choose ( ... ) { 1145 case 3: 1146 if ( ... ) { 1147 ... `fallthrough;` // goto case 4 1148 } else { 1149 ... 1150 } 1151 // implicit break 1152 case 4: 1153 \end{cfa} 1154 & 1155 \begin{cfa} 1156 choose ( ... ) { 1157 case 3: 1158 ... `fallthrough common;` 1159 case 4: 1160 ... `fallthrough common;` 1161 common: 1162 ... // common code for cases 3 and 4 1163 // implicit break 1164 case 4: 1165 \end{cfa} 1166 \end{tabular} 1167 \lstMakeShortInline@% 1168 \end{cquote} 1169 The target label may be case @default@. 1170 1171 Collectively, these control-structure enhancements reduce programmer burden and increase readability and safety. 1172 1173 1174 \subsection{\texorpdfstring{Labelled \protect\lstinline{continue} / \protect\lstinline{break}}{Labelled continue / break}} 1164 1175 1165 1176 While C provides @continue@ and @break@ statements for altering control flow, both are restricted to one level of nesting for a particular control structure. … … 1270 1281 \subsection{Exception Handling} 1271 1282 1272 The following framework for \CFA exception handling is in place, excluding some run -time type-information and dynamic casts.1283 The following framework for \CFA exception handling is in place, excluding some runtime type-information and virtual functions. 1273 1284 \CFA provides two forms of exception handling: \newterm{fix-up} and \newterm{recovery} (see Figure~\ref{f:CFAExceptionHandling})~\cite{Buhr92b,Buhr00a}. 1274 1285 Both mechanisms provide dynamic call to a handler using dynamic name-lookup, where fix-up has dynamic return and recovery has static return from the handler. … … 1340 1351 catch ( IOError err ) { ... } $\C{// handler error from other files}$ 1341 1352 \end{cfa} 1342 where the throw inserts the failing file-handle in the I/O exception.1353 where the throw inserts the failing file-handle into the I/O exception. 1343 1354 Conditional catch cannot be trivially mimicked by other mechanisms because once an exception is caught, handler clauses in that @try@ statement are no longer eligible.. 1344 1355 … … 1348 1359 resume( $\emph{alternate-stack}$ ) 1349 1360 \end{cfa} 1350 These overloads of @resume@ raise the specified exception or the currently propagating exception (reresume) at another \CFA coroutine or task ~\cite{Delisle18}.\footnote{\CFA coroutine and concurrency features are discussed in a separately submitted paper.}1351 Nonlocal raise is restricted to resumption to provide the exception handler the greatest flexibility because processing the exception does not unwind its stack, allowing it to continue after the handle returns.1352 1353 To facilitate nonlocal exception, \CFA provides dynamic enabling and disabling of nonlocal exception-propagation.1361 These overloads of @resume@ raise the specified exception or the currently propagating exception (reresume) at another \CFA coroutine or task\footnote{\CFA coroutine and concurrency features are discussed in a separately submitted paper.}~\cite{Delisle18}. 1362 Nonlocal raise is restricted to resumption to provide the exception handler the greatest flexibility because processing the exception does not unwind its stack, allowing it to continue after the handler returns. 1363 1364 To facilitate nonlocal raise, \CFA provides dynamic enabling and disabling of nonlocal exception-propagation. 1354 1365 The constructs for controlling propagation of nonlocal exceptions are the @enable@ and the @disable@ blocks: 1355 1366 \begin{cquote} … … 1358 1369 \begin{cfa} 1359 1370 enable $\emph{exception-type-list}$ { 1360 // allow non-local r esumption1371 // allow non-local raise 1361 1372 } 1362 1373 \end{cfa} … … 1364 1375 \begin{cfa} 1365 1376 disable $\emph{exception-type-list}$ { 1366 // disallow non-local r esumption1377 // disallow non-local raise 1367 1378 } 1368 1379 \end{cfa} … … 1375 1386 Coroutines and tasks start with non-local exceptions disabled, allowing handlers to be put in place, before non-local exceptions are explicitly enabled. 1376 1387 \begin{cfa} 1377 void main( mytask & c ) { 1378 try { 1379 enable { $\C{// now allow non-local exception delivery}$ 1388 void main( mytask & t ) { $\C{// thread starts here}$ 1389 // non-local exceptions disabled 1390 try { $\C{// establish handles for non-local exceptions}$ 1391 enable { $\C{// allow non-local exception delivery}$ 1380 1392 // task body 1381 1393 } 1382 // appropriate catchResume/catch 1394 // appropriate catchResume/catch handlers 1383 1395 } 1384 1396 } … … 1400 1412 1401 1413 1402 \subsection{\texorpdfstring{\ LstKeywordStyle{with} Clause / Statement}{with Clause / Statement}}1414 \subsection{\texorpdfstring{\protect\lstinline{with} Clause / Statement}{with Clause / Statement}} 1403 1415 \label{s:WithClauseStatement} 1404 1416 … … 1800 1812 int & r = *new( int ); 1801 1813 ... $\C{// non-null reference}$ 1802 delete &r; 1814 delete &r; $\C{// unmanaged (programmer) memory-management}$ 1803 1815 r += 1; $\C{// undefined reference}$ 1804 1816 \end{cfa} … … 1947 1959 Constructor calls seamlessly integrate with existing C initialization syntax, providing a simple and familiar syntax to C programmers and allowing constructor calls to be inserted into legacy C code with minimal code changes. 1948 1960 1949 In \CFA, a constructor is named @?{}@ and a destructor is named @^?{}@. 1950 The name @{}@ comes from the syntax for the initializer: @struct S { int i, j; } s = `{` 2, 3 `}`@\footnote{% 1961 In \CFA, a constructor is named @?{}@ and a destructor is named @^?{}@\footnote{% 1951 1962 The symbol \lstinline+^+ is used for the destructor name because it was the last binary operator that could be used in a unary context.}. 1963 The name @{}@ comes from the syntax for the initializer: @struct S { int i, j; } s = `{` 2, 3 `}`@. 1952 1964 Like other \CFA operators, these names represent the syntax used to call the constructor or destructor, \eg @?{}(x, ...)@ or @^{}(x, ...)@. 1953 1965 The constructor and destructor have return type @void@, and the first parameter is a reference to the object type to be constructed or destructed. … … 2071 2083 \subsection{0/1} 2072 2084 2073 In C, @0@ has the special property that it is the only ``false'' value; by the standard, any value which compares equal to @0@ is false, while any value that compares unequal to @0@ is true. 2074 As such, an expression @x@ in any boolean context (such as the condition of an @if@ or @while@ statement, or the arguments to @&&@, @||@, or @?:@) can be rewritten as @x != 0@ without changing its semantics. 2075 The operator overloading feature of \CFA provides a natural means to implement this truth value comparison for arbitrary types, but the C type system is not precise enough to distinguish an equality comparison with @0@ from an equality comparison with an arbitrary integer or pointer. 2076 To provide this precision, \CFA introduces a new type @zero_t@ as type type of literal @0@ (somewhat analagous to @nullptr_t@ and @nullptr@ in \CCeleven); @zero_t@ can only take the value @0@, but has implicit conversions to the integer and pointer types so that C code involving @0@ continues to work properly. 2077 With this addition, the \CFA compiler rewrites @if (x)@ and similar expressions to @if ((x) != 0)@ or the appropriate analogue, and any type @T@ can be made ``truthy'' by defining an operator overload @int ?!=?(T, zero_t)@. 2078 \CC makes types truthy by adding a conversion to @bool@; prior to the addition of explicit cast operators in \CCeleven this approach had the pitfall of making truthy types transitively convertable to any numeric type; our design for \CFA avoids this issue. 2079 2080 \CFA also includes a special type for @1@, @one_t@; like @zero_t@, @one_t@ has built-in implicit conversions to the various integral types so that @1@ maintains its expected semantics in legacy code. 2081 The addition of @one_t@ allows generic algorithms to handle the unit value uniformly for types where that is meaningful. 2082 \TODO{Make this sentence true} In particular, polymorphic functions in the \CFA prelude define @++x@ and @x++@ in terms of @x += 1@, allowing users to idiomatically define all forms of increment for a type @T@ by defining the single function @T & ?+=(T &, one_t)@; analogous overloads for the decrement operators are present as well. 2085 In C, @0@ has the special property that it is the only ``false'' value; 2086 from the standard, any value that compares equal to @0@ is false, while any value that compares unequal to @0@ is true. 2087 As such, an expression @x@ in any boolean context (such as the condition of an @if@ or @while@ statement, or the arguments to @&&@, @||@, or @?:@\,) can be rewritten as @x != 0@ without changing its semantics. 2088 Operator overloading in \CFA provides a natural means to implement this truth-value comparison for arbitrary types, but the C type system is not precise enough to distinguish an equality comparison with @0@ from an equality comparison with an arbitrary integer or pointer. 2089 To provide this precision, \CFA introduces a new type @zero_t@ as the type of literal @0@ (somewhat analagous to @nullptr_t@ and @nullptr@ in \CCeleven); 2090 @zero_t@ can only take the value @0@, but has implicit conversions to the integer and pointer types so that C code involving @0@ continues to work. 2091 With this addition, \CFA rewrites @if (x)@ and similar expressions to @if ((x) != 0)@ or the appropriate analogue, and any type @T@ is ``truthy'' by defining an operator overload @int ?!=?(T, zero_t)@. 2092 \CC makes types truthy by adding a conversion to @bool@; 2093 prior to the addition of explicit cast operators in \CCeleven, this approach had the pitfall of making truthy types transitively convertable to any numeric type; 2094 \CFA avoids this issue. 2095 2096 Similarly, \CFA also has a special type for @1@, @one_t@; 2097 like @zero_t@, @one_t@ has built-in implicit conversions to the various integral types so that @1@ maintains its expected semantics in legacy code for operations @++@ and @--@. 2098 The addition of @one_t@ allows generic algorithms to handle the unit value uniformly for types where it is meaningful. 2099 \TODO{Make this sentence true} 2100 In particular, polymorphic functions in the \CFA prelude define @++x@ and @x++@ in terms of @x += 1@, allowing users to idiomatically define all forms of increment for a type @T@ by defining the single function @T & ?+=(T &, one_t)@; 2101 analogous overloads for the decrement operators are present as well. 2083 2102 2084 2103 … … 2088 2107 The left of Figure~\ref{f:UserLiteral} shows the \CFA alternative call-syntax (literal argument before function name), using the backquote, to convert basic literals into user literals. 2089 2108 The backquote is a small character, making the unit (function name) predominate. 2090 For examples, the multi-precision integer s in Section~\ref{s:MultiPrecisionIntegers} make use ofuser literals:2109 For examples, the multi-precision integer-type in Section~\ref{s:MultiPrecisionIntegers} has user literals: 2091 2110 {\lstset{language=CFA,moredelim=**[is][\color{red}]{|}{|},deletedelim=**[is][]{`}{`}} 2092 2111 \begin{cfa} … … 2308 2327 \lstMakeShortInline@% 2309 2328 \end{cquote} 2310 In additon, there are polymorphic functions, like @min@ and @max@, whichwork on any type with operators @?<?@ or @?>?@.2329 In additon, there are polymorphic functions, like @min@ and @max@, that work on any type with operators @?<?@ or @?>?@. 2311 2330 2312 2331 The following shows one example where \CFA \emph{extends} an existing standard C interface to reduce complexity and provide safety. … … 2319 2338 In either case, new storage may or may not be allocated and, if there is a new allocation, as much data from the existing allocation is copied. 2320 2339 For an increase in storage size, new storage after the copied data may be filled. 2321 \item[align ment]2340 \item[align] 2322 2341 an allocation on a specified memory boundary, \eg, an address multiple of 64 or 128 for cache-line purposes. 2323 2342 \item[array] 2324 allocation of thespecified number of elements.2343 allocation with a specified number of elements. 2325 2344 An array may be filled, resized, or aligned. 2326 2345 \end{description} … … 2334 2353 \lstMakeShortInline~% 2335 2354 \begin{tabular}{@{}r|r|l|l|l|l@{}} 2336 \multicolumn{1}{c}{}& & \multicolumn{1}{c|}{fill} & resize & align ment& array \\2355 \multicolumn{1}{c}{}& & \multicolumn{1}{c|}{fill} & resize & align & array \\ 2337 2356 \hline 2338 2357 C & ~malloc~ & no & no & no & no \\ … … 2547 2566 In fact, \CFA's features for generic programming can enable faster runtime execution than idiomatic @void *@-based C code. 2548 2567 This claim is demonstrated through a set of generic-code-based micro-benchmarks in C, \CFA, and \CC (see stack implementations in Appendix~\ref{sec:BenchmarkStackImplementation}). 2549 Since all these languages share a subset essentially comprising standard C, maximal-performance benchmarks would show little runtime variance, differing onlyin length and clarity of source code.2568 Since all these languages share a subset essentially comprising standard C, maximal-performance benchmarks would show little runtime variance, other than in length and clarity of source code. 2550 2569 A more illustrative benchmark measures the costs of idiomatic usage of each language's features. 2551 Figure~\ref{fig:BenchmarkTest} shows the \CFA benchmark tests for a generic stack based on a singly linked-list .2570 Figure~\ref{fig:BenchmarkTest} shows the \CFA benchmark tests for a generic stack based on a singly linked-list, a generic pair-data-structure, and a variadic @print@ function similar to that in Section~\ref{sec:variadic-tuples}. 2552 2571 The benchmark test is similar for C and \CC. 2553 The experiment uses element types @int@ and @pair( short, char)@, and pushes $N=40M$ elements on a generic stack, copies the stack, clears one of the stacks, and finds the maximum value in the other stack.2572 The experiment uses element types @int@ and @pair(_Bool, char)@, and pushes $N=40M$ elements on a generic stack, copies the stack, clears one of the stacks, finds the maximum value in the other stack, and prints $N/2$ (to reduce graph height) constants. 2554 2573 2555 2574 \begin{figure} 2556 2575 \begin{cfa}[xleftmargin=3\parindentlnth,aboveskip=0pt,belowskip=0pt] 2557 int main( ) {2576 int main( int argc, char * argv[] ) { 2558 2577 int max = 0, val = 42; 2559 2578 stack( int ) si, ti; 2560 2579 2561 2580 REPEAT_TIMED( "push_int", N, push( si, val ); ) 2562 TIMED( "copy_int", ti { si }; )2581 TIMED( "copy_int", ti = si; ) 2563 2582 TIMED( "clear_int", clear( si ); ) 2564 REPEAT_TIMED( "pop_int", N, 2565 int x = pop( ti ); if ( x > max ) max = x; ) 2566 2567 pair( short, char ) max = { 0h, '\0' }, val = { 42h, 'a' }; 2568 stack( pair( short, char ) ) sp, tp; 2583 REPEAT_TIMED( "pop_int", N, int x = pop( ti ); if ( x > max ) max = x; ) 2584 2585 pair( _Bool, char ) max = { (_Bool)0, '\0' }, val = { (_Bool)1, 'a' }; 2586 stack( pair( _Bool, char ) ) sp, tp; 2569 2587 2570 2588 REPEAT_TIMED( "push_pair", N, push( sp, val ); ) 2571 TIMED( "copy_pair", tp { sp }; )2589 TIMED( "copy_pair", tp = sp; ) 2572 2590 TIMED( "clear_pair", clear( sp ); ) 2573 REPEAT_TIMED( "pop_pair", N, 2574 pair(short, char) x = pop( tp ); if ( x > max ) max = x; ) 2591 REPEAT_TIMED( "pop_pair", N, pair(_Bool, char) x = pop( tp ); if ( x > max ) max = x; ) 2575 2592 } 2576 2593 \end{cfa} … … 2583 2600 hence runtime checks are necessary to safely down-cast objects. 2584 2601 The most notable difference among the implementations is in memory layout of generic types: \CFA and \CC inline the stack and pair elements into corresponding list and pair nodes, while C and \CCV lack such a capability and instead must store generic objects via pointers to separately-allocated objects. 2585 Note that the C benchmark uses unchecked casts as there is no runtime mechanism to perform such checks, while \CFA and \CC provide type-safety statically. 2602 For the print benchmark, idiomatic printing is used: the C and \CFA variants used @stdio.h@, while the \CC and \CCV variants used @iostream@; preliminary tests show this distinction has negligible runtime impact. 2603 Note, the C benchmark uses unchecked casts as there is no runtime mechanism to perform such checks, while \CFA and \CC provide type-safety statically. 2586 2604 2587 2605 Figure~\ref{fig:eval} and Table~\ref{tab:eval} show the results of running the benchmark in Figure~\ref{fig:BenchmarkTest} and its C, \CC, and \CCV equivalents. 2588 2606 The graph plots the median of 5 consecutive runs of each program, with an initial warm-up run omitted. 2589 All code is compiled at \texttt{-O2} by gcc or g++ 6. 3.0, with all \CC code compiled as \CCfourteen.2607 All code is compiled at \texttt{-O2} by gcc or g++ 6.2.0, with all \CC code compiled as \CCfourteen. 2590 2608 The benchmarks are run on an Ubuntu 16.04 workstation with 16 GB of RAM and a 6-core AMD FX-6300 CPU with 3.5 GHz maximum clock frequency. 2591 2609 … … 2605 2623 & \CT{C} & \CT{\CFA} & \CT{\CC} & \CT{\CCV} \\ \hline 2606 2624 maximum memory usage (MB) & 10001 & 2502 & 2503 & 11253 \\ 2607 source code size (lines) & 187 & 188 & 133 & 303\\2608 redundant type annotations (lines) & 25 & 0 & 2 & 16\\2609 binary size (KB) & 14 & 2 57 & 14 & 37\\2625 source code size (lines) & 247 & 222 & 165 & 339 \\ 2626 redundant type annotations (lines) & 39 & 2 & 2 & 15 \\ 2627 binary size (KB) & 14 & 229 & 18 & 38 \\ 2610 2628 \end{tabular} 2611 2629 \end{table} 2612 2630 2613 2631 The C and \CCV variants are generally the slowest with the largest memory footprint, because of their less-efficient memory layout and the pointer-indirection necessary to implement generic types; 2614 this inefficiency is exacerbated by the second level of generic types in the pair benchmarks.2615 By contrast, the \CFA and \CC variants run in roughly equivalent time for both the integer and pair of @ short@ and @char@ because the storage layout is equivalent, with the inlined libraries (\ie no separate compilation) and greater maturity of the \CC compiler contributing to its lead.2632 this inefficiency is exacerbated by the second level of generic types in the pair-based benchmarks. 2633 By contrast, the \CFA and \CC variants run in roughly equivalent time for both the integer and pair of @_Bool@ and @char@ because the storage layout is equivalent, with the inlined libraries (\ie no separate compilation) and greater maturity of the \CC compiler contributing to its lead. 2616 2634 \CCV is slower than C largely due to the cost of runtime type-checking of down-casts (implemented with @dynamic_cast@); 2617 The outlier in the graph for \CFA, pop @pair@, results from the complexity of the generated-C polymorphic code. 2618 The gcc compiler is unable to optimize some dead code and condense nested calls; a compiler designed for \CFA could easily perform these optimizations. 2635 There are two outliers in the graph for \CFA: all prints and pop of @pair@. 2636 Both of these cases result from the complexity of the C-generated polymorphic code, so that the gcc compiler is unable to optimize some dead code and condense nested calls. 2637 A compiler designed for \CFA could easily perform these optimizations. 2619 2638 Finally, the binary size for \CFA is larger because of static linking with the \CFA libraries. 2620 2639 2621 \CFA is also competitive in terms of source code size, measured as a proxy for programmer effort. The line counts in Table~\ref{tab:eval} include implementations of @pair@ and @stack@ types for all four languages for purposes of direct comparison, though it should be noted that \CFA and \CC have pre-written data structures in their standard libraries that programmers would generally use instead. Use of these standard library types has minimal impact on the performance benchmarks, but shrinks the \CFA and \CC benchmarks to 41 and 42lines, respectively.2640 \CFA is also competitive in terms of source code size, measured as a proxy for programmer effort. The line counts in Table~\ref{tab:eval} include implementations of @pair@ and @stack@ types for all four languages for purposes of direct comparison, though it should be noted that \CFA and \CC have pre-written data structures in their standard libraries that programmers would generally use instead. Use of these standard library types has minimal impact on the performance benchmarks, but shrinks the \CFA and \CC benchmarks to 73 and 54 lines, respectively. 2622 2641 On the other hand, C does not have a generic collections-library in its standard distribution, resulting in frequent reimplementation of such collection types by C programmers. 2623 \CCV does not use the \CC standard template library by construction, and in fact includes the definition of @object@ and wrapper classes for @ char@, @short@, and @int@ in its line count, which inflates this count somewhat, as an actual object-oriented language would include these in the standard library;2642 \CCV does not use the \CC standard template library by construction, and in fact includes the definition of @object@ and wrapper classes for @bool@, @char@, @int@, and @const char *@ in its line count, which inflates this count somewhat, as an actual object-oriented language would include these in the standard library; 2624 2643 with their omission, the \CCV line count is similar to C. 2625 2644 We justify the given line count by noting that many object-oriented languages do not allow implementing new interfaces on library types without subclassing or wrapper types, which may be similarly verbose. … … 2627 2646 Raw line-count, however, is a fairly rough measure of code complexity; 2628 2647 another important factor is how much type information the programmer must manually specify, especially where that information is not checked by the compiler. 2629 Such unchecked type information produces a heavier documentation burden and increased potential for runtime bugs, and is much less common in \CFA than C, with its manually specified function pointer arguments and format codes, or \CCV, with its extensive use of un-type-checked downcasts (\eg @object@ to @integer@ when popping a stack, or @object@ to @printable@ when printing the elements of a @pair@).2648 Such unchecked type information produces a heavier documentation burden and increased potential for runtime bugs, and is much less common in \CFA than C, with its manually specified function pointers arguments and format codes, or \CCV, with its extensive use of un-type-checked downcasts (\eg @object@ to @integer@ when popping a stack, or @object@ to @printable@ when printing the elements of a @pair@). 2630 2649 To quantify this, the ``redundant type annotations'' line in Table~\ref{tab:eval} counts the number of lines on which the type of a known variable is re-specified, either as a format specifier, explicit downcast, type-specific function, or by name in a @sizeof@, struct literal, or @new@ expression. 2631 2650 The \CC benchmark uses two redundant type annotations to create a new stack nodes, while the C and \CCV benchmarks have several such annotations spread throughout their code. 2632 The \CFA benchmark was able to eliminate all redundant type annotations through use of the polymorphic @alloc@ function discussed in Section~\ref{sec:libraries}. 2651 The two instances in which the \CFA benchmark still uses redundant type specifiers are to cast the result of a polymorphic @malloc@ call (the @sizeof@ argument is inferred by the compiler). 2652 These uses are similar to the @new@ expressions in \CC, though the \CFA compiler's type resolver should shortly render even these type casts superfluous. 2653 2633 2654 2634 2655 \section{Related Work} 2656 2635 2657 2636 2658 \subsection{Polymorphism} … … 2713 2735 user defined: D, Objective-C 2714 2736 2737 2715 2738 \section{Conclusion and Future Work} 2716 2739 … … 2725 2748 Finally, we demonstrate that \CFA performance for some idiomatic cases is better than C and close to \CC, showing the design is practically applicable. 2726 2749 2727 There is ongoing work on a wide range of \CFA feature extensions, including arrays with size, user-defined conversions, concurrent primitives, and modules.2750 There is ongoing work on a wide range of \CFA feature extensions, including arrays with size, runtime type-information, virtual functions, user-defined conversions, concurrent primitives, and modules. 2728 2751 (While all examples in the paper compile and run, a public beta-release of \CFA will take another 8--12 months to finalize these additional extensions.) 2729 2752 In addition, there are interesting future directions for the polymorphism design. … … 2760 2783 \CFA 2761 2784 \begin{cfa}[xleftmargin=2\parindentlnth,aboveskip=0pt,belowskip=0pt] 2762 forall(otype T) struct stack_node { 2785 forall( otype T ) struct stack_node; 2786 forall( otype T ) struct stack { 2787 stack_node(T) * head; 2788 }; 2789 forall( otype T ) struct stack_node { 2763 2790 T value; 2764 2791 stack_node(T) * next; 2765 2792 }; 2766 forall(otype T) struct stack { stack_node(T) * head; }; 2767 forall(otype T) void ?{}( stack(T) & s ) { (s.head){ 0 }; } 2768 forall(otype T) void ?{}( stack(T) & s, stack(T) t ) { 2793 forall( otype T) void ?{}( stack(T) & s ) { (s.head){ 0 }; } 2794 forall( otype T) void ?{}( stack(T) & s, stack(T) t ) { 2769 2795 stack_node(T) ** crnt = &s.head; 2770 2796 for ( stack_node(T) * next = t.head; next; next = next->next ) { 2771 *crnt = alloc(); 2772 ((*crnt)->value){ next->value }; 2773 crnt = &(*crnt)->next; 2797 stack_node(T) * new_node = ((stack_node(T)*)malloc()); 2798 (*new_node){ next->value }; /***/ 2799 *crnt = new_node; 2800 stack_node(T) * acrnt = *crnt; 2801 crnt = &acrnt->next; 2774 2802 } 2775 2803 *crnt = 0; 2776 2804 } 2777 forall( otype T) stack(T) ?=?( stack(T) & s, stack(T) t ) {2805 forall( otype T ) stack(T) ?=?( stack(T) & s, stack(T) t ) { 2778 2806 if ( s.head == t.head ) return s; 2779 2807 clear( s ); … … 2781 2809 return s; 2782 2810 } 2783 forall(otype T) void ^?{}( stack(T) & s) { clear( s ); } 2784 forall(otype T) _Bool empty( const stack(T) & s ) { return s.head == 0; } 2785 forall(otype T) void push( stack(T) & s, T value ) { 2786 stack_node(T) * n = alloc(); 2787 (*n){ value, head }; 2788 head = n; 2789 } 2790 forall(otype T) T pop( stack(T) & s ) { 2791 stack_node(T) * n = head; 2792 head = n->next; 2793 T x = n->value; 2794 ^(*n){}; 2795 free( n ); 2796 return x; 2797 } 2798 forall(otype T) void clear( stack(T) & s ) { 2799 for ( stack_node(T) * next = head; next; ) { 2811 forall( otype T ) void ^?{}( stack(T) & s) { clear( s ); } 2812 forall( otype T ) _Bool empty( const stack(T) & s ) { return s.head == 0; } 2813 forall( otype T ) void push( stack(T) & s, T value ) { 2814 stack_node(T) * new_node = ((stack_node(T)*)malloc()); 2815 (*new_node){ value, s.head }; /***/ 2816 s.head = new_node; 2817 } 2818 forall( otype T ) T pop( stack(T) & s ) { 2819 stack_node(T) * n = s.head; 2820 s.head = n->next; 2821 T v = n->value; 2822 delete( n ); 2823 return v; 2824 } 2825 forall( otype T ) void clear( stack(T) & s ) { 2826 for ( stack_node(T) * next = s.head; next; ) { 2800 2827 stack_node(T) * crnt = next; 2801 2828 next = crnt->next; 2802 ^(*crnt){}; 2803 free(crnt); 2829 delete( crnt ); 2804 2830 } 2805 head = 0;2831 s.head = 0; 2806 2832 } 2807 2833 \end{cfa} … … 2810 2836 \CC 2811 2837 \begin{cfa}[xleftmargin=2\parindentlnth,aboveskip=0pt,belowskip=0pt] 2812 template<typename T> structstack {2838 template<typename T> class stack { 2813 2839 struct node { 2814 2840 T value; … … 2817 2843 }; 2818 2844 node * head; 2819 void copy(const stack<T> & o) {2845 void copy(const stack<T>& o) { 2820 2846 node ** crnt = &head; 2821 2847 for ( node * next = o.head;; next; next = next->next ) { … … 2825 2851 *crnt = nullptr; 2826 2852 } 2853 public: 2827 2854 stack() : head(nullptr) {} 2828 stack(const stack<T> & o) { copy(o); }2855 stack(const stack<T>& o) { copy(o); } 2829 2856 stack(stack<T> && o) : head(o.head) { o.head = nullptr; } 2830 2857 ~stack() { clear(); } 2831 stack & operator= (const stack<T> & o) {2858 stack & operator= (const stack<T>& o) { 2832 2859 if ( this == &o ) return *this; 2833 2860 clear(); … … 2868 2895 struct stack_node * next; 2869 2896 }; 2870 struct stack { struct stack_node* head; };2871 2897 struct stack new_stack() { return (struct stack){ NULL }; /***/ } 2872 2898 void copy_stack(struct stack * s, const struct stack * t, void * (*copy)(const void *)) { … … 2874 2900 for ( struct stack_node * next = t->head; next; next = next->next ) { 2875 2901 *crnt = malloc(sizeof(struct stack_node)); /***/ 2876 (*crnt)->value = copy(next->value);2902 **crnt = (struct stack_node){ copy(next->value) }; /***/ 2877 2903 crnt = &(*crnt)->next; 2878 2904 } 2879 *crnt = NULL;2905 *crnt = 0; 2880 2906 } 2881 2907 _Bool stack_empty(const struct stack * s) { return s->head == NULL; } … … 2906 2932 \CCV 2907 2933 \begin{cfa}[xleftmargin=2\parindentlnth,aboveskip=0pt,belowskip=0pt] 2908 struct stack { 2909 struct node { 2910 ptr<object> value; 2911 node* next; 2912 node( const object & v, node * n ) : value( v.new_copy() ), next( n ) {} 2913 }; 2914 node* head; 2915 void copy(const stack & o) { 2916 node ** crnt = &head; 2917 for ( node * next = o.head; next; next = next->next ) { 2918 *crnt = new node{ *next->value }; /***/ 2919 crnt = &(*crnt)->next; 2920 } 2921 *crnt = nullptr; 2934 stack::node::node( const object & v, node * n ) : value( v.new_copy() ), next( n ) {} 2935 void stack::copy(const stack & o) { 2936 node ** crnt = &head; 2937 for ( node * next = o.head; next; next = next->next ) { 2938 *crnt = new node{ *next->value }; 2939 crnt = &(*crnt)->next; 2922 2940 } 2923 stack() : head(nullptr) {} 2924 stack(const stack & o) { copy(o); } 2925 stack(stack && o) : head(o.head) { o.head = nullptr; } 2926 ~stack() { clear(); } 2927 stack & operator= (const stack & o) { 2928 if ( this == &o ) return *this; 2929 clear(); 2930 copy(o); 2931 return *this; 2941 *crnt = nullptr; 2942 } 2943 stack::stack() : head(nullptr) {} 2944 stack::stack(const stack & o) { copy(o); } 2945 stack::stack(stack && o) : head(o.head) { o.head = nullptr; } 2946 stack::~stack() { clear(); } 2947 stack & stack::operator= (const stack & o) { 2948 if ( this == &o ) return *this; 2949 clear(); 2950 copy(o); 2951 return *this; 2952 } 2953 stack & stack::operator= (stack && o) { 2954 if ( this == &o ) return *this; 2955 head = o.head; 2956 o.head = nullptr; 2957 return *this; 2958 } 2959 bool stack::empty() const { return head == nullptr; } 2960 void stack::push(const object & value) { head = new node{ value, head }; /***/ } 2961 ptr<object> stack::pop() { 2962 node * n = head; 2963 head = n->next; 2964 ptr<object> x = std::move(n->value); 2965 delete n; 2966 return x; 2967 } 2968 void stack::clear() { 2969 for ( node * next = head; next; ) { 2970 node * crnt = next; 2971 next = crnt->next; 2972 delete crnt; 2932 2973 } 2933 stack & operator= (stack && o) { 2934 if ( this == &o ) return *this; 2935 head = o.head; 2936 o.head = nullptr; 2937 return *this; 2938 } 2939 bool empty() const { return head == nullptr; } 2940 void push(const object & value) { head = new node{ value, head }; /***/ } 2941 ptr<object> pop() { 2942 node * n = head; 2943 head = n->next; 2944 ptr<object> x = std::move(n->value); 2945 delete n; 2946 return x; 2947 } 2948 void clear() { 2949 for ( node * next = head; next; ) { 2950 node * crnt = next; 2951 next = crnt->next; 2952 delete crnt; 2953 } 2954 head = nullptr; 2955 } 2956 }; 2974 head = nullptr; 2975 } 2957 2976 \end{cfa} 2958 2977
Note:
See TracChangeset
for help on using the changeset viewer.