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