Talvos  0.1
SPIR-V interpreter and dynamic analysis framework
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
Device.cpp
Go to the documentation of this file.
1 // Copyright (c) 2018 the Talvos developers. All rights reserved.
2 //
3 // This file is distributed under a three-clause BSD license. For full license
4 // terms please see the LICENSE file distributed with this source code.
5 
8 
9 #include <atomic>
10 #include <cassert>
11 #include <chrono>
12 #include <cstdlib>
13 #include <iostream>
14 #include <sstream>
15 
16 #if defined(_WIN32) && !defined(__MINGW32__)
17 #include <windows.h>
18 #undef ERROR
19 #undef VOID
20 #else
21 #include <dlfcn.h>
22 #endif
23 
24 #include "PipelineExecutor.h"
25 #include "Utils.h"
26 #include "talvos/Commands.h"
27 #include "talvos/ComputePipeline.h"
28 #include "talvos/Device.h"
29 #include "talvos/EntryPoint.h"
30 #include "talvos/Function.h"
31 #include "talvos/Instruction.h"
32 #include "talvos/Invocation.h"
33 #include "talvos/Memory.h"
34 #include "talvos/Module.h"
35 #include "talvos/PipelineStage.h"
36 #include "talvos/Plugin.h"
37 #include "talvos/Workgroup.h"
38 
39 namespace talvos
40 {
41 
42 typedef Plugin *(*CreatePluginFunc)(const Device *);
43 typedef void (*DestroyPluginFunc)(Plugin *);
44 
45 // Counter for the number of errors reported.
46 static std::atomic<size_t> NumErrors;
47 
49 {
51 
52  // Load plugins from dynamic libraries.
53  const char *PluginList = getenv("TALVOS_PLUGINS");
54  if (PluginList)
55  {
56  std::istringstream SS(PluginList);
57  std::string LibPath;
58  while (std::getline(SS, LibPath, ';'))
59  {
60 #if defined(_WIN32) && !defined(__MINGW32__)
61  // Open plugin library file.
62  HMODULE Library = LoadLibraryA(LibPath.c_str());
63  if (!Library)
64  {
65  std::cerr << "Talvos: Failed to load plugin '" << LibPath << "' - "
66  << GetLastError() << std::endl;
67  continue;
68  }
69 
70  // Get handle to plugin creation function.
71  void *Create = GetProcAddress(Library, "talvosCreatePlugin");
72  if (!Create)
73  {
74  std::cerr << "Talvos: Failed to load plugin '" << LibPath << "' - "
75  << GetLastError() << std::endl;
76  continue;
77  }
78 #else
79  // Open plugin library file.
80  void *Library = dlopen(LibPath.c_str(), RTLD_NOW);
81  if (!Library)
82  {
83  std::cerr << "Talvos: Failed to load plugin '" << LibPath << "' - "
84  << dlerror() << std::endl;
85  continue;
86  }
87 
88  // Get handle to plugin creation function.
89  void *Create = dlsym(Library, "talvosCreatePlugin");
90  if (!Create)
91  {
92  std::cerr << "Talvos: Failed to load plugin '" << LibPath << "' - "
93  << dlerror() << std::endl;
94  continue;
95  }
96 #endif
97 
98  // Create plugin and add to list.
99  Plugin *P = ((CreatePluginFunc)Create)(this);
100  Plugins.push_back({Library, P});
101  }
102  }
103 
105 
106  NumErrors = 0;
107  MaxErrors = getEnvUInt("TALVOS_MAX_ERRORS", 100);
108 }
109 
111 {
112  // Destroy plugins and unload their dynamic libraries.
113  for (auto P : Plugins)
114  {
115 #if defined(_WIN32) && !defined(__MINGW32__)
116  void *Destroy = GetProcAddress((HMODULE)P.first, "talvosDestroyPlugin");
117  if (Destroy)
118  ((DestroyPluginFunc)Destroy)(P.second);
119  FreeLibrary((HMODULE)P.first);
120 #else
121  void *Destroy = dlsym(P.first, "talvosDestroyPlugin");
122  if (Destroy)
123  ((DestroyPluginFunc)Destroy)(P.second);
124  dlclose(P.first);
125 #endif
126  }
127 
128  delete Executor;
129  delete GlobalMemory;
130 }
131 
133 {
134  for (auto P : Plugins)
135  if (!P.second->isThreadSafe())
136  return false;
137  return true;
138 }
139 
141 {
142  std::unique_lock<std::mutex> Lock(FenceMutex);
143  FenceSignaled.notify_all();
144 }
145 
146 void Device::reportError(const std::string &Error, bool Fatal)
147 {
148  // Increment error count, exit early if we have hit the limit.
149  size_t ErrorIdx = NumErrors++;
150  if (ErrorIdx >= MaxErrors)
151  return;
152 
153  // Guard output to avoid mangling error messages from multiple threads.
154  static std::mutex ErrorMutex;
155  std::lock_guard<std::mutex> Lock(ErrorMutex);
156 
157  std::cerr << std::endl;
158  std::cerr << Error << std::endl;
159 
160  if (Executor->isWorkerThread())
161  {
162  // Show current entry point.
163  const PipelineStage &Stage = Executor->getCurrentStage();
164  const EntryPoint *EP = Stage.getEntryPoint();
165  std::cerr << " Entry point:";
166  std::cerr << " %" << EP->getId();
167  std::cerr << " " << EP->getName();
168  std::cerr << std::endl;
169 
170  // Show current invocation and group.
171  const Invocation *Inv = Executor->getCurrentInvocation();
172  const Workgroup *Group = Executor->getCurrentWorkgroup();
173  if (Inv)
174  {
175  std::cerr << " Invocation:";
176  std::cerr << " Global" << Inv->getGlobalId();
177  if (Group)
178  {
179  std::cerr << " Local" << Inv->getGlobalId() % Stage.getGroupSize();
180  std::cerr << " Group" << Group->getGroupId();
181  }
182  }
183  else if (Group)
184  {
185  std::cerr << " Group: " << Group->getGroupId();
186  }
187  else
188  {
189  std::cerr << " <entity unknown>";
190  }
191  std::cerr << std::endl;
192 
193  if (Inv)
194  {
195  // Show current instruction.
196  std::cerr << " ";
197  Inv->getCurrentInstruction()->print(std::cerr, false);
198  }
199  else
200  {
201  std::cerr << " <location unknown>";
202  }
203 
204  std::cerr << std::endl;
205  }
206  else
207  {
208  std::cerr << " <origin unknown>" << std::endl;
209  }
210 
211  std::cerr << std::endl;
212 
213  // Display warning if maximum number of errors reached.
214  if (ErrorIdx == MaxErrors - 1)
215  {
216  std::cerr << "WARNING: " << MaxErrors << " errors reported - "
217  << "suppressing further errors" << std::endl;
218  std::cerr << "(configure this limit with TALVOS_MAX_ERRORS)" << std::endl;
219  std::cerr << std::endl;
220  }
221 
223 
224  if (Fatal)
225  abort();
226 }
227 
228 #define REPORT(func, ...) \
229  for (auto &P : Plugins) \
230  { \
231  P.second->func(__VA_ARGS__); \
232  }
233 
234 void Device::reportAtomicAccess(const Memory *Mem, uint64_t Address,
235  uint64_t NumBytes, uint32_t Opcode,
236  uint32_t Scope, uint32_t Semantics)
237 {
238  const Invocation *Invoc = Executor->getCurrentInvocation();
239  assert(Invoc);
240  REPORT(atomicAccess, Mem, Address, NumBytes, Opcode, Scope, Semantics, Invoc);
241 }
242 
244 {
245  REPORT(commandBegin, Cmd);
246 }
247 
249 {
250  REPORT(commandComplete, Cmd);
251 }
252 
254  const Instruction *Inst)
255 {
256  REPORT(instructionExecuted, Invoc, Inst);
257 }
258 
260 {
261  REPORT(invocationBegin, Invoc);
262 }
263 
265 {
266  REPORT(invocationComplete, Invoc);
267 }
268 
269 void Device::reportMemoryLoad(const Memory *Mem, uint64_t Address,
270  uint64_t NumBytes)
271 {
272  if (Executor->isWorkerThread())
273  {
274  // TODO: Workgroup/subgroup level accesses?
275  // TODO: Workgroup/Invocation scope initialization is not covered.
276  if (auto *I = Executor->getCurrentInvocation())
277  REPORT(memoryLoad, Mem, Address, NumBytes, I);
278  }
279  else if (Mem->getScope() == MemoryScope::Device)
280  {
281  REPORT(hostMemoryLoad, Mem, Address, NumBytes);
282  }
283 }
284 
285 void Device::reportMemoryMap(const Memory *Memory, uint64_t Base,
286  uint64_t Offset, uint64_t NumBytes)
287 {
288  REPORT(memoryMap, Memory, Base, Offset, NumBytes);
289 }
290 
291 void Device::reportMemoryStore(const Memory *Mem, uint64_t Address,
292  uint64_t NumBytes, const uint8_t *Data)
293 {
294  if (Executor->isWorkerThread())
295  {
296  // TODO: Workgroup/subgroup level accesses?
297  // TODO: Workgroup/Invocation scope initialization is not covered.
298  if (auto *I = Executor->getCurrentInvocation())
299  REPORT(memoryStore, Mem, Address, NumBytes, Data, I);
300  }
301  else if (Mem->getScope() == MemoryScope::Device)
302  {
303  REPORT(hostMemoryStore, Mem, Address, NumBytes, Data);
304  }
305 }
306 
307 void Device::reportMemoryUnmap(const Memory *Memory, uint64_t Base)
308 {
309  REPORT(memoryUnmap, Memory, Base);
310 }
311 
313 {
314  REPORT(workgroupBegin, Group);
315 }
316 
318 {
319  REPORT(workgroupBarrier, Group);
320 }
321 
323 {
324  REPORT(workgroupComplete, Group);
325 }
326 
327 #undef REPORT
328 
329 bool Device::waitForFences(const std::vector<const bool *> &Fences,
330  bool WaitAll, uint64_t Timeout) const
331 {
332  // Loop until fences have signaled or the timeout is exceeded.
333  auto Start = std::chrono::system_clock::now();
334  while (true)
335  {
336  // Lock so that we do not miss a fence signal notification.
337  std::unique_lock<std::mutex> Lock(FenceMutex);
338 
339  // Check status of fences.
340  uint32_t SignalCount = 0;
341  for (const auto Fence : Fences)
342  {
343  if (*Fence)
344  SignalCount++;
345  }
346  if (WaitAll ? SignalCount == (uint32_t)(Fences.size()) : SignalCount > 0)
347  return true;
348 
349  // Check if timeout has been exceeded.
350  auto Duration = std::chrono::system_clock::now() - Start;
351  auto DurationNanoseconds =
352  std::chrono::duration_cast<std::chrono::nanoseconds>(Duration);
353  uint64_t ElapsedTime = DurationNanoseconds.count();
354  if (ElapsedTime >= Timeout)
355  return false;
356 
357  // Wait for another fence to signal.
358  FenceSignaled.wait_for(
359  Lock, std::chrono::nanoseconds((uint64_t)(Timeout - ElapsedTime)));
360  }
361 }
362 
363 } // namespace talvos
This file declares the Workgroup class.
This file declares the ComputePipeline class.
void reportError(const std::string &Error, bool Fatal=false)
Report an error that has occurred during emulation.
Definition: Device.cpp:146
unsigned long getEnvUInt(const char *Name, unsigned Default)
Returns the integer value for the environment variable Name, or Default if it is not set...
Definition: Utils.cpp:33
This file declares the Device class.
Dim3 getGroupId() const
Returns the group ID of this workgroup.
Definition: Workgroup.h:52
Only allow Device objects to create PipelineExecutor instances.
bool isWorkerThread() const
Returns true if the calling thread is a PipelineExecutor worker thread.
Base class for Talvos plugins.
Definition: Plugin.h:26
void notifyFenceSignaled()
Notify the device that a fence was signaled.
Definition: Device.cpp:140
This file declares the Module class.
std::vector< std::pair< void *, Plugin * > > Plugins
List of plugins that are currently loaded.
Definition: Device.h:92
uint32_t getId() const
Returns the SPIR-V result ID of this entry point.
Definition: EntryPoint.h:41
This file declares the EntryPoint class.
const Invocation * getCurrentInvocation() const
Returns the current invocation being executed.
void reportMemoryMap(const Memory *Mem, uint64_t Base, uint64_t Offset, uint64_t NumBytes)
Definition: Device.cpp:285
void signalError()
Signal that an error has occurred, breaking the interactive debugger.
Plugin *(* CreatePluginFunc)(const Device *)
Definition: Device.cpp:42
const EntryPoint * getEntryPoint() const
Return the entry point this pipeline stage will invoke.
Definition: PipelineStage.h:51
void reportInvocationComplete(const Invocation *Invoc)
Definition: Device.cpp:264
void reportWorkgroupComplete(const Workgroup *Group)
Definition: Device.cpp:322
const PipelineStage & getCurrentStage() const
Returns the pipeline stage that is currently being executed.
const Workgroup * getCurrentWorkgroup() const
Returns the current workgroup being executed.
std::condition_variable FenceSignaled
Condition variable to notify threads waiting on fence signals.
Definition: Device.h:104
This class is a base class for all commands.
Definition: Commands.h:31
This class represents a single execution of a SPIR-V entry point.
Definition: Invocation.h:33
This file declares the Plugin class.
PipelineExecutor * Executor
The pipeline executor instance.
Definition: Device.h:95
This file declares miscellaneous utilities used internally by libtalvos.
Memory * GlobalMemory
The global memory of this device.
Definition: Device.h:89
This file declares the Instruction class.
size_t MaxErrors
The maximum number of errors to report.
Definition: Device.h:98
const std::string & getName() const
Returns the name of this entry point.
Definition: EntryPoint.h:44
void reportCommandBegin(const Command *Cmd)
Definition: Device.cpp:243
This file declares the PipelineExecutor class.
This class represents an address space in the virtual device.
Definition: Memory.h:37
void print(std::ostream &O, bool Align=true) const
Print a human-readable form of this instruction to O.
Definition: Instruction.cpp:43
MemoryScope getScope() const
Get the scope of this memory instance.
Definition: Memory.h:74
This file declares the PipelineStage class.
An internal class that handles pipeline execution, including the interactive debugger.
bool waitForFences(const std::vector< const bool * > &Fences, bool WaitAll, uint64_t Timeout) const
Wait for fences to signal.
Definition: Device.cpp:329
void reportWorkgroupBarrier(const Workgroup *Group)
Definition: Device.cpp:317
std::mutex FenceMutex
A mutex for synchronizing threads waiting on fence signals.
Definition: Device.h:101
A Device instance encapsulates properties and state for the virtual device.
Definition: Device.h:29
This class represents a workgroup executing a compute command.
Definition: Workgroup.h:27
This class encapsulates information about a pipeline stage.
Definition: PipelineStage.h:30
void reportInvocationBegin(const Invocation *Invoc)
Definition: Device.cpp:259
This file declares the Memory class.
#define REPORT(func,...)
Definition: Device.cpp:228
void reportMemoryStore(const Memory *Mem, uint64_t Address, uint64_t NumBytes, const uint8_t *Data)
Definition: Device.cpp:291
const Instruction * getCurrentInstruction() const
Returns the instruction that this invocation is executing.
Definition: Invocation.h:74
Dim3 getGroupSize() const
Return the workgroup size.
Definition: PipelineStage.h:54
void reportAtomicAccess(const Memory *Mem, uint64_t Address, uint64_t NumBytes, uint32_t Opcode, uint32_t Scope, uint32_t Semantics)
Definition: Device.cpp:234
This file declares the Command base class and its subclasses.
bool isThreadSafe() const
Returns true if all of the loaded plugins are thread-safe.
Definition: Device.cpp:132
This file declares the Function class.
This file declares the Invocation class.
Dim3 getGlobalId() const
Returns the global invocation ID.
Definition: Invocation.h:80
void reportCommandComplete(const Command *Cmd)
Definition: Device.cpp:248
void reportMemoryLoad(const Memory *Mem, uint64_t Address, uint64_t NumBytes)
Definition: Device.cpp:269
This class represents a SPIR-V instruction.
Definition: Instruction.h:27
void reportWorkgroupBegin(const Workgroup *Group)
Definition: Device.cpp:312
void reportMemoryUnmap(const Memory *Mem, uint64_t Base)
Definition: Device.cpp:307
void(* DestroyPluginFunc)(Plugin *)
Definition: Device.cpp:43
This class represents a shader entry point.
Definition: EntryPoint.h:25
void reportInstructionExecuted(const Invocation *Invoc, const Instruction *Inst)
Definition: Device.cpp:253