|  | //===- 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 |