summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2018-12-19 14:38:04 -0800
committerGitHub <noreply@github.com>2018-12-19 14:38:04 -0800
commit332056a947ec3d9e3588a60d449d64577a6f18c0 (patch)
treee9f0009482fa1c26a9e7ada6d600d05c91c00902 /source
parentb6a54744b6980041de0706fdcd9cba57cb706ff1 (diff)
Refactor several IR passes (#761)
* Refactor several IR passes This change takes some IR passes that lived together in `ir.cpp` and moves them into their own files to improve clarity. In most cases these were passes introduced early in the life of the IR, so that it didn't seem like a big deal to have them all in one file, but now that `ir.cpp` has grown unwieldly this seems like an important cleanup to make. To give a quick rundown of the passes involved: * The IR "linking" step has been pulled out to `ir-link.{h,cpp}`. This code for this pass is pretty much identical to what was in `ir.cpp`, and no attempt has been made to clean up or refactor it in the current change. * The GLSL legalization step has been pulled out to `ir-glsl-legalize.{h,cpp}`. This used to be invoked directly from the linking step, but has been made a new top-level pass invoked from `emit.cpp`. Just like with the linking, the code in the new file is just a copy-paste of what was in `ir.cpp`, and no attempt at cleanup has been made. Also note that it might be a good idea to move this pass later in the overall sequence, but this PR doesn't attempt to do that as it could change results. * The generic specialization step has been pulled out to `ir-specialize.{h,cpp}`. The file name does not explicitly reference *generic* specialization because I anticipate this pass having to perform other kinds of specialization as well. The code in this case amounts to a heavy cleanup/refactoring pass and thus deserves careful scrutiny. The reason for the cleanup is that the generic specialization step used to be part of the "linking" step long ago, and continued to share infrastructure with it long after that stopped making sense. The newly cleaned up pass has much simpler logic that should be easy enough to follow from the comments. * In order to reduce code dulication, the IR "cloning" part of the `ir-specialize-resources.{h,cpp}` pass was pulled into its own files (`ir-clone.{h,cpp}`) that both the generic specialization step and the resource-based specialization step now share. The remaining changes then pertain to deleting a bunch of code out of `ir.cpp` and adding the new files to the build. The only test that needed updating was `vkray/raygen`, where some subtle ordering change in the refactored generic specialization logic has lead to the relative order of the specialized `TraceRay` and `saturate` functions beind reversed. * fixup: typo in assert * fixup: typos in comments
Diffstat (limited to 'source')
-rw-r--r--source/slang/emit.cpp42
-rw-r--r--source/slang/ir-clone.cpp265
-rw-r--r--source/slang/ir-clone.h164
-rw-r--r--source/slang/ir-glsl-legalize.cpp1548
-rw-r--r--source/slang/ir-glsl-legalize.h21
-rw-r--r--source/slang/ir-insts.h55
-rw-r--r--source/slang/ir-link.cpp1297
-rw-r--r--source/slang/ir-link.h33
-rw-r--r--source/slang/ir-specialize-resources.cpp268
-rw-r--r--source/slang/ir-specialize.cpp685
-rw-r--r--source/slang/ir-specialize.h13
-rw-r--r--source/slang/ir.cpp3404
-rw-r--r--source/slang/ir.h1
-rw-r--r--source/slang/slang.vcxproj8
-rw-r--r--source/slang/slang.vcxproj.filters24
15 files changed, 4153 insertions, 3675 deletions
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index a1235f03a..49eaf917b 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -4,9 +4,12 @@
#include "../core/slang-writer.h"
#include "ir-dce.h"
#include "ir-existential.h"
+#include "ir-glsl-legalize.h"
#include "ir-insts.h"
+#include "ir-link.h"
#include "ir-restructure.h"
#include "ir-restructure-scoping.h"
+#include "ir-specialize.h"
#include "ir-specialize-resources.h"
#include "ir-ssa.h"
#include "ir-validate.h"
@@ -6517,10 +6520,9 @@ String emitEntryPoint(
session,
irModule);
- specializeIRForEntryPoint(
+ auto irEntryPoint = specializeIRForEntryPoint(
irSpecializationState,
- entryPoint,
- &sharedContext.extensionUsageTracker);
+ entryPoint);
#if 0
dumpIRIfEnabled(compileRequest, irModule, "CLONED");
@@ -6533,6 +6535,38 @@ String emitEntryPoint(
// un-specialized IR.
dumpIRIfEnabled(compileRequest, irModule);
+
+
+ // For GLSL only, we will need to perform "legalization" of
+ // the entry point and any entry-point parameters.
+ //
+ // TODO: We should consider moving this legalization work
+ // as late as possible, so that it doesn't affect how other
+ // optimization passes need to work.
+ //
+ switch (target)
+ {
+ case CodeGenTarget::GLSL:
+ {
+ legalizeEntryPointForGLSL(
+ session,
+ irModule,
+ irEntryPoint,
+ &compileRequest->mSink,
+ &sharedContext.extensionUsageTracker);
+ }
+ break;
+
+ default:
+ break;
+ }
+#if 0
+ dumpIRIfEnabled(compileRequest, irModule, "GLSL LEGALIZED");
+#endif
+ validateIRModuleIfEnabled(compileRequest, irModule);
+
+
+
// Any code that makes use of existential (interface) types
// needs to be simplified to use concrete types instead,
// wherever this is possible.
@@ -6559,7 +6593,7 @@ String emitEntryPoint(
// none of our target supports generics, or interfaces,
// so we need to specialize those away.
//
- specializeGenerics(irModule, sharedContext.target);
+ specializeGenerics(irModule);
diff --git a/source/slang/ir-clone.cpp b/source/slang/ir-clone.cpp
new file mode 100644
index 000000000..df9cf2bf6
--- /dev/null
+++ b/source/slang/ir-clone.cpp
@@ -0,0 +1,265 @@
+// ir-clone.cpp
+#include "ir-clone.h"
+
+#include "ir.h"
+#include "ir-insts.h"
+
+namespace Slang
+{
+
+IRInst* lookUp(IRCloneEnv* env, IRInst* oldVal)
+{
+ for( auto ee = env; ee; ee = ee->parent )
+ {
+ IRInst* newVal = nullptr;
+ if(ee->mapOldValToNew.TryGetValue(oldVal, newVal))
+ return newVal;
+ }
+ return nullptr;
+}
+
+IRInst* findCloneForOperand(
+ IRCloneEnv* env,
+ IRInst* oldOperand)
+{
+ if(!oldOperand) return nullptr;
+
+ // If there is a registered replacement for
+ // the existing operand, then use it.
+ //
+ if( IRInst* newVal = lookUp(env, oldOperand) )
+ return newVal;
+
+ // Otherwise, we assume that the caller wants
+ // to default to using existing values wherever
+ // an explicit replacement hasn't been registered.
+ //
+ // This is, notably, the right default whenever
+ // `oldOperand` is a global value or constant
+ // and our cloned code will sit in the same
+ // module as the original.
+ //
+ // TODO: We could make this a customization point
+ // down the road, if we ever had a case where
+ // we want to clone things with a different policy.
+ //
+ return oldOperand;
+}
+
+IRInst* cloneInstAndOperands(
+ IRCloneEnv* env,
+ IRBuilder* builder,
+ IRInst* oldInst)
+{
+ SLANG_ASSERT(env);
+ SLANG_ASSERT(builder);
+ SLANG_ASSERT(oldInst);
+
+ // This logic will not handle any instructions
+ // with special-case data attached, but that only
+ // applies to `IRConstant`s at this point, and those
+ // should only appear at the global scope rather than
+ // in function bodies.
+ //
+ // TODO: It would be easy enough to extend this logic
+ // to handle constants gracefully, if it ever comes up.
+ //
+ SLANG_ASSERT(!as<IRConstant>(oldInst));
+
+ // We start by mapping the type of the orignal instruction
+ // to its replacement value, if any.
+ //
+ auto oldType = oldInst->getFullType();
+ auto newType = (IRType*) findCloneForOperand(env, oldType);
+
+ // Next we will create an empty shell of the instruction,
+ // with space for the operands, but no actual operand
+ // values attached.
+ //
+ UInt operandCount = oldInst->getOperandCount();
+ auto newInst = builder->emitIntrinsicInst(
+ newType,
+ oldInst->op,
+ operandCount,
+ nullptr);
+
+ // Finally we will iterate over the operands of `oldInst`
+ // to find their replacements and install them as
+ // the operands of `newInst`.
+ //
+ for(UInt ii = 0; ii < operandCount; ++ii)
+ {
+ auto oldOperand = oldInst->getOperand(ii);
+ auto newOperand = findCloneForOperand(env, oldOperand);
+
+ newInst->getOperands()[ii].init(newInst, newOperand);
+ }
+
+ return newInst;
+}
+
+// The complexity of the second phase of cloning (the
+// one that deals with decorations and children) comes
+// from the fact that it needs to sequence the two phases
+// of cloning for any child instructions. We will do this
+// by performing the first phase of cloning, and building
+// up a list of children that require the second phase of processing.
+// Each entry in that list will be a pair of an old instruction
+// and its new clone.
+//
+struct IRCloningOldNewPair
+{
+ IRInst* oldInst;
+ IRInst* newInst;
+};
+
+// We will use an internal variant of `cloneInstDecorationsAndChildren`
+// that modifies the provided `env` as it goes as the main
+// workhorse, since we need to make sure that instructions in
+// earlier blocks are visible to those in other, later, blocks
+// when cloning a function, so that strict scoping along the
+// lines of the nesting of instructions isn't sufficient.
+//
+static void _cloneInstDecorationsAndChildren(
+ IRCloneEnv* env,
+ SharedIRBuilder* sharedBuilder,
+ IRInst* oldInst,
+ IRInst* newInst)
+{
+ SLANG_ASSERT(env);
+ SLANG_ASSERT(sharedBuilder);
+ SLANG_ASSERT(oldInst);
+ SLANG_ASSERT(newInst);
+
+ // We will set up an IR builder that inserts
+ // into the new parent instruction.
+ //
+ IRBuilder builderStorage;
+ auto builder = &builderStorage;
+ builder->sharedBuilder = sharedBuilder;
+ builder->setInsertInto(newInst);
+
+ // When applying the first phase of cloning to
+ // children, we will keep track of those that
+ // require the second phase.
+ //
+ List<IRCloningOldNewPair> pairs;
+
+ for( auto oldChild : oldInst->getDecorationsAndChildren() )
+ {
+ // As a very subtle special case, if one of the children
+ // of our `oldInst` already has a registered replacement,
+ // then we don't want to clone it (not least because
+ // the `Dictionary::Add` method would give us an error
+ // when we try to insert a new value for the same key).
+ //
+ // This arises for entries in `mapOldValToNew` that were
+ // seeded before cloning begain (e.g., function
+ // parameters that are to be replaced).
+ //
+ if(lookUp(env, oldChild))
+ continue;
+
+ // Now we can perform the first phase of cloning
+ // on the child, and register it in our map from
+ // old to new values.
+ //
+ auto newChild = cloneInstAndOperands(env, builder, oldChild);
+ env->mapOldValToNew.Add(oldChild, newChild);
+
+ // If and only if the old child had decorations
+ // or children, we will register it into our
+ // list for processing in the second phase.
+ //
+ if( oldChild->getFirstDecorationOrChild() )
+ {
+ IRCloningOldNewPair pair;
+ pair.oldInst = oldChild;
+ pair.newInst = newChild;
+ pairs.Add(pair);
+ }
+ }
+
+ // Once we have done first-phase processing for
+ // all child instructions, we scan through those
+ // in the list that required second-phase processing,
+ // and clone their decorations and/or children recursively.
+ //
+ for( auto pair : pairs )
+ {
+ auto oldChild = pair.oldInst;
+ auto newChild = pair.newInst;
+
+ _cloneInstDecorationsAndChildren(env, sharedBuilder, oldChild, newChild);
+ }
+}
+
+// The public version of `cloneInstDecorationsAndChildren` is then
+// just a wrapper over the internal one that sets up a temporary
+// environment to use for the cloning process, so that we do
+// not leave any lasting changes in the user-provided `env`.
+//
+void cloneInstDecorationsAndChildren(
+ IRCloneEnv* env,
+ SharedIRBuilder* sharedBuilder,
+ IRInst* oldInst,
+ IRInst* newInst)
+{
+ SLANG_ASSERT(sharedBuilder);
+ SLANG_ASSERT(oldInst);
+ SLANG_ASSERT(newInst);
+
+ IRCloneEnv subEnvStorage;
+ auto subEnv = &subEnvStorage;
+ subEnv->parent = env;
+
+ _cloneInstDecorationsAndChildren(subEnv, sharedBuilder, oldInst, newInst);
+}
+
+// The convenience function `cloneInst` just sequences the
+// operations that have already been defined.
+//
+IRInst* cloneInst(
+ IRCloneEnv* env,
+ IRBuilder* builder,
+ IRInst* oldInst)
+{
+ SLANG_ASSERT(env);
+ SLANG_ASSERT(builder);
+ SLANG_ASSERT(oldInst);
+
+ auto newInst = cloneInstAndOperands(
+ env, builder, oldInst);
+
+ env->mapOldValToNew.Add(oldInst, newInst);
+
+ cloneInstDecorationsAndChildren(
+ env, builder->sharedBuilder, oldInst, newInst);
+
+ return newInst;
+}
+
+bool IRSimpleSpecializationKey::operator==(IRSimpleSpecializationKey const& other) const
+{
+ auto valCount = vals.Count();
+ if(valCount != other.vals.Count()) return false;
+ for( UInt ii = 0; ii < valCount; ++ii )
+ {
+ if(vals[ii] != other.vals[ii]) return false;
+ }
+ return true;
+}
+
+int IRSimpleSpecializationKey::GetHashCode() const
+{
+ auto valCount = vals.Count();
+ int hash = Slang::GetHashCode(valCount);
+ for( UInt ii = 0; ii < valCount; ++ii )
+ {
+ hash = combineHash(hash, Slang::GetHashCode(vals[ii]));
+ }
+ return hash;
+}
+
+
+} // namespace Slang
diff --git a/source/slang/ir-clone.h b/source/slang/ir-clone.h
new file mode 100644
index 000000000..f1e27b785
--- /dev/null
+++ b/source/slang/ir-clone.h
@@ -0,0 +1,164 @@
+// ir-clone.h
+#pragma once
+
+#include "../core/dictionary.h"
+
+#include "ir.h"
+
+namespace Slang
+{
+struct IRBuilder;
+struct IRInst;
+struct SharedIRBuilder;
+
+// This file provides an interface to simplify the task of
+// correctling "cloning" IR code, whether individual
+// instructions, or whole functions.
+
+ /// An environment for mapping existing values to their cloned replacements.
+ ///
+ /// This type serves two main roles in the process of IR cloning:
+ ///
+ /// * Before cloning begins, a client will usually
+ /// register the mapping from things that are to be
+ /// replaced entirely (like function parameters to
+ /// be specialized away) to their replacements (e.g.,
+ /// a constant value).
+ ///
+ /// * During the process of cloning, env environment
+ /// will be maintained and updated so that when, e.g.,
+ /// an instruction later in a function refers to
+ /// something from earlier, we can look up the
+ /// replacement.
+ ///
+struct IRCloneEnv
+{
+ /// A mapping from old values to their replacements.
+ Dictionary<IRInst*, IRInst*> mapOldValToNew;
+
+ /// A parent environment to fall back to if `mapOldValToNew` doesn't contain a key.
+ IRCloneEnv* parent = nullptr;
+};
+
+ /// Look up the replacement for `oldVal`, if any, registered in `env`.
+ ///
+ /// Returns `nullptr` if `oldVal` has no registered replacement.
+ ///
+IRInst* lookUp(IRCloneEnv* env, IRInst* oldVal);
+
+// The SSA property and the way we have structured
+// our "phi nodes" (block parameters) means that
+// just going through the children of a function,
+// and then the children of a block will generally
+// do the Right Thing and always visit an instruction
+// before its uses.
+//
+// The big exception to this is that branch instructions
+// can refer to blocks later in the same function.
+//
+// We work around this sort of problem in a fairly
+// general fashion, by splitting the cloning of
+// an instruction into two steps.
+//
+// The first step is just to clone the instruction
+// and its direct operands, but not any decorations
+// or children.
+
+ /// Clone `oldInst` and its direct operands.
+ ///
+ /// The "direct operands" include the type of the instruction.
+ /// The type and operands of `oldInst` will be mapped to now
+ /// values using `findOrCloneOperand` with the given `env`.
+ ///
+ /// Any new instruction that gets emitted will be output to
+ /// the provided `builder`, which must be non-null.
+ ///
+ /// This operation does *not* clone any children or decorations on `oldInst`.
+ /// This operation does *not* register its result as a replacement
+ /// for `oldInst` in the given `env`.
+ ///
+IRInst* cloneInstAndOperands(
+ IRCloneEnv* env,
+ IRBuilder* builder,
+ IRInst* oldInst);
+
+// The second phase of cloning an instruction is to clone
+// its decorations and children. This step only needs to
+// be performed on those instructions that *have* decorations
+// and/or children.
+
+ /// Clone any decorations and/or children of `oldInst` onto `newInst`
+ ///
+ /// Any new instructions that get emitted will use the
+ /// provided `sharedBuilder`, which must be non-null.
+ ///
+ /// During the process of cloning decorations/children, operand values
+ /// will be looked up in the provided `env`, which should provide
+ /// replacement values for instructions that should have a different
+ /// identity in the clone.
+ /// The provided `env` will *not* be updated/modified during the
+ /// process of cloding decorations/children.
+ ///
+ /// If any child or decoration on `oldInst` already has a replacement
+ /// registered in `env`, it will *not* be cloned into `newInst`.
+ ///
+void cloneInstDecorationsAndChildren(
+ IRCloneEnv* env,
+ SharedIRBuilder* sharedBuilder,
+ IRInst* oldInst,
+ IRInst* newInst);
+
+// For the case where the user knows the sequencing constraints
+// on cloning operands before uses can be satisfied, we provide
+// a convenience wrapper around the two phases of cloning:
+
+ /// Clone `oldInst` and return the cloned value.
+ ///
+ /// This function is a convenience wrapper around
+ /// `cloneInstAndOperands` and `cloneInstDecorationsAndChildren`.
+ /// It also registers the resultint instruction as
+ /// the replacement value for `oldInst` in the given `env`
+ /// which must therefore be non-null.
+ ///
+IRInst* cloneInst(
+ IRCloneEnv* env,
+ IRBuilder* builder,
+ IRInst* oldInst);
+
+
+ /// Find the "cloned" value to use for an operand.
+ ///
+ /// This either returns the value registered for `oldOperand`
+ /// in `env`, or else `oldOperand` itself.
+IRInst* findCloneForOperand(
+ IRCloneEnv* env,
+ IRInst* oldOperand);
+
+// It isn't technically part of the cloning infrastructure,
+// but when make specialized copies of IR instructions via
+// cloning we often need a simple kind of key suitable
+// for caching existing specializations, so we'll define
+// it here so that is is easily accessible to code that
+// needs it.
+
+struct IRSimpleSpecializationKey
+{
+ // The structure of a specialization key will be a list
+ // of instructions, typically starting with the function,
+ // generic, or other object to be specialized, and then
+ // having one or more entries to represent the specialization
+ // arguments.
+ //
+ List<IRInst*> vals;
+
+ // In order to use this type as a `Dictionary` key we
+ // need it to support equality and hashing.
+ //
+ // TODO: honestly we might consider having `GetHashCode`
+ // and `operator==` defined for `List<T>`.
+
+ bool operator==(IRSimpleSpecializationKey const& other) const;
+ int GetHashCode() const;
+};
+
+}
diff --git a/source/slang/ir-glsl-legalize.cpp b/source/slang/ir-glsl-legalize.cpp
new file mode 100644
index 000000000..b659cf293
--- /dev/null
+++ b/source/slang/ir-glsl-legalize.cpp
@@ -0,0 +1,1548 @@
+// ir-glsl-legalize.cpp
+#include "ir-glsl-legalize.h"
+
+#include "ir.h"
+#include "ir-insts.h"
+
+namespace Slang
+{
+
+//
+// Legalization of entry points for GLSL:
+//
+
+IRGlobalParam* addGlobalParam(
+ IRModule* module,
+ IRType* valueType)
+{
+ auto session = module->session;
+
+ SharedIRBuilder shared;
+ shared.module = module;
+ shared.session = session;
+
+ IRBuilder builder;
+ builder.sharedBuilder = &shared;
+ return builder.createGlobalParam(valueType);
+}
+
+void moveValueBefore(
+ IRInst* valueToMove,
+ IRInst* placeBefore)
+{
+ valueToMove->removeFromParent();
+ valueToMove->insertBefore(placeBefore);
+}
+
+IRType* getFieldType(
+ IRType* baseType,
+ IRStructKey* fieldKey)
+{
+ if(auto structType = as<IRStructType>(baseType))
+ {
+ for(auto ff : structType->getFields())
+ {
+ if(ff->getKey() == fieldKey)
+ return ff->getFieldType();
+ }
+ }
+
+ SLANG_UNEXPECTED("no such field");
+ UNREACHABLE_RETURN(nullptr);
+}
+
+
+
+// When scalarizing shader inputs/outputs for GLSL, we need a way
+// to refer to a conceptual "value" that might comprise multiple
+// IR-level values. We could in principle introduce tuple types
+// into the IR so that everything stays at the IR level, but
+// it seems easier to just layer it over the top for now.
+//
+// The `ScalarizedVal` type deals with the "tuple or single value?"
+// question, and also the "l-value or r-value?" question.
+struct ScalarizedValImpl : RefObject
+{};
+struct ScalarizedTupleValImpl;
+struct ScalarizedTypeAdapterValImpl;
+struct ScalarizedVal
+{
+ enum class Flavor
+ {
+ // no value (null pointer)
+ none,
+
+ // A simple `IRInst*` that represents the actual value
+ value,
+
+ // An `IRInst*` that represents the address of the actual value
+ address,
+
+ // A `TupleValImpl` that represents zero or more `ScalarizedVal`s
+ tuple,
+
+ // A `TypeAdapterValImpl` that wraps a single `ScalarizedVal` and
+ // represents an implicit type conversion applied to it on read
+ // or write.
+ typeAdapter,
+ };
+
+ // Create a value representing a simple value
+ static ScalarizedVal value(IRInst* irValue)
+ {
+ ScalarizedVal result;
+ result.flavor = Flavor::value;
+ result.irValue = irValue;
+ return result;
+ }
+
+
+ // Create a value representing an address
+ static ScalarizedVal address(IRInst* irValue)
+ {
+ ScalarizedVal result;
+ result.flavor = Flavor::address;
+ result.irValue = irValue;
+ return result;
+ }
+
+ static ScalarizedVal tuple(ScalarizedTupleValImpl* impl)
+ {
+ ScalarizedVal result;
+ result.flavor = Flavor::tuple;
+ result.impl = (ScalarizedValImpl*)impl;
+ return result;
+ }
+
+ static ScalarizedVal typeAdapter(ScalarizedTypeAdapterValImpl* impl)
+ {
+ ScalarizedVal result;
+ result.flavor = Flavor::typeAdapter;
+ result.impl = (ScalarizedValImpl*)impl;
+ return result;
+ }
+
+ Flavor flavor = Flavor::none;
+ IRInst* irValue = nullptr;
+ RefPtr<ScalarizedValImpl> impl;
+};
+
+// This is the case for a value that is a "tuple" of other values
+struct ScalarizedTupleValImpl : ScalarizedValImpl
+{
+ struct Element
+ {
+ IRStructKey* key;
+ ScalarizedVal val;
+ };
+
+ IRType* type;
+ List<Element> elements;
+};
+
+// This is the case for a value that is stored with one type,
+// but needs to present itself as having a different type
+struct ScalarizedTypeAdapterValImpl : ScalarizedValImpl
+{
+ ScalarizedVal val;
+ IRType* actualType; // the actual type of `val`
+ IRType* pretendType; // the type this value pretends to have
+};
+
+struct GlobalVaryingDeclarator
+{
+ enum class Flavor
+ {
+ array,
+ };
+
+ Flavor flavor;
+ IRInst* elementCount;
+ GlobalVaryingDeclarator* next;
+};
+
+struct GLSLSystemValueInfo
+{
+ // The name of the built-in GLSL variable
+ char const* name;
+
+ // The name of an outer array that wraps
+ // the variable, in the case of a GS input
+ char const* outerArrayName;
+
+ // The required type of the built-in variable
+ IRType* requiredType;
+};
+
+void requireGLSLVersionImpl(
+ ExtensionUsageTracker* tracker,
+ ProfileVersion version);
+
+void requireGLSLExtension(
+ ExtensionUsageTracker* tracker,
+ String const& name);
+
+struct GLSLLegalizationContext
+{
+ Session* session;
+ ExtensionUsageTracker* extensionUsageTracker;
+ DiagnosticSink* sink;
+ Stage stage;
+
+ void requireGLSLExtension(String const& name)
+ {
+ Slang::requireGLSLExtension(extensionUsageTracker, name);
+ }
+
+ void requireGLSLVersion(ProfileVersion version)
+ {
+ Slang::requireGLSLVersionImpl(extensionUsageTracker, version);
+ }
+
+ Stage getStage()
+ {
+ return stage;
+ }
+
+ DiagnosticSink* getSink()
+ {
+ return sink;
+ }
+
+ IRBuilder* builder;
+ IRBuilder* getBuilder() { return builder; }
+};
+
+GLSLSystemValueInfo* getGLSLSystemValueInfo(
+ GLSLLegalizationContext* context,
+ VarLayout* varLayout,
+ LayoutResourceKind kind,
+ Stage stage,
+ GLSLSystemValueInfo* inStorage)
+{
+ char const* name = nullptr;
+ char const* outerArrayName = nullptr;
+
+ auto semanticNameSpelling = varLayout->systemValueSemantic;
+ if(semanticNameSpelling.Length() == 0)
+ return nullptr;
+
+ auto semanticName = semanticNameSpelling.ToLower();
+
+ IRType* requiredType = nullptr;
+
+ if(semanticName == "sv_position")
+ {
+ // This semantic can either work like `gl_FragCoord`
+ // when it is used as a fragment shader input, or
+ // like `gl_Position` when used in other stages.
+ //
+ // Note: This isn't as simple as testing input-vs-output,
+ // because a user might have a VS output `SV_Position`,
+ // and then pass it along to a GS that reads it as input.
+ //
+ if( stage == Stage::Fragment
+ && kind == LayoutResourceKind::VaryingInput )
+ {
+ name = "gl_FragCoord";
+ }
+ else if( stage == Stage::Geometry
+ && kind == LayoutResourceKind::VaryingInput )
+ {
+ // As a GS input, the correct syntax is `gl_in[...].gl_Position`,
+ // but that is not compatible with picking the array dimension later,
+ // of course.
+ outerArrayName = "gl_in";
+ name = "gl_Position";
+ }
+ else
+ {
+ name = "gl_Position";
+ }
+ }
+ else if(semanticName == "sv_target")
+ {
+ // Note: we do *not* need to generate some kind of `gl_`
+ // builtin for fragment-shader outputs: they are just
+ // ordinary `out` variables, with ordinary `location`s,
+ // as far as GLSL is concerned.
+ return nullptr;
+ }
+ else if(semanticName == "sv_clipdistance")
+ {
+ // TODO: type conversion is required here.
+ name = "gl_ClipDistance";
+ }
+ else if(semanticName == "sv_culldistance")
+ {
+ context->requireGLSLExtension("ARB_cull_distance");
+
+ // TODO: type conversion is required here.
+ name = "gl_CullDistance";
+ }
+ else if(semanticName == "sv_coverage")
+ {
+ // TODO: deal with `gl_SampleMaskIn` when used as an input.
+
+ // TODO: type conversion is required here.
+ name = "gl_SampleMask";
+ }
+ else if(semanticName == "sv_depth")
+ {
+ name = "gl_FragDepth";
+ }
+ else if(semanticName == "sv_depthgreaterequal")
+ {
+ // TODO: layout(depth_greater) out float gl_FragDepth;
+ name = "gl_FragDepth";
+ }
+ else if(semanticName == "sv_depthlessequal")
+ {
+ // TODO: layout(depth_greater) out float gl_FragDepth;
+ name = "gl_FragDepth";
+ }
+ else if(semanticName == "sv_dispatchthreadid")
+ {
+ name = "gl_GlobalInvocationID";
+ }
+ else if(semanticName == "sv_domainlocation")
+ {
+ name = "gl_TessCoord";
+ }
+ else if(semanticName == "sv_groupid")
+ {
+ name = "gl_WorkGroupID";
+ }
+ else if(semanticName == "sv_groupindex")
+ {
+ name = "gl_LocalInvocationIndex";
+ }
+ else if(semanticName == "sv_groupthreadid")
+ {
+ name = "gl_LocalInvocationID";
+ }
+ else if(semanticName == "sv_gsinstanceid")
+ {
+ name = "gl_InvocationID";
+ }
+ else if(semanticName == "sv_instanceid")
+ {
+ name = "gl_InstanceIndex";
+ }
+ else if(semanticName == "sv_isfrontface")
+ {
+ name = "gl_FrontFacing";
+ }
+ else if(semanticName == "sv_outputcontrolpointid")
+ {
+ name = "gl_InvocationID";
+ }
+ else if(semanticName == "sv_primitiveid")
+ {
+ name = "gl_PrimitiveID";
+ }
+ else if (semanticName == "sv_rendertargetarrayindex")
+ {
+ switch (context->getStage())
+ {
+ case Stage::Geometry:
+ context->requireGLSLVersion(ProfileVersion::GLSL_150);
+ break;
+
+ case Stage::Fragment:
+ context->requireGLSLVersion(ProfileVersion::GLSL_430);
+ break;
+
+ default:
+ context->requireGLSLVersion(ProfileVersion::GLSL_450);
+ context->requireGLSLExtension("GL_ARB_shader_viewport_layer_array");
+ break;
+ }
+
+ name = "gl_Layer";
+ requiredType = context->getBuilder()->getBasicType(BaseType::Int);
+ }
+ else if (semanticName == "sv_sampleindex")
+ {
+ name = "gl_SampleID";
+ }
+ else if (semanticName == "sv_stencilref")
+ {
+ context->requireGLSLExtension("ARB_shader_stencil_export");
+ name = "gl_FragStencilRef";
+ }
+ else if (semanticName == "sv_tessfactor")
+ {
+ name = "gl_TessLevelOuter";
+ }
+ else if (semanticName == "sv_vertexid")
+ {
+ name = "gl_VertexIndex";
+ }
+ else if (semanticName == "sv_viewportarrayindex")
+ {
+ name = "gl_ViewportIndex";
+ }
+ else if (semanticName == "nv_x_right")
+ {
+ context->requireGLSLVersion(ProfileVersion::GLSL_450);
+ context->requireGLSLExtension("GL_NVX_multiview_per_view_attributes");
+
+ // The actual output in GLSL is:
+ //
+ // vec4 gl_PositionPerViewNV[];
+ //
+ // and is meant to support an arbitrary number of views,
+ // while the HLSL case just defines a second position
+ // output.
+ //
+ // For now we will hack this by:
+ // 1. Mapping an `NV_X_Right` output to `gl_PositionPerViewNV[1]`
+ // (that is, just one element of the output array)
+ // 2. Adding logic to copy the traditional `gl_Position` output
+ // over to `gl_PositionPerViewNV[0]`
+ //
+
+ name = "gl_PositionPerViewNV[1]";
+
+// shared->requiresCopyGLPositionToPositionPerView = true;
+ }
+ else if (semanticName == "nv_viewport_mask")
+ {
+ context->requireGLSLVersion(ProfileVersion::GLSL_450);
+ context->requireGLSLExtension("GL_NVX_multiview_per_view_attributes");
+
+ name = "gl_ViewportMaskPerViewNV";
+// globalVarExpr = createGLSLBuiltinRef("gl_ViewportMaskPerViewNV",
+// getUnsizedArrayType(getIntType()));
+ }
+
+ if( name )
+ {
+ inStorage->name = name;
+ inStorage->outerArrayName = outerArrayName;
+ inStorage->requiredType = requiredType;
+ return inStorage;
+ }
+
+ context->getSink()->diagnose(varLayout->varDecl.getDecl()->loc, Diagnostics::unknownSystemValueSemantic, semanticNameSpelling);
+ return nullptr;
+}
+
+ScalarizedVal createSimpleGLSLGlobalVarying(
+ GLSLLegalizationContext* context,
+ IRBuilder* builder,
+ IRType* inType,
+ VarLayout* inVarLayout,
+ TypeLayout* inTypeLayout,
+ LayoutResourceKind kind,
+ Stage stage,
+ UInt bindingIndex,
+ GlobalVaryingDeclarator* declarator)
+{
+ // Check if we have a system value on our hands.
+ GLSLSystemValueInfo systemValueInfoStorage;
+ auto systemValueInfo = getGLSLSystemValueInfo(
+ context,
+ inVarLayout,
+ kind,
+ stage,
+ &systemValueInfoStorage);
+
+ IRType* type = inType;
+
+ // A system-value semantic might end up needing to override the type
+ // that the user specified.
+ if( systemValueInfo && systemValueInfo->requiredType )
+ {
+ type = systemValueInfo->requiredType;
+ }
+
+ // Construct the actual type and type-layout for the global variable
+ //
+ RefPtr<TypeLayout> typeLayout = inTypeLayout;
+ for( auto dd = declarator; dd; dd = dd->next )
+ {
+ // We only have one declarator case right now...
+ SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array);
+
+ auto arrayType = builder->getArrayType(
+ type,
+ dd->elementCount);
+
+ RefPtr<ArrayTypeLayout> arrayTypeLayout = new ArrayTypeLayout();
+// arrayTypeLayout->type = arrayType;
+ arrayTypeLayout->rules = typeLayout->rules;
+ arrayTypeLayout->originalElementTypeLayout = typeLayout;
+ arrayTypeLayout->elementTypeLayout = typeLayout;
+ arrayTypeLayout->uniformStride = 0;
+
+ if( auto resInfo = inTypeLayout->FindResourceInfo(kind) )
+ {
+ // TODO: it is kind of gross to be re-running some
+ // of the type layout logic here.
+
+ UInt elementCount = (UInt) GetIntVal(dd->elementCount);
+ arrayTypeLayout->addResourceUsage(
+ kind,
+ resInfo->count * elementCount);
+ }
+
+ type = arrayType;
+ typeLayout = arrayTypeLayout;
+ }
+
+ // We need to construct a fresh layout for the variable, even
+ // if the original had its own layout, because it might be
+ // an `inout` parameter, and we only want to deal with the case
+ // described by our `kind` parameter.
+ RefPtr<VarLayout> varLayout = new VarLayout();
+ varLayout->varDecl = inVarLayout->varDecl;
+ varLayout->typeLayout = typeLayout;
+ varLayout->flags = inVarLayout->flags;
+ varLayout->systemValueSemantic = inVarLayout->systemValueSemantic;
+ varLayout->systemValueSemanticIndex = inVarLayout->systemValueSemanticIndex;
+ varLayout->semanticName = inVarLayout->semanticName;
+ varLayout->semanticIndex = inVarLayout->semanticIndex;
+ varLayout->stage = inVarLayout->stage;
+ varLayout->AddResourceInfo(kind)->index = bindingIndex;
+
+ // We are going to be creating a global parameter to replace
+ // the function parameter, but we need to handle the case
+ // where the parameter represents a varying *output* and not
+ // just an input.
+ //
+ // Our IR global shader parameters are read-only, just
+ // like our IR function parameters, and need a wrapper
+ // `Out<...>` type to represent otuputs.
+ //
+ bool isOutput = kind == LayoutResourceKind::VaryingOutput;
+ IRType* paramType = isOutput ? builder->getOutType(type) : type;
+
+ auto globalParam = addGlobalParam(builder->getModule(), paramType);
+ moveValueBefore(globalParam, builder->getFunc());
+
+ ScalarizedVal val = isOutput ? ScalarizedVal::address(globalParam) : ScalarizedVal::value(globalParam);
+
+ if( systemValueInfo )
+ {
+ builder->addImportDecoration(globalParam, UnownedTerminatedStringSlice(systemValueInfo->name));
+
+ if( auto fromType = systemValueInfo->requiredType )
+ {
+ // We may need to adapt from the declared type to/from
+ // the actual type of the GLSL global.
+ auto toType = inType;
+
+ if( fromType != toType )
+ {
+ RefPtr<ScalarizedTypeAdapterValImpl> typeAdapter = new ScalarizedTypeAdapterValImpl;
+ typeAdapter->actualType = systemValueInfo->requiredType;
+ typeAdapter->pretendType = inType;
+ typeAdapter->val = val;
+
+ val = ScalarizedVal::typeAdapter(typeAdapter);
+ }
+ }
+
+ if(auto outerArrayName = systemValueInfo->outerArrayName)
+ {
+ builder->addGLSLOuterArrayDecoration(globalParam, UnownedTerminatedStringSlice(outerArrayName));
+ }
+ }
+
+ builder->addLayoutDecoration(globalParam, varLayout);
+
+ return val;
+}
+
+ScalarizedVal createGLSLGlobalVaryingsImpl(
+ GLSLLegalizationContext* context,
+ IRBuilder* builder,
+ IRType* type,
+ VarLayout* varLayout,
+ TypeLayout* typeLayout,
+ LayoutResourceKind kind,
+ Stage stage,
+ UInt bindingIndex,
+ GlobalVaryingDeclarator* declarator)
+{
+ if( as<IRBasicType>(type) )
+ {
+ return createSimpleGLSLGlobalVarying(
+ context,
+ builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
+ }
+ else if( as<IRVectorType>(type) )
+ {
+ return createSimpleGLSLGlobalVarying(
+ context,
+ builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
+ }
+ else if( as<IRMatrixType>(type) )
+ {
+ // TODO: a matrix-type varying should probably be handled like an array of rows
+ return createSimpleGLSLGlobalVarying(
+ context,
+ builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
+ }
+ else if( auto arrayType = as<IRArrayType>(type) )
+ {
+ // We will need to SOA-ize any nested types.
+
+ auto elementType = arrayType->getElementType();
+ auto elementCount = arrayType->getElementCount();
+ auto arrayLayout = dynamic_cast<ArrayTypeLayout*>(typeLayout);
+ SLANG_ASSERT(arrayLayout);
+ auto elementTypeLayout = arrayLayout->elementTypeLayout;
+
+ GlobalVaryingDeclarator arrayDeclarator;
+ arrayDeclarator.flavor = GlobalVaryingDeclarator::Flavor::array;
+ arrayDeclarator.elementCount = elementCount;
+ arrayDeclarator.next = declarator;
+
+ return createGLSLGlobalVaryingsImpl(
+ context,
+ builder,
+ elementType,
+ varLayout,
+ elementTypeLayout,
+ kind,
+ stage,
+ bindingIndex,
+ &arrayDeclarator);
+ }
+ else if( auto streamType = as<IRHLSLStreamOutputType>(type))
+ {
+ auto elementType = streamType->getElementType();
+ auto streamLayout = dynamic_cast<StreamOutputTypeLayout*>(typeLayout);
+ SLANG_ASSERT(streamLayout);
+ auto elementTypeLayout = streamLayout->elementTypeLayout;
+
+ return createGLSLGlobalVaryingsImpl(
+ context,
+ builder,
+ elementType,
+ varLayout,
+ elementTypeLayout,
+ kind,
+ stage,
+ bindingIndex,
+ declarator);
+ }
+ else if(auto structType = as<IRStructType>(type))
+ {
+ // We need to recurse down into the individual fields,
+ // and generate a variable for each of them.
+
+ auto structTypeLayout = dynamic_cast<StructTypeLayout*>(typeLayout);
+ SLANG_ASSERT(structTypeLayout);
+ RefPtr<ScalarizedTupleValImpl> tupleValImpl = new ScalarizedTupleValImpl();
+
+
+ // Construct the actual type for the tuple (including any outer arrays)
+ IRType* fullType = type;
+ for( auto dd = declarator; dd; dd = dd->next )
+ {
+ SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array);
+ fullType = builder->getArrayType(
+ fullType,
+ dd->elementCount);
+ }
+
+ tupleValImpl->type = fullType;
+
+ // Okay, we want to walk through the fields here, and
+ // generate one variable for each.
+ UInt fieldCounter = 0;
+ for(auto field : structType->getFields())
+ {
+ UInt fieldIndex = fieldCounter++;
+
+ auto fieldLayout = structTypeLayout->fields[fieldIndex];
+
+ UInt fieldBindingIndex = bindingIndex;
+ if(auto fieldResInfo = fieldLayout->FindResourceInfo(kind))
+ fieldBindingIndex += fieldResInfo->index;
+
+ auto fieldVal = createGLSLGlobalVaryingsImpl(
+ context,
+ builder,
+ field->getFieldType(),
+ fieldLayout,
+ fieldLayout->typeLayout,
+ kind,
+ stage,
+ fieldBindingIndex,
+ declarator);
+
+ ScalarizedTupleValImpl::Element element;
+ element.val = fieldVal;
+ element.key = field->getKey();
+
+ tupleValImpl->elements.Add(element);
+ }
+
+ return ScalarizedVal::tuple(tupleValImpl);
+ }
+
+ // Default case is to fall back on the simple behavior
+ return createSimpleGLSLGlobalVarying(
+ context,
+ builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
+}
+
+ScalarizedVal createGLSLGlobalVaryings(
+ GLSLLegalizationContext* context,
+ IRBuilder* builder,
+ IRType* type,
+ VarLayout* layout,
+ LayoutResourceKind kind,
+ Stage stage)
+{
+ UInt bindingIndex = 0;
+ if(auto rr = layout->FindResourceInfo(kind))
+ bindingIndex = rr->index;
+ return createGLSLGlobalVaryingsImpl(
+ context,
+ builder, type, layout, layout->typeLayout, kind, stage, bindingIndex, nullptr);
+}
+
+ScalarizedVal extractField(
+ IRBuilder* builder,
+ ScalarizedVal const& val,
+ UInt fieldIndex,
+ IRStructKey* fieldKey)
+{
+ switch( val.flavor )
+ {
+ case ScalarizedVal::Flavor::value:
+ return ScalarizedVal::value(
+ builder->emitFieldExtract(
+ getFieldType(val.irValue->getDataType(), fieldKey),
+ val.irValue,
+ fieldKey));
+
+ case ScalarizedVal::Flavor::address:
+ {
+ auto ptrType = as<IRPtrTypeBase>(val.irValue->getDataType());
+ auto valType = ptrType->getValueType();
+ auto fieldType = getFieldType(valType, fieldKey);
+ auto fieldPtrType = builder->getPtrType(ptrType->op, fieldType);
+ return ScalarizedVal::address(
+ builder->emitFieldAddress(
+ fieldPtrType,
+ val.irValue,
+ fieldKey));
+ }
+
+ case ScalarizedVal::Flavor::tuple:
+ {
+ auto tupleVal = val.impl.As<ScalarizedTupleValImpl>();
+ return tupleVal->elements[fieldIndex].val;
+ }
+
+ default:
+ SLANG_UNEXPECTED("unimplemented");
+ UNREACHABLE_RETURN(ScalarizedVal());
+ }
+
+}
+
+ScalarizedVal adaptType(
+ IRBuilder* builder,
+ IRInst* val,
+ IRType* toType,
+ IRType* /*fromType*/)
+{
+ // TODO: actually consider what needs to go on here...
+ return ScalarizedVal::value(builder->emitConstructorInst(
+ toType,
+ 1,
+ &val));
+}
+
+ScalarizedVal adaptType(
+ IRBuilder* builder,
+ ScalarizedVal const& val,
+ IRType* toType,
+ IRType* fromType)
+{
+ switch( val.flavor )
+ {
+ case ScalarizedVal::Flavor::value:
+ return adaptType(builder, val.irValue, toType, fromType);
+ break;
+
+ case ScalarizedVal::Flavor::address:
+ {
+ auto loaded = builder->emitLoad(val.irValue);
+ return adaptType(builder, loaded, toType, fromType);
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unimplemented");
+ UNREACHABLE_RETURN(ScalarizedVal());
+ }
+}
+
+void assign(
+ IRBuilder* builder,
+ ScalarizedVal const& left,
+ ScalarizedVal const& right)
+{
+ switch( left.flavor )
+ {
+ case ScalarizedVal::Flavor::address:
+ switch( right.flavor )
+ {
+ case ScalarizedVal::Flavor::value:
+ {
+ builder->emitStore(left.irValue, right.irValue);
+ }
+ break;
+
+ case ScalarizedVal::Flavor::address:
+ {
+ auto val = builder->emitLoad(right.irValue);
+ builder->emitStore(left.irValue, val);
+ }
+ break;
+
+ case ScalarizedVal::Flavor::tuple:
+ {
+ // We are assigning from a tuple to a destination
+ // that is not a tuple. We will perform assignment
+ // element-by-element.
+ auto rightTupleVal = right.impl.As<ScalarizedTupleValImpl>();
+ UInt elementCount = rightTupleVal->elements.Count();
+
+ for( UInt ee = 0; ee < elementCount; ++ee )
+ {
+ auto rightElement = rightTupleVal->elements[ee];
+ auto leftElementVal = extractField(
+ builder,
+ left,
+ ee,
+ rightElement.key);
+ assign(builder, leftElementVal, rightElement.val);
+ }
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unimplemented");
+ break;
+ }
+ break;
+
+ case ScalarizedVal::Flavor::tuple:
+ {
+ // We have a tuple, so we are going to need to try and assign
+ // to each of its constituent fields.
+ auto leftTupleVal = left.impl.As<ScalarizedTupleValImpl>();
+ UInt elementCount = leftTupleVal->elements.Count();
+
+ for( UInt ee = 0; ee < elementCount; ++ee )
+ {
+ auto rightElementVal = extractField(
+ builder,
+ right,
+ ee,
+ leftTupleVal->elements[ee].key);
+ assign(builder, leftTupleVal->elements[ee].val, rightElementVal);
+ }
+ }
+ break;
+
+ case ScalarizedVal::Flavor::typeAdapter:
+ {
+ // We are trying to assign to something that had its type adjusted,
+ // so we will need to adjust the type of the right-hand side first.
+ //
+ // In this case we are converting to the actual type of the GLSL variable,
+ // from the "pretend" type that it had in the IR before.
+ auto typeAdapter = left.impl.As<ScalarizedTypeAdapterValImpl>();
+ auto adaptedRight = adaptType(builder, right, typeAdapter->actualType, typeAdapter->pretendType);
+ assign(builder, typeAdapter->val, adaptedRight);
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unimplemented");
+ break;
+ }
+}
+
+ScalarizedVal getSubscriptVal(
+ IRBuilder* builder,
+ IRType* elementType,
+ ScalarizedVal val,
+ IRInst* indexVal)
+{
+ switch( val.flavor )
+ {
+ case ScalarizedVal::Flavor::value:
+ return ScalarizedVal::value(
+ builder->emitElementExtract(
+ elementType,
+ val.irValue,
+ indexVal));
+
+ case ScalarizedVal::Flavor::address:
+ return ScalarizedVal::address(
+ builder->emitElementAddress(
+ builder->getPtrType(elementType),
+ val.irValue,
+ indexVal));
+
+ case ScalarizedVal::Flavor::tuple:
+ {
+ auto inputTuple = val.impl.As<ScalarizedTupleValImpl>();
+
+ RefPtr<ScalarizedTupleValImpl> resultTuple = new ScalarizedTupleValImpl();
+ resultTuple->type = elementType;
+
+ UInt elementCount = inputTuple->elements.Count();
+ UInt elementCounter = 0;
+
+ auto structType = as<IRStructType>(elementType);
+ for(auto field : structType->getFields())
+ {
+ auto tupleElementType = field->getFieldType();
+
+ UInt elementIndex = elementCounter++;
+
+ SLANG_RELEASE_ASSERT(elementIndex < elementCount);
+ auto inputElement = inputTuple->elements[elementIndex];
+
+ ScalarizedTupleValImpl::Element resultElement;
+ resultElement.key = inputElement.key;
+ resultElement.val = getSubscriptVal(
+ builder,
+ tupleElementType,
+ inputElement.val,
+ indexVal);
+
+ resultTuple->elements.Add(resultElement);
+ }
+ SLANG_RELEASE_ASSERT(elementCounter == elementCount);
+
+ return ScalarizedVal::tuple(resultTuple);
+ }
+
+ default:
+ SLANG_UNEXPECTED("unimplemented");
+ UNREACHABLE_RETURN(ScalarizedVal());
+ }
+}
+
+ScalarizedVal getSubscriptVal(
+ IRBuilder* builder,
+ IRType* elementType,
+ ScalarizedVal val,
+ UInt index)
+{
+ return getSubscriptVal(
+ builder,
+ elementType,
+ val,
+ builder->getIntValue(
+ builder->getIntType(),
+ index));
+}
+
+IRInst* materializeValue(
+ IRBuilder* builder,
+ ScalarizedVal const& val);
+
+IRInst* materializeTupleValue(
+ IRBuilder* builder,
+ ScalarizedVal val)
+{
+ auto tupleVal = val.impl.As<ScalarizedTupleValImpl>();
+ SLANG_ASSERT(tupleVal);
+
+ UInt elementCount = tupleVal->elements.Count();
+ auto type = tupleVal->type;
+
+ if( auto arrayType = as<IRArrayType>(type))
+ {
+ // The tuple represent an array, which means that the
+ // individual elements are expected to yield arrays as well.
+ //
+ // We will extract a value for each array element, and
+ // then use these to construct our result.
+
+ List<IRInst*> arrayElementVals;
+ UInt arrayElementCount = (UInt) GetIntVal(arrayType->getElementCount());
+
+ for( UInt ii = 0; ii < arrayElementCount; ++ii )
+ {
+ auto arrayElementPseudoVal = getSubscriptVal(
+ builder,
+ arrayType->getElementType(),
+ val,
+ ii);
+
+ auto arrayElementVal = materializeValue(
+ builder,
+ arrayElementPseudoVal);
+
+ arrayElementVals.Add(arrayElementVal);
+ }
+
+ return builder->emitMakeArray(
+ arrayType,
+ arrayElementVals.Count(),
+ arrayElementVals.Buffer());
+ }
+ else
+ {
+ // The tuple represents a value of some aggregate type,
+ // so we can simply materialize the elements and then
+ // construct a value of that type.
+ //
+ // TODO: this should be using a `makeStruct` instruction.
+
+ List<IRInst*> elementVals;
+ for( UInt ee = 0; ee < elementCount; ++ee )
+ {
+ auto elementVal = materializeValue(builder, tupleVal->elements[ee].val);
+ elementVals.Add(elementVal);
+ }
+
+ return builder->emitConstructorInst(
+ tupleVal->type,
+ elementVals.Count(),
+ elementVals.Buffer());
+ }
+}
+
+IRInst* materializeValue(
+ IRBuilder* builder,
+ ScalarizedVal const& val)
+{
+ switch( val.flavor )
+ {
+ case ScalarizedVal::Flavor::value:
+ return val.irValue;
+
+ case ScalarizedVal::Flavor::address:
+ {
+ auto loadInst = builder->emitLoad(val.irValue);
+ return loadInst;
+ }
+ break;
+
+ case ScalarizedVal::Flavor::tuple:
+ {
+ auto tupleVal = val.impl.As<ScalarizedTupleValImpl>();
+ return materializeTupleValue(builder, val);
+ }
+ break;
+
+ case ScalarizedVal::Flavor::typeAdapter:
+ {
+ // Somebody is trying to use a value where its actual type
+ // doesn't match the type it pretends to have. To make this
+ // work we need to adapt the type from its actual type over
+ // to its pretend type.
+ auto typeAdapter = val.impl.As<ScalarizedTypeAdapterValImpl>();
+ auto adapted = adaptType(builder, typeAdapter->val, typeAdapter->pretendType, typeAdapter->actualType);
+ return materializeValue(builder, adapted);
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unimplemented");
+ break;
+ }
+}
+
+void legalizeRayTracingEntryPointParameterForGLSL(
+ GLSLLegalizationContext* context,
+ IRFunc* func,
+ IRParam* pp,
+ VarLayout* paramLayout)
+{
+ auto builder = context->getBuilder();
+ auto paramType = pp->getDataType();
+
+ // The parameter might be either an `in` parameter,
+ // or an `out` or `in out` parameter, and in those
+ // latter cases its IR-level type will include a
+ // wrapping "pointer-like" type (e.g., `Out<Float>`
+ // instead of just `Float`).
+ //
+ // Because global shader parameters are read-only
+ // in the same way function types are, we can take
+ // care of that detail here just by allocating a
+ // global shader parameter with exactly the type
+ // of the original function parameter.
+ //
+ auto globalParam = addGlobalParam(builder->getModule(), paramType);
+ builder->addLayoutDecoration(globalParam, paramLayout);
+ moveValueBefore(globalParam, builder->getFunc());
+ pp->replaceUsesWith(globalParam);
+
+ // Because linkage between ray-tracing shaders is
+ // based on the type of incoming/outgoing payload
+ // and attribute parameters, it would be an error to
+ // eliminate the global parameter *even if* it is
+ // not actually used inside the entry point.
+ //
+ // We attach a decoration to the entry point that
+ // makes note of the dependency, so that steps
+ // like dead code elimination cannot get rid of
+ // the parameter.
+ //
+ // TODO: We could consider using a structure like
+ // this for *all* of the entry point parameters
+ // that get moved to the global scope, since SPIR-V
+ // ends up requiring such information on an `OpEntryPoint`.
+ //
+ // As a further alternative, we could decide to
+ // keep entry point varying input/outtput attached
+ // to the parameter list through all of the Slang IR
+ // steps, and only declare it as global variables at
+ // the last minute when emitting a GLSL `main` or
+ // SPIR-V for an entry point.
+ //
+ builder->addDependsOnDecoration(func, globalParam);
+}
+
+void legalizeEntryPointParameterForGLSL(
+ GLSLLegalizationContext* context,
+ IRFunc* func,
+ IRParam* pp,
+ VarLayout* paramLayout)
+{
+ auto builder = context->getBuilder();
+ auto stage = context->getStage();
+
+ // We need to create a global variable that will replace the parameter.
+ // It seems superficially obvious that the variable should have
+ // the same type as the parameter.
+ // However, if the parameter was a pointer, in order to
+ // support `out` or `in out` parameter passing, we need
+ // to be sure to allocate a variable of the pointed-to
+ // type instead.
+ //
+ // We also need to replace uses of the parameter with
+ // uses of the variable, and the exact logic there
+ // will differ a bit between the pointer and non-pointer
+ // cases.
+ auto paramType = pp->getDataType();
+
+ // First we will special-case stage input/outputs that
+ // don't fit into the standard varying model.
+ // For right now we are only doing special-case handling
+ // of geometry shader output streams.
+ if( auto paramPtrType = as<IROutTypeBase>(paramType) )
+ {
+ auto valueType = paramPtrType->getValueType();
+ if( auto gsStreamType = as<IRHLSLStreamOutputType>(valueType) )
+ {
+ // An output stream type like `TriangleStream<Foo>` should
+ // more or less translate into `out Foo` (plus scalarization).
+
+ auto globalOutputVal = createGLSLGlobalVaryings(
+ context,
+ builder,
+ valueType,
+ paramLayout,
+ LayoutResourceKind::VaryingOutput,
+ stage);
+
+ // TODO: a GS output stream might be passed into other
+ // functions, so that we should really be modifying
+ // any function that has one of these in its parameter
+ // list (and in the limit we should be leagalizing any
+ // type that nests these...).
+ //
+ // For now we will just try to deal with `Append` calls
+ // directly in this function.
+
+
+
+ for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
+ {
+ for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
+ {
+ // Is it a call?
+ if(ii->op != kIROp_Call)
+ continue;
+
+ // Is it calling the append operation?
+ auto callee = ii->getOperand(0);
+ for(;;)
+ {
+ // If the instruction is `specialize(X,...)` then
+ // we want to look at `X`, and if it is `generic { ... return R; }`
+ // then we want to look at `R`. We handle this
+ // iteratively here.
+ //
+ // TODO: This idiom seems to come up enough that we
+ // should probably have a dedicated convenience routine
+ // for this.
+ //
+ // Alternatively, we could switch the IR encoding so
+ // that decorations are added to the generic instead of the
+ // value it returns.
+ //
+ switch(callee->op)
+ {
+ case kIROp_Specialize:
+ {
+ callee = cast<IRSpecialize>(callee)->getOperand(0);
+ continue;
+ }
+
+ case kIROp_Generic:
+ {
+ auto genericResult = findGenericReturnVal(cast<IRGeneric>(callee));
+ if(genericResult)
+ {
+ callee = genericResult;
+ continue;
+ }
+ }
+
+ default:
+ break;
+ }
+ break;
+ }
+ if(callee->op != kIROp_Func)
+ continue;
+
+ // HACK: we will identify the operation based
+ // on the target-intrinsic definition that was
+ // given to it.
+ auto decoration = findTargetIntrinsicDecoration(callee, "glsl");
+ if(!decoration)
+ continue;
+
+ if(decoration->getDefinition() != UnownedStringSlice::fromLiteral("EmitVertex()"))
+ {
+ continue;
+ }
+
+ // Okay, we have a declaration, and we want to modify it!
+
+ builder->setInsertBefore(ii);
+
+ assign(builder, globalOutputVal, ScalarizedVal::value(ii->getOperand(2)));
+ }
+ }
+
+ return;
+ }
+ }
+
+ // When we have an HLSL ray tracing shader entry point,
+ // we don't want to translate the inputs/outputs for GLSL/SPIR-V
+ // according to our default rules, for two reasons:
+ //
+ // 1. The input and output for these stages are expected to
+ // be packaged into `struct` types rather than be scalarized,
+ // so the usual scalarization approach we take here should
+ // not be applied.
+ //
+ // 2. An `in out` parameter isn't just sugar for a combination
+ // of an `in` and an `out` parameter, and instead represents the
+ // read/write "payload" that was passed in. It should legalize
+ // to a single variable, and we can lower reads/writes of it
+ // directly, rather than introduce an intermediate temporary.
+ //
+ switch( stage )
+ {
+ default:
+ break;
+
+ case Stage::AnyHit:
+ case Stage::Callable:
+ case Stage::ClosestHit:
+ case Stage::Intersection:
+ case Stage::Miss:
+ case Stage::RayGeneration:
+ legalizeRayTracingEntryPointParameterForGLSL(context, func, pp, paramLayout);
+ return;
+ }
+
+ // Is the parameter type a special pointer type
+ // that indicates the parameter is used for `out`
+ // or `inout` access?
+ if(auto paramPtrType = as<IROutTypeBase>(paramType) )
+ {
+ // Okay, we have the more interesting case here,
+ // where the parameter was being passed by reference.
+ // We are going to create a local variable of the appropriate
+ // type, which will replace the parameter, along with
+ // one or more global variables for the actual input/output.
+
+ auto valueType = paramPtrType->getValueType();
+
+ auto localVariable = builder->emitVar(valueType);
+ auto localVal = ScalarizedVal::address(localVariable);
+
+ if( auto inOutType = as<IRInOutType>(paramPtrType) )
+ {
+ // In the `in out` case we need to declare two
+ // sets of global variables: one for the `in`
+ // side and one for the `out` side.
+ auto globalInputVal = createGLSLGlobalVaryings(
+ context,
+ builder, valueType, paramLayout, LayoutResourceKind::VaryingInput, stage);
+
+ assign(builder, localVal, globalInputVal);
+ }
+
+ // Any places where the original parameter was used inside
+ // the function body should instead use the new local variable.
+ // Since the parameter was a pointer, we use the variable instruction
+ // itself (which is an `alloca`d pointer) directly:
+ pp->replaceUsesWith(localVariable);
+
+ // We also need one or more global variables to write the output to
+ // when the function is done. We create them here.
+ auto globalOutputVal = createGLSLGlobalVaryings(
+ context,
+ builder, valueType, paramLayout, LayoutResourceKind::VaryingOutput, stage);
+
+ // Now we need to iterate over all the blocks in the function looking
+ // for any `return*` instructions, so that we can write to the output variable
+ for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
+ {
+ auto terminatorInst = bb->getLastInst();
+ if(!terminatorInst)
+ continue;
+
+ switch( terminatorInst->op )
+ {
+ default:
+ continue;
+
+ case kIROp_ReturnVal:
+ case kIROp_ReturnVoid:
+ break;
+ }
+
+ // We dont' re-use `builder` here because we don't want to
+ // disrupt the source location it is using for inserting
+ // temporary variables at the top of the function.
+ //
+ IRBuilder terminatorBuilder;
+ terminatorBuilder.sharedBuilder = builder->sharedBuilder;
+ terminatorBuilder.setInsertBefore(terminatorInst);
+
+ // Assign from the local variabel to the global output
+ // variable before the actual `return` takes place.
+ assign(&terminatorBuilder, globalOutputVal, localVal);
+ }
+ }
+ else
+ {
+ // This is the "easy" case where the parameter wasn't
+ // being passed by reference. We start by just creating
+ // one or more global variables to represent the parameter,
+ // and attach the required layout information to it along
+ // the way.
+
+ auto globalValue = createGLSLGlobalVaryings(
+ context,
+ builder, paramType, paramLayout, LayoutResourceKind::VaryingInput, stage);
+
+ // Next we need to replace uses of the parameter with
+ // references to the variable(s). We are going to do that
+ // somewhat naively, by simply materializing the
+ // variables at the start.
+ IRInst* materialized = materializeValue(builder, globalValue);
+
+ pp->replaceUsesWith(materialized);
+ }
+}
+
+void legalizeEntryPointForGLSL(
+ Session* session,
+ IRModule* module,
+ IRFunc* func,
+ DiagnosticSink* sink,
+ ExtensionUsageTracker* extensionUsageTracker)
+{
+ auto layoutDecoration = func->findDecoration<IRLayoutDecoration>();
+ SLANG_ASSERT(layoutDecoration);
+
+ auto entryPointLayout = dynamic_cast<EntryPointLayout*>(layoutDecoration->getLayout());
+ SLANG_ASSERT(entryPointLayout);
+
+ GLSLLegalizationContext context;
+ context.session = session;
+ context.stage = entryPointLayout->profile.GetStage();
+ context.sink = sink;
+ context.extensionUsageTracker = extensionUsageTracker;
+
+ Stage stage = entryPointLayout->profile.GetStage();
+
+ // We require that the entry-point function has no uses,
+ // because otherwise we'd invalidate the signature
+ // at all existing call sites.
+ //
+ // TODO: the right thing to do here is to split any
+ // function that both gets called as an entry point
+ // and as an ordinary function.
+ SLANG_ASSERT(!func->firstUse);
+
+ // We create a dummy IR builder, since some of
+ // the functions require it.
+ //
+ // TODO: make some of these free functions...
+ //
+ SharedIRBuilder shared;
+ shared.module = module;
+ shared.session = session;
+ IRBuilder builder;
+ builder.sharedBuilder = &shared;
+ builder.setInsertInto(func);
+
+ context.builder = &builder;
+
+ // We will start by looking at the return type of the
+ // function, because that will enable us to do an
+ // early-out check to avoid more work.
+ //
+ // Specifically, we need to check if the function has
+ // a `void` return type, because there is no work
+ // to be done on its return value in that case.
+ auto resultType = func->getResultType();
+ if(as<IRVoidType>(resultType))
+ {
+ // In this case, the function doesn't return a value
+ // so we don't need to transform its `return` sites.
+ //
+ // We can also use this opportunity to quickly
+ // check if the function has any parameters, and if
+ // it doesn't use the chance to bail out immediately.
+ if( func->getParamCount() == 0 )
+ {
+ // This function is already legal for GLSL
+ // (at least in terms of parameter/result signature),
+ // so we won't bother doing anything at all.
+ return;
+ }
+
+ // If the function does have parameters, then we need
+ // to let the logic later in this function handle them.
+ }
+ else
+ {
+ // Function returns a value, so we need
+ // to introduce a new global variable
+ // to hold that value, and then replace
+ // any `returnVal` instructions with
+ // code to write to that variable.
+
+ auto resultGlobal = createGLSLGlobalVaryings(
+ &context,
+ &builder,
+ resultType,
+ entryPointLayout->resultLayout,
+ LayoutResourceKind::VaryingOutput,
+ stage);
+
+ for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
+ {
+ // TODO: This is silly, because we are looking at every instruction,
+ // when we know that a `returnVal` should only ever appear as a
+ // terminator...
+ for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
+ {
+ if(ii->op != kIROp_ReturnVal)
+ continue;
+
+ IRReturnVal* returnInst = (IRReturnVal*) ii;
+ IRInst* returnValue = returnInst->getVal();
+
+ // Make sure we add these instructions to the right block
+ builder.setInsertInto(bb);
+
+ // Write to our global variable(s) from the value being returned.
+ assign(&builder, resultGlobal, ScalarizedVal::value(returnValue));
+
+ // Emit a `returnVoid` to end the block
+ auto returnVoid = builder.emitReturn();
+
+ // Remove the old `returnVal` instruction.
+ returnInst->removeAndDeallocate();
+
+ // Make sure to resume our iteration at an
+ // appropriate instruciton, since we deleted
+ // the one we had been using.
+ ii = returnVoid;
+ }
+ }
+ }
+
+ // Next we will walk through any parameters of the entry-point function,
+ // and turn them into global variables.
+ if( auto firstBlock = func->getFirstBlock() )
+ {
+ // Any initialization code we insert for parameters needs
+ // to be at the start of the "ordinary" instructions in the block:
+ builder.setInsertBefore(firstBlock->getFirstOrdinaryInst());
+
+ UInt paramCounter = 0;
+ for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() )
+ {
+ UInt paramIndex = paramCounter++;
+
+ // We assume that the entry-point layout includes information
+ // on each parameter, and that these arrays are kept aligned.
+ // Note that this means that any transformations that mess
+ // with function signatures will need to also update layout info...
+ //
+ SLANG_ASSERT(entryPointLayout->fields.Count() > paramIndex);
+ auto paramLayout = entryPointLayout->fields[paramIndex];
+
+ legalizeEntryPointParameterForGLSL(
+ &context,
+ func,
+ pp,
+ paramLayout);
+ }
+
+ // At this point we should have eliminated all uses of the
+ // parameters of the entry block. Also, our control-flow
+ // rules mean that the entry block cannot be the target
+ // of any branches in the code, so there can't be
+ // any control-flow ops that try to match the parameter
+ // list.
+ //
+ // We can safely go through and destroy the parameters
+ // themselves, and then clear out the parameter list.
+
+ for( auto pp = firstBlock->getFirstParam(); pp; )
+ {
+ auto next = pp->getNextParam();
+ pp->removeAndDeallocate();
+ pp = next;
+ }
+ }
+
+ // Finally, we need to patch up the type of the entry point,
+ // because it is no longer accurate.
+
+ IRFuncType* voidFuncType = builder.getFuncType(
+ 0,
+ nullptr,
+ builder.getVoidType());
+ func->setFullType(voidFuncType);
+
+ // TODO: we should technically be constructing
+ // a new `EntryPointLayout` here to reflect
+ // the way that things have been moved around.
+}
+
+} // namespace Slang
diff --git a/source/slang/ir-glsl-legalize.h b/source/slang/ir-glsl-legalize.h
new file mode 100644
index 000000000..7fabac869
--- /dev/null
+++ b/source/slang/ir-glsl-legalize.h
@@ -0,0 +1,21 @@
+// ir-glsl-legalize.h
+#pragma once
+
+namespace Slang
+{
+
+class DiagnosticSink;
+class Session;
+
+struct ExtensionUsageTracker;
+struct IRFunc;
+struct IRModule;
+
+void legalizeEntryPointForGLSL(
+ Session* session,
+ IRModule* module,
+ IRFunc* func,
+ DiagnosticSink* sink,
+ ExtensionUsageTracker* extensionUsageTracker);
+
+}
diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h
index 29743eebc..cd5407acd 100644
--- a/source/slang/ir-insts.h
+++ b/source/slang/ir-insts.h
@@ -573,6 +573,9 @@ struct IRWitnessTableEntry : IRInst
// The IR-level value that satisfies the requirement
IRUse satisfyingVal;
+ IRInst* getRequirementKey() { return getOperand(0); }
+ IRInst* getSatisfyingVal() { return getOperand(1); }
+
IR_LEAF_ISA(WitnessTableEntry)
};
@@ -791,6 +794,12 @@ struct IRBuilder
UInt argCount,
IRInst* const* args);
+ IRInst* createIntrinsicInst(
+ IRType* type,
+ IROp op,
+ UInt argCount,
+ IRInst* const* args);
+
IRInst* emitIntrinsicInst(
IRType* type,
IROp op,
@@ -1142,6 +1151,10 @@ struct IRBuilder
}
};
+void addHoistableInst(
+ IRBuilder* builder,
+ IRInst* inst);
+
// Helper to establish the source location that will be used
// by an IRBuilder.
struct IRBuilderSourceLocRAII
@@ -1168,44 +1181,6 @@ struct IRBuilderSourceLocRAII
}
};
-
-//
-
-// Interface to IR specialization for use when cloning target-specific
-// IR as part of compiling an entry point.
-//
-// TODO: we really need to move all of this logic to its own files.
-
-// `IRSpecializationState` is used as an opaque type to wrap up all
-// the data needed to perform IR specialization, without exposing
-// implementation details.
-struct IRSpecializationState;
-IRSpecializationState* createIRSpecializationState(
- EntryPointRequest* entryPointRequest,
- ProgramLayout* programLayout,
- CodeGenTarget target,
- TargetRequest* targetReq);
-void destroyIRSpecializationState(IRSpecializationState* state);
-IRModule* getIRModule(IRSpecializationState* state);
-
-struct ExtensionUsageTracker;
-
-// Clone the IR values reachable from the given entry point
-// into the IR module associated with the specialization state.
-// When multiple definitions of a symbol are found, the one
-// that is best specialized for the given `targetReq` will be
-// used.
-void specializeIRForEntryPoint(
- IRSpecializationState* state,
- EntryPointRequest* entryPointRequest,
- ExtensionUsageTracker* extensionUsageTracker);
-
-// Find suitable uses of the `specialize` instruction that
-// can be replaced with references to specialized functions.
-void specializeGenerics(
- IRModule* module,
- CodeGenTarget target);
-
//
void markConstExpr(
@@ -1214,6 +1189,10 @@ void markConstExpr(
//
+IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration(
+ IRInst* val,
+ String const& targetName);
+
}
#endif
diff --git a/source/slang/ir-link.cpp b/source/slang/ir-link.cpp
new file mode 100644
index 000000000..586172cb2
--- /dev/null
+++ b/source/slang/ir-link.cpp
@@ -0,0 +1,1297 @@
+// ir-link.cpp
+#include "ir-link.h"
+
+#include "ir.h"
+#include "ir-insts.h"
+#include "mangle.h"
+
+namespace Slang
+{
+
+StructTypeLayout* getGlobalStructLayout(
+ ProgramLayout* programLayout);
+
+// Needed for lookup up entry-point layouts.
+//
+// TODO: maybe arrange so that codegen is driven from the layout layer
+// instead of the input/request layer.
+EntryPointLayout* findEntryPointLayout(
+ ProgramLayout* programLayout,
+ EntryPointRequest* entryPointRequest);
+
+struct IRSpecSymbol : RefObject
+{
+ IRInst* irGlobalValue;
+ RefPtr<IRSpecSymbol> nextWithSameName;
+};
+
+struct IRSpecEnv
+{
+ IRSpecEnv* parent = nullptr;
+
+ // A map from original values to their cloned equivalents.
+ typedef Dictionary<IRInst*, IRInst*> ClonedValueDictionary;
+ ClonedValueDictionary clonedValues;
+};
+
+struct IRSharedSpecContext
+{
+ // The code-generation target in use
+ CodeGenTarget target;
+
+ // The specialized module we are building
+ RefPtr<IRModule> module;
+
+ // The original, unspecialized module we are copying
+ IRModule* originalModule;
+
+ // A map from mangled symbol names to zero or
+ // more global IR values that have that name,
+ // in the *original* module.
+ typedef Dictionary<String, RefPtr<IRSpecSymbol>> SymbolDictionary;
+ SymbolDictionary symbols;
+
+ SharedIRBuilder sharedBuilderStorage;
+ IRBuilder builderStorage;
+
+ // The "global" specialization environment.
+ IRSpecEnv globalEnv;
+};
+
+struct IRSpecContextBase
+{
+ // A map from the mangled name of a global variable
+ // to the layout to use for it.
+ Dictionary<String, VarLayout*> globalVarLayouts;
+
+ IRSharedSpecContext* shared;
+
+ IRSharedSpecContext* getShared() { return shared; }
+
+ IRModule* getModule() { return getShared()->module; }
+
+ IRModule* getOriginalModule() { return getShared()->originalModule; }
+
+ IRSharedSpecContext::SymbolDictionary& getSymbols() { return getShared()->symbols; }
+
+ // The current specialization environment to use.
+ IRSpecEnv* env = nullptr;
+ IRSpecEnv* getEnv()
+ {
+ // TODO: need to actually establish environments on contexts we create.
+ //
+ // Or more realistically we need to change the whole approach
+ // to specialization and cloning so that we don't try to share
+ // logic between two very different cases.
+
+
+ return env;
+ }
+
+ // The IR builder to use for creating nodes
+ IRBuilder* builder;
+
+ // A callback to be used when a value that is not registerd in `clonedValues`
+ // is needed during cloning. This gives the subtype a chance to intercept
+ // the operation and clone (or not) as needed.
+ virtual IRInst* maybeCloneValue(IRInst* originalVal)
+ {
+ return originalVal;
+ }
+};
+
+void registerClonedValue(
+ IRSpecContextBase* context,
+ IRInst* clonedValue,
+ IRInst* originalValue)
+{
+ if(!originalValue)
+ return;
+
+ // TODO: now that things are scoped using environments, we
+ // shouldn't be running into the cases where a value with
+ // the same key already exists. This should be changed to
+ // an `Add()` call.
+ //
+ context->getEnv()->clonedValues[originalValue] = clonedValue;
+}
+
+// Information on values to use when registering a cloned value
+struct IROriginalValuesForClone
+{
+ IRInst* originalVal = nullptr;
+ IRSpecSymbol* sym = nullptr;
+
+ IROriginalValuesForClone() {}
+
+ IROriginalValuesForClone(IRInst* originalValue)
+ : originalVal(originalValue)
+ {}
+
+ IROriginalValuesForClone(IRSpecSymbol* symbol)
+ : sym(symbol)
+ {}
+};
+
+void registerClonedValue(
+ IRSpecContextBase* context,
+ IRInst* clonedValue,
+ IROriginalValuesForClone const& originalValues)
+{
+ registerClonedValue(context, clonedValue, originalValues.originalVal);
+ for( auto s = originalValues.sym; s; s = s->nextWithSameName )
+ {
+ registerClonedValue(context, clonedValue, s->irGlobalValue);
+ }
+}
+
+IRInst* cloneInst(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRInst* originalInst,
+ IROriginalValuesForClone const& originalValues);
+
+IRInst* cloneInst(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRInst* originalInst)
+{
+ return cloneInst(context, builder, originalInst, originalInst);
+}
+
+ /// Clone any decorations from `originalValue` onto `clonedValue`
+void cloneDecorations(
+ IRSpecContextBase* context,
+ IRInst* clonedValue,
+ IRInst* originalValue)
+{
+ // TODO: In many cases we might be able to use this as a general-purpose
+ // place to do cloning of *all* the children of an instruction, and
+ // not just its decorations. We should look to refactor this code
+ // later.
+
+ IRBuilder builderStorage = *context->builder;
+ IRBuilder* builder = &builderStorage;
+ builder->setInsertInto(clonedValue);
+
+
+ SLANG_UNUSED(context);
+ for(auto originalDecoration : originalValue->getDecorations())
+ {
+ cloneInst(context, builder, originalDecoration);
+ }
+
+ // We will also clone the location here, just because this is a convenient bottleneck
+ clonedValue->sourceLoc = originalValue->sourceLoc;
+}
+
+ /// Clone any decorations and children from `originalValue` onto `clonedValue`
+void cloneDecorationsAndChildren(
+ IRSpecContextBase* context,
+ IRInst* clonedValue,
+ IRInst* originalValue)
+{
+ IRBuilder builderStorage = *context->builder;
+ IRBuilder* builder = &builderStorage;
+ builder->setInsertInto(clonedValue);
+
+ SLANG_UNUSED(context);
+ for(auto originalItem : originalValue->getDecorationsAndChildren())
+ {
+ cloneInst(context, builder, originalItem);
+ }
+
+ // We will also clone the location here, just because this is a convenient bottleneck
+ clonedValue->sourceLoc = originalValue->sourceLoc;
+}
+
+// We use an `IRSpecContext` for the case where we are cloning
+// code from one or more input modules to create a "linked" output
+// module. Along the way, we will resolve profile-specific functions
+// to the best definition for a given target.
+//
+struct IRSpecContext : IRSpecContextBase
+{
+ // Override the "maybe clone" logic so that we always clone
+ virtual IRInst* maybeCloneValue(IRInst* originalVal) override;
+};
+
+
+IRInst* cloneGlobalValue(IRSpecContext* context, IRInst* originalVal);
+
+IRInst* cloneValue(
+ IRSpecContextBase* context,
+ IRInst* originalValue);
+
+IRType* cloneType(
+ IRSpecContextBase* context,
+ IRType* originalType);
+
+IRInst* IRSpecContext::maybeCloneValue(IRInst* originalValue)
+{
+ switch (originalValue->op)
+ {
+ case kIROp_StructType:
+ case kIROp_Func:
+ case kIROp_Generic:
+ case kIROp_GlobalVar:
+ case kIROp_GlobalConstant:
+ case kIROp_GlobalParam:
+ case kIROp_StructKey:
+ case kIROp_GlobalGenericParam:
+ case kIROp_WitnessTable:
+ return cloneGlobalValue(this, originalValue);
+
+ case kIROp_BoolLit:
+ {
+ IRConstant* c = (IRConstant*)originalValue;
+ return builder->getBoolValue(c->value.intVal != 0);
+ }
+ break;
+
+
+ case kIROp_IntLit:
+ {
+ IRConstant* c = (IRConstant*)originalValue;
+ return builder->getIntValue(cloneType(this, c->getDataType()), c->value.intVal);
+ }
+ break;
+
+ case kIROp_FloatLit:
+ {
+ IRConstant* c = (IRConstant*)originalValue;
+ return builder->getFloatValue(cloneType(this, c->getDataType()), c->value.floatVal);
+ }
+ break;
+
+ case kIROp_StringLit:
+ {
+ IRConstant* c = (IRConstant*)originalValue;
+ return builder->getStringValue(c->getStringSlice());
+ }
+ break;
+
+ case kIROp_PtrLit:
+ {
+ IRConstant* c = (IRConstant*)originalValue;
+ return builder->getPtrValue(c->value.ptrVal);
+ }
+ break;
+
+ default:
+ {
+ // In the deafult case, assume that we have some sort of "hoistable"
+ // instruction that requires us to create a clone of it.
+
+ UInt argCount = originalValue->getOperandCount();
+ IRInst* clonedValue = builder->createIntrinsicInst(
+ cloneType(this, originalValue->getFullType()),
+ originalValue->op,
+ argCount, nullptr);
+ registerClonedValue(this, clonedValue, originalValue);
+ for (UInt aa = 0; aa < argCount; ++aa)
+ {
+ IRInst* originalArg = originalValue->getOperand(aa);
+ IRInst* clonedArg = cloneValue(this, originalArg);
+ clonedValue->getOperands()[aa].init(clonedValue, clonedArg);
+ }
+ cloneDecorationsAndChildren(this, clonedValue, originalValue);
+
+ addHoistableInst(builder, clonedValue);
+
+ return clonedValue;
+ }
+ break;
+ }
+}
+
+IRInst* cloneValue(
+ IRSpecContextBase* context,
+ IRInst* originalValue);
+
+// Find a pre-existing cloned value, or return null if none is available.
+IRInst* findClonedValue(
+ IRSpecContextBase* context,
+ IRInst* originalValue)
+{
+ IRInst* clonedValue = nullptr;
+ for (auto env = context->getEnv(); env; env = env->parent)
+ {
+ if (env->clonedValues.TryGetValue(originalValue, clonedValue))
+ {
+ return clonedValue;
+ }
+ }
+
+ return nullptr;
+}
+
+IRInst* cloneValue(
+ IRSpecContextBase* context,
+ IRInst* originalValue)
+{
+ if (!originalValue)
+ return nullptr;
+
+ if (IRInst* clonedValue = findClonedValue(context, originalValue))
+ return clonedValue;
+
+ return context->maybeCloneValue(originalValue);
+}
+
+IRType* cloneType(
+ IRSpecContextBase* context,
+ IRType* originalType)
+{
+ return (IRType*)cloneValue(context, originalType);
+}
+
+void cloneGlobalValueWithCodeCommon(
+ IRSpecContextBase* context,
+ IRGlobalValueWithCode* clonedValue,
+ IRGlobalValueWithCode* originalValue);
+
+IRRate* cloneRate(
+ IRSpecContextBase* context,
+ IRRate* rate)
+{
+ return (IRRate*) cloneType(context, rate);
+}
+
+void maybeSetClonedRate(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRInst* clonedValue,
+ IRInst* originalValue)
+{
+ if(auto rate = originalValue->getRate() )
+ {
+ clonedValue->setFullType(builder->getRateQualifiedType(
+ cloneRate(context, rate),
+ clonedValue->getFullType()));
+ }
+}
+
+IRGlobalVar* cloneGlobalVarImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRGlobalVar* originalVar,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedVar = builder->createGlobalVar(
+ cloneType(context, originalVar->getDataType()->getValueType()));
+
+ maybeSetClonedRate(context, builder, clonedVar, originalVar);
+
+ registerClonedValue(context, clonedVar, originalValues);
+
+ // Clone any code in the body of the variable, since this
+ // represents the initializer.
+ cloneGlobalValueWithCodeCommon(
+ context,
+ clonedVar,
+ originalVar);
+
+ return clonedVar;
+}
+
+IRGlobalConstant* cloneGlobalConstantImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRGlobalConstant* originalVal,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedVal = builder->createGlobalConstant(
+ cloneType(context, originalVal->getFullType()));
+ registerClonedValue(context, clonedVal, originalValues);
+
+ // Clone any code in the body of the constant, since this
+ // represents the initializer.
+ cloneGlobalValueWithCodeCommon(
+ context,
+ clonedVal,
+ originalVal);
+
+ return clonedVal;
+}
+
+void cloneSimpleGlobalValueImpl(
+ IRSpecContextBase* context,
+ IRInst* originalInst,
+ IROriginalValuesForClone const& originalValues,
+ IRInst* clonedInst,
+ bool registerValue = true)
+{
+ if (registerValue)
+ registerClonedValue(context, clonedInst, originalValues);
+
+ // Set up an IR builder for inserting into the inst
+ IRBuilder builderStorage = *context->builder;
+ IRBuilder* builder = &builderStorage;
+ builder->setInsertInto(clonedInst);
+
+ // Clone any children of the instruction
+ for (auto child : originalInst->getDecorationsAndChildren())
+ {
+ cloneInst(context, builder, child);
+ }
+}
+
+IRGlobalParam* cloneGlobalParamImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRGlobalParam* originalVal,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedVal = builder->createGlobalParam(
+ cloneType(context, originalVal->getFullType()));
+ cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal);
+
+ if(auto linkage = originalVal->findDecoration<IRLinkageDecoration>())
+ {
+ auto mangledName = String(linkage->getMangledName());
+ VarLayout* layout = nullptr;
+ if (context->globalVarLayouts.TryGetValue(mangledName, layout))
+ {
+ builder->addLayoutDecoration(clonedVal, layout);
+ }
+ }
+
+ return clonedVal;
+}
+
+IRGeneric* cloneGenericImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRGeneric* originalVal,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedVal = builder->emitGeneric();
+ registerClonedValue(context, clonedVal, originalValues);
+
+ // Clone any code in the body of the generic, since this
+ // computes its result value.
+ cloneGlobalValueWithCodeCommon(
+ context,
+ clonedVal,
+ originalVal);
+
+ return clonedVal;
+}
+
+IRStructKey* cloneStructKeyImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRStructKey* originalVal,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedVal = builder->createStructKey();
+ cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal);
+ return clonedVal;
+}
+
+IRGlobalGenericParam* cloneGlobalGenericParamImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRGlobalGenericParam* originalVal,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedVal = builder->emitGlobalGenericParam();
+ cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal);
+ return clonedVal;
+}
+
+
+IRWitnessTable* cloneWitnessTableImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRWitnessTable* originalTable,
+ IROriginalValuesForClone const& originalValues,
+ IRWitnessTable* dstTable = nullptr,
+ bool registerValue = true)
+{
+ auto clonedTable = dstTable ? dstTable : builder->createWitnessTable();
+ cloneSimpleGlobalValueImpl(context, originalTable, originalValues, clonedTable, registerValue);
+ return clonedTable;
+}
+
+IRWitnessTable* cloneWitnessTableWithoutRegistering(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRWitnessTable* originalTable,
+ IRWitnessTable* dstTable = nullptr)
+{
+ return cloneWitnessTableImpl(context, builder, originalTable, IROriginalValuesForClone(), dstTable, false);
+}
+
+IRStructType* cloneStructTypeImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRStructType* originalStruct,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedStruct = builder->createStructType();
+ cloneSimpleGlobalValueImpl(context, originalStruct, originalValues, clonedStruct);
+ return clonedStruct;
+}
+
+
+IRInterfaceType* cloneInterfaceTypeImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRInterfaceType* originalInterface,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedInterface = builder->createInterfaceType();
+ cloneSimpleGlobalValueImpl(context, originalInterface, originalValues, clonedInterface);
+ return clonedInterface;
+}
+
+void cloneGlobalValueWithCodeCommon(
+ IRSpecContextBase* context,
+ IRGlobalValueWithCode* clonedValue,
+ IRGlobalValueWithCode* originalValue)
+{
+ // Next we are going to clone the actual code.
+ IRBuilder builderStorage = *context->builder;
+ IRBuilder* builder = &builderStorage;
+ builder->setInsertInto(clonedValue);
+
+ cloneDecorations(context, clonedValue, originalValue);
+
+ // We will walk through the blocks of the function, and clone each of them.
+ //
+ // We need to create the cloned blocks first, and then walk through them,
+ // because blocks might be forward referenced (this is not possible
+ // for other cases of instructions).
+ for (auto originalBlock = originalValue->getFirstBlock();
+ originalBlock;
+ originalBlock = originalBlock->getNextBlock())
+ {
+ IRBlock* clonedBlock = builder->createBlock();
+ clonedValue->addBlock(clonedBlock);
+ registerClonedValue(context, clonedBlock, originalBlock);
+
+#if 0
+ // We can go ahead and clone parameters here, while we are at it.
+ builder->curBlock = clonedBlock;
+ for (auto originalParam = originalBlock->getFirstParam();
+ originalParam;
+ originalParam = originalParam->getNextParam())
+ {
+ IRParam* clonedParam = builder->emitParam(
+ context->maybeCloneType(
+ originalParam->getFullType()));
+ cloneDecorations(context, clonedParam, originalParam);
+ registerClonedValue(context, clonedParam, originalParam);
+ }
+#endif
+ }
+
+ // Okay, now we are in a good position to start cloning
+ // the instructions inside the blocks.
+ {
+ IRBlock* ob = originalValue->getFirstBlock();
+ IRBlock* cb = clonedValue->getFirstBlock();
+ while (ob)
+ {
+ SLANG_ASSERT(cb);
+
+ builder->setInsertInto(cb);
+ for (auto oi = ob->getFirstInst(); oi; oi = oi->getNextInst())
+ {
+ cloneInst(context, builder, oi);
+ }
+
+ ob = ob->getNextBlock();
+ cb = cb->getNextBlock();
+ }
+ }
+
+}
+
+void checkIRDuplicate(IRInst* inst, IRInst* moduleInst, UnownedStringSlice const& mangledName)
+{
+#ifdef _DEBUG
+ for (auto child : moduleInst->getDecorationsAndChildren())
+ {
+ if (child == inst)
+ continue;
+
+ if(auto childLinkage = child->findDecoration<IRLinkageDecoration>())
+ {
+ if(mangledName == childLinkage->getMangledName())
+ {
+ SLANG_UNEXPECTED("duplicate global instruction");
+ }
+ }
+ }
+#else
+ SLANG_UNREFERENCED_PARAMETER(inst);
+ SLANG_UNREFERENCED_PARAMETER(moduleInst);
+ SLANG_UNREFERENCED_PARAMETER(mangledName);
+#endif
+}
+
+void cloneFunctionCommon(
+ IRSpecContextBase* context,
+ IRFunc* clonedFunc,
+ IRFunc* originalFunc,
+ bool checkDuplicate = true)
+{
+ // First clone all the simple properties.
+ clonedFunc->setFullType(cloneType(context, originalFunc->getFullType()));
+
+ cloneGlobalValueWithCodeCommon(
+ context,
+ clonedFunc,
+ originalFunc);
+
+ // Shuffle the function to the end of the list, because
+ // it needs to follow its dependencies.
+ //
+ // TODO: This isn't really a good requirement to place on the IR...
+ clonedFunc->moveToEnd();
+
+ if( checkDuplicate )
+ {
+ if( auto linkage = clonedFunc->findDecoration<IRLinkageDecoration>() )
+ {
+ checkIRDuplicate(clonedFunc, context->getModule()->getModuleInst(), linkage->getMangledName());
+ }
+ }
+}
+
+IRFunc* specializeIRForEntryPoint(
+ IRSpecContext* context,
+ EntryPointRequest* entryPointRequest,
+ EntryPointLayout* entryPointLayout)
+{
+ // Look up the IR symbol by name
+ auto mangledName = getMangledName(entryPointRequest->decl);
+ RefPtr<IRSpecSymbol> sym;
+ if (!context->getSymbols().TryGetValue(mangledName, sym))
+ {
+ SLANG_UNEXPECTED("no matching IR symbol");
+ return nullptr;
+ }
+
+ // TODO: deal with the case where we might
+ // have multiple versions...
+
+ auto globalValue = sym->irGlobalValue;
+ if (globalValue->op != kIROp_Func)
+ {
+ SLANG_UNEXPECTED("expected an IR function");
+ return nullptr;
+ }
+ auto originalFunc = (IRFunc*)globalValue;
+
+ // Create a clone for the IR function
+ auto clonedFunc = context->builder->createFunc();
+
+ // Note: we do *not* register this cloned declaration
+ // as the cloned value for the original symbol.
+ // This is kind of a kludge, but it ensures that
+ // in the unlikely case that the function is both
+ // used as an entry point and a callable function
+ // (yes, this would imply recursion...) we actually
+ // have two copies, which lets us arbitrarily
+ // transform the entry point to meet target requirements.
+ //
+ // TODO: The above statement is kind of bunk, though,
+ // because both versions of the function would have
+ // the same mangled name... :(
+
+ // We need to clone all the properties of the original
+ // function, including any blocks, their parameters,
+ // and their instructions.
+ cloneFunctionCommon(context, clonedFunc, originalFunc);
+
+ // We need to attach the layout information for
+ // the entry point to this declaration, so that
+ // we can use it to inform downstream code emit.
+ context->builder->addLayoutDecoration(
+ clonedFunc,
+ entryPointLayout);
+
+ // We will also go on and attach layout information
+ // to the function parameters, so that we have it
+ // available directly on the parameters, rather
+ // than having to look it up on the original entry-point layout.
+ if( auto firstBlock = clonedFunc->getFirstBlock() )
+ {
+ UInt paramLayoutCount = entryPointLayout->fields.Count();
+ UInt paramCounter = 0;
+ for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() )
+ {
+ UInt paramIndex = paramCounter++;
+ if( paramIndex < paramLayoutCount )
+ {
+ auto paramLayout = entryPointLayout->fields[paramIndex];
+ context->builder->addLayoutDecoration(
+ pp,
+ paramLayout);
+ }
+ else
+ {
+ SLANG_UNEXPECTED("too many parameters");
+ }
+ }
+ }
+
+ return clonedFunc;
+}
+
+// Get a string form of the target so that we can
+// use it to match against target-specialization modifiers
+//
+// TODO: We shouldn't be using strings for this.
+String getTargetName(IRSpecContext* context)
+{
+ switch( context->shared->target )
+ {
+ case CodeGenTarget::HLSL:
+ return "hlsl";
+
+ case CodeGenTarget::GLSL:
+ return "glsl";
+
+ default:
+ SLANG_UNEXPECTED("unhandled case");
+ UNREACHABLE_RETURN("unknown");
+ }
+}
+
+// How specialized is a given declaration for the chosen target?
+enum class TargetSpecializationLevel
+{
+ specializedForOtherTarget = 0,
+ notSpecialized,
+ specializedForTarget,
+};
+
+TargetSpecializationLevel getTargetSpecialiationLevel(
+ IRInst* inVal,
+ String const& targetName)
+{
+ // HACK: Currently the front-end is placing modifiers related
+ // to target specialization on nodes like functions, even when
+ // those functions are being returned by a generic. This
+ // means that we need to try and inspect the value being
+ // returned by the generic if we are looking at a generic.
+ IRInst* val = inVal;
+ while( auto genericVal = as<IRGeneric>(val) )
+ {
+ auto firstBlock = genericVal->getFirstBlock();
+ if(!firstBlock) break;
+
+ auto returnInst = as<IRReturnVal>(firstBlock->getLastInst());
+ if(!returnInst) break;
+
+ val = returnInst->getVal();
+ }
+
+ TargetSpecializationLevel result = TargetSpecializationLevel::notSpecialized;
+ for(auto dd : val->getDecorations())
+ {
+ if(dd->op != kIROp_TargetDecoration)
+ continue;
+
+ auto decoration = (IRTargetDecoration*) dd;
+ if(String(decoration->getTargetName()) == targetName)
+ return TargetSpecializationLevel::specializedForTarget;
+
+ result = TargetSpecializationLevel::specializedForOtherTarget;
+ }
+
+ return result;
+}
+
+// Is `newVal` marked as being a better match for our
+// chosen code-generation target?
+//
+// TODO: there is a missing step here where we need
+// to check if things are even available in the first place...
+bool isBetterForTarget(
+ IRSpecContext* context,
+ IRInst* newVal,
+ IRInst* oldVal)
+{
+ String targetName = getTargetName(context);
+
+ // For right now every declaration might have zero or more
+ // modifiers, representing the targets for which it is specialized.
+ // Each modifier has a single string "tag" to represent a target.
+ // We thus decide that a declaration is "more specialized" by:
+ //
+ // - Does it have a modifier with a tag with the string for the current target?
+ // If yes, it is the most specialized it can be.
+ //
+ // - Does it have a no tags? Then it is "unspecialized" and that is okay.
+ //
+ // - Does it have a modifier with a tag for a *different* target?
+ // If yes, then it shouldn't even be usable on this target.
+ //
+ // Longer term a better approach is to think of this in terms
+ // of a "disjunction of conjunctions" that is:
+ //
+ // (A and B and C) or (A and D) or (E) or (F and G) ...
+ //
+ // A code generation target would then consist of a
+ // conjunction of invidual tags:
+ //
+ // (HLSL and SM_4_0 and Vertex and ...)
+ //
+ // A declaration is *applicable* on a target if one of
+ // its conjunctions of tags is a subset of the target's.
+ //
+ // One declaration is *better* than another on a target
+ // if it is applicable and its tags are a superset
+ // of the other's.
+
+ auto newLevel = getTargetSpecialiationLevel(newVal, targetName);
+ auto oldLevel = getTargetSpecialiationLevel(oldVal, targetName);
+ if(newLevel != oldLevel)
+ return UInt(newLevel) > UInt(oldLevel);
+
+ // All other factors being equal, a definition is
+ // better than a declaration.
+ auto newIsDef = isDefinition(newVal);
+ auto oldIsDef = isDefinition(oldVal);
+ if (newIsDef != oldIsDef)
+ return newIsDef;
+
+ return false;
+}
+
+IRFunc* cloneFuncImpl(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRFunc* originalFunc,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedFunc = builder->createFunc();
+ registerClonedValue(context, clonedFunc, originalValues);
+ cloneFunctionCommon(context, clonedFunc, originalFunc);
+ return clonedFunc;
+}
+
+
+IRInst* cloneInst(
+ IRSpecContextBase* context,
+ IRBuilder* builder,
+ IRInst* originalInst,
+ IROriginalValuesForClone const& originalValues)
+{
+ switch (originalInst->op)
+ {
+ // We need to special-case any instruction that is not
+ // allocated like an ordinary `IRInst` with trailing args.
+ case kIROp_Func:
+ return cloneFuncImpl(context, builder, cast<IRFunc>(originalInst), originalValues);
+
+ case kIROp_GlobalVar:
+ return cloneGlobalVarImpl(context, builder, cast<IRGlobalVar>(originalInst), originalValues);
+
+ case kIROp_GlobalConstant:
+ return cloneGlobalConstantImpl(context, builder, cast<IRGlobalConstant>(originalInst), originalValues);
+
+ case kIROp_GlobalParam:
+ return cloneGlobalParamImpl(context, builder, cast<IRGlobalParam>(originalInst), originalValues);
+
+ case kIROp_WitnessTable:
+ return cloneWitnessTableImpl(context, builder, cast<IRWitnessTable>(originalInst), originalValues);
+
+ case kIROp_StructType:
+ return cloneStructTypeImpl(context, builder, cast<IRStructType>(originalInst), originalValues);
+
+ case kIROp_InterfaceType:
+ return cloneInterfaceTypeImpl(context, builder, cast<IRInterfaceType>(originalInst), originalValues);
+
+ case kIROp_Generic:
+ return cloneGenericImpl(context, builder, cast<IRGeneric>(originalInst), originalValues);
+
+ case kIROp_StructKey:
+ return cloneStructKeyImpl(context, builder, cast<IRStructKey>(originalInst), originalValues);
+
+ case kIROp_GlobalGenericParam:
+ return cloneGlobalGenericParamImpl(context, builder, cast<IRGlobalGenericParam>(originalInst), originalValues);
+
+ default:
+ break;
+ }
+
+ // The common case is that we just need to construct a cloned
+ // instruction with the right number of operands, intialize
+ // it, and then add it to the sequence.
+ UInt argCount = originalInst->getOperandCount();
+ IRInst* clonedInst = builder->createIntrinsicInst(
+ cloneType(context, originalInst->getFullType()),
+ originalInst->op,
+ argCount, nullptr);
+ registerClonedValue(context, clonedInst, originalValues);
+ auto oldBuilder = context->builder;
+ context->builder = builder;
+ for (UInt aa = 0; aa < argCount; ++aa)
+ {
+ IRInst* originalArg = originalInst->getOperand(aa);
+ IRInst* clonedArg = cloneValue(context, originalArg);
+ clonedInst->getOperands()[aa].init(clonedInst, clonedArg);
+ }
+ builder->addInst(clonedInst);
+ context->builder = oldBuilder;
+ cloneDecorations(context, clonedInst, originalInst);
+
+ return clonedInst;
+}
+
+IRInst* cloneGlobalValueImpl(
+ IRSpecContext* context,
+ IRInst* originalInst,
+ IROriginalValuesForClone const& originalValues)
+{
+ auto clonedValue = cloneInst(context, &context->shared->builderStorage, originalInst, originalValues);
+ clonedValue->moveToEnd();
+ return clonedValue;
+}
+
+
+ /// Clone a global value, which has the given `originalLinkage`.
+ ///
+ /// The `originalVal` is a known global IR value with that linkage, if one is available.
+ /// (It is okay for this parameter to be null).
+ ///
+IRInst* cloneGlobalValueWithLinkage(
+ IRSpecContext* context,
+ IRInst* originalVal,
+ IRLinkageDecoration* originalLinkage)
+{
+ // If the global value being cloned is already in target module, don't clone
+ // Why checking this?
+ // When specializing a generic function G (which is already in target module),
+ // where G calls a normal function F (which is already in target module),
+ // then when we are making a copy of G via cloneFuncCommom(), it will recursively clone F,
+ // however we don't want to make a duplicate of F in the target module.
+ if (originalVal->getParent() == context->getModule()->getModuleInst())
+ return originalVal;
+
+ // Check if we've already cloned this value, for the case where
+ // an original value has already been established.
+ if (originalVal)
+ {
+ if (IRInst* clonedVal = findClonedValue(context, originalVal))
+ {
+ return clonedVal;
+ }
+ }
+
+ if(!originalLinkage)
+ {
+ // If there is no mangled name, then we assume this is a local symbol,
+ // and it can't possibly have multiple declarations.
+ return cloneGlobalValueImpl(context, originalVal, IROriginalValuesForClone());
+ }
+
+ //
+ // We will scan through all of the available declarations
+ // with the same mangled name as `originalVal` and try
+ // to pick the "best" one for our target.
+
+ auto mangledName = String(originalLinkage->getMangledName());
+ RefPtr<IRSpecSymbol> sym;
+ if( !context->getSymbols().TryGetValue(mangledName, sym) )
+ {
+ if(!originalVal)
+ return nullptr;
+
+ // This shouldn't happen!
+ SLANG_UNEXPECTED("no matching values registered");
+ UNREACHABLE_RETURN(cloneGlobalValueImpl(context, originalVal, IROriginalValuesForClone()));
+ }
+
+ // We will try to track the "best" declaration we can find.
+ //
+ // Generally, one declaration wil lbe better than another if it is
+ // more specialized for the chosen target. Otherwise, we simply favor
+ // definitions over declarations.
+ //
+ IRInst* bestVal = sym->irGlobalValue;
+ for( auto ss = sym->nextWithSameName; ss; ss = ss->nextWithSameName )
+ {
+ IRInst* newVal = ss->irGlobalValue;
+ if(isBetterForTarget(context, newVal, bestVal))
+ bestVal = newVal;
+ }
+
+ // Check if we've already cloned this value, for the case where
+ // we didn't have an original value (just a name), but we've
+ // now found a representative value.
+ if (!originalVal)
+ {
+ if (IRInst* clonedVal = findClonedValue(context, bestVal))
+ {
+ return clonedVal;
+ }
+ }
+
+ return cloneGlobalValueImpl(context, bestVal, IROriginalValuesForClone(sym));
+}
+
+// Clone a global value, where `originalVal` is one declaration/definition, but we might
+// have to consider others, in order to find the "best" version of the symbol.
+IRInst* cloneGlobalValue(IRSpecContext* context, IRInst* originalVal)
+{
+ // We are being asked to clone a particular global value, but in
+ // the IR that comes out of the front-end there could still
+ // be multiple, target-specific, declarations of any given
+ // global value, all of which share the same mangled name.
+ return cloneGlobalValueWithLinkage(
+ context,
+ originalVal,
+ originalVal->findDecoration<IRLinkageDecoration>());
+}
+
+void insertGlobalValueSymbol(
+ IRSharedSpecContext* sharedContext,
+ IRInst* gv)
+{
+ auto linkage = gv->findDecoration<IRLinkageDecoration>();
+
+ // Don't try to register a symbol for global values
+ // that don't have linkage.
+ //
+ if (!linkage)
+ return;
+
+ auto mangledName = String(linkage->getMangledName());
+
+ RefPtr<IRSpecSymbol> sym = new IRSpecSymbol();
+ sym->irGlobalValue = gv;
+
+ RefPtr<IRSpecSymbol> prev;
+ if (sharedContext->symbols.TryGetValue(mangledName, prev))
+ {
+ sym->nextWithSameName = prev->nextWithSameName;
+ prev->nextWithSameName = sym;
+ }
+ else
+ {
+ sharedContext->symbols.Add(mangledName, sym);
+ }
+}
+
+void insertGlobalValueSymbols(
+ IRSharedSpecContext* sharedContext,
+ IRModule* originalModule)
+{
+ if (!originalModule)
+ return;
+
+ for(auto ii : originalModule->getGlobalInsts())
+ {
+ insertGlobalValueSymbol(sharedContext, ii);
+ }
+}
+
+void initializeSharedSpecContext(
+ IRSharedSpecContext* sharedContext,
+ Session* session,
+ IRModule* module,
+ IRModule* originalModule,
+ CodeGenTarget target)
+{
+
+ SharedIRBuilder* sharedBuilder = &sharedContext->sharedBuilderStorage;
+ sharedBuilder->module = nullptr;
+ sharedBuilder->session = session;
+
+ IRBuilder* builder = &sharedContext->builderStorage;
+ builder->sharedBuilder = sharedBuilder;
+
+ if( !module )
+ {
+ module = builder->createModule();
+ }
+
+ sharedBuilder->module = module;
+ sharedContext->module = module;
+ sharedContext->originalModule = originalModule;
+ sharedContext->target = target;
+ // We will populate a map with all of the IR values
+ // that use the same mangled name, to make lookup easier
+ // in other steps.
+ insertGlobalValueSymbols(sharedContext, originalModule);
+}
+
+// implementation provided in parameter-binding.cpp
+RefPtr<ProgramLayout> specializeProgramLayout(
+ TargetRequest * targetReq,
+ ProgramLayout* programLayout,
+ SubstitutionSet typeSubst);
+
+struct IRSpecializationState
+{
+ ProgramLayout* programLayout;
+ CodeGenTarget target;
+ TargetRequest* targetReq;
+
+ IRModule* irModule = nullptr;
+ RefPtr<ProgramLayout> newProgramLayout;
+
+ IRSharedSpecContext sharedContextStorage;
+ IRSpecContext contextStorage;
+
+ IRSpecEnv globalEnv;
+
+ IRSharedSpecContext* getSharedContext() { return &sharedContextStorage; }
+ IRSpecContext* getContext() { return &contextStorage; }
+
+ IRSpecializationState()
+ {
+ contextStorage.env = &globalEnv;
+ }
+
+ ~IRSpecializationState()
+ {
+ newProgramLayout = nullptr;
+ contextStorage = IRSpecContext();
+ sharedContextStorage = IRSharedSpecContext();
+ }
+};
+
+IRSpecializationState* createIRSpecializationState(
+ EntryPointRequest* entryPointRequest,
+ ProgramLayout* programLayout,
+ CodeGenTarget target,
+ TargetRequest* targetReq)
+{
+ IRSpecializationState* state = new IRSpecializationState();
+
+ state->programLayout = programLayout;
+ state->target = target;
+ state->targetReq = targetReq;
+
+
+ auto compileRequest = entryPointRequest->compileRequest;
+ auto translationUnit = entryPointRequest->getTranslationUnit();
+ auto originalIRModule = translationUnit->irModule;
+
+ auto sharedContext = state->getSharedContext();
+ initializeSharedSpecContext(
+ sharedContext,
+ compileRequest->mSession,
+ nullptr,
+ originalIRModule,
+ target);
+
+ state->irModule = sharedContext->module;
+
+ // We also need to attach the IR definitions for symbols from
+ // any loaded modules:
+ for (auto loadedModule : compileRequest->loadedModulesList)
+ {
+ insertGlobalValueSymbols(sharedContext, loadedModule->irModule);
+ }
+
+ auto context = state->getContext();
+ context->shared = sharedContext;
+ context->builder = &sharedContext->builderStorage;
+
+ // Now specialize the program layout using the substitution
+ //
+ // TODO: The specialization of the layout is conceptually an AST-level operations,
+ // and shouldn't be done here in the IR at all.
+ //
+ RefPtr<ProgramLayout> newProgramLayout = specializeProgramLayout(
+ targetReq,
+ programLayout,
+ SubstitutionSet(entryPointRequest->globalGenericSubst));
+
+ // TODO: we need to register the (IR-level) arguments of the global generic parameters as the
+ // substitutions for the generic parameters in the original IR.
+
+ // applyGlobalGenericParamSubsitution(...);
+
+
+ state->newProgramLayout = newProgramLayout;
+
+ // Next, we want to optimize lookup for layout infromation
+ // associated with global declarations, so that we can
+ // look things up based on the IR values (using mangled names)
+ auto globalStructLayout = getGlobalStructLayout(newProgramLayout);
+ for (auto globalVarLayout : globalStructLayout->fields)
+ {
+ auto mangledName = getMangledName(globalVarLayout->varDecl);
+ context->globalVarLayouts.AddIfNotExists(mangledName, globalVarLayout);
+ }
+
+ // for now, clone all unreferenced witness tables
+ for (auto sym :context->getSymbols())
+ {
+ if (sym.Value->irGlobalValue->op == kIROp_WitnessTable)
+ cloneGlobalValue(context, (IRWitnessTable*)sym.Value->irGlobalValue);
+ }
+ return state;
+}
+
+void destroyIRSpecializationState(IRSpecializationState* state)
+{
+ delete state;
+}
+
+IRModule* getIRModule(IRSpecializationState* state)
+{
+ return state->irModule;
+}
+
+IRFunc* specializeIRForEntryPoint(
+ IRSpecializationState* state,
+ EntryPointRequest* entryPointRequest)
+{
+ auto translationUnit = entryPointRequest->getTranslationUnit();
+ auto originalIRModule = translationUnit->irModule;
+ if (!originalIRModule)
+ {
+ // We should already have emitted IR for the original
+ // translation unit, and it we don't have it, then
+ // we are now in trouble.
+ return nullptr;
+ }
+
+ auto context = state->getContext();
+ auto newProgramLayout = state->newProgramLayout;
+
+ auto entryPointLayout = findEntryPointLayout(newProgramLayout, entryPointRequest);
+
+
+ // Next, we make sure to clone the global value for
+ // the entry point function itself, and rely on
+ // this step to recursively copy over anything else
+ // it might reference.
+ auto irEntryPoint = specializeIRForEntryPoint(context, entryPointRequest, entryPointLayout);
+
+ // HACK: right now the bindings for global generic parameters are coming in
+ // as part of the original IR module, and we need to make sure these get
+ // copied over, even if they aren't referenced.
+ //
+ for(auto inst : originalIRModule->getGlobalInsts())
+ {
+ auto bindInst = as<IRBindGlobalGenericParam>(inst);
+ if(!bindInst)
+ continue;
+
+ cloneValue(context, bindInst);
+ }
+
+
+ // TODO: *technically* we should consider the case where
+ // we have global variables with initializers, since
+ // these should get run whether or not the entry point
+ // references them.
+ return irEntryPoint;
+}
+
+
+
+} // namespace Slang
diff --git a/source/slang/ir-link.h b/source/slang/ir-link.h
new file mode 100644
index 000000000..18940f825
--- /dev/null
+++ b/source/slang/ir-link.h
@@ -0,0 +1,33 @@
+// ir-link.h
+#pragma once
+
+#include "compiler.h"
+
+namespace Slang
+{
+ // Interface to IR specialization for use when cloning target-specific
+ // IR as part of compiling an entry point.
+
+ // `IRSpecializationState` is used as an opaque type to wrap up all
+ // the data needed to perform IR specialization, without exposing
+ // implementation details.
+ struct IRSpecializationState;
+ IRSpecializationState* createIRSpecializationState(
+ EntryPointRequest* entryPointRequest,
+ ProgramLayout* programLayout,
+ CodeGenTarget target,
+ TargetRequest* targetReq);
+ void destroyIRSpecializationState(IRSpecializationState* state);
+ IRModule* getIRModule(IRSpecializationState* state);
+
+ struct ExtensionUsageTracker;
+
+ // Clone the IR values reachable from the given entry point
+ // into the IR module associated with the specialization state.
+ // When multiple definitions of a symbol are found, the one
+ // that is best specialized for the given `targetReq` will be
+ // used.
+ IRFunc* specializeIRForEntryPoint(
+ IRSpecializationState* state,
+ EntryPointRequest* entryPointRequest);
+}
diff --git a/source/slang/ir-specialize-resources.cpp b/source/slang/ir-specialize-resources.cpp
index e6d4351f2..0108a91f8 100644
--- a/source/slang/ir-specialize-resources.cpp
+++ b/source/slang/ir-specialize-resources.cpp
@@ -2,6 +2,7 @@
#include "ir-specialize-resources.h"
#include "ir.h"
+#include "ir-clone.h"
#include "ir-insts.h"
namespace Slang
@@ -297,50 +298,11 @@ struct ResourceParameterSpecializationContext
// well as a "key" to identify the specialized function
// that is required.
//
- // The key type is similar to that used for generic specialization
- // elsewhere in the IR code. It might be worth pulling this
- // notion out somewhere more centralized, but we are dealing
- // with the code duplication for now.
+ // We will use the key type defined as part of the IR cloning
+ // infrastructure, which uses a sequence of `IRInst*`s
+ // to hold the state of the key:
//
- struct Key
- {
- // The structure of a specialization key will be a list
- // of instructions starting with the function to be specialized,
- // and then having one or more entries for each parameter
- // that is being specialized to indicate the value to which
- // it is being specialized (e.g. the global shader parameter).
- //
- List<IRInst*> vals;
-
- // In order to use this type as a `Dictionary` key we
- // need it to support equality and hashing, but the
- // implementaitons are straightforward.
- //
- // TODO: honestly we might consider having `GetHashCode`
- // and `operator==` defined for `List<T>`.
-
- bool operator==(Key const& other) const
- {
- auto valCount = vals.Count();
- if(valCount != other.vals.Count()) return false;
- for( UInt ii = 0; ii < valCount; ++ii )
- {
- if(vals[ii] != other.vals[ii]) return false;
- }
- return true;
- }
-
- int GetHashCode() const
- {
- auto valCount = vals.Count();
- int hash = Slang::GetHashCode(valCount);
- for( UInt ii = 0; ii < valCount; ++ii )
- {
- hash = combineHash(hash, Slang::GetHashCode(vals[ii]));
- }
- return hash;
- }
- };
+ typedef IRSimpleSpecializationKey Key;
// As indicated above, the information we collect about a call
// site consists of the key for the specialized function we
@@ -768,199 +730,7 @@ struct ResourceParameterSpecializationContext
}
}
- // Now that we've covered how all the relevant information
- // gets gathered, we can turn our attention to the
- // meat of actually generating a specialized version
- // of a function.
- //
- // For the most part, this is just a matter of *cloning*
- // the original function, while keeping around a mapping
- // from original values/instructions to their replacements.
- //
- // Because we might perform specialization many times,
- // it will get is own nested context type.
- //
- struct CloneContext
- {
- // When cloning, we need an IR builder to use for
- // making new instructions.
- //
- IRBuilder* builder;
-
- // We also need a mapping from old instruction to their
- // new equivalents, which will serve double duty:
- //
- // * Before we start cloning, this will be used to
- // register the mapping from things that are to be
- // replaced entirely (like function parameters to
- // be specialized away) to their replacements (like
- // a global shader parameter).
- //
- // * During the process of cloning, this will be
- // updated as we clone instructions so that when
- // an instruction later in the function refers to
- // something from earlier, we can look up the
- // replacement.
- //
- Dictionary<IRInst*, IRInst*> mapOldValToNew;
-
- // Whenever we need to look up an operand value
- // during the cloning process we'll use `cloneOperand`,
- // which mostly just uses `mapOldValToNew`.
- //
- IRInst* cloneOperand(IRInst* oldOperand)
- {
- IRInst* newOperand = nullptr;
- if(mapOldValToNew.TryGetValue(oldOperand, newOperand))
- return newOperand;
-
- // The one wrinkle here, and the place where
- // this cloning logic differs from some other
- // IR cloning implementations we have lying around,
- // is that when we *don't* find an instruction in
- // our map, we automatically assume it is not
- // something taht needs to be cloned, so that the old
- // value is fine to use as-is.
- //
- // Note that this puts an ordering constraint on
- // our work: if we are going to clone some instruction
- // A, then we had better clone it *before* anything
- // that uses A as an operand.
- //
- return oldOperand;
- }
-
- // The SSA property and the way we have structured
- // our "phi nodes" (block parameters) means that
- // just going through the children of a function,
- // and then the children of a block will generally
- // do the Right Thing and always visit an instruction
- // before its uses.
- //
- // The big exception to this is that branch instructions
- // can refer to blocks later in the same function.
- //
- // We work around this sort of problem in a fairly
- // general fashion, by splitting the cloning of
- // an instruction into two steps.
- //
- // The first step is just to clone the instruction
- // and its direct operands, but not any decorations
- // or children.
- //
- IRInst* cloneInstAndOperands(IRInst* oldInst)
- {
- // In order to clone an instruction we first
- // need to map its operands over to their
- // new values.
- //
- List<IRInst*> newOperands;
- UInt operandCount = oldInst->getOperandCount();
- for(UInt ii = 0; ii < operandCount; ++ii)
- {
- auto oldOperand = oldInst->getOperand(ii);
- auto newOperand = cloneOperand(oldOperand);
- newOperands.Add(newOperand);
- }
-
- // Now we can just tell the IR builder to
- // go and create an instruction directly
- //
- // Note: this logic would not handle any instructions
- // with special-case data attached, but that only
- // applies to `IRConstant`s at this point, and those
- // should only appear at the global scope rather than
- // in function bodies.
- //
- SLANG_ASSERT(!as<IRConstant>(oldInst));
- auto newInst = builder->emitIntrinsicInst(
- oldInst->getFullType(),
- oldInst->op,
- newOperands.Count(),
- newOperands.Buffer());
-
- return newInst;
- }
-
- // The second phase of cloning an instruction is to clone
- // its decorations and children. This step only needs to
- // be performed on those instructions that *have* decorations
- // and/or children.
- //
- // The complexity of this step comes from the fact that it
- // needs to sequence the two phases of cloning for any
- // child instructions. We will do this by performing the
- // first phase of cloning, and building up a list of
- // children that require the second phase of processing.
- // Each entry in that list will be a pair of an old instruction
- // and its new clone.
- //
- struct OldNewPair
- {
- IRInst* oldInst;
- IRInst* newInst;
- };
- void cloneInstDecorationsAndChildren(IRInst* oldInst, IRInst* newInst)
- {
- List<OldNewPair> pairs;
- for( auto oldChild : oldInst->getDecorationsAndChildren() )
- {
- // As a very subtle special case, if one of the children
- // of our `oldInst` already has a registered replacement,
- // then we don't want to clone it (not least because
- // the `Dictionary::Add` method would give us an error
- // when we try to insert a new value for the same key).
- //
- // This arises for entries in `mapOldValToNew` that were
- // seeded before cloning begain (e.g., the function
- // parameters that are to be replaced).
- //
- if(mapOldValToNew.ContainsKey(oldChild))
- continue;
-
- // Because we are re-using the same IR builder in
- // multiple places, we need to make sure to set
- // its insertion location before creating the
- // child instruction.
- //
- builder->setInsertInto(newInst);
-
- // Now we can perform the first phase of cloning
- // on the child, and register it in our map from
- // old to new values.
- //
- auto newChild = cloneInstAndOperands(oldChild);
- mapOldValToNew.Add(oldChild, newChild);
-
- // If an only if the old child had decorations
- // or children, we will register it into our
- // list for processing in the second phase.
- //
- if( oldChild->getFirstDecorationOrChild() )
- {
- OldNewPair pair;
- pair.oldInst = oldChild;
- pair.newInst = newChild;
- pairs.Add(pair);
- }
- }
-
- // Once we have done first-phase processing for
- // all child instructions, we scan through those
- // in the list that required second-phase processing,
- // and clone their decorations and/or children recursively.
- //
- for( auto pair : pairs )
- {
- auto oldChild = pair.oldInst;
- auto newChild = pair.newInst;
-
- cloneInstDecorationsAndChildren(oldChild, newChild);
- }
- }
- };
-
- // With all of that machinery out of the way,
+ // With all of that data-gathering code out of the way,
// we are now prepared to walk through the process of
// specializing a given callee function based on
// the information we have gathered.
@@ -969,12 +739,14 @@ struct ResourceParameterSpecializationContext
IRFunc* oldFunc,
FuncSpecializationInfo const& funcInfo)
{
- // We start by setting up our context for cloning
- // the blocks and instructions in the old function.
+ // We will make use of the infrastructure for cloning
+ // IR code, that is defined in `ir-clone.{h,cpp}`.
//
- auto builder = getBuilder();
- CloneContext cloneContext;
- cloneContext.builder = builder;
+ // In order to do the cloning work we need an
+ // "environment" that will map old values to
+ // their replacements.
+ //
+ IRCloneEnv cloneEnv;
// Next we iterate over the parameters of the old
// function, and register each as being mapped
@@ -986,7 +758,7 @@ struct ResourceParameterSpecializationContext
{
UInt paramIndex = paramCounter++;
auto newVal = funcInfo.replacementsForOldParameters[paramIndex];
- cloneContext.mapOldValToNew.Add(oldParam, newVal);
+ cloneEnv.mapOldValToNew.Add(oldParam, newVal);
}
// Next we will create the skeleton of the new
@@ -1003,6 +775,8 @@ struct ResourceParameterSpecializationContext
{
paramTypes.Add(param->getFullType());
}
+
+ auto builder = getBuilder();
IRType* funcType = builder->getFuncType(
paramTypes.Count(),
paramTypes.Buffer(),
@@ -1015,11 +789,15 @@ struct ResourceParameterSpecializationContext
// of cloning the function (since `IRFunc`s have no
// operands).
//
- // We can now call into our `CloneContext` to perform
- // the second phase of cloning, which will recursively
+ // We can now use the shared IR cloning infrastructure
+ // to perform the second phase of cloning, which will recursively
// clone any nested decorations, blocks, and instructions.
//
- cloneContext.cloneInstDecorationsAndChildren(oldFunc, newFunc);
+ cloneInstDecorationsAndChildren(
+ &cloneEnv,
+ builder->sharedBuilder,
+ oldFunc,
+ newFunc);
// We are almost done at this point, except that `newFunc`
// is lacking its parameters, as well as any of the body
diff --git a/source/slang/ir-specialize.cpp b/source/slang/ir-specialize.cpp
new file mode 100644
index 000000000..4a20c6195
--- /dev/null
+++ b/source/slang/ir-specialize.cpp
@@ -0,0 +1,685 @@
+// ir-specialize.cpp
+#include "ir-specialize.h"
+
+#include "ir.h"
+#include "ir-clone.h"
+#include "ir-insts.h"
+
+namespace Slang
+{
+
+// This file implements the primary specialization pass, that takes
+// generic/polymorphic Slang code and specializes/monomorphises it.
+//
+// At present this primarily means generating specialized copies
+// of generic functions/types based on the concrete types used
+// at specialization sites, and also specializing instances
+// of witness-table lookup to directly refer to the concrete
+// values for witnesses when witness tables are known.
+//
+// Eventually, this pass will also need to perform specialization
+// of functions to argument values for parameters that must
+// be compile-time constants, and simplification of code using
+// existential (interface) types for function parameters/results.
+
+struct SpecializationContext
+{
+ // We know that we can only perform specialization when all
+ // of the arguments to a generic are also fully specialized.
+ // The "is fully specialized" condition is something we
+ // need to solve for over the program, because the fully-
+ // specialized-ness of an instruction depends on the
+ // fully-specialized-ness of its operands.
+ //
+ // We will build an explicit hash set to encode those
+ // instructions that are fully specialized.
+ //
+ HashSet<IRInst*> fullySpecializedInsts;
+
+ // An instruction is then fully specialized if and only
+ // if it is in our set.
+ //
+ bool isInstFullySpecialized(
+ IRInst* inst)
+ {
+ // A small wrinkle is that a null instruction pointer
+ // sometimes appears a a type, and so should be treated
+ // as fully specialized too.
+ //
+ // TODO: It would be nice to remove this wrinkle.
+ //
+ if(!inst) return true;
+
+ return fullySpecializedInsts.Contains(inst);
+ }
+
+ // When an instruction isn't fully specialized, but its operands *are*
+ // then it is a candidate for specialization itself, so we will have
+ // a query to check for the "all operands fully specialized" case.
+ //
+ bool areAllOperandsFullySpecialized(
+ IRInst* inst)
+ {
+ if(!isInstFullySpecialized(inst->getFullType()))
+ return false;
+
+ UInt operandCount = inst->getOperandCount();
+ for(UInt ii = 0; ii < operandCount; ++ii)
+ {
+ IRInst* operand = inst->getOperand(ii);
+ if(!isInstFullySpecialized(operand))
+ return false;
+ }
+
+ return true;
+ }
+
+ // We will also maintain a work list of instructions that are
+ // not fully specialized, and that we want to consider for
+ // specialization.
+ //
+ List<IRInst*> workList;
+
+ // When we consider adding an instruction to our work list
+ // we will try to be careful and only add things that aren't
+ // already fully specialized.
+ //
+ void maybeAddToWorkList(IRInst* inst)
+ {
+ if(isInstFullySpecialized(inst))
+ return;
+
+ workList.Add(inst);
+ }
+
+ // When we go to populate the work list by recursively
+ // traversing some code, we will be careful to *not*
+ // add generics or their children to the work list,
+ // and will instead consider a generic to be "fully
+ // specialized" already (because uses of that generic
+ // as an *operand* should be seen as fully specialized
+ // references).
+ //
+ void populateWorkListRec(
+ IRInst* inst)
+ {
+ if(auto genericInst = as<IRGeneric>(inst))
+ {
+ fullySpecializedInsts.Add(genericInst);
+ }
+ else
+ {
+ maybeAddToWorkList(inst);
+
+ for(auto child : inst->getChildren())
+ {
+ populateWorkListRec(child);
+ }
+ }
+ }
+
+ // Of course, somewhere along the way we expect
+ // to run into uses of `specialize(...)` instructions
+ // to bind a generic to arguments that we want to
+ // specialize into concrete code.
+ //
+ // We also know that if we encouter `specialize(g, a, b, c)`
+ // and then later `specialize(g, a, b, c)` again, we
+ // only want to generate the specialized code for `g<a,b,c>`
+ // *once*, and re-use it for both versions.
+ //
+ // We will cache existing specializations of generic function/types
+ // using the simple key type defined as part of the IR cloning infrastructure.
+ //
+ typedef IRSimpleSpecializationKey Key;
+ Dictionary<Key, IRInst*> genericSpecializations;
+
+ // We will also use some shared IR building state across
+ // all of our specialization/cloning steps.
+ //
+ SharedIRBuilder sharedBuilderStorage;
+
+ // Now let's look at the task of finding or generation a
+ // specialization of some generic `g`, given a specialization
+ // instruction like `specialize(g, a, b, c)`.
+ //
+ // The `specializeGeneric` function will return a value
+ // suitable for use as a replacement for the `specialize(...)`
+ // instruction.
+ //
+ IRInst* specializeGeneric(
+ IRGeneric* genericVal,
+ IRSpecialize* specializeInst)
+ {
+ // First, we want to see if an existing specialization
+ // has already been made. To do that we will construct a key
+ // for lookup in the generic specialization context.
+ //
+ // Our key will consist of the identity of the generic
+ // being specialized, and each of the argument values
+ // being pased to it. In our hypothetical example of
+ // `specialize(g, a, b, c)` the key will then be
+ // the array `[g, a, b, c]`.
+ //
+ Key key;
+ key.vals.Add(specializeInst->getBase());
+ UInt argCount = specializeInst->getArgCount();
+ for( UInt ii = 0; ii < argCount; ++ii )
+ {
+ key.vals.Add(specializeInst->getArg(ii));
+ }
+
+ {
+ // We use our generated key to look for an
+ // existing specialization that has been registered.
+ // If one is found, our work is done.
+ //
+ IRInst* specializedVal = nullptr;
+ if(genericSpecializations.TryGetValue(key, specializedVal))
+ return specializedVal;
+ }
+
+ // If no existing specialization is found, we need
+ // to create the specialization instead.
+ //
+ // Effectively this amounts to "calling" the generic
+ // on its concrete argument values and computing the
+ // result it returns.
+ //
+ // For now, all of our generics consist of a single
+ // basic block, so we can "call" them just by
+ // cloning the instructions in their single block
+ // into the global scope, using an environment for
+ // cloning that maps the generic parameters to
+ // the concrete arguments that were provided
+ // by the `specialize(...)` instruction.
+ //
+ IRCloneEnv env;
+
+ // We will walk through the parameters of the generic and
+ // register the corresponding argument of the `specialize`
+ // instruction to be used as the "cloned" value for each
+ // parameter.
+ //
+ // Suppose we are looking at `specialize(g, a, b, c)` and `g` has
+ // three generic parameters: `T`, `U`, and `V`. Then we will
+ // be initializing our environment to map `T -> a`, `U -> b`,
+ // and `V -> c`.
+ //
+ UInt argCounter = 0;
+ for( auto param : genericVal->getParams() )
+ {
+ UInt argIndex = argCounter++;
+ SLANG_ASSERT(argIndex < specializeInst->getArgCount());
+
+ IRInst* arg = specializeInst->getArg(argIndex);
+
+ env.mapOldValToNew.Add(param, arg);
+ }
+
+ // We will set up an IR builder for insertion
+ // into the global scope, at the same location
+ // as the original generic.
+ //
+ IRBuilder builderStorage;
+ IRBuilder* builder = &builderStorage;
+ builder->sharedBuilder = &sharedBuilderStorage;
+ builder->setInsertBefore(genericVal);
+
+ // Now we will run through the body of the generic and
+ // clone each of its instructions into the global scope,
+ // until we reach a `return` instruction.
+ //
+ for( auto bb : genericVal->getBlocks() )
+ {
+ // We expect a generic to only ever contain a single block.
+ //
+ SLANG_ASSERT(bb == genericVal->getFirstBlock());
+
+ // We will iterate over the non-parameter ("ordinary")
+ // instructions only, because parameters were dealt
+ // with explictly at an earlier point.
+ //
+ for( auto ii : bb->getOrdinaryInsts() )
+ {
+ // The last block of the generic is expected to end with
+ // a `return` instruction for the specialized value that
+ // comes out of the abstraction.
+ //
+ // We thus use that cloned value as the result of the
+ // specialization step.
+ //
+ if( auto returnValInst = as<IRReturnVal>(ii) )
+ {
+ auto specializedVal = findCloneForOperand(&env, returnValInst->getVal());
+
+ // The value that was returned from evaluating
+ // the generic is the specialized value, and we
+ // need to remember it in our dictionary of
+ // specializations so that we don't instantiate
+ // this generic again for the same arguments.
+ //
+ genericSpecializations.Add(key, specializedVal);
+
+ return specializedVal;
+ }
+
+ // For any instruction other than a `return`, we will
+ // simply clone it completely into the global scope.
+ //
+ IRInst* clonedInst = cloneInst(&env, builder, ii);
+
+ // Any new instructions we create during cloning were
+ // not present when we initially built our work list,
+ // so we need to make sure to consider them now.
+ //
+ // This is important for the cases where one generic
+ // invokes another, because there will be `specialize`
+ // operations nested inside the first generic that refer
+ // to the second.
+ //
+ populateWorkListRec(clonedInst);
+ }
+ }
+
+ // If we reach this point, something went wrong, because we
+ // never encountered a `return` inside the body of the generic.
+ //
+ SLANG_UNEXPECTED("no return from generic");
+ UNREACHABLE_RETURN(nullptr);
+ }
+
+ // The logic for generating a specialization of an IR generic
+ // relies on the ability to "evaluate" the code in the body of
+ // the generic, but that obviously doesn't work if we don't
+ // actually have the full definition for the body.
+ //
+ // This can arise in particular for builtin operations/types.
+ //
+ // Before calling `specializeGeneric()` we need to make sure
+ // that the generic is actually amenable to specialization,
+ // by looking at whether it is a definition or a declaration.
+ //
+ bool canSpecializeGeneric(
+ IRGeneric* generic)
+ {
+ // It is possible to have multiple "layers" of generics
+ // (e.g., when a generic function is nested in a generic
+ // type). Therefore we need to drill down through all
+ // of the layers present to see if at the leaf we have
+ // something that looks like a definition.
+ //
+ IRGeneric* g = generic;
+ for(;;)
+ {
+ // Given the generic `g`, we will find the value
+ // it appears to return in its body.
+ //
+ auto val = findGenericReturnVal(g);
+ if(!val)
+ return false;
+
+ // If `g` returns an inner generic, then we need
+ // to drill down further.
+ //
+ if (auto nestedGeneric = as<IRGeneric>(val))
+ {
+ g = nestedGeneric;
+ continue;
+ }
+
+ // Once we've found the leaf value that will be produced
+ // after all specialization is complete, we can check
+ // whether it looks like a definition or not.
+ //
+ return isDefinition(val);
+ }
+ }
+
+ // Now that we know when we can specialize a generic, and how
+ // to do it, we can write a subroutine that takes a
+ // `specialize(g, a, b, c, ...)` instruction and performs
+ // specialization if it is possible.
+ //
+ void maybeSpecializeGeneric(
+ IRSpecialize* specInst)
+ {
+ // The invariant that the arguments are fully specialized
+ // should mean that `a, b, c, ...` are in a form that
+ // we can work with, but it does *not* guarantee
+ // that the `g` operand is something we can work with.
+ //
+ // We can only perform specialization in the case where
+ // the base `g` is a known `generic` instruction.
+ //
+ auto baseVal = specInst->getBase();
+ auto genericVal = as<IRGeneric>(baseVal);
+ if(!genericVal)
+ return;
+
+ // We can also only specialize a generic if it
+ // represents a definition rather than a declaration.
+ //
+ if(!canSpecializeGeneric(genericVal))
+ return;
+
+ // Once we know that specialization is possible,
+ // the actual work is fairly simple.
+ //
+ // First, we find or generate a specialized
+ // version of the result of the generic (a specialized
+ // type, function, or whatever).
+ //
+ auto specializedVal = specializeGeneric(genericVal, specInst);
+
+ // Then we simply replace any uses of the `specialize(...)`
+ // instruction with the specialized value and delete
+ // the `specialize(...)` instruction from existence.
+ //
+ specInst->replaceUsesWith(specializedVal);
+ specInst->removeAndDeallocate();
+ }
+
+ // The basic rule we are following is that once all the operands
+ // to an instruction are fully specialized, we are safe
+ // to specialize the instruction itself, but the work
+ // required to specialize an instruction depends on the
+ // form of the instruction.
+ //
+ void fullySpecializeInst(
+ IRInst* inst)
+ {
+ // A precondition of the `fullySpecializeInst` operation
+ // is that the operands to `inst` have all been fully
+ // specialized.
+ //
+ SLANG_ASSERT(areAllOperandsFullySpecialized(inst));
+
+ switch(inst->op)
+ {
+ default:
+ // The default case is that there is nothing to
+ // be done to specialize an instruction; once all
+ // of its operands are specialized it is safe
+ // to consider the instruction itself as fully
+ // specialized.
+ //
+ break;
+
+ case kIROp_Specialize:
+ // The logic for specializing a `specialize(...)`
+ // instruction has already been elaborated above.
+ //
+ maybeSpecializeGeneric(cast<IRSpecialize>(inst));
+ break;
+
+ case kIROp_lookup_interface_method:
+ // The remaining case we need to consider here
+ // is when we have a `lookup_witness_method` instruction
+ // that is being applied to a concrete witness table,
+ // because we can specialize it to just be a direct
+ // reference to the actual witness value from the table.
+ //
+ maybeSpecializeWitnessLookup(cast<IRLookupWitnessMethod>(inst));
+ break;
+ }
+ }
+
+ void maybeSpecializeWitnessLookup(
+ IRLookupWitnessMethod* lookupInst)
+ {
+ // Note: While we currently have named the instruction
+ // `lookup_witness_method`, the `method` part is a misnomer
+ // and the same instruction can look up *any* interface
+ // requirement based on the witness table that provides
+ // a conformance, and the "key" that indicates the interface
+ // requirement.
+
+ // We can only specialize in the case where the lookup
+ // is being done on a concrete witness table, and not
+ // the result of a `specialize` instruction or other
+ // operation that will yield such a table.
+ //
+ auto witnessTable = as<IRWitnessTable>(lookupInst->getWitnessTable());
+ if(!witnessTable)
+ return;
+
+ // Because we have a concrete witness table, we can
+ // use it to look up the IR value that satisfies
+ // the given interface requirement.
+ //
+ auto requirementKey = lookupInst->getRequirementKey();
+ auto satisfyingVal = findWitnessVal(witnessTable, requirementKey);
+
+ // We expect to always find a satisfying value, but
+ // we will go ahead and code defensively so that
+ // we leave "correct" but unspecialized code if
+ // we cannot find a concrete value to use.
+ //
+ if(!satisfyingVal)
+ return;
+
+ // At this point, we know that `satisfyingVal` is what
+ // would result from executing this `lookup_witness_method`
+ // instruciton dynamically, so we can go ahead and
+ // replace the original instruction with that value.
+ //
+ lookupInst->replaceUsesWith(satisfyingVal);
+ lookupInst->removeAndDeallocate();
+ }
+
+ // The above subroutine needed a way to look up
+ // the satisfying value for a given requirement
+ // key in a concrete witness table, so let's
+ // define that now.
+ //
+ IRInst* findWitnessVal(
+ IRWitnessTable* witnessTable,
+ IRInst* requirementKey)
+ {
+ // A witness table is basically just a container
+ // for key-value pairs, and so the best we can
+ // do for now is a naive linear search.
+ //
+ for( auto entry : witnessTable->getEntries() )
+ {
+ if (requirementKey == entry->getRequirementKey())
+ {
+ return entry->getSatisfyingVal();
+ }
+ }
+ return nullptr;
+ }
+
+ // All of the machinery has been defined above, so
+ // we can now walk through the flow of the overall
+ // specialization pass.
+ //
+ void processModule(IRModule* module)
+ {
+ // We start by initializing our shared IR building state,
+ // since we will re-use that state for any code we
+ // generate along the way.
+ //
+ SharedIRBuilder* sharedBuilder = &sharedBuilderStorage;
+ sharedBuilder->module = module;
+ sharedBuilder->session = module->session;
+
+ // The unspecialized IR we receive as input will have
+ // `IRBindGlobalGenericParam` instructions that associate
+ // each global-scope generic parameter (a type, witness
+ // table, or what-have-you) with the value that it should
+ // be bound to for the purposes of this code-generation
+ // pass.
+ //
+ // Before doing any other specialization work, we will
+ // iterate over these instructions (which may only
+ // appear at the global scope) and use them to drive
+ // replacement of the given generic type parameter with
+ // the desired concrete value.
+ //
+ auto moduleInst = module->getModuleInst();
+ for(auto inst : moduleInst->getChildren())
+ {
+ // We only want to consider the `bind_global_generic_param`
+ // instructions, and ignore everything else.
+ //
+ auto bindInst = as<IRBindGlobalGenericParam>(inst);
+ if(!bindInst)
+ continue;
+
+ // HACK: Our current front-end emit logic can end up emitting multiple
+ // `bind_global_generic_param` instructions for the same parameter. This is
+ // a buggy behavior, but a real fix would require refactoring the way
+ // global generic arguments are specified today.
+ //
+ // For now we will do a sanity check to detect parameters that
+ // have already been specialized.
+ //
+ if( !as<IRGlobalGenericParam>(bindInst->getOperand(0)) )
+ {
+ // The "parameter" operand is no longer a parameter, so it
+ // seems things must have been specialized already.
+ //
+ continue;
+ }
+
+ // The actual logic for applying the substitution is
+ // almost trivial: we will replace any uses of the
+ // global generic parameter with its desired value.
+ //
+ auto param = bindInst->getParam();
+ auto val = bindInst->getVal();
+ param->replaceUsesWith(val);
+ }
+ {
+ // Now that we've replaced any uses of global generic
+ // parameters, we will do a second pass to remove
+ // the parameters and any `bind_global_generic_param`
+ // instructions, since both should be dead/unused.
+ //
+ IRInst* next = nullptr;
+ for(auto inst = moduleInst->getFirstChild(); inst; inst = next)
+ {
+ next = inst->getNextInst();
+
+ switch(inst->op)
+ {
+ default:
+ break;
+
+ case kIROp_GlobalGenericParam:
+ case kIROp_BindGlobalGenericParam:
+ // A `bind_global_generic_param` instruction should
+ // have no uses in the first place, and all the global
+ // generic parameters should have had their uses replaced.
+ //
+ SLANG_ASSERT(!inst->firstUse);
+ inst->removeAndDeallocate();
+ break;
+ }
+ }
+ }
+
+ // Now that we've eliminated all cases of global generic parameters,
+ // we should now have the properties that:
+ //
+ // 1. Execution starts in non-generic code, with no unbound
+ // generic parameters in scope.
+ //
+ // 2. Any case where non-generic code makes use of a generic
+ // type/function, there will be a `specialize` instruction
+ // that specifies both the generic and the (concrete) type
+ // arguments that should be provided to it.
+ //
+ // Our primary goal is then to find `specialize` instructions that
+ // can be replaced with references to, e.g., a suitably
+ // specialized function, and to resolve any `lookup_interface_method`
+ // instructions to the concrete value fetched from a witness
+ // table.
+ //
+ // We need to be careful of a few things:
+ //
+ // * It would not in general make sense to consider specialize-able
+ // instructions under an `IRGeneric`, since that could mean "specializing"
+ // code to parameter values that are still unknown.
+ //
+ // * We *also* need to be careful not to specialize something when one
+ // or more of its inputs is also a `specialize` or `lookup_interface_method`
+ // instruction, because then we'd be propagating through non-concrete
+ // values.
+ //
+ // The approach we use here is to build a work list of instructions
+ // that are candidates for specialization, and then process them one
+ // at a time to see if we can make some forward progress.
+ //
+ // We will start by recursively walking all the instructions to add
+ // the appropriate ones to our work list:
+ //
+ populateWorkListRec(moduleInst);
+
+ // We want to treat our work list like a queue rather than
+ // a stack, because we populated it in program order,
+ // and fully-specialized-ness will tend to flow top-down.
+ //
+ // To accomplish this we will ping-pong between the
+ // real work list and a copy so that we can iterate over
+ // one list while adding to the other.
+ //
+ List<IRInst*> workListCopy;
+ while(workList.Count() != 0)
+ {
+ workListCopy.Clear();
+ workListCopy.SwapWith(workList);
+
+ for( auto inst : workListCopy )
+ {
+ // We need to check whether it is possible to specialize
+ // the instruction yet. It might not be specializable
+ // because its operands haven't been specialized.
+ //
+ if(!areAllOperandsFullySpecialized(inst))
+ {
+ // If we can't fully specialize this instruction
+ // yet, then we need to toss it back onto the
+ // work list to be considered in the next round.
+ //
+ // TODO: We need to carefully vet that this can
+ // never lead to infinite looping
+ //
+ workList.Add(inst);
+ }
+ else
+ {
+ // If all of the operands to `inst` are
+ // fully specialized, then we can go
+ // ahead and do whatever is required
+ // to "fully specialize" `inst` itself.
+ //
+ fullySpecializeInst(inst);
+
+ // At this point, we want to start
+ // considering `inst` as fully specialized,
+ // so let's add it to our set.
+ //
+ fullySpecializedInsts.Add(inst);
+ }
+ }
+ }
+
+ // Once the work list has gone dry, we should have the invariant
+ // that there are no `specialize` instructions inside of non-generic
+ // functions that in turn reference a generic type/function, *except*
+ // in the case where that generic is for a builtin type/function, in
+ // which case we wouldn't want to specialize it anyway.
+ }
+};
+
+void specializeGenerics(
+ IRModule* module)
+{
+ SpecializationContext context;
+ context.processModule(module);
+}
+
+} // namespace Slang
diff --git a/source/slang/ir-specialize.h b/source/slang/ir-specialize.h
new file mode 100644
index 000000000..dc1f07481
--- /dev/null
+++ b/source/slang/ir-specialize.h
@@ -0,0 +1,13 @@
+// ir-specialize.h
+#pragma once
+
+namespace Slang
+{
+struct IRModule;
+
+// Find suitable uses of the `specialize` instruction that
+// can be replaced with references to specialized functions.
+void specializeGenerics(
+ IRModule* module);
+
+}
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index 6899e1494..ca424b4a4 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -1858,18 +1858,32 @@ namespace Slang
return inst;
}
- IRInst* IRBuilder::emitIntrinsicInst(
+ IRInst* IRBuilder::createIntrinsicInst(
IRType* type,
IROp op,
UInt argCount,
IRInst* const* args)
{
- auto inst = createInstWithTrailingArgs<IRInst>(
+ return createInstWithTrailingArgs<IRInst>(
this,
op,
type,
argCount,
args);
+ }
+
+
+ IRInst* IRBuilder::emitIntrinsicInst(
+ IRType* type,
+ IROp op,
+ UInt argCount,
+ IRInst* const* args)
+ {
+ auto inst = createIntrinsicInst(
+ type,
+ op,
+ argCount,
+ args);
addInst(inst);
return inst;
}
@@ -3909,1058 +3923,6 @@ namespace Slang
return t;
}
- //
- // Legalization of entry points for GLSL:
- //
-
- IRGlobalParam* addGlobalParam(
- IRModule* module,
- IRType* valueType)
- {
- auto session = module->session;
-
- SharedIRBuilder shared;
- shared.module = module;
- shared.session = session;
-
- IRBuilder builder;
- builder.sharedBuilder = &shared;
- return builder.createGlobalParam(valueType);
- }
-
- void moveValueBefore(
- IRInst* valueToMove,
- IRInst* placeBefore)
- {
- valueToMove->removeFromParent();
- valueToMove->insertBefore(placeBefore);
- }
-
- // When scalarizing shader inputs/outputs for GLSL, we need a way
- // to refer to a conceptual "value" that might comprise multiple
- // IR-level values. We could in principle introduce tuple types
- // into the IR so that everything stays at the IR level, but
- // it seems easier to just layer it over the top for now.
- //
- // The `ScalarizedVal` type deals with the "tuple or single value?"
- // question, and also the "l-value or r-value?" question.
- struct ScalarizedValImpl : RefObject
- {};
- struct ScalarizedTupleValImpl;
- struct ScalarizedTypeAdapterValImpl;
- struct ScalarizedVal
- {
- enum class Flavor
- {
- // no value (null pointer)
- none,
-
- // A simple `IRInst*` that represents the actual value
- value,
-
- // An `IRInst*` that represents the address of the actual value
- address,
-
- // A `TupleValImpl` that represents zero or more `ScalarizedVal`s
- tuple,
-
- // A `TypeAdapterValImpl` that wraps a single `ScalarizedVal` and
- // represents an implicit type conversion applied to it on read
- // or write.
- typeAdapter,
- };
-
- // Create a value representing a simple value
- static ScalarizedVal value(IRInst* irValue)
- {
- ScalarizedVal result;
- result.flavor = Flavor::value;
- result.irValue = irValue;
- return result;
- }
-
-
- // Create a value representing an address
- static ScalarizedVal address(IRInst* irValue)
- {
- ScalarizedVal result;
- result.flavor = Flavor::address;
- result.irValue = irValue;
- return result;
- }
-
- static ScalarizedVal tuple(ScalarizedTupleValImpl* impl)
- {
- ScalarizedVal result;
- result.flavor = Flavor::tuple;
- result.impl = (ScalarizedValImpl*)impl;
- return result;
- }
-
- static ScalarizedVal typeAdapter(ScalarizedTypeAdapterValImpl* impl)
- {
- ScalarizedVal result;
- result.flavor = Flavor::typeAdapter;
- result.impl = (ScalarizedValImpl*)impl;
- return result;
- }
-
- Flavor flavor = Flavor::none;
- IRInst* irValue = nullptr;
- RefPtr<ScalarizedValImpl> impl;
- };
-
- // This is the case for a value that is a "tuple" of other values
- struct ScalarizedTupleValImpl : ScalarizedValImpl
- {
- struct Element
- {
- IRStructKey* key;
- ScalarizedVal val;
- };
-
- IRType* type;
- List<Element> elements;
- };
-
- // This is the case for a value that is stored with one type,
- // but needs to present itself as having a different type
- struct ScalarizedTypeAdapterValImpl : ScalarizedValImpl
- {
- ScalarizedVal val;
- IRType* actualType; // the actual type of `val`
- IRType* pretendType; // the type this value pretends to have
- };
-
- struct GlobalVaryingDeclarator
- {
- enum class Flavor
- {
- array,
- };
-
- Flavor flavor;
- IRInst* elementCount;
- GlobalVaryingDeclarator* next;
- };
-
- struct GLSLSystemValueInfo
- {
- // The name of the built-in GLSL variable
- char const* name;
-
- // The name of an outer array that wraps
- // the variable, in the case of a GS input
- char const* outerArrayName;
-
- // The required type of the built-in variable
- IRType* requiredType;
- };
-
- void requireGLSLVersionImpl(
- ExtensionUsageTracker* tracker,
- ProfileVersion version);
-
- void requireGLSLExtension(
- ExtensionUsageTracker* tracker,
- String const& name);
-
- struct GLSLLegalizationContext
- {
- Session* session;
- ExtensionUsageTracker* extensionUsageTracker;
- DiagnosticSink* sink;
- Stage stage;
-
- void requireGLSLExtension(String const& name)
- {
- Slang::requireGLSLExtension(extensionUsageTracker, name);
- }
-
- void requireGLSLVersion(ProfileVersion version)
- {
- Slang::requireGLSLVersionImpl(extensionUsageTracker, version);
- }
-
- Stage getStage()
- {
- return stage;
- }
-
- DiagnosticSink* getSink()
- {
- return sink;
- }
-
- IRBuilder* builder;
- IRBuilder* getBuilder() { return builder; }
- };
-
- GLSLSystemValueInfo* getGLSLSystemValueInfo(
- GLSLLegalizationContext* context,
- VarLayout* varLayout,
- LayoutResourceKind kind,
- Stage stage,
- GLSLSystemValueInfo* inStorage)
- {
- char const* name = nullptr;
- char const* outerArrayName = nullptr;
-
- auto semanticNameSpelling = varLayout->systemValueSemantic;
- if(semanticNameSpelling.Length() == 0)
- return nullptr;
-
- auto semanticName = semanticNameSpelling.ToLower();
-
- IRType* requiredType = nullptr;
-
- if(semanticName == "sv_position")
- {
- // This semantic can either work like `gl_FragCoord`
- // when it is used as a fragment shader input, or
- // like `gl_Position` when used in other stages.
- //
- // Note: This isn't as simple as testing input-vs-output,
- // because a user might have a VS output `SV_Position`,
- // and then pass it along to a GS that reads it as input.
- //
- if( stage == Stage::Fragment
- && kind == LayoutResourceKind::VaryingInput )
- {
- name = "gl_FragCoord";
- }
- else if( stage == Stage::Geometry
- && kind == LayoutResourceKind::VaryingInput )
- {
- // As a GS input, the correct syntax is `gl_in[...].gl_Position`,
- // but that is not compatible with picking the array dimension later,
- // of course.
- outerArrayName = "gl_in";
- name = "gl_Position";
- }
- else
- {
- name = "gl_Position";
- }
- }
- else if(semanticName == "sv_target")
- {
- // Note: we do *not* need to generate some kind of `gl_`
- // builtin for fragment-shader outputs: they are just
- // ordinary `out` variables, with ordinary `location`s,
- // as far as GLSL is concerned.
- return nullptr;
- }
- else if(semanticName == "sv_clipdistance")
- {
- // TODO: type conversion is required here.
- name = "gl_ClipDistance";
- }
- else if(semanticName == "sv_culldistance")
- {
- context->requireGLSLExtension("ARB_cull_distance");
-
- // TODO: type conversion is required here.
- name = "gl_CullDistance";
- }
- else if(semanticName == "sv_coverage")
- {
- // TODO: deal with `gl_SampleMaskIn` when used as an input.
-
- // TODO: type conversion is required here.
- name = "gl_SampleMask";
- }
- else if(semanticName == "sv_depth")
- {
- name = "gl_FragDepth";
- }
- else if(semanticName == "sv_depthgreaterequal")
- {
- // TODO: layout(depth_greater) out float gl_FragDepth;
- name = "gl_FragDepth";
- }
- else if(semanticName == "sv_depthlessequal")
- {
- // TODO: layout(depth_greater) out float gl_FragDepth;
- name = "gl_FragDepth";
- }
- else if(semanticName == "sv_dispatchthreadid")
- {
- name = "gl_GlobalInvocationID";
- }
- else if(semanticName == "sv_domainlocation")
- {
- name = "gl_TessCoord";
- }
- else if(semanticName == "sv_groupid")
- {
- name = "gl_WorkGroupID";
- }
- else if(semanticName == "sv_groupindex")
- {
- name = "gl_LocalInvocationIndex";
- }
- else if(semanticName == "sv_groupthreadid")
- {
- name = "gl_LocalInvocationID";
- }
- else if(semanticName == "sv_gsinstanceid")
- {
- name = "gl_InvocationID";
- }
- else if(semanticName == "sv_instanceid")
- {
- name = "gl_InstanceIndex";
- }
- else if(semanticName == "sv_isfrontface")
- {
- name = "gl_FrontFacing";
- }
- else if(semanticName == "sv_outputcontrolpointid")
- {
- name = "gl_InvocationID";
- }
- else if(semanticName == "sv_primitiveid")
- {
- name = "gl_PrimitiveID";
- }
- else if (semanticName == "sv_rendertargetarrayindex")
- {
- switch (context->getStage())
- {
- case Stage::Geometry:
- context->requireGLSLVersion(ProfileVersion::GLSL_150);
- break;
-
- case Stage::Fragment:
- context->requireGLSLVersion(ProfileVersion::GLSL_430);
- break;
-
- default:
- context->requireGLSLVersion(ProfileVersion::GLSL_450);
- context->requireGLSLExtension("GL_ARB_shader_viewport_layer_array");
- break;
- }
-
- name = "gl_Layer";
- requiredType = context->getBuilder()->getBasicType(BaseType::Int);
- }
- else if (semanticName == "sv_sampleindex")
- {
- name = "gl_SampleID";
- }
- else if (semanticName == "sv_stencilref")
- {
- context->requireGLSLExtension("ARB_shader_stencil_export");
- name = "gl_FragStencilRef";
- }
- else if (semanticName == "sv_tessfactor")
- {
- name = "gl_TessLevelOuter";
- }
- else if (semanticName == "sv_vertexid")
- {
- name = "gl_VertexIndex";
- }
- else if (semanticName == "sv_viewportarrayindex")
- {
- name = "gl_ViewportIndex";
- }
- else if (semanticName == "nv_x_right")
- {
- context->requireGLSLVersion(ProfileVersion::GLSL_450);
- context->requireGLSLExtension("GL_NVX_multiview_per_view_attributes");
-
- // The actual output in GLSL is:
- //
- // vec4 gl_PositionPerViewNV[];
- //
- // and is meant to support an arbitrary number of views,
- // while the HLSL case just defines a second position
- // output.
- //
- // For now we will hack this by:
- // 1. Mapping an `NV_X_Right` output to `gl_PositionPerViewNV[1]`
- // (that is, just one element of the output array)
- // 2. Adding logic to copy the traditional `gl_Position` output
- // over to `gl_PositionPerViewNV[0]`
- //
-
- name = "gl_PositionPerViewNV[1]";
-
-// shared->requiresCopyGLPositionToPositionPerView = true;
- }
- else if (semanticName == "nv_viewport_mask")
- {
- context->requireGLSLVersion(ProfileVersion::GLSL_450);
- context->requireGLSLExtension("GL_NVX_multiview_per_view_attributes");
-
- name = "gl_ViewportMaskPerViewNV";
-// globalVarExpr = createGLSLBuiltinRef("gl_ViewportMaskPerViewNV",
-// getUnsizedArrayType(getIntType()));
- }
-
- if( name )
- {
- inStorage->name = name;
- inStorage->outerArrayName = outerArrayName;
- inStorage->requiredType = requiredType;
- return inStorage;
- }
-
- context->getSink()->diagnose(varLayout->varDecl.getDecl()->loc, Diagnostics::unknownSystemValueSemantic, semanticNameSpelling);
- return nullptr;
- }
-
- ScalarizedVal createSimpleGLSLGlobalVarying(
- GLSLLegalizationContext* context,
- IRBuilder* builder,
- IRType* inType,
- VarLayout* inVarLayout,
- TypeLayout* inTypeLayout,
- LayoutResourceKind kind,
- Stage stage,
- UInt bindingIndex,
- GlobalVaryingDeclarator* declarator)
- {
- // Check if we have a system value on our hands.
- GLSLSystemValueInfo systemValueInfoStorage;
- auto systemValueInfo = getGLSLSystemValueInfo(
- context,
- inVarLayout,
- kind,
- stage,
- &systemValueInfoStorage);
-
- IRType* type = inType;
-
- // A system-value semantic might end up needing to override the type
- // that the user specified.
- if( systemValueInfo && systemValueInfo->requiredType )
- {
- type = systemValueInfo->requiredType;
- }
-
- // Construct the actual type and type-layout for the global variable
- //
- RefPtr<TypeLayout> typeLayout = inTypeLayout;
- for( auto dd = declarator; dd; dd = dd->next )
- {
- // We only have one declarator case right now...
- SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array);
-
- auto arrayType = builder->getArrayType(
- type,
- dd->elementCount);
-
- RefPtr<ArrayTypeLayout> arrayTypeLayout = new ArrayTypeLayout();
-// arrayTypeLayout->type = arrayType;
- arrayTypeLayout->rules = typeLayout->rules;
- arrayTypeLayout->originalElementTypeLayout = typeLayout;
- arrayTypeLayout->elementTypeLayout = typeLayout;
- arrayTypeLayout->uniformStride = 0;
-
- if( auto resInfo = inTypeLayout->FindResourceInfo(kind) )
- {
- // TODO: it is kind of gross to be re-running some
- // of the type layout logic here.
-
- UInt elementCount = (UInt) GetIntVal(dd->elementCount);
- arrayTypeLayout->addResourceUsage(
- kind,
- resInfo->count * elementCount);
- }
-
- type = arrayType;
- typeLayout = arrayTypeLayout;
- }
-
- // We need to construct a fresh layout for the variable, even
- // if the original had its own layout, because it might be
- // an `inout` parameter, and we only want to deal with the case
- // described by our `kind` parameter.
- RefPtr<VarLayout> varLayout = new VarLayout();
- varLayout->varDecl = inVarLayout->varDecl;
- varLayout->typeLayout = typeLayout;
- varLayout->flags = inVarLayout->flags;
- varLayout->systemValueSemantic = inVarLayout->systemValueSemantic;
- varLayout->systemValueSemanticIndex = inVarLayout->systemValueSemanticIndex;
- varLayout->semanticName = inVarLayout->semanticName;
- varLayout->semanticIndex = inVarLayout->semanticIndex;
- varLayout->stage = inVarLayout->stage;
- varLayout->AddResourceInfo(kind)->index = bindingIndex;
-
- // We are going to be creating a global parameter to replace
- // the function parameter, but we need to handle the case
- // where the parameter represents a varying *output* and not
- // just an input.
- //
- // Our IR global shader parameters are read-only, just
- // like our IR function parameters, and need a wrapper
- // `Out<...>` type to represent otuputs.
- //
- bool isOutput = kind == LayoutResourceKind::VaryingOutput;
- IRType* paramType = isOutput ? builder->getOutType(type) : type;
-
- auto globalParam = addGlobalParam(builder->getModule(), paramType);
- moveValueBefore(globalParam, builder->getFunc());
-
- ScalarizedVal val = isOutput ? ScalarizedVal::address(globalParam) : ScalarizedVal::value(globalParam);
-
- if( systemValueInfo )
- {
- builder->addImportDecoration(globalParam, UnownedTerminatedStringSlice(systemValueInfo->name));
-
- if( auto fromType = systemValueInfo->requiredType )
- {
- // We may need to adapt from the declared type to/from
- // the actual type of the GLSL global.
- auto toType = inType;
-
- if( fromType != toType )
- {
- RefPtr<ScalarizedTypeAdapterValImpl> typeAdapter = new ScalarizedTypeAdapterValImpl;
- typeAdapter->actualType = systemValueInfo->requiredType;
- typeAdapter->pretendType = inType;
- typeAdapter->val = val;
-
- val = ScalarizedVal::typeAdapter(typeAdapter);
- }
- }
-
- if(auto outerArrayName = systemValueInfo->outerArrayName)
- {
- builder->addGLSLOuterArrayDecoration(globalParam, UnownedTerminatedStringSlice(outerArrayName));
- }
- }
-
- builder->addLayoutDecoration(globalParam, varLayout);
-
- return val;
- }
-
- ScalarizedVal createGLSLGlobalVaryingsImpl(
- GLSLLegalizationContext* context,
- IRBuilder* builder,
- IRType* type,
- VarLayout* varLayout,
- TypeLayout* typeLayout,
- LayoutResourceKind kind,
- Stage stage,
- UInt bindingIndex,
- GlobalVaryingDeclarator* declarator)
- {
- if( as<IRBasicType>(type) )
- {
- return createSimpleGLSLGlobalVarying(
- context,
- builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
- }
- else if( as<IRVectorType>(type) )
- {
- return createSimpleGLSLGlobalVarying(
- context,
- builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
- }
- else if( as<IRMatrixType>(type) )
- {
- // TODO: a matrix-type varying should probably be handled like an array of rows
- return createSimpleGLSLGlobalVarying(
- context,
- builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
- }
- else if( auto arrayType = as<IRArrayType>(type) )
- {
- // We will need to SOA-ize any nested types.
-
- auto elementType = arrayType->getElementType();
- auto elementCount = arrayType->getElementCount();
- auto arrayLayout = dynamic_cast<ArrayTypeLayout*>(typeLayout);
- SLANG_ASSERT(arrayLayout);
- auto elementTypeLayout = arrayLayout->elementTypeLayout;
-
- GlobalVaryingDeclarator arrayDeclarator;
- arrayDeclarator.flavor = GlobalVaryingDeclarator::Flavor::array;
- arrayDeclarator.elementCount = elementCount;
- arrayDeclarator.next = declarator;
-
- return createGLSLGlobalVaryingsImpl(
- context,
- builder,
- elementType,
- varLayout,
- elementTypeLayout,
- kind,
- stage,
- bindingIndex,
- &arrayDeclarator);
- }
- else if( auto streamType = as<IRHLSLStreamOutputType>(type))
- {
- auto elementType = streamType->getElementType();
- auto streamLayout = dynamic_cast<StreamOutputTypeLayout*>(typeLayout);
- SLANG_ASSERT(streamLayout);
- auto elementTypeLayout = streamLayout->elementTypeLayout;
-
- return createGLSLGlobalVaryingsImpl(
- context,
- builder,
- elementType,
- varLayout,
- elementTypeLayout,
- kind,
- stage,
- bindingIndex,
- declarator);
- }
- else if(auto structType = as<IRStructType>(type))
- {
- // We need to recurse down into the individual fields,
- // and generate a variable for each of them.
-
- auto structTypeLayout = dynamic_cast<StructTypeLayout*>(typeLayout);
- SLANG_ASSERT(structTypeLayout);
- RefPtr<ScalarizedTupleValImpl> tupleValImpl = new ScalarizedTupleValImpl();
-
-
- // Construct the actual type for the tuple (including any outer arrays)
- IRType* fullType = type;
- for( auto dd = declarator; dd; dd = dd->next )
- {
- SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array);
- fullType = builder->getArrayType(
- fullType,
- dd->elementCount);
- }
-
- tupleValImpl->type = fullType;
-
- // Okay, we want to walk through the fields here, and
- // generate one variable for each.
- UInt fieldCounter = 0;
- for(auto field : structType->getFields())
- {
- UInt fieldIndex = fieldCounter++;
-
- auto fieldLayout = structTypeLayout->fields[fieldIndex];
-
- UInt fieldBindingIndex = bindingIndex;
- if(auto fieldResInfo = fieldLayout->FindResourceInfo(kind))
- fieldBindingIndex += fieldResInfo->index;
-
- auto fieldVal = createGLSLGlobalVaryingsImpl(
- context,
- builder,
- field->getFieldType(),
- fieldLayout,
- fieldLayout->typeLayout,
- kind,
- stage,
- fieldBindingIndex,
- declarator);
-
- ScalarizedTupleValImpl::Element element;
- element.val = fieldVal;
- element.key = field->getKey();
-
- tupleValImpl->elements.Add(element);
- }
-
- return ScalarizedVal::tuple(tupleValImpl);
- }
-
- // Default case is to fall back on the simple behavior
- return createSimpleGLSLGlobalVarying(
- context,
- builder, type, varLayout, typeLayout, kind, stage, bindingIndex, declarator);
- }
-
- ScalarizedVal createGLSLGlobalVaryings(
- GLSLLegalizationContext* context,
- IRBuilder* builder,
- IRType* type,
- VarLayout* layout,
- LayoutResourceKind kind,
- Stage stage)
- {
- UInt bindingIndex = 0;
- if(auto rr = layout->FindResourceInfo(kind))
- bindingIndex = rr->index;
- return createGLSLGlobalVaryingsImpl(
- context,
- builder, type, layout, layout->typeLayout, kind, stage, bindingIndex, nullptr);
- }
-
- IRType* getFieldType(
- IRType* baseType,
- IRStructKey* fieldKey)
- {
- if(auto structType = as<IRStructType>(baseType))
- {
- for(auto ff : structType->getFields())
- {
- if(ff->getKey() == fieldKey)
- return ff->getFieldType();
- }
- }
-
- SLANG_UNEXPECTED("no such field");
- UNREACHABLE_RETURN(nullptr);
- }
-
- ScalarizedVal extractField(
- IRBuilder* builder,
- ScalarizedVal const& val,
- UInt fieldIndex,
- IRStructKey* fieldKey)
- {
- switch( val.flavor )
- {
- case ScalarizedVal::Flavor::value:
- return ScalarizedVal::value(
- builder->emitFieldExtract(
- getFieldType(val.irValue->getDataType(), fieldKey),
- val.irValue,
- fieldKey));
-
- case ScalarizedVal::Flavor::address:
- {
- auto ptrType = as<IRPtrTypeBase>(val.irValue->getDataType());
- auto valType = ptrType->getValueType();
- auto fieldType = getFieldType(valType, fieldKey);
- auto fieldPtrType = builder->getPtrType(ptrType->op, fieldType);
- return ScalarizedVal::address(
- builder->emitFieldAddress(
- fieldPtrType,
- val.irValue,
- fieldKey));
- }
-
- case ScalarizedVal::Flavor::tuple:
- {
- auto tupleVal = val.impl.As<ScalarizedTupleValImpl>();
- return tupleVal->elements[fieldIndex].val;
- }
-
- default:
- SLANG_UNEXPECTED("unimplemented");
- UNREACHABLE_RETURN(ScalarizedVal());
- }
-
- }
-
- ScalarizedVal adaptType(
- IRBuilder* builder,
- IRInst* val,
- IRType* toType,
- IRType* /*fromType*/)
- {
- // TODO: actually consider what needs to go on here...
- return ScalarizedVal::value(builder->emitConstructorInst(
- toType,
- 1,
- &val));
- }
-
- ScalarizedVal adaptType(
- IRBuilder* builder,
- ScalarizedVal const& val,
- IRType* toType,
- IRType* fromType)
- {
- switch( val.flavor )
- {
- case ScalarizedVal::Flavor::value:
- return adaptType(builder, val.irValue, toType, fromType);
- break;
-
- case ScalarizedVal::Flavor::address:
- {
- auto loaded = builder->emitLoad(val.irValue);
- return adaptType(builder, loaded, toType, fromType);
- }
- break;
-
- default:
- SLANG_UNEXPECTED("unimplemented");
- UNREACHABLE_RETURN(ScalarizedVal());
- }
- }
-
- void assign(
- IRBuilder* builder,
- ScalarizedVal const& left,
- ScalarizedVal const& right)
- {
- switch( left.flavor )
- {
- case ScalarizedVal::Flavor::address:
- switch( right.flavor )
- {
- case ScalarizedVal::Flavor::value:
- {
- builder->emitStore(left.irValue, right.irValue);
- }
- break;
-
- case ScalarizedVal::Flavor::address:
- {
- auto val = builder->emitLoad(right.irValue);
- builder->emitStore(left.irValue, val);
- }
- break;
-
- case ScalarizedVal::Flavor::tuple:
- {
- // We are assigning from a tuple to a destination
- // that is not a tuple. We will perform assignment
- // element-by-element.
- auto rightTupleVal = right.impl.As<ScalarizedTupleValImpl>();
- UInt elementCount = rightTupleVal->elements.Count();
-
- for( UInt ee = 0; ee < elementCount; ++ee )
- {
- auto rightElement = rightTupleVal->elements[ee];
- auto leftElementVal = extractField(
- builder,
- left,
- ee,
- rightElement.key);
- assign(builder, leftElementVal, rightElement.val);
- }
- }
- break;
-
- default:
- SLANG_UNEXPECTED("unimplemented");
- break;
- }
- break;
-
- case ScalarizedVal::Flavor::tuple:
- {
- // We have a tuple, so we are going to need to try and assign
- // to each of its constituent fields.
- auto leftTupleVal = left.impl.As<ScalarizedTupleValImpl>();
- UInt elementCount = leftTupleVal->elements.Count();
-
- for( UInt ee = 0; ee < elementCount; ++ee )
- {
- auto rightElementVal = extractField(
- builder,
- right,
- ee,
- leftTupleVal->elements[ee].key);
- assign(builder, leftTupleVal->elements[ee].val, rightElementVal);
- }
- }
- break;
-
- case ScalarizedVal::Flavor::typeAdapter:
- {
- // We are trying to assign to something that had its type adjusted,
- // so we will need to adjust the type of the right-hand side first.
- //
- // In this case we are converting to the actual type of the GLSL variable,
- // from the "pretend" type that it had in the IR before.
- auto typeAdapter = left.impl.As<ScalarizedTypeAdapterValImpl>();
- auto adaptedRight = adaptType(builder, right, typeAdapter->actualType, typeAdapter->pretendType);
- assign(builder, typeAdapter->val, adaptedRight);
- }
- break;
-
- default:
- SLANG_UNEXPECTED("unimplemented");
- break;
- }
- }
-
- ScalarizedVal getSubscriptVal(
- IRBuilder* builder,
- IRType* elementType,
- ScalarizedVal val,
- IRInst* indexVal)
- {
- switch( val.flavor )
- {
- case ScalarizedVal::Flavor::value:
- return ScalarizedVal::value(
- builder->emitElementExtract(
- elementType,
- val.irValue,
- indexVal));
-
- case ScalarizedVal::Flavor::address:
- return ScalarizedVal::address(
- builder->emitElementAddress(
- builder->getPtrType(elementType),
- val.irValue,
- indexVal));
-
- case ScalarizedVal::Flavor::tuple:
- {
- auto inputTuple = val.impl.As<ScalarizedTupleValImpl>();
-
- RefPtr<ScalarizedTupleValImpl> resultTuple = new ScalarizedTupleValImpl();
- resultTuple->type = elementType;
-
- UInt elementCount = inputTuple->elements.Count();
- UInt elementCounter = 0;
-
- auto structType = as<IRStructType>(elementType);
- for(auto field : structType->getFields())
- {
- auto tupleElementType = field->getFieldType();
-
- UInt elementIndex = elementCounter++;
-
- SLANG_RELEASE_ASSERT(elementIndex < elementCount);
- auto inputElement = inputTuple->elements[elementIndex];
-
- ScalarizedTupleValImpl::Element resultElement;
- resultElement.key = inputElement.key;
- resultElement.val = getSubscriptVal(
- builder,
- tupleElementType,
- inputElement.val,
- indexVal);
-
- resultTuple->elements.Add(resultElement);
- }
- SLANG_RELEASE_ASSERT(elementCounter == elementCount);
-
- return ScalarizedVal::tuple(resultTuple);
- }
-
- default:
- SLANG_UNEXPECTED("unimplemented");
- UNREACHABLE_RETURN(ScalarizedVal());
- }
- }
-
- ScalarizedVal getSubscriptVal(
- IRBuilder* builder,
- IRType* elementType,
- ScalarizedVal val,
- UInt index)
- {
- return getSubscriptVal(
- builder,
- elementType,
- val,
- builder->getIntValue(
- builder->getIntType(),
- index));
- }
-
- IRInst* materializeValue(
- IRBuilder* builder,
- ScalarizedVal const& val);
-
- IRInst* materializeTupleValue(
- IRBuilder* builder,
- ScalarizedVal val)
- {
- auto tupleVal = val.impl.As<ScalarizedTupleValImpl>();
- SLANG_ASSERT(tupleVal);
-
- UInt elementCount = tupleVal->elements.Count();
- auto type = tupleVal->type;
-
- if( auto arrayType = as<IRArrayType>(type))
- {
- // The tuple represent an array, which means that the
- // individual elements are expected to yield arrays as well.
- //
- // We will extract a value for each array element, and
- // then use these to construct our result.
-
- List<IRInst*> arrayElementVals;
- UInt arrayElementCount = (UInt) GetIntVal(arrayType->getElementCount());
-
- for( UInt ii = 0; ii < arrayElementCount; ++ii )
- {
- auto arrayElementPseudoVal = getSubscriptVal(
- builder,
- arrayType->getElementType(),
- val,
- ii);
-
- auto arrayElementVal = materializeValue(
- builder,
- arrayElementPseudoVal);
-
- arrayElementVals.Add(arrayElementVal);
- }
-
- return builder->emitMakeArray(
- arrayType,
- arrayElementVals.Count(),
- arrayElementVals.Buffer());
- }
- else
- {
- // The tuple represents a value of some aggregate type,
- // so we can simply materialize the elements and then
- // construct a value of that type.
- //
- // TODO: this should be using a `makeStruct` instruction.
-
- List<IRInst*> elementVals;
- for( UInt ee = 0; ee < elementCount; ++ee )
- {
- auto elementVal = materializeValue(builder, tupleVal->elements[ee].val);
- elementVals.Add(elementVal);
- }
-
- return builder->emitConstructorInst(
- tupleVal->type,
- elementVals.Count(),
- elementVals.Buffer());
- }
- }
-
- IRInst* materializeValue(
- IRBuilder* builder,
- ScalarizedVal const& val)
- {
- switch( val.flavor )
- {
- case ScalarizedVal::Flavor::value:
- return val.irValue;
-
- case ScalarizedVal::Flavor::address:
- {
- auto loadInst = builder->emitLoad(val.irValue);
- return loadInst;
- }
- break;
-
- case ScalarizedVal::Flavor::tuple:
- {
- auto tupleVal = val.impl.As<ScalarizedTupleValImpl>();
- return materializeTupleValue(builder, val);
- }
- break;
-
- case ScalarizedVal::Flavor::typeAdapter:
- {
- // Somebody is trying to use a value where its actual type
- // doesn't match the type it pretends to have. To make this
- // work we need to adapt the type from its actual type over
- // to its pretend type.
- auto typeAdapter = val.impl.As<ScalarizedTypeAdapterValImpl>();
- auto adapted = adaptType(builder, typeAdapter->val, typeAdapter->pretendType, typeAdapter->actualType);
- return materializeValue(builder, adapted);
- }
- break;
-
- default:
- SLANG_UNEXPECTED("unimplemented");
- break;
- }
- }
-
IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration(
IRInst* val,
String const& targetName)
@@ -4978,1360 +3940,14 @@ namespace Slang
return nullptr;
}
- void legalizeRayTracingEntryPointParameterForGLSL(
- GLSLLegalizationContext* context,
- IRFunc* func,
- IRParam* pp,
- VarLayout* paramLayout)
- {
- auto builder = context->getBuilder();
- auto paramType = pp->getDataType();
-
- // The parameter might be either an `in` parameter,
- // or an `out` or `in out` parameter, and in those
- // latter cases its IR-level type will include a
- // wrapping "pointer-like" type (e.g., `Out<Float>`
- // instead of just `Float`).
- //
- // Because global shader parameters are read-only
- // in the same way function types are, we can take
- // care of that detail here just by allocating a
- // global shader parameter with exactly the type
- // of the original function parameter.
- //
- auto globalParam = addGlobalParam(builder->getModule(), paramType);
- builder->addLayoutDecoration(globalParam, paramLayout);
- moveValueBefore(globalParam, builder->getFunc());
- pp->replaceUsesWith(globalParam);
-
- // Because linkage between ray-tracing shaders is
- // based on the type of incoming/outgoing payload
- // and attribute parameters, it would be an error to
- // eliminate the global parameter *even if* it is
- // not actually used inside the entry point.
- //
- // We attach a decoration to the entry point that
- // makes note of the dependency, so that steps
- // like dead code elimination cannot get rid of
- // the parameter.
- //
- // TODO: We could consider using a structure like
- // this for *all* of the entry point parameters
- // that get moved to the global scope, since SPIR-V
- // ends up requiring such information on an `OpEntryPoint`.
- //
- // As a further alternative, we could decide to
- // keep entry point varying input/outtput attached
- // to the parameter list through all of the Slang IR
- // steps, and only declare it as global variables at
- // the last minute when emitting a GLSL `main` or
- // SPIR-V for an entry point.
- //
- builder->addDependsOnDecoration(func, globalParam);
- }
-
- void legalizeEntryPointParameterForGLSL(
- GLSLLegalizationContext* context,
- IRFunc* func,
- IRParam* pp,
- VarLayout* paramLayout)
- {
- auto builder = context->getBuilder();
- auto stage = context->getStage();
-
- // We need to create a global variable that will replace the parameter.
- // It seems superficially obvious that the variable should have
- // the same type as the parameter.
- // However, if the parameter was a pointer, in order to
- // support `out` or `in out` parameter passing, we need
- // to be sure to allocate a variable of the pointed-to
- // type instead.
- //
- // We also need to replace uses of the parameter with
- // uses of the variable, and the exact logic there
- // will differ a bit between the pointer and non-pointer
- // cases.
- auto paramType = pp->getDataType();
-
- // First we will special-case stage input/outputs that
- // don't fit into the standard varying model.
- // For right now we are only doing special-case handling
- // of geometry shader output streams.
- if( auto paramPtrType = as<IROutTypeBase>(paramType) )
- {
- auto valueType = paramPtrType->getValueType();
- if( auto gsStreamType = as<IRHLSLStreamOutputType>(valueType) )
- {
- // An output stream type like `TriangleStream<Foo>` should
- // more or less translate into `out Foo` (plus scalarization).
-
- auto globalOutputVal = createGLSLGlobalVaryings(
- context,
- builder,
- valueType,
- paramLayout,
- LayoutResourceKind::VaryingOutput,
- stage);
-
- // TODO: a GS output stream might be passed into other
- // functions, so that we should really be modifying
- // any function that has one of these in its parameter
- // list (and in the limit we should be leagalizing any
- // type that nests these...).
- //
- // For now we will just try to deal with `Append` calls
- // directly in this function.
-
-
-
- for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
- {
- for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
- {
- // Is it a call?
- if(ii->op != kIROp_Call)
- continue;
-
- // Is it calling the append operation?
- auto callee = ii->getOperand(0);
- for(;;)
- {
- // If the instruction is `specialize(X,...)` then
- // we want to look at `X`, and if it is `generic { ... return R; }`
- // then we want to look at `R`. We handle this
- // iteratively here.
- //
- // TODO: This idiom seems to come up enough that we
- // should probably have a dedicated convenience routine
- // for this.
- //
- // Alternatively, we could switch the IR encoding so
- // that decorations are added to the generic instead of the
- // value it returns.
- //
- switch(callee->op)
- {
- case kIROp_Specialize:
- {
- callee = cast<IRSpecialize>(callee)->getOperand(0);
- continue;
- }
-
- case kIROp_Generic:
- {
- auto genericResult = findGenericReturnVal(cast<IRGeneric>(callee));
- if(genericResult)
- {
- callee = genericResult;
- continue;
- }
- }
-
- default:
- break;
- }
- break;
- }
- if(callee->op != kIROp_Func)
- continue;
-
- // HACK: we will identify the operation based
- // on the target-intrinsic definition that was
- // given to it.
- auto decoration = findTargetIntrinsicDecoration(callee, "glsl");
- if(!decoration)
- continue;
-
- if(decoration->getDefinition() != UnownedStringSlice::fromLiteral("EmitVertex()"))
- {
- continue;
- }
-
- // Okay, we have a declaration, and we want to modify it!
-
- builder->setInsertBefore(ii);
-
- assign(builder, globalOutputVal, ScalarizedVal::value(ii->getOperand(2)));
- }
- }
-
- return;
- }
- }
-
- // When we have an HLSL ray tracing shader entry point,
- // we don't want to translate the inputs/outputs for GLSL/SPIR-V
- // according to our default rules, for two reasons:
- //
- // 1. The input and output for these stages are expected to
- // be packaged into `struct` types rather than be scalarized,
- // so the usual scalarization approach we take here should
- // not be applied.
- //
- // 2. An `in out` parameter isn't just sugar for a combination
- // of an `in` and an `out` parameter, and instead represents the
- // read/write "payload" that was passed in. It should legalize
- // to a single variable, and we can lower reads/writes of it
- // directly, rather than introduce an intermediate temporary.
- //
- switch( stage )
- {
- default:
- break;
-
- case Stage::AnyHit:
- case Stage::Callable:
- case Stage::ClosestHit:
- case Stage::Intersection:
- case Stage::Miss:
- case Stage::RayGeneration:
- legalizeRayTracingEntryPointParameterForGLSL(context, func, pp, paramLayout);
- return;
- }
-
- // Is the parameter type a special pointer type
- // that indicates the parameter is used for `out`
- // or `inout` access?
- if(auto paramPtrType = as<IROutTypeBase>(paramType) )
- {
- // Okay, we have the more interesting case here,
- // where the parameter was being passed by reference.
- // We are going to create a local variable of the appropriate
- // type, which will replace the parameter, along with
- // one or more global variables for the actual input/output.
-
- auto valueType = paramPtrType->getValueType();
-
- auto localVariable = builder->emitVar(valueType);
- auto localVal = ScalarizedVal::address(localVariable);
-
- if( auto inOutType = as<IRInOutType>(paramPtrType) )
- {
- // In the `in out` case we need to declare two
- // sets of global variables: one for the `in`
- // side and one for the `out` side.
- auto globalInputVal = createGLSLGlobalVaryings(
- context,
- builder, valueType, paramLayout, LayoutResourceKind::VaryingInput, stage);
-
- assign(builder, localVal, globalInputVal);
- }
-
- // Any places where the original parameter was used inside
- // the function body should instead use the new local variable.
- // Since the parameter was a pointer, we use the variable instruction
- // itself (which is an `alloca`d pointer) directly:
- pp->replaceUsesWith(localVariable);
-
- // We also need one or more global variables to write the output to
- // when the function is done. We create them here.
- auto globalOutputVal = createGLSLGlobalVaryings(
- context,
- builder, valueType, paramLayout, LayoutResourceKind::VaryingOutput, stage);
-
- // Now we need to iterate over all the blocks in the function looking
- // for any `return*` instructions, so that we can write to the output variable
- for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
- {
- auto terminatorInst = bb->getLastInst();
- if(!terminatorInst)
- continue;
-
- switch( terminatorInst->op )
- {
- default:
- continue;
-
- case kIROp_ReturnVal:
- case kIROp_ReturnVoid:
- break;
- }
-
- // We dont' re-use `builder` here because we don't want to
- // disrupt the source location it is using for inserting
- // temporary variables at the top of the function.
- //
- IRBuilder terminatorBuilder;
- terminatorBuilder.sharedBuilder = builder->sharedBuilder;
- terminatorBuilder.setInsertBefore(terminatorInst);
-
- // Assign from the local variabel to the global output
- // variable before the actual `return` takes place.
- assign(&terminatorBuilder, globalOutputVal, localVal);
- }
- }
- else
- {
- // This is the "easy" case where the parameter wasn't
- // being passed by reference. We start by just creating
- // one or more global variables to represent the parameter,
- // and attach the required layout information to it along
- // the way.
-
- auto globalValue = createGLSLGlobalVaryings(
- context,
- builder, paramType, paramLayout, LayoutResourceKind::VaryingInput, stage);
-
- // Next we need to replace uses of the parameter with
- // references to the variable(s). We are going to do that
- // somewhat naively, by simply materializing the
- // variables at the start.
- IRInst* materialized = materializeValue(builder, globalValue);
-
- pp->replaceUsesWith(materialized);
- }
- }
-
- void legalizeEntryPointForGLSL(
- Session* session,
- IRModule* module,
- IRFunc* func,
- EntryPointLayout* entryPointLayout,
- DiagnosticSink* sink,
- ExtensionUsageTracker* extensionUsageTracker)
- {
- GLSLLegalizationContext context;
- context.session = session;
- context.stage = entryPointLayout->profile.GetStage();
- context.sink = sink;
- context.extensionUsageTracker = extensionUsageTracker;
-
- Stage stage = entryPointLayout->profile.GetStage();
-
- // We require that the entry-point function has no uses,
- // because otherwise we'd invalidate the signature
- // at all existing call sites.
- //
- // TODO: the right thing to do here is to split any
- // function that both gets called as an entry point
- // and as an ordinary function.
- SLANG_ASSERT(!func->firstUse);
-
- // We create a dummy IR builder, since some of
- // the functions require it.
- //
- // TODO: make some of these free functions...
- //
- SharedIRBuilder shared;
- shared.module = module;
- shared.session = session;
- IRBuilder builder;
- builder.sharedBuilder = &shared;
- builder.setInsertInto(func);
-
- context.builder = &builder;
-
- // We will start by looking at the return type of the
- // function, because that will enable us to do an
- // early-out check to avoid more work.
- //
- // Specifically, we need to check if the function has
- // a `void` return type, because there is no work
- // to be done on its return value in that case.
- auto resultType = func->getResultType();
- if(as<IRVoidType>(resultType))
- {
- // In this case, the function doesn't return a value
- // so we don't need to transform its `return` sites.
- //
- // We can also use this opportunity to quickly
- // check if the function has any parameters, and if
- // it doesn't use the chance to bail out immediately.
- if( func->getParamCount() == 0 )
- {
- // This function is already legal for GLSL
- // (at least in terms of parameter/result signature),
- // so we won't bother doing anything at all.
- return;
- }
-
- // If the function does have parameters, then we need
- // to let the logic later in this function handle them.
- }
- else
- {
- // Function returns a value, so we need
- // to introduce a new global variable
- // to hold that value, and then replace
- // any `returnVal` instructions with
- // code to write to that variable.
-
- auto resultGlobal = createGLSLGlobalVaryings(
- &context,
- &builder,
- resultType,
- entryPointLayout->resultLayout,
- LayoutResourceKind::VaryingOutput,
- stage);
-
- for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() )
- {
- // TODO: This is silly, because we are looking at every instruction,
- // when we know that a `returnVal` should only ever appear as a
- // terminator...
- for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
- {
- if(ii->op != kIROp_ReturnVal)
- continue;
-
- IRReturnVal* returnInst = (IRReturnVal*) ii;
- IRInst* returnValue = returnInst->getVal();
-
- // Make sure we add these instructions to the right block
- builder.setInsertInto(bb);
-
- // Write to our global variable(s) from the value being returned.
- assign(&builder, resultGlobal, ScalarizedVal::value(returnValue));
-
- // Emit a `returnVoid` to end the block
- auto returnVoid = builder.emitReturn();
-
- // Remove the old `returnVal` instruction.
- returnInst->removeAndDeallocate();
-
- // Make sure to resume our iteration at an
- // appropriate instruciton, since we deleted
- // the one we had been using.
- ii = returnVoid;
- }
- }
- }
-
- // Next we will walk through any parameters of the entry-point function,
- // and turn them into global variables.
- if( auto firstBlock = func->getFirstBlock() )
- {
- // Any initialization code we insert for parameters needs
- // to be at the start of the "ordinary" instructions in the block:
- builder.setInsertBefore(firstBlock->getFirstOrdinaryInst());
-
- UInt paramCounter = 0;
- for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() )
- {
- UInt paramIndex = paramCounter++;
-
- // We assume that the entry-point layout includes information
- // on each parameter, and that these arrays are kept aligned.
- // Note that this means that any transformations that mess
- // with function signatures will need to also update layout info...
- //
- SLANG_ASSERT(entryPointLayout->fields.Count() > paramIndex);
- auto paramLayout = entryPointLayout->fields[paramIndex];
-
- legalizeEntryPointParameterForGLSL(
- &context,
- func,
- pp,
- paramLayout);
- }
-
- // At this point we should have eliminated all uses of the
- // parameters of the entry block. Also, our control-flow
- // rules mean that the entry block cannot be the target
- // of any branches in the code, so there can't be
- // any control-flow ops that try to match the parameter
- // list.
- //
- // We can safely go through and destroy the parameters
- // themselves, and then clear out the parameter list.
-
- for( auto pp = firstBlock->getFirstParam(); pp; )
- {
- auto next = pp->getNextParam();
- pp->removeAndDeallocate();
- pp = next;
- }
- }
-
- // Finally, we need to patch up the type of the entry point,
- // because it is no longer accurate.
-
- IRFuncType* voidFuncType = builder.getFuncType(
- 0,
- nullptr,
- builder.getVoidType());
- func->setFullType(voidFuncType);
-
- // TODO: we should technically be constructing
- // a new `EntryPointLayout` here to reflect
- // the way that things have been moved around.
- }
-
- // Needed for lookup up entry-point layouts.
- //
- // TODO: maybe arrange so that codegen is driven from the layout layer
- // instead of the input/request layer.
- EntryPointLayout* findEntryPointLayout(
- ProgramLayout* programLayout,
- EntryPointRequest* entryPointRequest);
-
- struct IRSpecSymbol : RefObject
- {
- IRInst* irGlobalValue;
- RefPtr<IRSpecSymbol> nextWithSameName;
- };
-
- struct IRSpecEnv
- {
- IRSpecEnv* parent = nullptr;
-
- // A map from original values to their cloned equivalents.
- typedef Dictionary<IRInst*, IRInst*> ClonedValueDictionary;
- ClonedValueDictionary clonedValues;
- };
-
- struct IRSharedSpecContext
- {
- // The code-generation target in use
- CodeGenTarget target;
-
- // The specialized module we are building
- RefPtr<IRModule> module;
-
- // The original, unspecialized module we are copying
- IRModule* originalModule;
-
- // A map from mangled symbol names to zero or
- // more global IR values that have that name,
- // in the *original* module.
- typedef Dictionary<String, RefPtr<IRSpecSymbol>> SymbolDictionary;
- SymbolDictionary symbols;
-
- SharedIRBuilder sharedBuilderStorage;
- IRBuilder builderStorage;
-
- // The "global" specialization environment.
- IRSpecEnv globalEnv;
- };
-
- struct IRGenericSpecKey
- {
- // Note: Slang::Dictionary requires key types to have default constructors
- IRGenericSpecKey()
- {}
-
- IRGenericSpecKey(IRSpecialize* specializeInst)
- {
- m_values.Add(specializeInst->getBase());
- auto argCount = specializeInst->getArgCount();
- for(UInt aa = 0; aa < argCount; ++aa)
- {
- m_values.Add(specializeInst->getArg(aa));
- }
- }
-
- List<IRInst*> m_values;
-
- bool operator==(IRGenericSpecKey const& other) const
- {
- auto valueCount = m_values.Count();
- if(valueCount != other.m_values.Count()) return false;
- for(UInt ii = 0; ii < valueCount; ++ii)
- {
- if(m_values[ii] != other.m_values[ii]) return false;
- }
- return true;
- }
-
- UInt GetHashCode() const
- {
- auto hash = 0;
- auto valueCount = m_values.Count();
- for(UInt ii = 0; ii < valueCount; ++ii)
- {
- hash = combineHash(hash, Slang::GetHashCode(m_values[ii]));
- }
- return hash;
- }
- };
-
- struct IRSharedGenericSpecContext : IRSharedSpecContext
- {
- // Instructions to be processed (for generic specialization context)
- List<IRInst*> workList;
- HashSet<IRInst*> workListSet;
- void addToWorkList(IRInst* inst)
- {
- if(!workListSet.Contains(inst))
- {
- workList.Add(inst);
- workListSet.Add(inst);
- }
- }
- IRInst* popWorkList()
- {
- UInt count = workList.Count();
- if(count != 0)
- {
- IRInst* inst = workList[count - 1];
- workList.FastRemoveAt(count - 1);
- workListSet.Remove(inst);
- return inst;
- }
- return nullptr;
- }
-
- Dictionary<IRGenericSpecKey, IRInst*> specializations;
- };
-
- struct IRSpecContextBase
- {
- // A map from the mangled name of a global variable
- // to the layout to use for it.
- Dictionary<String, VarLayout*> globalVarLayouts;
-
- IRSharedSpecContext* shared;
-
- IRSharedSpecContext* getShared() { return shared; }
-
- IRModule* getModule() { return getShared()->module; }
-
- IRModule* getOriginalModule() { return getShared()->originalModule; }
-
- IRSharedSpecContext::SymbolDictionary& getSymbols() { return getShared()->symbols; }
-
- // The current specialization environment to use.
- IRSpecEnv* env = nullptr;
- IRSpecEnv* getEnv()
- {
- // TODO: need to actually establish environments on contexts we create.
- //
- // Or more realistically we need to change the whole approach
- // to specialization and cloning so that we don't try to share
- // logic between two very different cases.
-
-
- return env;
- }
-
- // The IR builder to use for creating nodes
- IRBuilder* builder;
-
- // A callback to be used when a value that is not registerd in `clonedValues`
- // is needed during cloning. This gives the subtype a chance to intercept
- // the operation and clone (or not) as needed.
- virtual IRInst* maybeCloneValue(IRInst* originalVal)
- {
- return originalVal;
- }
- };
-
- void registerClonedValue(
- IRSpecContextBase* context,
- IRInst* clonedValue,
- IRInst* originalValue)
- {
- if(!originalValue)
- return;
-
- // TODO: now that things are scoped using environments, we
- // shouldn't be running into the cases where a value with
- // the same key already exists. This should be changed to
- // an `Add()` call.
- //
- context->getEnv()->clonedValues[originalValue] = clonedValue;
- }
-
- // Information on values to use when registering a cloned value
- struct IROriginalValuesForClone
- {
- IRInst* originalVal = nullptr;
- IRSpecSymbol* sym = nullptr;
-
- IROriginalValuesForClone() {}
-
- IROriginalValuesForClone(IRInst* originalValue)
- : originalVal(originalValue)
- {}
-
- IROriginalValuesForClone(IRSpecSymbol* symbol)
- : sym(symbol)
- {}
- };
-
- void registerClonedValue(
- IRSpecContextBase* context,
- IRInst* clonedValue,
- IROriginalValuesForClone const& originalValues)
- {
- registerClonedValue(context, clonedValue, originalValues.originalVal);
- for( auto s = originalValues.sym; s; s = s->nextWithSameName )
- {
- registerClonedValue(context, clonedValue, s->irGlobalValue);
- }
- }
-
- IRInst* cloneInst(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRInst* originalInst,
- IROriginalValuesForClone const& originalValues);
-
- IRInst* cloneInst(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRInst* originalInst)
- {
- return cloneInst(context, builder, originalInst, originalInst);
- }
-
- /// Clone any decorations from `originalValue` onto `clonedValue`
- void cloneDecorations(
- IRSpecContextBase* context,
- IRInst* clonedValue,
- IRInst* originalValue)
- {
- // TODO: In many cases we might be able to use this as a general-purpose
- // place to do cloning of *all* the children of an instruction, and
- // not just its decorations. We should look to refactor this code
- // later.
-
- IRBuilder builderStorage = *context->builder;
- IRBuilder* builder = &builderStorage;
- builder->setInsertInto(clonedValue);
-
-
- SLANG_UNUSED(context);
- for(auto originalDecoration : originalValue->getDecorations())
- {
- cloneInst(context, builder, originalDecoration);
- }
-
- // We will also clone the location here, just because this is a convenient bottleneck
- clonedValue->sourceLoc = originalValue->sourceLoc;
- }
-
- /// Clone any decorations and children from `originalValue` onto `clonedValue`
- void cloneDecorationsAndChildren(
- IRSpecContextBase* context,
- IRInst* clonedValue,
- IRInst* originalValue)
- {
- IRBuilder builderStorage = *context->builder;
- IRBuilder* builder = &builderStorage;
- builder->setInsertInto(clonedValue);
-
- SLANG_UNUSED(context);
- for(auto originalItem : originalValue->getDecorationsAndChildren())
- {
- cloneInst(context, builder, originalItem);
- }
-
- // We will also clone the location here, just because this is a convenient bottleneck
- clonedValue->sourceLoc = originalValue->sourceLoc;
- }
-
- // We use an `IRSpecContext` for the case where we are cloning
- // code from one or more input modules to create a "linked" output
- // module. Along the way, we will resolve profile-specific functions
- // to the best definition for a given target.
- //
- struct IRSpecContext : IRSpecContextBase
- {
- // Override the "maybe clone" logic so that we always clone
- virtual IRInst* maybeCloneValue(IRInst* originalVal) override;
- };
-
-
- IRInst* cloneGlobalValue(IRSpecContext* context, IRInst* originalVal);
-
- IRInst* cloneValue(
- IRSpecContextBase* context,
- IRInst* originalValue);
-
- IRType* cloneType(
- IRSpecContextBase* context,
- IRType* originalType);
-
- IRInst* IRSpecContext::maybeCloneValue(IRInst* originalValue)
- {
- switch (originalValue->op)
- {
- case kIROp_StructType:
- case kIROp_Func:
- case kIROp_Generic:
- case kIROp_GlobalVar:
- case kIROp_GlobalConstant:
- case kIROp_GlobalParam:
- case kIROp_StructKey:
- case kIROp_GlobalGenericParam:
- case kIROp_WitnessTable:
- return cloneGlobalValue(this, originalValue);
-
- case kIROp_BoolLit:
- {
- IRConstant* c = (IRConstant*)originalValue;
- return builder->getBoolValue(c->value.intVal != 0);
- }
- break;
-
-
- case kIROp_IntLit:
- {
- IRConstant* c = (IRConstant*)originalValue;
- return builder->getIntValue(cloneType(this, c->getDataType()), c->value.intVal);
- }
- break;
-
- case kIROp_FloatLit:
- {
- IRConstant* c = (IRConstant*)originalValue;
- return builder->getFloatValue(cloneType(this, c->getDataType()), c->value.floatVal);
- }
- break;
-
- case kIROp_StringLit:
- {
- IRConstant* c = (IRConstant*)originalValue;
- return builder->getStringValue(c->getStringSlice());
- }
- break;
-
- case kIROp_PtrLit:
- {
- IRConstant* c = (IRConstant*)originalValue;
- return builder->getPtrValue(c->value.ptrVal);
- }
- break;
-
- default:
- {
- // In the deafult case, assume that we have some sort of "hoistable"
- // instruction that requires us to create a clone of it.
-
- UInt argCount = originalValue->getOperandCount();
- IRInst* clonedValue = createInstWithTrailingArgs<IRInst>(
- builder,
- originalValue->op,
- cloneType(this, originalValue->getFullType()),
- 0, nullptr,
- argCount, nullptr);
- registerClonedValue(this, clonedValue, originalValue);
- for (UInt aa = 0; aa < argCount; ++aa)
- {
- IRInst* originalArg = originalValue->getOperand(aa);
- IRInst* clonedArg = cloneValue(this, originalArg);
- clonedValue->getOperands()[aa].init(clonedValue, clonedArg);
- }
- cloneDecorationsAndChildren(this, clonedValue, originalValue);
-
- addHoistableInst(builder, clonedValue);
-
- return clonedValue;
- }
- break;
- }
- }
-
- IRInst* cloneValue(
- IRSpecContextBase* context,
- IRInst* originalValue);
-
- // Find a pre-existing cloned value, or return null if none is available.
- IRInst* findClonedValue(
- IRSpecContextBase* context,
- IRInst* originalValue)
- {
- IRInst* clonedValue = nullptr;
- for (auto env = context->getEnv(); env; env = env->parent)
- {
- if (env->clonedValues.TryGetValue(originalValue, clonedValue))
- {
- return clonedValue;
- }
- }
-
- return nullptr;
- }
-
- IRInst* cloneValue(
- IRSpecContextBase* context,
- IRInst* originalValue)
- {
- if (!originalValue)
- return nullptr;
-
- if (IRInst* clonedValue = findClonedValue(context, originalValue))
- return clonedValue;
-
- return context->maybeCloneValue(originalValue);
- }
-
- IRType* cloneType(
- IRSpecContextBase* context,
- IRType* originalType)
- {
- return (IRType*)cloneValue(context, originalType);
- }
-
- void cloneGlobalValueWithCodeCommon(
- IRSpecContextBase* context,
- IRGlobalValueWithCode* clonedValue,
- IRGlobalValueWithCode* originalValue);
-
- IRRate* cloneRate(
- IRSpecContextBase* context,
- IRRate* rate)
- {
- return (IRRate*) cloneType(context, rate);
- }
-
- void maybeSetClonedRate(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRInst* clonedValue,
- IRInst* originalValue)
- {
- if(auto rate = originalValue->getRate() )
- {
- clonedValue->setFullType(builder->getRateQualifiedType(
- cloneRate(context, rate),
- clonedValue->getFullType()));
- }
- }
-
- IRGlobalVar* cloneGlobalVarImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRGlobalVar* originalVar,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedVar = builder->createGlobalVar(
- cloneType(context, originalVar->getDataType()->getValueType()));
-
- maybeSetClonedRate(context, builder, clonedVar, originalVar);
-
- registerClonedValue(context, clonedVar, originalValues);
-
- // Clone any code in the body of the variable, since this
- // represents the initializer.
- cloneGlobalValueWithCodeCommon(
- context,
- clonedVar,
- originalVar);
-
- return clonedVar;
- }
-
- IRGlobalConstant* cloneGlobalConstantImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRGlobalConstant* originalVal,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedVal = builder->createGlobalConstant(
- cloneType(context, originalVal->getFullType()));
- registerClonedValue(context, clonedVal, originalValues);
-
- // Clone any code in the body of the constant, since this
- // represents the initializer.
- cloneGlobalValueWithCodeCommon(
- context,
- clonedVal,
- originalVal);
-
- return clonedVal;
- }
-
- void cloneSimpleGlobalValueImpl(
- IRSpecContextBase* context,
- IRInst* originalInst,
- IROriginalValuesForClone const& originalValues,
- IRInst* clonedInst,
- bool registerValue = true)
- {
- if (registerValue)
- registerClonedValue(context, clonedInst, originalValues);
-
- // Set up an IR builder for inserting into the inst
- IRBuilder builderStorage = *context->builder;
- IRBuilder* builder = &builderStorage;
- builder->setInsertInto(clonedInst);
-
- // Clone any children of the instruction
- for (auto child : originalInst->getDecorationsAndChildren())
- {
- cloneInst(context, builder, child);
- }
- }
-
- IRGlobalParam* cloneGlobalParamImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRGlobalParam* originalVal,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedVal = builder->createGlobalParam(
- cloneType(context, originalVal->getFullType()));
- cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal);
-
- if(auto linkage = originalVal->findDecoration<IRLinkageDecoration>())
- {
- auto mangledName = String(linkage->getMangledName());
- VarLayout* layout = nullptr;
- if (context->globalVarLayouts.TryGetValue(mangledName, layout))
- {
- builder->addLayoutDecoration(clonedVal, layout);
- }
- }
-
- return clonedVal;
- }
-
- IRGeneric* cloneGenericImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRGeneric* originalVal,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedVal = builder->emitGeneric();
- registerClonedValue(context, clonedVal, originalValues);
-
- // Clone any code in the body of the generic, since this
- // computes its result value.
- cloneGlobalValueWithCodeCommon(
- context,
- clonedVal,
- originalVal);
-
- return clonedVal;
- }
-
- IRStructKey* cloneStructKeyImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRStructKey* originalVal,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedVal = builder->createStructKey();
- cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal);
- return clonedVal;
- }
-
- IRGlobalGenericParam* cloneGlobalGenericParamImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRGlobalGenericParam* originalVal,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedVal = builder->emitGlobalGenericParam();
- cloneSimpleGlobalValueImpl(context, originalVal, originalValues, clonedVal);
- return clonedVal;
- }
-
-
- IRWitnessTable* cloneWitnessTableImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRWitnessTable* originalTable,
- IROriginalValuesForClone const& originalValues,
- IRWitnessTable* dstTable = nullptr,
- bool registerValue = true)
- {
- auto clonedTable = dstTable ? dstTable : builder->createWitnessTable();
- cloneSimpleGlobalValueImpl(context, originalTable, originalValues, clonedTable, registerValue);
- return clonedTable;
- }
-
- IRWitnessTable* cloneWitnessTableWithoutRegistering(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRWitnessTable* originalTable,
- IRWitnessTable* dstTable = nullptr)
- {
- return cloneWitnessTableImpl(context, builder, originalTable, IROriginalValuesForClone(), dstTable, false);
- }
-
- IRStructType* cloneStructTypeImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRStructType* originalStruct,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedStruct = builder->createStructType();
- cloneSimpleGlobalValueImpl(context, originalStruct, originalValues, clonedStruct);
- return clonedStruct;
- }
-
-
- IRInterfaceType* cloneInterfaceTypeImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRInterfaceType* originalInterface,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedInterface = builder->createInterfaceType();
- cloneSimpleGlobalValueImpl(context, originalInterface, originalValues, clonedInterface);
- return clonedInterface;
- }
-
- void cloneGlobalValueWithCodeCommon(
- IRSpecContextBase* context,
- IRGlobalValueWithCode* clonedValue,
- IRGlobalValueWithCode* originalValue)
- {
- // Next we are going to clone the actual code.
- IRBuilder builderStorage = *context->builder;
- IRBuilder* builder = &builderStorage;
- builder->setInsertInto(clonedValue);
-
- cloneDecorations(context, clonedValue, originalValue);
-
- // We will walk through the blocks of the function, and clone each of them.
- //
- // We need to create the cloned blocks first, and then walk through them,
- // because blocks might be forward referenced (this is not possible
- // for other cases of instructions).
- for (auto originalBlock = originalValue->getFirstBlock();
- originalBlock;
- originalBlock = originalBlock->getNextBlock())
- {
- IRBlock* clonedBlock = builder->createBlock();
- clonedValue->addBlock(clonedBlock);
- registerClonedValue(context, clonedBlock, originalBlock);
-
#if 0
- // We can go ahead and clone parameters here, while we are at it.
- builder->curBlock = clonedBlock;
- for (auto originalParam = originalBlock->getFirstParam();
- originalParam;
- originalParam = originalParam->getNextParam())
- {
- IRParam* clonedParam = builder->emitParam(
- context->maybeCloneType(
- originalParam->getFullType()));
- cloneDecorations(context, clonedParam, originalParam);
- registerClonedValue(context, clonedParam, originalParam);
- }
-#endif
- }
-
- // Okay, now we are in a good position to start cloning
- // the instructions inside the blocks.
- {
- IRBlock* ob = originalValue->getFirstBlock();
- IRBlock* cb = clonedValue->getFirstBlock();
- while (ob)
- {
- SLANG_ASSERT(cb);
-
- builder->setInsertInto(cb);
- for (auto oi = ob->getFirstInst(); oi; oi = oi->getNextInst())
- {
- cloneInst(context, builder, oi);
- }
-
- ob = ob->getNextBlock();
- cb = cb->getNextBlock();
- }
- }
-
- }
-
- void checkIRDuplicate(IRInst* inst, IRInst* moduleInst, UnownedStringSlice const& mangledName)
- {
-#ifdef _DEBUG
- for (auto child : moduleInst->getDecorationsAndChildren())
- {
- if (child == inst)
- continue;
-
- if(auto childLinkage = child->findDecoration<IRLinkageDecoration>())
- {
- if(mangledName == childLinkage->getMangledName())
- {
- SLANG_UNEXPECTED("duplicate global instruction");
- }
- }
- }
-#else
- SLANG_UNREFERENCED_PARAMETER(inst);
- SLANG_UNREFERENCED_PARAMETER(moduleInst);
- SLANG_UNREFERENCED_PARAMETER(mangledName);
-#endif
- }
-
- void cloneFunctionCommon(
- IRSpecContextBase* context,
- IRFunc* clonedFunc,
- IRFunc* originalFunc,
- bool checkDuplicate = true)
- {
- // First clone all the simple properties.
- clonedFunc->setFullType(cloneType(context, originalFunc->getFullType()));
-
- cloneGlobalValueWithCodeCommon(
- context,
- clonedFunc,
- originalFunc);
-
- // Shuffle the function to the end of the list, because
- // it needs to follow its dependencies.
- //
- // TODO: This isn't really a good requirement to place on the IR...
- clonedFunc->moveToEnd();
-
- if( checkDuplicate )
- {
- if( auto linkage = clonedFunc->findDecoration<IRLinkageDecoration>() )
- {
- checkIRDuplicate(clonedFunc, context->getModule()->getModuleInst(), linkage->getMangledName());
- }
- }
- }
-
- IRFunc* specializeIRForEntryPoint(
- IRSpecContext* context,
- EntryPointRequest* entryPointRequest,
- EntryPointLayout* entryPointLayout)
- {
- // Look up the IR symbol by name
- auto mangledName = getMangledName(entryPointRequest->decl);
- RefPtr<IRSpecSymbol> sym;
- if (!context->getSymbols().TryGetValue(mangledName, sym))
- {
- SLANG_UNEXPECTED("no matching IR symbol");
- return nullptr;
- }
-
- // TODO: deal with the case where we might
- // have multiple versions...
-
- auto globalValue = sym->irGlobalValue;
- if (globalValue->op != kIROp_Func)
- {
- SLANG_UNEXPECTED("expected an IR function");
- return nullptr;
- }
- auto originalFunc = (IRFunc*)globalValue;
-
- // Create a clone for the IR function
- auto clonedFunc = context->builder->createFunc();
-
- // Note: we do *not* register this cloned declaration
- // as the cloned value for the original symbol.
- // This is kind of a kludge, but it ensures that
- // in the unlikely case that the function is both
- // used as an entry point and a callable function
- // (yes, this would imply recursion...) we actually
- // have two copies, which lets us arbitrarily
- // transform the entry point to meet target requirements.
- //
- // TODO: The above statement is kind of bunk, though,
- // because both versions of the function would have
- // the same mangled name... :(
-
- // We need to clone all the properties of the original
- // function, including any blocks, their parameters,
- // and their instructions.
- cloneFunctionCommon(context, clonedFunc, originalFunc);
-
- // We need to attach the layout information for
- // the entry point to this declaration, so that
- // we can use it to inform downstream code emit.
- context->builder->addLayoutDecoration(
- clonedFunc,
- entryPointLayout);
-
- // We will also go on and attach layout information
- // to the function parameters, so that we have it
- // available directly on the parameters, rather
- // than having to look it up on the original entry-point layout.
- if( auto firstBlock = clonedFunc->getFirstBlock() )
- {
- UInt paramLayoutCount = entryPointLayout->fields.Count();
- UInt paramCounter = 0;
- for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() )
- {
- UInt paramIndex = paramCounter++;
- if( paramIndex < paramLayoutCount )
- {
- auto paramLayout = entryPointLayout->fields[paramIndex];
- context->builder->addLayoutDecoration(
- pp,
- paramLayout);
- }
- else
- {
- SLANG_UNEXPECTED("too many parameters");
- }
- }
- }
-
- return clonedFunc;
- }
-
IRFunc* cloneSimpleFuncWithoutRegistering(IRSpecContextBase* context, IRFunc* originalFunc)
{
auto clonedFunc = context->builder->createFunc();
cloneFunctionCommon(context, clonedFunc, originalFunc, false);
return clonedFunc;
}
-
- // Get a string form of the target so that we can
- // use it to match against target-specialization modifiers
- //
- // TODO: We shouldn't be using strings for this.
- String getTargetName(IRSpecContext* context)
- {
- switch( context->shared->target )
- {
- case CodeGenTarget::HLSL:
- return "hlsl";
-
- case CodeGenTarget::GLSL:
- return "glsl";
-
- default:
- SLANG_UNEXPECTED("unhandled case");
- UNREACHABLE_RETURN("unknown");
- }
- }
-
- // How specialized is a given declaration for the chosen target?
- enum class TargetSpecializationLevel
- {
- specializedForOtherTarget = 0,
- notSpecialized,
- specializedForTarget,
- };
-
- TargetSpecializationLevel getTargetSpecialiationLevel(
- IRInst* inVal,
- String const& targetName)
- {
- // HACK: Currently the front-end is placing modifiers related
- // to target specialization on nodes like functions, even when
- // those functions are being returned by a generic. This
- // means that we need to try and inspect the value being
- // returned by the generic if we are looking at a generic.
- IRInst* val = inVal;
- while( auto genericVal = as<IRGeneric>(val) )
- {
- auto firstBlock = genericVal->getFirstBlock();
- if(!firstBlock) break;
-
- auto returnInst = as<IRReturnVal>(firstBlock->getLastInst());
- if(!returnInst) break;
-
- val = returnInst->getVal();
- }
-
- TargetSpecializationLevel result = TargetSpecializationLevel::notSpecialized;
- for(auto dd : val->getDecorations())
- {
- if(dd->op != kIROp_TargetDecoration)
- continue;
-
- auto decoration = (IRTargetDecoration*) dd;
- if(String(decoration->getTargetName()) == targetName)
- return TargetSpecializationLevel::specializedForTarget;
-
- result = TargetSpecializationLevel::specializedForOtherTarget;
- }
-
- return result;
- }
+#endif
IRInst* findGenericReturnVal(IRGeneric* generic)
{
@@ -6406,992 +4022,6 @@ namespace Slang
}
}
- // Is `newVal` marked as being a better match for our
- // chosen code-generation target?
- //
- // TODO: there is a missing step here where we need
- // to check if things are even available in the first place...
- bool isBetterForTarget(
- IRSpecContext* context,
- IRInst* newVal,
- IRInst* oldVal)
- {
- String targetName = getTargetName(context);
-
- // For right now every declaration might have zero or more
- // modifiers, representing the targets for which it is specialized.
- // Each modifier has a single string "tag" to represent a target.
- // We thus decide that a declaration is "more specialized" by:
- //
- // - Does it have a modifier with a tag with the string for the current target?
- // If yes, it is the most specialized it can be.
- //
- // - Does it have a no tags? Then it is "unspecialized" and that is okay.
- //
- // - Does it have a modifier with a tag for a *different* target?
- // If yes, then it shouldn't even be usable on this target.
- //
- // Longer term a better approach is to think of this in terms
- // of a "disjunction of conjunctions" that is:
- //
- // (A and B and C) or (A and D) or (E) or (F and G) ...
- //
- // A code generation target would then consist of a
- // conjunction of invidual tags:
- //
- // (HLSL and SM_4_0 and Vertex and ...)
- //
- // A declaration is *applicable* on a target if one of
- // its conjunctions of tags is a subset of the target's.
- //
- // One declaration is *better* than another on a target
- // if it is applicable and its tags are a superset
- // of the other's.
-
- auto newLevel = getTargetSpecialiationLevel(newVal, targetName);
- auto oldLevel = getTargetSpecialiationLevel(oldVal, targetName);
- if(newLevel != oldLevel)
- return UInt(newLevel) > UInt(oldLevel);
-
- // All other factors being equal, a definition is
- // better than a declaration.
- auto newIsDef = isDefinition(newVal);
- auto oldIsDef = isDefinition(oldVal);
- if (newIsDef != oldIsDef)
- return newIsDef;
-
- return false;
- }
-
- IRFunc* cloneFuncImpl(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRFunc* originalFunc,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedFunc = builder->createFunc();
- registerClonedValue(context, clonedFunc, originalValues);
- cloneFunctionCommon(context, clonedFunc, originalFunc);
- return clonedFunc;
- }
-
-
- IRInst* cloneInst(
- IRSpecContextBase* context,
- IRBuilder* builder,
- IRInst* originalInst,
- IROriginalValuesForClone const& originalValues)
- {
- switch (originalInst->op)
- {
- // We need to special-case any instruction that is not
- // allocated like an ordinary `IRInst` with trailing args.
- case kIROp_Func:
- return cloneFuncImpl(context, builder, cast<IRFunc>(originalInst), originalValues);
-
- case kIROp_GlobalVar:
- return cloneGlobalVarImpl(context, builder, cast<IRGlobalVar>(originalInst), originalValues);
-
- case kIROp_GlobalConstant:
- return cloneGlobalConstantImpl(context, builder, cast<IRGlobalConstant>(originalInst), originalValues);
-
- case kIROp_GlobalParam:
- return cloneGlobalParamImpl(context, builder, cast<IRGlobalParam>(originalInst), originalValues);
-
- case kIROp_WitnessTable:
- return cloneWitnessTableImpl(context, builder, cast<IRWitnessTable>(originalInst), originalValues);
-
- case kIROp_StructType:
- return cloneStructTypeImpl(context, builder, cast<IRStructType>(originalInst), originalValues);
-
- case kIROp_InterfaceType:
- return cloneInterfaceTypeImpl(context, builder, cast<IRInterfaceType>(originalInst), originalValues);
-
- case kIROp_Generic:
- return cloneGenericImpl(context, builder, cast<IRGeneric>(originalInst), originalValues);
-
- case kIROp_StructKey:
- return cloneStructKeyImpl(context, builder, cast<IRStructKey>(originalInst), originalValues);
-
- case kIROp_GlobalGenericParam:
- return cloneGlobalGenericParamImpl(context, builder, cast<IRGlobalGenericParam>(originalInst), originalValues);
-
- default:
- break;
- }
-
- // The common case is that we just need to construct a cloned
- // instruction with the right number of operands, intialize
- // it, and then add it to the sequence.
- UInt argCount = originalInst->getOperandCount();
- IRInst* clonedInst = createInstWithTrailingArgs<IRInst>(
- builder, originalInst->op,
- cloneType(context, originalInst->getFullType()),
- 0, nullptr,
- argCount, nullptr);
- registerClonedValue(context, clonedInst, originalValues);
- auto oldBuilder = context->builder;
- context->builder = builder;
- for (UInt aa = 0; aa < argCount; ++aa)
- {
- IRInst* originalArg = originalInst->getOperand(aa);
- IRInst* clonedArg = cloneValue(context, originalArg);
- clonedInst->getOperands()[aa].init(clonedInst, clonedArg);
- }
- builder->addInst(clonedInst);
- context->builder = oldBuilder;
- cloneDecorations(context, clonedInst, originalInst);
-
- return clonedInst;
- }
-
- IRInst* cloneGlobalValueImpl(
- IRSpecContext* context,
- IRInst* originalInst,
- IROriginalValuesForClone const& originalValues)
- {
- auto clonedValue = cloneInst(context, &context->shared->builderStorage, originalInst, originalValues);
- clonedValue->moveToEnd();
- return clonedValue;
- }
-
-
- /// Clone a global value, which has the given `originalLinkage`.
- ///
- /// The `originalVal` is a known global IR value with that linkage, if one is available.
- /// (It is okay for this parameter to be null).
- ///
- IRInst* cloneGlobalValueWithLinkage(
- IRSpecContext* context,
- IRInst* originalVal,
- IRLinkageDecoration* originalLinkage)
- {
- // If the global value being cloned is already in target module, don't clone
- // Why checking this?
- // When specializing a generic function G (which is already in target module),
- // where G calls a normal function F (which is already in target module),
- // then when we are making a copy of G via cloneFuncCommom(), it will recursively clone F,
- // however we don't want to make a duplicate of F in the target module.
- if (originalVal->getParent() == context->getModule()->getModuleInst())
- return originalVal;
-
- // Check if we've already cloned this value, for the case where
- // an original value has already been established.
- if (originalVal)
- {
- if (IRInst* clonedVal = findClonedValue(context, originalVal))
- {
- return clonedVal;
- }
- }
-
- if(!originalLinkage)
- {
- // If there is no mangled name, then we assume this is a local symbol,
- // and it can't possibly have multiple declarations.
- return cloneGlobalValueImpl(context, originalVal, IROriginalValuesForClone());
- }
-
- //
- // We will scan through all of the available declarations
- // with the same mangled name as `originalVal` and try
- // to pick the "best" one for our target.
-
- auto mangledName = String(originalLinkage->getMangledName());
- RefPtr<IRSpecSymbol> sym;
- if( !context->getSymbols().TryGetValue(mangledName, sym) )
- {
- if(!originalVal)
- return nullptr;
-
- // This shouldn't happen!
- SLANG_UNEXPECTED("no matching values registered");
- UNREACHABLE_RETURN(cloneGlobalValueImpl(context, originalVal, IROriginalValuesForClone()));
- }
-
- // We will try to track the "best" declaration we can find.
- //
- // Generally, one declaration wil lbe better than another if it is
- // more specialized for the chosen target. Otherwise, we simply favor
- // definitions over declarations.
- //
- IRInst* bestVal = sym->irGlobalValue;
- for( auto ss = sym->nextWithSameName; ss; ss = ss->nextWithSameName )
- {
- IRInst* newVal = ss->irGlobalValue;
- if(isBetterForTarget(context, newVal, bestVal))
- bestVal = newVal;
- }
-
- // Check if we've already cloned this value, for the case where
- // we didn't have an original value (just a name), but we've
- // now found a representative value.
- if (!originalVal)
- {
- if (IRInst* clonedVal = findClonedValue(context, bestVal))
- {
- return clonedVal;
- }
- }
-
- return cloneGlobalValueImpl(context, bestVal, IROriginalValuesForClone(sym));
- }
-
- // Clone a global value, where `originalVal` is one declaration/definition, but we might
- // have to consider others, in order to find the "best" version of the symbol.
- IRInst* cloneGlobalValue(IRSpecContext* context, IRInst* originalVal)
- {
- // We are being asked to clone a particular global value, but in
- // the IR that comes out of the front-end there could still
- // be multiple, target-specific, declarations of any given
- // global value, all of which share the same mangled name.
- return cloneGlobalValueWithLinkage(
- context,
- originalVal,
- originalVal->findDecoration<IRLinkageDecoration>());
- }
-
- StructTypeLayout* getGlobalStructLayout(
- ProgramLayout* programLayout);
-
- void insertGlobalValueSymbol(
- IRSharedSpecContext* sharedContext,
- IRInst* gv)
- {
- auto linkage = gv->findDecoration<IRLinkageDecoration>();
-
- // Don't try to register a symbol for global values
- // that don't have linkage.
- //
- if (!linkage)
- return;
-
- auto mangledName = String(linkage->getMangledName());
-
- RefPtr<IRSpecSymbol> sym = new IRSpecSymbol();
- sym->irGlobalValue = gv;
-
- RefPtr<IRSpecSymbol> prev;
- if (sharedContext->symbols.TryGetValue(mangledName, prev))
- {
- sym->nextWithSameName = prev->nextWithSameName;
- prev->nextWithSameName = sym;
- }
- else
- {
- sharedContext->symbols.Add(mangledName, sym);
- }
- }
-
- void insertGlobalValueSymbols(
- IRSharedSpecContext* sharedContext,
- IRModule* originalModule)
- {
- if (!originalModule)
- return;
-
- for(auto ii : originalModule->getGlobalInsts())
- {
- insertGlobalValueSymbol(sharedContext, ii);
- }
- }
-
- void initializeSharedSpecContext(
- IRSharedSpecContext* sharedContext,
- Session* session,
- IRModule* module,
- IRModule* originalModule,
- CodeGenTarget target)
- {
-
- SharedIRBuilder* sharedBuilder = &sharedContext->sharedBuilderStorage;
- sharedBuilder->module = nullptr;
- sharedBuilder->session = session;
-
- IRBuilder* builder = &sharedContext->builderStorage;
- builder->sharedBuilder = sharedBuilder;
-
- if( !module )
- {
- module = builder->createModule();
- }
-
- sharedBuilder->module = module;
- sharedContext->module = module;
- sharedContext->originalModule = originalModule;
- sharedContext->target = target;
- // We will populate a map with all of the IR values
- // that use the same mangled name, to make lookup easier
- // in other steps.
- insertGlobalValueSymbols(sharedContext, originalModule);
- }
-
- // implementation provided in parameter-binding.cpp
- RefPtr<ProgramLayout> specializeProgramLayout(
- TargetRequest * targetReq,
- ProgramLayout* programLayout,
- SubstitutionSet typeSubst);
-
- struct IRSpecializationState
- {
- ProgramLayout* programLayout;
- CodeGenTarget target;
- TargetRequest* targetReq;
-
- IRModule* irModule = nullptr;
- RefPtr<ProgramLayout> newProgramLayout;
-
- IRSharedSpecContext sharedContextStorage;
- IRSpecContext contextStorage;
-
- IRSpecEnv globalEnv;
-
- IRSharedSpecContext* getSharedContext() { return &sharedContextStorage; }
- IRSpecContext* getContext() { return &contextStorage; }
-
- IRSpecializationState()
- {
- contextStorage.env = &globalEnv;
- }
-
- ~IRSpecializationState()
- {
- newProgramLayout = nullptr;
- contextStorage = IRSpecContext();
- sharedContextStorage = IRSharedSpecContext();
- }
- };
-
- IRSpecializationState* createIRSpecializationState(
- EntryPointRequest* entryPointRequest,
- ProgramLayout* programLayout,
- CodeGenTarget target,
- TargetRequest* targetReq)
- {
- IRSpecializationState* state = new IRSpecializationState();
-
- state->programLayout = programLayout;
- state->target = target;
- state->targetReq = targetReq;
-
-
- auto compileRequest = entryPointRequest->compileRequest;
- auto translationUnit = entryPointRequest->getTranslationUnit();
- auto originalIRModule = translationUnit->irModule;
-
- auto sharedContext = state->getSharedContext();
- initializeSharedSpecContext(
- sharedContext,
- compileRequest->mSession,
- nullptr,
- originalIRModule,
- target);
-
- state->irModule = sharedContext->module;
-
- // We also need to attach the IR definitions for symbols from
- // any loaded modules:
- for (auto loadedModule : compileRequest->loadedModulesList)
- {
- insertGlobalValueSymbols(sharedContext, loadedModule->irModule);
- }
-
- auto context = state->getContext();
- context->shared = sharedContext;
- context->builder = &sharedContext->builderStorage;
-
- // Now specialize the program layout using the substitution
- //
- // TODO: The specialization of the layout is conceptually an AST-level operations,
- // and shouldn't be done here in the IR at all.
- //
- RefPtr<ProgramLayout> newProgramLayout = specializeProgramLayout(
- targetReq,
- programLayout,
- SubstitutionSet(entryPointRequest->globalGenericSubst));
-
- // TODO: we need to register the (IR-level) arguments of the global generic parameters as the
- // substitutions for the generic parameters in the original IR.
-
- // applyGlobalGenericParamSubsitution(...);
-
-
- state->newProgramLayout = newProgramLayout;
-
- // Next, we want to optimize lookup for layout infromation
- // associated with global declarations, so that we can
- // look things up based on the IR values (using mangled names)
- auto globalStructLayout = getGlobalStructLayout(newProgramLayout);
- for (auto globalVarLayout : globalStructLayout->fields)
- {
- auto mangledName = getMangledName(globalVarLayout->varDecl);
- context->globalVarLayouts.AddIfNotExists(mangledName, globalVarLayout);
- }
-
- // for now, clone all unreferenced witness tables
- for (auto sym :context->getSymbols())
- {
- if (sym.Value->irGlobalValue->op == kIROp_WitnessTable)
- cloneGlobalValue(context, (IRWitnessTable*)sym.Value->irGlobalValue);
- }
- return state;
- }
-
- void destroyIRSpecializationState(IRSpecializationState* state)
- {
- delete state;
- }
-
- IRModule* getIRModule(IRSpecializationState* state)
- {
- return state->irModule;
- }
-
- void specializeIRForEntryPoint(
- IRSpecializationState* state,
- EntryPointRequest* entryPointRequest,
- ExtensionUsageTracker* extensionUsageTracker)
- {
- auto target = state->target;
-
- auto compileRequest = entryPointRequest->compileRequest;
- auto session = compileRequest->mSession;
- auto translationUnit = entryPointRequest->getTranslationUnit();
- auto originalIRModule = translationUnit->irModule;
- if (!originalIRModule)
- {
- // We should already have emitted IR for the original
- // translation unit, and it we don't have it, then
- // we are now in trouble.
- return;
- }
-
- auto context = state->getContext();
- auto newProgramLayout = state->newProgramLayout;
-
- auto entryPointLayout = findEntryPointLayout(newProgramLayout, entryPointRequest);
-
-
- // Next, we make sure to clone the global value for
- // the entry point function itself, and rely on
- // this step to recursively copy over anything else
- // it might reference.
- auto irEntryPoint = specializeIRForEntryPoint(context, entryPointRequest, entryPointLayout);
-
- // HACK: right now the bindings for global generic parameters are coming in
- // as part of the original IR module, and we need to make sure these get
- // copied over, even if they aren't referenced.
- //
- for(auto inst : originalIRModule->getGlobalInsts())
- {
- auto bindInst = as<IRBindGlobalGenericParam>(inst);
- if(!bindInst)
- continue;
-
- cloneValue(context, bindInst);
- }
-
-
- // TODO: *technically* we should consider the case where
- // we have global variables with initializers, since
- // these should get run whether or not the entry point
- // references them.
-
- // For GLSL only, we will need to perform "legalization" of
- // the entry point and any entry-point parameters.
- switch (target)
- {
- case CodeGenTarget::GLSL:
- {
- legalizeEntryPointForGLSL(
- session,
- context->getModule(),
- irEntryPoint,
- entryPointLayout,
- &compileRequest->mSink,
- extensionUsageTracker);
- }
- break;
-
- default:
- break;
- }
- }
-
- struct IRGenericSpecContext : IRSpecContextBase
- {
- IRSpecContextBase* parent = nullptr;
-
- IRSharedSpecContext* getShared() { return shared; }
-
- // Override the "maybe clone" logic so that we always clone
- virtual IRInst* maybeCloneValue(IRInst* originalVal) override;
- };
-
- IRInst* IRGenericSpecContext::maybeCloneValue(IRInst* originalVal)
- {
- if (parent)
- {
- return parent->maybeCloneValue(originalVal);
- }
- else
- {
- return originalVal;
- }
- }
-
- // See the work list for the generic spec context with
- // every relevant instruction from `inst` through its
- // descendents.
- void addToSpecializationWorkListRec(
- IRSharedGenericSpecContext* sharedContext,
- IRInst* inst)
- {
- if(auto genericInst = as<IRGeneric>(inst))
- {
- // We do *not* consider generics, or instructions nested under them.
- return;
- }
- else
- {
- for(auto child : inst->getChildren())
- {
- addToSpecializationWorkListRec(sharedContext, child);
- }
-
- // Default case: consider this instruction for specialization.
- sharedContext->addToWorkList(inst);
- }
- }
-
- IRInst* specializeGeneric(
- IRSharedGenericSpecContext* sharedContext,
- IRSpecContextBase* parentContext,
- IRGeneric* genericVal,
- IRSpecialize* specializeInst)
- {
- // First, we want to see if an existing specialization
- // has already been made. To do that we will construct a key
- // for lookup in the generic specialization context.
- //
- IRGenericSpecKey specializationKey(specializeInst);
- {
- IRInst* specializedValue = nullptr;
- if(sharedContext->specializations.TryGetValue(specializationKey, specializedValue))
- return specializedValue;
- }
-
- // If we get to this point, then we need to construct a
- // new IR value to represent the result of specialization.
-
- // We need to establish a new mapping from inst->inst to
- // handle the specialization, because we don't want the
- // clones we register in this pass to cause confusion
- // in later steps that might clone the same code.
-
- IRSpecEnv env;
- env.parent = &sharedContext->globalEnv;
- if (parentContext)
- {
- env.parent = parentContext->getEnv();
- }
-
- // The result of specialization should be inserted
- // into the global scope, at the same location as
- // the original generic.
- IRBuilder builderStorage;
- IRBuilder* builder = &builderStorage;
- builder->sharedBuilder = &sharedContext->sharedBuilderStorage;
- builder->setInsertBefore(genericVal);
-
- IRGenericSpecContext context;
- context.shared = sharedContext;
- context.parent = parentContext;
- context.builder = builder;
- context.env = &env;
-
- // Register the arguments of the `specialize` instruction to be used
- // as the "cloned" value for each of the parameters of the generic.
- //
- UInt argCounter = 0;
- for (auto param = genericVal->getFirstParam(); param; param = param->getNextParam())
- {
- UInt argIndex = argCounter++;
- SLANG_ASSERT(argIndex < specializeInst->getArgCount());
-
- IRInst* arg = specializeInst->getArg(argIndex);
-
- registerClonedValue(&context, arg, param);
- }
-
- // Okay, now we want to run through the body of the generic
- // and clone stuff into the parent scope (which had
- // better be the global scope).
- for (auto bb : genericVal->getBlocks())
- {
- // We expect a generic to only ever contain a single block.
- SLANG_ASSERT(bb == genericVal->getFirstBlock());
-
- // Iterate over the non-parameter ("ordinary") instructions.
- for (auto ii : bb->getOrdinaryInsts())
- {
- // The last block of the generic is expected to end with
- // a `return` instruction for the specialized value that
- // comes out of the abstraction.
- //
- // We thus use that cloned value as the result of the
- // specialization step.
- if (auto returnValInst = as<IRReturnVal>(ii))
- {
- auto clonedResult = cloneValue(&context, returnValInst->getVal());
-
- sharedContext->specializations.Add(specializationKey, clonedResult);
-
- return clonedResult;
- }
-
- // Otherwise, clone the instruction into the global scope
- IRInst* clonedInst = cloneInst(&context, context.builder, ii);
-
- // Now that we've cloned the instruction to a location outside
- // of a generic, we should consider whether it can now be specialized.
- addToSpecializationWorkListRec(sharedContext, clonedInst);
- }
- }
-
- // If we reach this point, something went wrong, because we
- // never encountered a `return` inside the body of the generic.
- SLANG_UNEXPECTED("no return from generic");
- UNREACHABLE_RETURN(nullptr);
- }
-
- // Find the value in the given witness table that
- // satisfies the given requirement (or return
- // null if not found).
- IRInst* findWitnessVal(
- IRWitnessTable* witnessTable,
- IRInst* requirementKey)
- {
- // For now we will do a dumb linear search
- for( auto entry : witnessTable->getEntries() )
- {
- // If the keys matched, then we use the value from this entry.
- if (requirementKey == entry->requirementKey.get())
- {
- auto satisfyingVal = entry->satisfyingVal.get();
- return satisfyingVal;
- }
- }
-
- // No matching entry found.
- return nullptr;
- }
-
- static bool canSpecializeGeneric(
- IRGeneric* generic)
- {
- IRGeneric* g = generic;
- for(;;)
- {
- auto val = findGenericReturnVal(g);
- if(!val)
- return false;
-
- if (auto nestedGeneric = as<IRGeneric>(val))
- {
- // The outer generic returns an *inner* generic
- // (so that multiple calls to `specialize` are
- // needed to resolve it). We should look at
- // what the nested generic returns to figure
- // out whether specialization is allowed.
- g = nestedGeneric;
- continue;
- }
-
- // We've found the leaf value that will be produced after
- // all of the specialization is done. Now we want to know
- // if that is a value suitable for actually specializing
- //
- if (isDefinition(val))
- return true;
- return false;
- }
- }
-
- // Add any instruction that uses `inst` to the work list,
- // so that it can be evaluated (or re-evaluated) for specialization.
- void addUsesToWorkList(
- IRSharedGenericSpecContext* sharedContext,
- IRInst* inst)
- {
- for(auto u = inst->firstUse; u; u = u->nextUse)
- {
- sharedContext->addToWorkList(u->getUser());
- }
- }
-
- void specializeGenericsForInst(
- IRSharedGenericSpecContext* sharedContext,
- IRInst* inst)
- {
- switch(inst->op)
- {
- default:
- // The default behavior is to do nothing.
- // An instruction is specialize-able once its operands
- // are specialized, and after that it is also safe
- // to consider the instruction specialized.
- break;
-
- case kIROp_Specialize:
- {
- // We have a `specialize` instruction, so lets see
- // whether we have an opportunity to perform the
- // specialization here and now.
- IRSpecialize* specInst = cast<IRSpecialize>(inst);
-
- // Look at the base of the `specialize`, and see if
- // it directly names a generic, so that we can apply
- // specialization here and now.
- auto baseVal = specInst->getBase();
- if(auto genericVal = as<IRGeneric>(baseVal))
- {
- if (canSpecializeGeneric(genericVal))
- {
- // Okay, we have a candidate for specialization here.
- //
- // We will apply the specialization logic to the body of the generic,
- // which will yield, e.g., a specialized `IRFunc`.
- //
- auto specializedVal = specializeGeneric(sharedContext, nullptr, genericVal, specInst);
- //
- // Then we will replace the use sites for the `specialize`
- // instruction with uses of the specialized value.
- //
- addUsesToWorkList(sharedContext, specInst);
- specInst->replaceUsesWith(specializedVal);
- specInst->removeAndDeallocate();
- }
- }
- }
- break;
-
- case kIROp_lookup_interface_method:
- {
- // We have a `lookup_interface_method` instruction,
- // so let's see whether it is a lookup in a known
- // witness table.
- IRLookupWitnessMethod* lookupInst = cast<IRLookupWitnessMethod>(inst);
-
- // We only want to deal with the case where the witness-table
- // argument points to a concrete global table (and not, e.g., a
- // `specialize` instruction that will yield a table)
- auto witnessTable = as<IRWitnessTable>(lookupInst->witnessTable.get());
- if(!witnessTable)
- break;
-
- // Use the witness table to look up the value that
- // satisfies the requirement.
- auto requirementKey = lookupInst->getRequirementKey();
- auto satisfyingVal = findWitnessVal(witnessTable, requirementKey);
- // We expect to always find something, but lets just
- // be careful here.
- if(!satisfyingVal)
- break;
-
- // If we get through all of the above checks, then we
- // have a (more) concrete method that implements the interface,
- // and so we should dispatch to that directly, rather than
- // use the `lookup_interface_method` instruction.
- addUsesToWorkList(sharedContext, lookupInst);
- lookupInst->replaceUsesWith(satisfyingVal);
- lookupInst->removeAndDeallocate();
- }
- break;
- }
- }
-
- static bool isInstSpecialized(
- IRSharedGenericSpecContext* sharedContext,
- IRInst* inst)
- {
- // If an instruction is still on our work list, then
- // it isn't specialized, and conversely we say that
- // if it *isn't* on the work list, it must be specialized.
- //
- // Note: if we end up with bugs in this logic, we could
- // maintain an explicit set of specialized insts instead.
- //
- return !sharedContext->workListSet.Contains(inst);
- }
-
- static bool canSpecializeInst(
- IRSharedGenericSpecContext* sharedContext,
- IRInst* inst)
- {
- // We can specialize an instruction once all its
- // operands are specialized.
-
- UInt operandCount = inst->getOperandCount();
- for(UInt ii = 0; ii < operandCount; ++ii)
- {
- IRInst* operand = inst->getOperand(ii);
- if(!isInstSpecialized(sharedContext, operand))
- return false;
- }
- return true;
- }
-
- // Go through the code in the module and try to identify
- // calls to generic functions where the generic arguments
- // are known, and specialize the callee based on those
- // known values.
- void specializeGenerics(
- IRModule* module,
- CodeGenTarget target)
- {
- IRSharedGenericSpecContext sharedContextStorage;
- auto sharedContext = &sharedContextStorage;
-
- initializeSharedSpecContext(
- sharedContext,
- module->session,
- module,
- module,
- target);
-
- auto moduleInst = module->getModuleInst();
-
- // First things first, let's deal with any bindings for global generic parameters.
- for(auto inst : moduleInst->getChildren())
- {
- auto bindInst = as<IRBindGlobalGenericParam>(inst);
- if(!bindInst)
- continue;
-
- // HACK: Our current front-end emit logic can end up emitting multiple
- // `bindGlobalGeneric` instructions for the same parameter. This is
- // a buggy behavior, but a real fix would require refactoring the way
- // global generic arguments are specified today.
- //
- // For now we will do a sanity check to detect parameters that
- // have already been specialized.
- if( !as<IRGlobalGenericParam>(bindInst->getOperand(0)) )
- {
- // parameter operand is no longer a parameter, so it
- // seems things must have been specialized already.
- continue;
- }
-
- auto param = bindInst->getParam();
- auto val = bindInst->getVal();
-
- param->replaceUsesWith(val);
- }
- {
- // Now we will do a second pass to clean up the
- // generic parameters and their bindings.
- IRInst* next = nullptr;
- for(auto inst = moduleInst->getFirstChild(); inst; inst = next)
- {
- next = inst->getNextInst();
-
- switch(inst->op)
- {
- default:
- break;
-
- case kIROp_GlobalGenericParam:
- case kIROp_BindGlobalGenericParam:
- // A "bind" instruction should have no uses in the
- // first place, and all the global generic parameters
- // should have had their uses replaced.
- SLANG_ASSERT(!inst->firstUse);
- inst->removeAndDeallocate();
- break;
- }
- }
- }
-
- // Our goal here is to find `specialize` instructions that
- // can be replaced with references to, e.g., a suitably
- // specialized function, and to resolve any `lookup_interface_method`
- // instructions to the concrete value fetched from a witness
- // table.
- //
- // We need to be careful of a few things:
- //
- // * It would not in general make sense to consider specialize-able
- // instructions under an `IRGeneric`, since that could mean "specialziing"
- // code to parameter values that are still unknown.
- //
- // * We *also* need to be careful not to specialize something when one
- // or more of its inputs is also a `specialize` or `lookup_interface_method`
- // instruction, because then we'd be propagating through non-concrete
- // values.
- //
- // The approach we use here is to build a work list of instructions
- // that *can* become fully specialized, but aren't yet. Any
- // instruction on the work list will be considered to be "unspecialized"
- // and any instruction not on the work list is considered specialized.
- //
- // We will start by recursively walking all the instructions to add
- // the appropriate ones to our work list:
- //
- addToSpecializationWorkListRec(sharedContext, moduleInst);
-
- // Now we are going to repeatedly walk our work list, and filter
- // it to create a new work list.
- List<IRInst*> workListCopy;
- for(;;)
- {
- // Swap out the work list on the context so we can
- // process it here without worrying about concurrent
- // modifications.
- workListCopy.Clear();
- workListCopy.SwapWith(sharedContext->workList);
-
- if(workListCopy.Count() == 0)
- break;
-
- for(auto inst : workListCopy)
- {
- // We need to check whether it is possible to specialize
- // the instruction yet (it might not be because its
- // operands haven't been specialized)
- if(!canSpecializeInst(sharedContext, inst))
- {
- // Put it back on the fresh work list, so that
- // we can re-consider it in another iteration.
- sharedContext->workList.Add(inst);
- }
- else
- {
- // Okay, perform any specialization step on this
- // instruction that makes sense (which might be
- // doing nothing).
- specializeGenericsForInst(sharedContext, inst);
-
- // Remove the instruction from consideration.
- sharedContext->workListSet.Remove(inst);
- }
- }
- }
-
- // Once the work list has gone dry, we should have the invariant
- // that there are no `specialize` instructions inside of non-generic
- // functions that in turn reference a generic function, *except*
- // in the case where that generic is for a builtin function, in
- // which case we wouldn't want to specialize it anyway.
- }
-
- void applyGlobalGenericParamSubstitution(
- IRSpecContext* /*context*/)
- {
- // TODO: we need to figure out how to apply this
- }
-
-
void markConstExpr(
IRBuilder* builder,
IRInst* irValue)
diff --git a/source/slang/ir.h b/source/slang/ir.h
index cf0ccae9a..86d1d1eb4 100644
--- a/source/slang/ir.h
+++ b/source/slang/ir.h
@@ -1133,5 +1133,4 @@ IRInst* createEmptyInstWithSize(
size_t totalSizeInBytes);
}
-
#endif
diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj
index 3ce172271..a36d9bfbf 100644
--- a/source/slang/slang.vcxproj
+++ b/source/slang/slang.vcxproj
@@ -181,18 +181,22 @@
<ClInclude Include="expr-defs.h" />
<ClInclude Include="glsl.meta.slang.h" />
<ClInclude Include="hlsl.meta.slang.h" />
+ <ClInclude Include="ir-clone.h" />
<ClInclude Include="ir-constexpr.h" />
<ClInclude Include="ir-dce.h" />
<ClInclude Include="ir-dominators.h" />
<ClInclude Include="ir-existential.h" />
+ <ClInclude Include="ir-glsl-legalize.h" />
<ClInclude Include="ir-inst-defs.h" />
<ClInclude Include="ir-insts.h" />
+ <ClInclude Include="ir-link.h" />
<ClInclude Include="ir-missing-return.h" />
<ClInclude Include="ir-restructure-scoping.h" />
<ClInclude Include="ir-restructure.h" />
<ClInclude Include="ir-sccp.h" />
<ClInclude Include="ir-serialize.h" />
<ClInclude Include="ir-specialize-resources.h" />
+ <ClInclude Include="ir-specialize.h" />
<ClInclude Include="ir-ssa.h" />
<ClInclude Include="ir-validate.h" />
<ClInclude Include="ir.h" />
@@ -232,17 +236,21 @@
<ClCompile Include="diagnostics.cpp" />
<ClCompile Include="dxc-support.cpp" />
<ClCompile Include="emit.cpp" />
+ <ClCompile Include="ir-clone.cpp" />
<ClCompile Include="ir-constexpr.cpp" />
<ClCompile Include="ir-dce.cpp" />
<ClCompile Include="ir-dominators.cpp" />
<ClCompile Include="ir-existential.cpp" />
+ <ClCompile Include="ir-glsl-legalize.cpp" />
<ClCompile Include="ir-legalize-types.cpp" />
+ <ClCompile Include="ir-link.cpp" />
<ClCompile Include="ir-missing-return.cpp" />
<ClCompile Include="ir-restructure-scoping.cpp" />
<ClCompile Include="ir-restructure.cpp" />
<ClCompile Include="ir-sccp.cpp" />
<ClCompile Include="ir-serialize.cpp" />
<ClCompile Include="ir-specialize-resources.cpp" />
+ <ClCompile Include="ir-specialize.cpp" />
<ClCompile Include="ir-ssa.cpp" />
<ClCompile Include="ir-validate.cpp" />
<ClCompile Include="ir.cpp" />
diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters
index 9f3666fc1..390c0cc5f 100644
--- a/source/slang/slang.vcxproj.filters
+++ b/source/slang/slang.vcxproj.filters
@@ -42,6 +42,9 @@
<ClInclude Include="hlsl.meta.slang.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="ir-clone.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="ir-constexpr.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -54,12 +57,18 @@
<ClInclude Include="ir-existential.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="ir-glsl-legalize.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="ir-inst-defs.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ir-insts.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="ir-link.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="ir-missing-return.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -78,6 +87,9 @@
<ClInclude Include="ir-specialize-resources.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="ir-specialize.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="ir-ssa.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -191,6 +203,9 @@
<ClCompile Include="emit.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="ir-clone.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="ir-constexpr.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -203,9 +218,15 @@
<ClCompile Include="ir-existential.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="ir-glsl-legalize.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="ir-legalize-types.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="ir-link.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="ir-missing-return.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@@ -224,6 +245,9 @@
<ClCompile Include="ir-specialize-resources.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="ir-specialize.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="ir-ssa.cpp">
<Filter>Source Files</Filter>
</ClCompile>