diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2020-12-11 08:50:43 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-12-11 08:50:43 -0800 |
| commit | 992778e25c444932921ce92fe7934893b2aca35f (patch) | |
| tree | 4351c61079da5586c5f469dc8c989364c7a2bd4e /source/slang/slang-emit-c-like.cpp | |
| parent | 4337338ed2d9525b4638f32c6b91ef61b69e41cd (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-emit-c-like.cpp')
| -rw-r--r-- | source/slang/slang-emit-c-like.cpp | 100 |
1 files changed, 13 insertions, 87 deletions
diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp index fa3b7c6c4..eefa2363c 100644 --- a/source/slang/slang-emit-c-like.cpp +++ b/source/slang/slang-emit-c-like.cpp @@ -130,6 +130,7 @@ CLikeSourceEmitter::CLikeSourceEmitter(const Desc& desc) SLANG_ASSERT(m_sourceLanguage != SourceLanguage::Unknown); m_target = desc.target; + m_targetCaps = desc.targetCaps; m_compileRequest = desc.compileRequest; m_entryPointStage = desc.entryPointStage; @@ -392,46 +393,6 @@ void CLikeSourceEmitter::maybeCloseParens(bool needClose) if(needClose) m_writer->emit(")"); } -bool CLikeSourceEmitter::isTargetIntrinsicModifierApplicable(const String& targetName) -{ - switch(getSourceLanguage()) - { - default: - SLANG_DIAGNOSE_UNEXPECTED(getSink(), SourceLoc(), "unhandled code generation target"); - return false; - - case SourceLanguage::C: return targetName == "c"; - case SourceLanguage::CPP: return targetName == "cpp"; - case SourceLanguage::GLSL: return targetName == "glsl"; - case SourceLanguage::HLSL: return targetName == "hlsl"; - case SourceLanguage::CUDA: return targetName == "cuda"; - } -} - -bool CLikeSourceEmitter::isTargetIntrinsicModifierApplicable(IRTargetIntrinsicDecoration* decoration) -{ - auto targetName = String(decoration->getTargetName()); - - // If no target name was specified, then the modifier implicitly - // applies to all targets. - if(targetName.getLength() == 0) - return true; - - return isTargetIntrinsicModifierApplicable(targetName); -} - -bool CLikeSourceEmitter::isTargetIntrinsicModifierBetter(IRTargetIntrinsicDecoration* candidate, IRTargetIntrinsicDecoration* existing) -{ - // For now, the rule is that an empty string represents a catch-all - // definition, which is worse than any target-specific declaration. - // Therefore, if the new `candidate` has a non-empty target name - // specified, then it is automatically better (or at least as - // good) as `existing`. - // - SLANG_UNUSED(existing); - return candidate->getTargetName().getLength() != 0; -} - void CLikeSourceEmitter::emitStringLiteral(String const& value) { m_writer->emit("\""); @@ -669,7 +630,7 @@ String CLikeSourceEmitter::generateName(IRInst* inst) // If the instruction names something // that should be emitted as a target intrinsic, // then use that name instead. - if(auto intrinsicDecoration = findTargetIntrinsicDecoration(inst)) + if(auto intrinsicDecoration = findBestTargetIntrinsicDecorationXXX(inst)) { return String(intrinsicDecoration->getDefinition()); } @@ -940,6 +901,7 @@ bool CLikeSourceEmitter::shouldFoldInstIntoUseSites(IRInst* inst) case kIROp_IntLit: case kIROp_FloatLit: case kIROp_BoolLit: + case kIROp_CapabilitySet: return true; // Always fold these in, because their results @@ -1130,7 +1092,7 @@ bool CLikeSourceEmitter::shouldFoldInstIntoUseSites(IRInst* inst) // This is significant, because we can within a target intrinsics definition multiple accesses to the same // parameter. This is not indicated into the call, and can lead to output code computes something multiple // times as it is folding into the expression of the the target intrinsic, which we don't want. - if (auto targetIntrinsicDecoration = findTargetIntrinsicDecoration(funcValue)) + if (auto targetIntrinsicDecoration = findBestTargetIntrinsicDecorationXXX(funcValue)) { // Find the index of the original instruction, to see if it's multiply used. IRUse* args = callInst->getArgs(); @@ -1333,50 +1295,14 @@ void CLikeSourceEmitter::emitInstResultDecl(IRInst* inst) m_writer->emit(" = "); } -IRTargetIntrinsicDecoration* CLikeSourceEmitter::findTargetIntrinsicDecoration(IRInst* inInst) +IRTargetSpecificDecoration* CLikeSourceEmitter::findBestTargetDecoration(IRInst* inInst) { - // An intrinsic generic function will be invoked through a `specialize` instruction, - // so the callee won't directly be the thing that is decorated. We will look up - // through specializations until we can see the actual thing being called. - // - IRInst* inst = inInst; - while (auto specInst = as<IRSpecialize>(inst)) - { - inst = getSpecializedValue(specInst); - - // If `getSpecializedValue` can't find the result value - // of the generic being specialized, then it returns - // the original instruction. This would be a disaster - // for use because this loop would go on forever. - // - // This case should never happen if the stdlib is well-formed - // and the compiler is doing its job right. - // - SLANG_ASSERT(inst != specInst); - } - - // 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. - // - IRTargetIntrinsicDecoration* best = nullptr; - for(auto dd : inst->getDecorations()) - { - if (dd->op != kIROp_TargetIntrinsicDecoration) - continue; - - auto targetIntrinsic = (IRTargetIntrinsicDecoration*)dd; - if (!isTargetIntrinsicModifierApplicable(targetIntrinsic)) - continue; - - if(!best || isTargetIntrinsicModifierBetter(targetIntrinsic, best)) - best = targetIntrinsic; - } + return Slang::findBestTargetDecoration(inInst, getTargetCaps()); +} - return best; +IRTargetIntrinsicDecoration* CLikeSourceEmitter::findBestTargetIntrinsicDecorationXXX(IRInst* inInst) +{ + return as<IRTargetIntrinsicDecoration>(findBestTargetDecoration(inInst)); } /* static */bool CLikeSourceEmitter::isOrdinaryName(UnownedStringSlice const& name) @@ -2029,7 +1955,7 @@ void CLikeSourceEmitter::emitCallExpr(IRCall* inst, EmitOpInfo outerPrec) // We want to detect any call to an intrinsic operation, // that we can emit it directly without mangling, etc. - if(auto targetIntrinsic = findTargetIntrinsicDecoration(funcValue)) + if(auto targetIntrinsic = findBestTargetIntrinsicDecorationXXX(funcValue)) { emitIntrinsicCallExpr(inst, targetIntrinsic, outerPrec); } @@ -3408,7 +3334,7 @@ bool CLikeSourceEmitter::isTargetIntrinsic(IRFunc* func) // it has a suitable decoration marking it as a // target intrinsic for the current compilation target. // - return findTargetIntrinsicDecoration(func) != nullptr; + return findBestTargetIntrinsicDecorationXXX(func) != nullptr; } void CLikeSourceEmitter::emitFunc(IRFunc* func) @@ -3441,7 +3367,7 @@ void CLikeSourceEmitter::emitStruct(IRStructType* structType) { // If the selected `struct` type is actually an intrinsic // on our target, then we don't want to emit anything at all. - if(auto intrinsicDecoration = findTargetIntrinsicDecoration(structType)) + if(auto intrinsicDecoration = findBestTargetIntrinsicDecorationXXX(structType)) { return; } |
