1 /* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies). |
|
2 * |
|
3 * Permission is hereby granted, free of charge, to any person obtaining a |
|
4 * copy of this software and /or associated documentation files |
|
5 * (the "Materials "), to deal in the Materials without restriction, |
|
6 * including without limitation the rights to use, copy, modify, merge, |
|
7 * publish, distribute, sublicense, and/or sell copies of the Materials, |
|
8 * and to permit persons to whom the Materials are furnished to do so, |
|
9 * subject to the following conditions: |
|
10 * |
|
11 * The above copyright notice and this permission notice shall be included |
|
12 * in all copies or substantial portions of the Materials. |
|
13 * |
|
14 * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
17 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
|
18 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
19 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR |
|
20 * THE USE OR OTHER DEALINGS IN THE MATERIALS. |
|
21 */ |
|
22 |
|
23 #ifndef __SFFUNCTIONCACHE_H |
|
24 #define __SFFUNCTIONCACHE_H |
|
25 |
|
26 // (LRU) Cache for compiled pixelpipe functions. Never takes ownership of |
|
27 // any of the objects. |
|
28 // \todo LRU might not be the best strategy or the best strategy might |
|
29 // depend on the use-case -> create more of these. |
|
30 |
|
31 #include "riArray.h" |
|
32 |
|
33 #if defined(__unix__) |
|
34 # include <pthread.h> |
|
35 #else |
|
36 # include <windows.h> |
|
37 #endif |
|
38 |
|
39 #include "llvm/ExecutionEngine/ExecutionEngine.h" |
|
40 #include "llvm/Module.h" |
|
41 |
|
42 namespace llvm { |
|
43 class Function; |
|
44 } |
|
45 |
|
46 namespace OpenVGRI { |
|
47 |
|
48 template<class HashClass> class FunctionCache |
|
49 { |
|
50 private: |
|
51 enum { IMPLEMENTATION_MAX_CACHE_ENTRIES = 1024 }; |
|
52 //enum { MAX_GLOBAL_TIME = 10000}; |
|
53 enum { MAX_GLOBAL_TIME = RI_UINT32_MAX }; |
|
54 |
|
55 struct CacheEntry |
|
56 { |
|
57 CacheEntry() : refCount(1) {} |
|
58 CacheEntry(HashClass aHash, ::llvm::Function* aFunc, ::llvm::GlobalVariable* aConst, RIuint32 theUT) : refCount(1) {hash = aHash; llvmFunction = aFunc; llvmConstant = aConst; ut = theUT;} |
|
59 bool operator==(const CacheEntry& rhs) const { return hash == rhs.hash; } |
|
60 bool operator<(const CacheEntry& rhs) const { return ut < rhs.ut; } // Sort by time of usage. |
|
61 |
|
62 HashClass hash; |
|
63 ::llvm::Function* llvmFunction; |
|
64 ::llvm::GlobalVariable* llvmConstant; |
|
65 RIuint32 ut; |
|
66 RIint32 refCount; |
|
67 }; |
|
68 |
|
69 public: |
|
70 typedef CacheEntry* EntryHandle; |
|
71 |
|
72 public: |
|
73 FunctionCache(int nMaxEntries) : |
|
74 m_time(0) |
|
75 { |
|
76 // Limit so that if the cache is too large, you must optimize the implementation. |
|
77 // Also note that the optimized pixel pipes are most likely small, so it would |
|
78 // be better to have a fast cache and a lot of entries! |
|
79 // \note A simple optimization is to sort the usage time sort order and remove the last |
|
80 // item in the array (instead of the first). |
|
81 RI_ASSERT(nMaxEntries > 0 && nMaxEntries < IMPLEMENTATION_MAX_CACHE_ENTRIES); |
|
82 m_nMaxEntries = nMaxEntries; |
|
83 m_entries.reserve(nMaxEntries); |
|
84 } |
|
85 |
|
86 ~FunctionCache() |
|
87 { |
|
88 for (int i = 0; i < m_entries.size(); i++) |
|
89 { |
|
90 clearEntry(m_entries[i]); |
|
91 } |
|
92 } |
|
93 |
|
94 // This info is needed for the module to remove functions and deallocate executable |
|
95 // functions: |
|
96 void setLLVMInterface(::llvm::ExecutionEngine* ee, ::llvm::Module* module) |
|
97 { |
|
98 m_executionEngine = ee; |
|
99 m_module = module; |
|
100 } |
|
101 |
|
102 // \todo If we never need the entry index, the locking can be |
|
103 // simplified a lot. |
|
104 // Must lock the cache during this operation! |
|
105 EntryHandle findCachedItemByHash(const HashClass& hash) |
|
106 { |
|
107 acquireMutex(); |
|
108 int i = findCachedItemIndexByHash(hash, true); |
|
109 if (i == -1) |
|
110 { |
|
111 releaseMutex(); |
|
112 return NULL; |
|
113 } |
|
114 EntryHandle handle = &m_entries[i]; |
|
115 releaseMutex(); |
|
116 |
|
117 return handle; |
|
118 } |
|
119 |
|
120 /** |
|
121 * \brief Caches a function. Sets the reference count to 1 |
|
122 * \return EntryHandle != NULL on success. |
|
123 * \todo The cache must be locked during the operation. |
|
124 */ |
|
125 EntryHandle cacheFunction(HashClass hash, ::llvm::Function* function, ::llvm::GlobalVariable* constant) |
|
126 { |
|
127 acquireMutex(); |
|
128 RI_ASSERT(findCachedItemIndexByHash(hash) == -1); |
|
129 |
|
130 if (m_entries.size() == m_nMaxEntries) |
|
131 { |
|
132 if (!removeLRU()) |
|
133 { |
|
134 releaseMutex(); |
|
135 return NULL; |
|
136 } |
|
137 } |
|
138 |
|
139 m_entries.push_back(CacheEntry(hash, function, constant, m_time)); |
|
140 |
|
141 RI_ASSERT(m_entries.size() > 0); |
|
142 EntryHandle ret = &m_entries[m_entries.size()-1]; |
|
143 incrementGlobalTime(); |
|
144 |
|
145 releaseMutex(); |
|
146 return ret; |
|
147 } |
|
148 |
|
149 ::llvm::Function* getFunction(EntryHandle handle) |
|
150 { |
|
151 return handle->llvmFunction; |
|
152 } |
|
153 |
|
154 // \note Does not remove the function from cache! |
|
155 void releaseEntry(EntryHandle handle) |
|
156 { |
|
157 RI_ASSERT(handle->refCount > 0); |
|
158 handle->refCount--; |
|
159 } |
|
160 |
|
161 private: |
|
162 void incrementGlobalTime() |
|
163 { |
|
164 m_time++; |
|
165 if (m_time == MAX_GLOBAL_TIME) |
|
166 rebaseUsageTime(); |
|
167 } |
|
168 |
|
169 void incrementAccessTime(CacheEntry &entry) |
|
170 { |
|
171 entry.ut = m_time; |
|
172 incrementGlobalTime(); |
|
173 } |
|
174 |
|
175 int findCachedItemIndexByHash(const HashClass& hash, bool reserve = false) |
|
176 { |
|
177 // \note Could just overload operator== from entry and use the Array.find function. |
|
178 for (int i = 0; i < m_entries.size(); i++) |
|
179 { |
|
180 if (m_entries[i].hash == hash) |
|
181 { |
|
182 if (reserve) |
|
183 { |
|
184 incrementAccessTime(m_entries[i]); |
|
185 m_entries[i].refCount++; |
|
186 } |
|
187 return i; |
|
188 } |
|
189 } |
|
190 return -1; |
|
191 } |
|
192 |
|
193 void clearEntry(CacheEntry& entry) |
|
194 { |
|
195 m_executionEngine->freeMachineCodeForFunction(entry.llvmFunction); |
|
196 entry.llvmFunction->eraseFromParent(); |
|
197 //entry.llvmConstant->eraseFromParent(); |
|
198 } |
|
199 |
|
200 /** |
|
201 * \return true if LRU item was successfully removed, false otherwise. |
|
202 * \note Could try other pipes, but it is unlikely that the cache gets filled |
|
203 * so soon that the blit for the least recently used blit has not been |
|
204 * released. |
|
205 * \todo Implement drop of other cache-entries? |
|
206 */ |
|
207 bool removeLRU() |
|
208 { |
|
209 // \note This is pretty inefficient for many cache size: |
|
210 // After first LRU removal, the cache is almost sorted anyway, so |
|
211 // more efficient solution should be implemented. |
|
212 // |
|
213 m_entries.sort(); |
|
214 |
|
215 if (m_entries[0].refCount > 0) |
|
216 return false; |
|
217 |
|
218 clearEntry(m_entries[0]); |
|
219 m_entries.remove(m_entries[0]); |
|
220 |
|
221 return true; |
|
222 } |
|
223 |
|
224 void rebaseUsageTime() |
|
225 { |
|
226 RIuint32 i; |
|
227 m_entries.sort(); |
|
228 RI_ASSERT(m_entries.size() > 0); |
|
229 for(i = 0; i < (RIuint32)m_entries.size(); i++) |
|
230 { |
|
231 m_entries[i].ut = i; |
|
232 }; |
|
233 m_time = i; |
|
234 } |
|
235 |
|
236 static void acquireMutex(); |
|
237 static void releaseMutex(); |
|
238 |
|
239 private: |
|
240 ::llvm::Module *m_module; |
|
241 ::llvm::ExecutionEngine *m_executionEngine; |
|
242 |
|
243 RIuint32 m_time; |
|
244 Array <CacheEntry> m_entries; |
|
245 int m_nMaxEntries; |
|
246 |
|
247 static bool s_mutexInitialized; |
|
248 #if defined(__unix__) |
|
249 static pthread_mutex_t s_mutex; |
|
250 #else |
|
251 static CRITICAL_SECTION s_mutex; |
|
252 #endif |
|
253 }; |
|
254 |
|
255 template<class HashClass> |
|
256 bool FunctionCache<HashClass>::s_mutexInitialized = false; |
|
257 |
|
258 #if defined(__unix__) |
|
259 template<class HashClass> |
|
260 pthread_mutex_t FunctionCache<HashClass>::s_mutex; |
|
261 #else |
|
262 template<class HashClass> |
|
263 CRITICAL_SECTION FunctionCache<HashClass>::s_mutex; |
|
264 #endif |
|
265 |
|
266 template<class HashClass> |
|
267 void FunctionCache<HashClass>::acquireMutex() |
|
268 { |
|
269 if (!s_mutexInitialized) |
|
270 { |
|
271 #if defined(__unix__) |
|
272 int ret; |
|
273 pthread_mutexattr_t attr; |
|
274 ret = pthread_mutexattr_init(&attr); //initially not locked |
|
275 RI_ASSERT(!ret); //check that there aren't any errors |
|
276 ret = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //count the number of recursive locks |
|
277 RI_ASSERT(!ret); //check that there aren't any errors |
|
278 ret = pthread_mutex_init(&s_mutex, &attr); |
|
279 pthread_mutexattr_destroy(&attr); |
|
280 RI_ASSERT(!ret); //check that there aren't more errors |
|
281 #else |
|
282 ::InitializeCriticalSection(&s_mutex); |
|
283 #endif |
|
284 s_mutexInitialized = true; |
|
285 } |
|
286 #if defined(__unix__) |
|
287 int ret = pthread_mutex_lock(&s_mutex); |
|
288 RI_ASSERT(!ret); |
|
289 #else |
|
290 ::EnterCriticalSection(&s_mutex); |
|
291 #endif |
|
292 } |
|
293 |
|
294 template<class HashClass> |
|
295 void FunctionCache<HashClass>::releaseMutex() |
|
296 { |
|
297 RI_ASSERT(s_mutexInitialized); |
|
298 #if defined(__unix__) |
|
299 int ret = pthread_mutex_unlock(&s_mutex); |
|
300 RI_ASSERT(!ret); |
|
301 #else |
|
302 ::LeaveCriticalSection(&s_mutex); |
|
303 #endif |
|
304 } |
|
305 |
|
306 } |
|
307 |
|
308 |
|
309 #endif |
|
310 |
|