1 //===- subzero/src/IceThreading.h - Threading functions ---------*- C++ -*-===// 2 // 3 // The Subzero Code Generator 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 /// 10 /// \file 11 /// \brief Declares threading-related functions. 12 /// 13 //===----------------------------------------------------------------------===// 14 15 #ifndef SUBZERO_SRC_ICETHREADING_H 16 #define SUBZERO_SRC_ICETHREADING_H 17 18 #include "IceDefs.h" 19 20 #include <condition_variable> 21 #include <memory> 22 #include <mutex> 23 #include <utility> 24 25 namespace Ice { 26 27 /// BoundedProducerConsumerQueue is a work queue that allows multiple producers 28 /// and multiple consumers. A producer adds entries using blockingPush(), and 29 /// may block if the queue is "full". A producer uses notifyEnd() to indicate 30 /// that no more entries will be added. A consumer removes an item using 31 /// blockingPop(), which will return nullptr if notifyEnd() has been called and 32 /// the queue is empty (it never returns nullptr if the queue contained any 33 /// items). 34 /// 35 /// The MaxSize ctor arg controls the maximum size the queue can grow to 36 /// (subject to a hard limit of MaxStaticSize-1). The Sequential arg indicates 37 /// purely sequential execution in which the single thread should never wait(). 38 /// 39 /// Two condition variables are used in the implementation. GrewOrEnded signals 40 /// a waiting worker that a producer has changed the state of the queue. Shrunk 41 /// signals a blocked producer that a consumer has changed the state of the 42 /// queue. 43 /// 44 /// The methods begin with Sequential-specific code to be most clear. The lock 45 /// and condition variables are not used in the Sequential case. 46 /// 47 /// Internally, the queue is implemented as a circular array of size 48 /// MaxStaticSize, where the queue boundaries are denoted by the Front and Back 49 /// fields. Front==Back indicates an empty queue. 50 template <typename T, size_t MaxStaticSize = 128> 51 class BoundedProducerConsumerQueue { 52 BoundedProducerConsumerQueue() = delete; 53 BoundedProducerConsumerQueue(const BoundedProducerConsumerQueue &) = delete; 54 BoundedProducerConsumerQueue & 55 operator=(const BoundedProducerConsumerQueue &) = delete; 56 57 public: 58 BoundedProducerConsumerQueue(bool Sequential, size_t MaxSize = MaxStaticSize) MaxSize(std::min (MaxSize,MaxStaticSize))59 : MaxSize(std::min(MaxSize, MaxStaticSize)), Sequential(Sequential) {} blockingPush(std::unique_ptr<T> Item)60 void blockingPush(std::unique_ptr<T> Item) { 61 { 62 std::unique_lock<GlobalLockType> L(Lock); 63 // If the work queue is already "full", wait for a consumer to grab an 64 // element and shrink the queue. 65 Shrunk.wait(L, [this] { return size() < MaxSize || Sequential; }); 66 push(std::move(Item)); 67 } 68 GrewOrEnded.notify_one(); 69 } 70 std::unique_ptr<T> blockingPop(size_t NotifyWhenDownToSize = MaxStaticSize) { 71 std::unique_ptr<T> Item; 72 bool ShouldNotifyProducer = false; 73 { 74 std::unique_lock<GlobalLockType> L(Lock); 75 GrewOrEnded.wait(L, [this] { return IsEnded || !empty() || Sequential; }); 76 if (!empty()) { 77 Item = pop(); 78 ShouldNotifyProducer = (size() < NotifyWhenDownToSize) && !IsEnded; 79 } 80 } 81 if (ShouldNotifyProducer) 82 Shrunk.notify_one(); 83 return Item; 84 } notifyEnd()85 void notifyEnd() { 86 { 87 std::lock_guard<GlobalLockType> L(Lock); 88 IsEnded = true; 89 } 90 GrewOrEnded.notify_all(); 91 } 92 93 private: 94 const static size_t MaxStaticSizeMask = MaxStaticSize - 1; 95 static_assert(!(MaxStaticSize & (MaxStaticSize - 1)), 96 "MaxStaticSize must be a power of 2"); 97 98 ICE_CACHELINE_BOUNDARY; 99 /// WorkItems and Lock are read/written by all. 100 std::unique_ptr<T> WorkItems[MaxStaticSize]; 101 ICE_CACHELINE_BOUNDARY; 102 /// Lock guards access to WorkItems, Front, Back, and IsEnded. 103 GlobalLockType Lock; 104 105 ICE_CACHELINE_BOUNDARY; 106 /// GrewOrEnded is written by the producers and read by the consumers. It is 107 /// notified (by the producer) when something is added to the queue, in case 108 /// consumers are waiting for a non-empty queue. 109 std::condition_variable GrewOrEnded; 110 /// Back is the index into WorkItems[] of where the next element will be 111 /// pushed. (More precisely, Back&MaxStaticSize is the index.) It is written 112 /// by the producers, and read by all via size() and empty(). 113 size_t Back = 0; 114 115 ICE_CACHELINE_BOUNDARY; 116 /// Shrunk is notified (by the consumer) when something is removed from the 117 /// queue, in case a producer is waiting for the queue to drop below maximum 118 /// capacity. It is written by the consumers and read by the producers. 119 std::condition_variable Shrunk; 120 /// Front is the index into WorkItems[] of the oldest element, i.e. the next 121 /// to be popped. (More precisely Front&MaxStaticSize is the index.) It is 122 /// written by the consumers, and read by all via size() and empty(). 123 size_t Front = 0; 124 125 ICE_CACHELINE_BOUNDARY; 126 127 /// MaxSize and Sequential are read by all and written by none. 128 const size_t MaxSize; 129 const bool Sequential; 130 /// IsEnded is read by the consumers, and only written once by the producer. 131 bool IsEnded = false; 132 133 /// The lock must be held when the following methods are called. empty()134 bool empty() const { return Front == Back; } size()135 size_t size() const { return Back - Front; } push(std::unique_ptr<T> Item)136 void push(std::unique_ptr<T> Item) { 137 WorkItems[Back++ & MaxStaticSizeMask] = std::move(Item); 138 assert(size() <= MaxStaticSize); 139 } pop()140 std::unique_ptr<T> pop() { 141 assert(!empty()); 142 return std::move(WorkItems[Front++ & MaxStaticSizeMask]); 143 } 144 }; 145 146 /// EmitterWorkItem is a simple wrapper around a pointer that represents a work 147 /// item to be emitted, i.e. a function or a set of global declarations and 148 /// initializers, and it includes a sequence number so that work items can be 149 /// emitted in a particular order for deterministic output. It acts like an 150 /// interface class, but instead of making the classes of interest inherit from 151 /// EmitterWorkItem, it wraps pointers to these classes. Some space is wasted 152 /// compared to storing the pointers in a union, but not too much due to the 153 /// work granularity. 154 class EmitterWorkItem { 155 EmitterWorkItem() = delete; 156 EmitterWorkItem(const EmitterWorkItem &) = delete; 157 EmitterWorkItem &operator=(const EmitterWorkItem &) = delete; 158 159 public: 160 /// ItemKind can be one of the following: 161 /// 162 /// WI_Nop: No actual work. This is a placeholder to maintain sequence numbers 163 /// in case there is a translation error. 164 /// 165 /// WI_GlobalInits: A list of global declarations and initializers. 166 /// 167 /// WI_Asm: A function that has already had emitIAS() called on it. The work 168 /// is transferred via the Assembler buffer, and the originating Cfg has been 169 /// deleted (to recover lots of memory). 170 /// 171 /// WI_Cfg: A Cfg that has not yet had emit() or emitIAS() called on it. This 172 /// is only used as a debugging configuration when we want to emit "readable" 173 /// assembly code, possibly annotated with liveness and other information only 174 /// available in the Cfg and not in the Assembler buffer. 175 enum ItemKind { WI_Nop, WI_GlobalInits, WI_Asm, WI_Cfg }; 176 /// Constructor for a WI_Nop work item. 177 explicit EmitterWorkItem(uint32_t Seq); 178 /// Constructor for a WI_GlobalInits work item. 179 EmitterWorkItem(uint32_t Seq, std::unique_ptr<VariableDeclarationList> D); 180 /// Constructor for a WI_Asm work item. 181 EmitterWorkItem(uint32_t Seq, std::unique_ptr<Assembler> A); 182 /// Constructor for a WI_Cfg work item. 183 EmitterWorkItem(uint32_t Seq, std::unique_ptr<Cfg> F); getSequenceNumber()184 uint32_t getSequenceNumber() const { return Sequence; } getKind()185 ItemKind getKind() const { return Kind; } 186 void setGlobalInits(std::unique_ptr<VariableDeclarationList> GloblInits); 187 std::unique_ptr<VariableDeclarationList> getGlobalInits(); 188 std::unique_ptr<Assembler> getAsm(); 189 std::unique_ptr<Cfg> getCfg(); 190 191 private: 192 const uint32_t Sequence; 193 const ItemKind Kind; 194 std::unique_ptr<VariableDeclarationList> GlobalInits; 195 std::unique_ptr<Assembler> Function; 196 std::unique_ptr<Cfg> RawFunc; 197 }; 198 199 } // end of namespace Ice 200 201 #endif // SUBZERO_SRC_ICETHREADING_H 202