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