summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-ir-entry-point-uniforms.cpp
blob: e75be9e24a28e25095389618717eedee3a7e9524 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
// slang-ir-entry-point-uniforms.cpp
#include "slang-ir-entry-point-uniforms.h"

#include "slang-ir-entry-point-pass.h"
#include "slang-ir-insts.h"
#include "slang-ir-util.h"
#include "slang-ir.h"
#include "slang-mangle.h"

namespace Slang
{


// The transformations in this file will solve the problem of taking
// code like the following:
//
//      float4 fragmentMain(
//          uniform Texture2D    t,
//          uniform SamplerState s;
//          uniform float4       c,
//                  float2       uv : UV) : SV_Target
//      {
//          return t.Sample(s, uv) + c;
//      }
//
// and transforming into code like this:
//
//      struct Params
//      {
//          Texture2D    t;
//          SamplerState s;
//          float4       c;
//      }
//      ConstantBuffer<Params> params;
//
//      float4 fragmentMain(
//          float2 uv : UV) : SV_Target
//      {
//          return params.t.Sample(params.s, uv) + params.c;
//      }
//
// As can be seen in this example, the `uniform` parameters
// declared as entry point parameters have been moved into
// a `struct` declaration that we then use to declare a global
// shader parameter that is a `ConstantBuffer`. We then
// rewrite references to those parameters to refer to the
// contents of the new constant buffer instead.
//
// We perform this transformation after the target-specific
// linking step, because that will have attached layout information
// to the entry point and its parameters. We need that layout
// information so that we can:
//
// * Identify which parameters are uniform vs. varying.
// * Have an appropriate layout to attached to the synthesized
//   global shader parameter `params`.
//
// One additional wrinkle this pass has to deal with is that
// in the case where the shader doesn't have any "ordinary"
// uniform parameters like `c` (e.g., it only has resource/object
// parameters), we do *not* wrap the parameter `struct` in
// a `ConstantBuffer`. For example, suppose we have:
//
//      float4 fragmentMain(
//          uniform Texture2D    t,
//          uniform SamplerState s;
//                  float2       uv : UV) : SV_Target
//      {
//          return t.Sample(s, uv);
//      }
//
// In this case the output of the transformation should be:
//
//      struct Params
//      {
//          Texture2D    t;
//          SamplerState s;
//      }
//      Params params;
//
//      float4 fragmentMain(
//          float2 uv : UV) : SV_Target
//      {
//          return params.t.Sample(params.s, uv) + params.c;
//      }
//
// Note that this pass should always come before type legalization,
// which will take responsibility for turning a variable like
// `params` above into individual variables for the `t` and
// `s` fields.

// For clarity and flexibility, the work is split across two
// different IR passes:
//
// * The first pass simply collects together uniform parameters
//   into a single parameter of `struct` or `ConstantBuffer<...>` type.
//
// * The second pass transforms entry-point uniform parameters
//   into global shader parameters.

// First we start with some helper subroutines for detecting
// whether a parameter represents a varying input rather than
// a uniform parameter.

// Setup some flags that will be used below to check for varying
// resource kinds. Ordinary varying input/output + Ray-tracing shader
// input/output kinds will be considered varying.
//
// Note: The set of cases that are considered
// varying here would need to be extended if we
// add more fine-grained resource kinds (e.g.,
// if we ever add an explicit resource kind
// for geometry shader output streams).
static const LayoutResourceKindFlags flags =
    LayoutResourceKindFlag::make(LayoutResourceKind::VaryingInput) |
    LayoutResourceKindFlag::make(LayoutResourceKind::VaryingOutput) |
    LayoutResourceKindFlag::make(LayoutResourceKind::CallablePayload) |
    LayoutResourceKindFlag::make(LayoutResourceKind::HitAttributes) |
    LayoutResourceKindFlag::make(LayoutResourceKind::RayPayload);

bool isVaryingParameter(IRTypeLayout* typeLayout)
{
    if (!typeLayout)
        return false;

    // If we're looking at a struct type layout, we need to check each of the fields.
    // If any of the fields is not a varying resource kind, then we consider the
    // whole struct to be uniform (and thus not varying).
    if (auto structTypeLayout = as<IRStructTypeLayout>(typeLayout))
    {
        for (auto fieldAttr : structTypeLayout->getFieldLayoutAttrs())
        {
            if (!isVaryingParameter(fieldAttr->getLayout()))
                return false;
        }
        // If we made it here, all struct fields were varying.
        return true;
    }

    // If we're looking at a parameter group type layout, we should return false
    // as these should always be associtated with LayoutResourceKind::Uniform.
    else if (as<IRParameterGroupTypeLayout>(typeLayout))
    {
        return false;
    }

    // If we're looking at an array type layout, we need to check the element's
    // type layout.
    else if (auto arrayTypeLayout = as<IRArrayTypeLayout>(typeLayout))
    {
        return isVaryingParameter(arrayTypeLayout->getElementTypeLayout());
    }

    // If we didn't match a type layout above, try finding a kind from sizeAttrs.
    else
    {
        // If all the element type's sizeAttrs have a varying kind matching our
        // flags above, return true.
        for (auto sizeAttr : typeLayout->getSizeAttrs())
        {
            if ((flags & LayoutResourceKindFlag::make(sizeAttr->getResourceKind())) == 0)
                return false;
        }
        return true;
    }
}

bool isVaryingParameter(IRVarLayout* varLayout)
{
    // If *any* of the resources consumed by the parameter type
    // is *not* a varying resource kind, then we consider the
    // whole parameter to be uniform (and thus not varying).
    //
    // Note that this means that some empty types will always
    // be considered varying, even if it had been explicitly
    // marked `uniform`.
    //
    // Note that this logic rules out support for parameters
    // that mix varying and non-varying resource kinds.
    //
    // TODO: This whole convoluted definition exists because
    // we currently don't give system-value parameters any
    // reosurce kind, so they show up as empty. Simply
    // adding `LayoutResourceKind`s for system-value inputs
    // and outputs would allow for simpler logic here.

    if (!varLayout)
        return false;

    // System-value parameters currently do not have kinds setup.
    // As a WAR, if we see a system-value attr just assume varying
    // for now.
    if (varLayout->findAttr<IRSystemValueSemanticAttr>())
    {
        return true;
    }

    if (varLayout->usesResourceFromKinds(flags))
    {
        return true;
    }

    // If the base cases above failed, we need to check if we are dealing with
    // an IRVarLayout that could have "nested" IRVarLayouts, such as an IRStructTypeLayout.
    return isVaryingParameter(varLayout->getTypeLayout());
}

struct CollectEntryPointUniformParams : PerEntryPointPass
{
    CollectEntryPointUniformParamsOptions m_options;

    // *If* the entry point has any uniform parameter then we want to create a
    // structure type to house them, and a single collected shader parameter (either
    // an instance of that type or a constant buffer).
    //
    // We only want to create these if actually needed, so we will declare
    // them here and then initialize them on-demand.
    //
    IRStructType* paramStructType = nullptr;
    IRParam* collectedParam = nullptr;

    bool needConstantBuffer = false;

    void processEntryPointImpl(EntryPointInfo const& info) SLANG_OVERRIDE
    {
        auto entryPointFunc = info.func;

        // This pass object may be used across multiple entry points,
        // so we need to make sure to reset state that could have been
        // left over from a previous entry point.
        //
        paramStructType = nullptr;
        collectedParam = nullptr;

        // We expect all entry points to have explicit layout information attached.
        //
        // We will assert that we have the information we need, but try to be
        // defensive and bail out in the failure case in release builds.
        //
        auto funcLayoutDecoration = entryPointFunc->findDecoration<IRLayoutDecoration>();

        // If the module contains two functions with entrypoint decorations,
        // and one entrypoint calls the other entrypoint, and the user
        // tells us to compile the caller entrypoint but not the callee
        // entrypoint, we will not have the layout decoration created for
        // the callee entrypoint. In this case, we should simply treat the
        // callee entrypoint as if it is an ordinary function and skip the
        // rest of the logic here.
        if (!funcLayoutDecoration)
            return;

        auto entryPointLayout = as<IREntryPointLayout>(funcLayoutDecoration->getLayout());
        SLANG_ASSERT(entryPointLayout);
        if (!entryPointLayout)
            return;

        // The parameter layout for an entry point will either be a structure
        // type layout, or a constant buffer (a case of parameter group)
        // wrapped around such a structure.
        //
        // If we are in the latter case we will need to make sure to allocate
        // an explicit IR constant buffer for that wrapper,
        //
        auto entryPointParamsLayout = entryPointLayout->getParamsLayout();
        needConstantBuffer =
            as<IRParameterGroupTypeLayout>(entryPointParamsLayout->getTypeLayout()) != nullptr;

        auto entryPointParamsStructLayout = getScopeStructLayout(entryPointLayout);

        // We will set up an IR builder so that we are ready to generate code.
        //
        IRBuilder builderStorage(m_module);
        auto builder = &builderStorage;

        if (m_options.alwaysCreateCollectedParam)
            ensureCollectedParamAndTypeHaveBeenCreated();

        IRStructTypeLayout::Builder structLayoutBuilder(builder);

        // We will be removing any uniform parameters we run into, so we
        // need to iterate the parameter list carefully to deal with
        // us modifying it along the way.
        //
        IRParam* nextParam = nullptr;
        UInt paramCounter = 0;
        HashSet<LayoutResourceKind> resourceKinds;
        for (IRParam* param = entryPointFunc->getFirstParam(); param; param = nextParam)
        {
            nextParam = param->getNextParam();
            UInt paramIndex = paramCounter++;

            // We expect all entry-point parameters to have layout information,
            // but we will be defensive and skip parameters without the required
            // information when we are in a release build.
            //
            auto layoutDecoration = param->findDecoration<IRLayoutDecoration>();
            SLANG_ASSERT(layoutDecoration);
            if (!layoutDecoration)
                continue;
            auto paramLayout = as<IRVarLayout>(layoutDecoration->getLayout());
            SLANG_ASSERT(paramLayout);
            if (!paramLayout)
                continue;

            // A parameter that has varying input/output behavior should be left alone,
            // since this pass is only supposed to apply to uniform (non-varying)
            // parameters.
            //
            if (isVaryingParameter(paramLayout))
                continue;

            for (auto offsetAttr : paramLayout->getOffsetAttrs())
                resourceKinds.add(offsetAttr->getResourceKind());

            // At this point we know that `param` is not a varying shader parameter,
            // so that we want to turn it into an equivalent global shader parameter.
            //
            // If this is the first parameter we are running into, then we need
            // to deal with creating the structure type and global shader
            // parameter that our transformed entry point will use.
            //
            ensureCollectedParamAndTypeHaveBeenCreated();

            // Now that we've ensured the global `struct` type and collected shader paramter
            // exist, we need to add a field to the `struct` to represent the
            // current parameter.
            //

            auto paramType = param->getFullType();

            builder->setInsertBefore(paramStructType);

            // We need to know the "key" that should be used for the parameter,
            // so we will read it off of the entry-point layout information.
            //
            // TODO: Maybe we should associate the key to the parameter via
            // a decoration to avoid this indirection?
            //
            // TODO: Alternatively, we should make this pass responsible for
            // dealing with the transfer of layout information from the entry
            // point to its parameters, rather than baking that behavior into
            // the linker. After all, this pass is traversing the same information
            // anyway, so it could do the work while it is here...
            //
            auto paramFieldKey = cast<IRStructKey>(
                entryPointParamsStructLayout->getFieldLayoutAttrs()[paramIndex]->getFieldKey());
            structLayoutBuilder.addField(
                paramFieldKey,
                entryPointParamsStructLayout->getFieldLayout(paramIndex));

            auto paramField = builder->createStructField(paramStructType, paramFieldKey, paramType);
            SLANG_UNUSED(paramField);

            // We will transfer all decorations on the parameter over to the key
            // so that they can affect downstream emit logic.
            //
            // TODO: We should double-check whether any of the decorations should
            // be moved to the *field* instead.
            //
            param->transferDecorationsTo(paramFieldKey);

            // At this point we want to eliminate the original entry point
            // parameter, in favor of the `struct` field we declared.
            // That required replacing any uses of the parameter with
            // appropriate code to pull out the field.
            //
            // We *could* extract the field at the start of the shader
            // and then do a `replaceAllUsesWith` to propragate it
            // down, but in practice we expect that it is better for
            // performance to "rematerialize" the value of a shader
            // parameter as close to where it is used as possible.
            //
            // We are therefore going to replace the uses one at a time.
            //
            while (auto use = param->firstUse)
            {
                // Given a `use` of the paramter, we will insert
                // the replacement code right before the instruction
                // that is doing the using.
                //
                builder->setInsertBefore(use->getUser());

                // The way to extract the field that corresponds
                // to the parameter depends on whether or not
                // we generated a constant buffer.
                //
                IRInst* fieldVal = nullptr;
                if (needConstantBuffer)
                {
                    // A constant buffer behaves like a pointer
                    // at the IR level, so we first do a pointer
                    // offset operation to compute what amounts
                    // to `&cb->field`, and then load from that address.
                    //
                    auto fieldAddress = builder->emitFieldAddress(
                        builder->getPtrType(paramType),
                        collectedParam,
                        paramFieldKey);
                    fieldVal = builder->emitLoad(fieldAddress);
                }
                else
                {
                    // In the ordinary struct case, the parameter
                    // has an ordinary `struct` type (not a pointer),
                    // so we just extract the field directly.
                    //
                    fieldVal = builder->emitFieldExtract(paramType, collectedParam, paramFieldKey);
                }

                // We replace the value used at this use site, which
                // will have a side effect of making `use` no longer
                // be on the list of uses for `param`, so that when
                // we get back to the top of the loop the list of
                // uses will be shorter.
                //
                use->set(fieldVal);
            }

            // Once we've replaced all the uses of `param`, we
            // can go ahead and remove it completely.
            //
            param->removeAndDeallocate();
        }

        // Filter unrelated offset attrs from entryPointParamsLayout.
        // Since entryPointParamsLayout will now be used as the varLayout for the newly created
        // struct typed uniform parameter, and we need to ensure it only contains uniform parameter
        // offsets, not varying ones, so isVaryingParameter() can correctly identify the newly
        // created struct param as a uniform param.

        if (collectedParam)
        {
            collectedParam->insertBefore(entryPointFunc->getFirstBlock()->getFirstChild());
            IRTypeLayout* entryPointUniformsTypeLayout = nullptr;

            if (auto originalParamGroupLayout =
                    as<IRParameterGroupTypeLayout>(entryPointParamsLayout->getTypeLayout()))
            {
                // If the original entry point layout is a parameter gorup layout,
                // the new layout of the newly created `entryPointParams` param should also
                // be a parameter group layout, but with unrelated offsets (e.g. varying offsets)
                // stripped off.
                // We will now create this layout inst.
                //
                // Register any existing ResourceKinds on the container var layout as "related".
                for (auto offsetAttr :
                     originalParamGroupLayout->getContainerVarLayout()->getOffsetAttrs())
                {
                    resourceKinds.add(offsetAttr->getResourceKind());
                }
                // Create the struct layout for parameters that goes into the `EntryPointParams`
                // struct.
                auto entryPointUniformStructTypeLayout = structLayoutBuilder.build();
                auto originalElementVarLayout = originalParamGroupLayout->getElementVarLayout();
                IRVarLayout::Builder elementVarLayoutBuilder(
                    builder,
                    entryPointUniformStructTypeLayout);
                elementVarLayoutBuilder.cloneEverythingButOffsetsFrom(originalElementVarLayout);
                // We assume everything in "pendingLayout" will appear as uniform parameters at
                // the moment. This means that we can just copy them as is through the pass right
                // now.
                elementVarLayoutBuilder.setPendingVarLayout(
                    originalElementVarLayout->getPendingVarLayout());

                IRParameterGroupTypeLayout::Builder paramGroupTypeLayoutBuilder(builder);
                // Filter offsets for the `elementVarLayout` part of the new parameter group layout.
                for (auto resKind : resourceKinds)
                {
                    auto originalOffset = originalElementVarLayout->findOffsetAttr(resKind);
                    if (!originalOffset)
                        continue;
                    auto resInfo = elementVarLayoutBuilder.findOrAddResourceInfo(resKind);
                    resInfo->offset = originalOffset->getOffset();
                    resInfo->space = originalOffset->getSpace();
                }
                for (auto resKind : resourceKinds)
                {
                    if (auto sizeAttr = originalParamGroupLayout->findSizeAttr(resKind))
                        paramGroupTypeLayoutBuilder.addResourceUsage(sizeAttr);
                }
                auto newElementVarLayout = elementVarLayoutBuilder.build();
                // The "containerVarLayout" part should remain unchanged from the original layout.
                // Because this is where we store the offset of the default constant buffer itself.
                paramGroupTypeLayoutBuilder.setContainerVarLayout(
                    originalParamGroupLayout->getContainerVarLayout());
                paramGroupTypeLayoutBuilder.setPendingTypeLayout(
                    originalParamGroupLayout->getPendingDataTypeLayout());
                // The "elementVarLayout" part should be the new one we just created.
                paramGroupTypeLayoutBuilder.setElementVarLayout(newElementVarLayout);
                // The "offsetElementTypeLayout" part is just redundant convenient info that
                // can be calculated from `entryPointUniformStructTypeLayout` and
                // `newElementVarLayout`.
                paramGroupTypeLayoutBuilder.setOffsetElementTypeLayout(applyOffsetToTypeLayout(
                    builder,
                    entryPointUniformStructTypeLayout,
                    newElementVarLayout));
                // Now we have the new type layout for the `EntryPointParams` parameter.
                entryPointUniformsTypeLayout = paramGroupTypeLayoutBuilder.build();
            }
            else
            {
                // If the original entry point layout isn't a constant buffer, we will simply use
                // the new struct type layout as the entrypoint layout.
                entryPointUniformsTypeLayout = structLayoutBuilder.build();
            }

            // Now create the var layout for the new `entryPointParams` parameter.
            // This can be done by simply filtering out unrelated offset attributes from the
            // original var layout.
            IRVarLayout::Builder varLayoutBuilder(builder, entryPointUniformsTypeLayout);
            varLayoutBuilder.cloneEverythingButOffsetsFrom(entryPointParamsLayout);
            List<IRVarOffsetAttr*> filteredOffsetAttrs;
            for (auto offsetAttr : entryPointParamsLayout->getOffsetAttrs())
            {
                if (resourceKinds.contains(offsetAttr->getResourceKind()))
                {
                    filteredOffsetAttrs.add(offsetAttr);
                }
            }
            for (auto offset : filteredOffsetAttrs)
            {
                auto resInfo = varLayoutBuilder.findOrAddResourceInfo(offset->getResourceKind());
                resInfo->offset = offset->getOffset();
                resInfo->space = offset->getSpace();
            }
            varLayoutBuilder.setPendingVarLayout(entryPointParamsLayout->getPendingVarLayout());
            auto entryPointUniformsVarLayout = varLayoutBuilder.build();
            builder->addLayoutDecoration(collectedParam, entryPointUniformsVarLayout);
        }

        fixUpFuncType(entryPointFunc);
    }

    void ensureCollectedParamAndTypeHaveBeenCreated()
    {
        if (paramStructType)
            return;

        IRBuilder builder(m_module);

        // First we create the structure to hold the parameters.
        //
        builder.setInsertBefore(m_entryPoint.func);
        paramStructType = builder.createStructType();
        builder.addNameHintDecoration(
            paramStructType,
            UnownedTerminatedStringSlice("EntryPointParams"));
        builder.addBinaryInterfaceTypeDecoration(paramStructType);

        if (needConstantBuffer)
        {
            // If we need a constant buffer, then the global
            // shader parameter will be a `ConstantBuffer<paramStructType>`
            //
            IRType* layoutType = nullptr;

            if (m_options.targetReq->getOptionSet().getBoolOption(
                    CompilerOptionName::GLSLForceScalarLayout))
                layoutType = builder.getType(kIROp_ScalarBufferLayoutType);
            else if (m_options.targetReq->getOptionSet().getBoolOption(
                         CompilerOptionName::ForceCLayout))
                layoutType = builder.getType(kIROp_CBufferLayoutType);
            else if (isKhronosTarget(m_options.targetReq))
                layoutType = builder.getType(kIROp_Std430BufferLayoutType);
            else
                layoutType = builder.getType(kIROp_DefaultBufferLayoutType);
            auto constantBufferType = builder.getConstantBufferType(paramStructType, layoutType);
            collectedParam = builder.createParam(constantBufferType);
        }
        else
        {
            // Otherwise, the global shader parameter is just
            // an instance of `paramStructType`.
            //
            collectedParam = builder.createParam(paramStructType);
        }

        collectedParam->insertBefore(m_entryPoint.func);
        // We add a name hint to the global parameter so that it will
        // emit to more readable code when referenced.
        //
        builder.addNameHintDecoration(
            collectedParam,
            UnownedTerminatedStringSlice("entryPointParams"));
    }
};

struct MoveEntryPointUniformParametersToGlobalScope : PerEntryPointPass
{
    void processEntryPointImpl(EntryPointInfo const& info) SLANG_OVERRIDE
    {
        auto entryPointFunc = info.func;

        // We will set up an IR builder so that we are ready to generate code.
        //
        IRBuilder builderStorage(m_module);
        auto builder = &builderStorage;

        builder->setInsertBefore(entryPointFunc);

        // We will be removing any uniform parameters we run into, so we
        // need to iterate the parameter list carefully to deal with
        // us modifying it along the way.
        //
        IRParam* nextParam = nullptr;
        for (IRParam* param = entryPointFunc->getFirstParam(); param; param = nextParam)
        {
            nextParam = param->getNextParam();

            // We expect all entry-point parameters to have layout information,
            // but we will be defensive and skip parameters without the required
            // information when we are in a release build.
            //
            auto layoutDecoration = param->findDecoration<IRLayoutDecoration>();
            SLANG_ASSERT(layoutDecoration);
            if (!layoutDecoration)
                continue;
            auto paramLayout = as<IRVarLayout>(layoutDecoration->getLayout());
            SLANG_ASSERT(paramLayout);
            if (!paramLayout)
                continue;

            // A parameter that has varying input/output behavior should be left alone,
            // since this pass is only supposed to apply to uniform (non-varying)
            // parameters.
            //
            if (isVaryingParameter(paramLayout))
                continue;

            auto paramType = param->getFullType();

            builder->setInsertBefore(entryPointFunc);
            auto globalParam = builder->createGlobalParam(paramType);

            param->transferDecorationsTo(globalParam);

            // We also decorate the parameter for the entry-point parameters
            // so that we can find it again in downstream passes (like emit
            // for CPU/CUDA) that might want to treat entry-point parameters
            // different from other cases.
            //
            // We need a way to associate these per-entry-point parameters
            // more closely with the original entry point. The two current
            // methods are:
            //
            // 1. Don't move the new aggregate parameter to the global scope
            // on those targets, and instead keep it as a parameter of the
            // entry point. This is used for CPU/CUDA targets.
            //
            // 2. Use a decoration on the global param itself to point at the
            // entry point for its per-entry-point parameter data, without moving
            // the parameter to the global scope. This is used for Metal targets, as
            // Metal does not have global parameters at the global scope.
            //
            // Method (1) is not used because Metal contains shading language concepts
            // such as binding offets, similar to other shading language targets.
            // We want to reuse code from other shading language targets for Metal, hence
            // we move parameters to the global scope, and then move the parameters back to
            // the entry points that they originate from. The originating entry points are
            // tracked through this decoration.
            //
            builder->addEntryPointParamDecoration(globalParam, entryPointFunc);

            param->replaceUsesWith(globalParam);
            param->removeAndDeallocate();
        }

        fixUpFuncType(entryPointFunc);
    }
};

void collectEntryPointUniformParams(
    IRModule* module,
    CollectEntryPointUniformParamsOptions const& options)
{
    CollectEntryPointUniformParams context;
    context.m_options = options;
    context.processModule(module);
}

void moveEntryPointUniformParamsToGlobalScope(IRModule* module)
{
    MoveEntryPointUniformParametersToGlobalScope context;
    context.processModule(module);
}

} // namespace Slang