summaryrefslogtreecommitdiff
path: root/source/slang/slang-ir.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2020-12-11 08:50:43 -0800
committerGitHub <noreply@github.com>2020-12-11 08:50:43 -0800
commit992778e25c444932921ce92fe7934893b2aca35f (patch)
tree4351c61079da5586c5f469dc8c989364c7a2bd4e /source/slang/slang-ir.cpp
parent4337338ed2d9525b4638f32c6b91ef61b69e41cd (diff)
Add first steps toward a "capability" system (#1636)
* Add first steps toward a "capability" system We already have cases in the stdlib where we mark declarations as being specific to certain targets, e.g.: ``` // My ordinary function to add two numbers. // Works everywhere. // void myFunc(int a, int b) { return a + b; } // On the "coolgpu" target, we can use a secret intrinsic // that adds numbers even faster! // __specialized_for_target(coolgpu) void myFunc(int a, int b) { return __secretIntrinsic(a, b); } ``` The existing logic for dealing with these modifiers (`__specialized_for_target` and `__target_intrinsic`) was almost entirely string-based. We would turn the chosen compilation target into a string, and then use that to try and search for the "best" definition of a function at a few steps: * During IR linking, we always pick one definition of an `[import]`ed function, and that definition will be the one with the "best" target-specialization modifier (if any) * During final code generation, we always look up the "best" target-intrinsic modifier, and use it as the template for the code we output. This change preserves the basic flow there, but replaces the ad hoc string-based logic with something a bit more principled, in terms of a new `CapabilitySet` type. A `CapabilitySet` represents a set of zero or more atomic features (here represented as `CapabilityAtom`s). What a `CapabilitySet` means depends on how and where it is used: * A compilation target implies a `CapabilitySet` where the contents of the set are the features the target *supports*. * A `CapabilitySet` attached to a declaration (or a modifier on that declaration) describes a set of feature that declaration *requires*. The current implementation of `CapabilitySet` is wasteful and inefficient, but that is something we can iterate on over time. In practice, most of the current code only ever uses capability sets that are either empty (because they represent a function with no specific requirements) or singleton (because they represent asingle atomic capability like "is a GLSL target," "is an HLSL target," etc.). The main goal here was to put in the skeleton of a new system, including some of the features it might need down the line, and then to leave changes that eventually use the greater flexibility for later. Eventually, the capability system should encompass: * Differences between shader model versions, GLSL versions, SPIR-V versions, etc. (currently tracked with other modifiers) * Optional extensions, and functions that are made available only with certain extensions (currently tracked with other modifiers) * Front-end checking that the call graph of a program doesn't violate any capability-requirements (e.g., having a GLSL+HLSL portable function call a GLSL-only subroutine) * Hypothetically we can also try to fold stage-specific (vertex-only, fragment-only, etc.) functions into this system, but doing so would require more linker cleverness if we allow overloading on stages (since we might have to clone a caller if it calls through to a callee with multiple stage-specific versions) One important complication that the system has to deal with just because of the "do what I mean" nature of the current compiler is that somethings a current Slang user might compile for target X and specify version N, but then use a function that actually requires version N+1 of that target. Currently the Slang compiler silently "upgrades" the version(s) used by user code in these cases, because it is often what users want in cross-compilation scenarios. Dealing with the "silent upgrade" situation requires us to be a little careful and sometimes pick a "best" capability set that doesn't appear to be supported on our target. Refining that system and potentially getting rid of the "do what I mean" behavior over time could be a goal for future changes. * fixup: handle case where value is incompatible during linking
Diffstat (limited to 'source/slang/slang-ir.cpp')
-rw-r--r--source/slang/slang-ir.cpp153
1 files changed, 130 insertions, 23 deletions
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index 99c601051..aa72cc0c3 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -234,6 +234,23 @@ namespace Slang
}
}
+ // IRCapabilitySet
+
+ CapabilitySet IRCapabilitySet::getCaps()
+ {
+ List<CapabilityAtom> atoms;
+
+ Index count = (Index) getOperandCount();
+ for(Index i = 0; i < count; ++i)
+ {
+ auto operand = cast<IRIntLit>(getOperand(i));
+ atoms.add(CapabilityAtom(operand->getValue()));
+ }
+
+ return CapabilitySet(atoms.getCount(), atoms.getBuffer());
+ }
+
+
// IRParam
IRParam* IRParam::getNextParam()
@@ -2138,6 +2155,21 @@ namespace Slang
return (IRPtrLit*) findOrEmitConstant(this, keyInst);
}
+ IRInst* IRBuilder::getCapabilityValue(CapabilitySet const& caps)
+ {
+ IRType* capabilityAtomType = getIntType();
+ IRType* capabilitySetType = getCapabilitySetType();
+
+ List<IRInst*> args;
+ for( auto atom : caps.getAtoms() )
+ {
+ args.add(getIntValue(capabilityAtomType, Int(atom)));
+ }
+
+ return findOrEmitHoistableInst(
+ capabilitySetType, kIROp_CapabilitySet, args.getCount(), args.getBuffer());
+ }
+
IRInst* IRBuilder::findOrEmitHoistableInst(
IRType* type,
IROp op,
@@ -2406,6 +2438,11 @@ namespace Slang
return (IRStringType*)getType(kIROp_StringType);
}
+ IRType* IRBuilder::getCapabilitySetType()
+ {
+ return getType(kIROp_CapabilitySetType);
+ }
+
IRDynamicType* IRBuilder::getDynamicType() { return (IRDynamicType*)getType(kIROp_DynamicType); }
IRAssociatedType* IRBuilder::getAssociatedType(ArrayView<IRInterfaceType*> constraintTypes)
@@ -5615,23 +5652,76 @@ namespace Slang
return t;
}
- IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration(
- IRInst* val,
- String const& targetName)
+ //
+ // IRTargetIntrinsicDecoration
+ //
+
+ static bool _areIntrinsicCapsBetterForTarget(
+ CapabilitySet const& candidateCaps,
+ CapabilitySet const& existingCaps,
+ CapabilitySet const& targetCaps)
+ {
+ bool candidateIsAvailable = targetCaps.implies(candidateCaps);
+ bool existingIsAvailable = targetCaps.implies(existingCaps);
+ if(candidateIsAvailable != existingIsAvailable)
+ return candidateIsAvailable;
+
+ if(candidateCaps.implies(existingCaps))
+ return true;
+
+ return false;
+ }
+
+ IRTargetIntrinsicDecoration* findAnyTargetIntrinsicDecoration(
+ IRInst* val)
{
- for(auto dd : val->getDecorations())
+ IRInst* inst = getResolvedInstForDecorations(val);
+ return inst->findDecoration<IRTargetIntrinsicDecoration>();
+ }
+
+ IRTargetSpecificDecoration* findBestTargetDecoration(
+ IRInst* inInst,
+ CapabilitySet const& targetCaps)
+ {
+ IRInst* inst = getResolvedInstForDecorations(inInst);
+
+ // We will search through all the `IRTargetIntrinsicDecoration`s on
+ // the instruction, looking for those that are applicable to the
+ // current code generation target. Among the application decorations
+ // we will try to find one that is "best" in the sense that it is
+ // more (or at least as) specialized for the target than the
+ // others.
+ //
+ IRTargetSpecificDecoration* bestDecoration = nullptr;
+ CapabilitySet bestCaps;
+ for(auto dd : inst->getDecorations())
{
- if(dd->op != kIROp_TargetIntrinsicDecoration)
+ auto decoration = as<IRTargetSpecificDecoration>(dd);
+ if(!decoration)
continue;
- auto decoration = (IRTargetIntrinsicDecoration*) dd;
- if(String(decoration->getTargetName()) == targetName)
- return decoration;
+ auto decorationCaps = decoration->getTargetCaps();
+ if (decorationCaps.isIncompatibleWith(targetCaps))
+ continue;
+
+ if(!bestDecoration || _areIntrinsicCapsBetterForTarget(decorationCaps, bestCaps, targetCaps))
+ {
+ bestDecoration = decoration;
+ bestCaps = decorationCaps;
+ }
}
- return nullptr;
+ return bestDecoration;
}
+ IRTargetSpecificDecoration* findBestTargetDecoration(
+ IRInst* val,
+ CapabilityAtom targetCapabilityAtom)
+ {
+ return findBestTargetDecoration(val, CapabilitySet(targetCapabilityAtom));
+ }
+
+
#if 0
IRFunc* cloneSimpleFuncWithoutRegistering(IRSpecContextBase* context, IRFunc* originalFunc)
{
@@ -5671,29 +5761,46 @@ namespace Slang
IRInst* findSpecializeReturnVal(IRSpecialize* specialize)
{
- auto generic = findSpecializedGeneric(specialize);
- if(!generic)
- return nullptr;
+ auto base = specialize->getBase();
+
+ while( auto baseSpec = as<IRSpecialize>(base) )
+ {
+ auto returnVal = findSpecializeReturnVal(baseSpec);
+ if(!returnVal)
+ break;
- return findGenericReturnVal(generic);
+ base = returnVal;
+ }
+
+ if( auto generic = as<IRGeneric>(base) )
+ {
+ return findGenericReturnVal(generic);
+ }
+
+ return nullptr;
}
IRInst* getResolvedInstForDecorations(IRInst* inst)
{
IRInst* candidate = inst;
- while(auto specInst = as<IRSpecialize>(candidate))
+ for(;;)
{
- auto genericInst = as<IRGeneric>(specInst->getBase());
- if(!genericInst)
- break;
-
- auto returnVal = findGenericReturnVal(genericInst);
- if(!returnVal)
- break;
+ if(auto specInst = as<IRSpecialize>(candidate))
+ {
+ candidate = specInst->getBase();
+ continue;
+ }
+ if( auto genericInst = as<IRGeneric>(candidate) )
+ {
+ if( auto returnVal = findGenericReturnVal(genericInst) )
+ {
+ candidate = returnVal;
+ continue;
+ }
+ }
- candidate = returnVal;
+ return candidate;
}
- return candidate;
}
bool isDefinition(