diff options
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/hlsl.meta.slang | 33 | ||||
| -rw-r--r-- | source/slang/slang-capability-defs.h | 62 | ||||
| -rw-r--r-- | source/slang/slang-capability.cpp | 428 | ||||
| -rw-r--r-- | source/slang/slang-capability.h | 156 | ||||
| -rwxr-xr-x | source/slang/slang-compiler.h | 4 | ||||
| -rw-r--r-- | source/slang/slang-emit-c-like.cpp | 100 | ||||
| -rw-r--r-- | source/slang/slang-emit-c-like.h | 30 | ||||
| -rw-r--r-- | source/slang/slang-emit.cpp | 6 | ||||
| -rw-r--r-- | source/slang/slang-hlsl-intrinsic-set.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang-ir-glsl-legalize.cpp | 2 | ||||
| -rw-r--r-- | source/slang/slang-ir-inst-defs.h | 4 | ||||
| -rw-r--r-- | source/slang/slang-ir-insts.h | 48 | ||||
| -rw-r--r-- | source/slang/slang-ir-legalize-varying-params.cpp | 12 | ||||
| -rw-r--r-- | source/slang/slang-ir-link.cpp | 165 | ||||
| -rw-r--r-- | source/slang/slang-ir-specialize.cpp | 27 | ||||
| -rw-r--r-- | source/slang/slang-ir.cpp | 153 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 40 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 68 |
18 files changed, 1077 insertions, 265 deletions
diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang index 9893effea..29779e796 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -4438,43 +4438,50 @@ __magic_type(Texture, $(feedbackTexture2DFlavor)) __intrinsic_type($(kIROp_TextureType + (feedbackTexture2DFlavor << kIROpMeta_OtherShift))) struct FeedbackTexture2D<T : __BuiltinSamplerFeedbackType> { - __target_intrinsic(hlsl) + __target_intrinsic void GetDimensions(out uint width, out uint height); - __target_intrinsic(hlsl) + __target_intrinsic void GetDimensions(uint mipLevel, out uint width, out uint height, out uint numberOfLevels); - __target_intrinsic(hlsl) + __target_intrinsic void GetDimensions(out float width,out float height); - __target_intrinsic(hlsl) + __target_intrinsic void GetDimensions(uint mipLevel, out float width,out float height, out float numberOfLevels); // With Clamp __target_intrinsic(hlsl, "($0).WriteSamplerFeedback($1, $2, $3, $4)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedback($1, $2, $3, $4)") void WriteSamplerFeedback<S>(Texture2D<S> tex, SamplerState samp, float2 location, float clamp); __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackBias($1, $2, $3, $4, $5)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackBias($1, $2, $3, $4, $5)") void WriteSamplerFeedbackBias<S>(Texture2D<S> tex, SamplerState samp, float2 location, float bias, float clamp); __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackGrad($1, $2, $3, $4, $5, $6)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackGrad($1, $2, $3, $4, $5, $6)") void WriteSamplerFeedbackGrad<S>(Texture2D<S> tex, SamplerState samp, float2 location, float2 ddx, float2 ddy, float clamp); // Level __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackLevel($1, $2, $3, $4)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackLevel($1, $2, $3, $4)") void WriteSamplerFeedbackLevel<S>(Texture2D<S> tex, SamplerState samp, float2 location, float lod); // Without Clamp __target_intrinsic(hlsl, "($0).WriteSamplerFeedback($1, $2, $3)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedback($1, $2, $3)") void WriteSamplerFeedback<S>(Texture2D<S> tex, SamplerState samp, float2 location); __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackBias($1, $2, $3, $4)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackBias($1, $2, $3, $4)") void WriteSamplerFeedbackBias<S>(Texture2D<S> tex, SamplerState samp, float2 location, float bias); __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackGrad($1, $2, $3, $4, $5)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackGrad($1, $2, $3, $4, $5)") void WriteSamplerFeedbackGrad<S>(Texture2D<S> tex, SamplerState samp, float2 location, float2 ddx, float2 ddy); }; @@ -4484,40 +4491,50 @@ __magic_type(Texture, $(feedbackTexture2DArrayFlavor)) __intrinsic_type($(kIROp_TextureType + (feedbackTexture2DArrayFlavor << kIROpMeta_OtherShift))) struct FeedbackTexture2DArray<T : __BuiltinSamplerFeedbackType> { - __target_intrinsic(hlsl) + __target_intrinsic void GetDimensions(out uint width,out uint height, out uint elements); - __target_intrinsic(hlsl) + + __target_intrinsic void GetDimensions(uint mipLevel, out uint width,out uint height, out uint elements, out uint numberOfLevels); - __target_intrinsic(hlsl) + + __target_intrinsic void GetDimensions(out float width,out float height, out float elements); - __target_intrinsic(hlsl) + + __target_intrinsic void GetDimensions(uint mipLevel, out float width,out float height, out float elements, out float numberOfLevels); // With Clamp __target_intrinsic(hlsl, "($0).WriteSamplerFeedback($1, $2, $3, $4)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedback($1, $2, $3, $4)") void WriteSamplerFeedback<S>(Texture2DArray<S> texArray, SamplerState samp, float3 location, float clamp); __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackBias($1, $2, $3, $4, $5)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackBias($1, $2, $3, $4, $5)") void WriteSamplerFeedbackBias<S>(Texture2DArray<S> texArray, SamplerState samp, float3 location, float bias, float clamp); __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackGrad($1, $2, $3, $4, $5, $6)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackGrad($1, $2, $3, $4, $5, $6)") void WriteSamplerFeedbackGrad<S>(Texture2DArray<S> texArray, SamplerState samp, float3 location, float3 ddx, float3 ddy, float clamp); // Level __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackLevel($1, $2, $3, $4)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackLevel($1, $2, $3, $4)") void WriteSamplerFeedbackLevel<S>(Texture2DArray<S> texArray, SamplerState samp, float3 location, float lod); // Without Clamp __target_intrinsic(hlsl, "($0).WriteSamplerFeedback($1, $2, $3)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedback($1, $2, $3)") void WriteSamplerFeedback<S>(Texture2DArray<S> texArray, SamplerState samp, float3 location); __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackBias($1, $2, $3, $4)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackBias($1, $2, $3, $4)") void WriteSamplerFeedbackBias<S>(Texture2DArray<S> texArray, SamplerState samp, float3 location, float bias); __target_intrinsic(hlsl, "($0).WriteSamplerFeedbackGrad($1, $2, $3, $4, $5)") + __target_intrinsic(cpp, "($0).WriteSamplerFeedbackGrad($1, $2, $3, $4, $5)") void WriteSamplerFeedbackGrad<S>(Texture2DArray<S> texArray, SamplerState samp, float3 location, float3 ddx, float3 ddy); }; diff --git a/source/slang/slang-capability-defs.h b/source/slang/slang-capability-defs.h new file mode 100644 index 000000000..8bf1d80e9 --- /dev/null +++ b/source/slang/slang-capability-defs.h @@ -0,0 +1,62 @@ +// slang-capability-defs.h + +// This file uses macros to define the capability "atoms" that +// are used by the `CapabilitySet` implementation. +// +// Any file that `#include`s this file is required to set +// the `SLANG_CAPABILITY_ATOM` macro before including it. +// +#ifndef SLANG_CAPABILITY_ATOM +#error Must define SLANG_CAPABILITY_ATOM before including. +#endif +// +// It is not necessary to `#undef` the macro in the client +// file, because this file will `#undef` it at the end. + +// Our representation allows each capability atom to define +// a number of other base atoms that it "inherits" from. +// +// Different atoms will need different numbers of bases, +// so we will define a few different macros that wrap +// `SLANG_CAPABILITY_ATOM` and let us handle the cases +// more conveniently. +// +// TODO: There is probably a way to handle this with +// variadic macros. +// +#define SLANG_CAPABILITY_ATOM4(ENUMERATOR, NAME, FLAGS, BASE0, BASE1, BASE2, BASE3) \ + SLANG_CAPABILITY_ATOM(ENUMERATOR, NAME, FLAGS, BASE0, BASE1, BASE2, BASE3) + +#define SLANG_CAPABILITY_ATOM3(ENUMERATOR, NAME, FLAGS, BASE0, BASE1, BASE2) \ + SLANG_CAPABILITY_ATOM(ENUMERATOR, NAME, FLAGS, BASE0, BASE1, BASE2, Invalid) + +#define SLANG_CAPABILITY_ATOM2(ENUMERATOR, NAME, FLAGS, BASE0, BASE1) \ + SLANG_CAPABILITY_ATOM(ENUMERATOR, NAME, FLAGS, BASE0, BASE1, Invalid, Invalid) + +#define SLANG_CAPABILITY_ATOM1(ENUMERATOR, NAME, FLAGS, BASE0) \ + SLANG_CAPABILITY_ATOM(ENUMERATOR, NAME, FLAGS, BASE0, Invalid, Invalid, Invalid) + +#define SLANG_CAPABILITY_ATOM0(ENUMERATOR, NAME, FLAGS) \ + SLANG_CAPABILITY_ATOM(ENUMERATOR, NAME, FLAGS, Invalid, Invalid, Invalid, Invalid) + +// The `__target` capability exists only to provide a common +// abstract base for the capabilities that represent each +// of our compilation targets. +// +SLANG_CAPABILITY_ATOM0(Target, __target, Abstract) + +SLANG_CAPABILITY_ATOM1(HLSL, hlsl, Concrete, Target) +SLANG_CAPABILITY_ATOM1(GLSL, glsl, Concrete, Target) +SLANG_CAPABILITY_ATOM1(C, c, Concrete, Target) +SLANG_CAPABILITY_ATOM1(CPP, cpp, Concrete, Target) +SLANG_CAPABILITY_ATOM1(CUDA, cuda, Concrete, Target) +SLANG_CAPABILITY_ATOM1(SPIRV, spirv, Concrete, Target) + + +#undef SLANG_CAPABILITY_ATOM0 +#undef SLANG_CAPABILITY_ATOM1 +#undef SLANG_CAPABILITY_ATOM2 +#undef SLANG_CAPABILITY_ATOM3 +#undef SLANG_CAPABILITY_ATOM4 + +#undef SLANG_CAPABILITY_ATOM diff --git a/source/slang/slang-capability.cpp b/source/slang/slang-capability.cpp new file mode 100644 index 000000000..7b4361a58 --- /dev/null +++ b/source/slang/slang-capability.cpp @@ -0,0 +1,428 @@ +// slang-capability.cpp +#include "slang-capability.h" + +// This file implements the core of the "capability" system. + +namespace Slang +{ + +// +// CapabilityAtom +// + +// We are going to divide capabilities into a few categories, +// which will be represented as flags for now. +// +// Every capability will be either concrete or abstract. +// An abstract capability basically represents a category +// of related capabilities that all fill a similar role. +// For example, we could have an abstract capability that +// represents "stages" and then the concrete capabilities +// `vertex`, `fragment`, etc. would inherit from it. +// +// Abstract capabilities are critical in our model for +// knowing when two capabilities are fundamentally incompatible. +// For example, it is meaningless to compile code for both +// the `vertex` and `fragment` capabilities at the same time, +// because no target processor supports both at once. +// +// TODO: It is possible that instead of flags this could simply +// identify a "kind" of atom, with two different states. +// +// TODO: It is likely that in a future change we will want to +// add a third case here for "alias" capabilities, which are +// pseudo-atomic capabilities that are just equivalent to +// the set of their bases. +// +typedef uint32_t CapabilityAtomFlags; +enum : CapabilityAtomFlags +{ + kCapabilityAtomFlags_Concrete = 0, + kCapabilityAtomFlags_Abstract = 1 << 0, +}; + +// The macros in the `slang-capability-defs.h` file will be used +// to fill out a `static const` array of information about each +// capability atom. +// +struct CapabilityAtomInfo +{ + /// The API-/language-exposed name of the capability. + char const* name; + + /// Flags to determine if the capability is concrete-vs-abstract, etc. + CapabilityAtomFlags flags; + CapabilityAtom bases[4]; +}; +// +// The array is going to be sized to include an entry for `CapabilityAtom::Invalid` +// which as a value of -1, so we need to size the array one larger than the `Count` +// value. +// +static const CapabilityAtomInfo kCapabilityAtoms[Int(CapabilityAtom::Count) + 1] = +{ + { "invalid", 0, { CapabilityAtom::Invalid, CapabilityAtom::Invalid, CapabilityAtom::Invalid, CapabilityAtom::Invalid } }, + +#define SLANG_CAPABILITY_ATOM(ENUMERATOR, NAME, FLAGS, BASE0, BASE1, BASE2, BASE3) \ + { #NAME, kCapabilityAtomFlags_##FLAGS, { CapabilityAtom::BASE0, CapabilityAtom::BASE1, CapabilityAtom::BASE2, CapabilityAtom::BASE3 } }, +#include "slang-capability-defs.h" +}; + + /// Get the extended information structure for the given capability `atom` +static CapabilityAtomInfo const& _getInfo(CapabilityAtom atom) +{ + SLANG_ASSERT(Int(atom) < Int(CapabilityAtom::Count)); + return kCapabilityAtoms[Int(atom) + 1]; +} + +// One capability set or capability atom A implies another set/atom B +// if any target that supports all of the atoms in A must also support +// all of those in B. + + /// Does `thisAtom` imply `thatAtom`? +static bool _implies(CapabilityAtom thisAtom, CapabilityAtom thatAtom) +{ + // When looking at atoms, the immediate easy case is when + // the two atoms are the same: an atomic capability always + // implies itself. + // + if(thisAtom == thatAtom) + return true; + + // Otherwise, we want to look at the bases of `thisAtom` + // to see if any of them imply `thatAtom`, since `thisAtom` + // implies each of its bases. + // + auto& thisAtomInfo = _getInfo(thisAtom); + for( auto thisAtomBase : thisAtomInfo.bases ) + { + // The lists of bases are currently using `Invalid` as + // a sentinel value to terminate them, so we need to + // bail out of the loop when we see the sentinel. + // + if(thisAtomBase == CapabilityAtom::Invalid) + break; + + if(_implies(thisAtomBase, thatAtom)) + return true; + } + + return false; +} + + /// Does `base` have any abstract capabilities in common with `otherAtom` + /// + /// This subroutine is a helper for `_isIncompatible`. +static bool _hasAbstractBaseInCommon(CapabilityAtom base, CapabilityAtom otherAtom) +{ + // First we check the case where `base` itself is an abstract + // capability atom. + // + auto& baseAtomInfo = _getInfo(base); + if(baseAtomInfo.flags & kCapabilityAtomFlags_Abstract) + { + // If `base` is abstract, and `otherAtom` implies `base`, + // then that means that `otherAtom` includes one or + // more atoms that inherit from `base`, and thus the + // two have an abstract base in common. + // + if( _implies(otherAtom, base) ) + return true; + } + + // If `base` itself has bases, then we want to check if any + // of *those* are abstract bases that overlap with `otherAtom`. + // + for( auto baseBase : baseAtomInfo.bases ) + { + if(baseBase == CapabilityAtom::Invalid) + break; + + if(_hasAbstractBaseInCommon(baseBase, otherAtom)) + return true; + } + + // If we didn't manage to find any overlaps, then we conclude + // that there are no shared abstract bases. + // + return false; +} + + /// Is `thisAtom` incompatible with `thatAtom` (such that no target could ever support both at once) +static bool _isIncompatible(CapabilityAtom thisAtom, CapabilityAtom thatAtom) +{ + // If either atom implies the other, then they aren't incompatible. + // + // For example, if there is an atom representing `sm_5_1` that inherits + // from an atom representing `sm_5_0`, then clearly the two aren't + // in any way incompatible (a single target can support both). + // + if(_implies(thisAtom, thatAtom) || _implies(thatAtom, thisAtom)) + return false; + + // If the two atoms are not in an inheritance relationship, then one of + // a few cases can apply: + // + // * They have no common bases; in this case they are compatible. + // An example would be `vertex` and `sm_5_0`. + // + // * They have a common base, but it is not marked abstract; in + // this case they are compatible. E.g., two GLSL extensions that + // both inherit from the `glsl` capability should not conflict. + // + // * They have a common base that is marked abstract; in this + // case they are incompatible. An example would be `vertex` + // and `fragment` both inheriting from the abstract atom + // `__stage`. + // + // To summarize the above list, we note that two atoms are + // incompatible with they have an abstract base in common. + // + return _hasAbstractBaseInCommon(thisAtom, thatAtom); + + // TODO: The above logic is a bit off, but in a way that doesn't + // matter just yet. + // + // We currently have capabilities like: + // + // abstract capability __target; + // capability hlsl : __target; + // capability glsl : __target; + // + // In this case it is clear that `hlsl` and `glsl` should + // be incompatible, and that the rules as implemented + // make that the case. + // + // A problem arises when we start to add things like extensions: + // + // capability EXT_cool_thing : glsl; + // capability EXT_other_stuff : glsl; + // + // In this case, it also seems clear that `EXT_cool_thing` + // and `EXT_other_stuff` should be mutually compatible. + // However, with the rules implemented here right now, they + // would be found incompatible because they share the + // abstract base `__target`. + // + // In this specific case, we know that the relationship + // between the extensions is fine because they both inherit + // from `__target` *through* the concrete atom `glsl`. + // + // Before adding capabilities that represent optional + // extensions like this we need to codify the semantics + // for how incompatibility checks should work in terms + // of the inheritance graph of capability atoms. +} + +CapabilityAtom findCapabilityAtom(UnownedStringSlice const& name) +{ + // For now we are implementing a linear search over the + // array of capability atoms to perform name lookup. + // + for( Index i = 0; i < Index(CapabilityAtom::Count); ++i ) + { + // Note: using `_getInfo` here instead of accessing + // the `kCapabilityAtoms` array directly lets us + // avoid dealing with the offset-by-one indexing + // choice. + // + auto& capInfo = _getInfo(CapabilityAtom(i)); + if(name == UnownedTerminatedStringSlice(capInfo.name)) + return CapabilityAtom(i); + } + return CapabilityAtom::Invalid; +} + +// +// CapabilitySet +// + +// The current design choice in `CapabilitySet` is that it blindly +// stores exactly the atoms it is told to, without any up-front +// processing. +// +// This choice has some down-sides, and there are other representations +// that could be much nicer in the future. Possible improcements include: +// +// * The list of atoms could be *expanded* so that if it contains atom A +// and atom A implies atom B, then the list should also include B. +// +// * The list of atoms could be *minimized*, such that if atom A implies +// atom B, then any list that contains A does not include B (both +// expanded and minimized lists have different benefits). +// +// * The list of atoms could be deduplicated. +// +// * The list of atoms could be sorted. +// +// * The lists could be deduplicated and cached in some central place +// (the like the session) so that repreated attempts to create the +// same capability sets return the same objects. +// +// In some parts of the code below we will call out how these improvements +// could affect the algorithms used. + +// Given our simple choices right now, the constructors for `CapabilitySet` +// are all straightforward: just adding the right atoms to the list. + +CapabilitySet::CapabilitySet() +{} + +CapabilitySet::CapabilitySet(Int atomCount, CapabilityAtom const* atoms) +{ + m_atoms.addRange(atoms, atomCount); +} + +CapabilitySet::CapabilitySet(CapabilityAtom atom) +{ + m_atoms.add(atom); +} + +CapabilitySet::CapabilitySet(List<CapabilityAtom> const& atoms) + : m_atoms(atoms) +{} + + +CapabilitySet CapabilitySet::makeEmpty() +{ + return CapabilitySet(); +} + +CapabilitySet CapabilitySet::makeInvalid() +{ + return CapabilitySet(CapabilityAtom::Invalid); +} + +bool CapabilitySet::isEmpty() const +{ + // Checking if a capability set is empty is trivial in any representation; + // all we need to know is if it has zero atoms in its definition. + // + return m_atoms.getCount() == 0; +} + +bool CapabilitySet::isInvalid() const +{ + // We will assume here that there is only one canonical representation of + // an invalid capability set, which is a singleton set of the `Invalid` + // atom. + // + // TODO: We should ensure that any algorithms that make new capability + // sets by combining others properly ensure that they return the + // canonical invalid set rather than any other set that happens to be + // invalid (e.g., a set {A,B} would be invalid if A and B are incompatible, + // but it would not be in the canonical form this subroutine checks). + // + if(m_atoms.getCount() != 1) return false; + return m_atoms[0] == CapabilityAtom::Invalid; +} + +bool CapabilitySet::isIncompatibleWith(CapabilityAtom that) const +{ + // We know that capabilities that are in an inheritnace + // relationship with one another can't be incompatible. + // + if(this->implies(that) || CapabilitySet(that).implies(*this)) + return false; + + // Othwerise, we want to perform a check for each of the + // atoms in this set, whether it is incompatible with any + // of the atoms in the other set (which in this case is one atom). + // + for( auto thisAtom : this->m_atoms ) + { + if(_isIncompatible(thisAtom, that)) + return true; + } + + return false; +} + +bool CapabilitySet::isIncompatibleWith(CapabilitySet const& that) const +{ + // We need to look at the atoms in `this` that are not + // present in `that`, and vice versa. For each such atom + // we will check if it is incompatible with the other, by + // virtue of the other already including a concrete atom + // that cannot co-exist with it. + // + for( auto thisAtom : this->m_atoms ) + { + if(that.isIncompatibleWith(thisAtom)) + return true; + } + for( auto thatAtom : that.m_atoms ) + { + if(this->isIncompatibleWith(thatAtom)) + return true; + } + return false; + + // TODO: If we had a representation that stored a minified, + // sorted, deduplicated list of atoms, then it would be easy + // to iterate over the two lists in tandem and identify any + // element that is present in one list but not the other. + // + // Those elements would be the candidates that could cause + // incompatiblity, so that we wouldn't need to perform + // the check on each atom like we do above. +} + +bool CapabilitySet::implies(CapabilitySet const& that) const +{ + // This capability set implies `other` if for every atom in `other`, + // that atom is present in this sets list of atoms or it is + // implies by something in the list of atoms. + // + for( auto atom : that.m_atoms ) + { + if(!this->implies(atom)) + return false; + } + return true; + + // TODO: If we had a representation that stored an expanded + // sorted, deduplicated list of atoms, then we could + // check the `implies` relationship by scanning through + // the two lists in tandem and identifying any element + // in the `that` list that isn't in the `this` list. + // Such elements would indicate that `that` is not a subset + // of `this`. +} + + +bool CapabilitySet::implies(CapabilityAtom atom) const +{ + // If our list of explicit atoms contains `atom`, then + // we definitely imply it. + // + // TODO: If we stored our atom lists sorted, then + // this operation could be logarithmic rather than + // linear. + // + if(m_atoms.contains(atom)) + return true; + + // If any of our atoms implies `atom` then we + // also imply it. + // + // TODO: If we stored an expanded atom list, then + // this recursion could be skipped completely, since + // the containment check above would cover inheirtance + // relationships too. + // + for( auto thisAtom : m_atoms ) + { + if(_implies(thisAtom, atom)) + return true; + } + + return false; +} + +bool CapabilitySet::operator==(CapabilitySet const& other) const +{ + return this->implies(other) && other.implies(*this); +} + +} diff --git a/source/slang/slang-capability.h b/source/slang/slang-capability.h new file mode 100644 index 000000000..662f7eed8 --- /dev/null +++ b/source/slang/slang-capability.h @@ -0,0 +1,156 @@ +// slang-capability.h +#pragma once + +#include "../core/slang-list.h" +#include "../core/slang-string.h" + +#include <stdint.h> + +namespace Slang +{ + +// This file defines a system for reasoning about the "capabilities" that a +// target supports or, conversely, the capabilities that a function or other +// symbol requires. +// +// The central idea is that we can think of the each of these cases as a set, +// where the elements of the set are atomic features that are either present +// on a target or not (no in-between states). For example, an atomic feature +// might be used to represent support for double-precision floating-point +// operations. When compiling for a target, we need to know whether the +// target supports double-precision or not, and for a particular function +// it either requires double-precision math to run, or not. +// +// In this system, the atomic capabilities are represented as cases of +// the `CapabilityAtom` enumeration, which is generated from declarations +// in the `slang-capability-defs.h` file. +// +enum class CapabilityAtom : int32_t +{ + // The "invalid" capability represents an atomic feature that no + // platform can/will ever support. If we ever determine that a + // function needs the invalid capability, it would be reasonable + // to report that situation as an error. + // + Invalid = -1, + +#define SLANG_CAPABILITY_ATOM(ENUMERATOR, NAME, FLAGS, BASE0, BASE1, BASE2, BASE3) \ + ENUMERATOR, + +#include "slang-capability-defs.h" + + Count, +}; + +// Once we have a universe of suitable capability atoms, we can define +// the capabilities of a target as simply the set of all atomic capabilities +// that it supports. +// +// The situation is slightly more complicated for a function. A function +// might require a specific set of atomic feature, and that is the simple +// case. In this simple case, we know that a target can run a function +// if the features of the target are a super-set of those required by +// the function. +// +// In the more general case, we might have a function that can be used +// with multiple different combinations of features: e.g., you can use +// the function if your target supports features A and B, or if it supports +// features C and D. In our representation, that case is handled by +// assocaiting multiple distinct sets of capabilities with one declaration, +// with each set expressing one way that the declaration can be legally used. +// +// In all cases, we represent a set of capabilities with `CapabilitySet`. + + /// A set of capabilities, representing features that are either supported or required +struct CapabilitySet +{ +public: + /// Default-construct an empty capability set + CapabilitySet(); + + /// Construct a capability set from an explicit list of atomic capabilities + CapabilitySet(Int atomCount, CapabilityAtom const* atoms); + + /// Construct a capability set from an explicit list of atomic capabilities + explicit CapabilitySet(List<CapabilityAtom> const& atoms); + + /// Construct a singleton set from a single atomic capability + explicit CapabilitySet(CapabilityAtom atom); + + /// Make an empty capability set + static CapabilitySet makeEmpty(); + + /// Make an invalid capability set (such that no target could ever support it) + static CapabilitySet makeInvalid(); + + /// Is this capability set empty (such that any target supports it)? + bool isEmpty() const; + + /// Is this capability set invalid (such that no target could support it)? + bool isInvalid() const; + + // Capabilities are "incompatible" if no target platform can ever support both + // at the same time. For example, the `HLSL` and `GLSL` capabilities are + // incompatible, because a single target cannot be both an HLSL target and + // a GLSL target (at least for now). + // + // Note that we are using the term "incompatible" here even though it + // seems like "disjoint" would be intuitively correct (HLSL and GLSL + // targets sure do seem to be disjoint). The problem is that in our + // set-theoretic representation of capabilities, incompatible capability + // sets are *never* disjoint sets of atoms, and (valid) disjoint sets of atoms + // *never* represent incompatible capability sets. + + /// Is this capability set incompatible with the given `other` set. + bool isIncompatibleWith(CapabilityAtom other) const; + + /// Is this capability set incompatible with the given `other` atomic capability. + bool isIncompatibleWith(CapabilitySet const& other) const; + + // One capability set A "implies" another set B if a target that + // supports A must also support all of B. + // + // In practice, this means that "A implies B" is the same as + // "A is a subset of B" in the set-theoretic model, but + // we ant to think of this primarily as supported/required features, + // and not get hung up on the set theory. + + /// Does this capability set imply all the capabilities in `other`? + bool implies(CapabilitySet const& other) const; + + /// Does this capability set imply the atomic capability `other`? + bool implies(CapabilityAtom other) const; + + // A capability set is equal to another if each implies the other. + + /// Are these two capability sets equal? + bool operator==(CapabilitySet const& that) const; + + /// Get access to the raw atomic capabilities that define this set. + List<CapabilityAtom> const& getAtoms() const { return m_atoms; } + +private: + + // The underlying representation we are using is currently very simple: + // a capability set is stored as a list of the atoms that were passed + // in at the time the set was constructed. + // + // Currently, no effort is made to sort the atoms, remove duplicates, + // or to expand the list when one atom entails another. + // + // TODO: Much more efficient representations are possible, and we + // should consider them if the performance of `CapabilitySet` ever + // prooves to be an issue. + // + List<CapabilityAtom> m_atoms; +}; + + /// Are the `left` and `right` capability sets unequal? +inline bool operator!=(CapabilitySet const& left, CapabilitySet const& right) +{ + return !(left == right); +} + + /// Find a capability atom with the given `name`, or return CapabilityAtom::Invalid. +CapabilityAtom findCapabilityAtom(UnownedStringSlice const& name); +} diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index ab02a0c54..ace2cb842 100755 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -8,6 +8,7 @@ #include "../../slang-com-ptr.h" +#include "slang-capability.h" #include "slang-diagnostics.h" #include "slang-name.h" #include "slang-preprocessor.h" @@ -1145,6 +1146,8 @@ namespace Slang SlangTargetFlags targetFlags = 0; Slang::Profile targetProfile = Slang::Profile(); FloatingPointMode floatingPointMode = FloatingPointMode::Default; + CapabilitySet targetCaps = CapabilitySet::makeInvalid(); + bool isWholeProgramRequest() { return (targetFlags & SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM) != 0; @@ -1154,6 +1157,7 @@ namespace Slang CodeGenTarget getTarget() { return target; } Profile getTargetProfile() { return targetProfile; } FloatingPointMode getFloatingPointMode() { return floatingPointMode; } + CapabilitySet getTargetCaps(); Session* getSession(); MatrixLayoutMode getDefaultMatrixLayoutMode(); 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; } diff --git a/source/slang/slang-emit-c-like.h b/source/slang/slang-emit-c-like.h index 9c91078ee..9c6da8a64 100644 --- a/source/slang/slang-emit-c-like.h +++ b/source/slang/slang-emit-c-like.h @@ -22,14 +22,20 @@ public: struct Desc { BackEndCompileRequest* compileRequest = nullptr; - // The target language we want to generate code for + + /// The target language we want to generate code for CodeGenTarget target = CodeGenTarget::Unknown; - // The stage for the entry point we are being asked to compile + + /// The stage for the entry point we are being asked to compile Stage entryPointStage = Stage::Unknown; - // The "effective" profile that is being used to emit code, - // combining information from the target and entry point. + + /// The "effective" profile that is being used to emit code, + /// combining information from the target and entry point. Profile effectiveProfile = Profile::RawEnum::Unknown; + /// The capabilities of the target + CapabilitySet targetCaps; + SourceWriter* sourceWriter = nullptr; }; @@ -105,6 +111,8 @@ public: void noteInternalErrorLoc(SourceLoc loc) { return getSink()->noteInternalErrorLoc(loc); } + CapabilitySet getTargetCaps() { return m_targetCaps; } + // // Types // @@ -126,13 +134,6 @@ public: void maybeCloseParens(bool needClose); - bool isTargetIntrinsicModifierApplicable(String const& targetName); - - bool isTargetIntrinsicModifierApplicable(IRTargetIntrinsicDecoration* decoration); - - /// Is the `candidate` decoration more specialized for the current target than `existing`? - bool isTargetIntrinsicModifierBetter(IRTargetIntrinsicDecoration* candidate, IRTargetIntrinsicDecoration* existing); - void emitStringLiteral(const String& value); void emitVal(IRInst* val, const EmitOpInfo& outerPrec); @@ -174,7 +175,8 @@ public: void emitInstResultDecl(IRInst* inst); - IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration(IRInst* inst); + IRTargetSpecificDecoration* findBestTargetDecoration(IRInst* inst); + IRTargetIntrinsicDecoration* findBestTargetIntrinsicDecorationXXX(IRInst* inst); // Check if the string being used to define a target intrinsic // is an "ordinary" name, such that we can simply emit a call @@ -381,6 +383,10 @@ public: // The target language we want to generate code for CodeGenTarget m_target; + + /// The capabilities of the target + CapabilitySet m_targetCaps; + // Source language (based on the more nuanced m_target) SourceLanguage m_sourceLanguage; diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index 574631567..2118268cd 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -722,6 +722,12 @@ SlangResult emitEntryPointsSourceFromIR( desc.entryPointStage = entryPoint->getStage(); desc.effectiveProfile = getEffectiveProfile(entryPoint, targetRequest); } + else + { + desc.entryPointStage = Stage::Unknown; + desc.effectiveProfile = targetRequest->getTargetProfile(); + } + desc.targetCaps = targetRequest->getTargetCaps(); desc.sourceWriter = &sourceWriter; // Define here, because must be in scope longer than the sourceEmitter, as sourceEmitter might reference diff --git a/source/slang/slang-hlsl-intrinsic-set.cpp b/source/slang/slang-hlsl-intrinsic-set.cpp index b73aa4e8e..13b14a548 100644 --- a/source/slang/slang-hlsl-intrinsic-set.cpp +++ b/source/slang/slang-hlsl-intrinsic-set.cpp @@ -514,10 +514,10 @@ HLSLIntrinsic::Op HLSLIntrinsicOpLookup::getOpFromTargetDecoration(IRInst* inIns // not a targets transformation) // // It turns out that addCatchAllIntrinsicDecorationIfNeeded will add a target intrinsic with the - // original HLSL name, which has a target of "" + // original HLSL name, which has an empty `CapabilitySet`. // // It's not 100% clear this covers all the cases, but for now lets go with that - if (decor->getTargetName().getLength() == 0) + if (decor->getTargetCaps().isEmpty()) { Op op = getOpByName(decor->getDefinition()); if (op != Op::Invalid) diff --git a/source/slang/slang-ir-glsl-legalize.cpp b/source/slang/slang-ir-glsl-legalize.cpp index 2abd8ba66..5a14fe1aa 100644 --- a/source/slang/slang-ir-glsl-legalize.cpp +++ b/source/slang/slang-ir-glsl-legalize.cpp @@ -1431,7 +1431,7 @@ void legalizeEntryPointParameterForGLSL( // HACK: we will identify the operation based // on the target-intrinsic definition that was // given to it. - auto decoration = findTargetIntrinsicDecoration(callee, "glsl"); + auto decoration = as<IRTargetIntrinsicDecoration>(findBestTargetDecoration(callee, CapabilityAtom::GLSL)); if(!decoration) continue; diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h index 4642ed3f0..736cb0cec 100644 --- a/source/slang/slang-ir-inst-defs.h +++ b/source/slang/slang-ir-inst-defs.h @@ -26,6 +26,8 @@ INST(Nop, nop, 0, 0) INST(StringType, String, 0, 0) + INST(CapabilitySetType, CapabilitySet, 0, 0) + INST(DynamicType, DynamicType, 0, 0) INST(AnyValueType, AnyValueType, 1, 0) @@ -236,6 +238,8 @@ INST(Block, block, 0, PARENT) INST(StringLit, string_constant, 0, 0) INST_RANGE(Constant, BoolLit, StringLit) +INST(CapabilitySet, capabilitySet, 0, 0) + INST(undefined, undefined, 0, 0) // A `defaultConstruct` operation creates an initialized diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index baa511a3c..dc42fde70 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -8,6 +8,7 @@ // // TODO: the builder probably needs its own file. +#include "slang-capability.h" #include "slang-compiler.h" #include "slang-ir.h" #include "slang-syntax.h" @@ -17,6 +18,13 @@ namespace Slang { class Decl; +struct IRCapabilitySet : IRInst +{ + IR_LEAF_ISA(CapabilitySet); + + CapabilitySet getCaps(); +}; + struct IRDecoration : IRInst { IR_PARENT_ISA(Decoration) @@ -63,12 +71,9 @@ struct IRTargetSpecificDecoration : IRDecoration { IR_PARENT_ISA(TargetSpecificDecoration) - IRStringLit* getTargetNameOperand() { return cast<IRStringLit>(getOperand(0)); } + IRCapabilitySet* getTargetCapsOperand() { return cast<IRCapabilitySet>(getOperand(0)); } - UnownedStringSlice getTargetName() - { - return getTargetNameOperand()->getStringSlice(); - } + CapabilitySet getTargetCaps() { return getTargetCapsOperand()->getCaps(); } }; struct IRTargetDecoration : IRTargetSpecificDecoration @@ -1822,6 +1827,7 @@ struct IRBuilder IRInst* getFloatValue(IRType* type, IRFloatingPointValue value); IRStringLit* getStringValue(const UnownedStringSlice& slice); IRPtrLit* getPtrValue(void* value); + IRInst* getCapabilityValue(CapabilitySet const& caps); IRBasicType* getBasicType(BaseType baseType); IRBasicType* getVoidType(); @@ -1830,6 +1836,7 @@ struct IRBuilder IRBasicType* getUIntType(); IRBasicType* getUInt64Type(); IRStringType* getStringType(); + IRType* getCapabilitySetType(); IRAssociatedType* getAssociatedType(ArrayView<IRInterfaceType*> constraintTypes); IRThisType* getThisType(IRInterfaceType* interfaceType); @@ -2483,14 +2490,24 @@ struct IRBuilder addDecoration(value, kIROp_SemanticDecoration, getStringValue(text), getIntValue(getIntType(), index)); } - void addTargetIntrinsicDecoration(IRInst* value, UnownedStringSlice const& target, UnownedStringSlice const& definition) + void addTargetIntrinsicDecoration(IRInst* value, IRInst* caps, UnownedStringSlice const& definition) { - addDecoration(value, kIROp_TargetIntrinsicDecoration, getStringValue(target), getStringValue(definition)); + addDecoration(value, kIROp_TargetIntrinsicDecoration, caps, getStringValue(definition)); } - void addTargetDecoration(IRInst* value, UnownedStringSlice const& target) + void addTargetIntrinsicDecoration(IRInst* value, CapabilitySet const& caps, UnownedStringSlice const& definition) { - addDecoration(value, kIROp_TargetDecoration, getStringValue(target)); + addTargetIntrinsicDecoration(value, getCapabilityValue(caps), definition); + } + + void addTargetDecoration(IRInst* value, IRInst* caps) + { + addDecoration(value, kIROp_TargetDecoration, caps); + } + + void addTargetDecoration(IRInst* value, CapabilitySet const& caps) + { + addTargetDecoration(value, getCapabilityValue(caps)); } void addRequireGLSLExtensionDecoration(IRInst* value, UnownedStringSlice const& extensionName) @@ -2640,9 +2657,16 @@ void markConstExpr( // -IRTargetIntrinsicDecoration* findTargetIntrinsicDecoration( - IRInst* val, - String const& targetName); +IRTargetIntrinsicDecoration* findAnyTargetIntrinsicDecoration( + IRInst* val); + +IRTargetSpecificDecoration* findBestTargetDecoration( + IRInst* val, + CapabilitySet const& targetCaps); + +IRTargetSpecificDecoration* findBestTargetDecoration( + IRInst* val, + CapabilityAtom targetCapabilityAtom); } diff --git a/source/slang/slang-ir-legalize-varying-params.cpp b/source/slang/slang-ir-legalize-varying-params.cpp index 3df651e6a..c802513e8 100644 --- a/source/slang/slang-ir-legalize-varying-params.cpp +++ b/source/slang/slang-ir-legalize-varying-params.cpp @@ -1092,15 +1092,15 @@ struct CUDAEntryPointVaryingParamLegalizeContext : EntryPointVaryingParamLegaliz // a unique name). threadIdxGlobalParam = builder.createGlobalParam(uint3Type); - builder.addTargetIntrinsicDecoration(threadIdxGlobalParam, UnownedTerminatedStringSlice(""), UnownedTerminatedStringSlice("threadIdx")); + builder.addTargetIntrinsicDecoration(threadIdxGlobalParam, CapabilitySet::makeEmpty(), UnownedTerminatedStringSlice("threadIdx")); builder.addLayoutDecoration(threadIdxGlobalParam, varLayout); blockIdxGlobalParam = builder.createGlobalParam(uint3Type); - builder.addTargetIntrinsicDecoration(blockIdxGlobalParam, UnownedTerminatedStringSlice(""), UnownedTerminatedStringSlice("blockIdx")); + builder.addTargetIntrinsicDecoration(blockIdxGlobalParam, CapabilitySet::makeEmpty(), UnownedTerminatedStringSlice("blockIdx")); builder.addLayoutDecoration(blockIdxGlobalParam, varLayout); blockDimGlobalParam = builder.createGlobalParam(uint3Type); - builder.addTargetIntrinsicDecoration(blockDimGlobalParam, UnownedTerminatedStringSlice(""), UnownedTerminatedStringSlice("blockDim")); + builder.addTargetIntrinsicDecoration(blockDimGlobalParam, CapabilitySet::makeEmpty(), UnownedTerminatedStringSlice("blockDim")); builder.addLayoutDecoration(blockDimGlobalParam, varLayout); } @@ -1220,14 +1220,14 @@ struct CPUEntryPointVaryingParamLegalizeContext : EntryPointVaryingParamLegalize varyingInputStructType = builder.createStructType(); varyingInputStructPtrType = builder.getPtrType(varyingInputStructType); - builder.addTargetIntrinsicDecoration(varyingInputStructType, UnownedTerminatedStringSlice(""), UnownedTerminatedStringSlice("ComputeThreadVaryingInput")); + builder.addTargetIntrinsicDecoration(varyingInputStructType, CapabilitySet::makeEmpty(), UnownedTerminatedStringSlice("ComputeThreadVaryingInput")); groupIDKey = builder.createStructKey(); - builder.addTargetIntrinsicDecoration(groupIDKey, UnownedTerminatedStringSlice(""), UnownedTerminatedStringSlice("groupID")); + builder.addTargetIntrinsicDecoration(groupIDKey, CapabilitySet::makeEmpty(), UnownedTerminatedStringSlice("groupID")); builder.createStructField(varyingInputStructType, groupIDKey, uint3Type); groupThreadIDKey = builder.createStructKey(); - builder.addTargetIntrinsicDecoration(groupThreadIDKey, UnownedTerminatedStringSlice(""), UnownedTerminatedStringSlice("groupThreadID")); + builder.addTargetIntrinsicDecoration(groupThreadIDKey, CapabilitySet::makeEmpty(), UnownedTerminatedStringSlice("groupThreadID")); builder.createStructField(varyingInputStructType, groupThreadIDKey, uint3Type); } diff --git a/source/slang/slang-ir-link.cpp b/source/slang/slang-ir-link.cpp index c96286eec..e4c1bad85 100644 --- a/source/slang/slang-ir-link.cpp +++ b/source/slang/slang-ir-link.cpp @@ -1,6 +1,7 @@ // slang-ir-link.cpp #include "slang-ir-link.h" +#include "slang-capability.h" #include "slang-ir.h" #include "slang-ir-insts.h" #include "slang-mangle.h" @@ -37,6 +38,9 @@ struct IRSharedSpecContext // The code-generation target in use CodeGenTarget target; + // The API-level target request + TargetRequest* targetReq = nullptr; + // The specialized module we are building RefPtr<IRModule> module; @@ -915,79 +919,35 @@ IRFunc* specializeIRForEntryPoint( // 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) +CapabilitySet getTargetCapabilities(IRSpecContext* context) { - switch( context->shared->target ) - { - case CodeGenTarget::HLSL: - return "hlsl"; - - case CodeGenTarget::GLSL: - return "glsl"; - - case CodeGenTarget::CSource: - return "c"; - - case CodeGenTarget::CPPSource: - return "cpp"; - - case CodeGenTarget::CUDASource: - return "cuda"; - - case CodeGenTarget::SPIRV: - return "spirv"; - - - default: - SLANG_UNEXPECTED("unhandled case"); - UNREACHABLE_RETURN("unknown"); - } + return context->getShared()->targetReq->getTargetCaps(); } -// How specialized is a given declaration for the chosen target? -enum class TargetSpecializationLevel -{ - specializedForOtherTarget = 0, - notSpecialized, - specializedForTarget, -}; - -TargetSpecializationLevel getTargetSpecialiationLevel( - IRInst* inVal, - String const& targetName) + /// Get the most appropriate ("best") capability requirements for `inVal` based on the `targetCaps`. +static CapabilitySet _getBestSpecializationCaps( + IRInst* inVal, + CapabilitySet const& targetCaps) { - // 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; + IRInst* val = getResolvedInstForDecorations(inVal); - auto returnInst = as<IRReturnVal>(firstBlock->getLastInst()); - if(!returnInst) break; + // If the instruction has no target-related decorations, + // then it is implied to be an unspecialized, target-independent + // declaration. + // + // Such a declaration amounts to an empty set of capabilities. + // + if(!val->findDecoration<IRTargetDecoration>()) + return CapabilitySet::makeEmpty(); - val = returnInst->getVal(); + if( auto targetDecoration = findBestTargetDecoration(inVal, targetCaps) ) + { + return targetDecoration->getTargetCaps(); } - - TargetSpecializationLevel result = TargetSpecializationLevel::notSpecialized; - for(auto dd : val->getDecorations()) + else { - if(dd->op != kIROp_TargetDecoration) - continue; - - auto decoration = (IRTargetDecoration*) dd; - if(String(decoration->getTargetName()) == targetName) - return TargetSpecializationLevel::specializedForTarget; - - result = TargetSpecializationLevel::specializedForOtherTarget; + return CapabilitySet::makeInvalid(); } - - return result; } // Is `newVal` marked as being a better match for our @@ -1006,43 +966,61 @@ bool isBetterForTarget( return true; } - 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: + // decorations, representing the capabilities for which it is specialized. + // Each decorations has a `CapabilitySet` to represent what it requires of a target. + // + // We need to look at all the candidate declarations for a symbol + // and pick the one that has the "most specialized" set of capabilities + // for our chosen target. // - // - 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. + // In principle, this should be as simple as: // - // - Does it have a no tags? Then it is "unspecialized" and that is okay. + // * Ignore all decorations with capability sets that aren't subsets + // of the capabilities of our target. // - // - Does it have a modifier with a tag for a *different* target? - // If yes, then it shouldn't even be usable on this target. + // * From the remaining decorations, pick the one that is a superset + // of all the others (and give an ambiguity error if there is + // no unique "best" option). // - // Longer term a better approach is to think of this in terms - // of a "disjunction of conjunctions" that is: + // In practice, the choice is complicated by the way that we currently + // have the compiler automatically deduce dependencies on extensions + // or other features that were not included as part of the target + // description by the user. // - // (A and B and C) or (A and D) or (E) or (F and G) ... + // In order to preserve the ability to infer more specialized requirements + // than what the target includes, we change the two steps slightly: // - // A code generation target would then consist of a - // conjunction of individual tags: + // * Ignore all decorations with capability sets that are *incompatible* + // with the capabilities of our target, such that they could never be + // used together. // - // (HLSL and SM_4_0 and Vertex and ...) + // * From all the remaining decorations, pick the one that is "better" + // than all the others in that it is either a supserset of each other + // candidate, or for each feature that another candidate requires, + // it requires a "better" feature that covers the same ground. // - // A declaration is *applicable* on a target if one of - // its conjunctions of tags is a subset of the target's. + // Note: This approach isn't really sound, so we are likely to have + // to tweak it over time. Most notably, we probably need/want to + // push back on the automatic inference of extensions/versions in + // the compiler as much as possible. // - // One declaration is *better* than another on a target - // if it is applicable and its tags are a superset - // of the other's. + CapabilitySet targetCaps = getTargetCapabilities(context); + CapabilitySet newCaps = _getBestSpecializationCaps(newVal, targetCaps); + CapabilitySet oldCaps = _getBestSpecializationCaps(oldVal, targetCaps); - auto newLevel = getTargetSpecialiationLevel(newVal, targetName); - - auto oldLevel = getTargetSpecialiationLevel(oldVal, targetName); - if(newLevel != oldLevel) - return UInt(newLevel) > UInt(oldLevel); + // If either value returned an invalid capability set, it implies + // that it cannot be used on this target at all, and the other + // value should be considered better by default. + // + // Note: if both of the candidate values we have are incompatible + // with our target, then it doesn't matter which we favor. + // + if(newCaps.isInvalid()) return false; + if(oldCaps.isInvalid()) return true; + + if(newCaps != oldCaps) + return newCaps.implies(oldCaps); // All preceding factors being equal, an `[export]` is better // than an `[import]`. @@ -1300,7 +1278,8 @@ void initializeSharedSpecContext( IRSharedSpecContext* sharedContext, Session* session, IRModule* module, - CodeGenTarget target) + CodeGenTarget target, + TargetRequest* targetReq) { SharedIRBuilder* sharedBuilder = &sharedContext->sharedBuilderStorage; @@ -1318,6 +1297,7 @@ void initializeSharedSpecContext( sharedBuilder->module = module; sharedContext->module = module; sharedContext->target = target; + sharedContext->targetReq = targetReq; } struct IRSpecializationState @@ -1372,7 +1352,8 @@ LinkedIR linkIR( sharedContext, compileRequest->getSession(), nullptr, - target); + target, + targetReq); state->irModule = sharedContext->module; diff --git a/source/slang/slang-ir-specialize.cpp b/source/slang/slang-ir-specialize.cpp index 693494ac1..91852ff88 100644 --- a/source/slang/slang-ir-specialize.cpp +++ b/source/slang/slang-ir-specialize.cpp @@ -756,27 +756,20 @@ struct SpecializationContext } } - // Finds any `IRTargetDecoration` from `inst`. Recursively chasing `specialize` chains. - IRTargetIntrinsicDecoration* findTargetIntrinsicDecorationRec(IRInst* inst) - { - while (auto specialize = as<IRSpecialize>(inst)) - { - inst = specialize->getBase(); - } - while (auto genericInst = as<IRGeneric>(inst)) - { - inst = findGenericReturnVal(genericInst); - } - if (auto decor = inst->findDecoration<IRTargetIntrinsicDecoration>()) - return decor; - return nullptr; - } - // Returns true if the call inst represents a call to // StructuredBuffer::operator[]/Load/Consume methods. bool isBufferLoadCall(IRCall* inst) { - if (auto targetIntrinsic = findTargetIntrinsicDecorationRec(inst->getCallee())) + // TODO: We should have something like a `[knownSemantics(...)]` decoration + // that can identify that an `IRFunc` has semantics that are consistent + // with a list of compiler-known behaviors. The operand to `knownSemantics` + // could come from a `KnownSemantics` enumeration or something similar, + // so that we don't have to make string-based checks here. + // + // Note that `[knownSemantics(...)]` would be independent of any targets, + // and could even apply to functions that are implemented entirely in Slang. + + if (auto targetIntrinsic = findAnyTargetIntrinsicDecoration(inst->getCallee())) { auto name = targetIntrinsic->getDefinition(); if (name == ".operator[]" || name == ".Load" || name == ".Consume") 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( diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index b2a7529c5..34b189b14 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -530,6 +530,9 @@ bool isEffectivelyStatic( Decl* decl, ContainerDecl* parentDecl); +bool isStdLibMemberFuncDecl( + Decl* decl); + // Ensure that a version of the given declaration has been emitted to the IR LoweredValInfo ensureDecl( IRGenContext* context, @@ -6522,7 +6525,15 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> } else { - definition = decl->getName()->text; + if( isStdLibMemberFuncDecl(decl) ) + { + // We will mark member functions by appending a `.` to the + // start of their name. + // + definition.append("."); + } + + definition.append(decl->getName()->text); } UnownedStringSlice targetName; @@ -6532,7 +6543,19 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> targetName = targetToken.getContent(); } - builder->addTargetIntrinsicDecoration(irInst, targetName, definition.getUnownedSlice()); + CapabilitySet targetCaps; + if( targetName.getLength() == 0 ) + { + targetCaps = CapabilitySet::makeEmpty(); + } + else + { + CapabilityAtom targetCap = findCapabilityAtom(targetName); + SLANG_ASSERT(targetCap != CapabilityAtom::Invalid); + targetCaps = CapabilitySet(targetCap); + } + + builder->addTargetIntrinsicDecoration(irInst, targetCaps, definition.getUnownedSlice()); } if(auto nvapiMod = decl->findModifier<NVAPIMagicModifier>()) @@ -6543,8 +6566,12 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> /// Is `decl` a member function (or effectively a member function) when considered as a stdlib declaration? bool isStdLibMemberFuncDecl( - CallableDecl* decl) + Decl* inDecl) { + auto decl = as<CallableDecl>(inDecl); + if(!decl) + return false; + // Constructors aren't really member functions, insofar // as they aren't called with a `this` parameter. // @@ -6655,7 +6682,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> definition.append(getText(declForName->getName())); - getBuilder()->addTargetIntrinsicDecoration(irInst, UnownedStringSlice(), definition.getUnownedSlice()); + getBuilder()->addTargetIntrinsicDecoration(irInst, CapabilitySet::makeEmpty(), definition.getUnownedSlice()); } void addParamNameHint(IRInst* inst, IRLoweringParameterInfo const& info) @@ -6974,7 +7001,10 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> // a specialized definition of the particular function for the given // target, and we need to reflect that at the IR level. - getBuilder()->addTargetDecoration(irFunc, targetMod->targetToken.getContent()); + auto targetName = targetMod->targetToken.getContent(); + auto targetCap = findCapabilityAtom(targetName); + + getBuilder()->addTargetDecoration(irFunc, CapabilitySet(targetCap)); } // If this declaration was marked as having a target-specific lowering diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 92a08a224..ef838b871 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -971,6 +971,74 @@ MatrixLayoutMode TargetRequest::getDefaultMatrixLayoutMode() return linkage->getDefaultMatrixLayoutMode(); } +CapabilitySet TargetRequest::getTargetCaps() +{ + if(!targetCaps.isInvalid()) + return targetCaps; + + // The full `CapabilitySet` for the target will be computed + // from the combination of the code generation format, and + // the profile. + // + // Note: the preofile might have been set in a way that is + // inconsistent with the output code format of SPIR-V, but + // a profile of Direct3D Shader Model 5.1. In those cases, + // the format should always override the implications in + // the profile. + // + // TODO: This logic isn't currently taking int account + // the information in the profile, because the current + // `CapabilityAtom`s that we support don't include any + // of the details there (e.g., the shader model versions). + // + // Eventually, we'd want to have a rich set of capability + // atoms, so that most of the information about what operations + // are available where can be directly encoded on the declarations. + + List<CapabilityAtom> atoms; + switch(target) + { + case CodeGenTarget::GLSL: + case CodeGenTarget::GLSL_Vulkan: + case CodeGenTarget::GLSL_Vulkan_OneDesc: + case CodeGenTarget::SPIRV: + case CodeGenTarget::SPIRVAssembly: + atoms.add(CapabilityAtom::GLSL); + break; + + case CodeGenTarget::HLSL: + case CodeGenTarget::DXBytecode: + case CodeGenTarget::DXBytecodeAssembly: + case CodeGenTarget::DXIL: + case CodeGenTarget::DXILAssembly: + atoms.add(CapabilityAtom::HLSL); + break; + + case CodeGenTarget::CSource: + atoms.add(CapabilityAtom::C); + break; + + case CodeGenTarget::CPPSource: + case CodeGenTarget::Executable: + case CodeGenTarget::SharedLibrary: + case CodeGenTarget::HostCallable: + atoms.add(CapabilityAtom::CPP); + break; + + case CodeGenTarget::CUDASource: + case CodeGenTarget::PTX: + atoms.add(CapabilityAtom::CUDA); + break; + + default: + break; + } + + targetCaps = CapabilitySet(atoms); + return targetCaps; +} + + TypeLayout* TargetRequest::getTypeLayout(Type* type) { // TODO: We are not passing in a `ProgramLayout` here, although one |
