Coverage Report

Created: 2026-06-05 03:56

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/Users/runner/work/slang/slang/source/compiler-core/slang-fxc-compiler.cpp
Line
Count
Source
1
// slang-fxc-compiler.cpp
2
#include "slang-fxc-compiler.h"
3
4
#if SLANG_ENABLE_DXBC_SUPPORT
5
6
#include "../core/slang-blob.h"
7
#include "../core/slang-char-util.h"
8
#include "../core/slang-common.h"
9
#include "../core/slang-io.h"
10
#include "../core/slang-semantic-version.h"
11
#include "../core/slang-shared-library.h"
12
#include "../core/slang-string-slice-pool.h"
13
#include "../core/slang-string-util.h"
14
#include "slang-artifact-associated-impl.h"
15
#include "slang-artifact-desc-util.h"
16
#include "slang-artifact-diagnostic-util.h"
17
#include "slang-com-helper.h"
18
#include "slang-include-system.h"
19
#include "slang-source-loc.h"
20
21
// Enable calling through to `fxc` or `dxc` to
22
// generate code on Windows.
23
#ifdef _WIN32
24
#include <d3dcompiler.h>
25
#include <windows.h>
26
#endif
27
28
// Some of the `D3DCOMPILE_*` constants aren't available in all
29
// versions of `d3dcompiler.h`, so we define them here just in case
30
#ifndef D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES
31
#define D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES (1 << 20)
32
#endif
33
34
#ifndef D3DCOMPILE_ALL_RESOURCES_BOUND
35
#define D3DCOMPILE_ALL_RESOURCES_BOUND (1 << 21)
36
#endif
37
38
#endif // SLANG_ENABLE_DXBC_SUPPORT
39
40
namespace Slang
41
{
42
43
#if SLANG_ENABLE_DXBC_SUPPORT
44
45
static UnownedStringSlice _getSlice(ID3DBlob* blob)
46
{
47
    return StringUtil::getSlice((ISlangBlob*)blob);
48
}
49
50
struct FxcIncludeHandler : ID3DInclude
51
{
52
53
    STDMETHOD(Open)
54
    (D3D_INCLUDE_TYPE includeType,
55
     LPCSTR fileName,
56
     LPCVOID parentData,
57
     LPCVOID* outData,
58
     UINT* outSize) override
59
    {
60
        SLANG_UNUSED(includeType);
61
        // NOTE! The pParentData means the *text* of any previous include.
62
        // In order to work out what *path* that came from, we need to seach which source file it
63
        // came from, and use it's path
64
65
        // Assume the root pathInfo initially
66
        PathInfo includedFromPathInfo = m_rootPathInfo;
67
68
        // Lets try and find the parent source if there is any
69
        if (parentData)
70
        {
71
            SourceFile* foundSourceFile =
72
                m_system.getSourceManager()->findSourceFileByContentRecursively(
73
                    (const char*)parentData);
74
            if (foundSourceFile)
75
            {
76
                includedFromPathInfo = foundSourceFile->getPathInfo();
77
            }
78
        }
79
80
        String path(fileName);
81
        PathInfo pathInfo;
82
        ComPtr<ISlangBlob> blob;
83
84
        SLANG_RETURN_ON_FAIL(
85
            m_system.findAndLoadFile(path, includedFromPathInfo.foundPath, pathInfo, blob));
86
87
        // Return the data
88
        *outData = blob->getBufferPointer();
89
        *outSize = (UINT)blob->getBufferSize();
90
91
        return S_OK;
92
    }
93
94
    STDMETHOD(Close)(LPCVOID pData) override
95
    {
96
        SLANG_UNUSED(pData);
97
        return S_OK;
98
    }
99
    FxcIncludeHandler(
100
        SearchDirectoryList* searchDirectories,
101
        ISlangFileSystemExt* fileSystemExt,
102
        SourceManager* sourceManager)
103
        : m_system(searchDirectories, fileSystemExt, sourceManager)
104
    {
105
    }
106
107
    PathInfo m_rootPathInfo;
108
    IncludeSystem m_system;
109
};
110
111
class FXCDownstreamCompiler : public DownstreamCompilerBase
112
{
113
public:
114
    typedef DownstreamCompilerBase Super;
115
116
    // IDownstreamCompiler
117
    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
118
    compile(const CompileOptions& options, IArtifact** outArtifact) SLANG_OVERRIDE;
119
    virtual SLANG_NO_THROW bool SLANG_MCALL
120
    canConvert(const ArtifactDesc& from, const ArtifactDesc& to) SLANG_OVERRIDE;
121
    virtual SLANG_NO_THROW SlangResult SLANG_MCALL
122
    convert(IArtifact* from, const ArtifactDesc& to, IArtifact** outArtifact) SLANG_OVERRIDE;
123
    virtual SLANG_NO_THROW bool SLANG_MCALL isFileBased() SLANG_OVERRIDE { return false; }
124
125
    /// Must be called before use
126
    SlangResult init(ISlangSharedLibrary* library);
127
128
    FXCDownstreamCompiler() {}
129
130
protected:
131
    pD3DCompile m_compile = nullptr;
132
    pD3DDisassemble m_disassemble = nullptr;
133
134
    ComPtr<ISlangSharedLibrary> m_sharedLibrary;
135
};
136
137
SlangResult FXCDownstreamCompiler::init(ISlangSharedLibrary* library)
138
{
139
    m_compile = (pD3DCompile)library->findFuncByName("D3DCompile");
140
    m_disassemble = (pD3DDisassemble)library->findFuncByName("D3DDisassemble");
141
142
    if (!m_compile || !m_disassemble)
143
    {
144
        return SLANG_FAIL;
145
    }
146
147
    m_sharedLibrary = library;
148
149
    // It's not clear how to query for a version, but we can get a version number from the header
150
    m_desc = Desc(SLANG_PASS_THROUGH_FXC, D3D_COMPILER_VERSION);
151
152
    return SLANG_OK;
153
}
154
155
static SlangResult _parseDiagnosticLine(
156
    SliceAllocator& allocator,
157
    const UnownedStringSlice& line,
158
    List<UnownedStringSlice>& lineSlices,
159
    ArtifactDiagnostic& outDiagnostic)
160
{
161
    /* tests/diagnostics/syntax-error-intrinsic.slang(14,2): error X3000: syntax error: unexpected
162
     * token '@' */
163
    if (lineSlices.getCount() < 3)
164
    {
165
        return SLANG_FAIL;
166
    }
167
168
    SLANG_RETURN_ON_FAIL(
169
        ArtifactDiagnosticUtil::splitPathLocation(allocator, lineSlices[0], outDiagnostic));
170
171
    {
172
        const UnownedStringSlice severityAndCodeSlice = lineSlices[1].trim();
173
        const UnownedStringSlice severitySlice =
174
            StringUtil::getAtInSplit(severityAndCodeSlice, ' ', 0);
175
176
        outDiagnostic.code =
177
            allocator.allocate(StringUtil::getAtInSplit(severityAndCodeSlice, ' ', 1));
178
179
        outDiagnostic.severity = ArtifactDiagnostic::Severity::Error;
180
        if (severitySlice == "warning")
181
        {
182
            outDiagnostic.severity = ArtifactDiagnostic::Severity::Warning;
183
        }
184
    }
185
186
    outDiagnostic.text = allocator.allocate(lineSlices[2].begin(), line.end());
187
    return SLANG_OK;
188
}
189
190
SlangResult FXCDownstreamCompiler::compile(const CompileOptions& inOptions, IArtifact** outArtifact)
191
{
192
    if (!isVersionCompatible(inOptions))
193
    {
194
        // Not possible to compile with this version of the interface.
195
        return SLANG_E_NOT_IMPLEMENTED;
196
    }
197
198
    CompileOptions options = getCompatibleVersion(&inOptions);
199
200
    // This compiler can only deal with a single source artifact
201
    if (options.sourceArtifacts.count != 1)
202
    {
203
        return SLANG_FAIL;
204
    }
205
206
    IArtifact* sourceArtifact = options.sourceArtifacts[0];
207
208
    if (options.sourceLanguage != SLANG_SOURCE_LANGUAGE_HLSL || options.targetType != SLANG_DXBC)
209
    {
210
        SLANG_ASSERT(!"Can only compile HLSL to DXBC");
211
        return SLANG_FAIL;
212
    }
213
214
    // If we have been invoked in a pass-through mode, then we need to make sure
215
    // that the downstream compiler sees whatever options were passed to Slang
216
    // via the command line or API.
217
    //
218
    // TODO: more pieces of information should be added here as needed.
219
    //
220
221
    SearchDirectoryList searchDirectories;
222
    for (const auto& includePath : options.includePaths)
223
    {
224
        searchDirectories.searchDirectories.add(asString(includePath));
225
    }
226
227
    const auto sourcePath = ArtifactUtil::findPath(sourceArtifact);
228
229
    // Use the default fileSystemExt is not set
230
    ID3DInclude* includeHandler = nullptr;
231
232
    FxcIncludeHandler fxcIncludeHandlerStorage(
233
        &searchDirectories,
234
        options.fileSystemExt,
235
        options.sourceManager);
236
    if (options.fileSystemExt)
237
    {
238
239
        if (sourcePath.getLength() > 0)
240
        {
241
            fxcIncludeHandlerStorage.m_rootPathInfo = PathInfo::makePath(sourcePath);
242
        }
243
        includeHandler = &fxcIncludeHandlerStorage;
244
    }
245
246
    List<D3D_SHADER_MACRO> dxMacrosStorage;
247
    D3D_SHADER_MACRO const* dxMacros = nullptr;
248
249
    if (options.defines.count > 0)
250
    {
251
        for (const auto& define : options.defines)
252
        {
253
            D3D_SHADER_MACRO dxMacro;
254
            dxMacro.Name = define.nameWithSig;
255
            dxMacro.Definition = define.value;
256
            dxMacrosStorage.add(dxMacro);
257
        }
258
        D3D_SHADER_MACRO nullTerminator = {0, 0};
259
        dxMacrosStorage.add(nullTerminator);
260
261
        dxMacros = dxMacrosStorage.getBuffer();
262
    }
263
264
    DWORD flags = 0;
265
266
    switch (options.floatingPointMode)
267
    {
268
    default:
269
        break;
270
271
    case FloatingPointMode::Precise:
272
        flags |= D3DCOMPILE_IEEE_STRICTNESS;
273
        break;
274
    }
275
276
    flags |= D3DCOMPILE_ENABLE_STRICTNESS;
277
    flags |= D3DCOMPILE_ENABLE_UNBOUNDED_DESCRIPTOR_TABLES;
278
279
    switch (options.optimizationLevel)
280
    {
281
    default:
282
        break;
283
284
    case OptimizationLevel::None:
285
        flags |= D3DCOMPILE_OPTIMIZATION_LEVEL0;
286
        break;
287
    case OptimizationLevel::Default:
288
        flags |= D3DCOMPILE_OPTIMIZATION_LEVEL1;
289
        break;
290
    case OptimizationLevel::High:
291
        flags |= D3DCOMPILE_OPTIMIZATION_LEVEL2;
292
        break;
293
    case OptimizationLevel::Maximal:
294
        flags |= D3DCOMPILE_OPTIMIZATION_LEVEL3;
295
        break;
296
    }
297
298
    switch (options.debugInfoType)
299
    {
300
    case DebugInfoType::None:
301
        break;
302
303
    // FXC has limited debug control options - it only supports a binary on/off for debug info
304
    // This covers DebugInfoType::Minimal, DebugInfoType::Standard
305
    default:
306
        flags |= D3DCOMPILE_DEBUG;
307
        // If optimization is disabled, FXC will generate much more usable debug information with
308
        // D3DCOMPILE_SKIP_OPTIMIZATION than simply D3DCOMPILE_OPTIMIZATION_LEVEL0. So if
309
        // debugInfoType is DebugInfoType::Maximal and optimization is OptimizationLevel::None,
310
        // disable optimizations completely.
311
        if (options.debugInfoType == DebugInfoType::Maximal &&
312
            options.optimizationLevel == OptimizationLevel::None)
313
        {
314
            flags |= D3DCOMPILE_SKIP_OPTIMIZATION;
315
        }
316
        break;
317
    }
318
319
    ComPtr<ISlangBlob> sourceBlob;
320
    SLANG_RETURN_ON_FAIL(sourceArtifact->loadBlob(ArtifactKeep::Yes, sourceBlob.writeRef()));
321
322
    ComPtr<ID3DBlob> codeBlob;
323
    ComPtr<ID3DBlob> diagnosticsBlob;
324
    HRESULT hr = m_compile(
325
        sourceBlob->getBufferPointer(),
326
        sourceBlob->getBufferSize(),
327
        String(sourcePath).getBuffer(),
328
        dxMacros,
329
        includeHandler,
330
        options.entryPointName,
331
        options.profileName,
332
        flags,
333
        0, // unused: effect flags
334
        codeBlob.writeRef(),
335
        diagnosticsBlob.writeRef());
336
337
    auto diagnostics = ArtifactDiagnostics::create();
338
339
    // HRESULT is compatible with SlangResult
340
    diagnostics->setResult(hr);
341
342
    SliceAllocator allocator;
343
344
    if (diagnosticsBlob)
345
    {
346
        UnownedStringSlice diagnosticText = _getSlice(diagnosticsBlob);
347
        diagnostics->setRaw(asCharSlice(diagnosticText));
348
349
        SlangResult diagnosticParseRes = ArtifactDiagnosticUtil::parseColonDelimitedDiagnostics(
350
            allocator,
351
            diagnosticText,
352
            0,
353
            _parseDiagnosticLine,
354
            diagnostics);
355
        SLANG_UNUSED(diagnosticParseRes);
356
        SLANG_ASSERT(SLANG_SUCCEEDED(diagnosticParseRes));
357
    }
358
359
    // If FXC failed, make sure we have an error in the diagnostics
360
    if (FAILED(hr))
361
    {
362
        diagnostics->requireErrorDiagnostic();
363
    }
364
365
    auto artifact = ArtifactUtil::createArtifactForCompileTarget(options.targetType);
366
367
    ArtifactUtil::addAssociated(artifact, diagnostics);
368
369
    if (codeBlob)
370
    {
371
        // ID3DBlob is compatible with ISlangBlob, so just cast away...
372
        artifact->addRepresentationUnknown((ISlangBlob*)codeBlob.get());
373
    }
374
375
    *outArtifact = artifact.detach();
376
    return SLANG_OK;
377
}
378
379
bool FXCDownstreamCompiler::canConvert(const ArtifactDesc& from, const ArtifactDesc& to)
380
{
381
    // Can only disassemble blobs that are DXBC
382
    return ArtifactDescUtil::isDisassembly(from, to) && from.payload == ArtifactPayload::DXBC;
383
}
384
385
SlangResult FXCDownstreamCompiler::convert(
386
    IArtifact* from,
387
    const ArtifactDesc& to,
388
    IArtifact** outArtifact)
389
{
390
    if (!canConvert(from->getDesc(), to))
391
    {
392
        return SLANG_FAIL;
393
    }
394
395
    ComPtr<ISlangBlob> dxbcBlob;
396
    SLANG_RETURN_ON_FAIL(from->loadBlob(ArtifactKeep::No, dxbcBlob.writeRef()));
397
398
    ComPtr<ID3DBlob> disassemblyBlob;
399
    SLANG_RETURN_ON_FAIL(m_disassemble(
400
        dxbcBlob->getBufferPointer(),
401
        dxbcBlob->getBufferSize(),
402
        0,
403
        nullptr,
404
        disassemblyBlob.writeRef()));
405
406
    auto artifact = ArtifactUtil::createArtifact(to);
407
    // ISlangBlob is compatible with ID3DBlob
408
    artifact->addRepresentationUnknown((ISlangBlob*)disassemblyBlob.get());
409
410
    *outArtifact = artifact.detach();
411
    return SLANG_OK;
412
}
413
414
/* static */ SlangResult FXCDownstreamCompilerUtil::locateCompilers(
415
    const String& path,
416
    ISlangSharedLibraryLoader* loader,
417
    DownstreamCompilerSet* set)
418
{
419
    ComPtr<ISlangSharedLibrary> library;
420
421
    const char* const libName = "d3dcompiler_47";
422
    SLANG_RETURN_ON_FAIL(
423
        DownstreamCompilerUtil::loadSharedLibrary(path, loader, nullptr, libName, library));
424
425
    SLANG_ASSERT(library);
426
    if (!library)
427
    {
428
        return SLANG_FAIL;
429
    }
430
431
    auto compiler = new FXCDownstreamCompiler;
432
    ComPtr<IDownstreamCompiler> compilerInft(compiler);
433
434
    SLANG_RETURN_ON_FAIL(compiler->init(library));
435
436
    set->addCompiler(compilerInft);
437
    return SLANG_OK;
438
}
439
440
#else // SLANG_ENABLE_DXBC_SUPPORT
441
442
/* static */ SlangResult FXCDownstreamCompilerUtil::locateCompilers(
443
    const String& path,
444
    ISlangSharedLibraryLoader* loader,
445
    DownstreamCompilerSet* set)
446
10
{
447
10
    SLANG_UNUSED(path);
448
10
    SLANG_UNUSED(loader);
449
10
    SLANG_UNUSED(set);
450
    return SLANG_E_NOT_AVAILABLE;
451
10
}
452
453
#endif // else SLANG_ENABLE_DXBC_SUPPORT
454
455
} // namespace Slang