Ignore:
Timestamp:
Nov 5, 2024, 7:27:44 PM (10 days ago)
Author:
Michael Brooks <mlbrooks@…>
Branches:
master
Children:
5ba756c
Parents:
7d95bce9
Message:

Thesis, array: add section on lifecycle

File:
1 edited

Legend:

Unmodified
Added
Removed
  • doc/theses/mike_brooks_MMath/array.tex

    r7d95bce9 r97ac01d3  
    10921092
    10931093
    1094 
     1094\section{Array lifecycle}
     1095
     1096An array is an aggregate, like a struct.
     1097Both wrap subordinate objects.
     1098An arbitrary other type, like @string@, can be used as a struct member or an array element.
     1099When it is, that type's lifecycle assumptions must be respected.
     1100In the case of @string@, it would be disasterous to start calling string-management functions on uninitialized storage.
     1101The @string@ type provides its constructors and destructor;
     1102\CFA is supposed to provide a reasonable assurance that they will be called, even if the string is wrapped in something else, even if the user-programmer isn't thinking about it.
     1103
     1104Preexisting \CFA mechanisms achieve this requirement in correctness, but not with reasonable performance.
     1105Furthermore, advanced users of arrays need an exception to the bluntly-stated requirement, which does not come up when working with other aggregates.
     1106So the \CFA array has subtleties in its support for element lifecycle.
     1107
     1108The preexisting \CFA support for contained-element lifecycle is based on a recursive implementation of the object-type (otype) pseudo-trait.
     1109A type is an otype if it provides a parameterless constructor, copy constructor, assignment operator and destructor.
     1110When declaring a struct whose members are all otypes, the compiler generates implementations of the four otype functions for the outer struct.
     1111The generated parameterless constructor for the outer struct calls the parameterless constructor for each memeber, and the other otype functions work similarly.
     1112For a member that is a C array, these calls occur in loops, which work correctly for dynamic bounds.
     1113This logic works the same, whether the member is a concrete type (that happens to be an otype) or if the member is a polymorphic type variable asserted to be an otype (which is implicit in the default syntax, @forall(T)@).
     1114The \CFA array has the simplified form (similar to one seen before):
     1115\begin{cfa}
     1116forall( T* )   // non-otype element, no lifecycle functions
     1117// forall( T )  // otype element, lifecycle functions asserted
     1118struct array5 {
     1119        T __items[ 5 ];
     1120};
     1121\end{cfa}
     1122Being a struct with a C-array member, using the otype-form declaration for @T@ causes @array5(float)@ to implement otype too.
     1123
     1124But this otype-recursion pattern has a performance issue.
     1125A cube of @float@ is, in the running simplification:
     1126\begin{cfa}
     1127array5( array5( array5( float ) ) )
     1128\end{cfa}
     1129Its lifecycle functions, under the otype-recursion pattern, would be:
     1130\begin{cfa}
     1131TODO: rework this crude exposition into a readable figure
     1132
     1133float offers
     11341 parameterless ctor
     11352 copy ctor
     11363 asgt
     11374 dtor
     1138
     1139array5(T) offers
     11401 parameterless ctor, which asks for T to have
     1141        1 parameterless ctor
     1142        2 copy ctor
     1143        3 asgt
     1144        4 dtor
     11452 copy ctor, which asks for T to have
     1146        1 parameterless ctor
     1147        2 copy ctor
     1148        3 asgt
     1149        4 dtor
     11503 asgt, which asks for T to have
     1151        1 parameterless ctor
     1152        2 copy ctor
     1153        3 asgt
     1154        4 dtor
     11554 dtor, which asks for T to have
     1156        1 parameterless ctor
     1157        2 copy ctor
     1158        3 asgt
     1159        4 dtor
     1160
     1161array5(float) has
     11621 parameterless ctor, which gets float's
     1163        1 parameterless ctor
     1164        2 copy ctor
     1165        3 asgt
     1166        4 dtor
     11672 copy ctor, which gets float's
     1168        1 parameterless ctor
     1169        2 copy ctor
     1170        3 asgt
     1171        4 dtor
     11723 asgt, which gets float's
     1173        1 parameterless ctor
     1174        2 copy ctor
     1175        3 asgt
     1176        4 dtor
     11774 dtor, which gets float's
     1178        1 parameterless ctor
     1179        2 copy ctor
     1180        3 asgt
     1181        4 dtor
     1182
     1183array5(array5(float)) has
     11841 parameterless ctor, which gets array5(float)'s
     1185        1 parameterless ctor, which gets float's
     1186                1 parameterless ctor
     1187                2 copy ctor
     1188                3 asgt
     1189                4 dtor
     1190        2 copy ctor, which gets float's
     1191                1 parameterless ctor
     1192                2 copy ctor
     1193                3 asgt
     1194                4 dtor
     1195        3 asgt, which gets float's
     1196                1 parameterless ctor
     1197                2 copy ctor
     1198                3 asgt
     1199                4 dtor
     1200        4 dtor, which gets float's
     1201                1 parameterless ctor
     1202                2 copy ctor
     1203                3 asgt
     1204                4 dtor
     12052 copy ctor, which gets array5(float)'s
     1206        ... 4 children, 16 leaves
     12073 asgt, which gets array5(float)'s
     1208        ... 4 children, 16 leaves
     12094 dtor, which gets array5(float)'s
     1210        ... 4 children, 16 leaves
     1211(64 leaves)
     1212
     1213array5(array5(array5(float))) has
     1214...(256 leaves)
     1215\end{cfa}
     1216
     1217Each xxx corresponds to a generated thunk.
     1218So the otype-recursion pattern generates a quantity of thunks that is exponential in the number of dimensions.
     1219Anecdotal experience with this solutuion found the resulting compile times annoying at three dimensions, and unusable at four.
     1220
     1221But the otype-recursion pattern uses more assertions than are really needed.
     1222Consider how @array5(float)@'s parameterless constructor is getting all four lifecycle assertions about the element type, @float@.
     1223It only needs @float@'s parameterless constructor; it is getting a destructor (among others) and never using it.
     1224The same idea applies to the copy constructor and the destructor.
     1225For the assignment operator, it turns out that today's RAII pattern has it using a copy constructor, assignment operator and destructor.
     1226Concurrent work on the \CFA team aims to improve this whole situation.
     1227A workaround is needed for now.
     1228
     1229The workaround is to provide parameterless constructor, copy constructor and destructor, defined with lean, bespoke assertions:
     1230
     1231\noindent
     1232\begin{tabular}{@{}l@{\hspace{0.5in}}l@{}}
     1233\begin{cfa}
     1234// autogenerated for otype-recursion:
     1235forall( T )
     1236void ?{}( array5(T) & this ) {
     1237        for (i; 5) { ( this[i] ){}; }
     1238}
     1239forall( T )
     1240void ?{}( array5(T) & this, array5(T) & src ) {
     1241        for (i; 5) { ( this[i] ){ src[i] }; }
     1242}
     1243forall( T )
     1244void ^?{}( array5(T) & this ) {
     1245        for (i; 5) { ^( this[i] ){}; }
     1246}
     1247\end{cfa}
     1248&
     1249\begin{cfa}
     1250// lean, bespoke:
     1251forall( T* | { void @?{}( T & )@; } )
     1252void ?{}( array5(T) & this ) {
     1253        for (i; 5) { ( this[i] ){}; }
     1254}
     1255forall( T* | { void @?{}( T &, T )@; } )
     1256void ?{}( array5(T) & this, array5(T) & src ) {
     1257        for (i; 5) { ( this[i] ){ src[i] }; }
     1258}
     1259forall( T* | { void @?{}( T & )@; } )
     1260void ^?{}( array5(T) & this ) {
     1261        for (i; 5) { ^( this[i] ){}; }
     1262}
     1263\end{cfa}
     1264\end{tabular}
     1265Moreover, the assignment operator is skipped, because array assignment is not a common operation.
     1266This way, the critical lifecycle functions are available, with no growth in thunk creation.
     1267
     1268Finally, the intuition that a programmer using an array always wants element constructors called \emph{automatically} is simplistic.
     1269Arrays exist to store different values at each position.
     1270So, an array initialization, in general, needs to let the prorammer provide different constructor arguments to each element.
     1271\begin{cfa}
     1272thread worker;
     1273void ?{}( worker & ) = void;
     1274void ?{}( worker &, int worker_num );
     1275array(worker, 5) ws @= ???@;
     1276for (i; 5) ( ws[i] ){ @i@ };
     1277\end{cfa}
     1278
     1279Two \CFA features may, at first, seem viable for initializing the array @ws@, but on closer inspection, they are not.
     1280\begin{itemize}
     1281\item
     1282        Empty initializer / default constructor: \lstinline{array(worker, 5) ws;}.
     1283        This option does not work with the semantics of a thread: it has forked as soon as the constructor returns.
     1284        The type does not provide a parameterless constructor because it is not possible to fork the thread until the @worker_num@ parameter is received.
     1285\item
     1286        C initialization: \lstinline{array(worker, 5) ws AT EQUALS OPEN CLOSE;} (TODO: typeset at-equals).
     1287        This option does achieve the desired semantics on the construction side.
     1288        But destruction side, the desired semantics is for implicit destructor calls to continue, because with threads, the destructor is a join operation.
     1289        C initialization disables \emph{all} implicit lifecycle management; here, the goal is to disable only the implicit construction.
     1290\end{itemize}
     1291
     1292So, I enhanced the \CFA standard library to provide the missing semantics, available in either form:
     1293\begin{cfa}
     1294array(@uninit@(worker), 5) ws1;
     1295array(worker, 5) ws2 = { @delay_init@ };
     1296\end{cfa}
     1297Both cause the @ws@-construction-time implicit call chain to stop before reaching a @worker@ constructor, while leaving the implicit destruction calls intact.
     1298Two forms are available, to parallel the development of this feature in \uCpp.
     1299Originally \uCpp offered only the @ws1@ form, there with the class-tempate @uNoCtor@ replacing \CFA's @uninit@.
     1300More recently, \uCpp got a declaration-helping macro, @uArray@, whose usage similar to the @ws2@ case.
     1301Based on results of piloting @uArray@ (as a replacement of @uNoCtor@), future work may choose to remove one of the options.
     1302
     1303% note to Mike, I have more fragments on some details available in push24\fragments\uNoCtor.txt
    10951304
    10961305\section{Comparison with other arrays}
Note: See TracChangeset for help on using the changeset viewer.