From 48ae5496516878768d7de241b9b7fbba91fbaa74 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Wed, 19 Jun 2019 07:23:49 -0700 Subject: Start exposing a new COM-lite API (#987) * Start exposing a new COM-lite API This change is mostly about exposing a new API to the Slang compiler that allows more fine-grained control over the compilation flow. The basic concepts in the new API are: * An `IGlobalSession` is the granularity at which we load/parse the Slang stdlib, and therefore gives applications a way to amortize startup cost for the library across multiple compiles. This is a concept that might be able to go away in a future version of Slang. * An `ISession` owns all the code that gets loaded/compiled/generated. Any `import`ed modules are shared across everything in a session (we don't re-parse/-check the code when we see another `import` for the same module). Any generic- or interface-based code in the session can be specialized using types from the same session (but not necessarily across sessions). * An `IModule` is the unit of code loading and scoping. It doesn't expose any API in this change, but would be the right scope for looking up types or entry points by name. * An `IProgram` is a "linked" combination of modules and entry points from which code can be generated and reflection information queried. This change re-uses the existing reflection API types, rather than introduce a new API that duplicates that functionality. That will probably change in a future revision. There are two major pieces of functionality added here that aren't related to the new API: * We now have an API concept of "entry point groups" which are one or more entry points that are intended to be used together so that they need to have non-overlapping parameters. For now this is being used to handle "hit groups" and local root signatures for ray tracing, but I'm not sure this is a concept we will keep in the long run. * We have a very special-case (client-application-specific) flag that ascribes special meaning to the `shared` keyword, so that it can be attached to global parameters to indicate that they are actually to be part of the local root signature rather than the global one for DXR. None of the API design (including naming) here is finalized; the only reason to check in the changes at this point to avoid having a long-running branch that leads to merge pain. Clients should *not* try to depend on the new API just yet, since it is still a work in progress. * fixup: clang warning * fixup: try to detect clang C++11 support * fixup * fixup * fixup * fixup * fixup: review feedback --- .gitignore | 2 + slang-com-helper.h | 8 +- slang.h | 407 ++++++++++++++++-- source/core/core.vcxproj | 4 +- source/core/slang-writer.h | 2 +- source/slang/slang-check.cpp | 300 ++++++++++++- source/slang/slang-compiler.cpp | 87 +++- source/slang/slang-compiler.h | 261 +++++++++++- source/slang/slang-diagnostic-defs.h | 2 + source/slang/slang-diagnostics.h | 6 +- source/slang/slang-emit.cpp | 50 ++- source/slang/slang-file-system.h | 6 +- source/slang/slang-ir-link.cpp | 40 +- source/slang/slang-parameter-binding.cpp | 276 ++++++++---- source/slang/slang-parser.cpp | 3 +- source/slang/slang-reflection.cpp | 131 +++++- source/slang/slang-type-layout.cpp | 91 ++++ source/slang/slang-type-layout.h | 40 ++ source/slang/slang.cpp | 464 ++++++++++++++++++--- source/slang/slang.natvis | 12 +- source/slang/slang.vcxproj | 5 +- source/slang/slang.vcxproj.filters | 7 +- .../compute/interface-shader-param-in-struct.slang | 19 +- tests/compute/interface-shader-param3.slang | 34 +- tests/compute/interface-shader-param4.slang | 28 +- .../parameter-block-explicit-space.slang.expected | 4 +- 26 files changed, 1957 insertions(+), 332 deletions(-) diff --git a/.gitignore b/.gitignore index 206dfca5e..09133cdf1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ *.VC.db *.vcxproj.user *.sdf +*.ilk +*.obj bin/ intermediate/ build.*/ diff --git a/slang-com-helper.h b/slang-com-helper.h index 245c800b6..e674ff27f 100644 --- a/slang-com-helper.h +++ b/slang-com-helper.h @@ -70,7 +70,7 @@ For SLANG_IUNKNOWN_QUERY_INTERFACE to work - must have a method 'getInterface' t if not found. */ #define SLANG_IUNKNOWN_QUERY_INTERFACE \ -SLANG_NO_THROW SlangResult SLANG_MCALL queryInterface(SlangUUID const& uuid, void** outObject) \ +SLANG_NO_THROW SlangResult SLANG_MCALL queryInterface(SlangUUID const& uuid, void** outObject) SLANG_OVERRIDE \ { \ ISlangUnknown* intf = getInterface(uuid); \ if (intf) \ @@ -108,7 +108,7 @@ SLANG_NO_THROW uint32_t SLANG_MCALL release() \ // ------------------------ RefObject IUnknown ----------------------------- #define SLANG_REF_OBJECT_IUNKNOWN_QUERY_INTERFACE \ -SLANG_NO_THROW SlangResult SLANG_MCALL queryInterface(SlangUUID const& uuid, void** outObject) \ +SLANG_NO_THROW SlangResult SLANG_MCALL queryInterface(SlangUUID const& uuid, void** outObject) SLANG_OVERRIDE \ { \ ISlangUnknown* intf = getInterface(uuid); \ if (intf) \ @@ -120,8 +120,8 @@ SLANG_NO_THROW SlangResult SLANG_MCALL queryInterface(SlangUUID const& uuid, voi return SLANG_E_NO_INTERFACE;\ } -#define SLANG_REF_OBJECT_IUNKNOWN_ADD_REF SLANG_NO_THROW uint32_t SLANG_MCALL addRef() { return (uint32_t)addReference(); } -#define SLANG_REF_OBJECT_IUNKNOWN_RELEASE SLANG_NO_THROW uint32_t SLANG_MCALL release() { return (uint32_t)releaseReference(); } +#define SLANG_REF_OBJECT_IUNKNOWN_ADD_REF SLANG_NO_THROW uint32_t SLANG_MCALL addRef() SLANG_OVERRIDE { return (uint32_t)addReference(); } +#define SLANG_REF_OBJECT_IUNKNOWN_RELEASE SLANG_NO_THROW uint32_t SLANG_MCALL release() SLANG_OVERRIDE { return (uint32_t)releaseReference(); } #define SLANG_REF_OBJECT_IUNKNOWN_ALL \ SLANG_REF_OBJECT_IUNKNOWN_QUERY_INTERFACE \ diff --git a/slang.h b/slang.h index 23d8c5f12..1fd92f440 100644 --- a/slang.h +++ b/slang.h @@ -298,8 +298,15 @@ convention for interface methods. #ifdef __cplusplus // C++ specific macros +// Clang +#if SLANG_CLANG +# if (__clang_major__*10 + __clang_minor__) >= 33 +# define SLANG_HAS_MOVE_SEMANTICS 1 +# define SLANG_HAS_ENUM_CLASS 1 +# define SLANG_OVERRIDE override +# endif // Gcc -# if SLANG_GCC_FAMILY +#elif SLANG_GCC_FAMILY // Check for C++11 # if (__cplusplus >= 201103L) # if (__GNUC__ * 100 + __GNUC_MINOR__) >= 405 @@ -1044,8 +1051,7 @@ extern "C" */ typedef struct SlangSession SlangSession; - typedef struct SlangLinkage SlangLinkage; - typedef struct SlangModule SlangModule; + typedef struct SlangProgramLayout SlangProgramLayout; /*! @brief A request for one or more compilation actions to be performed. @@ -1113,20 +1119,6 @@ extern "C" char const* sourcePath, char const* sourceString); - - - SLANG_API SlangLinkage* spCreateLinkage( - SlangSession* session); - - SLANG_API void spDestroyLinkage( - SlangLinkage* linkage); - - SLANG_API SlangModule* spLoadModule( - SlangLinkage* linkage, - char const* moduleName); - - - /*! @brief Create a compile request. */ @@ -1563,11 +1555,17 @@ extern "C" SlangCompileRequest* request, size_t* outSize); - /* Note(tfoley): working on new reflection interface... + /* + Forward declarations of types used in the reflection interface; */ - typedef struct SlangReflection SlangReflection; - typedef struct SlangReflectionEntryPoint SlangReflectionEntryPoint; + typedef struct SlangProgramLayout SlangProgramLayout; + typedef struct SlangEntryPoint SlangEntryPoint; + typedef struct SlangEntryPointLayout SlangEntryPointLayout; + typedef struct SlangEntryPointGroupLayout SlangEntryPointGroupLayout; + +// typedef struct SlangReflection SlangReflection; +// typedef struct SlangReflectionEntryPoint SlangReflectionEntryPoint; typedef struct SlangReflectionModifier SlangReflectionModifier; typedef struct SlangReflectionType SlangReflectionType; typedef struct SlangReflectionTypeLayout SlangReflectionTypeLayout; @@ -1576,6 +1574,12 @@ extern "C" typedef struct SlangReflectionTypeParameter SlangReflectionTypeParameter; typedef struct SlangReflectionUserAttribute SlangReflectionUserAttribute; + /* + Type aliases to maintain backward compatibility. + */ + typedef SlangProgramLayout SlangReflection; + typedef SlangEntryPointLayout SlangReflectionEntryPoint; + // get reflection data from a compilation request SLANG_API SlangReflection* spGetReflection( SlangCompileRequest* request); @@ -1909,6 +1913,9 @@ extern "C" SLANG_API SlangReflectionEntryPoint* spReflection_getEntryPointByIndex(SlangReflection* reflection, SlangUInt index); SLANG_API SlangReflectionEntryPoint* spReflection_findEntryPointByName(SlangReflection* reflection, char const* name); + SLANG_API SlangInt spReflection_getEntryPointGroupCount(SlangReflection* reflection); + SLANG_API SlangEntryPointGroupLayout* spReflection_getEntryPointGroupByIndex(SlangReflection* reflection, SlangInt index); + SLANG_API SlangUInt spReflection_getGlobalConstantBufferBinding(SlangReflection* reflection); SLANG_API size_t spReflection_getGlobalConstantBufferSize(SlangReflection* reflection); @@ -1919,6 +1926,15 @@ extern "C" SlangReflectionType* const* specializationArgs, ISlangBlob** outDiagnostics); + // Entry point group reflection + + SLANG_API SlangInt spEntryPointGroupLayout_getEntryPointCount(SlangEntryPointGroupLayout* group); + SLANG_API SlangReflectionEntryPoint* spEntryPointGroupLayout_getEntryPointByIndex(SlangEntryPointGroupLayout* group, SlangInt index); + SLANG_API SlangReflectionVariableLayout* spEntryPointGroupLayout_getVarLayout(SlangEntryPointGroupLayout* group); + + SLANG_API SlangInt spEntryPointGroupLayout_getParameterCount(SlangEntryPointGroupLayout* group); + SLANG_API SlangReflectionVariableLayout* spEntryPointGroupLayout_getParameterByIndex(SlangEntryPointGroupLayout* group, SlangInt index); + #ifdef __cplusplus } @@ -1926,9 +1942,6 @@ extern "C" /* Helper interfaces for C++ users */ namespace slang { -#define SLANG_SAFE_BOOL(expr) \ - operator bool() const { return expr; } - struct BufferReflection; struct TypeLayoutReflection; struct TypeReflection; @@ -2427,6 +2440,35 @@ namespace slang return 0 != spReflectionEntryPoint_usesAnySampleRateInput((SlangReflectionEntryPoint*) this); } }; + typedef EntryPointReflection EntryPointLayout; + + struct EntryPointGroupLayout + { + SlangInt getEntryPointCount() + { + return spEntryPointGroupLayout_getEntryPointCount((SlangEntryPointGroupLayout*) this); + } + + EntryPointReflection* getEntryPointByIndex(SlangInt index) + { + return (EntryPointReflection*) spEntryPointGroupLayout_getEntryPointByIndex((SlangEntryPointGroupLayout*) this, index); + } + + VariableLayoutReflection* getVarLayout() + { + return (VariableLayoutReflection*) spEntryPointGroupLayout_getVarLayout((SlangEntryPointGroupLayout*) this); + } + + SlangInt getParameterCount() + { + return spEntryPointGroupLayout_getParameterCount((SlangEntryPointGroupLayout*) this); + } + + VariableLayoutReflection* getParameterByIndex(SlangInt index) + { + return (VariableLayoutReflection*) spEntryPointGroupLayout_getParameterByIndex((SlangEntryPointGroupLayout*) this, index); + } + }; struct TypeParameterReflection { @@ -2453,6 +2495,8 @@ namespace slang Default = SLANG_LAYOUT_RULES_DEFAULT, }; + typedef struct ShaderReflection ProgramLayout; + struct ShaderReflection { unsigned getParameterCount() @@ -2480,9 +2524,9 @@ namespace slang return (VariableLayoutReflection*) spReflection_GetParameterByIndex((SlangReflection*) this, index); } - static ShaderReflection* get(SlangCompileRequest* request) + static ProgramLayout* get(SlangCompileRequest* request) { - return (ShaderReflection*) spGetReflection(request); + return (ProgramLayout*) spGetReflection(request); } SlangUInt getEntryPointCount() @@ -2495,6 +2539,16 @@ namespace slang return (EntryPointReflection*) spReflection_getEntryPointByIndex((SlangReflection*) this, index); } + SlangInt getEntryPointGroupCount() + { + return spReflection_getEntryPointGroupCount((SlangReflection*) this); + } + + EntryPointGroupLayout* getEntryPointGroupByIndex(SlangInt index) + { + return (EntryPointGroupLayout*) spReflection_getEntryPointGroupByIndex((SlangReflection*) this, index); + } + SlangUInt getGlobalConstantBufferBinding() { return spReflection_getGlobalConstantBufferBinding((SlangReflection*)this); @@ -2543,8 +2597,311 @@ namespace slang outDiagnostics); } }; + + typedef ISlangBlob IBlob; + + struct IGlobalSession; + struct IModule; + struct IProgram; + struct ISession; + + struct SessionDesc; + struct ProgramDesc; + struct SpecializationArg; + struct TargetDesc; + + /** A global session for interaction with the Slang library. + + An application may create and re-use a single global session across + multiple sessions, in order to amortize startups costs (in current + Slang this is mostly the cost of loading the Slang standard library). + + The global session is currently *not* thread-safe and objects created from + a single global session should only be used from a single thread at + a time. + */ + struct IGlobalSession : public ISlangUnknown + { + public: + /** Create a new session for loading and compiling code. + */ + virtual SLANG_NO_THROW SlangResult SLANG_MCALL createSession( + SessionDesc const& desc, + ISession** outSession) = 0; + + /** Look up the internal ID of a profile by its `name`. + + Profile IDs are *not* guaranteed to be stable across versions + of the Slang library, so clients are expected to look up + profiles by name at runtime. + */ + virtual SLANG_NO_THROW SlangProfileID SLANG_MCALL findProfile( + char const* name) = 0; + }; + + #define SLANG_UUID_IGlobalSession { 0xc140b5fd, 0xc78, 0x452e, { 0xba, 0x7c, 0x1a, 0x1e, 0x70, 0xc7, 0xf7, 0x1c } }; + + /** Description of a code generation target. + */ + struct TargetDesc + { + /** The target format to generate code for (e.g., SPIR-V, DXIL, etc.) + */ + SlangCompileTarget format = SLANG_TARGET_UNKNOWN; + + /** The compilation profile supported by the target (e.g., "Shader Model 5.1") + */ + SlangProfileID profile = SLANG_PROFILE_UNKNOWN; + + /** Flags for the code generation target. Currently unused. */ + SlangTargetFlags flags = 0; + + /** Default mode to use for floating-point operations on the target. + */ + SlangFloatingPointMode floatingPointMode = SLANG_FLOATING_POINT_MODE_DEFAULT; + + /** Optimization level to use for the target. + */ + SlangOptimizationLevel optimizationLevel = SLANG_OPTIMIZATION_LEVEL_DEFAULT; + }; + + typedef uint32_t SessionFlags; + enum + { + kSessionFlags_None = 0, + + /** Use application-specific policy for semantics of the `shared` keyword. + + This is a legacy/compatibility flag to help an existing Slang client + migrate to new language features, and should *not* be used by other + clients. This feature may be removed in a future release without a + deprecation warning, and this bit may be re-used for another feature. + You have been warned. + */ + kSessionFlag_FalcorCustomSharedKeywordSemantics = 1 << 0, + }; + + struct PreprocessorMacroDesc + { + const char* name; + const char* value; + }; + + struct SessionDesc + { + /** Code generation targets to include in the session. + */ + TargetDesc const* targets = nullptr; + SlangInt targetCount = 0; + + /** Flags to configure the session. + */ + SessionFlags flags = kSessionFlags_None; + + /** Default layout to assume for variables with matrix types. + */ + SlangMatrixLayoutMode defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_ROW_MAJOR; + + /** Paths to use when searching for `#include`d or `import`ed files. + */ + char const* const* searchPaths = nullptr; + SlangInt searchPathCount = 0; + + PreprocessorMacroDesc const* preprocessorMacros = nullptr; + SlangInt preprocessorMacroCount = 0; + + }; + + /** A session provides a scope for code that is loaded. + + A session can be used to load modules of Slang source code, + and to request target-specific compiled binaries and layout + information. + + In order to be able to load code, the session owns a set + of active "search paths" for resolving `#include` directives + and `import` declrations, as well as a set of global + preprocessor definitions that will be used for all code + that gets `import`ed in the session. + + If multiple user shaders are loaded in the same session, + and import the same module (e.g., two source files do `import X`) + then there will only be one copy of `X` loaded within the session. + + In order to be able to generate target code, the session + owns a list of available compilation targets, which specify + code generation options. + + Code loaded and compiled within a session is owned by the session + and will remain resident in memory until the session is released. + Applications wishing to control the memory usage for compiled + and loaded code should use multiple sessions. + */ + struct ISession : public ISlangUnknown + { + public: + /** Get the global session thas was used to create this session. + */ + virtual SLANG_NO_THROW IGlobalSession* SLANG_MCALL getGlobalSession() = 0; + + /** Load a module as it would be by code using `import`. + */ + virtual SLANG_NO_THROW IModule* SLANG_MCALL loadModule( + const char* moduleName, + IBlob** outDiagnostics = nullptr) = 0; + + /** Create a program out of existing compiled items. + */ + virtual SLANG_NO_THROW SlangResult SLANG_MCALL createProgram( + ProgramDesc const& desc, + IProgram** outProgram) = 0; + + /** Specialize a program based on type arguments. + */ + virtual SLANG_NO_THROW SlangResult SLANG_MCALL specializeProgram( + IProgram* program, + SlangInt specializationArgCount, + SpecializationArg const* specializationArgs, + IProgram** outSpecializedProgram, + ISlangBlob** outDiagnostics = nullptr) = 0; + + /** Specialize a type based on type arguments. + */ + virtual SLANG_NO_THROW TypeReflection* SLANG_MCALL specializeType( + TypeReflection* type, + SpecializationArg const* specializationArgs, + SlangInt specializationArgCount, + ISlangBlob** outDiagnostics = nullptr) = 0; + + + /** Get the layout `type` on the chosen `target`. + */ + virtual SLANG_NO_THROW TypeLayoutReflection* SLANG_MCALL getTypeLayout( + TypeReflection* type, + SlangInt targetIndex = 0, + LayoutRules rules = LayoutRules::Default, + ISlangBlob** outDiagnostics = nullptr) = 0; + + /** Create a request to load/compile front-end code. + */ + virtual SLANG_NO_THROW SlangResult SLANG_MCALL createCompileRequest( + SlangCompileRequest** outCompileRequest) = 0; + }; + + #define SLANG_UUID_ISession { 0x67618701, 0xd116, 0x468f, { 0xab, 0x3b, 0x47, 0x4b, 0xed, 0xce, 0xe, 0x3d } } + + + /** A module is the granularity of shader code compilation and loading. + + In most cases a module corresponds to a single compile "translation unit." + This will often be a single `.slang` or `.hlsl` file and everything it + `#include`s. + + Notably, a module `M` does *not* include the things it `import`s, as these + as distinct modules that `M` depends on. There is a directed graph of + module dependencies, and all modules in the graph must belong to the + same session (`ISession`). + */ + struct IModule : public ISlangUnknown + { + public: + }; + + #define SLANG_UUID_IModule { 0xc720e64, 0x8722, 0x4d31, { 0x89, 0x90, 0x63, 0x8a, 0x98, 0xb1, 0xc2, 0x79 } } + + + /** Argument used for specialization to types/values. + */ + struct SpecializationArg + { + enum class Kind : int32_t + { + Unknown, + Type, + }; + Kind kind; + union + { + TypeReflection* type; + }; + }; + + /** Description of a program to be created. + */ + struct ProgramDesc + { + struct Item + { + enum class Kind : int32_t + { + Program, + Module, + }; + Kind kind; + union + { + IProgram* program; + IModule* module; + }; + }; + + Item const* items; + SlangInt itemCount; + }; + + + /** A program comprises zero or more modules, entry points, etc. that have been linked together. + */ + struct IProgram : public ISlangUnknown + { + public: + /** Get the runtime session that this program belongs to. + */ + virtual SLANG_NO_THROW ISession* SLANG_MCALL getSession() = 0; + + /** Get the layout for this program for the chosen `targetIndex` + */ + virtual SLANG_NO_THROW ProgramLayout* SLANG_MCALL getLayout( + SlangInt targetIndex = 0, + IBlob** outDiagnostics = nullptr) = 0; + + /** Get the compiled code for the entry point at `entryPointIndex` for the chosen `targetIndex` + + If code has not already been generated for the given entry point and target, + then a compilation error may be detected, in which case `outDiagnostics` + (if non-null) will be filled in with a blob of messages diagnosing the error. + */ + virtual SLANG_NO_THROW SlangResult SLANG_MCALL getEntryPointCode( + SlangInt entryPointIndex, + SlangInt targetIndex, + IBlob** outCode, + IBlob** outDiagnostics = nullptr) = 0; + }; + + #define SLANG_UUID_IProgram { 0x5bc42be8, 0x5c50, 0x4929, { 0x9e, 0x5e, 0xd1, 0x5e, 0x7c, 0x24, 0x1, 0x5f } }; +} + +#define SLANG_API_VERSION 0 + +SLANG_API SlangResult slang_createGlobalSession( + SlangInt apiVersion, + slang::IGlobalSession** outGlobalSession); + +namespace slang +{ + inline SlangResult createGlobalSession( + slang::IGlobalSession** outGlobalSession) + { + return slang_createGlobalSession(SLANG_API_VERSION, outGlobalSession); + } } +/** +*/ +SLANG_API SlangResult spCompileRequest_getProgram( + SlangCompileRequest* request, + slang::IProgram** outProgram); + #endif /* DEPRECATED DEFINITIONS diff --git a/source/core/core.vcxproj b/source/core/core.vcxproj index 177cd2011..dd2f3c144 100644 --- a/source/core/core.vcxproj +++ b/source/core/core.vcxproj @@ -231,7 +231,9 @@ - + + Document + diff --git a/source/core/slang-writer.h b/source/core/slang-writer.h index 6e26d6750..cd5737b3d 100644 --- a/source/core/slang-writer.h +++ b/source/core/slang-writer.h @@ -45,7 +45,7 @@ public: // ISlangUnknown SLANG_REF_OBJECT_IUNKNOWN_QUERY_INTERFACE SLANG_REF_OBJECT_IUNKNOWN_ADD_REF - SLANG_NO_THROW uint32_t SLANG_MCALL release() { return (m_flags & WriterFlag::IsStatic) ? (uint32_t)decreaseReference() : (uint32_t)releaseReference(); } + SLANG_NO_THROW uint32_t SLANG_MCALL release() SLANG_OVERRIDE { return (m_flags & WriterFlag::IsStatic) ? (uint32_t)decreaseReference() : (uint32_t)releaseReference(); } // ISlangWriter - default impl SLANG_NO_THROW virtual void SLANG_MCALL flush() SLANG_OVERRIDE {} diff --git a/source/slang/slang-check.cpp b/source/slang/slang-check.cpp index 90947cf54..83b75b964 100644 --- a/source/slang/slang-check.cpp +++ b/source/slang/slang-check.cpp @@ -433,7 +433,7 @@ namespace Slang Session* getSession() { - return m_linkage->getSession(); + return m_linkage->getSessionImpl(); } public: @@ -9656,6 +9656,111 @@ namespace Slang } } +static bool shouldUseFalcorCustomSharedKeywordSemantics( + EntryPointGroup* entryPointGroup) +{ + if( !entryPointGroup->getLinkageImpl()->m_useFalcorCustomSharedKeywordSemantics ) + return false; + + // As a sanity check, if we are being asked to lay out an + // empty entry-point group, then don't apply the convention. + // + if(entryPointGroup->getEntryPointCount() == 0) + return false; + + // Otherwise we will look at the first entry point in the group, + // and use that to determine whether it looks like we are compiling + // ray-tracing shaders or not. + // + switch( entryPointGroup->getEntryPoint(0)->getStage() ) + { + case Stage::AnyHit: + case Stage::Callable: + case Stage::ClosestHit: + case Stage::Intersection: + case Stage::Miss: + case Stage::RayGeneration: + return true; + + default: + return false; + } +} + + +static bool shouldUseFalcorCustomSharedKeywordSemantics( + Program* program) +{ + if( !program->getLinkageImpl()->m_useFalcorCustomSharedKeywordSemantics ) + return false; + + // As a sanity check, if we are being asked to lay out a program + // with *no* entry points, then we don't apply the convention. + // + if(program->getEntryPointGroupCount() == 0) + return false; + + // Otherwise we let the first entry-point group determine if we should + // apply the policy for the entire program. + // + // Note: this could lead to confusing results if a `Program` mixes + // entry point groups for RT and non-RT pipelines, but that isn't + // a scenario we expect to come up and this whole routine is handling + // "do what I mean" semantics for legacy behavior. + // + return shouldUseFalcorCustomSharedKeywordSemantics(program->getEntryPointGroup(0)); +} + + +void EntryPointGroup::_collectShaderParams(DiagnosticSink* sink) +{ + // If and only if we are in the special mode for Falcor support + // where non-`shared` global shader parameters are actually + // supposed to go into the "local root signature," then we + // will consider such parameters as if they were entry-point + // parameters, attached to the group. + // + if( shouldUseFalcorCustomSharedKeywordSemantics(this) ) + { + for( auto module : getModuleDependencies() ) + { + auto moduleDecl = module->getModuleDecl(); + for( auto globalVar : moduleDecl->getMembersOfType() ) + { + // Don't consider globals that aren't shader parameters. + // + if(!isGlobalShaderParameter(globalVar)) + continue; + + // Don't consider global shader paramters that were marked + // `shared`, since that is how global-root-signature parameters + // are being specified. + // + if( globalVar->HasModifier() ) + continue; + + auto paramDeclRef = makeDeclRef(globalVar.Ptr()); + + ShaderParamInfo shaderParamInfo; + shaderParamInfo.paramDeclRef = paramDeclRef; + + ExistentialTypeSlots slots; + _collectExistentialSlotsForShaderParam( + shaderParamInfo, + slots, + paramDeclRef); + + if( slots.paramTypes.getCount() != 0 ) + { + sink->diagnose(globalVar, Diagnostics::typeParametersNotAllowedOnEntryPointGlobal, globalVar); + } + + m_shaderParams.add(shaderParamInfo); + } + } + } +} + // Validate that an entry point function conforms to any additional // constraints based on the stage (and profile?) it specifies. void validateEntryPoint( @@ -10334,9 +10439,6 @@ static bool doesParameterMatch( return true; } - - - /// Enumerate the existential-type parameters of a `Program`. /// /// Any parameters found will be added to the list of existential slots on `this`. @@ -10364,9 +10466,34 @@ static bool doesParameterMatch( auto moduleDecl = module->getModuleDecl(); for( auto globalVar : moduleDecl->getMembersOfType() ) { + // We do not want to consider global variable declarations + // that don't represents shader parameters. This includes + // things like `static` globals and `groupshared` variables. + // if(!isGlobalShaderParameter(globalVar)) continue; + // HACK: In order to support existing policy in the Falcor + // application, we support a custom mode where only + // global variables marked as `shared` should be considered + // as global shader parameters (that go in the "global + // root signature" for DXR). + // + // TODO: Eliminate this special case once all of the client + // application code has been ported to use more general-purpose + // Slang mechanisms (e.g., entry-point `uniform` parameters). + // + if( shouldUseFalcorCustomSharedKeywordSemantics(this) ) + { + if( !globalVar->HasModifier() ) + { + // Skip a non-`shared` global for purposes of enumerating + // shader parameters. + // + continue; + } + } + // This declaration may represent the same logical parameter // as a declaration that came from a different translation unit. // If that is the case, we want to re-use the same `ShaderParamInfo` @@ -10474,7 +10601,13 @@ static bool doesParameterMatch( entryPointReq); if( entryPoint ) { - program->addEntryPoint(entryPoint); + // TODO: We need to implement an explicit policy + // for what should happen if the user specified + // entry points via the command-line (or API), + // but didn't specify any groups (since the current + // compilation API doesn't allow for grouping). + // + program->addEntryPoint(entryPoint, sink); entryPointReq->getTranslationUnit()->entryPoints.add(entryPoint); } } @@ -10540,7 +10673,17 @@ static bool doesParameterMatch( validateEntryPoint(entryPoint, sink); - program->addEntryPoint(entryPoint); + // Note: in the case that the user didn't explicitly + // specify entry points and we are instead compiling + // a shader "library," then we do not want to automatically + // combine the entry points into groups in the generated + // `Program`, since that would be slightly too magical. + // + // Instead, each entry point will end up in a singleton + // group, so that its entry-point parameters lay out + // independent of the others. + // + program->addEntryPoint(entryPoint, sink); translationUnit->entryPoints.add(entryPoint); } } @@ -10636,7 +10779,7 @@ static bool doesParameterMatch( // generic application like `F` if it were // encountered in the source code. - auto session = linkage->getSession(); + auto session = linkage->getSessionImpl(); auto genericDeclRef = makeDeclRef(genericDecl); // The first pieces is a `VarExpr` that refers to `genericDecl`. @@ -10760,7 +10903,7 @@ static bool doesParameterMatch( List> const& args, DiagnosticSink* sink) { - Slang::_specializeExistentialTypeParams(getLinkage(), m_globalExistentialSlots, args, sink); + Slang::_specializeExistentialTypeParams(getLinkageImpl(), m_globalExistentialSlots, args, sink); } Type* Linkage::specializeType( @@ -10799,14 +10942,34 @@ static bool doesParameterMatch( return specializedType; } - /// Specialize a program to global generic arguments - RefPtr createSpecializedProgram( + // Shared implementation logic for the `_createSpecializedProgram*` entry points. + static RefPtr _createSpecializedProgramImpl( Linkage* linkage, Program* unspecializedProgram, List> const& globalGenericArgs, List> const& globalExistentialArgs, DiagnosticSink* sink) { + // TODO: If there are no specialization arguments, + // then the the result of specialization should + // be the same as the input... *but* we are promising + // to return a program without any entry points, + // and that screws things up here. + // + // For now we just carefully avod this early-exit + // if we have any entry points. + // + // Eventually we should try to revise the model so + // that specialization of a program that includes + // entry points can make some kind of sense. + // + if( globalGenericArgs.getCount() == 0 + && globalExistentialArgs.getCount() == 0 + && unspecializedProgram->getEntryPointCount() == 0) + { + return unspecializedProgram; + } + // The given `unspecializedProgram` should be one that // was checked through the front-end, so that now we // only need to check if the given arguments can satisfy @@ -10967,6 +11130,37 @@ static bool doesParameterMatch( specializedProgram->setGlobalGenericSubsitution(globalGenericSubsts); + return specializedProgram; + } + + /// Create a specialized copy of `unspecializedProgram`. + /// + /// The specialized program will include the entry points + /// from the original program (whether thsoe entry points + /// are specialized or not). + /// + static RefPtr _createSpecializedProgram( + Linkage* linkage, + Program* unspecializedProgram, + List> const& globalGenericArgs, + List> const& globalExistentialArgs, + DiagnosticSink* sink) + { + auto specializedProgram = _createSpecializedProgramImpl( + linkage, + unspecializedProgram, + globalGenericArgs, + globalExistentialArgs, + sink); + + // We need to ensure that the specialized program has whatever + // entry points (and groups) the unspecialized one had. + // + for( auto entryPointGroup : unspecializedProgram->getEntryPointGroups() ) + { + specializedProgram->addEntryPointGroup(entryPointGroup); + } + // Now deal with the shader parameters and existential arguments // // Note: We should in theory be able to just copy over the shader @@ -10981,6 +11175,61 @@ static bool doesParameterMatch( return specializedProgram; } + SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::specializeProgram( + slang::IProgram* inUnspecializedProgram, + SlangInt specializationArgCount, + slang::SpecializationArg const* specializationArgs, + slang::IProgram** outSpecializedProgram, + ISlangBlob** outDiagnostics) + { + auto unspecializedProgram = asInternal(inUnspecializedProgram); + + if( specializationArgCount == 0 ) + { + *outSpecializedProgram = ComPtr(asExternal(unspecializedProgram)).detach(); + return SLANG_OK; + } + + List> globalGenericArgs; + + List> globalExistentialArgs; + for( Int ii = 0; ii < specializationArgCount; ++ii ) + { + auto& specializationArg = specializationArgs[ii]; + switch( specializationArg.kind ) + { + case slang::SpecializationArg::Kind::Type: + { + auto typeArg = asInternal(specializationArg.type); + RefPtr argExpr = new SharedTypeExpr(); + argExpr->base = TypeExp(typeArg); + argExpr->type = QualType(getTypeType(typeArg)); + + globalExistentialArgs.add(argExpr); + } + break; + + default: + return SLANG_E_INVALID_ARG; + } + } + + DiagnosticSink sink(getSourceManager()); + auto specializedProgram = _createSpecializedProgram( + this, + unspecializedProgram, + globalGenericArgs, + globalExistentialArgs, + &sink); + sink.getBlobIfNeeded(outDiagnostics); + + if(!specializedProgram) + return SLANG_FAIL; + + *outSpecializedProgram = ComPtr(asExternal(specializedProgram)).detach(); + return SLANG_OK; + } + /// Specialize an entry point that was checked by the front-end, based on generic arguments. /// /// If the end-to-end compile request included generic argument strings @@ -11036,6 +11285,7 @@ static bool doesParameterMatch( // global or entry-point generic parameters. // auto unspecializedProgram = endToEndReq->getUnspecializedProgram(); + auto linkage = endToEndReq->getLinkage(); // First, let's parse the generic argument strings that were // provided via the API, so taht we can match them @@ -11058,12 +11308,13 @@ static bool doesParameterMatch( // applying the global generic arguments (if any) to the // unspecialized program. // - auto specializedProgram = createSpecializedProgram( + auto sink = endToEndReq->getSink(); + auto specializedProgram = _createSpecializedProgramImpl( endToEndReq->getLinkage(), unspecializedProgram, globalGenericArgs, globalExistentialArgs, - endToEndReq->getSink()); + sink); // If anything went wrong with the global generic // arguments, then bail out now. @@ -11090,15 +11341,30 @@ static bool doesParameterMatch( endToEndReq->entryPoints.setCount(entryPointCount); } - for( Index ii = 0; ii < entryPointCount; ++ii ) + Index entryPointCounter = 0; + + for( auto unspecializedEntryPointGroup : unspecializedProgram->getEntryPointGroups() ) { - auto unspecializedEntryPoint = unspecializedProgram->getEntryPoint(ii); - auto& entryPointInfo = endToEndReq->entryPoints[ii]; + List> specializedEntryPoints; + for( auto unspecializedEntryPoint : unspecializedEntryPointGroup->getEntryPoints() ) + { + Index entryPointIndex = entryPointCounter++; + auto& entryPointInfo = endToEndReq->entryPoints[entryPointIndex]; + + auto specializedEntryPoint = createSpecializedEntryPoint(endToEndReq, unspecializedEntryPoint, entryPointInfo); + specializedEntryPoints.add(specializedEntryPoint); + } - auto specializedEntryPoint = createSpecializedEntryPoint(endToEndReq, unspecializedEntryPoint, entryPointInfo); - specializedProgram->addEntryPoint(specializedEntryPoint); + RefPtr specializedEntryPointGroup = EntryPointGroup::create(linkage, specializedEntryPoints, endToEndReq->getSink()); + specializedProgram->addEntryPointGroup(specializedEntryPointGroup); } + // Finalize the information for the specialized program, + // now that we have computed its entry point list, etc. + // + specializedProgram->_collectShaderParams(sink); + specializedProgram->_specializeExistentialTypeParams(globalExistentialArgs, sink); + return specializedProgram; } diff --git a/source/slang/slang-compiler.cpp b/source/slang/slang-compiler.cpp index 99caed8f0..ff2facc26 100644 --- a/source/slang/slang-compiler.cpp +++ b/source/slang/slang-compiler.cpp @@ -292,6 +292,31 @@ namespace Slang return getModule()->getLinkage(); } + // + // EntryPointGroup + // + + RefPtr EntryPointGroup::create( + Linkage* linkage, + List> const& entryPoints, + DiagnosticSink* sink) + { + RefPtr group = new EntryPointGroup(linkage); + + for( auto entryPoint : entryPoints ) + { + for( auto module : entryPoint->getModuleDependencies() ) + { + group->m_dependencyList.addDependency(module); + } + group->m_entryPoints.add(entryPoint); + } + + group->_collectShaderParams(sink); + + return group; + } + // Profile Profile::LookUp(char const* name) @@ -1481,6 +1506,58 @@ SlangResult dissassembleDXILUsingDXC( writeEntryPointResultToStandardOutput(compileRequest, entryPoint, targetReq, result); } + CompileResult& TargetProgram::_createEntryPointResult( + Int entryPointIndex, + BackEndCompileRequest* backEndRequest, + EndToEndCompileRequest* endToEndRequest) + { + // It is possible that entry points goot added to the `Program` + // *after* we created this `TargetProgram`, so there might be + // a request for an entry point that we didn't allocate space for. + // + // TODO: Change the construction logic so that a `Program` is + // constructed all at once rather than incrementally, to avoid + // this problem. + // + if(entryPointIndex >= m_entryPointResults.getCount()) + m_entryPointResults.setCount(entryPointIndex+1); + + auto entryPoint = m_program->getEntryPoint(entryPointIndex); + + auto& result = m_entryPointResults[entryPointIndex]; + result = emitEntryPoint( + backEndRequest, + entryPoint, + entryPointIndex, + m_targetReq, + endToEndRequest); + + return result; + + } + + CompileResult& TargetProgram::getOrCreateEntryPointResult( + Int entryPointIndex, + DiagnosticSink* sink) + { + if(entryPointIndex >= m_entryPointResults.getCount()) + m_entryPointResults.setCount(entryPointIndex+1); + + auto& result = m_entryPointResults[entryPointIndex]; + if( result.format != ResultFormat::None ) + return result; + + RefPtr backEndRequest = new BackEndCompileRequest( + m_program->getLinkageImpl(), + sink, + m_program); + + return _createEntryPointResult( + entryPointIndex, + backEndRequest, + nullptr); + } + void generateOutputForTarget( BackEndCompileRequest* compileReq, TargetRequest* targetReq, @@ -1494,17 +1571,15 @@ SlangResult dissassembleDXILUsingDXC( auto entryPointCount = program->getEntryPointCount(); for(Index ii = 0; ii < entryPointCount; ++ii) { - auto entryPoint = program->getEntryPoint(ii); - CompileResult entryPointResult = emitEntryPoint( - compileReq, - entryPoint, + targetProgram->_createEntryPointResult( ii, - targetReq, + compileReq, endToEndReq); - targetProgram->setEntryPointResult(ii, entryPointResult); } } + + static void _generateOutput( BackEndCompileRequest* compileRequest, EndToEndCompileRequest* endToEndReq) diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 86c6272a9..2b2c35845 100644 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -366,6 +366,55 @@ namespace Slang ModuleDependencyList m_dependencyList; }; + class EntryPointGroup : public RefObject + { + public: + static RefPtr create( + Linkage* linkage, + List> const& entryPoints, + DiagnosticSink* sink); + + Linkage* getLinkageImpl() { return m_linkage; } + + /// Get the number of entry points in the group + Index getEntryPointCount() { return m_entryPoints.getCount(); } + + /// Get the entry point at the given `index`. + RefPtr getEntryPoint(Index index) { return m_entryPoints[index]; } + + /// Get the full ist of entry points in the group. + List> const& getEntryPoints() { return m_entryPoints; } + + /// Get a list of modules that this entry point group depends on. + /// + /// This will include the dependencies of all of the entry points in the group. + /// + List> getModuleDependencies() { return m_dependencyList.getModuleList(); } + + /// Get an array of all entry-point-group shader parameters. + List const& getShaderParams() { return m_shaderParams; } + + private: + EntryPointGroup(Linkage* linkage) + : m_linkage(linkage) + {} + + void _collectShaderParams(DiagnosticSink* sink); + + Linkage* m_linkage; + List> m_entryPoints; + + /// Information about shader parameters to be associated with the entry-point group itself. + /// + /// This list captures parameters that logically belong to the group itself, rather than + /// to any specific entry point in the group. + /// + List m_shaderParams; + + /// Modules the entry point group depends on. + ModuleDependencyList m_dependencyList; + }; + enum class PassThroughMode : SlangPassThrough { None = SLANG_PASS_THROUGH_NONE, // don't pass through: use Slang compiler @@ -382,9 +431,13 @@ namespace Slang /// may span multiple Slang source files), and provides access /// to both the AST and IR representations of that code. /// - class Module : public RefObject + class Module : public RefObject, public slang::IModule { public: + SLANG_REF_OBJECT_IUNKNOWN_ALL + + ISlangUnknown* getInterface(const Guid& guid); + /// Create a module (initially empty). Module(Linkage* linkage); @@ -525,6 +578,8 @@ namespace Slang Dictionary> typeLayouts; Dictionary>& getTypeLayouts() { return typeLayouts; } + + TypeLayout* getTypeLayout(Type* type); }; /// Are we generating code for a D3D API? @@ -576,14 +631,54 @@ namespace Slang ComPtr createRawBlob(void const* data, size_t size); /// A context for loading and re-using code modules. - class Linkage : public RefObject + class Linkage : public RefObject, public slang::ISession { public: + SLANG_REF_OBJECT_IUNKNOWN_ALL + + ISlangUnknown* getInterface(const Guid& guid); + + SLANG_NO_THROW slang::IGlobalSession* SLANG_MCALL getGlobalSession() override; + SLANG_NO_THROW slang::IModule* SLANG_MCALL loadModule( + const char* moduleName, + slang::IBlob** outDiagnostics = nullptr) override; + SLANG_NO_THROW SlangResult SLANG_MCALL createProgram( + slang::ProgramDesc const& desc, + slang::IProgram** outProgram) override; + SLANG_NO_THROW SlangResult SLANG_MCALL specializeProgram( + slang::IProgram* program, + SlangInt specializationArgCount, + slang::SpecializationArg const* specializationArgs, + slang::IProgram** outSpecializedProgram, + ISlangBlob** outDiagnostics = nullptr) override; + SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL specializeType( + slang::TypeReflection* type, + slang::SpecializationArg const* specializationArgs, + SlangInt specializationArgCount, + ISlangBlob** outDiagnostics = nullptr) override; + SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL getTypeLayout( + slang::TypeReflection* type, + SlangInt targetIndex = 0, + slang::LayoutRules rules = slang::LayoutRules::Default, + ISlangBlob** outDiagnostics = nullptr) override; + SLANG_NO_THROW SlangResult SLANG_MCALL createCompileRequest( + SlangCompileRequest** outCompileRequest) override; + + void addTarget( + slang::TargetDesc const& desc); + SlangResult addSearchPath( + char const* path); + SlangResult addPreprocessorDefine( + char const* name, + char const* value); + SlangResult setMatrixLayoutMode( + SlangMatrixLayoutMode mode); + /// Create an initially-empty linkage Linkage(Session* session); /// Get the parent session for this linkage - Session* getSession() { return m_session; } + Session* getSessionImpl() { return m_session; } // Information on the targets we are being asked to // generate code for. @@ -703,6 +798,8 @@ namespace Slang OptimizationLevel optimizationLevel = OptimizationLevel::Default; + bool m_useFalcorCustomSharedKeywordSemantics = false; + private: Session* m_session = nullptr; @@ -887,9 +984,25 @@ namespace Slang /// to be used togehter so that, e.g., layout can make sure to allocate /// space for the global shader parameters in all referenced modules. /// - class Program : public RefObject + class Program : public RefObject, public slang::IProgram { public: + SLANG_REF_OBJECT_IUNKNOWN_ALL; + ISlangUnknown* getInterface(Guid const& guid); + + SLANG_NO_THROW slang::ISession* SLANG_MCALL getSession() override; + + SLANG_NO_THROW slang::ProgramLayout* SLANG_MCALL getLayout( + SlangInt targetIndex, + slang::IBlob** outDiagnostics) override; + + SLANG_NO_THROW SlangResult SLANG_MCALL getEntryPointCode( + SlangInt entryPointIndex, + SlangInt targetIndex, + slang::IBlob** outCode, + slang::IBlob** outDiagnostics) override; + + /// Create a new program, initially empty. /// /// All code loaded into the program must come @@ -898,7 +1011,7 @@ namespace Slang Linkage* linkage); /// Get the linkage that this program uses. - Linkage* getLinkage() { return m_linkage; } + Linkage* getLinkageImpl() { return m_linkage; } /// Get the number of entry points added to the program Index getEntryPointCount() { return m_entryPoints.getCount(); } @@ -909,6 +1022,12 @@ namespace Slang /// Get the full ist of entry points on the program. List> const& getEntryPoints() { return m_entryPoints; } + + Index getEntryPointGroupCount() { return m_entryPointGroups.getCount(); } + RefPtr getEntryPointGroup(Index index) { return m_entryPointGroups[index]; } + List> const& getEntryPointGroups() { return m_entryPointGroups; } + + /// Get the substitution (if any) that represents how global generics are specialized. RefPtr getGlobalGenericSubstitution() { return m_globalGenericSubst; } @@ -936,7 +1055,13 @@ namespace Slang /// /// This also adds everything the entry point depends on to the list of references. /// - void addEntryPoint(EntryPoint* entryPoint); + void addEntryPoint(EntryPoint* entryPoint, DiagnosticSink* sink); + + /// Add an entry point group to the program + /// + /// This also adds everything the entry point group depends on to the list of references. + /// + void addEntryPointGroup(EntryPointGroup* entryPointGroup); /// Set the global generic argument substitution to use. void setGlobalGenericSubsitution(RefPtr subst) @@ -1011,6 +1136,9 @@ namespace Slang // Entry points that are part of the program. List > m_entryPoints; + // Entry points that are part of the program. + List > m_entryPointGroups; + // Specializations for global generic parameters (if any) RefPtr m_globalGenericSubst; @@ -1065,23 +1193,34 @@ namespace Slang /// Get the compiled code for an entry point on the target. /// - /// This routine assumes code generation has already been - /// performed and called `setEntryPointResult`. + /// If this is the first time that code generation has + /// been requested, report any errors that arise during + /// code generation to the given `sink`. + /// + CompileResult& getOrCreateEntryPointResult(Int entryPointIndex, DiagnosticSink* sink); + + /// Get the compiled code for an entry point on the target. + /// + /// This routine assumes that `getOrCreateEntryPointResult` + /// has already been called previously. /// CompileResult& getExistingEntryPointResult(Int entryPointIndex) { return m_entryPointResults[entryPointIndex]; } - // TODO: Need a lazy `getOrCreateEntryPointResult` - /// Set the compiled code for an entry point. + /// Internal helper for `getOrCreateEntryPointResult`. /// - /// Should only be called by code generation. - void setEntryPointResult(Int entryPointIndex, CompileResult const& result) - { - m_entryPointResults[entryPointIndex] = result; - } + /// This is used so that command-line and API-based + /// requests for code can bottleneck through the same place. + /// + /// Shouldn't be called directly by most code. + /// + CompileResult& _createEntryPointResult( + Int entryPointIndex, + BackEndCompileRequest* backEndRequest, + EndToEndCompileRequest* endToEndRequest); private: // The program being compiled or laid out @@ -1143,6 +1282,9 @@ namespace Slang EndToEndCompileRequest( Session* session); + EndToEndCompileRequest( + Linkage* linkage); + // What container format are we being asked to generate? // // Note: This field is unused except by the options-parsing @@ -1224,6 +1366,8 @@ namespace Slang Program* getSpecializedProgram() { return m_specializedProgram; } private: + void init(); + Session* m_session = nullptr; RefPtr m_linkage; DiagnosticSink m_sink; @@ -1277,9 +1421,24 @@ namespace Slang struct TypeCheckingCache; // - class Session + class Session : public RefObject, public slang::IGlobalSession { public: + SLANG_REF_OBJECT_IUNKNOWN_ALL + + ISlangUnknown* getInterface(const Guid& guid); + + /** Create a new linkage. + */ + SLANG_NO_THROW SlangResult SLANG_MCALL createSession( + slang::SessionDesc const& desc, + slang::ISession** outSession) override; + + SLANG_NO_THROW SlangProfileID SLANG_MCALL findProfile( + char const* name) override; + + + enum class SharedLibraryFuncType { Glslang_Compile, @@ -1421,6 +1580,76 @@ namespace Slang RefPtr m_builtinLinkage; }; + +// +// The following functions are utilties to convert between +// matching "external" (public API) and "internal" (implementation) +// types. They are favored over explicit casts because they +// help avoid making incorrect conversions (e.g., when using +// `reinterpret_cast` or C-style casts), and because they +// abstract over the conversion required for each pair of types. +// + +inline slang::IGlobalSession* asExternal(Session* session) +{ + return static_cast(session); +} + +inline slang::ISession* asExternal(Linkage* linkage) +{ + return static_cast(linkage); +} + +inline Module* asInternal(slang::IModule* module) +{ + return static_cast(module); +} + +inline slang::IModule* asExternal(Module* module) +{ + return static_cast(module); +} + +inline Program* asInternal(slang::IProgram* module) +{ + return static_cast(module); +} + +inline slang::IProgram* asExternal(Program* module) +{ + return static_cast(module); +} + +static inline slang::ProgramLayout* asExternal(ProgramLayout* programLayout) +{ + return (slang::ProgramLayout*) programLayout; +} + +inline Type* asInternal(slang::TypeReflection* type) +{ + return reinterpret_cast(type); +} + +inline slang::TypeReflection* asExternal(Type* type) +{ + return reinterpret_cast(type); +} + +inline TypeLayout* asInternal(slang::TypeLayoutReflection* type) +{ + return reinterpret_cast(type); +} + +inline slang::TypeLayoutReflection* asExternal(TypeLayout* type) +{ + return reinterpret_cast(type); +} + +inline SlangCompileRequest* asExternal(EndToEndCompileRequest* request) +{ + return reinterpret_cast(request); +} + } #endif diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index ce4131173..fc9cac3f3 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -409,6 +409,8 @@ DIAGNOSTIC(39016, Error, globalUniformsNotSupported, "'$0' is implicitly a globa DIAGNOSTIC(39017, Error, tooManyShaderRecordConstantBuffers, "can have at most one 'shader record' attributed constant buffer; found $0.") +DIAGNOSTIC(39018, Error, typeParametersNotAllowedOnEntryPointGlobal, "local-root-signature shader parameter '$0' at global scope must not include existential/interface types"); + // // 4xxxx - IL code generation. // diff --git a/source/slang/slang-diagnostics.h b/source/slang/slang-diagnostics.h index e1b9846d7..3d75c577d 100644 --- a/source/slang/slang-diagnostics.h +++ b/source/slang/slang-diagnostics.h @@ -143,7 +143,11 @@ namespace Slang class DiagnosticSink { - public: + public: + DiagnosticSink(SourceManager* sourceManager) + : sourceManager(sourceManager) + {} + struct Flag { enum Enum: uint32_t diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index 623c57f2b..aa56bc0b2 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -48,29 +48,37 @@ enum class BuiltInCOp // EntryPointLayout* findEntryPointLayout( - ProgramLayout* programLayout, - EntryPoint* entryPoint) + ProgramLayout* programLayout, + EntryPoint* entryPoint, + EntryPointGroupLayout** outEntryPointGroupLayout = nullptr) { - for( auto entryPointLayout : programLayout->entryPoints ) + for( auto entryPointGroupLayout : programLayout->entryPointGroups ) { - if(entryPointLayout->entryPoint->getName() != entryPoint->getName()) - continue; - - // TODO: We need to be careful about this check, since it relies on - // the profile information in the layout matching that in the request. - // - // What we really seem to want here is some dictionary mapping the - // `EntryPoint` directly to the `EntryPointLayout`, and maybe - // that is precisely what we should build... - // - if(entryPointLayout->profile != entryPoint->getProfile()) - continue; - - // TODO: can't easily filter on translation unit here... - // Ideally the `EntryPoint` should get filled in with a pointer - // the specific function declaration that represents the entry point. - - return entryPointLayout.Ptr(); + for( auto entryPointLayout : entryPointGroupLayout->entryPoints ) + { + if(entryPointLayout->entryPoint->getName() != entryPoint->getName()) + continue; + + // TODO: We need to be careful about this check, since it relies on + // the profile information in the layout matching that in the request. + // + // What we really seem to want here is some dictionary mapping the + // `EntryPoint` directly to the `EntryPointLayout`, and maybe + // that is precisely what we should build... + // + if(entryPointLayout->profile != entryPoint->getProfile()) + continue; + + // TODO: can't easily filter on translation unit here... + // Ideally the `EntryPoint` should get filled in with a pointer + // the specific function declaration that represents the entry point. + + if( outEntryPointGroupLayout ) + { + *outEntryPointGroupLayout = entryPointGroupLayout; + } + return entryPointLayout; + } } return nullptr; diff --git a/source/slang/slang-file-system.h b/source/slang/slang-file-system.h index e89bf45db..1d747d73f 100644 --- a/source/slang/slang-file-system.h +++ b/source/slang/slang-file-system.h @@ -48,7 +48,7 @@ public: const char* path, ISlangBlob** outCanonicalPath) SLANG_OVERRIDE; - virtual SLANG_NO_THROW void SLANG_MCALL clearCache() {} + virtual SLANG_NO_THROW void SLANG_MCALL clearCache() SLANG_OVERRIDE {} /// Get a default instance static ISlangFileSystemExt* getSingleton() { return &s_singleton; } @@ -132,9 +132,9 @@ class CacheFileSystem: public ISlangFileSystemExt, public RefObject virtual SLANG_NO_THROW SlangResult SLANG_MCALL getCanonicalPath( const char* path, - ISlangBlob** outCanonicalPath); + ISlangBlob** outCanonicalPath) SLANG_OVERRIDE; - virtual SLANG_NO_THROW void SLANG_MCALL clearCache(); + virtual SLANG_NO_THROW void SLANG_MCALL clearCache() SLANG_OVERRIDE; /// Ctor CacheFileSystem(ISlangFileSystem* fileSystem, UniqueIdentityMode uniqueIdentityMode = UniqueIdentityMode::Default, PathStyle pathStyle = PathStyle::Default); diff --git a/source/slang/slang-ir-link.cpp b/source/slang/slang-ir-link.cpp index 4c1f72adb..07135c148 100644 --- a/source/slang/slang-ir-link.cpp +++ b/source/slang/slang-ir-link.cpp @@ -13,8 +13,9 @@ namespace Slang // TODO: maybe arrange so that codegen is driven from the layout layer // instead of the input/request layer. EntryPointLayout* findEntryPointLayout( - ProgramLayout* programLayout, - EntryPoint* EntryPoint); + ProgramLayout* programLayout, + EntryPoint* entryPoint, + EntryPointGroupLayout** outEntryPointGroupLayout); struct IRSpecSymbol : RefObject { @@ -783,9 +784,12 @@ IRFunc* specializeIRForEntryPoint( if( paramIndex < paramLayoutCount ) { auto paramLayout = paramsStructLayout->fields[paramIndex]; + + auto offsetParamLayout = applyOffsetToVarLayout(paramLayout, entryPointLayout->parametersLayout); + context->builder->addLayoutDecoration( pp, - paramLayout); + offsetParamLayout); } else { @@ -1276,6 +1280,33 @@ LinkedIR linkIR( context->globalVarLayouts.AddIfNotExists(mangledName, globalVarLayout); } + EntryPointGroupLayout* entryPointGroupLayout = nullptr; + auto entryPointLayout = findEntryPointLayout(programLayout, entryPoint, &entryPointGroupLayout); + + auto offsetEntryPointLayout = entryPointLayout->getAbsoluteLayout(entryPointGroupLayout); + + // Note: when we are doing the compatibility approach for Falcor, we + // can have global-scope symbols that are actually part of the + // local root signature (entry point group), so we need to make + // sure to apply those layouts appropriately. + auto entryPointGroupStructLayout = getScopeStructLayout(entryPointGroupLayout); + for(auto entry : entryPointGroupStructLayout->mapVarToLayout) + { + if(!entry.Key) + continue; + + auto mangledName = getMangledName(entry.Key); + auto groupVarLayout = entry.Value; + + // We need to "adjust" the layout that was computed for the parameter + // because it will be relative to the start of the entry-point group, + // rather than absolute. + // + auto absoluteVarLayout = groupVarLayout->getAbsoluteLayout(entryPointGroupLayout->parametersLayout); + + context->globalVarLayouts.AddIfNotExists(mangledName, absoluteVarLayout); + } + context->builder->setInsertInto(context->getModule()->getModuleInst()); // for now, clone all unreferenced witness tables @@ -1289,13 +1320,12 @@ LinkedIR linkIR( cloneGlobalValue(context, (IRWitnessTable*)sym.Value->irGlobalValue); } - auto entryPointLayout = findEntryPointLayout(programLayout, entryPoint); // 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, entryPoint, entryPointLayout); + auto irEntryPoint = specializeIRForEntryPoint(context, entryPoint, offsetEntryPointLayout); // 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 diff --git a/source/slang/slang-parameter-binding.cpp b/source/slang/slang-parameter-binding.cpp index 5c4fd24b5..d5efc3515 100644 --- a/source/slang/slang-parameter-binding.cpp +++ b/source/slang/slang-parameter-binding.cpp @@ -377,6 +377,7 @@ struct SharedParameterBindingContext TargetRequest* getTargetRequest() { return targetRequest; } DiagnosticSink* getSink() { return m_sink; } + Linkage* getLinkage() { return targetRequest->getLinkage(); } }; static DiagnosticSink* getSink(SharedParameterBindingContext* shared) @@ -403,6 +404,8 @@ struct ParameterBindingContext TargetRequest* getTargetRequest() { return shared->getTargetRequest(); } LayoutRulesFamilyImpl* getRulesFamily() { return layoutContext.getRulesFamily(); } + + Linkage* getLinkage() { return shared->getLinkage(); } }; static DiagnosticSink* getSink(ParameterBindingContext* context) @@ -610,7 +613,7 @@ RefPtr getTypeLayoutForGlobalShaderParameter( auto layoutContext = context->layoutContext; auto rules = layoutContext.getRulesFamily(); - if( varDecl->HasModifier() && as(type) ) + if(varDecl->HasModifier() && as(type)) { return createTypeLayout( layoutContext.with(rules->getShaderRecordConstantBufferRules()), @@ -620,13 +623,20 @@ RefPtr getTypeLayoutForGlobalShaderParameter( // We want to check for a constant-buffer type with a `push_constant` layout // qualifier before we move on to anything else. - if (varDecl->HasModifier() && as(type)) + if( varDecl->HasModifier() && as(type) ) { return createTypeLayout( layoutContext.with(rules->getPushConstantBufferRules()), type); } + // TODO: The cases below for detecting globals that aren't actually + // shader parameters should be redundant now that the semantic + // checking logic is responsible for populating the list of + // parameters on a `Program`. We should be able to clean up + // the code by removing these two cases, and the related null + // pointer checks in the code that calls this. + // HLSL `static` modifier indicates "thread local" if(varDecl->HasModifier()) return nullptr; @@ -1940,6 +1950,45 @@ struct ScopeLayoutBuilder _addParameter(firstVarLayout, parameterInfo); } + + // Add a "simple" parameter that cannot have any user-defined + // register or binding modifiers, so that its layout computation + // can be simplified greatly. + // + void addSimpleParameter( + RefPtr varLayout) + { + // The main `addParameter` logic will deal with any ordinary/uniform data, + // and with the "pending" part of the layout. + // + addParameter(varLayout); + + // That leaves us to deal with the resource usage that isn't + // handled by `addParameter`. + // + auto paramTypeLayout = varLayout->getTypeLayout(); + for (auto paramTypeResInfo : paramTypeLayout->resourceInfos) + { + // We need to skip ordinary/uniform data because it was + // handled by `addParameter`. + // + if(paramTypeResInfo.kind == LayoutResourceKind::Uniform) + continue; + + // Whatever resources the parameter uses, we need to + // assign the parameter's location/register/binding offset to + // be the sum of everything added so far. + // + auto scopeResInfo = m_structLayout->findOrAddResourceInfo(paramTypeResInfo.kind); + varLayout->findOrAddResourceInfo(paramTypeResInfo.kind)->index = scopeResInfo->count.getFiniteValue(); + + // We then need to add the resources consumed by the parameter + // to those consumed by the scope. + // + scopeResInfo->count += paramTypeResInfo.count; + } + } + RefPtr endLayout() { // Finish computing the layout for the ordindary data (if any). @@ -2007,7 +2056,7 @@ static ParameterBindingAndKindInfo maybeAllocateConstantBufferBinding( /// Iterate over the parameters of an entry point to compute its requirements. /// -static void collectEntryPointParameters( +static RefPtr collectEntryPointParameters( ParameterBindingContext* context, EntryPoint* entryPoint, SubstitutionSet typeSubst) @@ -2119,35 +2168,7 @@ static void collectEntryPointParameters( // we need to add its resource usage to that of the entry // point as a whole. // - // Any "ordinary" data (e.g., a `float4x4`) needs to be accounted - // for using the `ScopeLayoutBuilder`, since it will handle - // the details of target-specific `struct` type layout. - // - scopeBuilder.addParameter(paramVarLayout); - - // All of the other resources types will be handled in a - // simpler loop that just increments the relevant counters. - // - for (auto paramTypeResInfo : paramTypeLayout->resourceInfos) - { - // We need to skip ordinary data because it is being - // handled by the `scopeBuilder`. - // - if(paramTypeResInfo.kind == LayoutResourceKind::Uniform) - continue; - - // Whatever resources the parameter uses, we need to - // assign the parameter's location/register/binding offset to - // be the sum of everything added so far. - // - auto entryPointResInfo = paramsStructLayout->findOrAddResourceInfo(paramTypeResInfo.kind); - paramVarLayout->findOrAddResourceInfo(paramTypeResInfo.kind)->index = entryPointResInfo->count.getFiniteValue(); - - // We then need to add the resources consumed by the parameter - // to those consumed by the entry point. - // - entryPointResInfo->count += paramTypeResInfo.count; - } + scopeBuilder.addSimpleParameter(paramVarLayout); } entryPointLayout->parametersLayout = scopeBuilder.endLayout(); @@ -2189,6 +2210,25 @@ static void collectEntryPointParameters( entryPointLayout->resultLayout = resultLayout; } + + return entryPointLayout; +} + + /// Remove resource usage from `typeLayout` that should only be stored per-entry-point. + /// + /// This is used when constructing the layout for an entry point group, to make sure + /// that certain kinds of resource usage from the entry point don't "leak" into + /// the resource usage of the group. + /// +static void removePerEntryPointParameterKinds( + TypeLayout* typeLayout) +{ + typeLayout->removeResourceUsage(LayoutResourceKind::VaryingInput); + typeLayout->removeResourceUsage(LayoutResourceKind::VaryingOutput); + typeLayout->removeResourceUsage(LayoutResourceKind::ShaderRecord); + typeLayout->removeResourceUsage(LayoutResourceKind::HitAttributes); + typeLayout->removeResourceUsage(LayoutResourceKind::ExistentialObjectParam); + typeLayout->removeResourceUsage(LayoutResourceKind::ExistentialTypeParam); } static void collectParameters( @@ -2242,10 +2282,66 @@ static void collectParameters( } // Next consider parameters for entry points - for(auto entryPoint : program->getEntryPoints()) + for( auto entryPointGroup : program->getEntryPointGroups() ) { - context->stage = entryPoint->getStage(); - collectEntryPointParameters(context, entryPoint, globalGenericSubst); + RefPtr entryPointGroupLayout = new EntryPointGroupLayout(); + entryPointGroupLayout->group = entryPointGroup; + + context->shared->programLayout->entryPointGroups.add(entryPointGroupLayout); + + + ScopeLayoutBuilder scopeBuilder; + scopeBuilder.beginLayout(context); + auto entryPointGroupParamsStructLayout = scopeBuilder.m_structLayout; + + // First lay out any shader parameters that belong to the group + // itself, rather than to its nested entry points. + // + // This ensures that looking up one of the parameters of the + // group by index in its parameters truct will Just Work. + // + for( auto groupParam : entryPointGroup->getShaderParams() ) + { + auto paramDeclRef = groupParam.paramDeclRef; + auto paramType = GetType(paramDeclRef); + + RefPtr paramVarLayout = new VarLayout(); + paramVarLayout->varDecl = paramDeclRef; + + auto paramTypeLayout = createTypeLayout( + context->layoutContext.with(context->getRulesFamily()->getConstantBufferRules()), + paramType); + paramVarLayout->typeLayout = paramTypeLayout; + + scopeBuilder.addSimpleParameter(paramVarLayout); + } + + for(auto entryPoint : entryPointGroup->getEntryPoints()) + { + // Note: we do not want the entry point group to accumulate + // locations for varying input/output parameters: those + // should be specific to each entry point. + // + // We address this issue by manually removing any + // layout information for the relevant resource kinds + // from the group's layout before adding the parameters + // of any entry point for layout. + // + removePerEntryPointParameterKinds(scopeBuilder.m_structLayout); + + context->stage = entryPoint->getStage(); + auto entryPointLayout = collectEntryPointParameters(context, entryPoint, globalGenericSubst); + + auto entryPointParamsLayout = entryPointLayout->parametersLayout; + auto entryPointParamsTypeLayout = entryPointParamsLayout->typeLayout; + + scopeBuilder.addSimpleParameter(entryPointParamsLayout); + + entryPointGroupLayout->entryPoints.add(entryPointLayout); + } + removePerEntryPointParameterKinds(scopeBuilder.m_structLayout); + + entryPointGroupLayout->parametersLayout = scopeBuilder.endLayout(); } context->entryPointLayout = nullptr; } @@ -2452,44 +2548,6 @@ RefPtr generateParameterBindings( completeBindingsForParameter(&context, parameter); } - // After we have allocated registers/bindings to everything - // in the global scope we will process the parameters - // of each entry point in order. - // - // Note: the effect of the current implementation is to - // allocate non-overlapping registers/bindings between all - // the entry points in the compile request (e.g., if you - // have a vertex and fragment shader being compiled together, - // we will allocate distinct constant buffer registers for - // their uniform parameters). - // - // TODO: We probably need to provide some more nuanced control - // over whether entry points get overlapping or non-overlapping - // bindings. It seems clear that if we were compiling multiple - // compute kernels in one invocation we'd want them to get - // overlapping bindings, because we cannot ever have them bound - // together in a single pipeline state. - // - // Similarly, entry point parameters of DirectX Raytracing (DXR) - // shaders should probably be allowed to overlap by default, - // since those parameters should really go into the "local root signature." - // (Note: there is a bit more subtlety around ray tracing - // shaders that will be assembled into a "hit group") - // - // For now we are just doing the simplest thing, which will be - // appropriate for: - // - // * Compiling a single compute shader in a compile request. - // * Compiling some number of rasterization shader entry points - // in a single request, to be used together. - // * Compiling a single ray-tracing shader in a compile request. - // - for( auto entryPoint : sharedContext.programLayout->entryPoints ) - { - auto entryPointParamsLayout = entryPoint->parametersLayout; - completeBindingsForParameter(&context, entryPointParamsLayout); - } - // Next we need to create a type layout to reflect the information // we have collected, and we will use the `ScopeLayoutBuilder` // to encapsulate the logic that can be shared with the entry-point @@ -2510,17 +2568,75 @@ RefPtr generateParameterBindings( cbInfo->index = globalConstantBufferBinding.index; } - // After we have laid out all the ordinary parameters, - // we need to go through the global scope plus each entry point, - // and "flush" out any pending data that was associated with - // those scopes as part of dealing with interface-type parameters. + // After we have laid out all the ordinary global parameters, + // we need to "flush" out any pending data that was associated with + // the global scope as part of dealing with interface-type parameters. // _allocateBindingsForPendingData(&context, globalScopeVarLayout->pendingVarLayout); - for( auto entryPoint : sharedContext.programLayout->entryPoints ) + + // After we have allocated registers/bindings to everything + // in the global scope we will process the parameters + // of the entry points. + // + // Note: at the moment we are laying out *all* information related to global-scope + // parameters (including pending data from interface-type parameters) before + // anything pertaining to entry points. This is a crucial design choice to + // get right, and we might want to revisit it based on experience. + + // In some cases, a user will want to ensure that all the + // entry points they compile get non-overlapping + // registers/bindings. E.g., if you have a vertex and fragment + // shader being compiled together for Vulkan, you probably want distinct + // bindings for their entry-point `uniform` parametres, so + // that they can be used together. + // + // In other cases, however, a user probably doesn't want us + // to conservatively allocate non-overlapping bindings. + // E.g., if they have a bunch of compute shaders in a single + // file, then they probably want each compute shader to + // compute its parameter layout "from scratch" as if the + // others don't exist. + // + // The way we handle this is by putting the entry points of a + // `Program` into groups, and ensuring that within each group + // we allocate parameters that don't overlap, but we don't + // worry about overlap across groups. + // + for( auto entryPointGroup : sharedContext.programLayout->entryPointGroups ) { - _allocateBindingsForPendingData(&context, entryPoint->parametersLayout->pendingVarLayout); - } + // We save off the allocation state as it was before the entry-point + // group, so that we can restore it to this state after each group. + // + // TODO: We probably ought to wrap all the state relevant to allocation + // of registers/bindings into a single struct/field so that we only + // have one thing to save/restore here even if new state gets added. + // + auto savedGlobalSpaceUsedRangeSets = sharedContext.globalSpaceUsedRangeSets; + auto savedUsedSpaces = sharedContext.usedSpaces; + + // The group will have been allocated a layout that combines the + // usage of all of the contained entry-points, so we just need to + // allocate the entry-point group to be placed after all the global-scope + // parameters. + // + auto entryPointGroupParamsLayout = entryPointGroup->parametersLayout; + completeBindingsForParameter(&context, entryPointGroupParamsLayout); + + _allocateBindingsForPendingData(&context, entryPointGroupParamsLayout->pendingVarLayout); + + // TODO: Should we add the offset information from the group to + // the layout information for each entry point (and thence to its parameters)? + // + // This seems important if we want to allow clients to conveniently + // ignore groups when doing their reflection queries. + // Once we've allocated bindigns for the parameters of entry points + // in the group, we restore the state for tracking what register/bindings + // are used to where it was before. + // + sharedContext.globalSpaceUsedRangeSets = savedGlobalSpaceUsedRangeSets; + sharedContext.usedSpaces = savedUsedSpaces; + } // HACK: we want global parameters to not have to deal with offsetting // by the `VarLayout` stored in `globalScopeVarLayout`, so we will scan diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index aff1d832d..fffbf2c62 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -1709,8 +1709,7 @@ namespace Slang TokenSpan tokenSpan; tokenSpan.mBegin = parser->tokenReader.mCursor; tokenSpan.mEnd = parser->tokenReader.mEnd; - DiagnosticSink newSink; - newSink.sourceManager = parser->sink->sourceManager; + DiagnosticSink newSink(parser->sink->sourceManager); Parser newParser(*parser); newParser.sink = &newSink; auto speculateParseRs = parseGenericApp(&newParser, base); diff --git a/source/slang/slang-reflection.cpp b/source/slang/slang-reflection.cpp index c5428cdeb..3871e2ccc 100644 --- a/source/slang/slang-reflection.cpp +++ b/source/slang/slang-reflection.cpp @@ -86,6 +86,15 @@ static inline SlangReflectionEntryPoint* convert(EntryPointLayout* entryPoint) return (SlangReflectionEntryPoint*) entryPoint; } +static inline EntryPointGroupLayout* convert(SlangEntryPointGroupLayout* entryPointGroup) +{ + return (EntryPointGroupLayout*) entryPointGroup; +} + +static inline SlangEntryPointGroupLayout* convert(EntryPointGroupLayout* entryPointGroup) +{ + return (SlangEntryPointGroupLayout*) entryPointGroup; +} static inline ProgramLayout* convert(SlangReflection* program) { @@ -596,9 +605,9 @@ SLANG_API SlangReflectionType * spReflection_FindTypeByName(SlangReflection * re // TODO: We should extend this API to support getting error messages // when type lookup fails. // - Slang::DiagnosticSink sink; - - sink.sourceManager = programLayout->getTargetReq()->getLinkage()->getSourceManager();; + Slang::DiagnosticSink sink( + programLayout->getTargetReq()->getLinkage()->getSourceManager()); + RefPtr result = program->getTypeFromString(name, &sink); return (SlangReflectionType*)result.Ptr(); } @@ -611,13 +620,9 @@ SLANG_API SlangReflectionTypeLayout* spReflection_GetTypeLayout( auto context = convert(reflection); auto type = convert(inType); auto targetReq = context->getTargetReq(); - auto layoutContext = getInitialLayoutContextForTarget(targetReq, context); - RefPtr result; - if (targetReq->getTypeLayouts().TryGetValue(type, result)) - return (SlangReflectionTypeLayout*)result.Ptr(); - result = createTypeLayout(layoutContext, type); - targetReq->getTypeLayouts()[type] = result; - return (SlangReflectionTypeLayout*)result.Ptr(); + + auto typeLayout = targetReq->getTypeLayout(type); + return convert(typeLayout); } SLANG_API SlangReflectionType* spReflectionType_GetResourceResultType(SlangReflectionType* inType) @@ -1071,11 +1076,32 @@ SLANG_API size_t spReflectionVariableLayout_GetSpace(SlangReflectionVariableLayo space += info->space; } - // Next, deal with any dedicated register-space offset applied to, e.g., a parameter block - if (auto spaceInfo = varLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) - { - space += spaceInfo->index; - } + // Note: this code used to try and take a variable with + // an offset for `LayoutResourceKind::RegisterSpace` and + // add it to the space returned, but that isn't going + // to be right in some cases. + // + // Imageine if we have: + // + // struct X { Texture2D y; } + // struct S { Texture2D t; ParmaeterBlock x; } + // + // Texture2D gA; + // S gS; + // + // We expect `gS` to have an offset for `LayoutResourceKind::ShaderResourceView` + // of one (since its texture must come after `gA`), and an offset for + // `LayoutResourceKind::RegisterSpace` of one (since the default space will be + // space zero). It would be incorrect for us to imply that `gS.t` should + // be `t1, space1`, though, because the space offset of `gS` doesn't actually + // apply to `t`. + // + // For now we are punting on this issue and leaving it in the hands of the + // application to determine when a space offset from an "outer" variable should + // apply to the locations of things in an "inner" variable. + // + // There is no policy we can apply locally in this function that + // will Just Work, so the best we can do is try to not lie. return space; } @@ -1392,7 +1418,7 @@ SLANG_API SlangReflectionEntryPoint* spReflection_findEntryPointByName(SlangRefl auto program = convert(inProgram); if(!program) return 0; - // TODO: improve on dumb linear search + // TODO: improve on naive linear search for(auto ep : program->entryPoints) { if(ep->entryPoint->getName()->text == name) @@ -1405,6 +1431,74 @@ SLANG_API SlangReflectionEntryPoint* spReflection_findEntryPointByName(SlangRefl } +SLANG_API SlangInt spReflection_getEntryPointGroupCount(SlangReflection* inProgram) +{ + auto program = convert(inProgram); + if(!program) return 0; + + return program->entryPointGroups.getCount(); +} + +SLANG_API SlangEntryPointGroupLayout* spReflection_getEntryPointGroupByIndex(SlangReflection* inProgram, SlangInt index) +{ + auto program = convert(inProgram); + if(!program) return 0; + + if(index < 0) return nullptr; + if(index >= program->entryPointGroups.getCount()) return nullptr; + + return convert(program->entryPointGroups[(int) index].Ptr()); +} + +SLANG_API SlangInt spEntryPointGroupLayout_getEntryPointCount(SlangEntryPointGroupLayout* inGroup) +{ + auto group = convert(inGroup); + if(!group) return 0; + + return group->entryPoints.getCount(); +} + +SLANG_API SlangReflectionEntryPoint* spEntryPointGroupLayout_getEntryPointByIndex(SlangEntryPointGroupLayout* inGroup, SlangInt index) +{ + auto group = convert(inGroup); + if(!group) return 0; + + if(index < 0) return nullptr; + if(index >= group->entryPoints.getCount()) return nullptr; + + return convert(group->entryPoints[(int) index].Ptr()); +} + +SLANG_API SlangReflectionVariableLayout* spEntryPointGroupLayout_getVarLayout(SlangEntryPointGroupLayout* inGroup) +{ + auto group = convert(inGroup); + if(!group) return 0; + + return convert(group->parametersLayout); +} + +SLANG_API SlangInt spEntryPointGroupLayout_getParameterCount(SlangEntryPointGroupLayout* inGroup) +{ + auto groupLayout = convert(inGroup); + if(!groupLayout) return 0; + + auto& params = groupLayout->group->getShaderParams(); + return params.getCount(); +} + +SLANG_API SlangReflectionVariableLayout* spEntryPointGroupLayout_getParameterByIndex(SlangEntryPointGroupLayout* inGroup, SlangInt index) +{ + auto groupLayout = convert(inGroup); + if(!groupLayout) return nullptr; + + auto& params = groupLayout->group->getShaderParams(); + if(index < 0) return nullptr; + if(index >= params.getCount()) return nullptr; + + return convert(getScopeStructLayout(groupLayout)->fields[index]); +} + + SLANG_API SlangUInt spReflection_getGlobalConstantBufferBinding(SlangReflection* inProgram) { auto program = convert(inProgram); @@ -1437,10 +1531,9 @@ SLANG_API SlangReflectionType* spReflection_specializeType( auto unspecializedType = convert(inType); if(!unspecializedType) return nullptr; - auto linkage = programLayout->getProgram()->getLinkage(); + auto linkage = programLayout->getProgram()->getLinkageImpl(); - DiagnosticSink sink; - sink.sourceManager = linkage->getSourceManager(); + DiagnosticSink sink(linkage->getSourceManager()); auto specializedType = linkage->specializeType(unspecializedType, specializationArgCount, (Type* const*) specializationArgs, &sink); diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp index 30ab53ca6..c3dbb4966 100644 --- a/source/slang/slang-type-layout.cpp +++ b/source/slang/slang-type-layout.cpp @@ -1165,6 +1165,83 @@ RefPtr applyOffsetToTypeLayout( return newTypeLayout; } +RefPtr applyOffsetToVarLayout( + VarLayout* baseLayout, + VarLayout* offsetLayout) +{ + RefPtr adjustedLayout = new VarLayout(); + adjustedLayout->typeLayout = baseLayout->typeLayout; + adjustedLayout->varDecl = baseLayout->varDecl; + adjustedLayout->flags = baseLayout->flags; + adjustedLayout->semanticName = baseLayout->semanticName; + adjustedLayout->semanticIndex = baseLayout->semanticIndex; + adjustedLayout->systemValueSemantic = baseLayout->systemValueSemantic; + adjustedLayout->systemValueSemanticIndex = baseLayout->systemValueSemanticIndex; + adjustedLayout->stage = baseLayout->stage; + + if( auto basePendingLayout = baseLayout->pendingVarLayout ) + { + if( auto offsetPendingLayout = offsetLayout->pendingVarLayout ) + { + adjustedLayout->pendingVarLayout = applyOffsetToVarLayout( + basePendingLayout, + offsetPendingLayout); + } + } + + for( auto baseResInfo : baseLayout->resourceInfos ) + { + auto adjustedResInfo = baseResInfo; + if( auto offsetResInfo = offsetLayout->FindResourceInfo(baseResInfo.kind) ) + { + adjustedResInfo.index += offsetResInfo->index; + adjustedResInfo.space += offsetResInfo->space; + } + adjustedLayout->resourceInfos.add(adjustedResInfo); + } + + return adjustedLayout; +} + +EntryPointLayout* EntryPointLayout::getAbsoluteLayout( + VarLayout* parentLayout) +{ + SLANG_ASSERT(parentLayout); + + if(m_absoluteLayout) + return m_absoluteLayout; + + RefPtr adjustedLayout = new EntryPointLayout(); + adjustedLayout->entryPoint = this->entryPoint; + adjustedLayout->flags = this->flags; + adjustedLayout->parametersLayout = this->parametersLayout->getAbsoluteLayout(parentLayout); + adjustedLayout->profile = this->profile; + if( auto baseResultLayout = this->resultLayout ) + { + adjustedLayout->resultLayout = baseResultLayout->getAbsoluteLayout(parentLayout); + } + adjustedLayout->taggedUnionTypeLayouts = this->taggedUnionTypeLayouts; + + m_absoluteLayout = adjustedLayout; + return adjustedLayout; +} + +EntryPointLayout* EntryPointLayout::getAbsoluteLayout(EntryPointGroupLayout* parentGroup) +{ + SLANG_ASSERT(parentGroup); + return getAbsoluteLayout(parentGroup->parametersLayout); +} + + +VarLayout* VarLayout::getAbsoluteLayout(VarLayout* parentAbsoluteLayout) +{ + if( !m_absoluteLayout ) + { + m_absoluteLayout = applyOffsetToVarLayout(this, parentAbsoluteLayout); + } + return m_absoluteLayout; +} + static bool _usesResourceKind(RefPtr typeLayout, LayoutResourceKind kind) { auto resInfo = typeLayout->FindResourceInfo(kind); @@ -3180,6 +3257,20 @@ RefPtr createTypeLayout( return _createTypeLayout(context, type).layout; } +void TypeLayout::removeResourceUsage(LayoutResourceKind kind) +{ + Int infoCount = resourceInfos.getCount(); + for( Int ii = 0; ii < infoCount; ++ii ) + { + if( resourceInfos[ii].kind == kind ) + { + resourceInfos.removeAt(ii); + return; + } + } +} + + void TypeLayout::addResourceUsageFrom(TypeLayout* otherTypeLayout) { for(auto resInfo : otherTypeLayout->resourceInfos) diff --git a/source/slang/slang-type-layout.h b/source/slang/slang-type-layout.h index 97113c77f..e066c2700 100644 --- a/source/slang/slang-type-layout.h +++ b/source/slang/slang-type-layout.h @@ -377,6 +377,8 @@ public: addResourceUsage(info); } + void removeResourceUsage(LayoutResourceKind kind); + void addResourceUsageFrom(TypeLayout* otherTypeLayout); /// "Unwrap" any layers of array-ness from this type layout. @@ -473,6 +475,21 @@ public: } RefPtr pendingVarLayout; + + + /// Get the "absolute" layout of this variable, if applicable. + /// + /// The `parentAbsoluteLayout` must be the absolute layout + /// of the parent of this variable. + /// + /// The absolute layout will be created once and cached on + /// future accesses; if `parentAbsoluteLayout` is not + /// consistent at different call sites an unexpected + /// layout could be returned. + /// + VarLayout* getAbsoluteLayout(VarLayout* parentAbsoluteLayout); + + RefPtr m_absoluteLayout; }; // type layout for a variable that has a constant-buffer type @@ -650,6 +667,8 @@ public: StructTypeLayout* getScopeStructLayout( ScopeLayout* programLayout); +class EntryPointGroupLayout; + // Layout information for a single shader entry point // within a program // @@ -682,6 +701,18 @@ public: /// These are any tagged union types used by the generic /// arguments that this entry point is being compiled with. List> taggedUnionTypeLayouts; + + EntryPointLayout* getAbsoluteLayout(VarLayout* parentLayout); + EntryPointLayout* getAbsoluteLayout(EntryPointGroupLayout* parentGroup); + + RefPtr m_absoluteLayout; +}; + +class EntryPointGroupLayout : public ScopeLayout +{ +public: + RefPtr group; + List> entryPoints; }; class GenericParamLayout : public Layout @@ -725,6 +756,10 @@ public: // will (eventually) belong there... List> entryPoints; + // Entry points can also be grouped for layout purposes (e.g., to form + // ray-tracing hit groups), so this array represents those groups + List> entryPointGroups; + List> globalGenericParams; Dictionary globalGenericParamsMap; }; @@ -1113,6 +1148,11 @@ RefPtr applyOffsetToTypeLayout( RefPtr oldTypeLayout, RefPtr offsetVarLayout); + /// Create a layout like `baseLayout`, but offset by `offsetLayout` +RefPtr applyOffsetToVarLayout( + VarLayout* baseLayout, + VarLayout* offsetLayout); + } #endif diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index a3875ef62..b18e4d4d9 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -33,6 +33,12 @@ namespace Slang { +// Allocate static const storage for the various interface IDs that the Slang API needs to expose +static const Guid IID_ISlangUnknown = SLANG_UUID_ISlangUnknown; +static const Guid IID_ISlangBlob = SLANG_UUID_ISlangBlob; +static const Guid IID_ISession = SLANG_UUID_ISession; +static const Guid IID_IGlobalSession = SLANG_UUID_IGlobalSession; +static const Guid IID_IModule = SLANG_UUID_IModule; Session::Session() { @@ -91,6 +97,55 @@ Session::Session() addBuiltinSource(hlslLanguageScope, "hlsl", getHLSLLibraryCode()); } +ISlangUnknown* Session::getInterface(const Guid& guid) +{ + if(guid == IID_ISlangUnknown || guid == IID_IGlobalSession) + return asExternal(this); + return nullptr; +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Session::createSession( + slang::SessionDesc const& desc, + slang::ISession** outSession) +{ + RefPtr linkage = new Linkage(this); + + Int targetCount = desc.targetCount; + for(Int ii = 0; ii < targetCount; ++ii) + { + linkage->addTarget(desc.targets[ii]); + } + + if(desc.flags & slang::kSessionFlag_FalcorCustomSharedKeywordSemantics) + { + linkage->m_useFalcorCustomSharedKeywordSemantics = true; + } + + linkage->setMatrixLayoutMode(desc.defaultMatrixLayoutMode); + + Int searchPathCount = desc.searchPathCount; + for(Int ii = 0; ii < searchPathCount; ++ii) + { + linkage->addSearchPath(desc.searchPaths[ii]); + } + + Int macroCount = desc.preprocessorMacroCount; + for(Int ii = 0; ii < macroCount; ++ii) + { + auto& macro = desc.preprocessorMacros[ii]; + linkage->addPreprocessorDefine(macro.name, macro.value); + } + + *outSession = asExternal(linkage.detach()); + return SLANG_OK; +} + +SLANG_NO_THROW SlangProfileID SLANG_MCALL Session::findProfile( + char const* name) +{ + return Slang::Profile::LookUp(name).raw; +} + struct IncludeHandlerImpl : IncludeHandler { Linkage* linkage; @@ -330,9 +385,183 @@ Linkage::Linkage(Session* session) setFileSystem(nullptr); } -// Allocate static const storage for the various interface IDs that the Slang API needs to expose -static const Guid IID_ISlangUnknown = SLANG_UUID_ISlangUnknown; -static const Guid IID_ISlangBlob = SLANG_UUID_ISlangBlob; +ISlangUnknown* Linkage::getInterface(const Guid& guid) +{ + if(guid == IID_ISlangUnknown || guid == IID_ISession) + return asExternal(this); + + return nullptr; +} + +SLANG_NO_THROW slang::IGlobalSession* SLANG_MCALL Linkage::getGlobalSession() +{ + return asExternal(getSessionImpl()); +} + +void Linkage::addTarget( + slang::TargetDesc const& desc) +{ + auto targetIndex = addTarget(CodeGenTarget(desc.format)); + auto target = targets[targetIndex]; + + target->floatingPointMode = FloatingPointMode(desc.floatingPointMode); + target->targetFlags = desc.flags; + target->targetProfile = Profile(desc.profile); +} + +#if 0 +SLANG_NO_THROW SlangInt SLANG_MCALL Linkage::getTargetCount() +{ + return targets.getCount(); +} + +SLANG_NO_THROW slang::ITarget* SLANG_MCALL Linkage::getTargetByIndex(SlangInt index) +{ + if(index < 0) return nullptr; + if(index >= targets.getCount()) return nullptr; + return asExternal(targets[index]); +} +#endif + +SLANG_NO_THROW slang::IModule* SLANG_MCALL Linkage::loadModule( + const char* moduleName, + slang::IBlob** outDiagnostics) +{ + auto name = getNamePool()->getName(moduleName); + + DiagnosticSink sink(getSourceManager()); + auto module = findOrImportModule(name, SourceLoc(), &sink); + sink.getBlobIfNeeded(outDiagnostics); + + return asExternal(module); +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::createProgram( + slang::ProgramDesc const& desc, + slang::IProgram** outProgram) +{ + RefPtr program = new Program(this); + + auto itemCount = desc.itemCount; + for(SlangInt ii = 0; ii < itemCount; ++ii) + { + auto& item = desc.items[ii]; + switch(item.kind) + { + case slang::ProgramDesc::Item::Kind::Program: + { + Program* existingProgram = asInternal(item.program); + for(auto referencedModule : existingProgram->getModuleDependencies()) + { + program->addReferencedLeafModule(referencedModule); + } + + // TODO: Need to decide whether to include the entry points as well... + } + break; + + case slang::ProgramDesc::Item::Kind::Module: + { + Module* module = asInternal(item.module); + program->addReferencedModule(module); + } + break; + + default: + return SLANG_E_INVALID_ARG; + } + } + + *outProgram = asExternal(program.detach()); + return SLANG_OK; +} + +SLANG_NO_THROW slang::TypeReflection* SLANG_MCALL Linkage::specializeType( + slang::TypeReflection* inUnspecializedType, + slang::SpecializationArg const* specializationArgs, + SlangInt specializationArgCount, + ISlangBlob** outDiagnostics) +{ + auto unspecializedType = asInternal(inUnspecializedType); + + List typeArgs; + + for(Int ii = 0; ii < specializationArgCount; ++ii) + { + auto& arg = specializationArgs[ii]; + if(arg.kind != slang::SpecializationArg::Kind::Type) + return nullptr; + + typeArgs.add(asInternal(arg.type)); + } + + DiagnosticSink sink(getSourceManager()); + auto specializedType = specializeType(unspecializedType, typeArgs.getCount(), typeArgs.getBuffer(), &sink); + sink.getBlobIfNeeded(outDiagnostics); + + return asExternal(specializedType); +} + +SLANG_NO_THROW slang::TypeLayoutReflection* SLANG_MCALL Linkage::getTypeLayout( + slang::TypeReflection* inType, + SlangInt targetIndex, + slang::LayoutRules rules, + ISlangBlob** outDiagnostics) +{ + auto type = asInternal(inType); + + if(targetIndex < 0 || targetIndex >= targets.getCount()) + return nullptr; + + auto target = targets[targetIndex]; + + // TODO: We need a way to pass through the layout rules + // that the user requested (e.g., constant buffers vs. + // structured buffer rules). Right now the API only + // exposes a single case, so this isn't a big deal. + // + SLANG_UNUSED(rules); + + auto typeLayout = target->getTypeLayout(type); + + // TODO: We currently don't have a path for capturing + // errors that occur during layout (e.g., types that + // are invalid because of target-specific layout constraints). + // + SLANG_UNUSED(outDiagnostics); + + return asExternal(typeLayout); +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Linkage::createCompileRequest( + SlangCompileRequest** outCompileRequest) +{ + auto compileRequest = new EndToEndCompileRequest(this); + *outCompileRequest = asExternal(compileRequest); + return SLANG_OK; +} + +SlangResult Linkage::addSearchPath( + char const* path) +{ + searchDirectories.searchDirectories.add(Slang::SearchDirectory(path)); + return SLANG_OK; +} + +SlangResult Linkage::addPreprocessorDefine( + char const* name, + char const* value) +{ + preprocessorDefinitions[name] = value; + return SLANG_OK; +} + +SlangResult Linkage::setMatrixLayoutMode( + SlangMatrixLayoutMode mode) +{ + defaultMatrixLayoutMode = MatrixLayoutMode(mode); + return SLANG_OK; +} /** Base class for simple blobs. */ @@ -386,7 +615,7 @@ ComPtr createRawBlob(void const* inData, size_t size) Session* TargetRequest::getSession() { - return linkage->getSession(); + return linkage->getSessionImpl(); } MatrixLayoutMode TargetRequest::getDefaultMatrixLayoutMode() @@ -394,6 +623,29 @@ MatrixLayoutMode TargetRequest::getDefaultMatrixLayoutMode() return linkage->getDefaultMatrixLayoutMode(); } +TypeLayout* TargetRequest::getTypeLayout(Type* type) +{ + // TODO: We are not passing in a `ProgramLayout` here, although one + // is nominally required to establish the global ordering of + // generic type parameters, which might be referenced from field types. + // + // The solution here is to make sure that the reflection data for + // uses of global generic/existential types does *not* include any + // kind of index in that global ordering, and just refers to the + // parameter instead (leaving the user to figure out how that + // maps to the ordering via some API on the program layout). + // + auto layoutContext = getInitialLayoutContextForTarget(this, nullptr); + + RefPtr result; + if (getTypeLayouts().TryGetValue(type, result)) + return result.Ptr(); + result = createTypeLayout(layoutContext, type); + getTypeLayouts()[type] = result; + return result.Ptr(); +} + + // // TranslationUnitRequest // @@ -485,8 +737,7 @@ RefPtr Linkage::parseTypeString(String typeStr, RefPtr scope) Slang::SourceFile* srcFile = localSourceManager.createSourceFileWithString(PathInfo::makeTypeParse(), typeStr); // We'll use a temporary diagnostic sink - DiagnosticSink sink; - sink.sourceManager = &localSourceManager; + DiagnosticSink sink(&localSourceManager); // RAII type to make make sure current SourceManager is restored after parse. // Use RAII - to make sure everything is reset even if an exception is thrown. @@ -521,7 +772,7 @@ RefPtr Linkage::parseTypeString(String typeStr, RefPtr scope) nullptr); return parseTypeFromSourceFile( - getSession(), + getSessionImpl(), tokens, &sink, scope, getNamePool(), SourceLanguage::Slang); } @@ -552,7 +803,7 @@ Type* Program::getTypeFromString(String typeStr, DiagnosticSink* sink) for(auto module : getModuleDependencies()) scopesToTry.add(module->getModuleDecl()->scope); - auto linkage = getLinkage(); + auto linkage = getLinkageImpl(); for(auto& s : scopesToTry) { RefPtr typeExpr = linkage->parseTypeString( @@ -785,13 +1036,15 @@ SlangResult FrontEndCompileRequest::executeActionsInner() if (getSink()->GetErrorCount() != 0) return SLANG_FAIL; - if ((compileFlags & SLANG_COMPILE_FLAG_NO_CODEGEN) == 0) - { - // Generate initial IR for all the translation - // units, if we are in a mode where IR is called for. - generateIR(); - } - + // We always generate IR for all the translation units. + // + // TODO: We may eventually have a mode where we skip + // IR codegen and only produce an AST (e.g., for use when + // debugging problems in the parser or semantic checking), + // but for now there are no cases where not having IR + // makes sense. + // + generateIR(); if (getSink()->GetErrorCount() != 0) return SLANG_FAIL; @@ -819,9 +1072,23 @@ BackEndCompileRequest::BackEndCompileRequest( EndToEndCompileRequest::EndToEndCompileRequest( Session* session) : m_session(session) + , m_sink(nullptr) { m_linkage = new Linkage(session); + init(); +} +EndToEndCompileRequest::EndToEndCompileRequest( + Linkage* linkage) + : m_session(linkage->getSessionImpl()) + , m_linkage(linkage) + , m_sink(nullptr) +{ + init(); +} + +void EndToEndCompileRequest::init() +{ m_sink.sourceManager = m_linkage->getSourceManager(); // Set all the default writers @@ -919,7 +1186,7 @@ SlangResult EndToEndCompileRequest::executeActionsInner() entryPointReq->getName(), entryPointReq->getProfile()); - specializedProgram->addEntryPoint(entryPoint); + specializedProgram->addEntryPoint(entryPoint, getSink()); } } @@ -1353,6 +1620,12 @@ Module::Module(Linkage* linkage) : m_linkage(linkage) {} +ISlangUnknown* Module::getInterface(const Guid& guid) +{ + if(guid == IID_ISlangUnknown || guid == IID_IModule) + return asExternal(this); + return nullptr; +} void Module::addModuleDependency(Module* module) { @@ -1367,10 +1640,69 @@ void Module::addFilePathDependency(String const& path) // Program +static const Guid IID_IProgram = SLANG_UUID_IProgram; + Program::Program(Linkage* linkage) : m_linkage(linkage) {} +ISlangUnknown* Program::getInterface(Guid const& guid) +{ + if(guid == IID_ISlangUnknown + || guid == IID_IProgram) + { + return static_cast(this); + } + + return nullptr; +} + +SLANG_NO_THROW slang::ISession* SLANG_MCALL Program::getSession() +{ + return m_linkage; +} + +SLANG_NO_THROW slang::ProgramLayout* SLANG_MCALL Program::getLayout( + Int targetIndex, + slang::IBlob** outDiagnostics) +{ + auto linkage = getLinkageImpl(); + if(targetIndex < 0 || targetIndex >= linkage->targets.getCount()) + return nullptr; + auto target = linkage->targets[targetIndex]; + + DiagnosticSink sink(linkage->getSourceManager()); + auto programLayout = getTargetProgram(target)->getOrCreateLayout(&sink); + sink.getBlobIfNeeded(outDiagnostics); + + return asExternal(programLayout); +} + +SLANG_NO_THROW SlangResult SLANG_MCALL Program::getEntryPointCode( + SlangInt entryPointIndex, + Int targetIndex, + slang::IBlob** outCode, + slang::IBlob** outDiagnostics) +{ + auto linkage = getLinkageImpl(); + if(targetIndex < 0 || targetIndex >= linkage->targets.getCount()) + return SLANG_E_INVALID_ARG; + auto target = linkage->targets[targetIndex]; + + auto targetProgram = getTargetProgram(target); + + DiagnosticSink sink(linkage->getSourceManager()); + auto& entryPointResult = targetProgram->getOrCreateEntryPointResult(entryPointIndex, &sink); + sink.getBlobIfNeeded(outDiagnostics); + + if(entryPointResult.format == ResultFormat::None ) + return SLANG_FAIL; + + *outCode = entryPointResult.getBlob().detach(); + return SLANG_OK; +} + + void Program::addReferencedModule(Module* module) { m_moduleDependencyList.addDependency(module); @@ -1383,13 +1715,27 @@ void Program::addReferencedLeafModule(Module* module) m_filePathDependencyList.addDependency(module); } -void Program::addEntryPoint(EntryPoint* entryPoint) +void Program::addEntryPoint(EntryPoint* entryPoint, DiagnosticSink* sink) +{ + List> entryPoints; + entryPoints.add(entryPoint); + + RefPtr entryPointGroup = EntryPointGroup::create(getLinkageImpl(), entryPoints, sink); + + addEntryPointGroup(entryPointGroup); +} + +void Program::addEntryPointGroup(EntryPointGroup* entryPointGroup) { - m_entryPoints.add(entryPoint); + m_entryPointGroups.add(entryPointGroup); - for(auto module : entryPoint->getModuleDependencies()) + for(auto entryPoint : entryPointGroup->getEntryPoints()) { - addReferencedModule(module); + m_entryPoints.add(entryPoint); + for(auto module : entryPoint->getModuleDependencies()) + { + addReferencedModule(module); + } } } @@ -1398,7 +1744,7 @@ RefPtr Program::getOrCreateIRModule(DiagnosticSink* sink) if(!m_irModule) { m_irModule = generateIRForProgram( - m_linkage->getSession(), + m_linkage->getSessionImpl(), this, sink); } @@ -1466,7 +1812,7 @@ SlangResult DiagnosticSink::getBlobIfNeeded(ISlangBlob** outBlob) Session* CompileRequestBase::getSession() { - return getLinkage()->getSession(); + return getLinkage()->getSessionImpl(); } static const Slang::Guid IID_ISlangFileSystemExt = SLANG_UUID_ISlangFileSystemExt; @@ -1512,12 +1858,13 @@ void Session::addBuiltinSource( String const& path, String const& source) { - DiagnosticSink sink; + SourceManager* sourceManager = getBuiltinSourceManager(); + + DiagnosticSink sink(sourceManager); RefPtr compileRequest = new FrontEndCompileRequest( m_builtinLinkage, &sink); - SourceManager* sourceManager = getBuiltinSourceManager(); // Set the source manager on the sink sink.sourceManager = sourceManager; @@ -1611,20 +1958,24 @@ static SlangCompileRequest* convert(Slang::EndToEndCompileRequest* request) static Slang::EndToEndCompileRequest* convert(SlangCompileRequest* request) { return reinterpret_cast(request); } -static SlangLinkage* convert(Slang::Linkage* linkage) -{ return reinterpret_cast(linkage); } - -static Slang::Linkage* convert(SlangLinkage* linkage) -{ return reinterpret_cast(linkage); } - -static SlangModule* convert(Slang::Module* module) -{ return reinterpret_cast(module); } - SLANG_API SlangSession* spCreateSession(const char*) { return convert(new Slang::Session()); } +SLANG_API SlangResult slang_createGlobalSession( + SlangInt apiVersion, + slang::IGlobalSession** outGlobalSession) +{ + if(apiVersion != 0) + return SLANG_E_NOT_IMPLEMENTED; + + Slang::Session* globalSession = new Slang::Session(); + Slang::ComPtr result(Slang::asExternal(globalSession)); + *outGlobalSession = result.detach(); + return SLANG_OK; +} + SLANG_API void spDestroySession( SlangSession* session) { @@ -1698,35 +2049,6 @@ SLANG_API SlangResult spSessionCheckPassThroughSupport( return Slang::checkExternalCompilerSupport(s, Slang::PassThroughMode(passThrough)); } - -SLANG_API SlangLinkage* spCreateLinkage( - SlangSession* session) -{ - auto s = convert(session); - auto linkage = new Slang::Linkage(s); - return convert(linkage); -} - -SLANG_API void spDestroyLinkage( - SlangLinkage* linkage) -{ - if(!linkage) return; - auto lnk = convert(linkage); - delete lnk; -} - -SLANG_API SlangModule* spLoadModule( - SlangLinkage* linkage, - char const* moduleName) -{ - if(!linkage) return nullptr; - auto lnk = convert(linkage); - - auto mod = lnk->loadModule(moduleName); - return convert(mod); -} - - SLANG_API SlangCompileRequest* spCreateCompileRequest( SlangSession* session) { @@ -1839,7 +2161,7 @@ SLANG_API void spSetMatrixLayoutMode( { auto req = convert(request); auto linkage = req->getLinkage(); - linkage->defaultMatrixLayoutMode = Slang::MatrixLayoutMode(mode); + linkage->setMatrixLayoutMode(mode); } SLANG_API void spSetTargetMatrixLayoutMode( @@ -1932,7 +2254,7 @@ SLANG_API void spAddSearchPath( { auto req = convert(request); auto linkage = req->getLinkage(); - linkage->searchDirectories.searchDirectories.add(Slang::SearchDirectory(path)); + linkage->addSearchPath(path); } SLANG_API void spAddPreprocessorDefine( @@ -1942,7 +2264,7 @@ SLANG_API void spAddPreprocessorDefine( { auto req = convert(request); auto linkage = req->getLinkage(); - linkage->preprocessorDefinitions[key] = value; + linkage->addPreprocessorDefine(key, value); } SLANG_API char const* spGetDiagnosticOutput( @@ -2391,6 +2713,18 @@ SLANG_API void const* spGetCompileRequestCode( // Reflection API +SLANG_API SlangResult spCompileRequest_getProgram( + SlangCompileRequest* request, + slang::IProgram** outProgram) +{ + if( !request ) return SLANG_ERROR_INVALID_PARAMETER; + auto req = convert(request); + auto program = req->getSpecializedProgram(); + + *outProgram = Slang::ComPtr(program).detach(); + return SLANG_OK; +} + SLANG_API SlangReflection* spGetReflection( SlangCompileRequest* request) { diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis index f19ed925b..0bf32c5e7 100644 --- a/source/slang/slang.natvis +++ b/source/slang/slang.natvis @@ -17,16 +17,10 @@ decl ? ($T1*)(decl) : ($T1*)0 "=========================" - substitutions.genericSubstitutions.pointer + substitutions.substitutions.pointer outer.pointer this - - substitutions.globalGenParamSubstitutions.pointer - outer.pointer - this - - substitutions.thisTypeSubstitution @@ -66,10 +60,10 @@ FuncDecl {nameAndLoc} - {{name={(char*)(text.buffer.pointer+1), s}}} + {{name={(char*)(text.m_buffer.pointer+1), s}}} - {{name={(char*)((*name).text.buffer.pointer+1), s} loc={loc.raw}}} + {{name={(char*)((*name).text.m_buffer.pointer+1), s} loc={loc.raw}}} diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index 7c6967f70..251b46b3e 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -297,7 +297,10 @@ - + + + Document + diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index bd9c2f297..3dd2c4a49 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -394,4 +394,9 @@ Source Files + + + Source Files + + \ No newline at end of file diff --git a/tests/compute/interface-shader-param-in-struct.slang b/tests/compute/interface-shader-param-in-struct.slang index 2ffc70c36..b033bd31c 100644 --- a/tests/compute/interface-shader-param-in-struct.slang +++ b/tests/compute/interface-shader-param-in-struct.slang @@ -49,6 +49,9 @@ cbuffer C IRandomNumberGenerationStrategy gStrategy; } +//TEST_INPUT: globalExistentialType MyStrategy +//TEST_INPUT:ubuffer(data=[1 2 4 8], stride=4):dxbinding(1),glbinding(1) + struct Stuff { IModifier modifier; @@ -107,21 +110,5 @@ struct MyModifier : IModifier } } -//TEST_INPUT: globalExistentialType MyStrategy //TEST_INPUT: entryPointExistentialType MyModifier - -// The concrete types we plug in for `gStrategy` and `modifier` -// have buffer resources in them, so we need to assign them -// data. The registers/bindings for these parameters will -// always come after all other shader parameters, and their -// relative order will match the relative order of their -// declarations in the global order that Slang uses for -// assigning bindings (all globals before all entry point parameters). -// -// Here's the data for `gStrategy`: -// -//TEST_INPUT:ubuffer(data=[1 2 4 8], stride=4):dxbinding(1),glbinding(1) -// -// Here's the data for `stuff.modifier`: -// //TEST_INPUT:ubuffer(data=[16 32 64 128], stride=4):dxbinding(2),glbinding(3) diff --git a/tests/compute/interface-shader-param3.slang b/tests/compute/interface-shader-param3.slang index 3c8b24be1..f4f45689c 100644 --- a/tests/compute/interface-shader-param3.slang +++ b/tests/compute/interface-shader-param3.slang @@ -45,17 +45,19 @@ int test( //TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out RWStructuredBuffer gOutputBuffer; -// Note: while we declare `gStrategy` here, there is no matching -// line providing input data at this point. That is partly to -// work around an apparent bug in the test runner, but it is also -// to reflect the fact that this declaration does not cause a -// constant buffer binding to be allocated, because just from -// the declaration of `gStrategy` we cannot tell whether a -// constant buffer binding is even needed (there might be no -// uniform/ordinary data). -// ConstantBuffer gStrategy; +// Note: The current strategy we use for laying out shader +// parameters in the presence of existential/interface types +// is to always put global-scope parameters before any +// entry-point parameters. As a result we need to provide +// the buffer for the specialized version of `gStrategy` +// here, and we will go ahead and provide the concrete +// type argument at the same time. +// +//TEST_INPUT: globalExistentialType MyStrategy +//TEST_INPUT:cbuffer(data=[1 0 0 0], stride=4):dxbinding(0),glbinding(1) + [numthreads(4, 1, 1)] void computeMain( @@ -81,7 +83,7 @@ void computeMain( // // Here's the incantation to make the test runner fill in the constant buffer: // -//TEST_INPUT:cbuffer(data=[256 0 0 0 16 0 0 0], stride=4):dxbinding(0),glbinding(1) +//TEST_INPUT:cbuffer(data=[256 0 0 0 16 0 0 0], stride=4):dxbinding(1),glbinding(2) // // So, the value `256` will be used for `extra` and the value `16` // will be written to the first four bytes of the concrete value @@ -134,16 +136,4 @@ struct MyModifier : IModifier } } -//TEST_INPUT: globalExistentialType MyStrategy //TEST_INPUT: entryPointExistentialType MyModifier - -// Once the concrete types are plugged in, the compiler can -// see that `gStrategy` needs a constant buffer register/binding. -// It will alocate the location for `gStrategy` after all other -// shader parameters, to ensure that different specializations -// of the same shader will agree on the locations for all the -// non-interface-type parameters. -// -//TEST_INPUT:cbuffer(data=[1 0 0 0], stride=4):dxbinding(1),glbinding(2) - - diff --git a/tests/compute/interface-shader-param4.slang b/tests/compute/interface-shader-param4.slang index 07c6951e0..51b6686ef 100644 --- a/tests/compute/interface-shader-param4.slang +++ b/tests/compute/interface-shader-param4.slang @@ -48,6 +48,18 @@ RWStructuredBuffer gOutputBuffer; ConstantBuffer gStrategy; +// The concrete types we plug in for `gStrategy` and `modifier` +// have buffer resources in them, so we need to assign them +// data. The registers/bindings for these parameters will +// always come after all other shader parameters in the same +// scope (global or entry-point). +// +// Here's the data for `gStrategy`: +// +//TEST_INPUT: globalExistentialType MyStrategy +//TEST_INPUT:ubuffer(data=[1 2 4 8], stride=4):dxbinding(1),glbinding(1) + + [numthreads(4, 1, 1)] void computeMain( @@ -112,21 +124,7 @@ struct MyModifier : IModifier } } -//TEST_INPUT: globalExistentialType MyStrategy -//TEST_INPUT: entryPointExistentialType MyModifier - -// The concrete types we plug in for `gStrategy` and `modifier` -// have buffer resources in them, so we need to assign them -// data. The registers/bindings for these parameters will -// always come after all other shader parameters, and their -// relative order will match the relative order of their -// declarations in the global order that Slang uses for -// assigning bindings (all globals before all entry point parameters). -// -// Here's the data for `gStrategy`: -// -//TEST_INPUT:ubuffer(data=[1 2 4 8], stride=4):dxbinding(1),glbinding(1) -// // Here's the data for `modifier`: // +//TEST_INPUT: entryPointExistentialType MyModifier //TEST_INPUT:ubuffer(data=[16 32 64 128], stride=4):dxbinding(2),glbinding(3) diff --git a/tests/reflection/parameter-block-explicit-space.slang.expected b/tests/reflection/parameter-block-explicit-space.slang.expected index 04093f4ca..f7944c644 100644 --- a/tests/reflection/parameter-block-explicit-space.slang.expected +++ b/tests/reflection/parameter-block-explicit-space.slang.expected @@ -7,7 +7,7 @@ standard output = { { "name": "a", "bindings": [ - {"kind": "constantBuffer", "space": 2, "index": 0, "count": 0}, + {"kind": "constantBuffer", "index": 0, "count": 0}, {"kind": "registerSpace", "index": 2} ], "type": { @@ -58,7 +58,7 @@ standard output = { { "name": "b", "bindings": [ - {"kind": "constantBuffer", "space": 3, "index": 0, "count": 0}, + {"kind": "constantBuffer", "index": 0, "count": 0}, {"kind": "registerSpace", "index": 3} ], "type": { -- cgit v1.2.3