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<UrApiKind::urProgramGetInfo>(
135  NativePrg, UR_PROGRAM_INFO_NUM_DEVICES, sizeof(DeviceNum), &DeviceNum,
136  nullptr);
137 
138  std::vector<size_t> BinarySizes(DeviceNum);
139  Plugin->call<UrApiKind::urProgramGetInfo>(
140  NativePrg, UR_PROGRAM_INFO_BINARY_SIZES,
141  sizeof(size_t) * BinarySizes.size(), BinarySizes.data(), nullptr);
142 
143  std::vector<std::vector<char>> Result;
144  std::vector<char *> Pointers;
145  for (size_t I = 0; I < BinarySizes.size(); ++I) {
146  Result.emplace_back(BinarySizes[I]);
147  Pointers.push_back(Result[I].data());
148  }
149 
150  Plugin->call<UrApiKind::urProgramGetInfo>(NativePrg, UR_PROGRAM_INFO_BINARIES,
151  sizeof(char *) * Pointers.size(),
152  Pointers.data(), nullptr);
153  size_t i = 0;
154  std::string FileName;
155  do {
156  FileName = DirName + "/" + std::to_string(i++);
157  } while (OSUtil::isPathPresent(FileName + ".bin") ||
158  OSUtil::isPathPresent(FileName + ".lock"));
159 
160  try {
161  OSUtil::makeDir(DirName.c_str());
162  LockCacheItem Lock{FileName};
163  if (Lock.isOwned()) {
164  std::string FullFileName = FileName + ".bin";
165  writeBinaryDataToFile(FullFileName, Result);
166  trace("device binary has been cached: " + FullFileName);
167  writeSourceItem(FileName + ".src", Device, SortedImgs, SpecConsts,
168  BuildOptionsString);
169  } else {
170  PersistentDeviceCodeCache::trace("cache lock not owned " + FileName);
171  }
172  } catch (std::exception &e) {
174  std::string("exception encountered making persistent cache: ") +
175  e.what());
176  } catch (...) {
178  std::string("error outputting persistent cache: ") +
179  std::strerror(errno));
180  }
181 }
182 
183 /* Program binaries built for one or more devices are read from persistent
184  * cache and returned in form of vector of programs. Each binary program is
185  * stored in vector of chars.
186  */
187 std::vector<std::vector<char>> PersistentDeviceCodeCache::getItemFromDisc(
188  const device &Device, const std::vector<const RTDeviceBinaryImage *> &Imgs,
189  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
190 
191  if (!areImagesCacheable(Imgs))
192  return {};
193 
194  std::vector<const RTDeviceBinaryImage *> SortedImgs = getSortedImages(Imgs);
195  std::string Path =
196  getCacheItemPath(Device, SortedImgs, SpecConsts, BuildOptionsString);
197 
198  if (Path.empty() || !OSUtil::isPathPresent(Path))
199  return {};
200 
201  int i = 0;
202 
203  std::string FileName{Path + "/" + std::to_string(i)};
204  while (OSUtil::isPathPresent(FileName + ".bin") ||
205  OSUtil::isPathPresent(FileName + ".src")) {
206 
207  if (!LockCacheItem::isLocked(FileName) &&
208  isCacheItemSrcEqual(FileName + ".src", Device, SortedImgs, SpecConsts,
209  BuildOptionsString)) {
210  try {
211  std::string FullFileName = FileName + ".bin";
212  std::vector<std::vector<char>> res =
213  readBinaryDataFromFile(FullFileName);
214  trace("using cached device binary: " + FullFileName);
215  return res; // subject for NRVO
216  } catch (...) {
217  // If read was unsuccessfull try the next item
218  }
219  }
220  FileName = Path + "/" + std::to_string(++i);
221  }
222  return {};
223 }
224 
225 /* Returns string value which can be used to identify different device
226  */
227 std::string PersistentDeviceCodeCache::getDeviceIDString(const device &Device) {
228  return Device.get_platform().get_info<sycl::info::platform::name>() + "/" +
229  Device.get_info<sycl::info::device::name>() + "/" +
230  Device.get_info<sycl::info::device::version>() + "/" +
231  Device.get_info<sycl::info::device::driver_version>();
232 }
233 
234 /* Write built binary to persistent cache
235  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
236  * Return on first unsuccessfull file operation
237  */
238 void PersistentDeviceCodeCache::writeBinaryDataToFile(
239  const std::string &FileName, const std::vector<std::vector<char>> &Data) {
240  std::ofstream FileStream{FileName, std::ios::binary};
241 
242  size_t Size = Data.size();
243  FileStream.write((char *)&Size, sizeof(Size));
244 
245  for (size_t i = 0; i < Data.size(); ++i) {
246  Size = Data[i].size();
247  FileStream.write((char *)&Size, sizeof(Size));
248  FileStream.write(Data[i].data(), Size);
249  }
250  FileStream.close();
251  if (FileStream.fail())
252  trace("Failed to write binary file " + FileName);
253 }
254 
255 /* Read built binary to persistent cache
256  * Format: numImages, 1stImageSize, Image[, NthImageSize, NthImage...]
257  */
258 std::vector<std::vector<char>>
259 PersistentDeviceCodeCache::readBinaryDataFromFile(const std::string &FileName) {
260  std::ifstream FileStream{FileName, std::ios::binary};
261  size_t ImgNum = 0, ImgSize = 0;
262  FileStream.read((char *)&ImgNum, sizeof(ImgNum));
263 
264  std::vector<std::vector<char>> Res(ImgNum);
265  for (size_t i = 0; i < ImgNum; ++i) {
266  FileStream.read((char *)&ImgSize, sizeof(ImgSize));
267 
268  std::vector<char> ImgData(ImgSize);
269  FileStream.read(ImgData.data(), ImgSize);
270 
271  Res[i] = std::move(ImgData);
272  }
273  FileStream.close();
274 
275  if (FileStream.fail()) {
276  trace("Failed to read binary file from " + FileName);
277  return {};
278  }
279 
280  return Res;
281 }
282 
283 /* Writing cache item key sources to be used for reliable identification
284  * Format: Four pairs of [size, value] for device, build options, specialization
285  * constant values, device code SPIR-V images.
286  */
287 void PersistentDeviceCodeCache::writeSourceItem(
288  const std::string &FileName, const device &Device,
289  const std::vector<const RTDeviceBinaryImage *> &SortedImgs,
290  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
291  std::ofstream FileStream{FileName, std::ios::binary};
292 
293  std::string DeviceString{getDeviceIDString(Device)};
294  size_t Size = DeviceString.size();
295  FileStream.write((char *)&Size, sizeof(Size));
296  FileStream.write(DeviceString.data(), Size);
297 
298  Size = BuildOptionsString.size();
299  FileStream.write((char *)&Size, sizeof(Size));
300  FileStream.write(BuildOptionsString.data(), Size);
301 
302  Size = SpecConsts.size();
303  FileStream.write((char *)&Size, sizeof(Size));
304  FileStream.write((const char *)SpecConsts.data(), Size);
305 
306  Size = 0;
307  for (const RTDeviceBinaryImage *Img : SortedImgs)
308  Size += Img->getSize();
309  FileStream.write((char *)&Size, sizeof(Size));
310  for (const RTDeviceBinaryImage *Img : SortedImgs)
311  FileStream.write((const char *)Img->getRawData().BinaryStart,
312  Img->getSize());
313  FileStream.close();
314 
315  if (FileStream.fail()) {
316  trace("Failed to write source file to " + FileName);
317  }
318 }
319 
320 /* Check that cache item key sources are equal to the current program.
321  * If file read operations fail cache item is treated as not equal.
322  */
323 bool PersistentDeviceCodeCache::isCacheItemSrcEqual(
324  const std::string &FileName, const device &Device,
325  const std::vector<const RTDeviceBinaryImage *> &SortedImgs,
326  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
327  std::ifstream FileStream{FileName, std::ios::binary};
328 
329  std::string ImgsString;
330  for (const RTDeviceBinaryImage *Img : SortedImgs)
331  ImgsString.append((const char *)Img->getRawData().BinaryStart,
332  Img->getSize());
333  std::string SpecConstsString{(const char *)SpecConsts.data(),
334  SpecConsts.size()};
335 
336  size_t Size = 0;
337  FileStream.read((char *)&Size, sizeof(Size));
338  std::string res(Size, '\0');
339  FileStream.read(&res[0], Size);
340  if (getDeviceIDString(Device).compare(res))
341  return false;
342 
343  FileStream.read((char *)&Size, sizeof(Size));
344  res.resize(Size);
345  FileStream.read(&res[0], Size);
346  if (BuildOptionsString.compare(res))
347  return false;
348 
349  FileStream.read((char *)&Size, sizeof(Size));
350  res.resize(Size);
351  FileStream.read(&res[0], Size);
352  if (SpecConstsString.compare(res))
353  return false;
354 
355  FileStream.read((char *)&Size, sizeof(Size));
356  res.resize(Size);
357  FileStream.read(&res[0], Size);
358  if (ImgsString.compare(res))
359  return false;
360 
361  FileStream.close();
362 
363  if (FileStream.fail()) {
364  trace("Failed to read source file from " + FileName);
365  }
366 
367  return true;
368 }
369 
370 /* Returns directory name to store specific kernel images for specified
371  * device, build options and specialization constants values.
372  */
374  const device &Device, const std::vector<const RTDeviceBinaryImage *> &Imgs,
375  const SerializedObj &SpecConsts, const std::string &BuildOptionsString) {
376  std::string cache_root{getRootDir()};
377  if (cache_root.empty()) {
378  trace("Disable persistent cache due to unconfigured cache root.");
379  return {};
380  }
381 
382  std::string ImgsString;
383  for (const RTDeviceBinaryImage *Img : Imgs)
384  if (Img->getRawData().BinaryStart)
385  ImgsString.append((const char *)Img->getRawData().BinaryStart,
386  Img->getSize());
387 
388  std::string DeviceString{getDeviceIDString(Device)};
389  std::string SpecConstsString{(const char *)SpecConsts.data(),
390  SpecConsts.size()};
391  std::hash<std::string> StringHasher{};
392 
393  return cache_root + "/" + std::to_string(StringHasher(DeviceString)) + "/" +
394  std::to_string(StringHasher(ImgsString)) + "/" +
395  std::to_string(StringHasher(SpecConstsString)) + "/" +
396  std::to_string(StringHasher(BuildOptionsString));
397 }
398 
399 /* Returns true if persistent cache is enabled.
400  */
401 bool PersistentDeviceCodeCache::isEnabled() {
402  bool CacheIsEnabled = SYCLConfig<SYCL_CACHE_PERSISTENT>::get();
403  static bool FirstCheck = true;
404  if (FirstCheck) {
405  PersistentDeviceCodeCache::trace(CacheIsEnabled ? "enabled" : "disabled");
406  FirstCheck = false;
407  }
408  return CacheIsEnabled;
409 }
410 
411 /* Returns path for device code cache root directory
412  */
413 std::string PersistentDeviceCodeCache::getRootDir() {
415 }
416 
417 } // namespace detail
418 } // namespace _V1
419 } // 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