yum-mirror/slang

Making it easier to work with shaders

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

Julius IkkalaPass compiler specific args to GCC and Visual Studio too (#6019)46149eeb2

master
15.6 KiB552 linesraw
1// slang-visual-studio-compiler-util.cpp
2#include "slang-visual-studio-compiler-util.h"
3
4#include "../core/slang-common.h"
5#include "../core/slang-string-slice-pool.h"
6#include "../core/slang-string-util.h"
7#include "slang-com-helper.h"
8
9// if Visual Studio import the visual studio platform specific header
10#if SLANG_VC
11#include "windows/slang-win-visual-studio-util.h"
12#endif
13
14#include "../core/slang-io.h"
15#include "slang-artifact-desc-util.h"
16#include "slang-artifact-diagnostic-util.h"
17#include "slang-artifact-representation-impl.h"
18#include "slang-artifact-util.h"
19
20namespace Slang
21{
22
23static void _addFile(
24    const String& path,
25    const ArtifactDesc& desc,
26    IOSFileArtifactRepresentation* lockFile,
27    List<ComPtr<IArtifact>>& outArtifacts)
28{
29    auto fileRep = OSFileArtifactRepresentation::create(
30        IOSFileArtifactRepresentation::Kind::Owned,
31        path.getUnownedSlice(),
32        lockFile);
33    auto artifact = ArtifactUtil::createArtifact(desc);
34    artifact->addRepresentation(fileRep);
35
36    outArtifacts.add(artifact);
37}
38
39/* static */ SlangResult VisualStudioCompilerUtil::calcCompileProducts(
40    const CompileOptions& options,
41    ProductFlags flags,
42    IOSFileArtifactRepresentation* lockFile,
43    List<ComPtr<IArtifact>>& outArtifacts)
44{
45    SLANG_ASSERT(options.modulePath.count);
46
47    const String modulePath = asString(options.modulePath);
48
49    const auto targetDesc = ArtifactDescUtil::makeDescForCompileTarget(options.targetType);
50
51    outArtifacts.clear();
52
53    if (flags & ProductFlag::Execution)
54    {
55        StringBuilder builder;
56        const auto desc = ArtifactDescUtil::makeDescForCompileTarget(options.targetType);
57        SLANG_RETURN_ON_FAIL(
58            ArtifactDescUtil::calcPathForDesc(desc, modulePath.getUnownedSlice(), builder));
59
60        _addFile(builder, desc, lockFile, outArtifacts);
61    }
62    if (flags & ProductFlag::Miscellaneous)
63    {
64
65        _addFile(
66            modulePath + ".ilk",
67            ArtifactDesc::make(
68                ArtifactKind::BinaryFormat,
69                ArtifactPayload::Unknown,
70                ArtifactStyle::None),
71            lockFile,
72            outArtifacts);
73
74        if (options.targetType == SLANG_SHADER_SHARED_LIBRARY)
75        {
76            _addFile(
77                modulePath + ".exp",
78                ArtifactDesc::make(
79                    ArtifactKind::BinaryFormat,
80                    ArtifactPayload::Unknown,
81                    ArtifactStyle::None),
82                lockFile,
83                outArtifacts);
84            _addFile(
85                modulePath + ".lib",
86                ArtifactDesc::make(ArtifactKind::Library, ArtifactPayload::HostCPU, targetDesc),
87                lockFile,
88                outArtifacts);
89        }
90    }
91    if (flags & ProductFlag::Compile)
92    {
93        _addFile(
94            modulePath + ".obj",
95            ArtifactDesc::make(ArtifactKind::ObjectCode, ArtifactPayload::HostCPU, targetDesc),
96            lockFile,
97            outArtifacts);
98    }
99    if (flags & ProductFlag::Debug)
100    {
101        // TODO(JS): Could try and determine based on debug information
102        _addFile(
103            modulePath + ".pdb",
104            ArtifactDesc::make(
105                ArtifactKind::BinaryFormat,
106                ArtifactPayload::PdbDebugInfo,
107                targetDesc),
108            lockFile,
109            outArtifacts);
110    }
111
112    return SLANG_OK;
113}
114
115/* static */ SlangResult VisualStudioCompilerUtil::calcArgs(
116    const CompileOptions& options,
117    CommandLine& cmdLine)
118{
119    SLANG_ASSERT(options.modulePath.count);
120
121    // https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=vs-2019
122
123    cmdLine.addArg("/nologo");
124
125    // Display full path of source files in diagnostics
126    cmdLine.addArg("/FC");
127
128    if (options.sourceLanguage == SLANG_SOURCE_LANGUAGE_CPP)
129    {
130        if (options.flags & CompileOptions::Flag::EnableExceptionHandling)
131        {
132            // https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=vs-2019
133            // Assumes c functions cannot throw
134            cmdLine.addArg("/EHsc");
135        }
136
137        // To maintain parity with the slang compiler headers which are shared
138        cmdLine.addArg("/std:c++17");
139    }
140
141    if (options.flags & CompileOptions::Flag::Verbose)
142    {
143        // Doesn't appear to be a VS equivalent
144    }
145
146    if (options.flags & CompileOptions::Flag::EnableSecurityChecks)
147    {
148        cmdLine.addArg("/GS");
149    }
150    else
151    {
152        cmdLine.addArg("/GS-");
153    }
154
155    switch (options.debugInfoType)
156    {
157    default:
158        {
159            // Multithreaded statically linked runtime library
160            cmdLine.addArg("/MD");
161            break;
162        }
163    case DebugInfoType::None:
164        {
165            break;
166        }
167    case DebugInfoType::Maximal:
168        {
169            // Multithreaded statically linked *debug* runtime library
170            cmdLine.addArg("/MDd");
171            break;
172        }
173    }
174
175    // /Fd - followed by name of the pdb file
176    if (options.debugInfoType != DebugInfoType::None)
177    {
178        // Generate complete debugging information
179        cmdLine.addArg("/Zi");
180        cmdLine.addPrefixPathArg("/Fd", asString(options.modulePath), ".pdb");
181    }
182
183    switch (options.optimizationLevel)
184    {
185    case OptimizationLevel::None:
186        {
187            // No optimization
188            cmdLine.addArg("/Od");
189            break;
190        }
191    case OptimizationLevel::Default:
192        {
193            break;
194        }
195    case OptimizationLevel::High:
196        {
197            cmdLine.addArg("/O2");
198            break;
199        }
200    case OptimizationLevel::Maximal:
201        {
202            cmdLine.addArg("/Ox");
203            break;
204        }
205    default:
206        break;
207    }
208
209    switch (options.floatingPointMode)
210    {
211    case FloatingPointMode::Default:
212        break;
213    case FloatingPointMode::Precise:
214        {
215            // precise is default behavior, VS also has 'strict'
216            //
217            // ```/fp:strict has behavior similar to /fp:precise, that is, the compiler preserves
218            // the source ordering and rounding properties of floating-point code when it generates
219            // and optimizes object code for the target machine, and observes the standard when
220            // handling special values. In addition, the program may safely access or modify the
221            // floating-point environment at runtime.```
222
223            cmdLine.addArg("/fp:precise");
224            break;
225        }
226    case FloatingPointMode::Fast:
227        {
228            cmdLine.addArg("/fp:fast");
229            break;
230        }
231    }
232
233    const auto modulePath = asString(options.modulePath);
234
235    switch (options.targetType)
236    {
237    case SLANG_SHADER_SHARED_LIBRARY:
238    case SLANG_HOST_SHARED_LIBRARY:
239        {
240            // Create dynamic link library
241            if (options.debugInfoType == DebugInfoType::None)
242            {
243                cmdLine.addArg("/LDd");
244            }
245            else
246            {
247                cmdLine.addArg("/LD");
248            }
249
250            cmdLine.addPrefixPathArg("/Fe", modulePath, ".dll");
251            break;
252        }
253    case SLANG_HOST_EXECUTABLE:
254        {
255            cmdLine.addPrefixPathArg("/Fe", modulePath, ".exe");
256            break;
257        }
258    default:
259        break;
260    }
261
262    // Object file specify it's location - needed if we are out
263    cmdLine.addPrefixPathArg("/Fo", modulePath, ".obj");
264
265    // Add defines
266    for (const auto& define : options.defines)
267    {
268        StringBuilder builder;
269        builder << "/D";
270        builder << asStringSlice(define.nameWithSig);
271        if (define.value.count)
272        {
273            builder << "=" << asStringSlice(define.value);
274        }
275
276        cmdLine.addArg(builder);
277    }
278
279    // Add includes
280    for (const auto& include : options.includePaths)
281    {
282        cmdLine.addArg("/I");
283        cmdLine.addArg(asString(include));
284    }
285
286    // https://docs.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=vs-2019
287    // /Eha - Specifies the model of exception handling. (a, s, c, r are options)
288
289    // Files to compile, need to be on the file system.
290    for (IArtifact* sourceArtifact : options.sourceArtifacts)
291    {
292        ComPtr<IOSFileArtifactRepresentation> fileRep;
293
294        // TODO(JS):
295        // Do we want to keep the file on the file system? It's probably reasonable to do so.
296        SLANG_RETURN_ON_FAIL(sourceArtifact->requireFile(ArtifactKeep::Yes, fileRep.writeRef()));
297        cmdLine.addArg(fileRep->getPath());
298    }
299
300    // Link options (parameters past /link go to linker)
301    cmdLine.addArg("/link");
302
303    StringSlicePool libPathPool(StringSlicePool::Style::Default);
304
305    for (const auto& libPath : options.libraryPaths)
306    {
307        libPathPool.add(libPath);
308    }
309
310    // Link libraries.
311    for (IArtifact* artifact : options.libraries)
312    {
313        auto desc = artifact->getDesc();
314
315        if (ArtifactDescUtil::isCpuBinary(desc) && desc.kind == ArtifactKind::Library)
316        {
317            // Get the libray name and path
318            ComPtr<IOSFileArtifactRepresentation> fileRep;
319            SLANG_RETURN_ON_FAIL(artifact->requireFile(ArtifactKeep::Yes, fileRep.writeRef()));
320
321            const UnownedStringSlice path(fileRep->getPath());
322            libPathPool.add(Path::getParentDirectory(path));
323            // We need the extension for windows
324            cmdLine.addArg(ArtifactDescUtil::getBaseNameFromPath(desc, path) + ".lib");
325        }
326    }
327
328    // Add all the library paths
329    for (const auto& libPath : libPathPool.getAdded())
330    {
331        // Note that any escaping of the path is handled in the ProcessUtil::
332        cmdLine.addPrefixPathArg("/LIBPATH:", libPath);
333    }
334
335    // Add compiler specific options from user.
336    for (auto compilerSpecificArg : options.compilerSpecificArguments)
337    {
338        const char* const arg = compilerSpecificArg;
339        cmdLine.addArg(arg);
340    }
341
342    return SLANG_OK;
343}
344
345static SlangResult _parseSeverity(
346    const UnownedStringSlice& in,
347    ArtifactDiagnostic::Severity& outSeverity)
348{
349    typedef ArtifactDiagnostic::Severity Severity;
350
351    if (in == "error" || in == "fatal error")
352    {
353        outSeverity = Severity::Error;
354    }
355    else if (in == "warning")
356    {
357        outSeverity = Severity::Warning;
358    }
359    else if (in == "info")
360    {
361        outSeverity = Severity::Info;
362    }
363    else
364    {
365        return SLANG_FAIL;
366    }
367    return SLANG_OK;
368}
369
370static SlangResult _parseVisualStudioLine(
371    SliceAllocator& allocator,
372    const UnownedStringSlice& line,
373    ArtifactDiagnostic& outDiagnostic)
374{
375    typedef IArtifactDiagnostics::Diagnostic Diagnostic;
376
377    UnownedStringSlice linkPrefix = UnownedStringSlice::fromLiteral("LINK :");
378    if (line.startsWith(linkPrefix))
379    {
380        outDiagnostic.stage = ArtifactDiagnostic::Stage::Link;
381        outDiagnostic.severity = ArtifactDiagnostic::Severity::Info;
382
383        outDiagnostic.text = allocator.allocate(line.begin() + linkPrefix.getLength(), line.end());
384
385        return SLANG_OK;
386    }
387
388    outDiagnostic.stage = ArtifactDiagnostic::Stage::Compile;
389
390    const char* const start = line.begin();
391    const char* const end = line.end();
392
393    UnownedStringSlice postPath;
394    // Handle the path and line no
395    {
396        const char* cur = start;
397
398        // We have to assume it is a path up to the first : that isn't part of a drive specification
399
400        if ((end - cur > 2) && Path::isDriveSpecification(UnownedStringSlice(start, start + 2)))
401        {
402            // Skip drive spec
403            cur += 2;
404        }
405
406        // Find the first colon after this
407        Index colonIndex = UnownedStringSlice(cur, end).indexOf(':');
408        if (colonIndex < 0)
409        {
410            return SLANG_FAIL;
411        }
412
413        // Looks like we have a line number
414        if (cur[colonIndex - 1] == ')')
415        {
416            const char* lineNoEnd = cur + colonIndex - 1;
417            const char* lineNoStart = lineNoEnd;
418            while (lineNoStart > start && *lineNoStart != '(')
419            {
420                lineNoStart--;
421            }
422            // Check this appears plausible
423            if (*lineNoStart != '(' || *lineNoEnd != ')')
424            {
425                return SLANG_FAIL;
426            }
427            Int numDigits = 0;
428            Int lineNo = 0;
429            for (const char* digitCur = lineNoStart + 1; digitCur < lineNoEnd; ++digitCur)
430            {
431                char c = *digitCur;
432                if (c >= '0' && c <= '9')
433                {
434                    lineNo = lineNo * 10 + (c - '0');
435                    numDigits++;
436                }
437                else
438                {
439                    return SLANG_FAIL;
440                }
441            }
442            if (numDigits == 0)
443            {
444                return SLANG_FAIL;
445            }
446
447            outDiagnostic.filePath = allocator.allocate(start, lineNoStart);
448            outDiagnostic.location.line = lineNo;
449        }
450        else
451        {
452            outDiagnostic.filePath = allocator.allocate(start, cur + colonIndex);
453            outDiagnostic.location.line = 0;
454        }
455
456        // Save the remaining text in 'postPath'
457        postPath = UnownedStringSlice(cur + colonIndex + 1, end);
458    }
459
460    // Split up the error section
461    UnownedStringSlice postError;
462    {
463        // tests/cpp-compiler/c-compile-link-error.exe : fatal error LNK1120: 1 unresolved externals
464
465        const Index errorColonIndex = postPath.indexOf(':');
466        if (errorColonIndex < 0)
467        {
468            return SLANG_FAIL;
469        }
470
471        const UnownedStringSlice errorSection =
472            UnownedStringSlice(postPath.begin(), postPath.begin() + errorColonIndex);
473        Index errorCodeIndex = errorSection.lastIndexOf(' ');
474        if (errorCodeIndex < 0)
475        {
476            return SLANG_FAIL;
477        }
478
479        // Extract the code
480        outDiagnostic.code =
481            allocator.allocate(errorSection.begin() + errorCodeIndex + 1, errorSection.end());
482        if (asStringSlice(outDiagnostic.code).startsWith(UnownedStringSlice::fromLiteral("LNK")))
483        {
484            outDiagnostic.stage = Diagnostic::Stage::Link;
485        }
486
487        // Extract the bit before the code
488        SLANG_RETURN_ON_FAIL(_parseSeverity(
489            UnownedStringSlice(errorSection.begin(), errorSection.begin() + errorCodeIndex).trim(),
490            outDiagnostic.severity));
491
492        // Link codes start with LNK prefix
493        postError = UnownedStringSlice(postPath.begin() + errorColonIndex + 1, end);
494    }
495
496    outDiagnostic.text = allocator.allocate(postError);
497
498    return SLANG_OK;
499}
500
501/* static */ SlangResult VisualStudioCompilerUtil::parseOutput(
502    const ExecuteResult& exeRes,
503    IArtifactDiagnostics* diagnostics)
504{
505    diagnostics->reset();
506
507    diagnostics->setRaw(SliceUtil::asTerminatedCharSlice(exeRes.standardOutput));
508
509    SliceAllocator allocator;
510
511    for (auto line : LineParser(exeRes.standardOutput.getUnownedSlice()))
512    {
513#if 0
514        fwrite(line.begin(), 1, line.size(), stdout);
515        fprintf(stdout, "\n");
516#endif
517
518        ArtifactDiagnostic diagnostic;
519        if (SLANG_SUCCEEDED(_parseVisualStudioLine(allocator, line, diagnostic)))
520        {
521            diagnostics->add(diagnostic);
522        }
523    }
524
525    // if it has a compilation error.. set on output
526    if (diagnostics->hasOfAtLeastSeverity(ArtifactDiagnostic::Severity::Error))
527    {
528        diagnostics->setResult(SLANG_FAIL);
529    }
530
531    return SLANG_OK;
532}
533
534/* static */ SlangResult VisualStudioCompilerUtil::locateCompilers(
535    const String& path,
536    ISlangSharedLibraryLoader* loader,
537    [[maybe_unused]] DownstreamCompilerSet* set)
538{
539    SLANG_UNUSED(loader);
540
541    // TODO(JS): We don't support fixed path for visual studio just yet
542    if (path.getLength() == 0)
543    {
544#if SLANG_VC
545        return WinVisualStudioUtil::find(set);
546#endif
547    }
548
549    return SLANG_OK;
550}
551
552} // namespace Slang