CVS difference for ai12s/ai12-0266-1.txt

Differences between 1.9 and version 1.10
Log of other versions for file ai12s/ai12-0266-1.txt

--- ai12s/ai12-0266-1.txt	2019/01/04 04:24:24	1.9
+++ ai12s/ai12-0266-1.txt	2019/01/11 04:15:26	1.10
@@ -1,4 +1,4 @@
-!standard 5.5.1(4/3)                                  18-10-16  AI12-0266-1/06
+!standard 5.5.1(4/3)                                  19-01-09  AI12-0266-1/07
 !standard 5.5.1(6/4)
 !standard 5.5.1(11/3)
 !standard 5.5.2(4/3)
@@ -21,13 +21,12 @@
 
 !proposal
 
-This proposal depends on the facilities for parallel loops (AI12-0119-1),
-aspect Global (AI12-0079-1), and for aspect Nonblocking (AI12-0064-2).
-Those proposals allow the compiler to statically determine where
-parallelism may be introduced without introducing data races.
+This proposal depends on the facilities for parallel loops (AI12-0119-1)
+and for aspect Nonblocking (AI12-0064-2).
 
 The goals of this proposal are:
 - To extend parallel iteration to the standard container libraries
+  and user defined containers;
 - To permit existing programs to benefit from parallelism through
   minimal restructuring of sequential programs into ones that permit
   more parallel execution;
@@ -40,9 +39,9 @@
 
 This proposal extends the interfaces in Ada.Containers.Iterator_Interfaces
 to include two new interfaces, a parallel iterator interface, and a
-reversible parallel iterator interface. Such an iterator can be used for
-parallel iteration, but can also be used for sequential iteration for
-loops that do not have the parallel reserved keyword.
+reversible parallel iterator interface. Such iterators derived from these
+interfaces can be used for parallel iteration, but can also be used for
+sequential iteration for loops that do not have the parallel reserved keyword.
 
 A parallel iterator provides a Split_Into_Chunks procedure that can be used to
 associate with the iterator a set of cursors that define the chunk boundaries
@@ -52,11 +51,13 @@
 chunks to collectively cover all the elements of the container.
 
 The starting cursor for each chunk of iteration can be obtained by calling the
-Start_Of_Chunk operation, passing in the chunk index of the split as a parameter
-to the call. A Chunk_Finished operation is provided to allow a logical thread of
-control to determine if all elements of the chunk have been processed. The idea
-is that the iteration for a chunk is complete when the current cursor gets
-advanced enough times to cause Chunk_Finished to return False.
+First_In_Chunk operation, passing in the chunk index of the split as a parameter
+to the call. The last cursor for each chunk of iteration can be obtained by
+calling the Last_In_Chunk operation, passing in the chunk index of the split
+as a parameter to the call. Similarly, the Next_In_Chunk and Previous_In_Chunk
+calls return the next or previous cursor in the chunk, and return a null
+cursor if the cursor is advanced past the last or before the first cursor
+of a chunk, respectively.
 
 In addition, the loop syntax is extended to allow the loops with iterator
 specifications to have the parallel keyword.
@@ -69,74 +70,62 @@
    | [parallel] for loop_parameter_specification
    | [parallel] for iterator_specification
 
-Add after 5.5.1(4/3)
-
-   type Iteration_Count_Type is mod System.Max_Binary_Modulus;
+Modify 5.5.1 (2/5)
+ generic
+   type Cursor;
+   with function Has_Element (Position : Cursor) return Boolean;
+package Ada.Iterator_Interfaces is
+   with Pure, Nonblocking => {Cursor'Nonblocking and Has_Element'Nonblocking}[False] is
 
-   type Parallel_Iterator is limited interface and Forward_Iterator
-     with Nonblocking;
+Add after 5.5.1(4/3)
 
-   function Iterations
-     (Object : Parallel_Iterator)
-      return Iteration_Count_Type is abstract
-     with Nonblocking;
+   type Parallel_Iterator is limited interface and Forward_Iterator;
 
-   subtype Cursor_Count is Positive;
    subtype Chunk_Index is Positive;
 
+   function Is_Split
+     (Object : Parallel_Iterator) return Boolean is abstract;
+
    procedure Split_Into_Chunks
-     (Object        : in out Parallel_Iterator;
-      Maximum_Split : Cursor_Count) is abstract
-     with Nonblocking,
-          Pre'Class =>
-            Iteration_Count_Type (Maximum_Split) <= Object.Iterations,
-          Post'Class => Object.Chunk_Count <= Maximum_Split;
+     (Object     : in out Parallel_Iterator;
+      Max_Chunks : Chunk_Index) is abstract
+     with
+       Pre'Class  => not Object.Is_Split or else raise Program_Error,
+         Post'Class => Object.Is_Split and then
+         Object.Chunk_Count <= Max_Chunks;
 
    function Chunk_Count
-     (Object  : Parallel_Iterator) return Cursor_Count is abstract
-      with Nonblocking;
+     (Object : Parallel_Iterator) return Chunk_Index is abstract
+     with Pre'Class => Object.Is_Split or else raise Program_Error;
 
-   function Start_Of_Chunk
+   function First_In_Chunk
      (Object : Parallel_Iterator;
       Chunk  : Chunk_Index) return Cursor is abstract
-     with Nonblocking,
-          Pre'Class => Chunk <= Object.Chunk_Count;
+     with Pre'Class => (Object.Is_Split and then Chunk <= Object.Chunk_Count)
+                        or else raise Program_Error;
 
-   function Chunk_Finished
+   function Next_In_Chunk
      (Object   : Parallel_Iterator;
-      Chunk    : Chunk_Index;
-      Position : Cursor) return Boolean is abstract
-     with Nonblocking,
-          Pre'Class => Chunk <= Object.Chunk_Count;
-
-   overriding
-   function First
-     (Object : Parallel_Iterator) return Cursor is abstract
-     with Nonblocking;
-
-   overriding
-   function Next
-     (Object   : Forward_Iterator;
-      Position : Cursor) return Cursor is abstract
-     with Nonblocking;
-
-   [Editor's note: We need the above overridings to make these routines
-   Nonblocking, else they would allow blocking as inherited from their
-   ancestors.]
+      Position : Cursor;
+      Chunk    : Chunk_Index) return Cursor is abstract
+     with Pre'Class => (Object.Is_Split and then Chunk <= Object.Chunk_Count)
+                        or else raise Program_Error;
 
    type Parallel_Reversible_Iterator is limited interface
-     and Parallel_Iterator and Reversible_Iterator with Nonblocking;
+     and Parallel_Iterator and Reversible_Iterator;
 
-   overriding
-   function Last
-     (Object : Parallel_Reversible_Iterator) return Cursor is abstract
-     with Nonblocking;
-
-   overriding
-   function Previous
-     (Object   : Reversible_Iterator;
-      Position : Cursor) return Cursor is abstract
-     with Nonblocking;
+   function Last_In_Chunk
+     (Object : Parallel_Reversible_Iterator;
+      Chunk  : Chunk_Index) return Cursor is abstract
+     with Pre'Class => (Object.Is_Split and then Chunk <= Object.Chunk_Count)
+                        or else raise Program_Error;
+
+   function Previous_In_Chunk
+     (Object   : Parallel_Reversible_Iterator;
+      Position : Cursor;
+      Chunk    : Chunk_Index) return Cursor is abstract
+     with Pre'Class => (Object.Is_Split and then Chunk <= Object.Chunk_Count)
+                        or else raise Program_Error;
 
 Modify 5.5.1(6/3)
 
@@ -182,36 +171,6 @@
 element iterator, the default iterator type for the type of the iterable_name
 shall be of a parallel iterator type or a parallel reversible iterator type.}
 
-Modify 5.5.2(7/5)
-
-An iterator_specification declares a {set of} loop parameter {objects. If the
-keyword parallel is present, multiple objects of the loop parameter are
-created where each iteration is associated with a specific loop parameter
-object; otherwise a single loop parameter object is created for the loop. Each
-loop parameter object is associated with a thread of control where each thread
-of control proceeds independently and concurrently between the points
-where they interact with other tasks and with each other}. In a generalized
-iterator, an array component iterator, or a container element iterator,
-if a loop_parameter_subtype_indication is present, it determines the nominal
-subtype of the loop parameter{s}. In a generalized iterator, if a
-loop_parameter_subtype_indication is not present, the nominal subtype of the
-loop parameter{s} is the iteration cursor subtype. In an array component
-iterator, if a loop_parameter_subtype_indication is not present, the nominal
-subtype of the loop parameter{s are}[ is] the component subtype of the type of
-the iterable_name. In a container element iterator, if a
-loop_parameter_subtype_indication is not present, the nominal subtype of the
-loop parameter{s are}[is] the default element subtype for the type of the
-iterable_name.
-
-Modify 5.5.2(8/3)
-In a generalized iterator, the loop parameter{s are} [is a] constant. In an
-array component iterator, the loop parameter{s are} [is a] constant if the
-iterable_name denotes a constant; otherwise [it]{they} denote[s a] variable{s}.
-In a container element iterator, the loop parameter{s are}[is a] constant if the
-iterable_name denotes a constant, or if the Variable_Indexing aspect is not
-specified for the type of the iterable_name; otherwise [it is a]{they are}
-variable.
-
 Modify AARM 5.5.2(8.a/5)
 
 Ramification: The loop parameter{s} of a generalized iterator {have}[has] the
@@ -248,29 +207,28 @@
 
 Add after 5.5.2(10/3)
 
-For a parallel generalized iterator, the operation Iterations of the iterator
-type is called first to determine the number of iterations associated with the
-iterator. If the result of calling Iterations is 0, then the execution of the
-loop_statement is complete. Otherwise, the operation Split_Into_Chunks of the
-iterator type is then called, with the Maximum_Split parameter specified as the
-upper bound for the number of loop parameter objects to be associated with the
-iterator. The number of loop parameters that were created is determined by
-calling the Chunk_Count operation of the iterator. Each loop parameter can be
-assigned to a different logical thread of control, with an ordinal chunk index
+For a parallel generalized iterator, the operation Split_Into_Chunks of the
+iterator type is then called, with the Max_Chunks parameter specified as the
+upper bound for the number of loop parameter objects (and the number of
+logical threads of control) to be associated with the iterator. The number of
+loop parameters that were created is determined by calling the Chunk_Count
+operation of the iterator. Each loop parameter can be assigned to a different
+logical thread of control, with an ordinal chunk index
 value. The Initial cursor values to be associated with each loop parameter is
-determined by calling the Start_Of_Chunk operation of the iterator using the
+determined by calling the First_In_Chunk operation of the iterator using the
 chunk index value of the loop parameter object as the Chunk parameter for the
 call. The sequence_of_statements is executed for each loop parameter object and
-the Next operation of the iterator type is called with the loop iterator and the
-current value of the loop parameter to produce the next value to be assigned to
-a given loop parameter. The Chunk_Finished operation is called with the chunk
-index value and current loop parameter value to determine if the iteration
-assigned to the logical thread of control is complete. Otherwise the iteration
-continues unless the loop is left as a consequence of a transfer of control.
+the Next_In_Chunk operation of the iterator type is called with the chunk index
+value of the loop parameter object and the current value of the loop parameter
+to produce the next value to be assigned to a given loop parameter. This
+repeats until the result of calling Has_Element on the loop parameter is False,
+or the loop is left as a consequence of a transfer of control.  For a reverse
+parallel generalized iterator, the operations Last_In_Chunk and Previous_In_Chunk
+are called rather than First_In_Chunk and Next_In_Chunk.
 
 AARM Note
 
-The number of loop parameters specified for the Maximum_Split parameter of the
+The number of loop parameters specified for the Max_Chunks parameter of the
 Split_Into_Chunks call is only a recommendation by the implementation. A
 container implementation may choose a lower value if a more optimal split can be
 determined. For instance, a tree based container might create the split based on
@@ -284,8 +242,8 @@
 subtype is indefinite, the variable is constrained by its initial value.
 Similarly, if the nominal subtype is class-wide, the variable (like all
 variables) has the tag of the initial value. Constraint_Error may be raised by a
-subsequent iteration if Next or Previous return an object with
-a different tag or constraint.
+subsequent iteration if Next [or]{,} Previous{, Next_In_Chunk, or
+Previous_In_Chunk} return an object with a different tag or constraint.
 
 Modify 5.5.2(11/3)
 
@@ -332,27 +290,23 @@
 container element iterator, the operations Last and Previous are called rather
 than First and Next.{
 
-For a parallel container element iterator, the operation Iterations is first
-called to determine the number of iterations associated with the iterator. If
-the result of calling Iterations is 0, then the execution of the loop_statement
-is complete. Otherwise, the operation Split_Into_Chunks of the iterator type is
-then called, with the Maximum_Split parameter specified as the upper bound for
-the number of loop parameter objects to be associated with the iterator. The
-number of loop parameters that were created is determined by calling the
+For a parallel container element iterator, the operation Split_Into_Chunks
+of the iterator type is called, with the Max_Chunks parameter specified as the
+upper bound for the number of loop parameter objects to be associated with the iterator.
+The number of loop parameters that were created is determined by calling the
 Chunk_Count operation of the iterator. Each loop parameter can be assigned to a
 different logical thread of control, with an ordinal chunk index value. The
 Initial cursor values to be associated with each loop parameter is determined by
-calling the Start_Of_Chunk operation of the iterator using the chunk index value
+calling the First_In_Chunk operation of the iterator using the chunk index value
 of the loop parameter object as the Chunk parameter for the call. The
-sequence_of_statements is executed for each loop parameter object and the Next
-operation of the iterator type is called with the loop iterator and the current
-value of the loop parameter to produce the next value to be assigned to a given
-loop parameter. The Chunk_Finished operation is called with the chunk index
-value and current loop parameter value to determine if the iteration assigned to
-the logical thread of control is complete. Otherwise, the iteration continues
-unless the loop is left as a consequence of a transfer of control.} If the loop
-parameter is a constant  (see above), then the indexing uses the default
-constant indexing function for the type of the iterable container object for the
+sequence_of_statements is executed for each loop parameter object and the Next_In_Chunk
+operation of the iterator type is called with the chunk index value of the loop
+parameter object and the current value of the loop parameter to produce the next
+value to be assigned to a given loop parameter. This repeats until the result of
+calling Has_Element on the loop parameter is False, or the loop is left as a
+consequence of a transfer of control.} If the loop parameter is a constant
+(see above), then the indexing uses the default constant indexing function for
+the type of the iterable container object for the
 loop; otherwise it uses the default variable indexing function.
 
 Modify 5.5.2(15/3)
@@ -660,20 +614,28 @@
 
 !discussion
 
-Containers such as vectors are obvious candidates for parallelisation, since it
+Containers such as vectors are obvious candidates for parallelization, since it
 is easy to conceptually think of a vector as a being splittable into a number of
 smaller vectors, and then applying a divide and conquer approach to the vector
 subsets. However, even a sequentially accessed container such as a linked list
 can be iterated in parallel if cursors for each parallel executor can be
 obtained prior to execution of the loop. This typically involves iterating once
-through the container to obtain the cursors, but can still be benefit from
+through the container to obtain the cursors, but can still benefit from
 parallel processing if the amount of time to process each node in the loop is
 significantly greater than the amount of time needed to iterate through the
-list.
+container.
 
 The container writer has the ability to implement the container to define
-an optimal number of loop parameters to be setup by the Split_Into_Chunks call, so long as
-the number of splits is not greater than the Maximum_Split parameter.
+an optimal number of loop parameters to be setup by the Split_Into_Chunks call,
+so long as the number of splits is not greater than the value of the
+Max_Chunks parameter.
+
+In AI12-0119 we admitted the possibility of chunks, but the model was
+always one logical thread of control per iteration. Here (and in AI12-0251-1)
+we make chunks explicit in the model, with one logical thread of control per chunk.
+This is important to make it clear that all of the iterations associated
+with a chunk are part of a single logical thread of control, so there is no
+data race between the iterations of a single chunk.
 
 This proposal allows the container implementer a fair amount of flexibility in
 deciding how to implement the cursor array stored in the iterator object. In
@@ -699,7 +661,7 @@
 a post condition in this package because the Cursor formal type is an
 untagged incomplete type.
 
-Similarly, it would have been nice to write a Post condition for Start_Of_Chunk
+Similarly, it would have been nice to write a Post condition for First_In_Chunk
 where calling Has_Element on the result would return true, however this is also
 not allowed for the same reason.
 
@@ -1805,6 +1767,238 @@
 
 In this particular case, we changed many of the names of the routines, as well
 as many of the parameters, but not the specific one you are concerned about.
+
+****************************************************************
+
+From: Brad Moore
+Sent: Wednesday, January 9, 2019  3:40 PM
+
+I was looking at my test program for the Parallel Iterator proposal, and it was
+bothering me that the implementation for the parallel loop was issuing two calls
+to the interface for every iteration.
+
+         Process_Loop :
+         while not Chunk_Finished (...) loop
+
+            Loop_Body;
+
+            Position := Iterator.Next (Position);
+         end loop Process_Loop;
+
+I am viewing subprogram calls into an interface as being expensive.
+I believe this can be reduced to a single call.
+
+So I am proposing modifying the Start_Of_Chunk call to have a Boolean out
+parameter, End_Of_Chunk,
+
+And then replacing the Chunk_Finished call with an override of Next that take a
+Chunk_Index as an input and has an out parameter, End_Of_Chunk.
+
+as in;
+
+   function Start_Of_Chunk
+     (Object       : Parallel_Iterator;
+      Chunk        : Chunk_Index;
+      End_Of_Chunk : out Boolean) return Cursor is abstract
+     with Pre'Class => Object.Is_Split and then Chunk <= Object.Chunk_Count;
+
+   function Next
+     (Object       : Parallel_Iterator;
+      Position     : Cursor;
+      Chunk        : Chunk_Index;
+      End_Of_Chunk : out Boolean) return Cursor is abstract
+     with Pre'Class => Object.Is_Split and then Chunk <= Object.Chunk_Count;
+
+
+Then the implementation loop would look something like;
+
+      procedure Chunk_Code
+        (Chunk : My_Interface.Chunk_Index)
+      is
+         End_Of_Chunk : Boolean := False;
+
+         Position : Employee_Lists.Cursor :=
+           Iterator.Start_Of_Chunk (Chunk,
+                                    End_Of_Chunk);
+      begin --
+
+         while not End_Of_Chunk loop
+
+            -- Loop_Body
+
+            Position := Iterator.Next (Position, Chunk, End_Of_Chunk);
+         end loop Process_Loop;
+
+         --  Finalize
+      end Chunk_Code;
+
+I believe this would potentially improve the execution time of the loop, which
+is the main point for parallel iterators, so unless I hear otherwise, I am
+proceeding with this change to the Parallel Iterator interface in the writeup of
+the AI.
+
+Comments?
+
+****************************************************************
+
+From: Randy Brukardt
+Sent: Wednesday, January 9, 2019  3:58 PM
+
+> I am viewing subprogram calls into an interface as being expensive.
+> I believe this can be reduced to a single call.
+
+I suppose, but that would be different than the usual iterators. It seems best
+for everyone if these work as similarly as possible. I note that this particular
+interface can be implemented rather like an abstract type, at least in the usual
+use cases, so dispatching doesn't have to be as expensive as it is in cases of
+multiple inheritance.
+
+> So I am proposing modifying the Start_Of_Chunk call to have a Boolean
+> out parameter, End_Of_Chunk,
+>
+> And then replacing the Chunk_Finished call with an override of Next
+> that take a Chunk_Index as an input and has an out parameter,
+> End_Of_Chunk.
+
+Huh? You can't "override" a routine and change the profile. That's called
+"overloading". And Ada's rules would require that you still implement the older
+(and now unused) Next, which doesn't seem great for usability.
+
+Seriously, Brad, we're far beyond the point where you ought to be tinkering with
+the interface itself. We discussed the interface and asked for a bunch of name
+and subtype changes, not a wholesale overhaul. (And then we requested a few
+addition changes in e-mail threads last month.) We can never finish these things
+if you redesign them every time you look at them - we've already agreed to a
+design.
+
+****************************************************************
+
+From: Brad Moore
+Sent: Wednesday, January 9, 2019  4:28 PM
+
+>> I am viewing subprogram calls into an interface as being expensive.
+>> I believe this can be reduced to a single call.
+>
+> I suppose, but that would be different than the usual iterators. It
+> seems best for everyone if these work as similarly as possible. I note
+> that this particular interface can be implemented rather like an
+> abstract type, at least in the usual use cases, so dispatching doesn't
+> have to be as expensive as it is in cases of multiple inheritance.
+
+Its not necessarily the dispatching that I am concerned about. It's been my
+experience that subprogram calls in general are expensive. Adding an extra call
+for every iteration seems a lot worse than the concern you had about checking to
+see at the beginning of the loop whether there were zero iterations, which you
+were quite adamant about eliminating the overhead for.
+
+And using your argument, if performance weren't the main point of this feature,
+then I would agree with you, but since we want to in theory squeeze out whatever
+performance we can, it seems we should be eliminating this overhead, since it is
+possible to do so.
+
+>> So I am proposing modifying the Start_Of_Chunk call to have a Boolean
+>> out parameter, End_Of_Chunk,
+>>
+>> And then replacing the Chunk_Finished call with an override of Next
+>> that take a Chunk_Index as an input and has an out parameter,
+>> End_Of_Chunk.
+>
+> Huh? You can't "override" a routine and change the profile. That's
+> called "overloading". And Ada's rules would require that you still
+> implement the older (and now unused) Next, which doesn't seem great for usability.
+
+Sorry, overload is what I meant.
+
+And actually, one of Tuckers comments was to rename Start_Of_Chunk to
+First_In_Chunk, and then have a subprogram called Next_In_Chunk, rather than
+Chunk_Finished in the name of making the usage look more similar to other
+interfaces.
+
+So there actually would not any overload, and having the Next_In_Chunk is
+actually addressing a comment in the minutes from our last discussion of this
+AI. Plus, I think the First_In_Chunk, Next_In_Chunk calls end up looking more
+similar to other iterator interfaces this way.
+
+Plus it would not make sense to have a call Next_In_Chunk at the top of the
+loop. It needs to be at the end of the loop to make sense.
+
+So, I really think the changes I am proposing fall in scope for what was asked
+in the last discussion.
+
+****************************************************************
+
+From: Brad Moore
+Sent: Wednesday, January 9, 2019  4:43 PM
+
+Also, in the name of addressing the comments from the Lexington meeting, I would
+eliminate the End_Of_Chunk boolean output parameters, and instead return a NULL
+Cursor if we are at the end of the chunk. Which makes the interface look even
+more similar to the other iterator interfaces.
+
+****************************************************************
+
+From: Brad Moore
+Sent: Wednesday, January 9, 2019  4:56 PM
+
+Note that by returning a null cursor, though, we'd be requiring a call to
+Has_Element at the top of the loop
+
+eg
+
+while Has_Element (Position) loop
+
+So we are back to having two calls per iteration.
+
+I suppose a compile could inline that call though. Right?
+
+In which case, I think case, I think my concerns would be addressed, as well as
+addressing comments raised against the AI at the last discussion of the AI.
+
+****************************************************************
+
+From: Tucker Taft
+Sent: Wednesday, January 9, 2019  4:02 PM
+
+By all means please make this look as much as possible like a normal iterator.
+
+****************************************************************
+
+From: Randy Brukardt
+Sent: Wednesday, January 9, 2019  5:22 PM
+
+Agreed. To the extent that some other interface could have provided better
+performance, that's a mistake that was made when the original iterators were
+designed and it's rather too late to change that now. (As I recall, the intent
+was that the iterator interface would operate similarly to the way one would
+write an iterator explicitly using the operations of the containers. Originally,
+the iterators shared the implementation with the containers, but since that
+didn't work for multiple iterations on the same object, we abandoned that model
+but never really rethought the interface.)
+
+****************************************************************
+
+From: Brad Moore
+Sent: Wednesday, January 9, 2019  11:36 PM
+
+Attached is my homework for AI12-0266-01, Parallel Container Iterators.
+[This is version /07 of the AI - Editor.]
+
+The summary of the main changes are;
+
+- Changed Nonblocking of everything in the Iterator_Interfaces to depend on the
+  Nonblocking of the generic formal parameters
+- Removed the Iterations call from the Parallel interfaces
+- Added Is_Split to use as precondition for the parallel iterator calls, and
+  raises Program_Error if Split_Into_Chunks is called more than once, or if the
+  other calls are made without having called Split_Into_Chunks
+- Renamed parameter Maximum_Split of Split_Into_Chunks to Max_Chunks
+   A number of splits is confusing. If number of splits is 1, does that mean 1
+   or 2 chunks. Renaming to Max_Chunks eliminates the confusion.
+- Renamed Start_Of_Chunk to First_In_Chunk
+- Replaced Chunk_Finished call with Last_In_Chunk so the iteration looks much
+  more similar to the existing iterator interfaces
+- Added a Last_In_Chunk and Previous_In_Chunk call for
+  Parallel_Reversible_Iterator_Interfaces
 
 ****************************************************************
 

Questions? Ask the ACAA Technical Agent