summaryrefslogtreecommitdiff
path: root/tests/diagnostics/command-line/option-missing-argument.slang.expected
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2018-01-16 15:38:36 -0500
committerYong He <yonghe@outlook.com>2018-01-16 15:38:36 -0500
commita74a5494b34e2b41a294042ab8b3e7bce115dcba (patch)
tree9895842d33893b9a233f58964cfa55b3974d6134 /tests/diagnostics/command-line/option-missing-argument.slang.expected
parent59691aeeb013c5bb7cdaa31a6fc572eebd8be610 (diff)
bug fixes to get falcor example shader code to compile.
1. prevent cyclic lookups when an interface inherits transitively from itself. 2. in `createGlobalGenericParamSubstitution`, create a default substitution for the base type declref before using it to lookup the witness table.
Diffstat (limited to 'tests/diagnostics/command-line/option-missing-argument.slang.expected')
0 files changed, 0 insertions, 0 deletions
id='n127' href='#n127'>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
// slang-ir-bind-existentials.cpp
#include "slang-ir-bind-existentials.h"

#include "slang-ir-insts.h"
#include "slang-ir.h"

namespace Slang
{

// The code that comes out of the linking step will have instructions added
// that indicate how parameters with existential (interface) types are supposed
// to be specialized to concrete types.
//
// If there are any global existential-type parameters there should be a
// `bindGlobalExistentialSlots(...)` instruction at module scope.
//
// For each entry point with entry-point existential parameters, there should
// be a `[bindExistentialSlots(...)]` decoration attached to the entry
// point itself.
//
// In each case, the operands of the instruction should be a sequence of
// pairs. The number of pairs should match the number of existential "slots"
// at global or entry-point scope. Each pair should comprise a type `T`
// to plug into the slot, and a witness table `w` for the conformance of
// `T` to the interface type in that slot.
//
// In the simplest case, if we have a global shader parameter of interface
// type:
//
//      IFoo p;
//
// Then this will lower to the IR as:
//
//      global_param p : IFoo;
//
// And if the user tries to specialize `p` to type `Bar`, and a witness
// table `bar_is_ifoo`, we've have:
//
//      bindGlobalExistentialSlots(Bar, bar_is_ifoo);
//
// The goal of this pass is to replace the parameter of interface type
// with one of concrete type:
//
//      global_param p_new : Bar;
//
// and replace any reference to the old `p` parameter with
// a `makeExistential(p_new, bar_is_ifoo)`. That preserves the
// fact that a reference to `p` is conceptually of type `IFoo`,
// but allows downstream optimization passes to start specializing
// code based on the concrete knowledge that the value "backing"
// the parameter is actaully of type `Bar`.

// As is typically for IR passes, we will encapsulate all the
// logic in a `struct` type.
//
struct BindExistentialSlots
{
    IRModule* module = nullptr;
    DiagnosticSink* sink = nullptr;

    void processModule()
    {
        // We will start by dealing with the global existential slots.
        processGlobalExistentialSlots();

        // Then we will process the per-entry-point existential slots.
        processEntryPointExistentialSlots();
    }

    void processGlobalExistentialSlots()
    {
        // We will search for global shader parameters that make
        // use of existential specialization parameters.
        //
        for (auto inst : module->getGlobalInsts())
        {
            // We only care about global shader parameters.
            //
            auto globalParam = as<IRGlobalParam>(inst);
            if (!globalParam)
                continue;

            // We only care about global shader parameters
            // that have existential specialization parameters,
            // and we expect all such parameters to have a
            // `[bindExistentialSlots(...)]` decoration that
            // was added during IR linking.
            //
            auto bindSlotsInst =
                globalParam->findDecorationImpl(kIROp_BindExistentialSlotsDecoration);
            if (!bindSlotsInst)
                continue;

            replaceTypeUsingExistentialSlots(
                globalParam,
                bindSlotsInst->getOperandCount(),
                bindSlotsInst->getOperands());

            // Once we have propagated the information from
            // the `[bindExistentialSlots(...)]` decoration
            // down into the parameter's type, we no longer
            // need the decoration.
            //
            bindSlotsInst->removeAndDeallocate();
        }
    }

    void processEntryPointExistentialSlots()
    {
        // The overall flow for the entry-point case is similar
        // to the global case.
        //
        // We start by iterating over all the functions at
        // global scope and look for entry points.
        //
        for (auto inst : module->getGlobalInsts())
        {
            auto func = as<IRFunc>(inst);
            if (!func)
                continue;

            if (!func->findDecorationImpl(kIROp_EntryPointDecoration))
                continue;

            // We then process each entry point we find.
            //
            processEntryPointExistentialSlots(func);
        }
    }

    void processEntryPointExistentialSlots(IRFunc* func)
    {
        // When looking at a single `func`, we need
        // to find the `[bindExistentialSlots(...)]` decoration,
        // if it has one.
        //
        auto bindEntryPointExistentialSlotsInst =
            func->findDecorationImpl(kIROp_BindExistentialSlotsDecoration);

        // We then need to process each of the entry-point
        // parameters just like we did for global parameters.
        //
        // Because the existential slot arguments for *all* of the parameters
        // are attached in a single `[bindExistentialSlots(...)]` decoration,
        // we need to carve them up appropriately across the parameters.
        // The way we do this is a bit of a kludge, in that we track a
        // single `slotOffset` and increment it for each parameter by the
        // number of arguments it consumed.
        //
        // Note: a better approach here might rely on the layout information
        // for the parameters, which should directly encode an offset for
        // the existential specialization parameters it uses. The challenge
        // with this is that we'd need to correctly interpret the offset
        // relative to any global-scope specialization parameters or
        // generic specialization parameters of the entry point.
        // Ultimately the simplistic counter approach is less complicated.
        //
        Index slotOffset = 0;
        for (auto param : func->getParams())
        {
            processEntryPointParameter(param, bindEntryPointExistentialSlotsInst, slotOffset);
        }

        // TODO: We would need to consider what to do if
        // we had an existential return type for `func`.
        //
        // In general, it probably doesn't make sense to
        // have existential types in varying input/output
        // at all, so the front-end should probably be
        // validating that.

        // Once we've processed all the parameters, the information
        // in the `[bindExistentialSlots(...)]` decoration is
        // no longer needed, and we can remove it.
        //
        if (bindEntryPointExistentialSlotsInst)
        {
            bindEntryPointExistentialSlotsInst->removeAndDeallocate();
        }
    }

    // When processing a single parameter we need to have access
    // to the corresponding instruction that will bind its slots.
    //
    // We don't care whether we have a `global_param` and a
    // `bindGlobalExistentialSlots` instruction, or an entry-point
    // function `param` and a `[bindExistentialSlots(...)]`
    // decoration; both use the same subroutine.
    //
    void processEntryPointParameter(
        IRInst* param,
        IRInst* bindSlotsInst,
        Index& ioSlotOperandOffset)
    {
        // We expect all shader parameters to have layout information,
        // but to be defensive we will skip any that don't.
        //
        auto layoutDecoration = param->findDecoration<IRLayoutDecoration>();
        if (!layoutDecoration)
            return;
        auto varLayout = as<IRVarLayout>(layoutDecoration->getLayout());
        if (!varLayout)
            return;

        // We only care about parameters that are associated
        // with one or more existential slots.
        //
        auto resInfo = varLayout->findOffsetAttr(LayoutResourceKind::ExistentialTypeParam);
        if (!resInfo)
            return;

        // We will use the layout information on the variable to
        // find out the stating slot, and the information on
        // the type to find out the number of slots.
        //
        UInt slotCount = 0;
        if (auto typeResInfo =
                varLayout->getTypeLayout()->findSizeAttr(LayoutResourceKind::ExistentialTypeParam))
            slotCount = UInt(typeResInfo->getFiniteSize());

        // At this point we know that the parameter consumes
        // some number of slots, so it would be an error
        // if we don't have an instruction to bind the slots.
        //
        if (!bindSlotsInst)
        {
            // Note: This error is considered an internal error because
            // we should be detecting and diagnosing this problem before
            // we make it to back-end code generation.
            //
            sink->diagnose(param->sourceLoc, Diagnostics::missingExistentialBindingsForParameter);
            return;
        }

        // Each existential slot corresponds to *two* arguments
        // on the binding instruction: one for the type, and
        // another for the witness table.
        //
        // We will check to make sure we have enough operands to cover
        // this parameter.
        //
        UInt bindOperandCount = bindSlotsInst->getOperandCount();
        UInt slotOperandCount = 2 * slotCount;
        if ((ioSlotOperandOffset + slotOperandCount) > bindOperandCount)
        {
            sink->diagnose(param->sourceLoc, Diagnostics::missingExistentialBindingsForParameter);
            return;
        }
        //
        // If there are enough operands, then we will offset to
        // get to the starting point for the current parameter,
        // keeping in mind that each slot accounts for two
        // operands.
        //
        auto operandsForInst = bindSlotsInst->getOperands() + ioSlotOperandOffset;

        // Once we've found the operands that are relevent to
        // the slots used by `param`, we will defer to a routine
        // that replaces the type of `param` based on the
        // information in the slots.
        //
        replaceTypeUsingExistentialSlots(param, slotOperandCount, operandsForInst);

        ioSlotOperandOffset += slotOperandCount;
    }

    void replaceTypeUsingExistentialSlots(
        IRInst* inst,
        UInt slotOperandCount,
        IRUse const* slotArgs)
    {
        // We are going to alter the type of the
        // given `inst` based on information in
        // the `slotArgs`.

        auto fullType = inst->getFullType();

        IRBuilder builder(module);

        // Every argument that is filling an existential
        // type param/slot comprises both a type and
        // a witness table, so the total number of operands
        // is twice the number of slots we are filling.
        //
        List<IRInst*> slotOperands;
        for (UInt ii = 0; ii < slotOperandCount; ++ii)
            slotOperands.add(slotArgs[ii].get());

        // We are going to create a proxy type that represents
        // the results of plugging all the information
        // from the existential slots into the original type.
        //
        auto newType =
            builder.getBindExistentialsType(fullType, slotOperandCount, slotOperands.getBuffer());

        // We will replace the type of the original parameter
        // with the new proxy type.
        //
        builder.setDataType(inst, newType);

        // Next we want to replace all uses of `inst` (which
        // expect a value of its old type) with a fresh
        // `wrapExistential(...)` instruction that refers to