DPC++ Runtime
Runtime libraries for oneAPI DPC++
global_handler.cpp
Go to the documentation of this file.
1 //==--------- global_handler.cpp --- Global objects handler ----------------==//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #ifdef ENABLE_STACK_TRACE
10 #include "llvm/ADT/StringRef.h"
11 #include "llvm/Support/Signals.h"
12 #endif
13 
14 #include <detail/config.hpp>
16 #include <detail/platform_impl.hpp>
17 #include <detail/plugin.hpp>
20 #include <detail/thread_pool.hpp>
21 #include <detail/xpti_registry.hpp>
23 #include <sycl/detail/spinlock.hpp>
24 #include <sycl/detail/ur.hpp>
25 
26 #ifdef _WIN32
27 #include <windows.h>
28 #endif
29 
30 #include <vector>
31 
32 namespace sycl {
33 inline namespace _V1 {
34 namespace detail {
35 
36 using LockGuard = std::lock_guard<SpinLock>;
37 SpinLock GlobalHandler::MSyclGlobalHandlerProtector{};
38 
39 // forward decl
40 void shutdown_win(); // TODO: win variant will go away soon
41 void shutdown_early();
42 void shutdown_late();
43 
44 // Utility class to track references on object.
45 // Used for GlobalHandler now and created as thread_local object on the first
46 // Scheduler usage. Origin idea is to track usage of Scheduler from main and
47 // other used threads - they increment MCounter; and to use but not add extra
48 // reference by our thread_pool threads. For this control MIncrementCounter
49 // class member is used.
51 public:
52  ObjectUsageCounter(bool ModifyCounter) : MModifyCounter(ModifyCounter) {
53  if (MModifyCounter)
54  MCounter++;
55  }
57  try {
58  if (!MModifyCounter)
59  return;
60 
61  LockGuard Guard(GlobalHandler::MSyclGlobalHandlerProtector);
62  MCounter--;
63  GlobalHandler *RTGlobalObjHandler = GlobalHandler::getInstancePtr();
64  if (RTGlobalObjHandler) {
65  RTGlobalObjHandler->prepareSchedulerToRelease(!MCounter);
66  }
67  } catch (std::exception &e) {
68  __SYCL_REPORT_EXCEPTION_TO_STREAM("exception in ~ObjectUsageCounter", e);
69  }
70  }
71 
72 private:
73  static std::atomic_uint MCounter;
74  bool MModifyCounter;
75 };
76 std::atomic_uint ObjectUsageCounter::MCounter{0};
77 
78 GlobalHandler::GlobalHandler() = default;
79 GlobalHandler::~GlobalHandler() = default;
80 
82 #ifdef XPTI_ENABLE_INSTRUMENTATION
83  // Let subscribers know a new stream is being initialized
85  GVerStr);
86  xpti::payload_t SYCLPayload("SYCL Runtime Exceptions");
87  uint64_t SYCLInstanceNo;
88  GSYCLCallEvent = xptiMakeEvent("SYCL Try-catch Exceptions", &SYCLPayload,
89  xpti::trace_algorithm_event, xpti_at::active,
90  &SYCLInstanceNo);
91 #endif
92 }
93 
94 void GlobalHandler::TraceEventXPTI(const char *Message) {
95  if (!Message)
96  return;
97 #ifdef XPTI_ENABLE_INSTRUMENTATION
98  static std::once_flag InitXPTIFlag;
99  if (xptiTraceEnabled()) {
100  std::call_once(InitXPTIFlag, [&]() { InitXPTI(); });
101 
102  // We have to handle the cases where: (1) we may have just the code location
103  // set and not UID and (2) UID set
105  auto CodeLocation = Tls.query();
106 
107  // Creating a tracepoint will convert a CodeLocation to UID, if not set
108  xpti::framework::tracepoint_t TP(
109  CodeLocation.fileName(), CodeLocation.functionName(),
110  CodeLocation.lineNumber(), CodeLocation.columnNumber(), nullptr);
111 
112  // The call to notify will have the signature of:
113  // (1) the stream defined in .stream()
114  // (2) The trace type equal to what is set by .trace_type()
115  // (3) Parent event set to NULL
116  // (4) Current event set to one created from CodeLocation and UID
117  // (5) An instance ID that records the number of times this code location
118  // has been seen (6) The message generated by the exception handler
119  TP.stream(SYCL_STREAM_NAME)
120  .trace_type(xpti::trace_point_type_t::diagnostics)
121  .notify(static_cast<const void *>(Message));
122  }
123 
124 #endif
125 }
126 
127 GlobalHandler *&GlobalHandler::getInstancePtr() {
128  static GlobalHandler *RTGlobalObjHandler = new GlobalHandler();
129  return RTGlobalObjHandler;
130 }
131 
133  GlobalHandler *RTGlobalObjHandler = GlobalHandler::getInstancePtr();
134  assert(RTGlobalObjHandler && "Handler must not be deallocated earlier");
135  return *RTGlobalObjHandler;
136 }
137 
138 template <typename T, typename... Types>
139 T &GlobalHandler::getOrCreate(InstWithLock<T> &IWL, Types... Args) {
140  const LockGuard Lock{IWL.Lock};
141 
142  if (!IWL.Inst)
143  IWL.Inst = std::make_unique<T>(Args...);
144 
145  return *IWL.Inst;
146 }
147 
149  // The method is used in unit tests only. Do not protect with lock since
150  // releaseResources will cause dead lock due to host queue release
151  if (MScheduler.Inst)
153  MScheduler.Inst.reset(Scheduler);
154 }
155 
157 #ifdef ENABLE_STACK_TRACE
158  static std::once_flag PrintStackFlag;
159  std::call_once(PrintStackFlag, []() {
160  llvm::sys::PrintStackTraceOnErrorSignal(llvm::StringRef());
161  });
162 #endif
163 }
164 
166  getOrCreate(MScheduler);
168  // On Windows the registration of the signal handler before main function
169  // (e.g. from DLLMain or from constructors of program scope objects) doesn't
170  // work. So, registering signal handler here because:
171  // 1) getScheduler is likely to be called for any non-trivial application;
172  // 2) first call to getScheduler is likely to be done after main starts.
173  // The same is done in getPlugins.
175  return *MScheduler.Inst;
176 }
177 
178 bool GlobalHandler::isSchedulerAlive() const { return MScheduler.Inst.get(); }
179 
180 void GlobalHandler::registerSchedulerUsage(bool ModifyCounter) {
181  thread_local ObjectUsageCounter SchedulerCounter(ModifyCounter);
182 }
183 
185  return getOrCreate(MProgramManager);
186 }
187 
188 std::unordered_map<PlatformImplPtr, ContextImplPtr> &
190  return getOrCreate(MPlatformToDefaultContextCache);
191 }
192 
194  return getOrCreate(MPlatformToDefaultContextCacheMutex);
195 }
196 
197 Sync &GlobalHandler::getSync() { return getOrCreate(MSync); }
198 
199 std::vector<PlatformImplPtr> &GlobalHandler::getPlatformCache() {
200  return getOrCreate(MPlatformCache);
201 }
202 
204  return getOrCreate(MPlatformMapMutex);
205 }
206 
208  return getOrCreate(MFilterMutex);
209 }
210 
211 std::vector<PluginPtr> &GlobalHandler::getPlugins() {
213  return getOrCreate(MPlugins);
214 }
215 
217 GlobalHandler::getOneapiDeviceSelectorTargets(const std::string &InitValue) {
218  return getOrCreate(MOneapiDeviceSelectorTargets, InitValue);
219 }
220 
222  return getOrCreate(MXPTIRegistry);
223 }
224 
227  ThreadPool &TP = getOrCreate(MHostTaskThreadPool, Size);
228 
229  return TP;
230 }
231 
233  // Release shared-pointers to SYCL objects.
234  // Note that on Windows the destruction of the default context
235  // races with the detaching of the DLL object that calls urLoaderTearDown.
236 
237  MPlatformToDefaultContextCache.Inst.reset(nullptr);
238 }
239 
242  try {
243 #ifdef _WIN32
244  // on Windows we keep to the existing shutdown procedure
246 #else
247  shutdown_early();
248 #endif
249  } catch (std::exception &e) {
250  __SYCL_REPORT_EXCEPTION_TO_STREAM("exception in ~EarlyShutdownHandler",
251  e);
252  }
253  }
254 };
255 
257  static EarlyShutdownHandler handler{};
258 }
259 
260 bool GlobalHandler::isOkToDefer() const { return OkToDefer; }
261 
262 void GlobalHandler::endDeferredRelease() { OkToDefer = false; }
263 
264 // Note: Split from shutdown so it is available to the unittests for ensuring
265 // that the mock plugin is the lone plugin.
267  // Call to GlobalHandler::instance().getPlugins() initializes plugins. If
268  // user application has loaded SYCL runtime, and never called any APIs,
269  // there's no need to load and unload plugins.
270  if (MPlugins.Inst) {
271  for (const auto &Plugin : getPlugins()) {
272  Plugin->release();
273  }
274  }
275 
276  urLoaderTearDown();
277 
278  // Clear after unload to avoid uses after unload.
279  getPlugins().clear();
280 }
281 
283 #ifndef _WIN32
284  if (Blocking)
285  drainThreadPool();
286  if (MScheduler.Inst)
287  MScheduler.Inst->releaseResources(Blocking ? BlockingT::BLOCKING
289 #endif
290 }
291 
293  if (MHostTaskThreadPool.Inst)
294  MHostTaskThreadPool.Inst->drain();
295 }
296 
297 #ifdef _WIN32
298 // because of something not-yet-understood on Windows
299 // threads may be shutdown once the end of main() is reached
300 // making an orderly shutdown difficult. Fortunately, Windows
301 // itself is very aggressive about reclaiming memory. Thus,
302 // we focus solely on unloading the plugins, so as to not
303 // accidentally retain device handles. etc
304 void shutdown_win() {
305  GlobalHandler *&Handler = GlobalHandler::getInstancePtr();
306  Handler->unloadPlugins();
307 }
308 #else
310  const LockGuard Lock{GlobalHandler::MSyclGlobalHandlerProtector};
311  GlobalHandler *&Handler = GlobalHandler::getInstancePtr();
312  if (!Handler)
313  return;
314 
315  // Now that we are shutting down, we will no longer defer MemObj releases.
316  Handler->endDeferredRelease();
317 
318  // Ensure neither host task is working so that no default context is accessed
319  // upon its release
320  Handler->prepareSchedulerToRelease(true);
321 
322  if (Handler->MHostTaskThreadPool.Inst)
323  Handler->MHostTaskThreadPool.Inst->finishAndWait();
324 
325  // This releases OUR reference to the default context, but
326  // other may yet have refs
327  Handler->releaseDefaultContexts();
328 }
329 
331  const LockGuard Lock{GlobalHandler::MSyclGlobalHandlerProtector};
332  GlobalHandler *&Handler = GlobalHandler::getInstancePtr();
333  if (!Handler)
334  return;
335 
336  // First, release resources, that may access plugins.
337  Handler->MPlatformCache.Inst.reset(nullptr);
338  Handler->MScheduler.Inst.reset(nullptr);
339  Handler->MProgramManager.Inst.reset(nullptr);
340 
341  // Clear the plugins and reset the instance if it was there.
342  Handler->unloadPlugins();
343  if (Handler->MPlugins.Inst)
344  Handler->MPlugins.Inst.reset(nullptr);
345 
346  Handler->MXPTIRegistry.Inst.reset(nullptr);
347 
348  // Release the rest of global resources.
349  delete Handler;
350  Handler = nullptr;
351 }
352 #endif
353 
354 #ifdef _WIN32
355 extern "C" __SYCL_EXPORT BOOL WINAPI DllMain(HINSTANCE hinstDLL,
356  DWORD fdwReason,
357  LPVOID lpReserved) {
358  // TODO: Remove from public header files and implementation during the next
359  // ABI Breaking window.
360  if (std::getenv("SYCL_PI_TRACE")) {
361  std::cerr << "SYCL_PI_TRACE has been removed use SYCL_UR_TRACE instead\n";
362  std::exit(1);
363  }
364 
365  bool PrintUrTrace = sycl::detail::ur::trace();
366  // Perform actions based on the reason for calling.
367  switch (fdwReason) {
368  case DLL_PROCESS_DETACH:
369  if (PrintUrTrace)
370  std::cout << "---> DLL_PROCESS_DETACH syclx.dll\n" << std::endl;
371 
372 #ifdef XPTI_ENABLE_INSTRUMENTATION
373  if (xptiTraceEnabled())
374  return TRUE; // When doing xpti tracing, we can't safely call shutdown.
375  // TODO: figure out what XPTI is doing that prevents release.
376 #endif
377 
378  shutdown_win();
379  break;
380  case DLL_PROCESS_ATTACH:
381  if (PrintUrTrace)
382  std::cout << "---> DLL_PROCESS_ATTACH syclx.dll\n" << std::endl;
383  break;
384  case DLL_THREAD_ATTACH:
385  break;
386  case DLL_THREAD_DETACH:
387  break;
388  }
389  return TRUE; // Successful DLL_PROCESS_ATTACH.
390 }
391 #else
392 // Setting low priority on destructor ensures it runs after all other global
393 // destructors. Priorities 0-100 are reserved by the compiler. The priority
394 // value 110 allows SYCL users to run their destructors after runtime library
395 // deinitialization.
396 __attribute__((destructor(110))) static void syclUnload() { shutdown_late(); }
397 #endif
398 } // namespace detail
399 } // namespace _V1
400 } // namespace sycl
Wrapper class for global data structures with non-trivial destructors.
ods_target_list & getOneapiDeviceSelectorTargets(const std::string &InitValue)
std::vector< PlatformImplPtr > & getPlatformCache()
std::unordered_map< PlatformImplPtr, ContextImplPtr > & getPlatformToDefaultContextCache()
std::vector< PluginPtr > & getPlugins()
void prepareSchedulerToRelease(bool Blocking)
void TraceEventXPTI(const char *Message)
void attachScheduler(Scheduler *Scheduler)
void registerSchedulerUsage(bool ModifyCounter=true)
std::mutex & getPlatformToDefaultContextCacheMutex()
static GlobalHandler & instance()
static const char * get()
Definition: config.hpp:115
DPC++ graph scheduler class.
Definition: scheduler.hpp:366
SpinLock is a synchronization primitive, that uses atomic variable and causes thread trying acquire l...
Definition: spinlock.hpp:27
Groups and provides access to all the locks used the SYCL runtime.
Definition: util.hpp:25
void initializeStream(const std::string &StreamName, uint32_t MajVer, uint32_t MinVer, const std::string &VerStr)
Notifies XPTI subscribers about new stream.
Data type that manages the code_location information in TLS.
Definition: common.hpp:131
const detail::code_location & query()
Query the information in the TLS slot.
Definition: common.cpp:66
Command group handler class.
Definition: handler.hpp:467
#define __SYCL_REPORT_EXCEPTION_TO_STREAM(str, e)
Definition: common.hpp:367
__SYCL_EXTERN_STREAM_ATTRS ostream cout
Linked to standard output.
__SYCL_EXTERN_STREAM_ATTRS ostream cerr
Linked to standard error (unbuffered)
constexpr const char * SYCL_STREAM_NAME
__attribute__((destructor(110))) static void syclUnload()
std::lock_guard< SpinLock > LockGuard
static void enableOnCrashStackPrinting()
Definition: access.hpp:18
C++ utilities for Unified Runtime integration.