package DPJRuntime.Framework; import extra166y.*; import java.util.ArrayList; import java.util.concurrent.ConcurrentLinkedQueue; /** * <p>class Pipeline</p> * * <p>An algorithm template for parallel pipeline computations. A * Pipeline consists of an ordered collection of pipeline stages. * Each stage contains an operation called a filter and a buffer of * data elements. The data elements pass through the stages in order; * as a data element passes through a stage, the filter for that stage * is applied to the element. Parallelism occurs because different * filters in different stages can operate on different elements at * the same time.</p> * * <p>We will probably need some mechanisms to control the rate of flow * but let's keep this simple for now. </p> * * @author Vikram Adve * @author Rob Bocchino * * @param<T> The type of a data element flowing through the pipeline * @param<TR> The first region argument of the type T * @param<PR> Region of the pipeline * @param<E> Effect bound for all filters. Filters added to the pipeline * may write TR:*, write PR:*, and do effect E, but do no * other effects. **/ public class Pipeline<type T<region TR>, region PR, effect E | effect E # writes TR:*, PR:* effect E> { // PUBLIC INTERFACES TO BE IMPLEMENTED BY THE USER /** * <p>interface Filter</p> * * A Filter represents an operation performed by a pipeline stage. * * @param<T> The type of an element operated on by the filter * @param<TR> The first region argument of type T * @param<FR> The region of the filter * @param<E> Bound on filter operation effects, other than writing * R and FR **/ public interface Filter<type T<region TR>, region FR, effect E> { /** * Operate on an item. The user implements this method. The * implementation may write R and FR, and do effect E. * * The method region parameter R captures the actual region * (unspecified here) of the item coming in. It ensures that * the returned item was created by some invocation of op * (either this one, or in a previous stage). That ensures * that multiple data elements are never shared by different * stages of the pipeline, so they can be updated in parallel * by the different stages. * * @param <R> Region for parameterizing the incoming and * outgoing items (which may be the same or * different) * @param item Item to operate on * @return Either item or different object with the same * region */ public <region R>T<R> op(T<R> item) writes R, FR effect E; } /** * <p>interface FilterFactory</p> * * A FilterFactory creates a single filter. * * @param<T> Type of element operated on by the filter * @param<TR> First region argument of T * @param<E> Effect bound for the filter **/ public interface FilterFactory<type T<region TR>, effect E> { /** * Create and return a fresh filter instance. The region * parameter R ensures freshness. */ public <region R>Filter<T,R, effect E> createFilter() effect E; } // PUBLICLY VISIBLE METHODS /** * Create an empty pipeline. */ public Pipeline() writes DPJRuntime.RuntimeState.Global { stages = new ArrayList<PipelineStage>(); // Ensure one task per filter DPJRuntime.RuntimeState.dpjForeachCutoff = 1; } /** * Create a new pipeline stage with a new filter, add it to the * tail of the pipeline, and return the filter. * * Notice that the region of the returned filter is PR:[?]. * That's because the region of the filter in stage i is PR:[i]. * Since we're exposing these types to the user, we have to be * careful. If we gave out references to Filter with PR as the * region, then the parameter FR in the Filter interface would be * bound to the same user-visible region for all the different * stages. Because Filter.op allows a write effect on FR, that * could cause a race. * * Also notice we don't need to do this for T<TR> (i.e., we don't * need to make the type here T<TR:[?]>) because Filter.op doesn't * allow any effect on TR. The effect is on R, which captures the * actual region in the type of the item going in. * * @param factory Factory for creating a new filter * @return The created filter. */ public Filter<T,PR:[?],effect E> appendStageWithFilter(FilterFactory<T, effect E> factory) writes PR:[?] effect E { final int idx = stages.size(); Filter<T,PR:[idx],effect E> filter = factory.<region PR:[idx]>createFilter(); PipelineStage stage = new PipelineStage(idx, filter); stages.add(stage); return filter; } /** * Launch tasks for all pipeline stages. * This method returns only when all stages have exited. */ public void launchAllStages() writes TR:[?], PR:[?] effect E { int pipeLength = stages.size(); // Pipeline not set up! assert(pipeLength > 0); // Fork one task per pipeline stage // FIXME: The order of execution may be suboptimal. foreach (int i in 0, pipeLength) { stages.get(i).run(); } } // PRIVATE IMPLEMENTATION OF DPJPIPELINE /** * Pipeline stages */ private final ArrayList<PipelineStage> stages; /** * Debug switch */ private static final boolean DEBUG_PIPE = true; /** * Buffer for input and output at a pipeline stage * * @param<R> Region of the buffer */ private class ItemBuffer<region R> { /** * Queue is thread-safe */ final ConcurrentLinkedQueue<BufferElement> itemQueue; ItemBuffer() pure { itemQueue = new ConcurrentLinkedQueue<BufferElement>(); } boolean isEmpty() reads R { return itemQueue.isEmpty(); } BufferElement peek() reads R { return itemQueue.peek(); } BufferElement poll() writes R { return itemQueue.poll(); } boolean add(BufferElement elt) writes R { return itemQueue.add(elt); } } /** * Wrapper class for elements in the buffer, to represent two * kinds of values flowing through the pipeline: (1) actual data * items; and (2) sentinel values (start and end of stream). */ private class BufferElement { /** * The type of an item in a BufferElement is T<TR:[?]>, to * represent the fact that (1) items are only ever created by * Filter.op, and (2) every newly created object is assigned a * region TR:[i], where the i is unique to that creation. * This careful management of regions isn't strictly * necessary, because it's not checked by the DPJ effect * system, and these types are never seen by the user. * However, it does help document what's going on. Further, * if we ever wanted to give out the types of these data * elements to the user, then the regions would really be * necessary (as they are for the filter regions, q.v.). */ private final T<TR:[?]> item; public BufferElement(T<TR:[?]> item) pure { this.item = item; } public BufferElement() pure { this.item = null; } public T<TR:[?]> getItem() pure { return item; } public String toString() pure { return "BufferElement: " + item.toString(); } } /** * Sentinel value for start of stream */ private final BufferElement STREAM_START = new BufferElement() { public String toString() pure { return "BufferElement: START OF STREAM"; } }; /** * Sentinel value for end of stream */ private final BufferElement STREAM_END = new BufferElement() { public String toString() pure { return "BufferElement: END OF STREAM"; } }; /** * A pipeline stage. Stage i has its mutable data in region * PR:[i]. As far as the DPJ effect system is concerned, all * those regions are PR:[?]. */ private class PipelineStage { /** * The filter operation for this stage */ final Filter<T,PR:[?],effect E> filter; /** * Buffer for the output of this stage */ final ItemBuffer<PR:[?]> outBuf; /** * The index of this stage */ final int idx; /** * Create a new pipeline stage * * @param filter Filter for the stage */ PipelineStage(int idx, Filter<T,PR:[?],effect E> filter) pure { this.idx = idx; this.filter = filter; outBuf = new ItemBuffer<PR:[?]>(); } private void debugPrint(String s) pure { if (DEBUG_PIPE) { System.out.println("Stage " + idx + ": " + s); } } /** * Execute the stage until STREAM_END sentinel either appears * in the input, or is produced by the current stage. */ public void run() writes TR:[?], PR:[?] effect E { PipelineStage previousStage = (idx == 0) ? null : stages.get(idx-1); ItemBuffer<PR:[?]> inBuf = (previousStage == null) ? null : previousStage.outBuf; while (true) { BufferElement elt = STREAM_START; if (inBuf != null) { debugPrint("Looking for input buffer element"); while ((elt = inBuf.poll()) == null) // Spin until input buffer has an entry ; debugPrint("Found " + elt); } if (elt != STREAM_END) { debugPrint("Input element: "+elt); // Every newly created item gets a new index // region, unspecified here. As described above, // these index regions aren't really necessary, // because the user never sees these types. If // the user did see them, they would prevent // aliases that could cause races in the // user-defined Filter.op method. T<TR:[?]> item = filter.op(elt.getItem()); debugPrint("Item produced by filter: " +item); // Null produced by filter means end of stream elt = (item == null) ? STREAM_END : new BufferElement(item); } outBuf.add(elt); if (elt == STREAM_END) break; } } } }