DPC++ Runtime
Runtime libraries for oneAPI DPC++
persistent_device_code_cache.cpp
Go to the documentation of this file.
1 //==---------- persistent_device_code_cache.cpp -----------------*- C++-*---==//
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 #include <detail/device_impl.hpp>
11 #include <detail/plugin.hpp>
13 
14 #include <cerrno>
15 #include <cstdio>
16 #include <fstream>
17 #include <optional>
18 
19 #if defined(__SYCL_RT_OS_POSIX_SUPPORT)
20 #include <unistd.h>
21 #else
22 #include <direct.h>
23 #include <io.h>
24 #endif
25 
26 namespace sycl {
27 inline namespace _V1 {
28 namespace detail {
29 
30 /* Lock file suffix */
31 const char LockCacheItem::LockSuffix[] = ".lock";
32 
33 LockCacheItem::LockCacheItem(const std::string &Path)
34  : FileName(Path + LockSuffix) {
35  int fd;
36 
37  /* If the lock fail is not created */
38  if ((fd = open(FileName.c_str(), O_CREAT | O_EXCL, S_IWRITE)) != -1) {
39  close(fd);
40  Owned = true;
41  } else {
42  PersistentDeviceCodeCache::trace("Failed to acquire lock file: " +
43  FileName + " " + std::strerror(errno));
44  PersistentDeviceCodeCache::trace("Failed to acquire lock file: " +
45  FileName + " " + std::strerror(errno));
46  }
47 }
48 
50  if (Owned && std::remove(FileName.c_str()))
51  PersistentDeviceCodeCache::trace("Failed to release lock file: " +
52  FileName);
53 }
54 
55 // Returns true if the specified format is either SPIRV or a native binary.
56 static bool
58  return Format == SYCL_DEVICE_BINARY_TYPE_SPIRV ||
60 }
61 
62 /* Returns true if specified images should be cached on disk. It checks if
63  * cache is enabled, images have supported format and match thresholds. */
64 bool PersistentDeviceCodeCache::areImagesCacheable(
65  const std::vector<const RTDeviceBinaryImage *> &Imgs) {
66  assert(!Imgs.empty());
67  auto Format = Imgs[0]->getFormat();
68  assert(std::all_of(Imgs.begin(), Imgs.end(),
69  [&Format](const RTDeviceBinaryImage *Img) {
70  return Img->getFormat() == Format;
71  }) &&
72  "All images are expected to have the same format");
73  // Cache should be enabled and image type is one of the supported formats.
74  if (!isEnabled() || !IsSupportedImageFormat(Format))
75  return false;
76 
77  // Disable cache for ITT-profiled images.
79  return false;
80  }
81 
82  // TODO: Move parsing logic and caching to specializations of SYCLConfig.
83  static auto MaxImgSize = getNumParam<SYCL_CACHE_MAX_DEVICE_IMAGE_SIZE>(
84  DEFAULT_MAX_DEVICE_IMAGE_SIZE);
85  static auto MinImgSize = getNumParam<SYCL_CACHE_MIN_DEVICE_IMAGE_SIZE>(
86  DEFAULT_MIN_DEVICE_IMAGE_SIZE);
87 
88  // Make sure that image size is between caching thresholds if they are set.
89  // Zero values for threshold is treated as disabled threshold.
90  size_t TotalSize = 0;
91  for (const RTDeviceBinaryImage *Img : Imgs)
92  TotalSize += Img->getSize();
93  if ((MaxImgSize && (TotalSize > MaxImgSize)) ||
94  (MinImgSize && (TotalSize < MinImgSize)))
95  return false;
96 
97  return true;
98 }
99 
100 static std::vector<const RTDeviceBinaryImage *>
101 getSortedImages(const std::vector<const RTDeviceBinaryImage *> &Imgs) {
102  std::vector<const RTDeviceBinaryImage *> SortedImgs = Imgs;
103  std::sort(SortedImgs.begin(), SortedImgs.end(),
104  [](const RTDeviceBinaryImage *A, const RTDeviceBinaryImage *B) {
105  // All entry names are unique among these images, so comparing the
106  // first ones is enough.
107  return std::strcmp(A->getRawData().EntriesBegin->name,
108  B->getRawData().EntriesBegin->name) < 0;
109  });
110  return SortedImgs;
111 }
112 
113 /* Stores built program in persistent cache
114  */
116  const device &Device, const std::vector<const RTDeviceBinaryImage *> &Imgs,
117  const SerializedObj &SpecConsts, const std::string &BuildOptionsString,
118  const ur_program_handle_t &NativePrg) {
119 
120  if (!areImagesCacheable(Imgs))
121  return;
122 
123  std::vector<const RTDeviceBinaryImage *> SortedImgs = getSortedImages(Imgs);
124  std::string DirName =
125  getCacheItemPath(Device, SortedImgs, SpecConsts, BuildOptionsString);
126 
127  if (DirName.empty())
128  return;
129 
130  auto Plugin = detail::getSyclObjImpl(Device)->getPlugin();
131 
132  unsigned int DeviceNum = 0;
133 
134  Plugin->call(urProgramGetInfo, NativePrg, UR_PROGRAM_INFO_NUM_DEVICES,
135  sizeof(DeviceNum), &DeviceNum, nullptr);
136 
137  std::vector<size_t> BinarySizes(DeviceNum);
138  Plugin->call(urProgramGetInfo, NativePrg, UR_PROGRAM_INFO_BINARY_SIZES,
139  sizeof(size_t) * BinarySizes.size(), BinarySizes.data(),
140  nullptr);
141 
142  std::vector<std::vector<char>> Result;
143  std::vector<char *> Pointers;
144  for (size_t I = 0; I < BinarySizes.size(); ++I) {
145  Result.emplace_back(BinarySizes[I]);
146  Pointers.push_back(Result[I].data());
147  }
148 
149  Plugin->call(urProgramGetInfo, NativePrg, UR_PROGRAM_INFO_BINARIES,
150  sizeof(char *) * Pointers.size(), Pointers.data(), nullptr);
151  size_t i = 0;
152  std::string FileName;
153  do {
154  FileName = DirName + "/" + std::to_string(i++);
155  } while (OSUtil::isPathPresent(FileName + ".bin") ||
156  OSUtil::isPathPresent(FileName + ".lock"));
157 
158  try {
159  OSUtil::makeDir(DirName.c_str());
160  LockCacheItem Lock{FileName};
161  if (Lock.isOwned()) {
162  std::string FullFileName = FileName + ".bin";
163  writeBinaryDataToFile(FullFileName, Result);
164  trace("device binary has been cached: " + FullFileName);
165  writeSourceItem(FileName + ".src", Device, SortedImgs, SpecConsts,
166  BuildOptionsString);
167  } else {
168  PersistentDeviceCodeCache::trace("cache lock not owned " + FileName);
169  }
170  } catch (std::exception &e) {
172  std::string("exception encountered making persistent cache: ") +
173  e.what());
174  } catch (...) {
176  std::string("error outputting persistent cache: ") +
177  std::strerror(errno));
178  }
179 }
180 
181 /* Program binaries built for one or more devices are read from persistent
182  * cache and returned in form of vector of programs. Each binary program is
183  * stored in vector of chars.
184  */
185 std::vector<std::vector<char>> PersistentDeviceCodeCache::getItemFromDisc(
186  const device &Device, const std::vector<const RTDeviceBinaryImage *> &Imgs,
187  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
188 
189  if (!areImagesCacheable(Imgs))
190  return {};
191 
192  std::vector<const RTDeviceBinaryImage *> SortedImgs = getSortedImages(Imgs);
193  std::string Path =
194  getCacheItemPath(Device, SortedImgs, SpecConsts, BuildOptionsString);
195 
196  if (Path.empty() || !OSUtil::isPathPresent(Path))
197  return {};
198 
199  int i = 0;
200 
201  std::string FileName{Path + "/" + std::to_string(i)};
202  while (OSUtil::isPathPresent(FileName + ".bin") ||
203  OSUtil::isPathPresent(FileName + ".src")) {
204 
205  if (!LockCacheItem::isLocked(FileName) &&
206  isCacheItemSrcEqual(FileName + ".src", Device, SortedImgs, SpecConsts,
207  BuildOptionsString)) {
208  try {
209  std::string FullFileName = FileName + ".bin";
210  std::vector<std::vector<char>> res =
211  readBinaryDataFromFile(FullFileName);
212  trace("using cached device binary: " + FullFileName);
213  return res; // subject for NRVO
214  } catch (...) {
215  // If read was unsuccessfull try the next item
216  }
217  }
218  FileName = Path + "/" + std::to_string(++i);
219  }
220  return {};
221 }
222 
223 /* Returns string value which can be used to identify different device
224  */
225 std::string PersistentDeviceCodeCache::getDeviceIDString(const device &Device) {
226  return Device.get_platform().get_info<sycl::info::platform::name>() + "/" +
227  Device.get_info<sycl::info::device::name>() + "/" +
228  Device.get_info<sycl::info::device::version>() + "/" +
229  Device.get_info<sycl::info::device::driver_version>();
230 }
231 
232 /* Write built binary to persistent cache
233  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
234  * Return on first unsuccessfull file operation
235  */
236 void PersistentDeviceCodeCache::writeBinaryDataToFile(
237  const std::string &FileName, const std::vector<std::vector<char>> &Data) {
238  std::ofstream FileStream{FileName, std::ios::binary};
239 
240  size_t Size = Data.size();
241  FileStream.write((char *)&Size, sizeof(Size));
242 
243  for (size_t i = 0; i < Data.size(); ++i) {
244  Size = Data[i].size();
245  FileStream.write((char *)&Size, sizeof(Size));
246  FileStream.write(Data[i].data(), Size);
247  }
248  FileStream.close();
249  if (FileStream.fail())
250  trace("Failed to write binary file " + FileName);
251 }
252 
253 /* Read built binary to persistent cache
254  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
255  */
256 std::vector<std::vector<char>>
257 PersistentDeviceCodeCache::readBinaryDataFromFile(const std::string &FileName) {
258  std::ifstream FileStream{FileName, std::ios::binary};
259  size_t ImgNum = 0, ImgSize = 0;
260  FileStream.read((char *)&ImgNum, sizeof(ImgNum));
261 
262  std::vector<std::vector<char>> Res(ImgNum);
263  for (size_t i = 0; i < ImgNum; ++i) {
264  FileStream.read((char *)&ImgSize, sizeof(ImgSize));
265 
266  std::vector<char> ImgData(ImgSize);
267  FileStream.read(ImgData.data(), ImgSize);
268 
269  Res[i] = std::move(ImgData);
270  }
271  FileStream.close();
272 
273  if (FileStream.fail()) {
274  trace("Failed to read binary file from " + FileName);
275  return {};
276  }
277 
278  return Res;
279 }
280 
281 /* Writing cache item key sources to be used for reliable identification
282  * Format: Four pairs of [size, value] for device, build options, specialization
283  * constant values, device code SPIR-V images.
284  */
285 void PersistentDeviceCodeCache::writeSourceItem(
286  const std::string &FileName, const device &Device,
287  const std::vector<const RTDeviceBinaryImage *> &SortedImgs,
288  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
289  std::ofstream FileStream{FileName, std::ios::binary};
290 
291  std::string DeviceString{getDeviceIDString(Device)};
292  size_t Size = DeviceString.size();
293  FileStream.write((char *)&Size, sizeof(Size));
294  FileStream.write(DeviceString.data(), Size);
295 
296  Size = BuildOptionsString.size();
297  FileStream.write((char *)&Size, sizeof(Size));
298  FileStream.write(BuildOptionsString.data(), Size);
299 
300  Size = SpecConsts.size();
301  FileStream.write((char *)&Size, sizeof(Size));
302  FileStream.write((const char *)SpecConsts.data(), Size);
303 
304  Size = 0;
305  for (const RTDeviceBinaryImage *Img : SortedImgs)
306  Size += Img->getSize();
307  FileStream.write((char *)&Size, sizeof(Size));
308  for (const RTDeviceBinaryImage *Img : SortedImgs)
309  FileStream.write((const char *)Img->getRawData().BinaryStart,
310  Img->getSize());
311  FileStream.close();
312 
313  if (FileStream.fail()) {
314  trace("Failed to write source file to " + FileName);
315  }
316 }
317 
318 /* Check that cache item key sources are equal to the current program.
319  * If file read operations fail cache item is treated as not equal.
320  */
321 bool PersistentDeviceCodeCache::isCacheItemSrcEqual(
322  const std::string &FileName, const device &Device,
323  const std::vector<const RTDeviceBinaryImage *> &SortedImgs,
324  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
325  std::ifstream FileStream{FileName, std::ios::binary};
326 
327  std::string ImgsString;
328  for (const RTDeviceBinaryImage *Img : SortedImgs)
329  ImgsString.append((const char *)Img->getRawData().BinaryStart,
330  Img->getSize());
331  std::string SpecConstsString{(const char *)SpecConsts.data(),
332  SpecConsts.size()};
333 
334  size_t Size = 0;
335  FileStream.read((char *)&Size, sizeof(Size));
336  std::string res(Size, '\0');
337  FileStream.read(&res[0], Size);
338  if (getDeviceIDString(Device).compare(res))
339  return false;
340 
341  FileStream.read((char *)&Size, sizeof(Size));
342  res.resize(Size);
343  FileStream.read(&res[0], Size);
344  if (BuildOptionsString.compare(res))
345  return false;
346 
347  FileStream.read((char *)&Size, sizeof(Size));
348  res.resize(Size);
349  FileStream.read(&res[0], Size);
350  if (SpecConstsString.compare(res))
351  return false;
352 
353  FileStream.read((char *)&Size, sizeof(Size));
354  res.resize(Size);
355  FileStream.read(&res[0], Size);
356  if (ImgsString.compare(res))
357  return false;
358 
359  FileStream.close();
360 
361  if (FileStream.fail()) {
362  trace("Failed to read source file from " + FileName);
363  }
364 
365  return true;
366 }
367 
368 /* Returns directory name to store specific kernel images for specified
369  * device, build options and specialization constants values.
370  */
372  const device &Device, const std::vector<const RTDeviceBinaryImage *> &Imgs,
373  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
374  std::string cache_root{getRootDir()};
375  if (cache_root.empty()) {
376  trace("Disable persistent cache due to unconfigured cache root.");
377  return {};
378  }
379 
380  std::string ImgsString;
381  for (const RTDeviceBinaryImage *Img : Imgs)
382  if (Img->getRawData().BinaryStart)
383  ImgsString.append((const char *)Img->getRawData().BinaryStart,
384  Img->getSize());
385 
386  std::string DeviceString{getDeviceIDString(Device)};
387  std::string SpecConstsString{(const char *)SpecConsts.data(),
388  SpecConsts.size()};
389  std::hash<std::string> StringHasher{};
390 
391  return cache_root + "/" + std::to_string(StringHasher(DeviceString)) + "/" +
392  std::to_string(StringHasher(ImgsString)) + "/" +
393  std::to_string(StringHasher(SpecConstsString)) + "/" +
394  std::to_string(StringHasher(BuildOptionsString));
395 }
396 
397 /* Returns true if persistent cache is enabled.
398  */
399 bool PersistentDeviceCodeCache::isEnabled() {
400  bool CacheIsEnabled = SYCLConfig<SYCL_CACHE_PERSISTENT>::get();
401  static bool FirstCheck = true;
402  if (FirstCheck) {
403  PersistentDeviceCodeCache::trace(CacheIsEnabled ? "enabled" : "disabled");
404  FirstCheck = false;
405  }
406  return CacheIsEnabled;
407 }
408 
409 /* Returns path for device code cache root directory
410  */
411 std::string PersistentDeviceCodeCache::getRootDir() {
413 }
414 
415 } // namespace detail
416 } // namespace _V1
417 } // namespace sycl
static bool isLocked(const std::string &Path)
static int makeDir(const char *Dir)
Make all directories on the path, throws on error.
Definition: os_util.cpp:242
static bool isPathPresent(const std::string &Path)
Checks if specified path is present.
Definition: os_util.hpp:72
static std::string getCacheItemPath(const device &Device, const std::vector< const RTDeviceBinaryImage * > &SortedImgs, const SerializedObj &SpecConsts, const std::string &BuildOptionsString)
static std::vector< std::vector< char > > getItemFromDisc(const device &Device, const std::vector< const RTDeviceBinaryImage * > &Imgs, const SerializedObj &SpecConsts, const std::string &BuildOptionsString)
static void putItemToDisc(const device &Device, const std::vector< const RTDeviceBinaryImage * > &Imgs, const SerializedObj &SpecConsts, const std::string &BuildOptionsString, const ur_program_handle_t &NativePrg)
static const char * get()
Definition: config.hpp:115
The SYCL device class encapsulates a single SYCL device on which kernels may be executed.
Definition: device.hpp:64
detail::is_device_info_desc< Param >::return_type get_info() const
Queries this SYCL device for information requested by the template parameter param.
Definition: device.hpp:215
platform get_platform() const
Get associated SYCL platform.
Definition: device.cpp:81
sycl_device_binary_type
Types of device binary.
Definition: compiler.hpp:114
@ SYCL_DEVICE_BINARY_TYPE_SPIRV
Definition: compiler.hpp:117
@ SYCL_DEVICE_BINARY_TYPE_NATIVE
Definition: compiler.hpp:116
decltype(Obj::impl) const & getSyclObjImpl(const Obj &SyclObject)
Definition: impl_utils.hpp:31
static std::vector< const RTDeviceBinaryImage * > getSortedImages(const std::vector< const RTDeviceBinaryImage * > &Imgs)
static bool IsSupportedImageFormat(ur::DeviceBinaryType Format)
std::vector< unsigned char > SerializedObj
Definition: util.hpp:69
Definition: access.hpp:18
std::enable_if_t< std::is_same_v< std::invoke_result_t< BinaryOperation, ValueT, ValueT >, bool >, bool > compare(const ValueT a, const ValueT b, const BinaryOperation binary_op)
Performs comparison.
Definition: math.hpp:463
bool all_of(const simd_mask< _Tp, _Abi > &) noexcept