Proteus
Programmable JIT compilation and optimization for C/C++ using LLVM
Loading...
Searching...
No Matches
CompilerAsync.h
Go to the documentation of this file.
1#ifndef PROTEUS_ASYNC_COMPILER_H
2#define PROTEUS_ASYNC_COMPILER_H
3
7
8#include <condition_variable>
9#include <deque>
10#include <thread>
11
12namespace proteus {
13
14using namespace llvm;
15
17public:
18 explicit CompilationResult() : IsReadyFlag{false} {}
19
22
23 CompilationResult(CompilationResult &&) noexcept = delete;
24 CompilationResult &operator=(CompilationResult &&) noexcept = delete;
25
26 bool isReady() { return IsReadyFlag; }
27
28 void set(std::unique_ptr<MemoryBuffer> ObjBuf) {
29 ResultObjBuf = std::move(ObjBuf);
30 IsReadyFlag = true;
31 }
32
33 void wait() {
34 // Busy wait until it's ready.
35 while (!IsReadyFlag) {
36 std::this_thread::yield();
37 }
38 }
39
40 std::unique_ptr<MemoryBuffer> take() { return std::move(ResultObjBuf); }
41
42private:
43 std::atomic<bool> IsReadyFlag;
44 std::unique_ptr<MemoryBuffer> ResultObjBuf;
45};
46
48public:
50 std::unique_lock Lock{Mutex};
51 // Get HashValue before the move of CT.
52 HashT HashValue = CT.getHashValue();
53 Worklist.emplace_back(std::move(CT));
54 CompilationResultMap.emplace(HashValue,
55 std::make_unique<CompilationResult>());
56 CondVar.notify_one();
57 }
58
59 void run() {
60 [[maybe_unused]] int Count = 0;
61 while (Active) {
62 std::unique_lock Lock(Mutex);
63 CondVar.wait(Lock, [this] { return !Worklist.empty() || !Active; });
64 if (!Active)
65 break;
66 if (Worklist.empty())
67 reportFatalError("Expected non-empty Worklist");
68 CompilationTask CT = std::move(Worklist.front());
69 Worklist.pop_front();
70 Lock.unlock();
71
72 Count++;
73 std::unique_ptr<MemoryBuffer> ObjBuf = CT.compile();
74
75 // Ensure threads are joined before static objects are destroyed during
76 // program exit. This static is initialized after COMGR's own statics
77 // (triggered by compile() above), so C++ reverse destruction order
78 // guarantees ~ShutdownGuard runs first.
79 static ShutdownGuard Guard{this};
80
81 Lock.lock();
82 CompilationResultMap.at(CT.getHashValue())->set(std::move(ObjBuf));
83 Lock.unlock();
84 }
85
86 PROTEUS_DBG(Logger::logs("proteus")
87 << "Thread exiting! Compiled " + std::to_string(Count) + "\n");
88 }
89
91 {
92 std::unique_lock Lock{Mutex};
93 // Return if already inactive.
94 if (!Active)
95 return;
96
97 Active = false;
98 }
99 CondVar.notify_all();
100
101 for (auto &Thread : Threads)
102 if (Thread.joinable())
103 Thread.join();
104
105 Threads.clear();
106 }
107
108 bool isCompilationPending(HashT HashValue) {
109 std::unique_lock Lock{Mutex};
110 return !(CompilationResultMap.find(HashValue) ==
111 CompilationResultMap.end());
112 }
113
114 std::unique_ptr<MemoryBuffer> takeCompilationResult(HashT HashValue,
115 bool BlockingWait) {
116 std::unique_lock Lock{Mutex};
117 auto It = CompilationResultMap.find(HashValue);
118 if (It == CompilationResultMap.end())
119 return nullptr;
120
121 std::unique_ptr<CompilationResult> &CRes = It->second;
122
123 if (BlockingWait) {
124 // Release the lock while waiting for the result.
125 Lock.unlock();
126 CRes->wait();
127 // Reacquire the lock for synchronized access, the result is ready.
128 Lock.lock();
129 } else {
130 if (!CRes->isReady())
131 return nullptr;
132 }
133
134 // If compilation result is ready, take ownership of the buffer, erase it
135 // from the compilation results map and move the buffer to the caller.
136 std::unique_ptr<MemoryBuffer> ObjBuf = CRes->take();
137 // Use the HashValue key as the iterator may have been invalidated by
138 // insert/emplace from another thread.
139 CompilationResultMap.erase(HashValue);
140 Lock.unlock();
141 return ObjBuf;
142 }
143
144 CompilerAsync(int NumThreads) {
145 Active = true;
146 for (int I = 0; I < NumThreads; ++I)
147 Threads.emplace_back(&CompilerAsync::run, this);
148 }
149
151
152private:
153 // Joins threads before static objects are destroyed at exit (e.g.,
154 // COMGR/HIPRTC). Must be constructed after the first compile()) so that
155 // reverse destruction order ensures this runs first.
156 struct ShutdownGuard {
157 CompilerAsync *CA;
158 ~ShutdownGuard() {
159 if (CA)
160 CA->joinAllThreads();
161 }
162 };
163
164 bool Active;
165 std::mutex Mutex;
166 std::condition_variable CondVar;
167 std::unordered_map<HashT, std::unique_ptr<CompilationResult>>
168 CompilationResultMap;
169 std::deque<CompilationTask> Worklist;
170 std::vector<std::thread> Threads;
171};
172
173} // namespace proteus
174
175#endif
#define PROTEUS_DBG(x)
Definition Debug.h:9
Definition CompilerAsync.h:16
CompilationResult(CompilationResult &&) noexcept=delete
void set(std::unique_ptr< MemoryBuffer > ObjBuf)
Definition CompilerAsync.h:28
bool isReady()
Definition CompilerAsync.h:26
CompilationResult(const CompilationResult &)=delete
CompilationResult & operator=(const CompilationResult &)=delete
std::unique_ptr< MemoryBuffer > take()
Definition CompilerAsync.h:40
void wait()
Definition CompilerAsync.h:33
CompilationResult()
Definition CompilerAsync.h:18
Definition CompilationTask.h:19
HashT getHashValue() const
Definition CompilationTask.h:120
std::unique_ptr< MemoryBuffer > compile()
Definition CompilationTask.h:122
Definition CompilerAsync.h:47
bool isCompilationPending(HashT HashValue)
Definition CompilerAsync.h:108
CompilerAsync(int NumThreads)
Definition CompilerAsync.h:144
void compile(CompilationTask &&CT)
Definition CompilerAsync.h:49
std::unique_ptr< MemoryBuffer > takeCompilationResult(HashT HashValue, bool BlockingWait)
Definition CompilerAsync.h:114
void joinAllThreads()
Definition CompilerAsync.h:90
~CompilerAsync()
Definition CompilerAsync.h:150
void run()
Definition CompilerAsync.h:59
Definition Hashing.h:21
static llvm::raw_ostream & logs(const std::string &Name)
Definition Logger.h:19
Definition CompiledLibrary.h:7
Definition MemoryCache.h:26
void reportFatalError(const llvm::Twine &Reason, const char *FILE, unsigned Line)
Definition Error.cpp:14