1 /*========================== begin_copyright_notice ============================
2 
3 Copyright (C) 2017-2021 Intel Corporation
4 
5 SPDX-License-Identifier: MIT
6 
7 ============================= end_copyright_notice ===========================*/
8 
9 #include "ocl_igc_interface/impl/fcl_ocl_translation_ctx_impl.h"
10 #include "AdaptorCommon/customApi.hpp"
11 #include "common/StringMacros.hpp"
12 #include "common/VCPlatformSelector.hpp"
13 #include "common/debug/Dump.hpp"
14 #include "ocl_igc_interface/fcl_ocl_translation_ctx.h"
15 #include "ocl_igc_interface/impl/platform_impl.h"
16 #include "3d/common/iStdLib/utility.h"
17 #include "3d/common/iStdLib/File.h"
18 #include "OCLFE/igd_fcl_mcl/headers/clang_tb.h"
19 
20 #pragma warning(disable:4141)
21 #pragma warning(disable:4146)
22 #pragma warning(disable:4242)
23 #include <llvm/ADT/Triple.h>
24 #include <llvm/ADT/STLExtras.h>
25 #include <llvm/Support/CommandLine.h>
26 #include <llvm/Support/FileSystem.h>
27 #include <llvm/Support/Path.h>
28 #include <llvm/Support/Process.h>
29 #include <llvm/Support/Program.h>
30 #include <llvm/Support/DynamicLibrary.h>
31 #include <llvm/Support/StringSaver.h>
32 #include <llvm/Support/Host.h>
33 #pragma warning(default:4242)
34 #pragma warning(default:4146)
35 #pragma warning(default:4141)
36 
37 #include <algorithm>
38 #include <fstream>
39 #include <iomanip>
40 #include <iostream>
41 #include <string>
42 #include <vector>
43 #if defined(__ANDROID__)
44 #include <sstream>
45 #endif
46 
47 #if defined( _DEBUG ) || defined( _INTERNAL )
48 #include <numeric>
49 #include <array>
50 #endif // defined( _DEBUG ) || defined( _INTERNAL )
51 #if defined(IGC_VC_ENABLED)
52 #include "Frontend.h"
53 #if defined(_WIN32)
54 #include <Windows.h>
55 #include "inc/common/DriverStore.h"
56 #endif
57 #endif // defined(IGC_VC_ENABLED)
58 
59 #include "cif/macros/enable.h"
60 
61 #include <memory>
62 
63 namespace IGC {
64 
65 OclTranslationOutputBase *
TranslateImpl(CIF::Version_t outVersion,CIF::Builtins::BufferSimple * src,CIF::Builtins::BufferSimple * options,CIF::Builtins::BufferSimple * internalOptions,CIF::Builtins::BufferSimple * tracingOptions,uint32_t tracingOptionsCount)66 CIF_GET_INTERFACE_CLASS(FclOclTranslationCtx, 1)::TranslateImpl(
67         CIF::Version_t outVersion,
68         CIF::Builtins::BufferSimple *src,
69         CIF::Builtins::BufferSimple *options,
70         CIF::Builtins::BufferSimple *internalOptions,
71         CIF::Builtins::BufferSimple *tracingOptions,
72         uint32_t tracingOptionsCount) {
73 
74     return CIF_GET_PIMPL()->Translate(outVersion, src, options, internalOptions,
75                                       tracingOptions, tracingOptionsCount);
76 }
77 
GetFclOptions(CIF::Builtins::BufferSimple * opts)78 void CIF_GET_INTERFACE_CLASS(FclOclTranslationCtx, 2)::GetFclOptions(CIF::Builtins::BufferSimple* opts){
79    CIF_GET_PIMPL()->GetFclOptions(opts);
80 }
81 
GetFclInternalOptions(CIF::Builtins::BufferSimple * opts)82 void CIF_GET_INTERFACE_CLASS(FclOclTranslationCtx, 2)::GetFclInternalOptions(CIF::Builtins::BufferSimple* opts){
83    CIF_GET_PIMPL()->GetFclInternalOptions(opts);
84 }
85 }
86 
87 namespace IGC {
88 
readBinaryFile(const std::string & fileName)89 llvm::Optional<std::vector<char>> readBinaryFile(const std::string& fileName) {
90     std::ifstream file(fileName, std::ios_base::binary);
91     if (!file.good()) {
92         return llvm::Optional<std::vector<char>>::create(nullptr);
93     }
94     size_t length;
95     file.seekg(0, file.end);
96     length = static_cast<size_t>(file.tellg());
97     file.seekg(0, file.beg);
98     std::vector<char> binary(length);
99     file.read(binary.data(), length);
100     return binary;
101 }
102 
103 #if defined(IGC_VC_ENABLED)
104 
105 using InvocationInfo = IGC::AdaptorCM::Frontend::IDriverInvocation;
106 using PathT = llvm::SmallVector<char, 1024>;
107 
108 using CMStringVect = IGC::AdaptorCM::Frontend::StringVect_t;
makeVcOptPayload(uint64_t IR_size,const std::string & VCApiOpts,const CMStringVect & VCLLVMOptions,const std::string & TargetFeaturesStr)109 std::vector<char> makeVcOptPayload(uint64_t IR_size,
110                                    const std::string& VCApiOpts,
111                                    const CMStringVect& VCLLVMOptions,
112                                    const std::string& TargetFeaturesStr) {
113     std::string InternalVcOptions;
114     if (!VCLLVMOptions.empty()) {
115         InternalVcOptions +=
116             " -llvm-options='" + llvm::join(VCLLVMOptions, " ") + "'";
117     }
118     if (!TargetFeaturesStr.empty()) {
119         InternalVcOptions.append(" -target-features=").append(TargetFeaturesStr);
120     }
121     // Payload format:
122     // |-vc-payload|api opts|internal opts|i64(IR size)|i64(Payload size)|-vc-payload|
123     // NOTE: <api/internal opts> are c-strings.
124     //
125     // Should be in sync with:
126     //      Source/IGC/AdaptorOCL/dllInterfaceCompute.cpp
127     const std::string PayloadMarker = "-vc-payload";
128     std::vector<char> VcPayload;
129     VcPayload.insert(VcPayload.end(), PayloadMarker.begin(), PayloadMarker.end());
130     VcPayload.insert(VcPayload.end(), VCApiOpts.begin(), VCApiOpts.end());
131     VcPayload.push_back(0); // zero-terminate api options
132     VcPayload.insert(VcPayload.end(), InternalVcOptions.begin(), InternalVcOptions.end());
133     VcPayload.push_back(0); // zero-terminate internal options
134     uint64_t payloadSize = VcPayload.size() +
135                             /* as suffix */ PayloadMarker.size() +
136                             /* for sanity-checks */ + sizeof(IR_size) +
137                             /* include the size itself */ + sizeof(uint64_t);
138     VcPayload.insert(VcPayload.end(),
139                      reinterpret_cast<const char*>(&IR_size),
140                      reinterpret_cast<const char*>(&IR_size + 1));
141     VcPayload.insert(VcPayload.end(),
142                      reinterpret_cast<const char*>(&payloadSize),
143                      reinterpret_cast<const char*>(&payloadSize + 1));
144     VcPayload.insert(VcPayload.end(), PayloadMarker.begin(), PayloadMarker.end());
145     return VcPayload;
146 }
147 
finalizeFEOutput(const IGC::AdaptorCM::Frontend::IOutputArgs & FEOutput,const std::string & VCApiOptions,const CMStringVect & VCLLVMOptions,const std::string & TargetFeatures,OclTranslationOutputBase & Output)148 void finalizeFEOutput(const IGC::AdaptorCM::Frontend::IOutputArgs& FEOutput,
149                       const std::string& VCApiOptions,
150                       const CMStringVect& VCLLVMOptions,
151                       const std::string& TargetFeatures,
152                       OclTranslationOutputBase& Output)
153 {
154     auto& OutputInterface = *Output.GetImpl();
155     const auto& ErrLog = FEOutput.getLog();
156     if (FEOutput.getStatus() ==
157         Intel::CM::ClangFE::IOutputArgs::ErrT::COMPILE_PROGRAM_FAILURE)
158     {
159         if (ErrLog.empty())
160             OutputInterface.SetError(
161                 TranslationErrorType::Internal,
162                 "unknown error during cm source compilation");
163         else
164             OutputInterface.SetError(TranslationErrorType::UnhandledInput,
165                                      ErrLog.c_str());
166         return;
167     }
168 
169     if (!ErrLog.empty())
170         OutputInterface.AddWarning(ErrLog);
171 
172     const auto& IR = FEOutput.getIR();
173     // This is where the tricky part starts
174     // Right now we have no way to pass auxiliary options to vc-codegen backend
175     // So we introduce a temporary hack to incorporate the options
176     // into the underlying buffer which contains SPIRV IR
177     std::vector<char> Payload = makeVcOptPayload(IR.size(), VCApiOptions,
178                                                  VCLLVMOptions, TargetFeatures);
179 
180     std::vector<char> FinalOutput;
181     FinalOutput.reserve(IR.size() + Payload.size());
182     FinalOutput.insert(FinalOutput.end(), IR.begin(), IR.end());
183     FinalOutput.insert(FinalOutput.end(), Payload.begin(), Payload.end());
184 
185     if (!OutputInterface.SetSuccessfulAndCloneOutput(FinalOutput.data(),
186                                                      FinalOutput.size()))
187     {
188         OutputInterface.SetError(TranslationErrorType::Internal, "OOM (cm FE)");
189         return;
190     }
191 }
192 
193 static llvm::Optional<std::string> MakeTemporaryCMSource(
194     CIF::Builtins::BufferSimple* Src,
195     std::string tmpFilename,
196     OclTranslationOutputBase& outI);
197 
processCmSrcOptions(llvm::SmallVectorImpl<const char * > & userArgs,std::string optname,std::string & inputFile)198 static bool processCmSrcOptions(
199     llvm::SmallVectorImpl<const char*> &userArgs,
200     std::string optname,
201     std::string &inputFile) {
202 
203     auto toErase = std::find_if(userArgs.begin(), userArgs.end(),
204         [&optname](const auto& Item) { return std::strcmp(Item, optname.c_str()) == 0; });
205     if (toErase != userArgs.end()) {
206         auto itFilename = toErase + 1;
207         if (itFilename != userArgs.end() && strcmp(*itFilename, "--") != 0) {
208             inputFile = *itFilename;
209             userArgs.erase(toErase, toErase + 2);
210             return true;
211         }
212     }
213 
214     optname += "=";
215     toErase = std::find_if(userArgs.begin(), userArgs.end(),
216         [&optname](const auto& Item) {
217           llvm::StringRef S = Item;
218           return S.startswith(optname);
219         });
220     if (toErase != userArgs.end()) {
221         inputFile = *toErase;
222         inputFile = inputFile.substr(optname.length());
223         userArgs.erase(toErase);
224         return true;
225     }
226 
227     return false;
228 }
229 
230 static std::vector<const char*>
processFeOptions(llvm::sys::DynamicLibrary & LibInfo,CIF::Builtins::BufferSimple * Src,OclTranslationOutputBase & outI,CIF::Builtins::BufferSimple * options,llvm::StringSaver & stringSaver,const char * platform,unsigned stepping,bool & isMemFile)231     processFeOptions(llvm::sys::DynamicLibrary &LibInfo,
232                      CIF::Builtins::BufferSimple* Src,
233                      OclTranslationOutputBase& outI,
234                      CIF::Builtins::BufferSimple* options,
235                      llvm::StringSaver& stringSaver,
236                      const char *platform,
237                      unsigned stepping,
238                      bool &isMemFile) {
239 
240     llvm::SmallVector<const char*, 20> userArgs;
241 
242     std::string userArgsStr { options->GetMemory<char>(), options->GetSize<char>() };
243     if (llvm::Triple(llvm::sys::getProcessTriple()).isOSWindows())
244         llvm::cl::TokenizeWindowsCommandLine(userArgsStr, stringSaver, userArgs);
245     else
246         llvm::cl::TokenizeGNUCommandLine(userArgsStr, stringSaver, userArgs);
247 
248     auto toErase = std::remove_if(userArgs.begin(), userArgs.end(),
249         [](const auto& Item) { return std::strcmp(Item, "-cmc") == 0; });
250     userArgs.erase(toErase, userArgs.end());
251 
252     // this was old hack before FE can pass platform, now we prefer to use argument
253     // but if it is null, it may be still useful, so let it be for a while
254     auto cmfeDefaultArchOpt = llvm::sys::Process::GetEnv("IGC_CMFE_DEFAULT_ARCH");
255     const std::string& cmfeDefaultArch =
256         cmfeDefaultArchOpt ? cmfeDefaultArchOpt.getValue() : "SKL";
257 
258     std::string inputFile = "src.cm";
259     isMemFile = processCmSrcOptions(userArgs, "-cm-src", inputFile) ||
260                 processCmSrcOptions(userArgs, "-s", inputFile);
261     if (!isMemFile) {
262         auto OptSrc = MakeTemporaryCMSource(Src, inputFile, outI);
263         if (!OptSrc)
264             return {};
265         inputFile = OptSrc.getValue();
266     }
267 
268     std::vector<const char*> result = {
269         "-emit-spirv",
270         "-fcmocl",
271     };
272 
273     // Pass the runtime-specified architecture.
274     // User still can override it if -march is passed in user arguments
275     if (platform)
276       result.push_back(stringSaver.save("-march=" + std::string(platform)).data());
277     else
278       result.push_back(stringSaver.save("-march=" + cmfeDefaultArch).data());
279 
280     result.push_back(stringSaver.save(inputFile).data());
281     result.insert(result.end(), userArgs.begin(), userArgs.end());
282 
283     auto ExtraCMOpts = llvm::sys::Process::GetEnv("IGC_ExtraCMOptions");
284     if (ExtraCMOpts) {
285         llvm::SmallVector<const char *, 8> Argv;
286         llvm::cl::TokenizeGNUCommandLine(ExtraCMOpts.getValue(), stringSaver, Argv);
287         result.insert(result.end(), Argv.begin(), Argv.end());
288     }
289 
290     return result;
291 }
292 
293 // TODO: most probably we should remove this function once FrontendWrapper
294 // is capable to handle in-memory objects properly
MakeTemporaryCMSource(CIF::Builtins::BufferSimple * Src,std::string tmpFilename,OclTranslationOutputBase & outI)295 static llvm::Optional<std::string> MakeTemporaryCMSource(
296     CIF::Builtins::BufferSimple* Src,
297     std::string tmpFilename,
298     OclTranslationOutputBase& outI) {
299 
300     auto& outputInterface = *outI.GetImpl();
301 
302     assert(Src);
303 
304     PathT workDir;
305 
306     // TODO: resulting temporary file can collide with the existing one
307     auto Err = llvm::sys::fs::getPotentiallyUniqueTempFileName("tmp_cm_to_spv",
308                                                                "", workDir);
309     if (Err) {
310         outputInterface.SetError(TranslationErrorType::Internal,
311                                  "could not determine temporary directory name");
312         return {};
313     }
314     if (llvm::sys::fs::create_directories(workDir)) {
315         outputInterface.SetError(TranslationErrorType::Internal,
316                                  "could not create temporary directory");
317         return {};
318     }
319 
320     size_t sizeToDump = Src->GetSize<char>();
321     // TODO: consider removing this legacy code.
322     // It looks like that we are trying to handle some kind of weird user input,
323     // instead of reporting an error.
324     while( (sizeToDump > 0) && (Src->GetMemory<char>()[sizeToDump - 1] == 0)) {
325         --sizeToDump;
326     }
327     if(sizeToDump == 0){
328         outputInterface.SetError(TranslationErrorType::Internal,
329                                  "effective input is zero");
330         return {};
331     }
332 
333     // Dump Src
334     PathT srcPath{ workDir.begin(), workDir.end() };
335     llvm::sys::path::append(srcPath, tmpFilename);
336 
337     std::string strPath(srcPath.begin(), srcPath.end());
338     std::ofstream TmpSrc(strPath, std::ios::out);
339     TmpSrc.write(Src->GetMemory<char>(), sizeToDump);
340     TmpSrc.close();
341     if (!TmpSrc) {
342         outputInterface.SetError(TranslationErrorType::Internal,
343                                  "could not create temporary temporary file");
344         return {};
345     }
346     return strPath;
347 }
348 
349 // Retrieve directory where FEWrapper is located.
350 // Return driver store path on windows and empty path on linux.
getCMFEWrapperDir()351 static std::string getCMFEWrapperDir() {
352 #if defined(_WIN32)
353     // Expand libname to full driver path on windows.
354     char DriverPath[MAX_PATH] = {};
355     GetDependencyPath(DriverPath, "");
356     return DriverPath;
357 #else
358     return "";
359 #endif
360 }
361 
362 #endif // defined(IGC_VC_ENABLED)
363 
364 // This essentialy duplicates existing DumpShaderFile in dllInterfaceCompute
365 // but now I see no way to reuse it. To be converged later
DumpInputs(PLATFORM * Platform,const char * Selected,int Stepping,CIF::Builtins::BufferSimple * src,CIF::Builtins::BufferSimple * options,CIF::Builtins::BufferSimple * internalOptions,CIF::Builtins::BufferSimple * tracingOptions)366 static void DumpInputs(PLATFORM *Platform, const char *Selected,
367                        int Stepping, CIF::Builtins::BufferSimple* src,
368                        CIF::Builtins::BufferSimple* options,
369                        CIF::Builtins::BufferSimple* internalOptions,
370                        CIF::Builtins::BufferSimple* tracingOptions) {
371 #if defined( _DEBUG ) || defined( _INTERNAL )
372   // check for shader dumps enabled
373   if (!FCL_IGC_IS_FLAG_ENABLED(ShaderDumpEnable))
374     return;
375 
376   auto AsStringRef = [](CIF::Builtins::BufferSimple* Src) {
377      if (!Src)
378        return llvm::StringRef();
379      size_t Size = Src->GetSize<char>();
380      const char* Buff = Src->GetMemory<char>();
381      return llvm::StringRef(Buff, Size);
382   };
383   // dump if yes
384   std::ostringstream Os;
385   if (Platform) {
386     auto Core = Platform->eDisplayCoreFamily;
387     auto RenderCore = Platform->eRenderCoreFamily;
388     auto Product = Platform->eProductFamily;
389     auto RevId = Platform->usRevId;
390 
391     Os << "NEO passed: DisplayCore = " << Core
392        << ", RenderCore = " << RenderCore << ", Product = " << Product
393        << ", Revision = " << RevId << "\n";
394     Os << "IGC translated into: " << Selected << ", " << Stepping << "\n";
395   } else {
396     Os << "Nothing came from NEO\n";
397   }
398   auto PlatformInfo = Os.str();
399 
400   struct DumpDescriptor {
401     llvm::StringRef OutputName;
402     llvm::StringRef Contents;
403   };
404   std::array<DumpDescriptor, 5> Dumps = {{
405     { "platform", PlatformInfo.c_str() },
406     { "options", AsStringRef(options) },
407     { "internal_options", AsStringRef(internalOptions) },
408     { "tracing_options", AsStringRef(tracingOptions) },
409     { "source_code", AsStringRef(src) }
410   }};
411   using HashType = unsigned long long;
412   auto Hash = std::accumulate(Dumps.begin(), Dumps.end(), 0ull,
413       [](const auto& HashVal, const auto& DI) {
414           const auto *BuffStart = DI.Contents.data();
415           auto BuffSize = DI.Contents.size();
416           return HashVal ^ iSTD::HashFromBuffer(BuffStart, BuffSize);
417       });
418 
419   std::for_each(Dumps.begin(), Dumps.end(),
420       [&Hash](const auto &DI) {
421           // do factual dump, this part can be replaced to DumpShaderFile
422           const char *DstDir = FCL::GetShaderOutputFolder();
423           std::ostringstream FullPath(DstDir, std::ostringstream::ate);
424           FullPath << "VC_fe_"  << std::hex << std::setfill('0')
425               << std::setw(sizeof(Hash) * CHAR_BIT / 4) << Hash << std::dec
426               << std::setfill(' ') << "_" << DI.OutputName.str() << ".txt";
427           // NOTE: for now, we expect only text data here
428           std::ofstream OutF(FullPath.str(), std::ofstream::out);
429           // if we can create dump file at all...
430           if (OutF)
431             OutF.write(DI.Contents.data(), DI.Contents.size());
432       });
433 
434 #endif // defined( _DEBUG ) || defined( _INTERNAL )
435 }
436 
CIF_PIMPL(FclOclTranslationCtx)437 OclTranslationOutputBase* CIF_PIMPL(FclOclTranslationCtx)::TranslateCM(
438     CIF::Version_t outVersion,
439     CIF::Builtins::BufferSimple* src,
440     CIF::Builtins::BufferSimple* options,
441     CIF::Builtins::BufferSimple* internalOptions,
442     CIF::Builtins::BufferSimple* tracingOptions,
443     uint32_t tracingOptionsCount) {
444 
445     // Output
446     auto* outputInterface =
447         CIF::InterfaceCreator<OclTranslationOutput>::CreateInterfaceVer(
448             outVersion, outType);
449     if (outputInterface == nullptr)
450         return nullptr;
451 
452     PLATFORM *platformDescr = nullptr;
453     const char *platformStr = nullptr;
454     unsigned stepping       = 0U;
455 
456     if(globalState.GetPlatformImpl()){
457       // NEO supports platform interface
458       auto *PlatformImpl = globalState.GetPlatformImpl();
459       platformDescr = &PlatformImpl->p;
460       stepping = PlatformImpl->p.usRevId;
461       platformStr = cmc::getPlatformStr(PlatformImpl->p, /* inout */ stepping);
462     }
463 
464     DumpInputs(platformDescr, platformStr, stepping,
465                src, options, internalOptions, tracingOptions);
466 
467     OclTranslationOutputBase& Out = *outputInterface;
468 
469 #if defined(IGC_VC_ENABLED)
470     auto ErrFn = [&Out](const std::string& Err) {
471         Out.GetImpl()->SetError(TranslationErrorType::Internal, Err.c_str());
472     };
473     auto MaybeFE =
474         IGC::AdaptorCM::Frontend::makeFEWrapper(ErrFn, getCMFEWrapperDir());
475     if (!MaybeFE)
476         return outputInterface;
477 
478     llvm::BumpPtrAllocator A;
479     llvm::StringSaver Saver(A);
480     auto& FE = MaybeFE.getValue();
481     bool isMemFile = false;
482     auto FeArgs = processFeOptions(FE.LibInfo(), src, Out,
483                                    options, Saver, platformStr,
484                                    stepping, isMemFile);
485     if (FeArgs.empty())
486         return outputInterface; // proper error message is already set
487 
488     auto Drv = FE.buildDriverInvocation(FeArgs.size(), FeArgs.data());
489     if (!Drv)
490     {
491         ErrFn("Null driver invocation in CMFE");
492         return outputInterface;
493     }
494     if (Drv->getOutputType() != InvocationInfo::OutputTypeT::SPIRV)
495     {
496         ErrFn("CM frontend: unsupported output request");
497         return outputInterface;
498     }
499 
500     IGC::AdaptorCM::Frontend::InputArgs InputArgs;
501     InputArgs.CompilationOpts = Drv->getFEArgs();
502     // if real file is used, then don't fill InputArgs.InputText
503     // with content of source to avoid compilation from
504     // both memory and real temporary files
505     if (isMemFile)
506         InputArgs.InputText = src->GetMemory<char>();
507     auto FEOutput = FE.translate(InputArgs);
508     if (!FEOutput)
509     {
510         ErrFn("Null output in CMFE");
511         return outputInterface;
512     }
513     const auto& TargetFeatures = Drv->getTargetFeaturesStr();
514     const auto& [VCLLVMOpts, VCFinalizerOpts] =
515         IGC::AdaptorCM::Frontend::convertBackendArgsToVcAndFinalizerOpts(
516             Drv->getBEArgs());
517     auto VCApiOpts = FE.getVCApiOptions(&*Drv);
518     if (!VCFinalizerOpts.empty())
519       VCApiOpts += " -Xfinalizer '" + llvm::join(VCFinalizerOpts, " ") + "'";
520 
521     finalizeFEOutput(*FEOutput, VCApiOpts, VCLLVMOpts, TargetFeatures, Out);
522 
523     FclInternalOpts = std::string(" -llvm-options='" + llvm::join(VCLLVMOpts, " ") + "'");
524     FclOpts = VCApiOpts;
525 
526 #else
527     Out.GetImpl()->SetError(TranslationErrorType::Internal,
528                             "CM compilation is not supported in this configuration");
529 #endif // defined(IGC_VC_ENABLED)
530 
531     return outputInterface;
532 }
533 
534 }
535 
536 #include "cif/macros/disable.h"
537