| //===- subzero/src/IceThreading.h - Threading functions ---------*- C++ -*-===// | 
 | // | 
 | //                        The Subzero Code Generator | 
 | // | 
 | // This file is distributed under the University of Illinois Open Source | 
 | // License. See LICENSE.TXT for details. | 
 | // | 
 | //===----------------------------------------------------------------------===// | 
 | /// | 
 | /// \file | 
 | /// \brief Declares threading-related functions. | 
 | /// | 
 | //===----------------------------------------------------------------------===// | 
 |  | 
 | #ifndef SUBZERO_SRC_ICETHREADING_H | 
 | #define SUBZERO_SRC_ICETHREADING_H | 
 |  | 
 | #include "IceDefs.h" | 
 |  | 
 | #include <condition_variable> | 
 | #include <memory> | 
 | #include <mutex> | 
 | #include <utility> | 
 |  | 
 | namespace Ice { | 
 |  | 
 | /// BoundedProducerConsumerQueue is a work queue that allows multiple producers | 
 | /// and multiple consumers. A producer adds entries using blockingPush(), and | 
 | /// may block if the queue is "full". A producer uses notifyEnd() to indicate | 
 | /// that no more entries will be added. A consumer removes an item using | 
 | /// blockingPop(), which will return nullptr if notifyEnd() has been called and | 
 | /// the queue is empty (it never returns nullptr if the queue contained any | 
 | /// items). | 
 | /// | 
 | /// The MaxSize ctor arg controls the maximum size the queue can grow to | 
 | /// (subject to a hard limit of MaxStaticSize-1). The Sequential arg indicates | 
 | /// purely sequential execution in which the single thread should never wait(). | 
 | /// | 
 | /// Two condition variables are used in the implementation. GrewOrEnded signals | 
 | /// a waiting worker that a producer has changed the state of the queue. Shrunk | 
 | /// signals a blocked producer that a consumer has changed the state of the | 
 | /// queue. | 
 | /// | 
 | /// The methods begin with Sequential-specific code to be most clear. The lock | 
 | /// and condition variables are not used in the Sequential case. | 
 | /// | 
 | /// Internally, the queue is implemented as a circular array of size | 
 | /// MaxStaticSize, where the queue boundaries are denoted by the Front and Back | 
 | /// fields. Front==Back indicates an empty queue. | 
 | template <typename T, size_t MaxStaticSize = 128> | 
 | class BoundedProducerConsumerQueue { | 
 |   BoundedProducerConsumerQueue() = delete; | 
 |   BoundedProducerConsumerQueue(const BoundedProducerConsumerQueue &) = delete; | 
 |   BoundedProducerConsumerQueue & | 
 |   operator=(const BoundedProducerConsumerQueue &) = delete; | 
 |  | 
 | public: | 
 |   BoundedProducerConsumerQueue(bool Sequential, size_t MaxSize = MaxStaticSize) | 
 |       : MaxSize(std::min(MaxSize, MaxStaticSize)), Sequential(Sequential) {} | 
 |   void blockingPush(std::unique_ptr<T> Item) { | 
 |     { | 
 |       std::unique_lock<GlobalLockType> L(Lock); | 
 |       // If the work queue is already "full", wait for a consumer to grab an | 
 |       // element and shrink the queue. | 
 |       Shrunk.wait(L, [this] { return size() < MaxSize || Sequential; }); | 
 |       push(std::move(Item)); | 
 |     } | 
 |     GrewOrEnded.notify_one(); | 
 |   } | 
 |   std::unique_ptr<T> blockingPop(size_t NotifyWhenDownToSize = MaxStaticSize) { | 
 |     std::unique_ptr<T> Item; | 
 |     bool ShouldNotifyProducer = false; | 
 |     { | 
 |       std::unique_lock<GlobalLockType> L(Lock); | 
 |       GrewOrEnded.wait(L, [this] { return IsEnded || !empty() || Sequential; }); | 
 |       if (!empty()) { | 
 |         Item = pop(); | 
 |         ShouldNotifyProducer = (size() < NotifyWhenDownToSize) && !IsEnded; | 
 |       } | 
 |     } | 
 |     if (ShouldNotifyProducer) | 
 |       Shrunk.notify_one(); | 
 |     return Item; | 
 |   } | 
 |   void notifyEnd() { | 
 |     { | 
 |       std::lock_guard<GlobalLockType> L(Lock); | 
 |       IsEnded = true; | 
 |     } | 
 |     GrewOrEnded.notify_all(); | 
 |   } | 
 |  | 
 | private: | 
 |   const static size_t MaxStaticSizeMask = MaxStaticSize - 1; | 
 |   static_assert(!(MaxStaticSize & (MaxStaticSize - 1)), | 
 |                 "MaxStaticSize must be a power of 2"); | 
 |  | 
 |   ICE_CACHELINE_BOUNDARY; | 
 |   /// WorkItems and Lock are read/written by all. | 
 |   std::unique_ptr<T> WorkItems[MaxStaticSize]; | 
 |   ICE_CACHELINE_BOUNDARY; | 
 |   /// Lock guards access to WorkItems, Front, Back, and IsEnded. | 
 |   GlobalLockType Lock; | 
 |  | 
 |   ICE_CACHELINE_BOUNDARY; | 
 |   /// GrewOrEnded is written by the producers and read by the consumers. It is | 
 |   /// notified (by the producer) when something is added to the queue, in case | 
 |   /// consumers are waiting for a non-empty queue. | 
 |   std::condition_variable GrewOrEnded; | 
 |   /// Back is the index into WorkItems[] of where the next element will be | 
 |   /// pushed. (More precisely, Back&MaxStaticSize is the index.) It is written | 
 |   /// by the producers, and read by all via size() and empty(). | 
 |   size_t Back = 0; | 
 |  | 
 |   ICE_CACHELINE_BOUNDARY; | 
 |   /// Shrunk is notified (by the consumer) when something is removed from the | 
 |   /// queue, in case a producer is waiting for the queue to drop below maximum | 
 |   /// capacity. It is written by the consumers and read by the producers. | 
 |   std::condition_variable Shrunk; | 
 |   /// Front is the index into WorkItems[] of the oldest element, i.e. the next | 
 |   /// to be popped. (More precisely Front&MaxStaticSize is the index.) It is | 
 |   /// written by the consumers, and read by all via size() and empty(). | 
 |   size_t Front = 0; | 
 |  | 
 |   ICE_CACHELINE_BOUNDARY; | 
 |  | 
 |   /// MaxSize and Sequential are read by all and written by none. | 
 |   const size_t MaxSize; | 
 |   const bool Sequential; | 
 |   /// IsEnded is read by the consumers, and only written once by the producer. | 
 |   bool IsEnded = false; | 
 |  | 
 |   /// The lock must be held when the following methods are called. | 
 |   bool empty() const { return Front == Back; } | 
 |   size_t size() const { return Back - Front; } | 
 |   void push(std::unique_ptr<T> Item) { | 
 |     WorkItems[Back++ & MaxStaticSizeMask] = std::move(Item); | 
 |     assert(size() <= MaxStaticSize); | 
 |   } | 
 |   std::unique_ptr<T> pop() { | 
 |     assert(!empty()); | 
 |     return std::move(WorkItems[Front++ & MaxStaticSizeMask]); | 
 |   } | 
 | }; | 
 |  | 
 | /// EmitterWorkItem is a simple wrapper around a pointer that represents a work | 
 | /// item to be emitted, i.e. a function or a set of global declarations and | 
 | /// initializers, and it includes a sequence number so that work items can be | 
 | /// emitted in a particular order for deterministic output. It acts like an | 
 | /// interface class, but instead of making the classes of interest inherit from | 
 | /// EmitterWorkItem, it wraps pointers to these classes. Some space is wasted | 
 | /// compared to storing the pointers in a union, but not too much due to the | 
 | /// work granularity. | 
 | class EmitterWorkItem { | 
 |   EmitterWorkItem() = delete; | 
 |   EmitterWorkItem(const EmitterWorkItem &) = delete; | 
 |   EmitterWorkItem &operator=(const EmitterWorkItem &) = delete; | 
 |  | 
 | public: | 
 |   /// ItemKind can be one of the following: | 
 |   /// | 
 |   /// WI_Nop: No actual work. This is a placeholder to maintain sequence numbers | 
 |   /// in case there is a translation error. | 
 |   /// | 
 |   /// WI_GlobalInits: A list of global declarations and initializers. | 
 |   /// | 
 |   /// WI_Asm: A function that has already had emitIAS() called on it. The work | 
 |   /// is transferred via the Assembler buffer, and the originating Cfg has been | 
 |   /// deleted (to recover lots of memory). | 
 |   /// | 
 |   /// WI_Cfg: A Cfg that has not yet had emit() or emitIAS() called on it. This | 
 |   /// is only used as a debugging configuration when we want to emit "readable" | 
 |   /// assembly code, possibly annotated with liveness and other information only | 
 |   /// available in the Cfg and not in the Assembler buffer. | 
 |   enum ItemKind { WI_Nop, WI_GlobalInits, WI_Asm, WI_Cfg }; | 
 |   /// Constructor for a WI_Nop work item. | 
 |   explicit EmitterWorkItem(uint32_t Seq); | 
 |   /// Constructor for a WI_GlobalInits work item. | 
 |   EmitterWorkItem(uint32_t Seq, std::unique_ptr<VariableDeclarationList> D); | 
 |   /// Constructor for a WI_Asm work item. | 
 |   EmitterWorkItem(uint32_t Seq, std::unique_ptr<Assembler> A); | 
 |   /// Constructor for a WI_Cfg work item. | 
 |   EmitterWorkItem(uint32_t Seq, std::unique_ptr<Cfg> F); | 
 |   uint32_t getSequenceNumber() const { return Sequence; } | 
 |   ItemKind getKind() const { return Kind; } | 
 |   void setGlobalInits(std::unique_ptr<VariableDeclarationList> GloblInits); | 
 |   std::unique_ptr<VariableDeclarationList> getGlobalInits(); | 
 |   std::unique_ptr<Assembler> getAsm(); | 
 |   std::unique_ptr<Cfg> getCfg(); | 
 |  | 
 | private: | 
 |   const uint32_t Sequence; | 
 |   const ItemKind Kind; | 
 |   std::unique_ptr<VariableDeclarationList> GlobalInits; | 
 |   std::unique_ptr<Assembler> Function; | 
 |   std::unique_ptr<Cfg> RawFunc; | 
 | }; | 
 |  | 
 | } // end of namespace Ice | 
 |  | 
 | #endif // SUBZERO_SRC_ICETHREADING_H |