xref: /libCEED/backends/sycl/online_compiler.sycl.cpp (revision bd882c8a454763a096666645dc9a6229d5263694)
1*bd882c8aSJames Wright //==----------- online_compiler.cpp ----------------------------------------==//
2*bd882c8aSJames Wright //
3*bd882c8aSJames Wright // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*bd882c8aSJames Wright // See https://llvm.org/LICENSE.txt for license information.
5*bd882c8aSJames Wright // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*bd882c8aSJames Wright //
7*bd882c8aSJames Wright //===----------------------------------------------------------------------===//
8*bd882c8aSJames Wright #include <cstring>
9*bd882c8aSJames Wright #include <string>
10*bd882c8aSJames Wright #include <dlfcn.h>
11*bd882c8aSJames Wright 
12*bd882c8aSJames Wright #include <sycl/sycl.hpp>
13*bd882c8aSJames Wright #include "ocloc_api.h"
14*bd882c8aSJames Wright #include "online_compiler.hpp"
15*bd882c8aSJames Wright 
16*bd882c8aSJames Wright namespace sycl {
17*bd882c8aSJames Wright namespace ext::libceed {
18*bd882c8aSJames Wright 
loadOsLibrary(const std::string & PluginPath)19*bd882c8aSJames Wright void *loadOsLibrary(const std::string &PluginPath) {
20*bd882c8aSJames Wright   // TODO: Check if the option RTLD_NOW is correct. Explore using
21*bd882c8aSJames Wright   // RTLD_DEEPBIND option when there are multiple plugins.
22*bd882c8aSJames Wright   void *so = dlopen(PluginPath.c_str(), RTLD_NOW);
23*bd882c8aSJames Wright   if (!so) {
24*bd882c8aSJames Wright     char *Error = dlerror();
25*bd882c8aSJames Wright     std::cerr << "dlopen(" << PluginPath << ") failed with <" << (Error ? Error : "unknown error") << ">" << std::endl;
26*bd882c8aSJames Wright   }
27*bd882c8aSJames Wright   return so;
28*bd882c8aSJames Wright }
29*bd882c8aSJames Wright 
30*bd882c8aSJames Wright // int unloadOsPluginLibrary(void *Library) {
31*bd882c8aSJames Wright //   // The mock plugin does not have an associated library, so we allow nullptr
32*bd882c8aSJames Wright //   // here to avoid it trying to free a non-existent library.
33*bd882c8aSJames Wright //   if (!Library)
34*bd882c8aSJames Wright //     return 0;
35*bd882c8aSJames Wright //   return dlclose(Library);
36*bd882c8aSJames Wright // }
37*bd882c8aSJames Wright 
getOsLibraryFuncAddress(void * Library,const std::string & FunctionName)38*bd882c8aSJames Wright void *getOsLibraryFuncAddress(void *Library, const std::string &FunctionName) { return dlsym(Library, FunctionName.c_str()); }
39*bd882c8aSJames Wright 
prepareOclocArgs(sycl::info::device_type DeviceType,device_arch DeviceArch,bool Is64Bit,const std::string & DeviceStepping,const std::string & UserArgs)40*bd882c8aSJames Wright static std::vector<const char *> prepareOclocArgs(sycl::info::device_type DeviceType, device_arch DeviceArch, bool Is64Bit,
41*bd882c8aSJames Wright                                                   const std::string &DeviceStepping, const std::string &UserArgs) {
42*bd882c8aSJames Wright   std::vector<const char *> Args = {"ocloc", "-q", "-spv_only", "-device"};
43*bd882c8aSJames Wright 
44*bd882c8aSJames Wright   if (DeviceType == sycl::info::device_type::gpu) {
45*bd882c8aSJames Wright     switch (DeviceArch) {
46*bd882c8aSJames Wright       case device_arch::gpu_gen9:
47*bd882c8aSJames Wright         Args.push_back("skl");
48*bd882c8aSJames Wright         break;
49*bd882c8aSJames Wright 
50*bd882c8aSJames Wright       case device_arch::gpu_gen9_5:
51*bd882c8aSJames Wright         Args.push_back("cfl");
52*bd882c8aSJames Wright         break;
53*bd882c8aSJames Wright 
54*bd882c8aSJames Wright       case device_arch::gpu_gen11:
55*bd882c8aSJames Wright         Args.push_back("icllp");
56*bd882c8aSJames Wright         break;
57*bd882c8aSJames Wright 
58*bd882c8aSJames Wright       case device_arch::gpu_gen12:
59*bd882c8aSJames Wright         Args.push_back("tgllp");
60*bd882c8aSJames Wright         break;
61*bd882c8aSJames Wright 
62*bd882c8aSJames Wright       default:
63*bd882c8aSJames Wright         Args.push_back("pvc");
64*bd882c8aSJames Wright     }
65*bd882c8aSJames Wright   } else {
66*bd882c8aSJames Wright     // TODO: change that to generic device when ocloc adds support for it.
67*bd882c8aSJames Wright     // For now "tgllp" is used as the option supported on all known GPU RT.
68*bd882c8aSJames Wright     Args.push_back("pvc");
69*bd882c8aSJames Wright   }
70*bd882c8aSJames Wright 
71*bd882c8aSJames Wright   if (DeviceStepping != "") {
72*bd882c8aSJames Wright     Args.push_back("-revision_id");
73*bd882c8aSJames Wright     Args.push_back(DeviceStepping.c_str());
74*bd882c8aSJames Wright   }
75*bd882c8aSJames Wright 
76*bd882c8aSJames Wright   Args.push_back(Is64Bit ? "-64" : "-32");
77*bd882c8aSJames Wright 
78*bd882c8aSJames Wright   if (UserArgs != "") {
79*bd882c8aSJames Wright     Args.push_back("-options");
80*bd882c8aSJames Wright     Args.push_back(UserArgs.c_str());
81*bd882c8aSJames Wright   }
82*bd882c8aSJames Wright 
83*bd882c8aSJames Wright   return Args;
84*bd882c8aSJames Wright }
85*bd882c8aSJames Wright 
86*bd882c8aSJames Wright /// Compiles the given source \p Source to SPIR-V IL and returns IL as a vector
87*bd882c8aSJames Wright /// of bytes.
88*bd882c8aSJames Wright /// @param Source - Either OpenCL or CM source code.
89*bd882c8aSJames Wright /// @param DeviceType - SYCL device type, e.g. cpu, gpu, accelerator, etc.
90*bd882c8aSJames Wright /// @param DeviceArch - More detailed info on the target device architecture.
91*bd882c8aSJames Wright /// @param Is64Bit - If set to true, specifies the 64-bit architecture.
92*bd882c8aSJames Wright ///                  Otherwise, 32-bit is assumed.
93*bd882c8aSJames Wright /// @param DeviceStepping - implementation specific target device stepping.
94*bd882c8aSJames Wright /// @param CompileToSPIRVHandle - Output parameter. It is set to the address
95*bd882c8aSJames Wright ///                               of the library function doing the compilation.
96*bd882c8aSJames Wright /// @param FreeSPIRVOutputsHandle - Output parameter. It is set to the address
97*bd882c8aSJames Wright ///                                 of the library function freeing memory
98*bd882c8aSJames Wright ///                                 allocated during the compilation.
99*bd882c8aSJames Wright /// @param UserArgs - User's options to ocloc compiler.
compileToSPIRV(const std::string & Source,sycl::info::device_type DeviceType,device_arch DeviceArch,bool Is64Bit,const std::string & DeviceStepping,void * & CompileToSPIRVHandle,void * & FreeSPIRVOutputsHandle,const std::vector<std::string> & UserArgs)100*bd882c8aSJames Wright static std::vector<byte> compileToSPIRV(const std::string &Source, sycl::info::device_type DeviceType, device_arch DeviceArch, bool Is64Bit,
101*bd882c8aSJames Wright                                         const std::string &DeviceStepping, void *&CompileToSPIRVHandle, void *&FreeSPIRVOutputsHandle,
102*bd882c8aSJames Wright                                         const std::vector<std::string> &UserArgs) {
103*bd882c8aSJames Wright   if (!CompileToSPIRVHandle) {
104*bd882c8aSJames Wright #ifdef __SYCL_RT_OS_WINDOWS
105*bd882c8aSJames Wright     static const std::string OclocLibraryName = "ocloc64.dll";
106*bd882c8aSJames Wright #else
107*bd882c8aSJames Wright     static const std::string OclocLibraryName = "libocloc.so";
108*bd882c8aSJames Wright #endif
109*bd882c8aSJames Wright     void *OclocLibrary = loadOsLibrary(OclocLibraryName);
110*bd882c8aSJames Wright     if (!OclocLibrary) throw online_compile_error("Cannot load ocloc library: " + OclocLibraryName);
111*bd882c8aSJames Wright     void *OclocVersionHandle = getOsLibraryFuncAddress(OclocLibrary, "oclocVersion");
112*bd882c8aSJames Wright     // The initial versions of ocloc library did not have the oclocVersion()
113*bd882c8aSJames Wright     // function. Those versions had the same API as the first version of ocloc
114*bd882c8aSJames Wright     // library having that oclocVersion() function.
115*bd882c8aSJames Wright     int LoadedVersion = ocloc_version_t::OCLOC_VERSION_1_0;
116*bd882c8aSJames Wright     if (OclocVersionHandle) {
117*bd882c8aSJames Wright       decltype(::oclocVersion) *OclocVersionFunc = reinterpret_cast<decltype(::oclocVersion) *>(OclocVersionHandle);
118*bd882c8aSJames Wright       LoadedVersion                              = OclocVersionFunc();
119*bd882c8aSJames Wright     }
120*bd882c8aSJames Wright     // The loaded library with version (A.B) is compatible with expected API/ABI
121*bd882c8aSJames Wright     // version (X.Y) used here if A == B and B >= Y.
122*bd882c8aSJames Wright     int LoadedVersionMajor  = LoadedVersion >> 16;
123*bd882c8aSJames Wright     int LoadedVersionMinor  = LoadedVersion & 0xffff;
124*bd882c8aSJames Wright     int CurrentVersionMajor = ocloc_version_t::OCLOC_VERSION_CURRENT >> 16;
125*bd882c8aSJames Wright     int CurrentVersionMinor = ocloc_version_t::OCLOC_VERSION_CURRENT & 0xffff;
126*bd882c8aSJames Wright     if (LoadedVersionMajor != CurrentVersionMajor || LoadedVersionMinor < CurrentVersionMinor)
127*bd882c8aSJames Wright       throw online_compile_error(std::string("Found incompatible version of ocloc library: (") + std::to_string(LoadedVersionMajor) + "." +
128*bd882c8aSJames Wright                                  std::to_string(LoadedVersionMinor) + "). The supported versions are (" + std::to_string(CurrentVersionMajor) +
129*bd882c8aSJames Wright                                  ".N), where (N >= " + std::to_string(CurrentVersionMinor) + ").");
130*bd882c8aSJames Wright 
131*bd882c8aSJames Wright     CompileToSPIRVHandle = getOsLibraryFuncAddress(OclocLibrary, "oclocInvoke");
132*bd882c8aSJames Wright     if (!CompileToSPIRVHandle) throw online_compile_error("Cannot load oclocInvoke() function");
133*bd882c8aSJames Wright     FreeSPIRVOutputsHandle = getOsLibraryFuncAddress(OclocLibrary, "oclocFreeOutput");
134*bd882c8aSJames Wright     if (!FreeSPIRVOutputsHandle) throw online_compile_error("Cannot load oclocFreeOutput() function");
135*bd882c8aSJames Wright   }
136*bd882c8aSJames Wright 
137*bd882c8aSJames Wright   std::string CombinedUserArgs;
138*bd882c8aSJames Wright   for (auto UserArg : UserArgs) {
139*bd882c8aSJames Wright     if (UserArg == "") continue;
140*bd882c8aSJames Wright     if (CombinedUserArgs != "") CombinedUserArgs = CombinedUserArgs + " " + UserArg;
141*bd882c8aSJames Wright     else CombinedUserArgs = UserArg;
142*bd882c8aSJames Wright   }
143*bd882c8aSJames Wright   std::vector<const char *> Args = prepareOclocArgs(DeviceType, DeviceArch, Is64Bit, DeviceStepping, CombinedUserArgs);
144*bd882c8aSJames Wright 
145*bd882c8aSJames Wright   uint32_t  NumOutputs    = 0;
146*bd882c8aSJames Wright   byte    **Outputs       = nullptr;
147*bd882c8aSJames Wright   uint64_t *OutputLengths = nullptr;
148*bd882c8aSJames Wright   char    **OutputNames   = nullptr;
149*bd882c8aSJames Wright 
150*bd882c8aSJames Wright   const byte    *Sources[]       = {reinterpret_cast<const byte *>(Source.c_str())};
151*bd882c8aSJames Wright   const char    *SourceName      = "main.cl";
152*bd882c8aSJames Wright   const uint64_t SourceLengths[] = {Source.length() + 1};
153*bd882c8aSJames Wright 
154*bd882c8aSJames Wright   Args.push_back("-file");
155*bd882c8aSJames Wright   Args.push_back(SourceName);
156*bd882c8aSJames Wright 
157*bd882c8aSJames Wright   decltype(::oclocInvoke) *OclocInvokeFunc = reinterpret_cast<decltype(::oclocInvoke) *>(CompileToSPIRVHandle);
158*bd882c8aSJames Wright   int CompileError = OclocInvokeFunc(Args.size(), Args.data(), 1, Sources, SourceLengths, &SourceName, 0, nullptr, nullptr, nullptr, &NumOutputs,
159*bd882c8aSJames Wright                                      &Outputs, &OutputLengths, &OutputNames);
160*bd882c8aSJames Wright 
161*bd882c8aSJames Wright   std::vector<byte> SpirV;
162*bd882c8aSJames Wright   std::string       CompileLog;
163*bd882c8aSJames Wright   for (uint32_t I = 0; I < NumOutputs; I++) {
164*bd882c8aSJames Wright     size_t NameLen = strlen(OutputNames[I]);
165*bd882c8aSJames Wright     if (NameLen >= 4 && strstr(OutputNames[I], ".spv") != nullptr && Outputs[I] != nullptr) {
166*bd882c8aSJames Wright       assert(SpirV.size() == 0 && "More than one SPIR-V output found.");
167*bd882c8aSJames Wright       SpirV = std::vector<byte>(Outputs[I], Outputs[I] + OutputLengths[I]);
168*bd882c8aSJames Wright     } else if (!strcmp(OutputNames[I], "stdout.log")) {
169*bd882c8aSJames Wright       CompileLog = std::string(reinterpret_cast<const char *>(Outputs[I]));
170*bd882c8aSJames Wright     }
171*bd882c8aSJames Wright   }
172*bd882c8aSJames Wright 
173*bd882c8aSJames Wright   // Try to free memory before reporting possible error.
174*bd882c8aSJames Wright   decltype(::oclocFreeOutput) *OclocFreeOutputFunc = reinterpret_cast<decltype(::oclocFreeOutput) *>(FreeSPIRVOutputsHandle);
175*bd882c8aSJames Wright   int                          MemFreeError        = OclocFreeOutputFunc(&NumOutputs, &Outputs, &OutputLengths, &OutputNames);
176*bd882c8aSJames Wright 
177*bd882c8aSJames Wright   if (CompileError) throw online_compile_error("ocloc reported compilation errors: {\n" + CompileLog + "\n}");
178*bd882c8aSJames Wright   if (SpirV.empty()) throw online_compile_error("Unexpected output: ocloc did not return SPIR-V");
179*bd882c8aSJames Wright   if (MemFreeError) throw online_compile_error("ocloc cannot safely free resources");
180*bd882c8aSJames Wright 
181*bd882c8aSJames Wright   return SpirV;
182*bd882c8aSJames Wright }
183*bd882c8aSJames Wright 
184*bd882c8aSJames Wright template <>
185*bd882c8aSJames Wright template <>
compile(const std::string & Source,const std::vector<std::string> & UserArgs)186*bd882c8aSJames Wright std::vector<byte> online_compiler<source_language::opencl_c>::compile(const std::string &Source, const std::vector<std::string> &UserArgs) {
187*bd882c8aSJames Wright   if (OutputFormatVersion != std::pair<int, int>{0, 0}) {
188*bd882c8aSJames Wright     std::string Version = std::to_string(OutputFormatVersion.first) + ", " + std::to_string(OutputFormatVersion.second);
189*bd882c8aSJames Wright     throw online_compile_error(std::string("The output format version (") + Version + ") is not supported yet");
190*bd882c8aSJames Wright   }
191*bd882c8aSJames Wright 
192*bd882c8aSJames Wright   return compileToSPIRV(Source, DeviceType, DeviceArch, Is64Bit, DeviceStepping, CompileToSPIRVHandle, FreeSPIRVOutputsHandle, UserArgs);
193*bd882c8aSJames Wright }
194*bd882c8aSJames Wright 
195*bd882c8aSJames Wright template <>
196*bd882c8aSJames Wright template <>
compile(const std::string & Source,const std::vector<std::string> & UserArgs)197*bd882c8aSJames Wright std::vector<byte> online_compiler<source_language::cm>::compile(const std::string &Source, const std::vector<std::string> &UserArgs) {
198*bd882c8aSJames Wright   if (OutputFormatVersion != std::pair<int, int>{0, 0}) {
199*bd882c8aSJames Wright     std::string Version = std::to_string(OutputFormatVersion.first) + ", " + std::to_string(OutputFormatVersion.second);
200*bd882c8aSJames Wright     throw online_compile_error(std::string("The output format version (") + Version + ") is not supported yet");
201*bd882c8aSJames Wright   }
202*bd882c8aSJames Wright 
203*bd882c8aSJames Wright   std::vector<std::string> CMUserArgs = UserArgs;
204*bd882c8aSJames Wright   CMUserArgs.push_back("-cmc");
205*bd882c8aSJames Wright   return compileToSPIRV(Source, DeviceType, DeviceArch, Is64Bit, DeviceStepping, CompileToSPIRVHandle, FreeSPIRVOutputsHandle, CMUserArgs);
206*bd882c8aSJames Wright }
207*bd882c8aSJames Wright 
208*bd882c8aSJames Wright }  // namespace ext::libceed
209*bd882c8aSJames Wright }  // namespace sycl
210