• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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