Changeset b195498 for doc/theses/mike_brooks_MMath/background.tex
- Timestamp:
- Apr 24, 2025, 6:35:41 PM (5 months ago)
- Branches:
- master
- Children:
- 6b33e89, f85de47
- Parents:
- f632bd50
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
doc/theses/mike_brooks_MMath/background.tex
rf632bd50 rb195498 4 4 5 5 6 \section{Ill- typed expressions}6 \section{Ill-Typed Expressions} 7 7 8 8 C reports many ill-typed expressions as warnings. … … 24 24 Clearly, @gcc@ understands these ill-typed case, and yet allows the program to compile, which seems inappropriate. 25 25 Compiling with flag @-Werror@, which turns warnings into errors, is often too pervasive, because some warnings are just warnings, \eg an unused variable. 26 In the following discussion, ``ill-typed''means giving a nonzero @gcc@ exit condition with a message that discusses typing.26 In the following discussion, \emph{ill-typed} means giving a nonzero @gcc@ exit condition with a message that discusses typing. 27 27 Note, \CFA's type-system rejects all these ill-typed cases as type mismatch errors. 28 28 … … 37 37 % *1 TAPL-pg1 definition of a type system 38 38 39 40 \section{Reading declarations} 41 42 A significant area of confusion for reading C declarations results from: 43 \begin{itemize} 39 % reading C declaration: https://c-faq.com/decl/spiral.anderson.html 40 41 42 \section{Reading Declarations} 43 44 A significant area of confusion is reading C declarations, which results from interesting design choices. 45 \begin{itemize}[leftmargin=*] 44 46 \item 45 C is unique in having dimension be higher priority than pointer in declarations.\footnote{ 46 For consistency, subscript has higher priority than dereference, yielding \lstinline{(*arp)[3]} rather than \lstinline{*arp[3]}.} 47 In C, it is possible to have a value and a pointer to it. 48 \begin{cfa} 49 int i = 3, * pi = &i; 50 \end{cfa} 51 Extending this idea, it should be possible to have an array of values and pointer to it. 52 \begin{cfa} 53 int a[5] = { 1, 2, 3, 4, 5 }, * pa[5] = &a; 54 \end{cfa} 55 However, the declaration of @pa@ is incorrect because dimension has higher priority than pointer, so the declaration means an array of 5 pointers to integers. 56 The declarations for the two interpretations of @* [5]@ are: 57 \begin{cquote} 58 \begin{tabular}[t]{@{}ll@{\hspace{15pt}}|@{\hspace{15pt}}ll@{}} 59 \begin{cfa} 60 int (* pa)[5] 61 \end{cfa} 62 & 63 \raisebox{-0.4\totalheight}{\includegraphics{PtrToArray.pdf}} 64 & 65 \begin{cfa} 66 int * ap[5] 67 \end{cfa} 68 & 69 \raisebox{-0.75\totalheight}{\includegraphics{ArrayOfPtr.pdf}} 70 \end{tabular} 71 \end{cquote} 72 If the priorities of dimension and pointer were reversed, the declarations become more intuitive: @int * pa[5]@ and @int * (ap[5])@. 47 73 \item 48 Embedding a declared variable in a declaration, mimics the way the variable is used in executable statements. 74 This priority inversion extends into an expression between dereference and subscript, so usage syntax mimics declaration. 75 \begin{cquote} 76 \setlength{\tabcolsep}{20pt} 77 \begin{tabular}{@{}ll@{}} 78 \begin{cfa} 79 int (* pa)[5] 80 (*pa)[i] += 1; 81 \end{cfa} 82 & 83 \begin{cfa} 84 int * ap[5] 85 *ap[i] += 1; 86 \end{cfa} 87 \end{tabular} 88 \end{cquote} 89 (\VRef{s:ArraysDecay} shows pointer decay allows the first form to be written @pa[i] += 1@, which is further syntax confusion.) 90 Again, if the priorities were reversed, the expressions become more intuitive: @*pa[i] += 1@ and @*(ap[i]) += 1@. 91 Note, a similar priority inversion exists between deference @*@ and field selection @.@ (period), so @*ps.f@ means @*(ps.f)@; 92 this anomaly is \emph{fixed} with operator @->@, which performs the two operations in the more intuitive order: @sp->f@ $\Rightarrow$ @(*sp).f@. 49 93 \end{itemize} 50 \begin{cquote} 51 \begin{tabular}{@{}ll@{}} 52 \multicolumn{1}{@{}c}{\textbf{Array}} & \multicolumn{1}{c@{}}{\textbf{Function Pointer}} \\ 53 \begin{cfa} 54 int @(*@ar@)[@5@]@; // definition 55 ... @(*@ar@)[@3@]@ += 1; // usage 56 \end{cfa} 57 & 58 \begin{cfa} 59 int @(*@f@())[@5@]@ { ... }; // definition 60 ... @(*@f@())[@3@]@ += 1; // usage 61 \end{cfa} 62 \end{tabular} 63 \end{cquote} 64 The parenthesis are necessary to achieve a pointer to a @T@, and the type is wrapped around the name in successive layers (like an \Index{onion}) to match usage in an expression. 65 While attempting to make the two contexts consistent is a laudable goal, it has not worked out in practice, even though Dennis Richie believed otherwise: 94 While attempting to make the declaration and expression contexts consistent is a laudable goal, it has not worked out in practice, even though Dennis Richie believed otherwise: 66 95 \begin{quote} 67 96 In spite of its difficulties, I believe that the C's approach to declarations remains plausible, and am comfortable with it; it is a useful unifying principle.~\cite[p.~12]{Ritchie93} 68 97 \end{quote} 69 98 After all, reading a C array type is easy: just read it from the inside out, and know when to look left and when to look right! 70 71 \CFA provides its own type, variable and routine declarations, using a simpler syntax. 99 Unfortunately, \CFA cannot correct these operator priority inversions without breaking C compatibility. 100 101 The alternative solution is for \CFA to provide its own type, variable and routine declarations, using a more intuitive syntax. 72 102 The new declarations place qualifiers to the left of the base type, while C declarations place qualifiers to the right of the base type. 73 The qualifiers have the same syntax and semantics in \CFA as in C .74 Then, a \CFA declaration is read left to right, where a function return 103 The qualifiers have the same syntax and semantics in \CFA as in C, so there is nothing to learn. 104 Then, a \CFA declaration is read left to right, where a function return-type is enclosed in brackets @[@\,@]@. 75 105 \begin{cquote} 76 106 \begin{tabular}{@{}l@{\hspace{3em}}ll@{}} … … 98 128 \end{tabular} 99 129 \end{cquote} 100 As declaration size increases, it becomes corresponding difficult to read and understand the C declaration form, whereas reading and understanding a \CFA declaration has linear complexity as the declaration size increases.130 As declaration size increases, it becomes corresponding difficult to read and understand the C form, whereas reading and understanding a \CFA declaration has linear complexity. 101 131 Note, writing declarations left to right is common in other programming languages, where the function return-type is often placed after the parameter declarations, \eg \CC \lstinline[language=C++]{auto f( int ) -> int}. 102 Unfortunately, \CFA cannot interchange the priorities of subscript and dereference in expressions without breaking C compatibility. 132 (Note, putting the return type at the end deviates from where the return value logically appears in an expression, @x = f(...)@ versus @f(...) = x@.) 133 Interestingly, programmers normally speak a declaration from left to right, regardless of how it is written. 134 (It is unclear if Hebrew or Arabic speakers, say declarations right to left.) 103 135 104 136 \VRef[Table]{bkgd:ar:usr:avp} introduces the many layers of the C and \CFA array story, where the \CFA story is discussion in \VRef[Chapter]{c:Array}. … … 168 200 The conjoining of pointers and arrays could also be applied to structures, where a pointer references a structure field like an array element. 169 201 Finally, while subscripting involves pointer arithmetic (as does a field reference @x.y.z@), the computation is complex for multi-dimensional arrays and requires array descriptors to know stride lengths along dimensions. 170 Many C errors result from manually performing pointer arithmetic instead of using language subscripting, letting the compiler perform any arithmetic. 171 172 Some C textbooks erroneously suggest manual pointer arithmetic is faster than subscripting. 173 A sound and efficient C program does not require explicit pointer arithmetic. 174 TODO: provide an example, explain the belief, and give modern refutation 175 176 C semantics wants a programmer to \emph{believe} an array variable is a ``pointer to its first element.'' 202 Many C errors result from manually performing pointer arithmetic instead of using language subscripting so the compiler performs the arithmetic. 203 204 Some modern C textbooks and web sites erroneously suggest manual pointer arithmetic is faster than subscripting. 205 When compiler technology was young, this statement might have been true. 206 However, a sound and efficient C program coupled with a modern C compiler does not require explicit pointer arithmetic. 207 For example, the @gcc@ compiler at @-O3@ generates identical code for the following two summation loops. 208 \begin{cquote} 209 \vspace*{-10pt} 210 \begin{cfa} 211 int a[1000], sum; 212 \end{cfa} 213 \setlength{\tabcolsep}{20pt} 214 \begin{tabular}{@{}ll@{}} 215 \begin{cfa} 216 for ( int i = 0; i < 1000; i += 1 ) { 217 sum += a[i]; 218 } 219 \end{cfa} 220 & 221 \begin{cfa} 222 for ( int * ip = a ; ip < &a[1000]; ip += 1 ) { 223 sum += *ip; 224 } 225 \end{cfa} 226 \end{tabular} 227 \end{cquote} 228 I believe it is possible to refute any code examples purporting to show pointer arithmetic is faster than subscripting. 229 This believe stems from the performance work I did on \CFA arrays, where it is possible to generate equivalent \CFA subscripting and performance to C subscripting. 230 231 Unfortunately, C semantics want a programmer to \emph{believe} an array variable is a \emph{pointer to its first element}. 177 232 This desire becomes apparent by a detailed inspection of an array declaration. 178 233 \lstinput{34-34}{bkgd-carray-arrty.c} … … 224 279 225 280 226 \subsection{Arrays decay and pointers diffract} 281 \subsection{Arrays Decay and Pointers Diffract} 282 \label{s:ArraysDecay} 227 283 228 284 The last section established the difference among these four types: … … 247 303 Thus, subscripting happens on pointers not arrays. 248 304 249 Subscripting proceeds first with pointer decay, if needed. Next, \cite[\S~6.5.2.1.2]{C11} explains that @ar[i]@ is treated as if it were @(*((a)+(i)))@. 250 \cite[\S~6.5.6.8]{C11} explains that the addition, of a pointer with an integer type, is defined only when the pointer refers to an element that is in an array, with a meaning of ``@i@ elements away from,'' which is valid if @ar@ is big enough and @i@ is small enough. 305 Subscripting proceeds first with pointer decay, if needed. 306 Next, \cite[\S~6.5.2.1.2]{C11} explains that @ar[i]@ is treated as if it were @(*((a)+(i)))@. 307 \cite[\S~6.5.6.8]{C11} explains that the addition, of a pointer with an integer type, is defined only when the pointer refers to an element that is in an array, with a meaning of @i@ elements away from, which is valid if @ar@ is big enough and @i@ is small enough. 251 308 Finally, \cite[\S~6.5.3.2.4]{C11} explains that the @*@ operator's result is the referenced element. 252 309 Taken together, these rules illustrate that @ar[i]@ and @i[a]@ mean the same thing! … … 285 342 \end{cfa} 286 343 287 The shortened parameter syntax @T x[]@ is a further way to spell ``pointer.''344 The shortened parameter syntax @T x[]@ is a further way to spell \emph{pointer}. 288 345 Note the opposite meaning of this spelling now, compared with its use in local variable declarations. 289 346 This point of confusion is illustrated in: … … 321 378 \begin{table} 322 379 \caption{Syntactic Reference for Decay during Parameter-Passing. 323 Includes interaction with \lstinline{const}ness, where ``immutable''refers to a restriction on the callee's ability.}380 Includes interaction with \lstinline{const}ness, where \emph{immutable} refers to a restriction on the callee's ability.} 324 381 \label{bkgd:ar:usr:decay-parm} 325 382 \centering … … 357 414 358 415 359 \subsection{Variable Length Arrays}416 \subsection{Variable-length Arrays} 360 417 361 418 As of C99, the C standard supports a \newterm{variable length array} (VLA)~\cite[\S~6.7.5.2.5]{C99}, providing a dynamic-fixed array feature \see{\VRef{s:ArrayIntro}}. … … 379 436 % TODO: introduce multidimensional array feature and approaches 380 437 381 When working with arrays, \eg linear algebra, array dimensions are referred to as ``rows'' and ``columns'' for a matrix, adding ``planes''for a cube.438 When working with arrays, \eg linear algebra, array dimensions are referred to as \emph{rows} and \emph{columns} for a matrix, adding \emph{planes} for a cube. 382 439 (There is little terminology for higher dimensional arrays.) 383 440 For example, an acrostic poem\footnote{A type of poetry where the first, last or other letters in a line spell out a particular word or phrase in a vertical column.} … … 433 490 Many languages allow multidimensional arrays-of-arrays, \eg in Pascal or \CC. 434 491 \begin{cquote} 492 \setlength{\tabcolsep}{15pt} 435 493 \begin{tabular}{@{}ll@{}} 436 494 \begin{pascal} … … 467 525 468 526 \VRef[Figure]{f:ContiguousNon-contiguous} shows a powerful extension made in C99 for manipulating contiguous \vs non-contiguous arrays.\footnote{C90 also supported non-contiguous arrays.} 469 For contiguous-array (including VLA) arguments, C99 conjoins one or more of the parameters as a downstream dimension(s), \eg @cols@, implicitly using this parameter to compute the row stride of @m@.527 For contiguous-array arguments (including VLA), C99 conjoins one or more of the parameters as a downstream dimension(s), \eg @cols@, implicitly using this parameter to compute the row stride of @m@. 470 528 There is now sufficient information to support array copying and subscript checking along the columns to prevent changing the argument or buffer-overflow problems, but neither feature is provided. 471 529 If the declaration of @fc@ is changed to: … … 517 575 518 576 519 \subsection{Multi-dimensional arrays decay and pointers diffract} 520 521 As for single-dimension arrays, multi-dimensional arrays have similar issues. 577 \subsection{Multi-Dimensional Arrays Decay and Pointers Diffract} 578 579 As for single-dimension, multi-dimensional arrays have similar issues \see{\VRef{s:Array}}. 580 Again, the inspection begins by using @sizeof@ to provide program semantics for the intuition of an expression's type. 522 581 \lstinput{16-18}{bkgd-carray-mdim.c} 523 The significant axis of deriving expressions from @ar@ is now ``itself,'' ``first element'' or ``first grand-element (meaning, first element of first element).'' 524 \PAB{Explain, explain, explain.} 582 There are now three axis for deriving expressions from @mx@: \emph{itself}, \emph{first element}, and \emph{first grand-element} (meaning, first element of first element). 525 583 \lstinput{20-26}{bkgd-carray-mdim.c} 526 \PAB{Explain, explain, explain.} 584 Given that arrays are contiguous and the size of @float@ is 4, then the size of @mx@ with 3 $\times$ 10 floats is 120 bytes, the size of its first element (row) is 40 bytes, and the size of the first element of the first row is 4. 585 Again, an array and a point to each of its axes are different. 527 586 \lstinput{28-36}{bkgd-carray-mdim.c} 528 \PAB{Explain, explain, explain.} 587 As well, there is pointer decay from each of the matrix axes to pointers, which all have the same address. 529 588 \lstinput{38-44}{bkgd-carray-mdim.c} 589 Finally, subscripting on a @malloc@ result, where the referent may or may not allow subscripting or have the right number of subscripts. 530 590 531 591 … … 533 593 534 594 Passing an array as an argument to a function is necessary. 535 Assume a parameter is an array whe nthe function intends to subscript it.536 This section asserts that a more satisfactory/formal characterization does not exist in C, surveys the ways that C API authors communicate ``@p@ has zero or more dimensions'' andcalls out the minority cases where the C type system is using or verifying such claims.537 538 A C parameter declaration s look different,from the caller's and callee's perspectives.595 Assume a parameter is an array where the function intends to subscript it. 596 This section asserts that a more satisfactory/formal characterization does not exist in C, then surveys the ways that C API authors communicate @p@ has zero or more dimensions, and finally calls out the minority cases where the C type system is using or verifying such claims. 597 598 A C parameter declaration looks different from the caller's and callee's perspectives. 539 599 Both perspectives consist of the text read by a programmer and the semantics enforced by the type system. 540 The caller's perspective is available from a function declaration, which allow definition-before-use and separate compilation, but can also be read from (the non-body part of) a function definition.600 The caller's perspective is available from a function declaration, which allows definition-before-use and separate compilation, but can also be read from (the non-body part of) a function definition. 541 601 The callee's perspective is what is available inside the function. 542 602 \begin{cfa} 543 int foo( int, float, char ); $\C{// declaration, names optional}$544 int bar( int i, float f, char c ) { $\C{// definition, names mandatory}$545 // calle r's perspective of foo; callee's perspective ofbar603 int foo( int, float, char ); $\C{// declaration, parameter names optional}$ 604 int bar( int i, float f, char c ) { $\C{// definition, parameter names mandatory}$ 605 // callee's perspective of foo and bar 546 606 } 547 // caller's perspectives of foo 's and bar's548 \end{cfa} 549 In caller's perspective, the parameter names (by virtue of being optional) are reallycomments;550 inthe callee's perspective, parameter names are semantically significant.607 // caller's perspectives of foo and bar 608 \end{cfa} 609 From the caller's perspective, the parameter names (by virtue of being optional) are (useful) comments; 610 From the callee's perspective, parameter names are semantically significant. 551 611 Array parameters introduce a further, subtle, semantic difference and considerable freedom to comment. 552 612 553 613 At the semantic level, there is no such thing as an array parameter, except for one case (@T [static 5]@) discussed shortly. 554 614 Rather, there are only pointer parameters. 555 This fact probably shares considerable responsibility for the common sense of ``an array is just a pointer,''which has been refuted in non-parameter contexts.615 This fact probably shares considerable responsibility for the common sense of \emph{an array is just a pointer}, which has been refuted in non-parameter contexts. 556 616 This fact holds in both the caller's and callee's perspectives. 557 However, a parameter's type can include ``array of .'', \eg the type ``pointer to array of 5 ints'' (@T (*)[5]@) is a pointer type.617 However, a parameter's type can include ``array of'', \eg the type ``pointer to array of 5 ints'' (@T (*)[5]@) is a pointer type. 558 618 This type is fully meaningful in the sense that its description does not contain any information that the type system ignores, and the type appears the same in the caller's \vs callee's perspectives. 559 In fact, the outermost type constructor (syntactically first dimension) is really the one that determines the flavour of parameter. 560 561 Yet, C allows array syntax for the outermost type constructor, from which comes the freedom to comment. 562 An array parameter declaration can specify the outermost dimension with a dimension value, @[10]@ (which is ignored), an empty dimension list, @[ ]@, or a pointer, @*@, as seen in \VRef[Figure]{f:ArParmEquivDecl}. 563 The rationale for rejecting the first ``invalid'' row follows shortly, while the second ``invalid'' row is simple nonsense, included to complete the pattern; its syntax hints at what the final row actually achieves. 619 In fact, the outermost type constructor (syntactically first dimension) is really the one that determines the parameter flavour. 564 620 565 621 \begin{figure} 566 \begin{cquote}567 622 \begin{tabular}{@{}llll@{}} 568 623 \begin{cfa} 569 624 float sum( float a[5] ); 570 float sum( float a[5][4] );625 float sum( float m[5][4] ); 571 626 float sum( float a[5][] ); 572 627 float sum( float a[5]* ); … … 576 631 \begin{cfa} 577 632 float sum( float a[] ); 578 float sum( float a[][4] );633 float sum( float m[][4] ); 579 634 float sum( float a[][] ); 580 635 float sum( float a[]* ); … … 584 639 \begin{cfa} 585 640 float sum( float *a ); 586 float sum( float (* a)[4] );641 float sum( float (*m)[4] ); 587 642 float sum( float (*a)[] ); 588 643 float sum( float (*a)* ); … … 591 646 & 592 647 \begin{cfa} 593 // ar of float594 // mat of float648 // array of float 649 // matrix of float 595 650 // invalid 596 651 // invalid 597 // ar of ptr to float 598 \end{cfa} 599 \end{tabular} 600 \end{cquote} 652 // array of ptr to float 653 \end{cfa} 654 \end{tabular} 601 655 \caption{Multiple ways to declare an array parameter. 602 656 Across a valid row, every declaration is equivalent. … … 606 660 \end{figure} 607 661 608 In the leftmost style, the typechecker ignores the actual value in most practical cases. 609 This value is allowed to be a dynamic expression, and then it has practical cases. 610 \begin{cfa} 611 void foo( int @n@ ) { 612 float _42( float @a[n]@ ) { // nested function 613 a[0] = 42; 614 } 615 float b[n]; 616 _42( b ); 617 } 662 Yet, C allows array syntax for the outermost type constructor, from which comes the freedom to comment. 663 An array parameter declaration can specify the outermost dimension with a dimension value, @[10]@ (which is ignored), an empty dimension list, @[ ]@, or a pointer, @*@, as seen in \VRef[Figure]{f:ArParmEquivDecl}. 664 The rationale for rejecting the first invalid row follows shortly, while the second invalid row is simple nonsense, included to complete the pattern; its syntax hints at what the final row actually achieves. 665 Note, in the leftmost style, the typechecker ignores the actual value even in a dynamic expression. 666 \begin{cfa} 667 int N; 668 void foo( float @a[N] ) ; // N is ignored 618 669 \end{cfa} 619 670 … … 622 673 % So are @float[5]*@, @float[]*@ and @float (*)*@. These latter ones are simply nonsense, though they hint at ``1d array of pointers'', whose equivalent syntax options are, @float *[5]@, @float *[]@, and @float **@. 623 674 624 It is a matter of taste as to whether a programmer should use a form as far left as possible (getting the most out of possible subscripting and dimension sizes), sticking to the right (avoiding false comfort from suggesting the typechecker is checking more than it is), or compromising in the middle (reducing unchecked information, yet clearly stating, ``I will subscript ).675 It is a matter of taste as to whether a programmer should use a form as far left as possible (getting the most out of possible subscripting and dimension sizes), sticking to the right (avoiding false comfort from suggesting the typechecker is checking more than it is), or compromising in the middle (reducing unchecked information, yet clearly stating, ``I will subscript''). 625 676 626 677 Note that this equivalence of pointer and array declarations is special to parameters. … … 635 686 } 636 687 \end{cfa} 637 This equivalence has the consequence that the type system does not help a caller get it right.688 Unfortunately, this equivalence has the consequence that the type system does not help a caller get it right. 638 689 \begin{cfa} 639 690 float sum( float v[] ); … … 656 707 void foo( int [@const volatile@ 5] ); $\C{// 5 is ignored}$ 657 708 \end{cfa} 658 659 709 To make the first dimension size meaningful, C adds this form. 660 710 \begin{cquote} … … 667 717 \end{cfa} 668 718 Here, the @static@ storage qualifier defines the minimum array size for its argument. 669 @gcc@ ignores this dimension qualifier, \ie it gives no warning if the argument array size is less than the parameter minimum. However, @clang@ implements the check, in accordance with the standard. TODO: be specific about versions 719 Earlier versions of @gcc@ ($<$ 11) and possibly @clang@ ignore this dimension qualifier, while later versions implement the check, in accordance with the standard. 720 670 721 671 722 Note that there are now two different meanings for modifiers in the same position. In … … 685 736 Here, the distance between the first and second elements of each array depends on the inner dimension size. 686 737 687 Th issignificance of an inner dimension's length is a fact of the callee's perspective.688 In the caller's perspective, the type sy tem is quite lax.738 The significance of an inner dimension's length is a fact of the callee's perspective. 739 In the caller's perspective, the type system is quite lax. 689 740 Here, there is (some, but) little checking that what is being passed, matches. 690 741 % void f( float [][10] ); … … 709 760 \end{cfa} 710 761 The cases without comments are rejections, but simply because the array ranks do not match; in the commented cases, the ranks match and the rules being discussed apply. 711 The cases @f( b)@ and @f(&a)@ show where some length checking occurs.712 But this checking misses the cases @f( d)@ and @f(&c)@, allowing the calls with mismatched lengths, actually 100 for 10.762 The cases @f( b )@ and @f( &a )@ show where some length checking occurs. 763 But this checking misses the cases @f( d )@ and @f( &c )@, allowing the calls with mismatched lengths, actually 100 for 10. 713 764 The C checking rule avoids false alarms, at the expense of safety, by allowing any combinations that involve dynamic values. 714 765 Ultimately, an inner dimension's size is a callee's \emph{assumption} because the type system uses declaration details in the callee's perspective that it does not enforce in the caller's perspective. 715 766 716 Finally, to handle higher-dimensional VLAs, C repurposed the @*@ \emph{within} the dimension in a declaration to mean that the callee has make an assumption about the size, but no ( unchecked, possibly wrong) information about this assumption is included for the caller-programmer's benefit/over-confidence.767 Finally, to handle higher-dimensional VLAs, C repurposed the @*@ \emph{within} the dimension in a declaration to mean that the callee has make an assumption about the size, but no (checked, possibly wrong) information about this assumption is included for the caller-programmer's benefit/\-over-confidence. 717 768 \begin{cquote} 718 769 @[@ \textit{type-qualifier-list$_{opt}$} @* ]@ … … 731 782 732 783 733 \subsection{Arrays could be values}784 \subsection{Arrays Could be Values} 734 785 735 786 All arrays have a know runtime size at their point of declaration. … … 751 802 \begin{cfa} 752 803 Jmp_Buf jb1, jb2; 753 jb1 = jb2; 804 jb1 = jb2; // copy 754 805 void foo( Jmp_Buf ); 755 foo( jb2 ); 806 foo( jb2 ); // copy 756 807 \end{cfa} 757 808 758 809 This same argument applies to returning arrays from functions. 759 There can be sufficient information to return an array by value but it is notsupported.760 Again, array wrapping allows an array to be returned from a function and copied into variable.810 There can be sufficient information to return an array by value but it is unsupported. 811 Again, array wrapping allows an array to be returned from a function and copied into a variable. 761 812 762 813 … … 772 823 773 824 774 \subsection{Design issues}825 \subsection{Design Issues} 775 826 \label{toc:lst:issue} 776 827 777 828 This thesis focuses on a reduced design space for linked lists that target \emph{system programmers}. 778 829 Within this restricted space, all design-issue discussions assume the following invariants; 779 alternatives to the assumptions are discussed under Future Work ( Section~\ref{toc:lst:futwork}).830 alternatives to the assumptions are discussed under Future Work (\VRef{toc:lst:futwork}). 780 831 \begin{itemize} 781 832 \item A doubly-linked list is being designed. 782 833 Generally, the discussed issues apply similarly for singly-linked lists. 783 Circular \vs ordered linking is discussed under List identity ( Section~\ref{toc:lst:issue:ident}).834 Circular \vs ordered linking is discussed under List identity (\VRef{toc:lst:issue:ident}). 784 835 \item Link fields are system-managed. 785 836 The user works with the system-provided API to query and modify list membership. … … 790 841 791 842 792 \subsection{Preexisting linked-list libraries}843 \subsection{Preexisting Linked-List Libraries} 793 844 \label{s:PreexistingLinked-ListLibraries} 794 845 … … 799 850 \item \CC Standard Template Library's (STL)\footnote{The term STL is contentious as some people prefer the term standard library.} @std::list@\cite{lst:stl} 800 851 \end{enumerate} 801 %A general comparison of libraries' abilities is given under Related Work ( Section~\ref{toc:lst:relwork}).852 %A general comparison of libraries' abilities is given under Related Work (\VRef{toc:lst:relwork}). 802 853 For the discussion, assume the fictional type @req@ (request) is the user's payload in examples. 803 854 As well, the list library is helping the user manage (organize) requests, \eg a request can be work on the level of handling a network arrival-event or scheduling a thread. 804 855 805 856 806 \subsection{Link attachment: intrusive vs.\ wrapped}857 \subsection{Link Attachment: Intrusive \vs Wrapped} 807 858 \label{toc:lst:issue:attach} 808 859 … … 837 888 Three styles of link attachment: (a)~intrusive, (b)~wrapped reference, and (c)~wrapped value. 838 889 The diagrams show the memory layouts that result after the code runs, eliding the head object \lstinline{reqs}; 839 head objects are discussed in Section~\ref{toc:lst:issue:ident}.890 head objects are discussed in \VRef{toc:lst:issue:ident}. 840 891 In (a), the field \lstinline{req.x} names a list direction; 841 these are discussed in Section~\ref{toc:lst:issue:simultaneity}.892 these are discussed in \VRef{toc:lst:issue:simultaneity}. 842 893 In (b) and (c), the type \lstinline{node} represents a system-internal type, 843 894 which is \lstinline{std::_List_node} in the GNU implementation. … … 889 940 % \protect\subref*{f:Intrusive}~intrusive, \protect\subref*{f:WrappedRef}~wrapped reference, and \protect\subref*{f:WrappedValue}~wrapped value. 890 941 The diagrams show the memory layouts that result after the code runs, eliding the head object \lstinline{reqs}; 891 head objects are discussed in Section~\ref{toc:lst:issue:ident}.942 head objects are discussed in \VRef{toc:lst:issue:ident}. 892 943 In \protect\subref*{f:Intrusive}, the field \lstinline{req.d} names a list direction; 893 these are discussed in Section~\ref{toc:lst:issue:simultaneity}.944 these are discussed in \VRef{toc:lst:issue:simultaneity}. 894 945 In \protect\subref*{f:WrappedRef} and \protect\subref*{f:WrappedValue}, the type \lstinline{node} represents a 895 946 library-internal type, which is \lstinline{std::_List_node} in the GNU implementation … … 905 956 In all three cases, a @req@ object can enter and leave a list many times. 906 957 However, in intrusive a @req@ can only be on one list at a time, unless there are separate link-fields for each simultaneous list. 907 In wrapped reference, a @req@ can appear multiple times on the same or different lists simultaneously, but since @req@ is shared via the pointer, care must be taken if updating data also occurs simultaneously, \eg concurrency.958 In wrapped reference, a @req@ can appear multiple times on the same or different lists, but since @req@ is shared via the pointer, care must be taken if updating data also occurs simultaneously, \eg concurrency. 908 959 In wrapped value, the @req@ is copied, which increases storage usage, but allows independent simultaneous changes; 909 however, knowing which of the @req@ object is the ``true''object becomes complex.960 however, knowing which of the @req@ object is the \emph{true} object becomes complex. 910 961 \see*{\VRef{toc:lst:issue:simultaneity} for further discussion.} 911 962 912 963 The implementation of @LIST_ENTRY@ uses a trick to find the links and the node containing the links. 913 The macro @LIST_INSERT_HEAD( &reqs, &r2, d);@ takes the list header, a pointer to the node, and the offset of the link fields in the node.964 The macro @LIST_INSERT_HEAD( &reqs, &r2, d )@ takes the list header, a pointer to the node, and the offset of the link fields in the node. 914 965 One of the fields generated by @LIST_ENTRY@ is a pointer to the node, which is set to the node address, \eg @r2@. 915 966 Hence, the offset to the link fields provides an access to the entire node, \ie the node points at itself. 916 For list traversal, @LIST_FOREACH( cur, &reqs_pri, by_pri)@, there is the node cursor, the list, and the offset of the link fields within the node.967 For list traversal, @LIST_FOREACH( cur, &reqs_pri, by_pri )@, there is the node cursor, the list, and the offset of the link fields within the node. 917 968 The traversal actually moves from link fields to link fields within a node and sets the node cursor from the pointer within the link fields back to the node. 918 969 … … 926 977 Another subtle advantage of intrusive arrangement is that a reference to a user-level item (@req@) is sufficient to navigate or manage the item's membership. 927 978 In LQ, the intrusive @req@ pointer is the right argument type for operations @LIST_NEXT@ or @LIST_REMOVE@; 928 there is no distinguishing a @req@ from ``a @req@ in a list.''979 there is no distinguishing a @req@ from a @req@ in a list. 929 980 The same is not true of STL, wrapped reference or value. 930 981 There, the analogous operations, @iterator::operator++()@, @iterator::operator*()@, and @list::erase(iterator)@, work on a parameter of type @list<T>::iterator@; … … 946 997 the LQ C macros do not expand to valid C++ when instantiated with template parameters---there is no \lstinline{struct El}. 947 998 When using a custom-patched version of LQ to work around this issue, 948 the programs of Figure~\ref{f:WrappedRef} and wrapped value work with this shim in place of real STL.999 the programs of \VRef[Figure]{f:WrappedRef} and wrapped value work with this shim in place of real STL. 949 1000 Their executions lead to the same memory layouts. 950 1001 } … … 952 1003 \end{figure} 953 1004 954 It is possible to simulate wrapped using intrusive, illustrated in Figure~\ref{fig:lst-issues-attach-reduction}.1005 It is possible to simulate wrapped using intrusive, illustrated in \VRef[Figure]{fig:lst-issues-attach-reduction}. 955 1006 This shim layer performs the implicit dynamic allocations that pure intrusion avoids. 956 1007 But there is no reduction going the other way. … … 961 1012 An intrusive-primitive library like LQ lets users choose when to make this tradeoff. 962 1013 A wrapped-primitive library like STL forces users to incur the costs of wrapping, whether or not they access its benefits. 963 964 965 \subsection{Simultaneity: single vs.\ multi-static vs.\ dynamic} 1014 \CFA is capable of supporting a wrapped library, if need arose. 1015 1016 1017 \subsection{Simultaneity: Single \vs Multi-Static \vs Dynamic} 966 1018 \label{toc:lst:issue:simultaneity} 967 1019 … … 986 1038 \newterm{Simultaneity} deals with the question: 987 1039 In how many different lists can a node be stored, at the same time? 988 Figure~\ref{fig:lst-issues-multi-static} shows an example that can traverse all requests in priority order (field @pri@) or navigate among requests with the same request value (field @rqr@).1040 \VRef[Figure]{fig:lst-issues-multi-static} shows an example that can traverse all requests in priority order (field @pri@) or navigate among requests with the same request value (field @rqr@). 989 1041 Each of ``by priority'' and ``by common request value'' is a separate list. 990 1042 For example, there is a single priority-list linked in order [1, 2, 2, 3, 3, 4], where nodes may have the same priority, and there are three common request-value lists combining requests with the same values: [42, 42], [17, 17, 17], and [99], giving four head nodes one for each list. … … 996 1048 This feature is used in the \CFA runtime, where a thread node may be on a blocked or running list, but never on both simultaneously. 997 1049 998 Now consider the STL in the wrapped-reference arrangement of Figure~\ref{f:WrappedRef}.1050 Now consider the STL in the wrapped-reference arrangement of \VRef[Figure]{f:WrappedRef}. 999 1051 Here it is possible to construct the same simultaneity by creating multiple STL lists, each pointing at the appropriate nodes. 1000 1052 Each group of intrusive links become the links for each separate STL list. … … 1003 1055 Note, it might be possible to wrap the multiple lists in another type to hide this implementation issue. 1004 1056 1005 Now consider the STL in the wrapped-value arrangement of Figure~\ref{f:WrappedValue}.1057 Now consider the STL in the wrapped-value arrangement of \VRef[Figure]{f:WrappedValue}. 1006 1058 Again, it is possible to construct the same simultaneity by creating multiple STL lists, each copying the appropriate nodes, where the intrusive links become the links for each separate STL list. 1007 1059 The upside is the same as for wrapped-reference arrangement with an unlimited number of list bindings. … … 1015 1067 % LQ's ergonomics are well-suited to the uncommon case of multiple list directions. 1016 1068 % Its intrusion declaration and insertion operation both use a mandatory explicit parameter naming the direction. 1017 % This decision works well in Figure~\ref{fig:lst-issues-multi-static}, where the names @by_pri@ and @by_rqr@ work well,1018 % but it clutters Figure~\ref{f:Intrusive}, where a contrived name must be invented and used.1069 % This decision works well in \VRef[Figure]{fig:lst-issues-multi-static}, where the names @by_pri@ and @by_rqr@ work well, 1070 % but it clutters \VRef[Figure]{f:Intrusive}, where a contrived name must be invented and used. 1019 1071 % The example uses @x@; @reqs@ would be a more readily ignored choice. \PAB{wording?} 1020 1072 … … 1089 1141 1090 1142 1091 \subsection{User integration: preprocessed vs.\ type-system mediated} 1092 1093 \PAB{What do you want to say here?} 1143 \subsection{User Integration: Preprocessed \vs Type-System Mediated} 1144 1145 While the syntax for LQ is reasonably succinct, it comes at the cost of using C preprocessor macros for generics, which are not part of the language type-system, like \CC templates. 1146 Hence, small errors in macro arguments can lead to large substitution mistakes, as the arguments maybe textually written in many places and/or concatenated with other arguments/text to create new names and expressions. 1147 This can lead to a cascade of error messages that are confusing and difficult to debug. 1148 For example, argument errors like @a.b,c@, comma instead of period, or @by-pri@, minus instead of underscore, can produce many error messages. 1149 1150 Instead, language function calls (even with inlining) handled argument mistakes locally at the call, giving very specific error message. 1151 \CC @concepts@ were introduced in @templates@ to deal with just this problem. 1094 1152 1095 1153 % example of poor error message due to LQ's preprocessed integration … … 1101 1159 1102 1160 1103 \subsection{List identity: headed vs.\ ad-hoc}1161 \subsection{List Identity: Headed \vs Ad-Hoc} 1104 1162 \label{toc:lst:issue:ident} 1105 1163 1106 All examples so far use distinct user-facing types: 1107 an item found in a list (type @req@ of variable @r1@, see \VRef[Figure]{fig:lst-issues-attach}), and a list (type @reql@ of variable @reqs_pri@, see \VRef[Figure]{fig:lst-issues-ident}). 1108 The latter type is a head. 1109 The resulting identity model (empty list) is just the head. 1110 A bespoke ``pointer to next @req@'' implementation often omits the header; 1111 hence, a pointer to any node can traverse its link fields: right or left and around, depending on the data structure. 1112 The resulting identity model is ad-hoc. 1113 1114 Figure~\ref{fig:lst-issues-ident} shows the identity model's impact on 1115 the existence of certain conceptual constructs, like zero-lengths lists and unlisted elements. 1116 In headed thinking, there are length-zero lists (heads with no elements), and an element can be listed or not listed. 1117 In ad-hoc thinking, there are no length-zero lists and every element belongs to a list of length at least one. 1118 By omitting the head, elements can enter into an adjacency relationship, without requiring allocation for a head for the list, or finding a correct existing head. 1164 All examples so far use two distinct types for: 1165 an item found in a list (type @req@ of variable @r1@, see \VRef[Figure]{fig:lst-issues-attach}), and the list (type @reql@ of variable @reqs_pri@, see \VRef[Figure]{fig:lst-issues-ident}). 1166 This kind of list is ``headed'', where the empty list is just a head. 1167 An alternate ``ad-hoc'' approach omits the header, where the empty list is no nodes. 1168 Here, a pointer to any node can traverse its link fields: right or left and around, depending on the data structure. 1169 Note, a headed list is superset of an ad-hoc list, and can normally perform all of the ad-hoc operations. 1170 \VRef[Figure]{fig:lst-issues-ident} shows both approaches for different list lengths and unlisted elements. 1171 For headed, there are length-zero lists (heads with no elements), and an element can be listed or not listed. 1172 For ad-hoc, there are no length-zero lists and every element belongs to a list of length at least one. 1119 1173 1120 1174 \begin{figure} … … 1128 1182 \end{figure} 1129 1183 1130 A head defines one or more element roles, among elements that share a transitive adjacency.1131 ``First'' and ``last'' are element roles.1132 One moment's ``first'' need not be the next moment's.1133 1134 There is a cost to maintaining these roles.1184 The purpose of a header is to provide specialized but implicit node access, such as the first/last nodes in the list, where accessing these nodes is deemed a commonly occurring operation and should be $O(1)$ for performance of certain operations. 1185 For example, without a last pointer in a singly-linked list, adding to the end of the list is an $O(N)$ operation to traverse the list to find the last node. 1186 Without the header, this specialized information must be managed explicitly, where the programmer builds their own external, equivalent header information. 1187 However, external management of particular nodes might not be beneficial because the list does not provide operations that can take advantage of them, such as using an external pointer to update an internal link. 1188 Clearly, there is a cost maintaining this specialized information, which needs to be amortized across the list operations that use it, \eg rarely adding to the end of a list. 1135 1189 A runtime component of this cost is evident in LQ's offering the choice of type generators @LIST@ \vs @TAILQ@. 1136 Its @LIST@ maintains a ``first,'' but not a ``last;'' its @TAILQ@ maintains both roles. 1190 Its @LIST@ maintains a \emph{first}, but not a \emph{last}; 1191 its @TAILQ@ maintains both roles. 1137 1192 (Both types are doubly linked and an analogous choice is available for singly linked.) 1138 1193 1139 TODO: finish making this point 1140 1141 See WIP in lst-issues-adhoc-*.ignore.*. 1142 1143 The code-complexity component of the cost ... 1144 1145 Ability to offer heads is good. Point: Does maintaining a head mean that the user has to provide more state when manipulating the list? Requiring the user to do so is bad, because the user may have lots of "list" typed variables in scope, and detecting that the user passed the wrong one requires testing all the listing edge cases. 1146 1147 1148 \subsection{End treatment: cased vs.\ uniform } 1149 1150 A linear (non-circular), nonempty linked-list has a first element and a last element, whether or not the list is headed. 1151 A first element has no predecessor and a last element has no successor. 1194 1195 \subsection{End Treatment: Cased \vs Uniform } 1196 1197 All lists must have a logical \emph{beginning/ending}, otherwise list traversal is infinite. 1198 \emph{End treatment} refers to how the list represents the lack of a predecessor/successor to demarcate end point(s). 1199 For example, in a doubly-linked list containing a single node, the next/prev links have no successor/predecessor nodes. 1200 Note, a list does not need to use links to denote its size; 1201 it can use a node counter in the header, where $N$ node traversals indicates complete navigation of the list. 1202 However, managing the number of nodes is an additional cost, as the links must always be managed. 1203 1204 The following discussion refers to the LQ representations, detailed in \VRef[Figure]{fig:lst-issues-end}, using a null pointer to mark end points. 1205 LQ uses this representation for its successor/last. 1206 For example, consider the operation of inserting after a given element. 1207 A doubly-linked list must update the given node's successor, to make its predecessor-pointer refer to the new node. 1208 This step must happen when the given node has a successor (when its successor pointer is non-null), 1209 and must be skipped when it does not (when its successor pointer cannot be navigated). 1210 So this operation contains a branch, to decide which case is happening. 1211 All branches have pathological cases where branch prediction's success rate is low and the execution pipeline stalls often. 1212 Hence, this issue is relevant to achieving high performance. 1152 1213 1153 1214 \begin{figure} … … 1164 1225 \end{figure} 1165 1226 1166 End treatment refers to how the list represents the lack of a predecessor/successor. 1167 The following elaboration refers to the LQ representations, detailed in Figure~\ref{fig:lst-issues-end}. 1168 The most obvious representation, a null pointer, mandates a cased end treatment. 1169 LQ uses this representation for its successor/last. 1170 Consider the operation of inserting after a given element. 1171 A doubly-linked list must update the given node's successor, to make its predecessor-pointer refer to the new node. 1172 This step must happen when the given node has a successor (when its successor pointer is non-null), 1173 and must be skipped when it does not (when its successor pointer cannot be navigated). 1174 So this operation contains a branch, to decide which case is happening. 1175 All branches have pathological cases where branch prediction's success rate is low and the execution pipeline is stalling often. 1176 Hence, this issue is implementation-level, relevant to achieving high performance. 1177 1178 This branch is sometimes avoidable; the result is a uniform end treatment. 1179 Here is one example of such an implementation that works for a headed list. 1180 LQ uses this representation for its predecessor/first. (All LQ offerings are headed at the front.) 1227 Interestingly, this branch is sometimes avoidable, giving a uniform end-treatment in the code. 1228 For example, LQ is headed at the front. 1181 1229 For predecessor/first navigation, the relevant operation is inserting before a given element. 1182 1230 LQ's predecessor representation is not a pointer to a node, but a pointer to a pseudo-successor pointer. 1183 1231 When there is a predecessor node, that node contains a real-successor pointer; it is the target of the reference node's predecessor pointer. 1184 1232 When there is no predecessor node, the reference node (now known to be first node) acts as the pseudo-successor of the list head. 1185 The list head contains a pointer to the first node.1233 Now, the list head contains a pointer to the first node. 1186 1234 When inserting before the first node, the list head's first-pointer is the one that must change. 1187 So, the first node's ``predecessor''pointer (to a pseudo-successor pointer) is set as the list head's first-pointer.1235 So, the first node's \emph{predecessor} pointer (to a pseudo-successor pointer) is set as the list head's first-pointer. 1188 1236 Now, inserting before a given element does the same logic in both cases: 1189 follow the guaranteed-non-null predecessor pointer, and update what you find thereto refer to the new node.1237 follow the guaranteed-non-null predecessor pointer, and update that location to refer to the new node. 1190 1238 Applying this trick makes it possible to have list management routines that are completely free of conditional control-flow. 1191 1239 Considering a path length of only a few instructions (less than the processor's pipeline length),
Note:
See TracChangeset
for help on using the changeset viewer.