yum-mirror/slang

Making it easier to work with shaders

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

Sam EstepInvoke `clang++` on C++ code instead of `clang` (#7958)ff96d5935

master
24.9 KiB806 linesraw
1// slang-gcc-compiler-util.cpp
2#include "slang-gcc-compiler-util.h"
3
4#include "../core/slang-char-util.h"
5#include "../core/slang-common.h"
6#include "../core/slang-io.h"
7#include "../core/slang-shared-library.h"
8#include "../core/slang-string-slice-pool.h"
9#include "../core/slang-string-util.h"
10#include "slang-artifact-desc-util.h"
11#include "slang-artifact-diagnostic-util.h"
12#include "slang-artifact-representation-impl.h"
13#include "slang-artifact-util.h"
14#include "slang-com-helper.h"
15
16namespace Slang
17{
18
19static Index _findVersionEnd(const UnownedStringSlice& in)
20{
21    Index numDots = 0;
22    const Index len = in.getLength();
23
24    for (Index i = 0; i < len; ++i)
25    {
26        const char c = in[i];
27        if (CharUtil::isDigit(c))
28        {
29            continue;
30        }
31        if (c == '.')
32        {
33            if (numDots >= 2)
34            {
35                return i;
36            }
37            numDots++;
38            continue;
39        }
40        return i;
41    }
42    return len;
43}
44
45/* static */ SlangResult GCCDownstreamCompilerUtil::parseVersion(
46    const UnownedStringSlice& text,
47    const UnownedStringSlice& prefix,
48    DownstreamCompilerDesc& outDesc)
49{
50    List<UnownedStringSlice> lines;
51    StringUtil::calcLines(text, lines);
52
53    for (auto line : lines)
54    {
55        Index prefixIndex = line.indexOf(prefix);
56        if (prefixIndex < 0)
57        {
58            continue;
59        }
60
61        const UnownedStringSlice remainingSlice =
62            UnownedStringSlice(line.begin() + prefixIndex + prefix.getLength(), line.end()).trim();
63
64        const Index versionEndIndex = _findVersionEnd(remainingSlice);
65        if (versionEndIndex < 0)
66        {
67            return SLANG_FAIL;
68        }
69
70        const UnownedStringSlice versionSlice(
71            remainingSlice.begin(),
72            remainingSlice.begin() + versionEndIndex);
73
74        // Version is in format 0.0.0
75        List<UnownedStringSlice> split;
76        StringUtil::split(versionSlice, '.', split);
77        List<Int> digits;
78
79        for (auto v : split)
80        {
81            Int version;
82            SLANG_RETURN_ON_FAIL(StringUtil::parseInt(v, version));
83            digits.add(version);
84        }
85
86        if (digits.getCount() < 2)
87        {
88            return SLANG_FAIL;
89        }
90
91        outDesc.version.set(int(digits[0]), int(digits[1]));
92        return SLANG_OK;
93    }
94
95    return SLANG_FAIL;
96}
97
98SlangResult GCCDownstreamCompilerUtil::calcVersion(
99    const ExecutableLocation& exe,
100    DownstreamCompilerDesc& outDesc)
101{
102    CommandLine cmdLine;
103    cmdLine.setExecutableLocation(exe);
104    cmdLine.addArg("-v");
105
106    ExecuteResult exeRes;
107    SLANG_RETURN_ON_FAIL(ProcessUtil::execute(cmdLine, exeRes));
108
109    // Note we now have builds that add other words in front of the version
110    // such as "Ubuntu clang version"
111    const UnownedStringSlice prefixes[] = {
112        UnownedStringSlice::fromLiteral("clang version"),
113        UnownedStringSlice::fromLiteral("gcc version"),
114        UnownedStringSlice::fromLiteral("Apple LLVM version"),
115        UnownedStringSlice::fromLiteral("Apple metal version"),
116
117    };
118    const SlangPassThrough types[] = {
119        SLANG_PASS_THROUGH_CLANG,
120        SLANG_PASS_THROUGH_GCC,
121        SLANG_PASS_THROUGH_CLANG,
122        SLANG_PASS_THROUGH_METAL,
123    };
124
125    SLANG_COMPILE_TIME_ASSERT(SLANG_COUNT_OF(prefixes) == SLANG_COUNT_OF(types));
126
127    for (Index i = 0; i < SLANG_COUNT_OF(prefixes); ++i)
128    {
129        // Set the type
130        outDesc.type = types[i];
131        // Extract the version
132        if (SLANG_SUCCEEDED(
133                parseVersion(exeRes.standardError.getUnownedSlice(), prefixes[i], outDesc)))
134        {
135            return SLANG_OK;
136        }
137    }
138
139    return SLANG_FAIL;
140}
141
142static SlangResult _parseSeverity(
143    const UnownedStringSlice& in,
144    ArtifactDiagnostic::Severity& outSeverity)
145{
146    typedef ArtifactDiagnostic::Severity Severity;
147
148    if (in == "error" || in == "fatal error")
149    {
150        outSeverity = Severity::Error;
151    }
152    else if (in == "warning")
153    {
154        outSeverity = Severity::Warning;
155    }
156    else if (in == "info" || in == "note")
157    {
158        outSeverity = Severity::Info;
159    }
160    else
161    {
162        return SLANG_FAIL;
163    }
164    return SLANG_OK;
165}
166
167namespace
168{ // anonymous
169
170enum class LineParseResult
171{
172    Single,       ///< It's a single line
173    Start,        ///< Line was the start of a message
174    Continuation, ///< Not totally clear, add to previous line if nothing else hit
175    Ignore,       ///< Ignore the line
176};
177
178} // namespace
179
180static SlangResult _parseGCCFamilyLine(
181    SliceAllocator& allocator,
182    const UnownedStringSlice& line,
183    LineParseResult& outLineParseResult,
184    ArtifactDiagnostic& outDiagnostic)
185{
186    typedef ArtifactDiagnostic Diagnostic;
187    typedef Diagnostic::Severity Severity;
188
189    // Set to default case
190    outLineParseResult = LineParseResult::Ignore;
191
192    /* example error output from different scenarios */
193
194    /*
195        tests/cpp-compiler/c-compile-error.c: In function 'int main(int, char**)':
196        tests/cpp-compiler/c-compile-error.c:8:13: error: 'b' was not declared in this scope
197        int a = b + c;
198        ^
199        tests/cpp-compiler/c-compile-error.c:8:17: error: 'c' was not declared in this scope
200        int a = b + c;
201        ^
202    */
203
204    /* /tmp/ccS0JCWe.o:c-compile-link-error.c:(.rdata$.refptr.thing[.refptr.thing]+0x0): undefined
205       reference to `thing' collect2: error: ld returned 1 exit status*/
206
207    /*
208     clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
209     [-Wdeprecated] Undefined symbols for architecture x86_64:
210     "_thing", referenced from:
211     _main in c-compile-link-error-a83ace.o
212     ld: symbol(s) not found for architecture x86_64
213     clang: error: linker command failed with exit code 1 (use -v to see invocation) */
214
215    /* /tmp/c-compile-link-error-ccf151.o: In function `main':
216     c-compile-link-error.c:(.text+0x19): undefined reference to `thing'
217    clang: error: linker command failed with exit code 1 (use -v to see invocation)
218    */
219
220    /* /tmp/c-compile-link-error-301c8c.o: In function `main':
221       /home/travis/build/shader-slang/slang/tests/cpp-compiler/c-compile-link-error.c:10: undefined
222       reference to `thing' clang-7: error: linker command failed with exit code 1 (use -v to see
223       invocation)*/
224
225    /*  /path/slang-cpp-prelude.h:4:10: fatal error: ../slang.h: No such file or directory
226        #include "slang.h"
227        ^~~~~~~~~~~~
228        compilation terminated.*/
229
230    /* g++: error: unrecognized command line option ‘-std=c++14’ */
231
232    outDiagnostic.stage = Diagnostic::Stage::Compile;
233
234    List<UnownedStringSlice> split;
235    StringUtil::split(line, ':', split);
236
237    // On windows we can have paths that are a: etc... if we detect this we can combine 0 - 1 to
238    // be 1.
239    if (split.getCount() > 1 && split[0].getLength() == 1)
240    {
241        const char c = split[0][0];
242        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
243        {
244            // We'll assume it's a path
245            UnownedStringSlice path(split[0].begin(), split[1].end());
246            split.removeAt(0);
247            split[0] = path;
248        }
249    }
250
251    if (split.getCount() == 2)
252    {
253        const auto split0 = split[0].trim();
254        if (split0 == UnownedStringSlice::fromLiteral("ld"))
255        {
256            // We'll ignore for now
257            outDiagnostic.stage = Diagnostic::Stage::Link;
258            outDiagnostic.severity = Severity::Info;
259            outDiagnostic.text = allocator.allocate(split[1].trim());
260            outLineParseResult = LineParseResult::Start;
261            return SLANG_OK;
262        }
263
264        if (SLANG_SUCCEEDED(_parseSeverity(split0, outDiagnostic.severity)))
265        {
266            // Command line errors can be just contain 'error:' etc. Can be seen on apple/clang
267            outDiagnostic.stage = Diagnostic::Stage::Compile;
268            outDiagnostic.text = allocator.allocate(split[1].trim());
269            outLineParseResult = LineParseResult::Single;
270            return SLANG_OK;
271        }
272
273        outLineParseResult = LineParseResult::Ignore;
274        return SLANG_OK;
275    }
276    else if (split.getCount() == 3)
277    {
278        const auto split0 = split[0].trim();
279        const auto split1 = split[1].trim();
280        const auto text = split[2].trim();
281
282        // Check for special handling for clang or metal
283        if (split0.startsWith(UnownedStringSlice::fromLiteral("clang")) ||
284            split0.startsWith(UnownedStringSlice::fromLiteral("metal")) ||
285            split0.startsWith(UnownedStringSlice::fromLiteral("Clang")) ||
286            split0 == UnownedStringSlice::fromLiteral("g++") ||
287            split0 == UnownedStringSlice::fromLiteral("gcc"))
288        {
289            // Extract the type
290            SLANG_RETURN_ON_FAIL(_parseSeverity(split[1].trim(), outDiagnostic.severity));
291
292            if (text.startsWith("linker command failed"))
293            {
294                outDiagnostic.stage = Diagnostic::Stage::Link;
295            }
296
297            outDiagnostic.text = allocator.allocate(text);
298            outLineParseResult = LineParseResult::Start;
299            return SLANG_OK;
300        }
301        else if (split1.startsWith("(.text"))
302        {
303            // This is a little weak... but looks like it's a link error
304            outDiagnostic.filePath = allocator.allocate(split[0]);
305            outDiagnostic.severity = Severity::Error;
306            outDiagnostic.stage = Diagnostic::Stage::Link;
307            outDiagnostic.text = allocator.allocate(text);
308            outLineParseResult = LineParseResult::Single;
309            return SLANG_OK;
310        }
311        else if (text.startsWith("ld returned"))
312        {
313            outDiagnostic.stage = ArtifactDiagnostic::Stage::Link;
314            SLANG_RETURN_ON_FAIL(_parseSeverity(split[1].trim(), outDiagnostic.severity));
315            outDiagnostic.text = allocator.allocate(line);
316            outLineParseResult = LineParseResult::Single;
317            return SLANG_OK;
318        }
319        else if (text == "")
320        {
321            // This is probably a prelude line, we'll just ignore it
322            outLineParseResult = LineParseResult::Ignore;
323            return SLANG_OK;
324        }
325    }
326    else if (split.getCount() == 4)
327    {
328        // Probably a link error, give the source line
329        String ext = Path::getPathExt(split[0]);
330
331        // Maybe a bit fragile -> but probably okay for now
332        if (ext != "o" && ext != "obj")
333        {
334            outLineParseResult = LineParseResult::Ignore;
335            return SLANG_OK;
336        }
337        else
338        {
339            outDiagnostic.filePath = allocator.allocate(split[1]);
340            outDiagnostic.location.line = 0;
341            outDiagnostic.location.column = 0;
342            outDiagnostic.severity = Diagnostic::Severity::Error;
343            outDiagnostic.stage = Diagnostic::Stage::Link;
344            outDiagnostic.text = allocator.allocate(split[3]);
345
346            outLineParseResult = LineParseResult::Start;
347            return SLANG_OK;
348        }
349    }
350    else if (split.getCount() >= 5)
351    {
352        // Probably a regular error line
353        SLANG_RETURN_ON_FAIL(_parseSeverity(split[3].trim(), outDiagnostic.severity));
354
355        outDiagnostic.filePath = allocator.allocate(split[0]);
356        SLANG_RETURN_ON_FAIL(StringUtil::parseInt(split[1], outDiagnostic.location.line));
357
358        // Everything from 4 to the end is the error
359        outDiagnostic.text = allocator.allocate(split[4].begin(), split.getLast().end());
360
361        outLineParseResult = LineParseResult::Start;
362        return SLANG_OK;
363    }
364
365    // Assume it's a continuation
366    outLineParseResult = LineParseResult::Continuation;
367    return SLANG_OK;
368}
369
370/* static */ SlangResult GCCDownstreamCompilerUtil::parseOutput(
371    const ExecuteResult& exeRes,
372    IArtifactDiagnostics* diagnostics)
373{
374    LineParseResult prevLineResult = LineParseResult::Ignore;
375
376    SliceAllocator allocator;
377
378    diagnostics->reset();
379    diagnostics->setRaw(SliceUtil::asCharSlice(exeRes.standardError));
380
381    // We hold in workDiagnostics so as it is more convenient to append to the last with a
382    // continuation also means we don't hold the allocations of building up continuations, just the
383    // results when finally allocated at the end
384    List<ArtifactDiagnostic> workDiagnostics;
385
386    for (auto line : LineParser(exeRes.standardError.getUnownedSlice()))
387    {
388        ArtifactDiagnostic diagnostic;
389
390        LineParseResult lineRes;
391
392        SLANG_RETURN_ON_FAIL(_parseGCCFamilyLine(allocator, line, lineRes, diagnostic));
393
394        switch (lineRes)
395        {
396        case LineParseResult::Start:
397            {
398                // It's start of a new message
399                workDiagnostics.add(diagnostic);
400                prevLineResult = LineParseResult::Start;
401                break;
402            }
403        case LineParseResult::Single:
404            {
405                // It's a single message, without anything following
406                workDiagnostics.add(diagnostic);
407                prevLineResult = LineParseResult::Ignore;
408                break;
409            }
410        case LineParseResult::Continuation:
411            {
412                if (prevLineResult == LineParseResult::Start ||
413                    prevLineResult == LineParseResult::Continuation)
414                {
415                    if (workDiagnostics.getCount() > 0)
416                    {
417                        auto& last = workDiagnostics.getLast();
418
419                        // TODO(JS): Note that this is somewhat wasteful as every time we append we
420                        // just allocate more memory to hold the result. If we had an allocator
421                        // dedicated to 'text' we could perhaps just append to the end of the last
422                        // allocation
423                        //
424                        // We are now in a continuation, add to the last
425                        StringBuilder buf;
426                        buf.append(asStringSlice(last.text));
427                        buf.append("\n");
428                        buf.append(line);
429
430                        last.text = allocator.allocate(buf);
431                    }
432                    prevLineResult = LineParseResult::Continuation;
433                }
434                break;
435            }
436        case LineParseResult::Ignore:
437            {
438                prevLineResult = lineRes;
439                break;
440            }
441        default:
442            return SLANG_FAIL;
443        }
444    }
445
446    for (const auto& diagnostic : workDiagnostics)
447    {
448        diagnostics->add(diagnostic);
449    }
450
451    if (diagnostics->hasOfAtLeastSeverity(ArtifactDiagnostic::Severity::Error) ||
452        exeRes.resultCode != 0)
453    {
454        diagnostics->setResult(SLANG_FAIL);
455    }
456
457    return SLANG_OK;
458}
459
460/* static */ SlangResult GCCDownstreamCompilerUtil::calcCompileProducts(
461    const CompileOptions& options,
462    ProductFlags flags,
463    IOSFileArtifactRepresentation* lockFile,
464    List<ComPtr<IArtifact>>& outArtifacts)
465{
466    SLANG_ASSERT(options.modulePath.count);
467
468    outArtifacts.clear();
469
470    if (flags & ProductFlag::Execution)
471    {
472        StringBuilder builder;
473        const auto desc = ArtifactDescUtil::makeDescForCompileTarget(options.targetType);
474        SLANG_RETURN_ON_FAIL(
475            ArtifactDescUtil::calcPathForDesc(desc, asStringSlice(options.modulePath), builder));
476
477        auto fileRep = OSFileArtifactRepresentation::create(
478            IOSFileArtifactRepresentation::Kind::Owned,
479            builder.getUnownedSlice(),
480            lockFile);
481        auto artifact = ArtifactUtil::createArtifact(desc);
482        artifact->addRepresentation(fileRep);
483
484        outArtifacts.add(artifact);
485    }
486
487    return SLANG_OK;
488}
489
490/* static */ SlangResult GCCDownstreamCompilerUtil::calcArgs(
491    const CompileOptions& options,
492    CommandLine& cmdLine)
493{
494    SLANG_ASSERT(options.modulePath.count);
495
496    PlatformKind platformKind = (options.platform == PlatformKind::Unknown)
497                                    ? PlatformUtil::getPlatformKind()
498                                    : options.platform;
499
500    const auto targetDesc = ArtifactDescUtil::makeDescForCompileTarget(options.targetType);
501
502    if (options.sourceLanguage == SLANG_SOURCE_LANGUAGE_CPP)
503    {
504        cmdLine.addArg("-fvisibility=hidden");
505
506        // C++17 since we share headers with slang itself (which uses c++17)
507        cmdLine.addArg("-std=c++17");
508    }
509
510    if (targetDesc.payload == ArtifactDesc::Payload::MetalAIR)
511    {
512        cmdLine.addArg("-std=metal3.1");
513    }
514
515    // Our generated code very often casts between dissimilar types with the
516    // knowledge that they have the same representation. This is strictly
517    // speaking UB, and GCC 10+ is happy to take advantage of this, stop it.
518    cmdLine.addArg("-fno-strict-aliasing");
519
520    // TODO(JS): Here we always set -m32 on x86. It could be argued it is only necessary when
521    // creating a shared library but if we create an object file, we don't know what to choose
522    // because we don't know what final usage is. It could also be argued that the platformKind
523    // could define the actual desired target - but as it stands we only have a target of 'Linux'
524    // (as opposed to Win32/64). Really it implies we need an arch enumeration too.
525    //
526    // For now we just make X86 binaries try and produce x86 compatible binaries as fixes the
527    // immediate problems.
528#if SLANG_PROCESSOR_X86
529    /* Used to specify the processor more broadly. For a x86 binary we need to make sure we build
530    x86 builds even when on an x64 system. -m32 -m64*/
531    cmdLine.addArg("-m32");
532#endif
533
534    switch (options.optimizationLevel)
535    {
536    case OptimizationLevel::None:
537        {
538            // No optimization
539            cmdLine.addArg("-O0");
540            break;
541        }
542    case OptimizationLevel::Default:
543        {
544            cmdLine.addArg("-Os");
545            break;
546        }
547    case OptimizationLevel::High:
548        {
549            cmdLine.addArg("-O2");
550            break;
551        }
552    case OptimizationLevel::Maximal:
553        {
554            cmdLine.addArg("-O3");
555            break;
556        }
557    default:
558        break;
559    }
560
561    if (options.debugInfoType != DebugInfoType::None)
562    {
563        cmdLine.addArg("-g");
564    }
565
566    if (options.flags & CompileOptions::Flag::Verbose)
567    {
568        cmdLine.addArg("-v");
569    }
570
571    switch (options.floatingPointMode)
572    {
573    case FloatingPointMode::Default:
574        break;
575    case FloatingPointMode::Precise:
576        {
577            // cmdLine.addArg("-fno-unsafe-math-optimizations");
578            break;
579        }
580    case FloatingPointMode::Fast:
581        {
582            // We could enable SSE with -mfpmath=sse
583            // But that would only make sense on a x64/x86 type processor and only if that feature
584            // is present (it is on all x64)
585            cmdLine.addArg("-ffast-math");
586            break;
587        }
588    }
589
590    StringBuilder moduleFilePath;
591    SLANG_RETURN_ON_FAIL(ArtifactDescUtil::calcPathForDesc(
592        targetDesc,
593        asStringSlice(options.modulePath),
594        moduleFilePath));
595
596    cmdLine.addArg("-o");
597    cmdLine.addArg(moduleFilePath);
598
599    switch (options.targetType)
600    {
601    case SLANG_SHADER_SHARED_LIBRARY:
602    case SLANG_HOST_SHARED_LIBRARY:
603        {
604            // Shared library
605            cmdLine.addArg("-shared");
606
607            if (PlatformUtil::isFamily(PlatformFamily::Unix, platformKind))
608            {
609                // Position independent
610                cmdLine.addArg("-fPIC");
611            }
612            break;
613        }
614    case SLANG_HOST_EXECUTABLE:
615        {
616            cmdLine.addArg("-rdynamic");
617            break;
618        }
619    case SLANG_OBJECT_CODE:
620        {
621            // Don't link, just produce object file
622            cmdLine.addArg("-c");
623            break;
624        }
625    default:
626        break;
627    }
628
629    // Add defines
630    for (const auto& define : options.defines)
631    {
632        StringBuilder builder;
633
634        builder << "-D";
635        builder << define.nameWithSig;
636        if (define.value.count)
637        {
638            builder << "=" << asStringSlice(define.value);
639        }
640
641        cmdLine.addArg(builder);
642    }
643
644    // Add includes
645    for (const auto& include : options.includePaths)
646    {
647        cmdLine.addArg("-I");
648        cmdLine.addArg(asString(include));
649    }
650
651    // Link options
652    if (0) // && options.targetType != TargetType::Object)
653    {
654        // linkOptions << "-Wl,";
655        // cmdLine.addArg(linkOptions);
656    }
657
658    if (options.targetType == SLANG_SHADER_SHARED_LIBRARY)
659    {
660        if (!PlatformUtil::isFamily(PlatformFamily::Apple, platformKind))
661        {
662            // On MacOS, this linker option is not supported. That's ok though in
663            // so far as on MacOS it does report any unfound symbols without the option.
664
665            // Linker flag to report any undefined symbols as a link error
666            cmdLine.addArg("-Wl,--no-undefined");
667        }
668    }
669
670    // Files to compile, need to be on the file system.
671    for (IArtifact* sourceArtifact : options.sourceArtifacts)
672    {
673        ComPtr<IOSFileArtifactRepresentation> fileRep;
674
675        // TODO(JS):
676        // Do we want to keep the file on the file system? It's probably reasonable to do so.
677        SLANG_RETURN_ON_FAIL(sourceArtifact->requireFile(ArtifactKeep::Yes, fileRep.writeRef()));
678        cmdLine.addArg(fileRep->getPath());
679    }
680
681    // Add the library paths
682
683    if (options.libraryPaths.count && (options.targetType == SLANG_HOST_EXECUTABLE))
684    {
685        if (PlatformUtil::isFamily(PlatformFamily::Apple, platformKind))
686            cmdLine.addArg("-Wl,-rpath,@loader_path,-rpath,@loader_path/../lib");
687        else
688            cmdLine.addArg("-Wl,-rpath,$ORIGIN,-rpath,$ORIGIN/../lib");
689    }
690
691    StringSlicePool libPathPool(StringSlicePool::Style::Default);
692
693    for (const auto& libPath : options.libraryPaths)
694    {
695        libPathPool.add(libPath);
696    }
697
698    // Artifacts might add library paths
699    for (IArtifact* artifact : options.libraries)
700    {
701        const auto artifactDesc = artifact->getDesc();
702        // If it's a library for CPU types, try and use it
703        if (ArtifactDescUtil::isCpuBinary(artifactDesc) &&
704            artifactDesc.kind == ArtifactKind::Library)
705        {
706            ComPtr<IOSFileArtifactRepresentation> fileRep;
707
708            // Get the name and path (can be empty) to the library
709            SLANG_RETURN_ON_FAIL(artifact->requireFile(ArtifactKeep::Yes, fileRep.writeRef()));
710
711            const UnownedStringSlice path(fileRep->getPath());
712            libPathPool.add(Path::getParentDirectory(path));
713
714            cmdLine.addPrefixPathArg(
715                "-l",
716                ArtifactDescUtil::getBaseNameFromPath(artifact->getDesc(), path));
717        }
718    }
719
720    if (options.sourceLanguage == SLANG_SOURCE_LANGUAGE_CPP &&
721        !PlatformUtil::isFamily(PlatformFamily::Windows, platformKind))
722    {
723        // Make STD libs available
724        cmdLine.addArg("-lstdc++");
725        // Make maths lib available
726        cmdLine.addArg("-lm");
727    }
728
729    for (const auto& libPath : libPathPool.getAdded())
730    {
731        // Note that any escaping of the path is handled in the ProcessUtil::
732        cmdLine.addArg("-L");
733        cmdLine.addArg(libPath);
734        cmdLine.addArg("-F");
735        cmdLine.addArg(libPath);
736    }
737
738    // Add compiler specific options from user.
739    for (auto compilerSpecificArg : options.compilerSpecificArguments)
740    {
741        const char* const arg = compilerSpecificArg;
742        cmdLine.addArg(arg);
743    }
744
745    return SLANG_OK;
746}
747
748/* static */ SlangResult GCCDownstreamCompilerUtil::createCompiler(
749    const ExecutableLocation& exe,
750    ComPtr<IDownstreamCompiler>& outCompiler)
751{
752    DownstreamCompilerDesc desc;
753    SLANG_RETURN_ON_FAIL(GCCDownstreamCompilerUtil::calcVersion(exe, desc));
754
755    auto compiler = new GCCDownstreamCompiler(desc);
756    ComPtr<IDownstreamCompiler> compilerIntf(compiler);
757    compiler->m_cmdLine.setExecutableLocation(exe);
758
759    outCompiler.swap(compilerIntf);
760    return SLANG_OK;
761}
762
763/* static */ SlangResult GCCDownstreamCompilerUtil::locateGCCCompilers(
764    const String& path,
765    ISlangSharedLibraryLoader* loader,
766    DownstreamCompilerSet* set)
767{
768    SLANG_UNUSED(loader);
769
770    ComPtr<IDownstreamCompiler> compiler;
771    if (SLANG_SUCCEEDED(createCompiler(ExecutableLocation(path, "g++"), compiler)))
772    {
773        // A downstream compiler for Slang must currently support C++17 - such that
774        // the prelude and generated code works.
775        //
776        // The first version of gcc that supports stable `-std=c++17` is 9.0
777        // https://gcc.gnu.org/projects/cxx-status.html
778
779        auto desc = compiler->getDesc();
780        if (desc.version.m_major < 9)
781        {
782            // If the version isn't 9 or higher, we don't add this version of the compiler.
783            return SLANG_OK;
784        }
785
786        set->addCompiler(compiler);
787    }
788    return SLANG_OK;
789}
790
791/* static */ SlangResult GCCDownstreamCompilerUtil::locateClangCompilers(
792    const String& path,
793    ISlangSharedLibraryLoader* loader,
794    DownstreamCompilerSet* set)
795{
796    SLANG_UNUSED(loader);
797
798    ComPtr<IDownstreamCompiler> compiler;
799    if (SLANG_SUCCEEDED(createCompiler(ExecutableLocation(path, "clang++"), compiler)))
800    {
801        set->addCompiler(compiler);
802    }
803    return SLANG_OK;
804}
805
806} // namespace Slang