yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Gangzheng TongEnable ccache for self-hosted runner (#8345)7d15d388d

master
44.7 KiB1413 linesraw
1// slang-nvrtc-compiler.cpp
2#include "slang-nvrtc-compiler.h"
3
4#include "../core/slang-blob.h"
5#include "../core/slang-char-util.h"
6#include "../core/slang-common.h"
7#include "../core/slang-io.h"
8#include "../core/slang-semantic-version.h"
9#include "../core/slang-shared-library.h"
10#include "../core/slang-string-slice-pool.h"
11#include "../core/slang-string-util.h"
12#include "slang-artifact-associated-impl.h"
13#include "slang-artifact-desc-util.h"
14#include "slang-artifact-diagnostic-util.h"
15#include "slang-artifact-util.h"
16#include "slang-com-helper.h"
17
18namespace nvrtc
19{
20
21typedef enum
22{
23    NVRTC_SUCCESS = 0,
24    NVRTC_ERROR_OUT_OF_MEMORY = 1,
25    NVRTC_ERROR_PROGRAM_CREATION_FAILURE = 2,
26    NVRTC_ERROR_INVALID_INPUT = 3,
27    NVRTC_ERROR_INVALID_PROGRAM = 4,
28    NVRTC_ERROR_INVALID_OPTION = 5,
29    NVRTC_ERROR_COMPILATION = 6,
30    NVRTC_ERROR_BUILTIN_OPERATION_FAILURE = 7,
31    NVRTC_ERROR_NO_NAME_EXPRESSIONS_AFTER_COMPILATION = 8,
32    NVRTC_ERROR_NO_LOWERED_NAMES_BEFORE_COMPILATION = 9,
33    NVRTC_ERROR_NAME_EXPRESSION_NOT_VALID = 10,
34    NVRTC_ERROR_INTERNAL_ERROR = 11
35} nvrtcResult;
36
37typedef struct _nvrtcProgram* nvrtcProgram;
38
39// clang-format off
40#define SLANG_NVRTC_FUNCS(x) \
41    x(const char*, nvrtcGetErrorString, (nvrtcResult result)) \
42    x(nvrtcResult, nvrtcVersion, (int *major, int *minor)) \
43    x(nvrtcResult, nvrtcCreateProgram, (nvrtcProgram *prog, const char *src, const char *name, int numHeaders, const char * const *headers, const char * const *includeNames)) \
44    x(nvrtcResult, nvrtcDestroyProgram, (nvrtcProgram *prog)) \
45    x(nvrtcResult, nvrtcCompileProgram, (nvrtcProgram prog, int numOptions, const char * const *options)) \
46    x(nvrtcResult, nvrtcGetPTXSize, (nvrtcProgram prog, size_t *ptxSizeRet)) \
47    x(nvrtcResult, nvrtcGetPTX, (nvrtcProgram prog, char *ptx)) \
48    x(nvrtcResult, nvrtcGetProgramLogSize, (nvrtcProgram prog, size_t *logSizeRet)) \
49    x(nvrtcResult, nvrtcGetProgramLog, (nvrtcProgram prog, char *log))\
50    x(nvrtcResult, nvrtcAddNameExpression, (nvrtcProgram prog, const char * const name_expression)) \
51    x(nvrtcResult, nvrtcGetLoweredName, (nvrtcProgram prog, const char *const name_expression, const char** lowered_name))
52// clang-format on
53
54} // namespace nvrtc
55
56namespace Slang
57{
58using namespace nvrtc;
59
60static SlangResult _asResult(nvrtcResult res)
61{
62    switch (res)
63    {
64    case NVRTC_SUCCESS:
65        {
66            return SLANG_OK;
67        }
68    case NVRTC_ERROR_OUT_OF_MEMORY:
69        {
70            return SLANG_E_OUT_OF_MEMORY;
71        }
72    case NVRTC_ERROR_PROGRAM_CREATION_FAILURE:
73    case NVRTC_ERROR_INVALID_INPUT:
74    case NVRTC_ERROR_INVALID_PROGRAM:
75        {
76            return SLANG_FAIL;
77        }
78    case NVRTC_ERROR_INVALID_OPTION:
79        {
80            return SLANG_E_INVALID_ARG;
81        }
82    case NVRTC_ERROR_COMPILATION:
83    case NVRTC_ERROR_BUILTIN_OPERATION_FAILURE:
84    case NVRTC_ERROR_NO_NAME_EXPRESSIONS_AFTER_COMPILATION:
85    case NVRTC_ERROR_NO_LOWERED_NAMES_BEFORE_COMPILATION:
86    case NVRTC_ERROR_NAME_EXPRESSION_NOT_VALID:
87        {
88            return SLANG_FAIL;
89        }
90    case NVRTC_ERROR_INTERNAL_ERROR:
91        {
92            return SLANG_E_INTERNAL_FAIL;
93        }
94    default:
95        return SLANG_FAIL;
96    }
97}
98
99class NVRTCDownstreamCompiler : public DownstreamCompilerBase
100{
101public:
102    typedef DownstreamCompilerBase Super;
103
104    // IDownstreamCompiler
105    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
106    compile(const CompileOptions& options, IArtifact** outArtifact) SLANG_OVERRIDE;
107    virtual SLANG_NO_THROW bool SLANG_MCALL isFileBased() SLANG_OVERRIDE { return false; }
108    virtual SLANG_NO_THROW bool SLANG_MCALL
109    canConvert(const ArtifactDesc& from, const ArtifactDesc& to) SLANG_OVERRIDE;
110    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
111    convert(IArtifact* from, const ArtifactDesc& to, IArtifact** outArtifact) SLANG_OVERRIDE;
112
113    /// Must be called before use
114    SlangResult init(ISlangSharedLibrary* library);
115
116    NVRTCDownstreamCompiler() {}
117
118protected:
119    struct ScopeProgram
120    {
121        ScopeProgram(NVRTCDownstreamCompiler* compiler, nvrtcProgram program)
122            : m_compiler(compiler), m_program(program)
123        {
124        }
125        ~ScopeProgram() { m_compiler->m_nvrtcDestroyProgram(&m_program); }
126        NVRTCDownstreamCompiler* m_compiler;
127        nvrtcProgram m_program;
128    };
129
130    SlangResult _findCUDAIncludePath(String& outPath);
131    SlangResult _getCUDAIncludePath(String& outIncludePath);
132
133    SlangResult _findOptixIncludePath(String& outIncludePath);
134    SlangResult _getOptixIncludePath(String& outIncludePath);
135
136    SlangResult _maybeAddHalfSupport(const CompileOptions& options, CommandLine& ioCmdLine);
137    SlangResult _maybeAddOptixSupport(const CompileOptions& options, CommandLine& ioCmdLine);
138
139#define SLANG_NVTRC_MEMBER_FUNCS(ret, name, params) ret(*m_##name) params;
140
141    SLANG_NVRTC_FUNCS(SLANG_NVTRC_MEMBER_FUNCS);
142
143    // Holds list of paths passed in where cuda_fp16.h is found. Does *NOT*
144    // include cuda_fp16.h.
145    List<String> m_cudaFp16FoundPaths;
146
147    bool m_cudaIncludeSearched = false;
148    // Holds location of where include (for cuda_fp16.h) is found.
149    String m_cudaIncludePath;
150
151    // Holds list of paths passed in where optix.h is found. Does *NOT* include
152    // optix.h.
153    List<String> m_optixFoundPaths;
154
155    bool m_optixIncludeSearched = false;
156    // Holds location of where include (for optix.h) is found.
157    String m_optixIncludePath;
158
159    ComPtr<ISlangSharedLibrary> m_sharedLibrary;
160};
161
162#define SLANG_NVRTC_RETURN_ON_FAIL(x) \
163    {                                 \
164        nvrtcResult _res = x;         \
165        if (_res != NVRTC_SUCCESS)    \
166            return _asResult(_res);   \
167    }
168
169SlangResult NVRTCDownstreamCompiler::init(ISlangSharedLibrary* library)
170{
171#define SLANG_NVTRC_GET_FUNC(ret, name, params)               \
172    m_##name = (ret(*) params)library->findFuncByName(#name); \
173    if (m_##name == nullptr)                                  \
174        return SLANG_FAIL;
175
176    SLANG_NVRTC_FUNCS(SLANG_NVTRC_GET_FUNC)
177
178    m_sharedLibrary = library;
179
180    m_desc.type = SLANG_PASS_THROUGH_NVRTC;
181
182    int major, minor;
183    m_nvrtcVersion(&major, &minor);
184    m_desc.version.set(major, minor);
185    return SLANG_OK;
186}
187
188static SlangResult _parseLocation(
189    SliceAllocator& allocator,
190    const UnownedStringSlice& in,
191    ArtifactDiagnostic& outDiagnostic)
192{
193    const Index startIndex = in.indexOf('(');
194
195    if (startIndex >= 0)
196    {
197        outDiagnostic.filePath = allocator.allocate(in.begin(), in.begin() + startIndex);
198        UnownedStringSlice remaining(in.begin() + startIndex + 1, in.end());
199        const Int endIndex = remaining.indexOf(')');
200
201        UnownedStringSlice lineText =
202            UnownedStringSlice(remaining.begin(), remaining.begin() + endIndex);
203
204        Int line;
205        SLANG_RETURN_ON_FAIL(StringUtil::parseInt(lineText, line));
206        outDiagnostic.location.line = line;
207    }
208    else
209    {
210        outDiagnostic.location.line = 0;
211        outDiagnostic.filePath = allocator.allocate(in);
212    }
213    return SLANG_OK;
214}
215
216static bool _isDriveLetter(char c)
217{
218    return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
219}
220
221static bool _hasDriveLetter(const UnownedStringSlice& line)
222{
223    return line.getLength() > 2 && line[1] == ':' && _isDriveLetter(line[0]);
224}
225
226static SlangResult _parseNVRTCLine(
227    SliceAllocator& allocator,
228    const UnownedStringSlice& line,
229    ArtifactDiagnostic& outDiagnostic)
230{
231    typedef ArtifactDiagnostic Diagnostic;
232    typedef ArtifactDiagnostic::Severity Severity;
233
234    outDiagnostic.stage = Diagnostic::Stage::Compile;
235
236    List<UnownedStringSlice> split;
237    if (_hasDriveLetter(line))
238    {
239        // The drive letter has :, which confuses things, so skip that and then fix
240        // up first entry
241        UnownedStringSlice lineWithoutDrive(line.begin() + 2, line.end());
242        StringUtil::split(lineWithoutDrive, ':', split);
243        split[0] = UnownedStringSlice(line.begin(), split[0].end());
244    }
245    else
246    {
247        StringUtil::split(line, ':', split);
248    }
249
250    if (split.getCount() >= 3)
251    {
252        // tests/cuda/cuda-compile.cu(7): warning: variable "c" is used before its
253        // value is set
254        const auto split1 = split[1].trim();
255
256        Severity severity = Severity::Unknown;
257
258        if (split1 == toSlice("error") || split1 == toSlice("catastrophic error"))
259        {
260            severity = Severity::Error;
261        }
262        else if (split1 == toSlice("warning"))
263        {
264            severity = Severity::Warning;
265        }
266        else
267        {
268            // Fall back position to try and determine if this really is some kind of
269            // error/warning without succeeding when it's due to some other property
270            // of the output diagnostics.
271            //
272            // Anything ending with " warning:" or " error:" in effect.
273
274            // We can expand to include character after as this is split1, as must be
275            // followed by at a minimum : (as the split has at least 3 parts).
276            const UnownedStringSlice expandSplit1(split1.begin(), split1.end() + 1);
277
278            if (expandSplit1.endsWith(toSlice(" error:")))
279            {
280                severity = Severity::Error;
281            }
282            else if (expandSplit1.endsWith(toSlice(" warning:")))
283            {
284                severity = Severity::Warning;
285            }
286        }
287
288        if (severity != Severity::Unknown)
289        {
290            // The text is everything following the : after the warning.
291            UnownedStringSlice text(split[2].begin(), split.getLast().end());
292
293            // Trim whitespace at start and end
294            text = text.trim();
295
296            // Set the diagnostic
297            outDiagnostic.severity = severity;
298            outDiagnostic.text = allocator.allocate(text);
299            SLANG_RETURN_ON_FAIL(_parseLocation(allocator, split[0], outDiagnostic));
300
301            return SLANG_OK;
302        }
303
304        // TODO(JS): Note here if it's not possible to determine a line as being the
305        // main diagnostics we fall through to it potentially being a note.
306        //
307        // That could mean a valid diagnostic (from NVRTCs point of view) is
308        // ignored/noted, because this code can't parse it. Ideally that situation
309        // would lead to an error such that we can detect and things will fail.
310        //
311        // So we might want to revisit this determination in the future.
312    }
313
314    // There isn't a diagnostic on this line
315    if (line.getLength() == 0 || line.trim().getLength() == 0)
316    {
317        return SLANG_E_NOT_FOUND;
318    }
319
320    // We'll assume it's info, associated with a previous line
321    outDiagnostic.severity = Severity::Info;
322    outDiagnostic.text = allocator.allocate(line);
323
324    return SLANG_OK;
325}
326
327/* An implementation of Path::Visitor that can be used for finding NVRTC shared
328 * library installations. */
329struct NVRTCPathVisitor : Path::Visitor
330{
331    struct Candidate
332    {
333        typedef Candidate ThisType;
334
335        bool operator==(const ThisType& rhs) const
336        {
337            return path == rhs.path && version == rhs.version;
338        }
339        bool operator!=(const ThisType& rhs) const { return !(*this == rhs); }
340
341        static Candidate make(const String& path, const SemanticVersion& version)
342        {
343            Candidate can;
344            can.version = version;
345            can.path = path;
346            return can;
347        }
348        String path;
349        SemanticVersion version;
350    };
351
352    Index findVersion(const SemanticVersion& version) const
353    {
354        const Index count = m_candidates.getCount();
355        for (Index i = 0; i < count; ++i)
356        {
357            if (m_candidates[i].version == version)
358            {
359                return i;
360            }
361        }
362        return -1;
363    }
364
365    static bool _orderCandiate(const Candidate& a, const Candidate& b)
366    {
367        return a.version < b.version;
368    }
369    void sortCandidates() { m_candidates.sort(_orderCandiate); }
370
371#if SLANG_WINDOWS_FAMILY
372    SlangResult getVersion(const UnownedStringSlice& filename, SemanticVersion& outVersion)
373    {
374        // Versions on windows of the form
375        // nvrtc64_110_2.dll
376        //          11 - Major
377        //           0 Minor
378        //           2 Patch
379        Index endIndex = filename.indexOf('.');
380        endIndex = (endIndex < 0) ? filename.getLength() : endIndex;
381
382        // If we have a version slice, split it
383        UnownedStringSlice versionSlice = UnownedStringSlice(
384            filename.begin() + m_prefix.getLength(),
385            filename.begin() + endIndex);
386
387        if (versionSlice.getLength() <= 0)
388        {
389            return SLANG_E_NOT_FOUND;
390        }
391        Int patch = 0;
392        UnownedStringSlice majorMinorSlice;
393        {
394            List<UnownedStringSlice> slices;
395            StringUtil::split(versionSlice, '_', slices);
396            if (slices.getCount() >= 2)
397            {
398                // We don't bother checking for error here, if it's not parsable, it
399                // will be 0
400                StringUtil::parseInt(slices[1], patch);
401            }
402            majorMinorSlice = slices[0];
403        }
404
405        if (majorMinorSlice.getLength() < 2)
406        {
407            // Must be a major and minor
408            return SLANG_FAIL;
409        }
410
411        UnownedStringSlice majorSlice = majorMinorSlice.head(majorMinorSlice.getLength() - 1);
412        UnownedStringSlice minorSlice =
413            majorMinorSlice.subString(majorMinorSlice.getLength() - 1, 1);
414
415        Int major;
416        Int minor;
417
418        SLANG_RETURN_ON_FAIL(StringUtil::parseInt(majorSlice, major));
419        SLANG_RETURN_ON_FAIL(StringUtil::parseInt(minorSlice, minor));
420
421        outVersion = SemanticVersion(int(major), int(minor), int(patch));
422        return SLANG_OK;
423    }
424#else
425    // How the path is constructed depends on platform
426    // https://docs.nvidia.com/cuda/nvrtc/index.html
427    // TODO(JS): Handle version number depending on the platform - it's different
428    // for Windows/OSX/Linux
429    SlangResult getVersion(const UnownedStringSlice& filename, SemanticVersion& outVersion)
430    {
431        SLANG_UNUSED(filename);
432        SLANG_UNUSED(outVersion);
433        return SLANG_E_NOT_IMPLEMENTED;
434    }
435
436#endif
437
438    void accept(Path::Type type, const UnownedStringSlice& filename) SLANG_OVERRIDE
439    {
440        // Lets make sure it start's with nvrtc, but not worry about case
441        if (type == Path::Type::File)
442        {
443            // If there is a defined extension, make sure it has it
444            if (m_postfix.getLength() && filename.getLength() >= m_postfix.getLength())
445            {
446                // We test without case - really for windows
447                UnownedStringSlice filenamePostfix =
448                    filename.tail(filename.getLength() - m_postfix.getLength());
449                if (!filenamePostfix.caseInsensitiveEquals(m_postfix.getUnownedSlice()))
450                {
451                    return;
452                }
453            }
454
455            if (filename.getLength() >= m_prefix.getLength() &&
456                filename.subString(0, m_prefix.getLength())
457                    .caseInsensitiveEquals(m_prefix.getUnownedSlice()))
458            {
459                SemanticVersion version;
460                // If it produces an error, just use 0.0.0
461                if (SLANG_FAILED(getVersion(filename, version)))
462                {
463                    version = SemanticVersion();
464                }
465
466                // We may want to add multiple versions, if they are in different
467                // locations - as there may be multiple entries in the PATH, and only
468                // one works. We'll only know which works by loading
469
470#if 0
471                // We already found this version, so let's not add it again
472                if (findVersion(version) >= 0)
473                {
474                    return;
475                }
476#endif
477
478                // Strip to make a shared library name
479                UnownedStringSlice sharedLibraryName =
480                    filename.tail(m_prefix.getLength() - m_sharedLibraryStem.getLength());
481                sharedLibraryName = filename.head(filename.getLength() - m_postfix.getLength());
482
483                auto candidate =
484                    Candidate::make(Path::combine(m_basePath, sharedLibraryName), version);
485
486                // If we already have this candidate, then skip
487                if (m_candidates.indexOf(candidate) >= 0)
488                {
489                    return;
490                }
491
492                // Add to the list of candidates
493                m_candidates.add(candidate);
494            }
495        }
496    }
497
498    SlangResult findInDirectory(const String& path)
499    {
500        m_basePath = path;
501        return Path::find(path, nullptr, this);
502    }
503
504    bool hasCandidates() const { return m_candidates.getCount() > 0; }
505
506    NVRTCPathVisitor(const UnownedStringSlice& sharedLibraryStem)
507        : m_sharedLibraryStem(sharedLibraryStem)
508    {
509        // Work out the prefix and postfix of the shader
510        StringBuilder buf;
511        SharedLibrary::appendPlatformFileName(sharedLibraryStem, buf);
512        const Index index = buf.indexOf(sharedLibraryStem);
513        SLANG_ASSERT(index >= 0);
514
515        m_prefix = buf.getUnownedSlice().head(index + sharedLibraryStem.getLength());
516        m_postfix = buf.getUnownedSlice().tail(index + sharedLibraryStem.getLength());
517    }
518
519    String m_prefix;
520    String m_postfix;
521    String m_basePath;
522    String m_sharedLibraryStem;
523
524    List<Candidate> m_candidates;
525};
526
527template<typename T>
528SLANG_FORCE_INLINE static void _unusedFunction(const T& func)
529{
530    SLANG_UNUSED(func);
531}
532
533#define SLANG_UNUSED_FUNCTION(x) _unusedFunction(x)
534
535static UnownedStringSlice _getNVRTCBaseName()
536{
537#if SLANG_WINDOWS_FAMILY && SLANG_PTR_IS_64
538    return UnownedStringSlice::fromLiteral("nvrtc64_");
539#else
540    return UnownedStringSlice::fromLiteral("nvrtc");
541#endif
542}
543
544// Candidates are in m_candidates list. Will be ordered from the oldest to
545// newest (in version number)
546static SlangResult _findNVRTC(NVRTCPathVisitor& visitor)
547{
548    // First try the instance path (if supported on platform)
549    {
550        StringBuilder instancePath;
551        if (SLANG_SUCCEEDED(PlatformUtil::getInstancePath(instancePath)))
552        {
553            visitor.findInDirectory(instancePath);
554        }
555    }
556
557    // If we don't have a candidate, try CUDA_PATH
558    if (!visitor.hasCandidates())
559    {
560        StringBuilder buf;
561        if (SLANG_SUCCEEDED(PlatformUtil::getEnvironmentVariable(
562                UnownedStringSlice::fromLiteral("CUDA_PATH"),
563                buf)))
564        {
565            // Look for candidates in the directory
566            visitor.findInDirectory(Path::combine(buf, "bin"));
567        }
568    }
569
570    // If we haven't we go searching through PATH
571    if (!visitor.hasCandidates())
572    {
573        List<UnownedStringSlice> splitPath;
574
575        StringBuilder buf;
576        if (SLANG_SUCCEEDED(
577                PlatformUtil::getEnvironmentVariable(UnownedStringSlice::fromLiteral("PATH"), buf)))
578        {
579            // Split so we get individual paths
580            List<UnownedStringSlice> paths;
581            StringUtil::split(buf.getUnownedSlice(), ';', paths);
582
583            // We use a pool to make sure we only check each path once
584            StringSlicePool pool(StringSlicePool::Style::Empty);
585
586            // We are going to search the paths in order
587            for (const auto& path : paths)
588            {
589                // PATH can have the same path multiple times. If we have already
590                // searched this path, we don't need to again
591                if (!pool.has(path))
592                {
593                    pool.add(path);
594
595                    Path::split(path, splitPath);
596
597                    // We could search every path, but here we restrict to paths that look
598                    // like CUDA installations. It's a path that contains a CUDA directory
599                    // and has bin
600                    if (splitPath.indexOf("CUDA") >= 0 &&
601                        splitPath[splitPath.getCount() - 1].caseInsensitiveEquals(
602                            UnownedStringSlice::fromLiteral("bin")))
603                    {
604                        // Okay lets search it
605                        visitor.findInDirectory(path);
606                    }
607                }
608            }
609        }
610    }
611
612    // Put into version order with oldest first.
613    visitor.sortCandidates();
614
615    return SLANG_OK;
616}
617
618static const UnownedStringSlice g_fp16HeaderName = UnownedStringSlice::fromLiteral("cuda_fp16.h");
619static const UnownedStringSlice g_optixHeaderName = UnownedStringSlice::fromLiteral("optix.h");
620
621SlangResult _findFileInIncludePath(
622    const String& path,
623    const UnownedStringSlice& filename,
624    String& outPath)
625{
626    if (File::exists(Path::combine(path, filename)))
627    {
628        outPath = path;
629        return SLANG_OK;
630    }
631
632    {
633        String includePath = Path::combine(path, "include");
634        if (File::exists(Path::combine(includePath, filename)))
635        {
636            outPath = includePath;
637            return SLANG_OK;
638        }
639    }
640
641    {
642        String cudaIncludePath = Path::combine(path, "CUDA/include");
643        if (File::exists(Path::combine(cudaIncludePath, filename)))
644        {
645            outPath = cudaIncludePath;
646            return SLANG_OK;
647        }
648    }
649
650    return SLANG_E_NOT_FOUND;
651}
652
653SlangResult NVRTCDownstreamCompiler::_findCUDAIncludePath(String& outPath)
654{
655    outPath = String();
656
657    // Try looking up from a symbol. This will work as long as the nvrtc is loaded
658    // somehow from a dll/sharedlibrary And the header is included from there
659    {
660        String libPath = SharedLibraryUtils::getSharedLibraryFileName((void*)m_nvrtcCreateProgram);
661        if (libPath.getLength())
662        {
663            String parentPath = Path::getParentDirectory(libPath);
664
665            if (SLANG_SUCCEEDED(_findFileInIncludePath(parentPath, g_fp16HeaderName, outPath)))
666            {
667                return SLANG_OK;
668            }
669
670            // See if the shared library is in the SDK, as if so we know how to find
671            // the includes
672            // TODO(JS):
673            // This directory structure is correct for windows perhaps could be
674            // different elsewhere.
675            {
676                List<UnownedStringSlice> pathSlices;
677                Path::split(parentPath.getUnownedSlice(), pathSlices);
678
679                // This -2 split holds the version number.
680                const auto pathSplitCount = pathSlices.getCount();
681                if (pathSplitCount >= 3 && pathSlices[pathSplitCount - 1] == toSlice("bin") &&
682                    pathSlices[pathSplitCount - 3] == toSlice("CUDA"))
683                {
684                    // We want to make sure that one of these paths is CUDA...
685                    const auto sdkPath = Path::getParentDirectory(parentPath);
686
687                    if (SLANG_SUCCEEDED(_findFileInIncludePath(sdkPath, g_fp16HeaderName, outPath)))
688                    {
689                        return SLANG_OK;
690                    }
691                }
692            }
693        }
694    }
695
696    // Try CUDA_PATH environment variable
697    {
698        StringBuilder buf;
699        if (SLANG_SUCCEEDED(PlatformUtil::getEnvironmentVariable(
700                UnownedStringSlice::fromLiteral("CUDA_PATH"),
701                buf)))
702        {
703            String includePath = Path::combine(buf, "include");
704
705            if (File::exists(Path::combine(includePath, g_fp16HeaderName)))
706            {
707                outPath = includePath;
708                return SLANG_OK;
709            }
710        }
711    }
712
713#if SLANG_LINUX_FAMILY
714    List<String> candidatePaths;
715    candidatePaths.add("/usr/local/include");
716    candidatePaths.add("/usr/local/cuda/include");
717    candidatePaths.add("/usr/include");
718
719    for (const String& includePath : candidatePaths)
720    {
721        if (File::exists(Path::combine(includePath, g_fp16HeaderName)))
722        {
723            outPath = includePath;
724            return SLANG_OK;
725        }
726    }
727#endif
728    return SLANG_E_NOT_FOUND;
729}
730
731SlangResult NVRTCDownstreamCompiler::_getCUDAIncludePath(String& outPath)
732{
733    if (!m_cudaIncludeSearched)
734    {
735        m_cudaIncludeSearched = true;
736
737        SLANG_ASSERT(m_cudaIncludePath.getLength() == 0);
738
739        _findCUDAIncludePath(m_cudaIncludePath);
740    }
741
742    outPath = m_cudaIncludePath;
743    return m_cudaIncludePath.getLength() ? SLANG_OK : SLANG_E_NOT_FOUND;
744}
745
746SlangResult NVRTCDownstreamCompiler::_findOptixIncludePath(String& outPath)
747{
748    outPath = String();
749
750    // First try to find OptiX headers in the local external/optix-dev/include
751    // directory relative to the executable path
752    {
753        StringBuilder instancePathBuilder;
754        if (SLANG_SUCCEEDED(PlatformUtil::getInstancePath(instancePathBuilder)))
755        {
756            // Get executable path, then go up to project root
757            // instancePathBuilder already contains the bin directory path
758            // Executable is in build/Debug/bin or build/Release/bin
759            // Go up 3 levels: bin -> Debug/Release -> build -> project root
760            String binPath = instancePathBuilder;
761            String buildTypeDir = Path::getParentDirectory(binPath);
762            String buildDir = Path::getParentDirectory(buildTypeDir);
763            String projectRoot = Path::getParentDirectory(buildDir);
764
765            String localOptixPath =
766                Path::combine(Path::combine(projectRoot, "external/optix-dev"), "include");
767            String optixHeader = Path::combine(localOptixPath, g_optixHeaderName);
768
769            if (File::exists(optixHeader))
770            {
771                outPath = localOptixPath;
772                return SLANG_OK;
773            }
774        }
775    }
776    List<String> rootPaths;
777
778#if SLANG_WINDOWS_FAMILY
779    const char* searchPattern = "OptiX SDK *";
780    StringBuilder builder;
781    if (SLANG_SUCCEEDED(PlatformUtil::getEnvironmentVariable(
782            UnownedStringSlice::fromLiteral("PROGRAMDATA"),
783            builder)))
784    {
785        rootPaths.add(Path::combine(builder, "NVIDIA Corporation"));
786    }
787#else
788    const char* searchPattern = "NVIDIA-OptiX-SDK-*";
789    StringBuilder builder;
790    if (SLANG_SUCCEEDED(
791            PlatformUtil::getEnvironmentVariable(UnownedStringSlice::fromLiteral("HOME"), builder)))
792    {
793        rootPaths.add(builder);
794    }
795#endif
796
797    struct OptixHeaders
798    {
799        String path;
800        SemanticVersion version;
801    };
802
803    // Visitor to find Optix headers.
804    struct Visitor : public Path::Visitor
805    {
806        const String& rootPath;
807        List<OptixHeaders>& optixPaths;
808        Visitor(const String& rootPath, List<OptixHeaders>& optixPaths)
809            : rootPath(rootPath), optixPaths(optixPaths)
810        {
811        }
812        void accept(Path::Type type, const UnownedStringSlice& path) SLANG_OVERRIDE
813        {
814            if (type != Path::Type::Directory)
815                return;
816
817            OptixHeaders optixPath;
818#if SLANG_WINDOWS_FAMILY
819            // Paths are expected to look like ".\OptiX SDK X.X.X"
820            auto versionString = path.subString(path.lastIndexOf(' ') + 1, path.getLength());
821#else
822            // Paths are expected to look like "./NVIDIA-OptiX-SDK-X.X.X-suffix"
823            auto versionString = path.subString(0, path.lastIndexOf('-'));
824            versionString =
825                versionString.subString(path.lastIndexOf('-') + 1, versionString.getLength());
826#endif
827            if (SLANG_SUCCEEDED(SemanticVersion::parse(versionString, '.', optixPath.version)))
828            {
829                optixPath.path = Path::combine(Path::combine(rootPath, path), "include");
830                String optixHeader = Path::combine(optixPath.path, g_optixHeaderName);
831                if (File::exists(optixHeader))
832                {
833                    optixPaths.add(optixPath);
834                }
835            }
836        }
837    };
838
839    List<OptixHeaders> optixPaths;
840
841    for (const String& rootPath : rootPaths)
842    {
843        Visitor visitor(rootPath, optixPaths);
844        Path::find(rootPath, searchPattern, &visitor);
845    }
846
847    // Find newest version
848    const OptixHeaders* newest = nullptr;
849    for (Index i = 0; i < optixPaths.getCount(); ++i)
850    {
851        if (!newest || optixPaths[i].version > newest->version)
852        {
853            newest = &optixPaths[i];
854        }
855    }
856
857    if (newest)
858    {
859        outPath = newest->path;
860        return SLANG_OK;
861    }
862
863    return SLANG_E_NOT_FOUND;
864}
865
866SlangResult NVRTCDownstreamCompiler::_getOptixIncludePath(String& outPath)
867{
868    if (!m_optixIncludeSearched)
869    {
870        m_optixIncludeSearched = true;
871
872        SLANG_ASSERT(m_optixIncludePath.getLength() == 0);
873
874        _findOptixIncludePath(m_optixIncludePath);
875    }
876
877    outPath = m_optixIncludePath;
878    return m_optixIncludePath.getLength() ? SLANG_OK : SLANG_E_NOT_FOUND;
879}
880
881SlangResult NVRTCDownstreamCompiler::_maybeAddHalfSupport(
882    const DownstreamCompileOptions& options,
883    CommandLine& ioCmdLine)
884{
885    if ((options.flags & DownstreamCompileOptions::Flag::EnableFloat16) == 0)
886    {
887        return SLANG_OK;
888    }
889
890    // First check if we know if one of the include paths contains cuda_fp16.h
891    for (const auto& includePath : options.includePaths)
892    {
893        if (m_cudaFp16FoundPaths.indexOf(includePath) >= 0)
894        {
895            // Okay we have an include path that we know works.
896            // Just need to enable HALF in prelude
897            ioCmdLine.addArg("-DSLANG_CUDA_ENABLE_HALF");
898            return SLANG_OK;
899        }
900    }
901
902    // Let's see if one of the paths finds cuda_fp16.h
903    for (const auto& curIncludePath : options.includePaths)
904    {
905        const String includePath = asString(curIncludePath);
906        const String checkPath = Path::combine(includePath, g_fp16HeaderName);
907        if (File::exists(checkPath))
908        {
909            m_cudaFp16FoundPaths.add(includePath);
910            // Just need to enable HALF in prelude
911            ioCmdLine.addArg("-DSLANG_CUDA_ENABLE_HALF");
912            return SLANG_OK;
913        }
914    }
915
916    String includePath;
917    SLANG_RETURN_ON_FAIL(_getCUDAIncludePath(includePath));
918
919    // Add the found include path
920    ioCmdLine.addArg("-I");
921    ioCmdLine.addArg(includePath);
922
923    ioCmdLine.addArg("-DSLANG_CUDA_ENABLE_HALF");
924
925    return SLANG_OK;
926}
927
928SlangResult NVRTCDownstreamCompiler::_maybeAddOptixSupport(
929    const DownstreamCompileOptions& options,
930    CommandLine& ioCmdLine)
931{
932    // First check if we know if one of the include paths contains optix.h
933    for (const auto& includePath : options.includePaths)
934    {
935        if (m_optixFoundPaths.indexOf(includePath) >= 0)
936        {
937            // Okay we have an include path that we know works.
938            // Just need to enable OptiX in prelude
939            ioCmdLine.addArg("-DSLANG_CUDA_ENABLE_OPTIX");
940            return SLANG_OK;
941        }
942    }
943
944    // Let's see if one of the paths finds optix.h
945    for (const auto& curIncludePath : options.includePaths)
946    {
947        const String includePath = asString(curIncludePath);
948        const String checkPath = Path::combine(includePath, g_optixHeaderName);
949        if (File::exists(checkPath))
950        {
951            m_optixFoundPaths.add(includePath);
952            // Just need to enable OptiX in prelude
953            ioCmdLine.addArg("-DSLANG_CUDA_ENABLE_OPTIX");
954            return SLANG_OK;
955        }
956    }
957
958    String includePath;
959    SLANG_RETURN_ON_FAIL(_getOptixIncludePath(includePath));
960
961    // Add the found include path
962    ioCmdLine.addArg("-I");
963    ioCmdLine.addArg(includePath);
964
965    ioCmdLine.addArg("-DSLANG_CUDA_ENABLE_OPTIX");
966
967    return SLANG_OK;
968}
969
970SlangResult NVRTCDownstreamCompiler::compile(
971    const DownstreamCompileOptions& inOptions,
972    IArtifact** outArtifact)
973{
974    if (!isVersionCompatible(inOptions))
975    {
976        // Not possible to compile with this version of the interface.
977        return SLANG_E_NOT_IMPLEMENTED;
978    }
979
980    CompileOptions options = getCompatibleVersion(&inOptions);
981
982    // This compiler can only deal with a single artifact
983    if (options.sourceArtifacts.count != 1)
984    {
985        return SLANG_FAIL;
986    }
987
988    IArtifact* sourceArtifact = options.sourceArtifacts[0];
989
990    CommandLine cmdLine;
991
992    // --dopt option is only available in CUDA 11.7 and later
993    bool hasDoptOption = m_desc.version >= SemanticVersion(11, 7);
994
995    switch (options.debugInfoType)
996    {
997    case DebugInfoType::None:
998        {
999            break;
1000        }
1001    default:
1002        {
1003            cmdLine.addArg("--device-debug");
1004            if (hasDoptOption)
1005            {
1006                cmdLine.addArg("--dopt=on");
1007            }
1008            break;
1009        }
1010    case DebugInfoType::Maximal:
1011        {
1012            cmdLine.addArg("--device-debug");
1013            cmdLine.addArg("--generate-line-info");
1014            if (hasDoptOption)
1015            {
1016                cmdLine.addArg("--dopt=on");
1017            }
1018            break;
1019        }
1020    }
1021
1022    // Don't seem to have such a control, so ignore for now
1023    // switch (options.optimizationLevel)
1024    //{
1025    //    default: break;
1026    //}
1027
1028    switch (options.floatingPointMode)
1029    {
1030    case FloatingPointMode::Default:
1031        break;
1032    case FloatingPointMode::Precise:
1033        {
1034            break;
1035        }
1036    case FloatingPointMode::Fast:
1037        {
1038            cmdLine.addArg("--use_fast_math");
1039            break;
1040        }
1041    }
1042
1043    // Add defines
1044    for (const auto& define : options.defines)
1045    {
1046        StringBuilder builder;
1047        builder << "-D";
1048        builder << asStringSlice(define.nameWithSig);
1049        if (define.value.count)
1050        {
1051            builder << "=" << asStringSlice(define.value);
1052        }
1053
1054        cmdLine.addArg(builder);
1055    }
1056
1057    // Add includes
1058    for (const auto& include : options.includePaths)
1059    {
1060        cmdLine.addArg("-I");
1061        cmdLine.addArg(asString(include));
1062    }
1063
1064    SLANG_RETURN_ON_FAIL(_maybeAddHalfSupport(options, cmdLine));
1065
1066    // Neither of these options are strictly required, for general use of nvrtc,
1067    // but are enabled to make use withing Slang work more smoothly
1068    {
1069        // Require c++17, the default at the time of writing, since we share
1070        // some functionality between slang itself and the compiled code
1071        cmdLine.addArg("-std=c++17");
1072
1073        // Disable all warnings
1074        // This is arguably too much - but nvrtc does not appear to have a mechanism
1075        // to switch off individual warnings. I tried the -Xcudafe mechanism but
1076        // that does not appear to work for nvrtc
1077        cmdLine.addArg("-w");
1078    }
1079
1080    {
1081        // The lowest supported CUDA architecture version supported
1082        // by any version of NVRTC we support is `compute_30`.
1083        //
1084        SemanticVersion version(3);
1085
1086        // Newer releases of NVRTC only support newer CUDA architectures.
1087        if (m_desc.version.m_major > 12 ||
1088            (m_desc.version.m_major == 12 && m_desc.version.m_minor >= 8))
1089        {
1090            // NVRTC 12.8+ warns about architectures prior to compute_75 being deprecated
1091            // The exact warning message is:
1092            //   nvrtc 12.8: nvrtc: warning : Architectures prior to '<compute/sm>_75' are
1093            //   deprecated and may be removed in a future release
1094            version = SemanticVersion(7, 5);
1095        }
1096        else if (m_desc.version.m_major == 12)
1097        {
1098            // NVRTC 12.0 supports `compute_50` and up
1099            version = SemanticVersion(5, 0);
1100        }
1101        else if (m_desc.version.m_major == 11)
1102        {
1103            // NVRTC in CUDA 11 only supports `compute_35` and up
1104            // (with everything before `compute_52` being deprecated).
1105            version = SemanticVersion(3, 5);
1106        }
1107
1108        // If constructs used in the code to be compield require
1109        // a higher architecture version than the minimum, then
1110        // we will set the version to the highest version listed
1111        // among the requirements.
1112        //
1113        for (const auto& capabilityVersion : options.requiredCapabilityVersions)
1114        {
1115            if (capabilityVersion.kind == DownstreamCompileOptions::CapabilityVersion::Kind::CUDASM)
1116            {
1117                if (capabilityVersion.version > version)
1118                {
1119                    version = capabilityVersion.version;
1120                }
1121            }
1122        }
1123
1124        StringBuilder builder;
1125        builder << "-arch=compute_";
1126        builder << version.m_major;
1127
1128        SLANG_ASSERT(version.m_minor >= 0 && version.m_minor <= 9);
1129        builder << char('0' + version.m_minor);
1130
1131        cmdLine.addArg(builder);
1132    }
1133
1134    List<const char*> headers;
1135    List<const char*> headerIncludeNames;
1136
1137    // If compiling for OptiX, we need to add the appropriate search paths to the
1138    // command line.
1139    //
1140    if (options.pipelineType == PipelineType::RayTracing)
1141    {
1142        SLANG_RETURN_ON_FAIL(_maybeAddOptixSupport(options, cmdLine));
1143    }
1144
1145    // Add any compiler specific options
1146    // NOTE! If these clash with any previously set options (as set via other
1147    // flags) compilation might fail.
1148    if (options.compilerSpecificArguments.count > 0)
1149    {
1150        for (auto compilerSpecificArg : options.compilerSpecificArguments)
1151        {
1152            const char* const arg = compilerSpecificArg;
1153            cmdLine.addArg(arg);
1154        }
1155    }
1156
1157    SLANG_ASSERT(headers.getCount() == headerIncludeNames.getCount());
1158
1159    ComPtr<ISlangBlob> sourceBlob;
1160    SLANG_RETURN_ON_FAIL(sourceArtifact->loadBlob(ArtifactKeep::Yes, sourceBlob.writeRef()));
1161
1162    auto sourcePath = ArtifactUtil::findPath(sourceArtifact);
1163
1164    StringBuilder storage;
1165    auto sourceContents = SliceUtil::toTerminatedCharSlice(storage, sourceBlob);
1166
1167    nvrtcProgram program = nullptr;
1168    nvrtcResult res = m_nvrtcCreateProgram(
1169        &program,
1170        sourceContents,
1171        String(sourcePath).getBuffer(),
1172        (int)headers.getCount(),
1173        headers.getBuffer(),
1174        headerIncludeNames.getBuffer());
1175    if (res != NVRTC_SUCCESS)
1176    {
1177        return _asResult(res);
1178    }
1179    ScopeProgram scope(this, program);
1180
1181    List<const char*> dstOptions;
1182    dstOptions.setCount(cmdLine.m_args.getCount());
1183    for (Index i = 0; i < cmdLine.m_args.getCount(); ++i)
1184    {
1185        dstOptions[i] = cmdLine.m_args[i].getBuffer();
1186    }
1187
1188    res = m_nvrtcCompileProgram(program, int(dstOptions.getCount()), dstOptions.getBuffer());
1189
1190    auto artifact = ArtifactUtil::createArtifactForCompileTarget(options.targetType);
1191    auto diagnostics = ArtifactDiagnostics::create();
1192
1193    ArtifactUtil::addAssociated(artifact, diagnostics);
1194
1195    ComPtr<ISlangBlob> blob;
1196
1197    diagnostics->setResult(_asResult(res));
1198
1199    {
1200        String rawDiagnostics;
1201
1202        size_t logSize = 0;
1203        SLANG_NVRTC_RETURN_ON_FAIL(m_nvrtcGetProgramLogSize(program, &logSize));
1204
1205        if (logSize)
1206        {
1207            char* dst = rawDiagnostics.prepareForAppend(Index(logSize));
1208            SLANG_NVRTC_RETURN_ON_FAIL(m_nvrtcGetProgramLog(program, dst));
1209
1210            // If there is a terminating zero remove it, as the rawDiagnostics
1211            // string will already contain one.
1212            logSize -= size_t(logSize > 0 && dst[logSize - 1] == 0);
1213
1214            rawDiagnostics.appendInPlace(dst, Index(logSize));
1215
1216            diagnostics->setRaw(SliceUtil::asCharSlice(rawDiagnostics));
1217        }
1218
1219        SliceAllocator allocator;
1220
1221        // Get all of the lines
1222        List<UnownedStringSlice> lines;
1223        StringUtil::calcLines(rawDiagnostics.getUnownedSlice(), lines);
1224
1225        // Remove any trailing empty lines
1226        while (lines.getCount() && lines.getLast().getLength() == 0)
1227        {
1228            lines.removeLast();
1229        }
1230
1231        // Find the index searching from last line, that is blank
1232        // indicating the end of the output
1233        Index lastIndex = lines.getCount();
1234
1235        // Look for the first blank line after this point.
1236        // We'll assume any information after that blank line to the end of the
1237        // diagnostic is compilation summary information.
1238        for (Index i = lastIndex - 1; i >= 0; --i)
1239        {
1240            if (lines[i].getLength() == 0)
1241            {
1242                lastIndex = i;
1243                break;
1244            }
1245        }
1246
1247        // Parse the diagnostics here
1248        for (auto line : makeConstArrayView(lines.getBuffer(), lastIndex))
1249        {
1250            ArtifactDiagnostic diagnostic;
1251            SlangResult lineRes = _parseNVRTCLine(allocator, line, diagnostic);
1252
1253            if (SLANG_SUCCEEDED(lineRes))
1254            {
1255                // We only allow info diagnostics after a 'regular' diagnostic.
1256                if (diagnostic.severity == ArtifactDiagnostic::Severity::Info &&
1257                    diagnostics->getCount() == 0)
1258                {
1259                    continue;
1260                }
1261
1262                diagnostics->add(diagnostic);
1263            }
1264            else if (lineRes != SLANG_E_NOT_FOUND)
1265            {
1266                // If there is an error exit
1267                // But if SLANG_E_NOT_FOUND that just means this line couldn't be
1268                // parsed, so ignore.
1269                return lineRes;
1270            }
1271        }
1272
1273        // If it has a compilation error.. and there isn't already an error set
1274        // set as failed.
1275        if (SLANG_SUCCEEDED(diagnostics->getResult()) &&
1276            diagnostics->hasOfAtLeastSeverity(ArtifactDiagnostic::Severity::Error))
1277        {
1278            diagnostics->setResult(SLANG_FAIL);
1279        }
1280    }
1281
1282    if (res == nvrtc::NVRTC_SUCCESS)
1283    {
1284        // We should parse the log to set up the diagnostics
1285        size_t ptxSize;
1286        SLANG_NVRTC_RETURN_ON_FAIL(m_nvrtcGetPTXSize(program, &ptxSize));
1287
1288        List<uint8_t> ptx;
1289        ptx.setCount(Index(ptxSize));
1290
1291        SLANG_NVRTC_RETURN_ON_FAIL(m_nvrtcGetPTX(program, (char*)ptx.getBuffer()));
1292
1293        artifact->addRepresentationUnknown(ListBlob::moveCreate(ptx));
1294    }
1295
1296    *outArtifact = artifact.detach();
1297    return SLANG_OK;
1298}
1299
1300bool NVRTCDownstreamCompiler::canConvert(const ArtifactDesc& from, const ArtifactDesc& to)
1301{
1302    return ArtifactDescUtil::isDisassembly(from, to) || ArtifactDescUtil::isDisassembly(to, from);
1303}
1304
1305SlangResult NVRTCDownstreamCompiler::convert(
1306    IArtifact* from,
1307    const ArtifactDesc& to,
1308    IArtifact** outArtifact)
1309{
1310    if (!canConvert(from->getDesc(), to))
1311    {
1312        return SLANG_FAIL;
1313    }
1314
1315    // PTX is 'binary like' and 'assembly like' so we allow conversion either way
1316    // We do it by just getting as a blob and sharing that blob.
1317    // A more sophisticated implementation could proxy to the original artifact,
1318    // but this is simpler, and probably fine in most scenarios.
1319    ComPtr<ISlangBlob> blob;
1320    SLANG_RETURN_ON_FAIL(from->loadBlob(ArtifactKeep::Yes, blob.writeRef()));
1321
1322    auto artifact = ArtifactUtil::createArtifact(to);
1323    artifact->addRepresentationUnknown(blob);
1324
1325    *outArtifact = artifact.detach();
1326    return SLANG_OK;
1327}
1328
1329static SlangResult _findAndLoadNVRTC(
1330    ISlangSharedLibraryLoader* loader,
1331    ComPtr<ISlangSharedLibrary>& outLibrary)
1332{
1333#if SLANG_WINDOWS_FAMILY && SLANG_PTR_IS_64
1334
1335    // We only need to search 64 bit versions on windows
1336    NVRTCPathVisitor visitor(_getNVRTCBaseName());
1337    SLANG_RETURN_ON_FAIL(_findNVRTC(visitor));
1338
1339    // We want to start with the newest version...
1340    for (Index i = visitor.m_candidates.getCount() - 1; i >= 0; --i)
1341    {
1342        const auto& candidate = visitor.m_candidates[i];
1343        if (SLANG_SUCCEEDED(
1344                loader->loadSharedLibrary(candidate.path.getBuffer(), outLibrary.writeRef())))
1345        {
1346            return SLANG_OK;
1347        }
1348    }
1349
1350#else
1351    SLANG_UNUSED(loader);
1352    SLANG_UNUSED(outLibrary);
1353
1354    SLANG_UNUSED_FUNCTION(_getNVRTCBaseName);
1355    SLANG_UNUSED_FUNCTION(_findNVRTC);
1356#endif
1357
1358    // This is an official-ish list of versions is here:
1359    // https://developer.nvidia.com/cuda-toolkit-archive
1360
1361    // Filenames for NVRTC
1362    // https://docs.nvidia.com/cuda/nvrtc/index.html
1363    //
1364    // From this it appears on platforms other than windows the SharedLibrary name
1365    // should be nvrtc which is already tried, so we can give up now.
1366    return SLANG_E_NOT_FOUND;
1367}
1368
1369/* static */ SlangResult NVRTCDownstreamCompilerUtil::locateCompilers(
1370    const String& path,
1371    ISlangSharedLibraryLoader* loader,
1372    DownstreamCompilerSet* set)
1373{
1374    ComPtr<ISlangSharedLibrary> library;
1375
1376    // If the user supplies a path to their preferred version of NVRTC,
1377    // we just use this.
1378    if (path.getLength() != 0)
1379    {
1380        SLANG_RETURN_ON_FAIL(loader->loadSharedLibrary(path.getBuffer(), library.writeRef()));
1381    }
1382    else
1383    {
1384        // As a catch-all for non-Windows platforms, we search for
1385        // a library simply named `nvrtc` (well, `libnvrtc`) which
1386        // is expected to match whatever the user has installed.
1387        //
1388        // On Windows an installation could place the version of nvrtc it uses in
1389        // the same directory as the slang binary, such that it's loaded. Using this
1390        // name also allows a ISlangSharedLibraryLoader to easily identify what is
1391        // required and perhaps load a specific version
1392        if (SLANG_FAILED(loader->loadSharedLibrary("nvrtc", library.writeRef())))
1393        {
1394            // Try something more sophisticated to locate NVRTC
1395            SLANG_RETURN_ON_FAIL(_findAndLoadNVRTC(loader, library));
1396        }
1397    }
1398
1399    SLANG_ASSERT(library);
1400    if (!library)
1401    {
1402        return SLANG_FAIL;
1403    }
1404
1405    auto compiler = new NVRTCDownstreamCompiler;
1406    ComPtr<IDownstreamCompiler> compilerIntf(compiler);
1407    SLANG_RETURN_ON_FAIL(compiler->init(library));
1408
1409    set->addCompiler(compilerIntf);
1410    return SLANG_OK;
1411}
1412
1413} // namespace Slang