source: doc/theses/fangren_yu_MMath/features.tex@ 1e28e05

Last change on this file since 1e28e05 was edd11bd, checked in by Peter A. Buhr <pabuhr@…>, 6 months ago

add identification information

  • Property mode set to 100644
File size: 45.0 KB
Line 
1\chapter{\CFA Features and Type System Interactions}
2\label{c:content1}
3
4This chapter discusses \CFA features introduced over time by multiple people and their interactions with the type system.
5
6
7\section{Reference Types}
8
9Reference types were added to \CFA by Robert Schluntz and Aaron Moss~\cite{Moss18}.
10The \CFA reference type generalizes the \CC reference type (and its equivalent in other modern programming languages) by providing both mutable and immutable forms and cascading referencing and dereferencing.
11Specifically, \CFA attempts to extend programmer intuition about pointers to references.
12That is, use a pointer when its primary purpose is manipulating the address of storage, \eg a top/head/tail pointer or link field in a mutable data structure.
13Here, manipulating the pointer address is the primary operation, while dereferencing the pointer to its value is the secondary operation.
14For example, \emph{within} a data structure, \eg stack or queue, all operations involve pointer addresses and the pointer may never be dereferenced because the referenced object is opaque.
15Alternatively, use a reference when its primary purpose is to alias a value, \eg a function parameter that does not copy the argument, for performance reasons.
16Here, manipulating the value is the primary operation, while changing the pointer address is the secondary operation.
17Succinctly, if the address changes often, use a pointer;
18if the value changes often, use a reference.
19Java has mutable references but no pointers.
20\CC has mutable pointers but immutable references;
21here, references match with functional programming.
22However, the consequence is asymmetric semantics between pointer and reference.
23\CFA adopts a uniform policy between pointers and references where mutability is a separate property made at the declaration.
24
25The following examples show how pointers and references are treated uniformly in \CFA.
26\begin{cfa}[numbers=left,numberblanklines=false]
27int x = 1, y = 2, z = 3;$\label{p:refexamples}$
28int * p1 = &x, ** p2 = &p1, *** p3 = &p2, $\C{// pointers to x}$
29 @&@ r1 = x, @&&@ r2 = r1, @&&&@ r3 = r2; $\C{// references to x}$
30int * p4 = &z, & r4 = z;
31
32*p1 = 3; **p2 = 3; ***p3 = 3; $\C{// different ways to change x to 3}$
33 r1 = 3; r2 = 3; r3 = 3; $\C{// change x: implicit dereference *r1, **r2, ***r3}$
34**p3 = &y; *p3 = &p4; $\C{// change p1, p2}$
35// cancel implicit dereferences (&*)**r3, (&(&*)*)*r3, &(&*)r4
36@&@r3 = @&@y; @&&@r3 = @&&@r4; $\C{// change r1, r2}$
37\end{cfa}
38Like pointers, references can be cascaded, \ie a reference to a reference, \eg @&& r2@.\footnote{
39\CC uses \lstinline{&&} for rvalue reference, a feature for move semantics and handling the \lstinline{const} Hell problem.}
40Usage of a reference variable automatically performs the same number of dereferences as the number of references in its declaration, \eg @r2@ becomes @**r2@.
41Finally, to reassign a reference's address needs a mechanism to stop the auto-referencing, which is accomplished by using a single reference to cancel all the auto-dereferencing, \eg @&r3 = &y@ resets @r3@'s address to point to @y@.
42\CFA's reference type (including multi-de/references) is powerful enough to describe the lvalue rules in C by types only.
43As a result, the \CFA type checker now works on just types without using the notion of lvalue in an expression.
44(\CFA internals still use lvalue for code generation purposes.)
45
46The current reference typing rules in \CFA are summarized as follows:
47\begin{enumerate}
48 \item For a variable $x$ with declared type $T$, the variable-expression $x$ has type reference to $T$, even if $T$ itself is a reference type.
49 \item For an expression $e$ with type $T\ \&_1...\&_n$, \ie $T$ followed by $n$ references, where $T$ is not a reference type, the expression $\&T$ (address of $T$) has type $T *$ followed by $n - 1$ references.
50 \item For an expression $e$ with type $T *\&_1...\&_n$, \ie $T *$ followed by $n$ references, the expression $* T$ (dereference $T$) has type $T$ followed by $n + 1$ references.
51 This rule is the reverse of the previous rule, such that address-of and dereference operators are perfect inverses.
52 \item When matching argument and parameter types at a function call, the number of references on the argument type is stripped off to match the number of references on the parameter type.\footnote{
53 \CFA handles the \lstinline{const} Hell problem by allowing rvalue expressions to be converted to reference values by implicitly creating a temporary variable, with some restrictions.
54 As well, there is a warning that the output nature of the reference is lost.
55 Hence, a single function handles \lstinline{const} and non-\lstinline{const} as constness is handled at the call site.}
56 In an assignment context, the left-hand-side operand-type is always reduced to a single reference.
57\end{enumerate}
58Under this ruleset, a type parameter is never bound to a reference type in a function-call context.
59\begin{cfa}
60forall( T ) void f( T & );
61int & x;
62f( x ); // implicit dereference
63\end{cfa}
64The call applies an implicit dereference once to @x@ so the call is typed @f( int & )@ with @T = int@, rather than with @T = int &@.
65
66As with a pointer type, a reference type may have qualifiers, where @const@ is most common.
67\begin{cfa}
68int x = 3; $\C{// mutable}$
69const int cx = 5; $\C{// immutable}$
70int * const cp = &x, $\C{// immutable pointer pointer/reference}$
71 & const cr = cx;
72const int * const ccp = &cx, $\C{// immutable value and pointer/reference}$
73 & const ccr = cx;
74\end{cfa}
75\begin{cquote}
76\setlength{\tabcolsep}{26pt}
77\begin{tabular}{@{}lll@{}}
78pointer & reference & \\
79\begin{cfa}
80*cp = 7;
81cp = &x;
82*ccp = 7;
83ccp = &cx;
84\end{cfa}
85&
86\begin{cfa}
87cr = 7;
88cr = &x;
89*ccr = 7;
90ccr = &cx;
91\end{cfa}
92&
93\begin{cfa}
94// allowed
95// error, assignment of read-only variable
96// error, assignment of read-only location
97// error, assignment of read-only variable
98\end{cfa}
99\end{tabular}
100\end{cquote}
101Interestingly, C does not give a warning/error if a @const@ pointer is not initialized, while \CC does.
102Hence, type @& const@ is similar to a \CC reference, but \CFA does not preclude initialization with a non-variable address.
103For example, in systems programming, there are cases where an immutable address is initialized to a specific memory location.
104\begin{cfa}
105int & const mem_map = *0xe45bbc67@p@; $\C{// hardware mapped registers ('p' for pointer)}$
106\end{cfa}
107Finally, qualification is generalized across all pointer/reference declarations.
108\begin{cfa}
109const * const * const * const ccccp = ...
110const & const & const & const ccccr = ...
111\end{cfa}
112
113In the initial \CFA reference design, the goal was to make the reference type a \emph{real} data type \vs a restricted \CC reference, which is mostly used for choosing the argument-passing method, \ie by-value or by-reference.
114However, there is an inherent ambiguity for auto-dereferencing: every argument expression involving a reference variable can potentially mean passing the reference's value or address.
115For example, in
116\begin{cfa}
117int & x;
118forall( T ) void foo( T );
119forall( T ) void bar( T & );
120foo( x ); $\C{// means pass by value}$
121bar( x ); $\C{// means pass by reference}$
122\end{cfa}
123the call to @foo@ must pass @x@ by value, implying auto-dereference, while the call to @bar@ must pass @x@ by reference, implying no auto-dereference.
124
125\PAB{My analysis shows} without any restrictions, this ambiguity limits the behaviour of reference types in \CFA polymorphic functions, where a type @T@ can bind to a reference or non-reference type.
126This ambiguity prevents the type system treating reference types the same way as other types, even if type variables could be bound to reference types.
127The reason is that \CFA uses a common \emph{object trait}\label{p:objecttrait} (constructor, destructor and assignment operators) to handle passing dynamic concrete type arguments into polymorphic functions, and the reference types are handled differently in these contexts so they do not satisfy this common interface.
128
129Moreover, there is also some discrepancy in how the reference types are treated in initialization and assignment expressions.
130For example, in line 3 of the example code on \VPageref{p:refexamples}:
131\begin{cfa}
132int @&@ r1 = x, @&&@ r2 = r1, @&&&@ r3 = r2; $\C{// references to x}$
133\end{cfa}
134each initialization expression is implicitly dereferenced to match the types, \eg @&x@, because an address is always required and a variable normally returns its value;
135\CC does the same implicit dereference when initializing its reference variables.
136For lines 6 and 9 of the previous example code:
137\begin{cfa}
138 r1 = 3; r2 = 3; r3 = 3; $\C{// change x: implicit dereference *r1, **r2, ***r3}$
139@&@r3 = @&@y; @&&@r3 = @&&@r4; $\C{// change r1, r2}$
140\end{cfa}
141there are no actual assignment operators defined for reference types that can be overloaded;
142instead, all reference assignments are handled by semantic actions in the type system.
143In fact, the reassignment of reference variables is setup internally to use the assignment operators for pointer types.
144Finally, there is an annoying issue (although purely syntactic) for setting a mutable reference to a specific address like null, @int & r1 = *0p@, which looks like dereferencing a null pointer.
145Here, the expression is rewritten as @int & r1 = &(*0p)@, like the variable dereference of @x@ above.
146However, the implicit @&@ needs to be cancelled for an address, which is done with the @*@, \ie @&*@ cancel each other, giving @0p@.
147Therefore, the dereferencing operation does not actually happen and the expression is translated into directly initializing the reference variable with the address.
148Note, the same explicit reference is used in \CC to set a reference variable to null.
149\begin{c++}
150int & ip = @*@(int *)nullptr;
151\end{c++}
152which is used in certain systems-programming situations.
153
154When generic types were introduced to \CFA~\cite{Moss19}, some thought was given to allow reference types as type arguments.
155\begin{cfa}
156forall( T ) struct vector { T t; }; $\C{// generic type}$
157vector( int @&@ ) vec; $\C{// vector of references to ints}$
158\end{cfa}
159While it is possible to write a reference type as the argument to a generic type, it is disallowed in assertion checking, if the generic type requires the object trait \see{\VPageref{p:objecttrait}} for the type argument, a fairly common use case.
160Even if the object trait can be made optional, the current compiler implementation often misbehaves by adding undesirable auto-dereference on the referenced-to value rather than the reference variable itself, as intended.
161Some tweaks are necessary to accommodate reference types in polymorphic contexts and it is unclear what can or cannot be achieved.
162Currently, there are contexts where the \CFA programmer is forced to use a pointer type, giving up the benefits of auto-dereference operations and better syntax with reference types.
163
164
165\section{Tuple Types}
166
167The addition of tuples to \CFA can be traced back to the original design by David Till in \mbox{K-W C}~\cite{Till89,Buhr94a}, a predecessor project of \CFA.
168The primary purpose of tuples is to eliminate output parameters or creating an aggregate type to return multiple values from a function, called a multiple-value-returning (MVR) function.
169Traditionally, returning multiple values is accomplished via (in/)output parameters or packing the results in a structure.
170The following examples show these two techniques for a function returning three values.
171\begin{cquote}
172\begin{tabular}{@{}l@{\hspace{20pt}}l@{}}
173\begin{cfa}
174int foo( int &p1, int &p2 ); // in/out parameters
175int x, y = 3, z = 4;
176x = foo( y, z ); // return 3 values: 1 out, 2 in/out
177\end{cfa}
178&
179\begin{cfa}
180struct Ret { int x, y, z; } ret;
181Ret foo( int p1, int p2 ); // return structure
182ret = foo( 3, 4 ); // return 3 values: 3 out
183\end{cfa}
184\end{tabular}
185\end{cquote}
186Like Go, K-W C allows direct return of multiple values into a tuple.
187\begin{cfa}
188@[int, int, int]@ foo( int p1, int p2 );
189@[x, y, z]@ = foo( 3, 4 ); // return 3 values into a tuple
190\end{cfa}
191Along with making returning multiple values a first-class feature, tuples were extended to simplify a number of other common contexts that normally require multiple statements and/or additional declarations.
192\begin{cfa}
193[x, y, z] = 3; $\C[2in]{// x = 3; y = 3; z = 3, where types may be different}$
194[x, y] = [y, x]; $\C{// int tmp = x; x = y; y = tmp;}$
195void bar( int, int, int );
196bar( foo( 3, 4 ) ); $\C{// int t0, t1, t2; [t0, t1, t2] = foo( 3, 4 ); bar( t0, t1, t2 );}$
197x = foo( 3, 4 )@.1@; $\C{// int t0, t1, t2; [t0, t1, t2] = foo( 3, 4 ); x = t1;}\CRT$
198\end{cfa}
199For the call to @bar@, the three results (tuple value) from @foo@ are \newterm{flattened} into individual arguments.
200Flattening is how tuples interact with parameter and subscript lists, and with other tuples, \eg:
201\begin{cfa}
202[ [ x, y ], z, [a, b, c] ] = [2, [3, 4], foo( 3, 4) ] $\C{// structured}$
203[ x, y, z, a, b, c] = [2, 3, 4, foo( 3, 4) ] $\C{// flattened, where foo results are t0, t1, t2}$
204\end{cfa}
205Note, in most cases, a tuple is just compile-time syntactic-sugar for a number of individual assignments statements and possibly temporary variables.
206Only when returning a tuple from a function is there the notion of a tuple value.
207
208Overloading in the \CFA type-system must support complex composition of tuples and C type conversions using a conversion cost scheme giving lower cost to widening conversions that do not truncate a value.
209\begin{cfa}
210[ int, int ] foo$\(_1\)$( int ); $\C{// overloaded foo functions}$
211[ double ] foo$\(_2\)$( int );
212void bar( int, double, double );
213bar( @foo@( 3 ), @foo@( 3 ) );
214\end{cfa}
215The type resolver only has the tuple return types to resolve the call to @bar@ as the @foo@ parameters are identical.
216The resulution involves unifying the flattened @foo@ return values with @bar@'s parameter list.
217However, no combination of @foo@s is an exact match with @bar@'s parameters;
218thus, the resolver applies C conversions to obtain a best match.
219The resulting minimal cost expression is @bar( foo@$_1$@( 3 ), foo@$_2$@( 3 ) )@, where the two possible coversions are (@int@, {\color{red}@int@}, @double@) to (@int@, {\color{red}@double@}, @double@) with a safe (widening) conversion from @int@ to @double@ versus ({\color{red}@double@}, {\color{red}@int@}, {\color{red}@int@}) to ({\color{red}@int@}, {\color{red}@double@}, {\color{red}@double@}) with one unsafe (narrowing) conversion from @double@ to @int@ and two safe conversions from @int@ to @double@.
220Go provides a simplified mechanism where only one tuple returning function call is allowed and there are no implicit type conversions.
221\begin{lstlisting}[language=Go]
222func foo( int ) ( int, int, int ) { return 3, 7, 8 }
223func bar( int, int, int ) { ... } // types must match
224bar( foo( 3 ) ) // only one tuple returning call
225\end{lstlisting}
226Hence, programmers cannot take advantage of the full power of tuples but type match is straightforward.
227
228K-W C also supported tuple variables, but with a strong distinction between tuples and tuple values/variables.
229\begin{quote}
230Note that tuple variables are not themselves tuples.
231Tuple variables reference contiguous areas of storage, in which tuple values are stored;
232tuple variables and tuple values are entities which appear during program execution.
233Tuples, on the other hand, are compile-time constructs;
234they are lists of expressions, whose values may not be stored contiguously.~\cite[p.~30]{Till89}
235\end{quote}
236Fundamentally, a tuple value/variable is just a structure (contiguous areas) with an alternate (tuple) interface.
237A tuple value/variable is assignable (like structures), its fields can be accessed using position rather than name qualification, and it can interact with regular tuples.
238\begin{cfa}
239[ int, int, int ] t1, t2;
240t1 = t2; $\C{// tuple assignment}$
241t1@.1@ = t2@.0@; $\C{// position qualification}$
242int x, y, z;
243t1 = [ x, y, z ]; $\C{// interact with regular tuples}$
244[ x, y, z ] = t1;
245bar( t2 ); $\C{// bar defined above}$
246\end{cfa}
247\VRef[Figure]{f:Nesting} shows the difference is nesting of structures and tuples.
248The left \CC nested-structure is named so it is not flattened.
249The middle C/\CC nested-structure is unnamed and flattened, causing an error because @i@ and @j@ are duplication names.
250The right \CFA nested tuple cannot be named and is flattened.
251C allows named nested-structures, but they have issues \see{\VRef{s:inlineSubstructure}}.
252Note, it is common in C to have an unnamed @union@ so its fields do not require qualification.
253
254\begin{figure}
255\setlength{\tabcolsep}{20pt}
256\begin{tabular}{@{}ll@{\hspace{90pt}}l@{}}
257\multicolumn{1}{c}{\CC} & \multicolumn{1}{c}{C/\CC} & \multicolumn{1}{c}{tuple} \\
258\begin{cfa}
259struct S {
260 struct @T@ { // not flattened
261 int i, j;
262 };
263 int i, j;
264};
265\end{cfa}
266&
267\begin{cfa}
268struct S2 {
269 struct ${\color{red}/* unnamed */}$ { // flatten
270 int i, j;
271 };
272 int i, j;
273};
274\end{cfa}
275&
276\begin{cfa}
277[
278 [ // flatten
279 1, 2
280 ]
281 1, 2
282]
283\end{cfa}
284\end{tabular}
285\caption{Nesting}
286\label{f:Nesting}
287\end{figure}
288
289\PAB{I identified} the primary issues for tuples in the \CFA type system are polymorphism and conversions.
290Specifically, does it make sense to have a generic (polymorphic) tuple type, as is possible for a structure?
291\begin{cfa}
292forall( T, S ) [ T, S ] GT; // polymorphic tuple type
293GT( int, char ) @gt@;
294GT( int, double ) @gt@;
295@gt@ = [ 3, 'a' ]; // select correct gt
296@gt@ = [ 3, 3.5 ];
297\end{cfa}
298and what is the cost model for C conversions across multiple values?
299\begin{cfa}
300gt = [ 'a', 3L ]; // select correct gt
301\end{cfa}
302
303
304\section{Tuple Implementation}
305
306As noted, traditional languages manipulate multiple values by in/out parameters and/or structures.
307K-W C adopted the structure for tuple values or variables, and as needed, the fields are extracted by field access operations.
308As well, for the tuple-assignment implementation, the left-hand tuple expression is expanded into assignments of each component, creating temporary variables to avoid unexpected side effects.
309For example, the tuple value returned from @foo@ is a structure, and its fields are individually assigned to a left-hand tuple, @x@, @y@, @z@, \emph{or} copied directly into a corresponding tuple variable.
310
311In the second implementation of \CFA tuples by Rodolfo Gabriel Esteves~\cite{Esteves04}, a different strategy is taken to handle MVR functions.
312The return values are converted to output parameters passed in by pointers.
313When the return values of a MVR function are directly used in an assignment expression, the addresses of the left-hand operands can be directly passed into the function;
314composition of MVR functions is handled by creating temporaries for the returns.
315For example, given a function returning two values:
316\begin{cfa}
317[int, int] gives_two() { int r1, r2; ... return [ r1, r2 ]; }
318int x, y;
319[x, y] = gives_two();
320\end{cfa}
321\VRef[Figure]{f:AlternateTupleImplementation} shows the two implementation approaches.
322In the left approach, the return statement is rewritten to pack the return values into a structure, which is returned by value, and the structure fields are individually assigned to the left-hand side of the assignment.
323In the right approach, the return statement is rewritten as direct assignments into the passed-in argument addresses.
324The upside of the right implementation is consistence and no copying.
325The downside is indirection within @gives_two@ to access values, unless values get hoisted into registers for some period of time, which is common.
326
327\begin{figure}
328\begin{cquote}
329\setlength{\tabcolsep}{20pt}
330\begin{tabular}{@{}ll@{}}
331Till K-W C implementation & Esteves \CFA implementation \\
332\begin{cfa}
333struct _tuple2 { int _0; int _1; }
334struct _tuple2 gives_two() {
335 ... struct _tuple2 ret = { r1, r2 };
336 return ret;
337}
338int x, y;
339struct _tuple2 _tmp = gives_two();
340x = _tmp._0; y = _tmp._1;
341\end{cfa}
342&
343\begin{cfa}
344
345void gives_two( int * r1, int * r2 ) {
346 ... *r1 = ...; *r2 = ...;
347 return;
348}
349int x, y;
350
351gives_two( &x, &y );
352\end{cfa}
353\end{tabular}
354\end{cquote}
355\caption{Alternate Tuple Implementation}
356\label{f:AlternateTupleImplementation}
357\end{figure}
358
359Interestingly, in the third implementation of \CFA tuples by Robert Schluntz~\cite[\S~3]{Schluntz17}, the MVR functions revert back to structure based, and this remains in the current version of \CFA.
360The reason for the reversion is a uniform approach for tuple values/variables making tuples first-class types in \CFA, \ie allow tuples with corresponding tuple variables.
361This reversion was possible, because in parallel with Schluntz's work, generic types were added independently by Moss~\cite{Moss19}, and the tuple variables leveraged the same implementation techniques as for generic variables~\cite[\S~3.7]{Schluntz17}.
362For example, these two tuples:
363\begin{cfa}
364[double, double] x;
365[int, double, int] y;
366\end{cfa}
367are transformed internally into two generic structures:
368\begin{cfa}
369forall( T0 &, & T1 | sized( T0 ) | sized( T1 ) )
370struct _tuple2_ {
371 T0 field_0 ; T1 field_1 ;
372};
373forall( T0 &, T1 &, T2 & | sized( T0 ) | sized( T1 ) | sized( T2 ) )
374struct _tuple3_ {
375 T0 field_0 ; T1 field_1 ; T2 field_2 ;
376};
377\end{cfa}
378and the declarations become instances of these generic structure types:
379\begin{cfa}
380_tuple2_( double, double ) x;
381_tuple3_( int, double, int ) y;
382\end{cfa}
383Now types @_tuple2_@ and @_tuple3_@ are available for any further 2 or 3 tuple-types in the translation unit, simplifying internal code transformations by memoizing a small set of tuple structures.
384Ultimately, these generic types are lowered to specific C structures during code generation.
385Scala, like \CC, provides tuple types through a library using this structural expansion, \eg Scala provides tuple sizes 1 through 22 via hand-coded generic data-structures.
386
387However, after experience gained building the \CFA runtime system, \PAB{I convinced them} making tuple-types first-class seems to add little benefit.
388The main reason is that tuples usages are largely unstructured,
389\begin{cfa}
390[int, int] foo( int, int ); $\C[2in]{// unstructured function}$
391typedef [int, int] Pair; $\C{// tuple type}$
392Pair bar( Pair ); $\C{// structured function}$
393int x = 3, y = 4;
394[x, y] = foo( x, y ); $\C{// unstructured call}$
395Pair ret = [3, 4]; $\C{// tuple variable}$
396ret = bar( ret ); $\C{// structured call}\CRT$
397\end{cfa}
398Basically, creating the tuple-type @Pair@ is largely equivalent to creating a @struct@ type, and creating more types and names defeats the simplicity that tuples are trying to achieve.
399Furthermore, since operator overloading in \CFA is implemented by treating operators as overloadable functions, tuple types are very rarely used in a structured way.
400When a tuple-type expression appears in a function call (except assignment expressions, which are handled differently by mass- or multiple-assignment expansions), it is always flattened, and the tuple structure of function parameter is not considered a part of the function signatures.
401For example, these two prototypes for @foo@:
402\begin{cfa}
403void f( int, int );
404void f( @[@ int, int @]@ );
405f( 3, 4 ); // ambiguous call
406\end{cfa}
407have the same signature (a function taking two @int@s and returning nothing), and therefore invalid overloads.
408Note, the ambiguity error occurs at the call rather than at the second declaration of @f@, because it is possible to have multiple equivalent prototype definitions of a function.
409Furthermore, ordinary polymorphic type-parameters are not allowed to have tuple types.
410\begin{cfa}
411forall( T ) T foo( T );
412int x, y, z;
413[x, y, z] = foo( [x, y, z] ); // substitute tuple type for T
414\end{cfa}
415Without this restriction, the expression resolution algorithm can create too many argument-parameter matching options.
416For example, with multiple type parameters,
417\begin{cfa}
418forall( T, U ) void f( T, U );
419f( [1, 2, 3, 4] );
420\end{cfa}
421the call to @f@ can be interpreted as @T = [1]@ and @U = [2, 3, 4, 5]@, or @T = [1, 2]@ and @U = [3, 4, 5]@, and so on.
422The restriction ensures type checking remains tractable and does not take too long to compute.
423Therefore, tuple types are never present in any fixed-argument function calls, because of the flattening.
424
425\begin{comment}
426Date: Mon, 13 Jan 2025 10:09:06 -0500
427Subject: Re: structure / tuple
428To: "Peter A. Buhr" <pabuhr@uwaterloo.ca>
429CC: Andrew Beach <ajbeach@uwaterloo.ca>,
430 Michael Brooks <mlbrooks@uwaterloo.ca>,
431 Fangren Yu <f37yu@uwaterloo.ca>, Jiada Liang <j82liang@uwaterloo.ca>,
432 Alvin Zhang <alvin.zhang@uwaterloo.ca>,
433 Kyoung Seo <lseo@plg.uwaterloo.ca>
434From: Gregor Richards <gregor.richards@uwaterloo.ca>
435
436Languages support tuples to abbreviate syntax where the meaning of several
437values is obvious from context, such as returns from functions, or where the
438effort of creating a dedicated type is not worth the reward of using that type
439in exactly one location. The positions always have meanings which could be
440given names, and are only not given names for brevity. Whether that brevity is
441a good idea or not is the programmer's problem to deal with. I don't think
442there's any pragmatic value to tuples beyond brevity. (From a theoretical
443perspective, having the empty tuple is useful for type-theoretical reasons, and
444tuples are usually easier to reason about than structures, but that only
445applies to theoretical reasoning, not to actual programming.)
446
447Your distinction unstructured tuples could just as well be made for structs as
448well, if you had named arguments (or named returns?). Personally, I think that
449having these be a syntactic distinction is a mistake. Other languages return
450fully codified tuples, and if you immediately destructure them, even the most
451naive optimizer will manage to never create an actual tuple in memory. In my
452opinion, since tuples are for brevity, they should always be declared with your
453"unstructured" syntax, and it's up to the optimizer to realize when you've
454never stored them. But, you live closer to the metal in CFA than most
455languages, so perhaps communicating that intent is of sufficient value.
456
457The only value of tuples beyond that is to make it possible for annoying
458students to use std::pair in place of ever creating their own class hierarchy
459or naming things. Then again, I hear that that is one of the hard problems in
460computer science.
461
462With valediction,
463 - Gregor Richards
464
465On 1/13/25 09:11, Peter A. Buhr wrote:
466> The CFA team has been discussing the difference between a structure and
467> tuple. Basically, a structure has named fields and a tuple has anonymous
468> fields. As a result, structure access uses field names and tuple access uses
469> position.
470>
471> struct S { int i, j, k ; };
472> S s;
473> s.i; s.j; // field access
474>
475> tuple T { int, int };
476> T t;
477> t.0; t.1; // position access, zero origin
478> t[0]; t[1]; // alternate access
479>
480> Hence the difference is small.
481>
482> In CFA, we differentiate between unstructured and structured tuples. An
483> unstructured tuple is a lexical grouping of potentially disjoint variables.
484>
485> [ int, int, int ] f();
486> void g( int, int, int );
487> x, y, z = f(); // Go unstructured tuple, flatten tuple
488> g( foo() ); // flatten tuple
489>
490> Here, the tuple returned from f is flattened into disjoint variables. A
491> structured tuple is like above and has contiguous memory.
492>
493> CFA has fancy unstructured stuff like
494>
495> s.[i,k] += 1; // add 1 to each field
496> t.[1,0] = 1; // don't think this works but could
497>
498> which is just an unstructured tuple access (sugar).
499>
500> What is your opinion of structures and tuples since the difference is
501> small. Why do many languages support both features? Are we missing some
502> important aspect of tuples that differentiates them from structures? Is CFA
503> unique in having both unstructured and structured tuples?
504\end{comment}
505
506Finally, a type-safe variadic argument signature was added by Robert Schluntz~\cite[\S~4.1.2]{Schluntz17} using @forall@ and a new tuple parameter-type, denoted by the keyword @ttype@ in Schluntz's implementation, but changed to the ellipsis syntax similar to \CC's template parameter pack.
507For C variadics, \eg @va_list@, the number and types of the arguments must be conveyed in some way, \eg @printf@ uses a format string indicating the number and types of the arguments.
508\begin{cfa}
509int printf( const char * format, ${\color{red}\LARGE ...}$ ); // variadic list of variables to print
510\end{cfa}
511\VRef[Figure]{f:CVariadicMaxFunction} shows an $N$ argument @maxd@ function using the C untyped @va_list@ interface.
512In the example, the first argument is the number of following arguments, and the following arguments are assumed to be @double@;
513looping is used to traverse the argument pack from left to right.
514The @va_list@ interface is walking up the stack (by address) looking at the arguments pushed by the caller.
515(Compiler-specific ABI knowledge is needed for arguments pushed using registers.)
516
517\begin{figure}
518\begin{cfa}
519double maxd( int @count@, @...@ ) { // ellipse parameter
520 double max = 0;
521 va_list args;
522 va_start( args, count );
523 for ( int i = 0; i < count; i += 1 ) {
524 double num = va_arg( args, double );
525 if ( num > max ) max = num;
526 }
527 va_end(args);
528 return max;
529}
530printf( "%g\n", maxd( @4@, 25.0, 27.3, 26.9, 25.7 ) );
531\end{cfa}
532\caption{C Variadic Maximum Function}
533\label{f:CVariadicMaxFunction}
534\end{figure}
535
536There are two common patterns for using variadic functions in \CFA.
537\begin{enumerate}[leftmargin=*]
538\item
539Argument forwarding to another function, \eg:
540\begin{cfa}
541forall( T *, TT ... | { @void ?{}( T &, TT );@ } ) // constructor assertion
542T * new( TT tp ) { return ((T *)malloc())@{@ tp @}@; } // call constructor on storage
543\end{cfa}
544Note, the assertion on @T@ requires it to have a constructor @?{}@.
545The function @new@ calls @malloc@ to obtain storage and then invokes @T@'s constructor passing the tuple pack by flattening it over the constructor's arguments, \eg:
546\begin{cfa}
547struct S { int i, j; };
548void ?{}( S & s, int i, int j ) { s.[ i, j ] = [ i, j ]; } // constructor
549S * sp = new( 3, 4 ); // flatten [3, 4] into call ?{}( 3, 4 );
550\end{cfa}
551\item
552Structural recursion for processing the argument-pack values one at a time, \eg:
553\begin{cfa}
554forall( T | { int ?<?( T, T ); } )
555T max( T v1, T v2 ) { return v1 < v2 ? v2 : v1; }
556$\vspace{-10pt}$
557forall( T, TT ... | { T max( T, T ); T max( TT ); } )
558T max( T arg, TT args ) { return max( arg, max( args ) ); }
559\end{cfa}
560The first non-recursive @max@ function is the polymorphic base-case for the recursion, \ie, find the maximum of two identically typed values with a less-than (@<@) operator.
561The second recursive @max@ function takes two parameters, @T@ and the @TT@ tuple pack, handling all argument lengths greater than two.
562The recursive function computes the maximum for the first argument and the maximum value of the rest of the tuple pack.
563The call of @max@ with one argument is the recursive call, where the tuple pack is converted into two arguments by taking the first value (lisp @car@) from the tuple pack as the first argument (flattening) and the remaining pack becomes the second argument (lisp @cdr@).
564The recursion stops when the argument pack is empty.
565For example, @max( 2, 3, 4 )@ matches with the recursive function, which performs @return max( 2, max( [3, 4] ) )@ and one more step yields @return max( 2, max( 3, 4 ) )@, so the tuple pack is empty.
566\end{enumerate}
567
568As an aside, polymorphic functions are precise with respect to their parameter types, \eg @max@ states all argument values must be the same type, which logically makes sense.
569However, this precision precludes normal C conversions among the base types, \eg, this mix-mode call @max( 2h, 2l, 3.0f, 3.0ld )@ fails because the types are not the same.
570Unfortunately, this failure violates programmer intuition because there are specialized two-argument non-polymorphic versions of @max@ that work, \eg @max( 3, 3.5 )@.
571Allowing C conversions for polymorphic types will require a significant change to the type resolver.
572
573Currently in \CFA, variadic polymorphic functions are the only place tuple types are used.
574\PAB{My analysis showed} many wrapper functions are generated to implement both user-defined generic-types and polymorphism with variadics, because \CFA compiles polymorphic functions versus template expansion.
575Fortunately, the only permitted operations on polymorphic function parameters are given by the list of assertion (trait) functions.
576Nevertheless, this small set of functions eventually needs to be called with flattened tuple arguments.
577Unfortunately, packing the variadic arguments into a rigid @struct@ type and generating all the required wrapper functions is significant work and largely wasted because most are never called.
578Interested readers can refer to pages 77-80 of Robert Schluntz's thesis to see how verbose the translator output is to implement a simple variadic call with 3 arguments.
579As the number of arguments increases, \eg a call with 5 arguments, the translator generates concrete @struct@ types for a 4-tuple and a 3-tuple along with all the polymorphic type data for them.
580An alternative approach is to put the variadic arguments into an array, along with an offset array to retrieve each individual argument.
581This method is similar to how the C @va_list@ object is used (and how \CFA accesses polymorphic fields in a generic type), but the \CFA variadics generate the required type information to guarantee type safety (like the @printf@ format string).
582For example, given the following heterogeneous, variadic, typed @print@ and usage:
583\begin{cquote}
584\begin{tabular}{@{}ll@{}}
585\begin{cfa}
586forall( T, TT ... | { void print( T ); void print( TT ); } )
587void print( T arg , TT rest ) {
588 print( arg );
589 print( rest );
590}
591\end{cfa}
592&
593\begin{cfa}
594void print( int i ) { printf( "%d ", i ); }
595void print( double d ) { printf( "%g ", d ); }
596... // other types
597int i = 3 ; double d = 3.5;
598print( i, d );
599\end{cfa}
600\end{tabular}
601\end{cquote}
602it would look like the following using the offset-array implementation approach.
603\begin{cfa}
604void print( T arg, char * _data_rest, size_t * _offset_rest ) {
605 print( arg );
606 print( *((typeof rest.0)*) _data_rest, $\C{// first element of rest}$
607 _data_rest + _offset_rest[0], $\C{// remainder of data}$
608 _offset_rest + 1); $\C{// remainder of offset array}$
609}
610\end{cfa}
611where the fixed-arg polymorphism for @T@ can be handled by the standard @void *@-based \CFA polymorphic calling conventions, and the type information can be deduced at the call site.
612Note, the variadic @print@ supports heterogeneous types because the polymorphic @T@ is not returned (unlike variadic @max@), so there is no cascade of type relationships.
613
614Turning tuples into first-class values in \CFA does have a few benefits, namely allowing pointers to tuples and arrays of tuples to exist.
615However, it seems unlikely that these types have realistic use cases that cannot be achieved with structures.
616And having a pointer-to-tuple type potentially forbids the simple offset-array implementation of variadic polymorphism.
617For example, in the case where a type assertion requests the pointer type @TT *@ in the above example, it forces the tuple type to be a @struct@, and thus incurring a high cost.
618My conclusion is that tuples should not be structured (first-class), rather they should be unstructured.
619This agrees with Rodolfo's original description:
620\begin{quote}
621As such, their [tuples] use does not enforce a particular memory layout, and in particular, does not guarantee that the components of a tuple occupy a contiguous region of memory.~\cite[pp.~74--75]{Esteves04}
622\end{quote}
623allowing the simplified implementations for MVR and variadic functions.
624
625Finally, a missing feature for tuples is using an MVR in an initialization context.
626Currently, this feature is \emph{only} possible when declaring a tuple variable.
627\begin{cfa}
628[int, int] ret = gives_two(); $\C{// no constructor call (unstructured)}$
629Pair ret = gives_two(); $\C{// constructor call (structured)}$
630\end{cfa}
631However, this forces the programer to use a tuple variable and possibly a tuple type to support a constructor, when they actually want separate variables with separate constructors.
632And as stated previously, type variables (structured tuples) are rare in general \CFA programming so far.
633To address this issue, while retaining the ability to leverage constructors, I proposed the following new tuple-like declaration syntax.
634\begin{cfa}
635[ int x, int y ] = gives_two();
636\end{cfa}
637where the semantics is:
638\begin{cfa}
639T t0, t1;
640[ t0, t1 ] = gives_two();
641T x = t0; // constructor call
642T y = t1; // constructor call
643\end{cfa}
644and the implementation performs as much copy elision as possible.
645Currently, this new declaration form is parsed by \CFA, showing its syntax is viable, but it is unimplemented because of downstream resolver issues.
646
647
648\section{\lstinline{inline} Substructure}
649\label{s:inlineSubstructure}
650
651As mentioned, C allows an anonymous aggregate type (@struct@ or @union@) to be embedded (nested) within another one \see{\VRef[Figure]{f:Nesting}}, \eg a tagged union.
652\begin{cfa}
653struct S {
654 unsigned int tag;
655 union { // anonymous nested aggregate
656 int x; double y; char z;
657 };
658} s;
659\end{cfa}
660Here, the @union@ combines its field into a common block of storage, and because there is no variable-name overloading in C, all of the union field names must be unique.
661Furthermore, because the union is unnamed, these field-names are hoisted into the @struct@, giving direct access, \eg @s.x@;
662hence, the union field names must be unique with the structure field names.
663The same semantics applies to a nested anonymous @struct@:
664\begin{cquote}
665\begin{tabular}{@{}l@{\hspace{35pt}}l@{}}
666original & rewritten \\
667\begin{cfa}
668struct S {
669 struct { int i, j; };
670 struct { int k, l; };
671};
672\end{cfa}
673&
674\begin{cfa}
675struct S {
676 int i, j;
677 int k, l;
678};
679\end{cfa}
680\end{tabular}
681\end{cquote}
682However, unlike the union which provides storage sharing, there is no semantic difference between the nested anonymous structure and its rewritten counterpart.
683Hence, the nested anonymous structure provides no useful capability.
684
685Nested \emph{named} aggregates are allowed in C but there is no qualification operator, like the \CC type operator `@::@', to access an inner type.
686To compensate for the missing type operator, all named nested aggregates are hoisted to global scope, regardless of the nesting depth, and type usages within the nested type are replaced with global type name.
687Hoisting nested types can result in name collisions among types at the global level, which defeats the purpose of nesting the type.
688\VRef[Figure]{f:NestedNamedAggregate} shows the nested type @T@ is hoisted to the global scope and the declaration rewrites within structure @S@.
689Hence, the possible accesses are:
690\begin{cfa}
691struct S s;
692s.i = 1;
693s.t.i = 2;
694s.w = (struct T){ 7, 8 };
695struct T x = { 5, 6 }; // use (un)nested type name
696s.t = (struct T){ 2, 3 };
697\end{cfa}
698where @T@ is used without qualification even though it is nested in @S@.
699It is for these reasons that nested types are not used in C, and if used, are extremely confusing.
700
701\begin{figure}
702\begin{cquote}
703\begin{tabular}{@{}l@{\hspace{35pt}}l@{}}
704original & rewritten \\
705\begin{cfa}
706struct S {
707 @struct T@ {
708 int i, j;
709 } t; // warning without declaration
710 struct T w;
711 int k;
712};
713
714\end{cfa}
715&
716\begin{cfa}
717@struct T@ {
718 int i, j;
719};
720struct S {
721 @struct T t@;
722 struct T w;
723 int k;
724};
725\end{cfa}
726\end{tabular}
727\end{cquote}
728\caption{Nested Named Aggregate}
729\label{f:NestedNamedAggregate}
730\end{figure}
731
732\CC chose to change this semantics:
733\begin{cquote}
734\begin{description}[leftmargin=*,topsep=0pt,itemsep=0pt,parsep=0pt]
735\item[Change:] A struct is a scope in C++, not in C.
736\item[Rationale:] Class scope is crucial to C++, and a struct is a class.
737\item[Effect on original feature:] Change to semantics of well-defined feature.
738\item[Difficulty of converting:] Semantic transformation.
739\item[How widely used:] C programs use @struct@ extremely frequently, but the change is only noticeable when @struct@, enumeration, or enumerator names are referred to outside the @struct@.
740The latter is probably rare.
741\end{description}
742\hfill ISO/IEC 14882:1998 (\CC Programming Language Standard)~\cite[C.1.2.3.3]{ANSI98:C++}
743\end{cquote}
744\CFA chose to adopt the \CC non-compatible change for nested types, since \CC's change has already forced certain coding changes in C libraries that must be parsed by \CC.
745\CFA also added the ability to access from a variable through a type to a field.
746\begin{cfa}
747struct S s; @s.i@; @s.T@.i;
748\end{cfa}
749See the use case for this feature at the end of this section.
750
751% https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html
752
753A polymorphic extension to nested aggregates appears in the Plan-9 C dialect, used in the Bell Labs' Plan-9 research operating-system.
754The feature is called \newterm{unnamed substructures}~\cite[\S~3.3]{Thompson90new}, which continues to be supported by @gcc@ and @clang@ using the extension (@-fplan9-extensions@).
755The goal is to provided the same effect as a nested aggregate with the aggregate type defined elsewhere, which requires it be named.
756\begin{cfa}
757union U { $\C{// unnested named}$
758 int x; double y; char z;
759} u;
760struct W {
761 int i; double j; char k;
762} w;
763struct S {
764 @struct W;@ $\C{// Plan-9 substructure}$
765 unsigned int tag;
766 @union U;@ $\C{// Plan-9 substructure}$
767} s;
768\end{cfa}
769Note, the position of the substructure is normally unimportant, unless there is some form of memory or @union@ overlay.
770Like an anonymous nested type, a named Plan-9 nested type has its field names hoisted into @struct S@, so there is direct access, \eg @s.x@ and @s.i@.
771Hence, the field names must be unique, unlike \CC nested types, but the type names are at a nested scope level, unlike type nesting in C.
772In addition, a pointer to a structure is automatically converted to a pointer to an anonymous field for assignments and function calls, providing containment inheritance with implicit subtyping, \ie @U@ $<:$ @S@ and @W@ $<:$ @S@, \eg:
773\begin{cfa}
774void f( union U * u );
775void g( struct W * );
776union U * up; struct W * wp; struct S * sp;
777up = &s; $\C{// assign pointer to U in S}$
778wp = &s; $\C{// assign pointer to W in S}$
779f( &s ); $\C{// pass pointer to U in S}$
780g( &s ); $\C{// pass pointer to W in S}$
781\end{cfa}
782Note, there is no value assignment, such as, @w = s@, to copy the @W@ field from @S@.
783
784Unfortunately, the Plan-9 designers did not look ahead to other useful features, specifically nested types.
785This nested type compiles in \CC and \CFA.
786\begin{cfa}
787struct R {
788 @struct T;@ $\C[2in]{// forward declaration, conflicts with Plan-9 syntax}$
789 struct S { $\C{// nested types, mutually recursive reference}\CRT$
790 S * sp; T * tp; ...
791 };
792 struct T {
793 S * sp; T * tp; ...
794 };
795};
796\end{cfa}
797Note, the syntax for the forward declaration conflicts with the Plan-9 declaration syntax.
798
799\CFA extends the Plan-9 substructure by allowing polymorphism for values and pointers, where the extended substructure is denoted using @inline@.
800\begin{cfa}
801struct S {
802 @inline@ struct W; $\C{// extended Plan-9 substructure}$
803 unsigned int tag;
804 @inline@ U; $\C{// extended Plan-9 substructure}$
805} s;
806\end{cfa}
807Note, the declaration of @U@ is not prefixed with @union@.
808Like \CC, \CFA allows optional prefixing of type names with their kind, \eg @struct@, @union@, and @enum@, unless there is ambiguity with variable names in the same scope.
809In addition, a semi-non-compatible change is made so that Plan-9 syntax means a forward declaration in a nested type.
810Since the Plan-9 extension is not part of C and rarely used, this change has minimal impact.
811Hence, all Plan-9 semantics are denoted by the @inline@ qualifier, which clearly indicates the usage of Plan-9 definitions.
812Finally, the following code shows the value and pointer polymorphism.
813\begin{cfa}
814void f( U, U * ); $\C{// value, pointer}$
815void g( W, W * ); $\C{// value, pointer}$
816U u, * up; S s, * sp; W w, * wp;
817u = s; up = sp; $\C{// value, pointer}$
818w = s; wp = sp; $\C{// value, pointer}$
819f( s, &s ); $\C{// value, pointer}$
820g( s, &s ); $\C{// value, pointer}$
821\end{cfa}
822
823In general, non-standard C features (@gcc@) do not need any special treatment, as they are directly passed through to the C compiler.
824However, \PAB{I found} the Plan-9 semantics allow implicit conversions from the outer type to the inner type, which means the \CFA type resolver must take this information into account.
825Therefore, the \CFA resolver must implement the Plan-9 features and insert necessary type conversions into the translated code output.
826In the current version of \CFA, this is the only kind of implicit type conversion other than the standard C arithmetic conversions.
827
828Plan-9 polymorphism can result in duplicate field names.
829For example, the \newterm{diamond pattern}~\cite[\S~6.1]{Stroustrup89}\cite[\S~4]{Cargill91} can result in nested fields being embedded twice.
830\begin{cfa}
831struct A { int x; };
832struct B { inline A; };
833struct C { inline A; };
834struct D {
835 inline B; // B.x
836 inline C; // C.x
837} d;
838\end{cfa}
839Because the @inline@ structures are flattened, the expression @d.x@ is ambiguous, as it can refer to the embedded field either from @B@ or @C@.
840@gcc@ generates a syntax error about the duplicate member @x@.
841The equivalent \CC definition compiles:
842\begin{c++}
843struct A { int x; };
844struct B : public A {};
845struct C : public A {};
846struct D : @public B, C@ { // multiple inheritance
847} d;
848\end{c++}
849and again the expression @d.x@ is ambiguous.
850While \CC has no direct syntax to disambiguate @x@, \eg @d.B.x@ or @d.C.x@, it is possible with casts, @((B)d).x@ or @((C)d).x@.
851Like \CC, \CFA compiles the Plan-9 version and provides direct qualification and casts to disambiguate @x@.
852While ambiguous definitions are allowed, duplicate field names are poor practice and should be avoided if possible.
853However, when a programmer does not control all code, this problem can occur and a naming workaround must exist.
Note: See TracBrowser for help on using the repository browser.