- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
doc/papers/concurrency/Paper.tex
rc8ad5d9 ra87c86f 22 22 \captionsetup{justification=raggedright,singlelinecheck=false} 23 23 \usepackage{siunitx} 24 \sisetup{ binary-units=true}24 \sisetup{binary-units=true} 25 25 26 26 \hypersetup{breaklinks=true} … … 32 32 \renewcommand{\linenumberfont}{\scriptsize\sffamily} 33 33 34 \renewcommand{\textfraction}{0.0} % the entire page maybe devoted to floats with no text on the page at all34 \renewcommand{\textfraction}{0.0} % the entire page maybe devoted to floats with no text on the page at all 35 35 36 36 \lefthyphenmin=3 % hyphen only after 4 characters … … 70 70 %\DeclareTextCommandDefault{\textunderscore}{\leavevmode\makebox[1.2ex][c]{\rule{1ex}{0.1ex}}} 71 71 \renewcommand{\textunderscore}{\leavevmode\makebox[1.2ex][c]{\rule{1ex}{0.075ex}}} 72 %\def\myCHarFont{\fontencoding{T1}\selectfont}% 73 % \def\{{\ttfamily\upshape\myCHarFont \char`\}}}% 72 74 73 75 \makeatletter … … 244 246 \maketitle 245 247 246 % ====================================================================== 247 % ====================================================================== 248 248 249 \section{Introduction} 249 % ======================================================================250 % ======================================================================251 250 252 251 This paper provides a minimal concurrency \newterm{Abstract Program Interface} (API) that is simple, efficient and can be used to build other concurrency features. … … 254 253 An easier approach for programmers is to support higher-level constructs as the basis of concurrency. 255 254 Indeed, for highly productive concurrent programming, high-level approaches are much more popular~\cite{Hochstein05}. 256 Examples of high-level approaches are task based~\cite{TBB}, message passing~\cite{Erlang,MPI}, and implicit threading~\cite{OpenMP}.255 Examples of high-level approaches are task (work) based~\cite{TBB}, implicit threading~\cite{OpenMP}, monitors~\cite{Java}, channels~\cite{CSP,Go}, and message passing~\cite{Erlang,MPI}. 257 256 258 257 This paper uses the following terminology. … … 272 271 The paper discusses how the language features are added to the \CFA translator with respect to parsing, semantic, and type checking, and the corresponding high-perforamnce runtime-library to implement the concurrency features. 273 272 274 % ====================================================================== 275 % ====================================================================== 273 276 274 \section{\CFA Overview} 277 % ======================================================================278 % ======================================================================279 275 280 276 The following is a quick introduction to the \CFA language, specifically tailored to the features needed to support concurrency. 281 Most of the following code examples can be found on the \CFA website~\cite{Cforall}.282 283 \CFA is an extension of ISO-C, and therefore, supports all of the same paradigms as C.277 Extended versions of the following code examples are available at the \CFA website~\cite{Cforall} or Moss~\etal~\cite{XXX}. 278 279 \CFA is an extension of ISO-C, and hence, supports all C paradigms. 284 280 %It is a non-object-oriented system-language, meaning most of the major abstractions have either no runtime overhead or can be opted out easily. 285 Like C, the basics of \CFA revolve around structures and routines, which are thin abstractions over machine code. 286 The vast majority of the code produced by the \CFA translator respects memory layouts and calling conventions laid out by C. 287 Interestingly, while \CFA is not an object-oriented language, lacking the concept of a receiver (\eg @this@) and inheritance, it does have some notion of objects\footnote{C defines the term objects as : ``region of data storage in the execution environment, the contents of which can represent 288 values''~\cite[3.15]{C11}}, most importantly construction and destruction of objects. 281 Like C, the basics of \CFA revolve around structures and functions. 282 Virtually all of the code generated by the \CFA translator respects C memory layouts and calling conventions. 283 While \CFA is not an object-oriented language, lacking the concept of a receiver (\eg @this@) and nominal inheritance-relationships, C does have a notion of objects: ``region of data storage in the execution environment, the contents of which can represent values''~\cite[3.15]{C11}. 289 284 290 285 291 286 \subsection{References} 292 287 293 Like \CC, \CFA introduces rebind-able references providing multiple dereferencing as an alternative to pointers. 294 In regards to concurrency, the semantic difference between pointers and references are not particularly relevant, but since this document uses mostly references, here is a quick overview of the semantics: 295 \begin{cfa} 296 int x, y, z; 297 int * p1 = &x, ** p2 = &p1, *** p3 = &p2, $\C{// pointers to x}$ 298 & r1 = x, && r2 = r1, &&& r3 = r2; $\C{// references to x}$ 299 300 *p1 = 3; **p2 = 3; ***p3 = 3; $\C{// change x}$ 301 r1 = 3; r2 = 3; r3 = 3; $\C{// change x}$ 302 **p3 = &y; *p3 = &z; $\C{// change p1, p2}$ 303 &&r3 = &y; &r3 = &z; $\C{// change p1, p2}$ 304 int & ar[3] = {x, y, z}; $\C{// initialize array of references}$ 305 306 typeof( ar[1]) p; $\C{// is int, referenced object type}$ 307 typeof(&ar[1]) q; $\C{// is int \&, reference type}$ 308 sizeof( ar[1]) == sizeof(int); $\C{// is true, referenced object size}$ 309 sizeof(&ar[1]) == sizeof(int *); $\C{// is true, reference size}$ 310 \end{cfa} 311 The important take away from this code example is that a reference offers a handle to an object, much like a pointer, but which is automatically dereferenced for convenience. 312 313 % ====================================================================== 288 \CFA provides multi-level rebindable references, as an alternative to pointers, which significantly reduces syntactic noise. 289 \begin{cfa} 290 int x = 1, y = 2, z = 3; 291 int * p1 = &x, ** p2 = &p1, *** p3 = &p2, $\C{// pointers to x}$ 292 `&` r1 = x, `&&` r2 = r1, `&&&` r3 = r2; $\C{// references to x}$ 293 int * p4 = &z, `&` r4 = z; 294 295 *p1 = 3; **p2 = 3; ***p3 = 3; // change x 296 r1 = 3; r2 = 3; r3 = 3; // change x: implicit dereferences *r1, **r2, ***r3 297 **p3 = &y; *p3 = &p4; // change p1, p2 298 `&`r3 = &y; `&&`r3 = &`&`r4; // change r1, r2: cancel implicit deferences (&*)**r3, (&(&*)*)*r3, &(&*)r4 299 \end{cfa} 300 A reference is a handle to an object, like a pointer, but is automatically dereferenced the specified number of levels. 301 Referencing (address-of @&@) a reference variable cancels one of the implicit dereferences, until there are no more implicit references, after which normal expression behaviour applies. 302 303 304 \subsection{\texorpdfstring{\protect\lstinline{with} Statement}{with Statement}} 305 \label{s:WithStatement} 306 307 Heterogeneous data is often aggregated into a structure/union. 308 To reduce syntactic noise, \CFA provides a @with@ statement (see Pascal~\cite[\S~4.F]{Pascal}) to elide aggregate field-qualification by opening a scope containing the field identifiers. 309 \begin{cquote} 310 \vspace*{-\baselineskip}%??? 311 \lstDeleteShortInline@% 312 \begin{cfa} 313 struct S { char c; int i; double d; }; 314 struct T { double m, n; }; 315 // multiple aggregate parameters 316 \end{cfa} 317 \begin{tabular}{@{}l@{\hspace{\parindentlnth}}|@{\hspace{\parindentlnth}}l@{}} 318 \begin{cfa} 319 void f( S & s, T & t ) { 320 `s.`c; `s.`i; `s.`d; 321 `t.`m; `t.`n; 322 } 323 \end{cfa} 324 & 325 \begin{cfa} 326 void f( S & s, T & t ) `with ( s, t )` { 327 c; i; d; // no qualification 328 m; n; 329 } 330 \end{cfa} 331 \end{tabular} 332 \lstMakeShortInline@% 333 \end{cquote} 334 Object-oriented programming languages only provide implicit qualification for the receiver. 335 336 In detail, the @with@ statement has the form: 337 \begin{cfa} 338 $\emph{with-statement}$: 339 'with' '(' $\emph{expression-list}$ ')' $\emph{compound-statement}$ 340 \end{cfa} 341 and may appear as the body of a function or nested within a function body. 342 Each expression in the expression-list provides a type and object. 343 The type must be an aggregate type. 344 (Enumerations are already opened.) 345 The object is the implicit qualifier for the open structure-fields. 346 All expressions in the expression list are open in parallel within the compound statement, which is different from Pascal, which nests the openings from left to right. 347 348 314 349 \subsection{Overloading} 315 350 316 Another important feature of \CFA is function overloading as in Java and \CC, where routines with the same name are selected based on the number and type of the arguments. 317 As well, \CFA uses the return type as part of the selection criteria, as in Ada~\cite{Ada}. 318 For routines with multiple parameters and returns, the selection is complex. 351 \CFA maximizes the ability to reuse names via overloading to aggressively address the naming problem. 352 Both variables and functions may be overloaded, where selection is based on types, and number of returns (as in Ada~\cite{Ada}) and arguments. 353 \begin{cquote} 354 \vspace*{-\baselineskip}%??? 355 \lstDeleteShortInline@% 356 \begin{cfa} 357 // selection based on type 358 \end{cfa} 359 \begin{tabular}{@{}l@{\hspace{\parindentlnth}}|@{\hspace{\parindentlnth}}l@{}} 360 \begin{cfa} 361 const short int MIN = -32768; 362 const int MIN = -2147483648; 363 const long int MIN = -9223372036854775808L; 364 \end{cfa} 365 & 366 \begin{cfa} 367 short int si = MIN; 368 int i = MIN; 369 long int li = MIN; 370 \end{cfa} 371 \end{tabular} 319 372 \begin{cfa} 320 373 // selection based on type and number of parameters 321 void f(void); $\C{// (1)}$ 322 void f(char); $\C{// (2)}$ 323 void f(int, double); $\C{// (3)}$ 324 f(); $\C{// select (1)}$ 325 f('a'); $\C{// select (2)}$ 326 f(3, 5.2); $\C{// select (3)}$ 327 328 // selection based on type and number of returns 329 char f(int); $\C{// (1)}$ 330 double f(int); $\C{// (2)}$ 331 char c = f(3); $\C{// select (1)}$ 332 double d = f(4); $\C{// select (2)}$ 333 \end{cfa} 334 This feature is particularly important for concurrency since the runtime system relies on creating different types to represent concurrency objects. 374 \end{cfa} 375 \begin{tabular}{@{}l@{\hspace{1.7\parindentlnth}}|@{\hspace{\parindentlnth}}l@{}} 376 \begin{cfa} 377 void f( void ); 378 void f( char ); 379 void f( int, double ); 380 \end{cfa} 381 & 382 \begin{cfa} 383 f(); 384 f( 'a' ); 385 f( 3, 5.2 ); 386 \end{cfa} 387 \end{tabular} 388 \begin{cfa} 389 // selection based on type and number of returns 390 \end{cfa} 391 \begin{tabular}{@{}l@{\hspace{\parindentlnth}}|@{\hspace{\parindentlnth}}l@{}} 392 \begin{cfa} 393 char f( int ); 394 double f( int ); 395 [char, double] f( int ); 396 \end{cfa} 397 & 398 \begin{cfa} 399 char c = f( 3 ); 400 double d = f( 3 ); 401 [d, c] = f( 3 ); 402 \end{cfa} 403 \end{tabular} 404 \lstMakeShortInline@% 405 \end{cquote} 406 Overloading is important for \CFA concurrency since the runtime system relies on creating different types to represent concurrency objects. 335 407 Therefore, overloading is necessary to prevent the need for long prefixes and other naming conventions that prevent name clashes. 336 As seen in section \ref{basics}, routine @main@ is an example that benefits from overloading. 337 338 % ====================================================================== 408 As seen in Section~\ref{basics}, function @main@ is heavily overloaded. 409 410 Variable overloading is useful in the parallel semantics of the @with@ statement for fields with the same name: 411 \begin{cfa} 412 struct S { int `i`; int j; double m; } s; 413 struct T { int `i`; int k; int m; } t; 414 with ( s, t ) { 415 j + k; $\C{// unambiguous, s.j + t.k}$ 416 m = 5.0; $\C{// unambiguous, t.m = 5.0}$ 417 m = 1; $\C{// unambiguous, s.m = 1}$ 418 int a = m; $\C{// unambiguous, a = s.i }$ 419 double b = m; $\C{// unambiguous, b = t.m}$ 420 int c = `s.i` + `t.i`; $\C{// unambiguous, qualification}$ 421 (double)m; $\C{// unambiguous, cast}$ 422 } 423 \end{cfa} 424 For parallel semantics, both @s.i@ and @t.i@ are visible with the same type, so only @i@ is ambiguous without qualification. 425 426 339 427 \subsection{Operators} 428 340 429 Overloading also extends to operators. 341 The syntax for denoting operator-overloading is to name a routine with the symbol of the operator and question marks where the arguments of the operation appear, \eg: 342 \begin{cfa} 343 int ++? (int op); $\C{// unary prefix increment}$ 344 int ?++ (int op); $\C{// unary postfix increment}$ 345 int ?+? (int op1, int op2); $\C{// binary plus}$ 346 int ?<=?(int op1, int op2); $\C{// binary less than}$ 347 int ?=? (int & op1, int op2); $\C{// binary assignment}$ 348 int ?+=?(int & op1, int op2); $\C{// binary plus-assignment}$ 349 350 struct S {int i, j;}; 351 S ?+?(S op1, S op2) { $\C{// add two structures}$ 430 Operator-overloading syntax names a routine with the operator symbol and question marks for the operands: 431 \begin{cquote} 432 \lstDeleteShortInline@% 433 \begin{tabular}{@{}ll@{\hspace{\parindentlnth}}|@{\hspace{\parindentlnth}}l@{}} 434 \begin{cfa} 435 int ++? (int op); 436 int ?++ (int op); 437 int `?+?` (int op1, int op2); 438 int ?<=?(int op1, int op2); 439 int ?=? (int & op1, int op2); 440 int ?+=?(int & op1, int op2); 441 \end{cfa} 442 & 443 \begin{cfa} 444 // unary prefix increment 445 // unary postfix increment 446 // binary plus 447 // binary less than 448 // binary assignment 449 // binary plus-assignment 450 \end{cfa} 451 & 452 \begin{cfa} 453 struct S { int i, j; }; 454 S `?+?`( S op1, S op2) { // add two structures 352 455 return (S){op1.i + op2.i, op1.j + op2.j}; 353 456 } 354 457 S s1 = {1, 2}, s2 = {2, 3}, s3; 355 s3 = s1 + s2; $\C{// compute sum: s3 == {2, 5}}$ 356 \end{cfa} 357 While concurrency does not use operator overloading directly, this feature is more important as an introduction for the syntax of constructors. 358 359 % ====================================================================== 360 \subsection{Constructors/Destructors} 361 Object lifetime is often a challenge in concurrency. \CFA uses the approach of giving concurrent meaning to object lifetime as a means of synchronization and/or mutual exclusion. 362 Since \CFA relies heavily on the lifetime of objects, constructors and destructors is a core feature required for concurrency and parallelism. \CFA uses the following syntax for constructors and destructors: 363 \begin{cfa} 364 struct S { 365 size_t size; 366 int * ia; 367 }; 368 void ?{}(S & s, int asize) { $\C{// constructor operator}$ 369 s.size = asize; $\C{// initialize fields}$ 370 s.ia = calloc(size, sizeof(S)); 371 } 372 void ^?{}(S & s) { $\C{// destructor operator}$ 373 free(ia); $\C{// de-initialization fields}$ 374 } 375 int main() { 376 S x = {10}, y = {100}; $\C{// implicit calls: ?\{\}(x, 10), ?\{\}(y, 100)}$ 377 ... $\C{// use x and y}$ 378 ^x{}; ^y{}; $\C{// explicit calls to de-initialize}$ 379 x{20}; y{200}; $\C{// explicit calls to reinitialize}$ 380 ... $\C{// reuse x and y}$ 381 } $\C{// implicit calls: \^?\{\}(y), \^?\{\}(x)}$ 382 \end{cfa} 383 The language guarantees that every object and all their fields are constructed. 384 Like \CC, construction of an object is automatically done on allocation and destruction of the object is done on deallocation. 385 Allocation and deallocation can occur on the stack or on the heap. 386 \begin{cfa} 387 { 388 struct S s = {10}; $\C{// allocation, call constructor}$ 389 ... 390 } $\C{// deallocation, call destructor}$ 391 struct S * s = new(); $\C{// allocation, call constructor}$ 392 ... 393 delete(s); $\C{// deallocation, call destructor}$ 394 \end{cfa} 395 Note that like \CC, \CFA introduces @new@ and @delete@, which behave like @malloc@ and @free@ in addition to constructing and destructing objects, after calling @malloc@ and before calling @free@, respectively. 396 397 % ====================================================================== 458 s3 = s1 `+` s2; // compute sum: s3 == {2, 5} 459 \end{cfa} 460 \end{tabular} 461 \lstMakeShortInline@% 462 \end{cquote} 463 While concurrency does not use operator overloading directly, it provides an introduction for the syntax of constructors. 464 465 398 466 \subsection{Parametric Polymorphism} 399 467 \label{s:ParametricPolymorphism} 400 Routines in \CFA can also be reused for multiple types. 401 Th is capability is done using the @forall@ clauses, which allow separately compiled routines to support generic usage over multiple types.468 469 The signature feature of \CFA is parametric-polymorphic functions~\cite{} with functions generalized using a @forall@ clause (giving the language its name), which allow separately compiled routines to support generic usage over multiple types. 402 470 For example, the following sum function works for any type that supports construction from 0 and addition: 403 471 \begin{cfa} 404 // constraint type, 0 and + 405 forall(otype T | { void ?{}(T *, zero_t); T ?+?(T, T); }) 406 T sum(T a[ ], size_t size) { 407 T total = 0; $\C{// construct T from 0}$ 408 for(size_t i = 0; i < size; i++) 409 total = total + a[i]; $\C{// select appropriate +}$ 472 forall( otype T | { void `?{}`( T *, zero_t ); T `?+?`( T, T ); } ) // constraint type, 0 and + 473 T sum( T a[$\,$], size_t size ) { 474 `T` total = { `0` }; $\C{// initialize by 0 constructor}$ 475 for ( size_t i = 0; i < size; i += 1 ) 476 total = total `+` a[i]; $\C{// select appropriate +}$ 410 477 return total; 411 478 } 412 413 479 S sa[5]; 414 int i = sum(sa, 5); $\C{// use S's 0 construction and +}$ 415 \end{cfa} 416 417 Since writing constraints on types can become cumbersome for more constrained functions, \CFA also has the concept of traits. 418 Traits are named collection of constraints that can be used both instead and in addition to regular constraints: 419 \begin{cfa} 420 trait summable( otype T ) { 421 void ?{}(T *, zero_t); $\C{// constructor from 0 literal}$ 422 T ?+?(T, T); $\C{// assortment of additions}$ 423 T ?+=?(T *, T); 424 T ++?(T *); 425 T ?++(T *); 480 int i = sum( sa, 5 ); $\C{// use S's 0 construction and +}$ 481 \end{cfa} 482 483 \CFA provides \newterm{traits} to name a group of type assertions, where the trait name allows specifying the same set of assertions in multiple locations, preventing repetition mistakes at each function declaration: 484 \begin{cfa} 485 trait `sumable`( otype T ) { 486 void `?{}`( T &, zero_t ); $\C{// 0 literal constructor}$ 487 T `?+?`( T, T ); $\C{// assortment of additions}$ 488 T ?+=?( T &, T ); 489 T ++?( T & ); 490 T ?++( T & ); 426 491 }; 427 forall( otype T | summable(T) ) $\C{// use trait}$ 428 T sum(T a[], size_t size); 429 \end{cfa} 430 431 Note that the type use for assertions can be either an @otype@ or a @dtype@. 432 Types declared as @otype@ refer to ``complete'' objects, \ie objects with a size, a default constructor, a copy constructor, a destructor and an assignment operator. 433 Using @dtype@, on the other hand, has none of these assumptions but is extremely restrictive, it only guarantees the object is addressable. 434 435 % ====================================================================== 436 \subsection{with Clause/Statement} 437 Since \CFA lacks the concept of a receiver, certain functions end up needing to repeat variable names often. 438 To remove this inconvenience, \CFA provides the @with@ statement, which opens an aggregate scope making its fields directly accessible (like Pascal). 439 \begin{cfa} 440 struct S { int i, j; }; 441 int mem(S & this) with (this) $\C{// with clause}$ 442 i = 1; $\C{// this->i}$ 443 j = 2; $\C{// this->j}$ 444 } 445 int foo() { 446 struct S1 { ... } s1; 447 struct S2 { ... } s2; 448 with (s1) $\C{// with statement}$ 449 { 450 // access fields of s1 without qualification 451 with (s2) $\C{// nesting}$ 452 { 453 // access fields of s1 and s2 without qualification 454 } 455 } 456 with (s1, s2) $\C{// scopes open in parallel}$ 457 { 458 // access fields of s1 and s2 without qualification 459 } 460 } 461 \end{cfa} 462 463 For more information on \CFA see \cite{cforall-ug,Schluntz17,www-cfa}. 464 465 % ====================================================================== 466 % ====================================================================== 492 forall( otype T `| sumable( T )` ) $\C{// use trait}$ 493 T sum( T a[$\,$], size_t size ); 494 \end{cfa} 495 496 Assertions can be @otype@ or @dtype@. 497 @otype@ refers to a ``complete'' object, \ie an object has a size, default constructor, copy constructor, destructor and an assignment operator. 498 @dtype@ only guarantees an object has a size and alignment. 499 500 Using the return type for discrimination, it is possible to write a type-safe @alloc@ based on the C @malloc@: 501 \begin{cfa} 502 forall( dtype T | sized(T) ) T * alloc( void ) { return (T *)malloc( sizeof(T) ); } 503 int * ip = alloc(); $\C{// select type and size from left-hand side}$ 504 double * dp = alloc(); 505 struct S {...} * sp = alloc(); 506 \end{cfa} 507 where the return type supplies the type/size of the allocation, which is impossible in most type systems. 508 509 510 \subsection{Constructors / Destructors} 511 512 Object lifetime is a challenge in non-managed programming languages. 513 \CFA responds with \CC-like constructors and destructors: 514 \begin{cfa} 515 struct VLA { int len, * data; }; $\C{// variable length array of integers}$ 516 void ?{}( VLA & vla ) with ( vla ) { len = 10; data = alloc( len ); } // default constructor 517 void ?{}( VLA & vla, int size, char fill ) with ( vla ) { len = size; data = alloc( len, fill ); } // initialization 518 void ?{}( VLA & vla, VLA other ) { vla.len = other.len; vla.data = other.data; } // copy, shallow 519 void ^?{}( VLA & vla ) with ( vla ) { free( data ); } $\C{// destructor}$ 520 { 521 VLA x, y = { 20, 0x01 }, z = y; $\C{// z points to y}$ 522 // ?{}( x ); ?{}( y, 20, 0x01 ); ?{}( z, y ); 523 ^x{}; $\C{// deallocate x}$ 524 x{}; $\C{// reallocate x}$ 525 z{ 5, 0xff }; $\C{// reallocate z, not pointing to y}$ 526 ^y{}; $\C{// deallocate y}$ 527 y{ x }; $\C{// reallocate y, points to x}$ 528 x{}; $\C{// reallocate x, not pointing to y}$ 529 // ^?{}(z); ^?{}(y); ^?{}(x); 530 } 531 \end{cfa} 532 Like \CC, construction is implicit on allocation (stack/heap) and destruction is implicit on deallocation. 533 The object and all their fields are constructed/destructed. 534 \CFA also provides @new@ and @delete@, which behave like @malloc@ and @free@, in addition to constructing and destructing objects: 535 \begin{cfa} 536 { struct S s = {10}; $\C{// allocation, call constructor}$ 537 ... 538 } $\C{// deallocation, call destructor}$ 539 struct S * s = new(); $\C{// allocation, call constructor}$ 540 ... 541 delete( s ); $\C{// deallocation, call destructor}$ 542 \end{cfa} 543 \CFA concurrency uses object lifetime as a means of synchronization and/or mutual exclusion. 544 545 467 546 \section{Concurrency Basics}\label{basics} 468 % ====================================================================== 469 % ====================================================================== 470 471 At its core, concurrency is based on having multiple call-stacks and scheduling among threads of execution executing on these stacks. 547 548 At its core, concurrency is based on multiple call-stacks and scheduling among threads executing on these stacks. 472 549 Multiple call stacks (or contexts) and a single thread of execution does \emph{not} imply concurrency. 473 550 Execution with a single thread and multiple stacks where the thread is deterministically self-scheduling across the stacks is called \newterm{coroutining}; … … 585 662 \begin{lstlisting}[aboveskip=0pt,belowskip=0pt] 586 663 `coroutine` Fib { int fn; }; 664 void ?{}( Fibonacci & fib ) with( fib ) { fn = 0; } 587 665 void main( Fib & f ) with( f ) { 588 666 int f1, f2;
Note: See TracChangeset
for help on using the changeset viewer.