DPC++ Runtime
Runtime libraries for oneAPI DPC++
kernel_compiler_opencl.cpp
Go to the documentation of this file.
1 //==-- kernel_compiler_opencl.cpp OpenCL kernel compilation support -==//
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 <sycl/detail/pi.hpp> // getOsLibraryFuncAddress
10 #include <sycl/exception.hpp> // make_error_code
11 
13 
14 #include "../online_compiler/ocloc_api.h"
15 #include "../split_string.hpp"
16 
17 #include <cstring> // strlen
18 #include <numeric> // for std::accumulate
19 
20 namespace sycl {
21 inline namespace _V1 {
22 namespace ext::oneapi::experimental {
23 namespace detail {
24 
25 // ensures the OclocLibrary has the right version, etc.
27  void *OclocVersionHandle =
29  // The initial versions of ocloc library did not have the oclocVersion()
30  // function. Those versions had the same API as the first version of ocloc
31  // library having that oclocVersion() function.
32  int LoadedVersion = ocloc_version_t::OCLOC_VERSION_1_0;
33  if (OclocVersionHandle) {
34  decltype(::oclocVersion) *OclocVersionFunc =
35  reinterpret_cast<decltype(::oclocVersion) *>(OclocVersionHandle);
36  LoadedVersion = OclocVersionFunc();
37  }
38  // The loaded library with version (A.B) is compatible with expected API/ABI
39  // version (X.Y) used here if A == B and B >= Y.
40  int LoadedVersionMajor = LoadedVersion >> 16;
41  int LoadedVersionMinor = LoadedVersion & 0xffff;
42  int CurrentVersionMajor = ocloc_version_t::OCLOC_VERSION_CURRENT >> 16;
43  int CurrentVersionMinor = ocloc_version_t::OCLOC_VERSION_CURRENT & 0xffff;
44  if (LoadedVersionMajor != CurrentVersionMajor ||
45  LoadedVersionMinor < CurrentVersionMinor) {
46  throw sycl::exception(
48  std::string("Found incompatible version of ocloc library: (") +
49  std::to_string(LoadedVersionMajor) + "." +
50  std::to_string(LoadedVersionMinor) +
51  "). The supported versions are (" +
52  std::to_string(CurrentVersionMajor) +
53  ".N), where (N >= " + std::to_string(CurrentVersionMinor) + ").");
54  }
55 }
56 
57 static void *OclocLibrary = nullptr;
58 
59 // load the ocloc shared library, check it.
61 #ifdef __SYCL_RT_OS_WINDOWS
62  static const std::string OclocLibraryName = "ocloc64.dll";
63 #else
64  static const std::string OclocLibraryName = "libocloc.so";
65 #endif
66  void *tempPtr = OclocLibrary;
67  if (tempPtr == nullptr) {
68  tempPtr = sycl::detail::pi::loadOsLibrary(OclocLibraryName);
69 
70  if (tempPtr == nullptr)
72  "Unable to load ocloc library " + OclocLibraryName);
73 
74  checkOclocLibrary(tempPtr);
75 
76  OclocLibrary = tempPtr;
77  }
78 
79  return OclocLibrary;
80 }
81 
83  // Already loaded?
84  if (OclocLibrary != nullptr)
85  return true;
86 
87  try {
88  // loads and checks version
90  return true;
91  } catch (...) {
92  return false;
93  }
94 }
95 
96 using voidPtr = void *;
97 
98 void SetupLibrary(voidPtr &oclocInvokeHandle, voidPtr &oclocFreeOutputHandle,
99  std::error_code the_errc) {
100  if (!oclocInvokeHandle) {
101  if (OclocLibrary == nullptr)
103 
104  oclocInvokeHandle =
106  if (!oclocInvokeHandle)
107  throw sycl::exception(the_errc, "Cannot load oclocInvoke() function");
108 
109  oclocFreeOutputHandle = sycl::detail::pi::getOsLibraryFuncAddress(
110  OclocLibrary, "oclocFreeOutput");
111  if (!oclocFreeOutputHandle)
112  throw sycl::exception(the_errc, "Cannot load oclocFreeOutput() function");
113  }
114 }
115 
116 std::string IPVersionsToString(const std::vector<uint32_t> IPVersionVec) {
117  std::stringstream ss;
118  bool amFirst = true;
119  for (uint32_t ipVersion : IPVersionVec) {
120  // if any device is not intelGPU, bail.
121  if (ipVersion < 0x02000000)
122  return "";
123 
124  if (!amFirst)
125  ss << ",";
126  amFirst = false;
127  ss << ipVersion;
128  }
129  return ss.str();
130 }
131 
132 spirv_vec_t OpenCLC_to_SPIRV(const std::string &Source,
133  const std::vector<uint32_t> &IPVersionVec,
134  const std::vector<std::string> &UserArgs,
135  std::string *LogPtr) {
136  std::vector<std::string> CMUserArgs = UserArgs;
137  CMUserArgs.push_back("-cmc");
138 
139  // handles into ocloc shared lib
140  static void *oclocInvokeHandle = nullptr;
141  static void *oclocFreeOutputHandle = nullptr;
143 
144  SetupLibrary(oclocInvokeHandle, oclocFreeOutputHandle, build_errc);
145 
146  // assemble ocloc args
147  std::string CombinedUserArgs =
148  std::accumulate(UserArgs.begin(), UserArgs.end(), std::string(""),
149  [](const std::string &acc, const std::string &s) {
150  return acc + s + " ";
151  });
152 
153  std::vector<const char *> Args = {"ocloc", "-q", "-spv_only", "-options",
154  CombinedUserArgs.c_str()};
155 
156  uint32_t NumOutputs = 0;
157  uint8_t **Outputs = nullptr;
158  uint64_t *OutputLengths = nullptr;
159  char **OutputNames = nullptr;
160 
161  const uint8_t *Sources[] = {
162  reinterpret_cast<const uint8_t *>(Source.c_str())};
163  const char *SourceName = "main.cl";
164  const uint64_t SourceLengths[] = {Source.length() + 1};
165 
166  Args.push_back("-file");
167  Args.push_back(SourceName);
168 
169  // device
170  std::string IPVersionsStr = IPVersionsToString(IPVersionVec);
171  if (!IPVersionsStr.empty()) {
172  Args.push_back("-device");
173  Args.push_back(IPVersionsStr.c_str());
174  }
175 
176  // invoke
177  decltype(::oclocInvoke) *OclocInvokeFunc =
178  reinterpret_cast<decltype(::oclocInvoke) *>(oclocInvokeHandle);
179  int CompileError =
180  OclocInvokeFunc(Args.size(), Args.data(), 1, Sources, SourceLengths,
181  &SourceName, 0, nullptr, nullptr, nullptr, &NumOutputs,
182  &Outputs, &OutputLengths, &OutputNames);
183 
184  // gather the results ( the SpirV and the Log)
185  spirv_vec_t SpirV;
186  std::string CompileLog;
187  for (uint32_t i = 0; i < NumOutputs; i++) {
188  size_t NameLen = strlen(OutputNames[i]);
189  if (NameLen >= 4 && strstr(OutputNames[i], ".spv") != nullptr &&
190  Outputs[i] != nullptr) {
191  assert(SpirV.size() == 0 && "More than one SPIR-V output found.");
192  SpirV = spirv_vec_t(Outputs[i], Outputs[i] + OutputLengths[i]);
193  } else if (!strcmp(OutputNames[i], "stdout.log")) {
194  if (OutputLengths[i] > 0) {
195  const char *LogText = reinterpret_cast<const char *>(Outputs[i]);
196  CompileLog.append(LogText, OutputLengths[i]);
197  if (LogPtr != nullptr)
198  LogPtr->append(LogText, OutputLengths[i]);
199  }
200  }
201  }
202 
203  // Try to free memory before reporting possible error.
204  decltype(::oclocFreeOutput) *OclocFreeOutputFunc =
205  reinterpret_cast<decltype(::oclocFreeOutput) *>(oclocFreeOutputHandle);
206  int MemFreeError =
207  OclocFreeOutputFunc(&NumOutputs, &Outputs, &OutputLengths, &OutputNames);
208 
209  if (CompileError)
210  throw sycl::exception(build_errc, "ocloc reported compilation errors: {\n" +
211  CompileLog + "\n}");
212 
213  if (SpirV.empty())
214  throw sycl::exception(build_errc,
215  "Unexpected output: ocloc did not return SPIR-V");
216 
217  if (MemFreeError)
218  throw sycl::exception(build_errc, "ocloc cannot safely free resources");
219 
220  return SpirV;
221 }
222 
223 std::string InvokeOclocQuery(uint32_t IPVersion, const char *identifier) {
224 
225  std::string QueryLog = "";
226 
227  // handles into ocloc shared lib
228  static void *oclocInvokeHandle = nullptr;
229  static void *oclocFreeOutputHandle = nullptr;
231 
232  SetupLibrary(oclocInvokeHandle, oclocFreeOutputHandle, the_errc);
233 
234  uint32_t NumOutputs = 0;
235  uint8_t **Outputs = nullptr;
236  uint64_t *OutputLengths = nullptr;
237  char **OutputNames = nullptr;
238 
239  std::vector<const char *> Args = {"ocloc", "query"};
240  std::vector<uint32_t> IPVersionVec{IPVersion};
241  std::string IPVersionsStr = IPVersionsToString(IPVersionVec);
242  if (!IPVersionsStr.empty()) {
243  Args.push_back("-device");
244  Args.push_back(IPVersionsStr.c_str());
245  }
246  Args.push_back(identifier);
247 
248  decltype(::oclocInvoke) *OclocInvokeFunc =
249  reinterpret_cast<decltype(::oclocInvoke) *>(oclocInvokeHandle);
250 
251  int InvokeError = OclocInvokeFunc(
252  Args.size(), Args.data(), 0, nullptr, 0, nullptr, 0, nullptr, nullptr,
253  nullptr, &NumOutputs, &Outputs, &OutputLengths, &OutputNames);
254 
255  // Gather the results.
256  for (uint32_t i = 0; i < NumOutputs; i++) {
257  if (!strcmp(OutputNames[i], "stdout.log")) {
258  if (OutputLengths[i] > 0) {
259  const char *LogText = reinterpret_cast<const char *>(Outputs[i]);
260  QueryLog.append(LogText, OutputLengths[i]);
261  }
262  }
263  }
264 
265  // Try to free memory before reporting possible error.
266  decltype(::oclocFreeOutput) *OclocFreeOutputFunc =
267  reinterpret_cast<decltype(::oclocFreeOutput) *>(oclocFreeOutputHandle);
268  int MemFreeError =
269  OclocFreeOutputFunc(&NumOutputs, &Outputs, &OutputLengths, &OutputNames);
270 
271  if (InvokeError)
272  throw sycl::exception(the_errc,
273  "ocloc reported errors: {\n" + QueryLog + "\n}");
274 
275  if (MemFreeError)
276  throw sycl::exception(the_errc, "ocloc cannot safely free resources");
277 
278  return QueryLog;
279 }
280 
281 bool OpenCLC_Feature_Available(const std::string &Feature, uint32_t IPVersion) {
282  static std::string FeatureLog = "";
283  if (FeatureLog.empty()) {
284  try {
285  FeatureLog = InvokeOclocQuery(IPVersion, "CL_DEVICE_OPENCL_C_FEATURES");
286  } catch (sycl::exception &) {
287  return false;
288  }
289  }
290 
291  // Allright, we have FeatureLog, so let's find that feature!
292  return (FeatureLog.find(Feature) != std::string::npos);
293 }
294 
296  const ext::oneapi::experimental::cl_version &Version, uint32_t IPVersion) {
297  static std::string VersionLog = "";
298  if (VersionLog.empty()) {
299  try {
300  VersionLog =
301  InvokeOclocQuery(IPVersion, "CL_DEVICE_OPENCL_C_ALL_VERSIONS");
302  } catch (sycl::exception &) {
303  return false;
304  }
305  }
306 
307  // Have VersionLog, will search.
308  // "OpenCL C":1.0.0 "OpenCL C":1.1.0 "OpenCL C":1.2.0 "OpenCL C":3.0.0
309  std::stringstream ss;
310  ss << Version.major << "." << Version.minor << "." << Version.patch;
311  return VersionLog.find(ss.str()) != std::string::npos;
312 }
313 
315  const std::string &Name, ext::oneapi::experimental::cl_version *VersionPtr,
316  uint32_t IPVersion) {
318  static std::string ExtensionByVersionLog = "";
319  if (ExtensionByVersionLog.empty()) {
320  try {
321  ExtensionByVersionLog =
322  InvokeOclocQuery(IPVersion, "CL_DEVICE_EXTENSIONS_WITH_VERSION");
323  } catch (sycl::exception &) {
324  return false;
325  }
326  }
327 
328  // ExtensionByVersionLog is ready. Time to find Name, and update VersionPtr.
329  // cl_khr_byte_addressable_store:1.0.0 cl_khr_device_uuid:1.0.0 ...
330  size_t where = ExtensionByVersionLog.find(Name);
331  if (where == std::string::npos) {
332  return false;
333  } // not there
334 
335  size_t colon = ExtensionByVersionLog.find(':', where);
336  if (colon == std::string::npos) {
337  throw sycl::exception(
338  rt_errc,
339  "trouble parsing query returned from CL_DEVICE_EXTENSIONS_WITH_VERSION "
340  "- extension not followed by colon (:)");
341  }
342  colon++; // move it forward
343 
344  size_t space = ExtensionByVersionLog.find(' ', colon); // could be npos
345 
346  size_t count = (space == std::string::npos) ? space : (space - colon);
347 
348  std::string versionStr = ExtensionByVersionLog.substr(colon, count);
349  std::vector<std::string> versionVec =
350  sycl::detail::split_string(versionStr, '.');
351  if (versionVec.size() != 3) {
352  throw sycl::exception(
353  rt_errc,
354  "trouble parsing query returned from "
355  "CL_DEVICE_EXTENSIONS_WITH_VERSION - version string unexpected: " +
356  versionStr);
357  }
358 
359  VersionPtr->major = std::stoi(versionVec[0]);
360  VersionPtr->minor = std::stoi(versionVec[1]);
361  VersionPtr->patch = std::stoi(versionVec[2]);
362 
363  return true;
364 }
365 
366 std::string OpenCLC_Profile(uint32_t IPVersion) {
367  try {
368  std::string result = InvokeOclocQuery(IPVersion, "CL_DEVICE_PROFILE");
369  // NOTE: result has \n\n amended. Clean it up.
370  // TODO: remove this once the ocloc query is fixed.
371  result.erase(std::remove_if(result.begin(), result.end(),
372  [](char c) {
373  return !std::isprint(c) || std::isspace(c);
374  }),
375  result.end());
376 
377  return result;
378  } catch (sycl::exception &) {
379  return "";
380  }
381 }
382 
383 } // namespace detail
384 } // namespace ext::oneapi::experimental
385 } // namespace _V1
386 } // namespace sycl
void * loadOsLibrary(const std::string &Library)
Definition: posix_pi.cpp:20
void * getOsLibraryFuncAddress(void *Library, const std::string &FunctionName)
Definition: posix_pi.cpp:47
std::vector< std::string > split_string(std::string_view str, char delimeter)
bool OpenCLC_Feature_Available(const std::string &Feature, uint32_t IPVersion)
std::string InvokeOclocQuery(uint32_t IPVersion, const char *identifier)
bool OpenCLC_Supports_Extension(const std::string &Name, ext::oneapi::experimental::cl_version *VersionPtr, uint32_t IPVersion)
spirv_vec_t OpenCLC_to_SPIRV(const std::string &Source, const std::vector< uint32_t > &IPVersionVec, const std::vector< std::string > &UserArgs, std::string *LogPtr)
std::string IPVersionsToString(const std::vector< uint32_t > IPVersionVec)
bool OpenCLC_Supports_Version(const ext::oneapi::experimental::cl_version &Version, uint32_t IPVersion)
void SetupLibrary(voidPtr &oclocInvokeHandle, voidPtr &oclocFreeOutputHandle, std::error_code the_errc)
std::error_code make_error_code(sycl::errc E) noexcept
Constructs an error code using e and sycl_category()
Definition: exception.cpp:93
Definition: access.hpp:18
error_code
Definition: defs.hpp:59
SIGNATURE oclocVersion()
Returns the current version of ocloc.
SIGNATURE oclocInvoke(uint32_t NumArgs, const char *Argv[], uint32_t NumSources, const uint8_t **DataSources, const uint64_t *LenSources, const char **NameSources, uint32_t NumInputHeaders, const uint8_t **DataInputHeaders, const uint64_t *LenInputHeaders, const char **NameInputHeaders, uint32_t *NumOutputs, uint8_t ***DataOutputs, uint64_t **LenOutputs, char ***NameOutputs)
Invokes ocloc API using C interface.
@ OCLOC_VERSION_CURRENT
latest known version
Definition: ocloc_api.h:22
@ OCLOC_VERSION_1_0
version 1.0
Definition: ocloc_api.h:21
SIGNATURE oclocFreeOutput(uint32_t *NumOutputs, uint8_t ***DataOutputs, uint64_t **LenOutputs, char ***NameOutputs)
Frees results of oclocInvoke.
C++ wrapper of extern "C" PI interfaces.
where_expression< simd_mask< _Tp, _Abi >, simd< _Tp, _Abi > > where(const typename simd< _Tp, _Abi >::mask_type &, simd< _Tp, _Abi > &) noexcept