Home | History | Annotate | Download | only in src
      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)
     59       : MaxSize(std::min(MaxSize, MaxStaticSize)), Sequential(Sequential) {}
     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   }
     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.
    134   bool empty() const { return Front == Back; }
    135   size_t size() const { return Back - Front; }
    136   void push(std::unique_ptr<T> Item) {
    137     WorkItems[Back++ & MaxStaticSizeMask] = std::move(Item);
    138     assert(size() <= MaxStaticSize);
    139   }
    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);
    184   uint32_t getSequenceNumber() const { return Sequence; }
    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