summaryrefslogtreecommitdiffstats
path: root/source/slang/parameter-binding.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2019-02-15 09:08:19 -0800
committerGitHub <noreply@github.com>2019-02-15 09:08:19 -0800
commita3fd4e2bc40cfc77db953b14744c30e7a18e7c1d (patch)
tree5c226a6a4304086412c051f642a5f45fb043083c /source/slang/parameter-binding.cpp
parent4cd317bcae0a13dc2bbb78448c8d60cd1dcc76bd (diff)
Split front- and back-ends (#846)
* Split front- and back-ends This change is a major refactor of several of the types that provide the behind-the-scenes implementation of the public C API. The goal of this refactor is primarily to allow for future API services that let the user operate both the front- and back-ends of the compiler in a more complex fashion. For example, as user should be able to compile a bunch of source code into modules, look up types, functions, etc. in those modules, specialize generic types/functions to the types they've looked up, and then finally request target code to be gernerated for specialized entry points. The back-end code generation they trigger should re-use the front-end compilation work (parsing, semantic checking, IR generation) that was already performed. The most visible change is that `CompileRequest` has been split up into several smaller types that take responsibility for parts of what it did: * The `Linkage` type owns the storage for `import`ed modules, and well as the `TargetRequest`s that represent code-generation targets. The intention is that an application could use a single `Linkage` for the duration of its runtime (so long as it was okay with the memory usage), so that each `import`ed module only gets loaded once. For now, this type needs to manage the search paths, file system, and source manager, because of its responsibility for loading files. * A `FrontEndCompileRequest` owns the stuff related to parsing, semantic checking, and initial IR generation. This most notably includes the `TranslationUnitRequest`s and the `FrontEndEntryPointRequest`s (which used to be just `EntryPointRequest`s). It's main job is to produce AST and IR modules for each translation unit, and to find and validate the entry points. The front-end request does *not* interact with generic arguments for global or entry-point generic parameters. * The main output of both `import` operations and front-end translation units is the `Module` type, which is just a simple container for both the AST module (to service the reflection/layout APIs, and also for semantic checking of code that `import`s the module) and the IR module (for linking and code generation). This type captures the commonalities between the old `LoadedModule` (which is now just an alias for `Module`) and `TranslationUnitRequest` (which now owns a `Module`). * The secondary output of front-end compilation is a `Program`, which comprises a list of referenced `Module`s and validated `EntryPoint`s that will be used together. Layout and code generation both need a `Program` to tell them what modules and entry points will be used together (we don't want to just code-gen everythin that has ever been loaded into the linakge). The `Program`s created by the front-end do not include generic arguments, so they may provide incomplete layout information and/or be unsuitable for code generation. * A `BackEndCompileRequest` owns stuff related to turning a `Program` into output kernels for the targets of a `Linkage`. Most of the data it owns beyond the `Program` to be compiled is minor, so this is a good candidate for demotion from a heap-allocated object to just a `struct` of options that gets passed around. * The `CompileRequestBase` type is an attempt to wrap up the common functionality of both front-end and back-end compile requests. Most of it is just exposing the availability of a linkage and `DiagnosticSink`, so this type is a good candidate for subsequent removal. The main interesting thing it has is the flags related to dumping and validation of IR, so there is probably a good refactoring still to be made around deciding how options should be handled going forward. * Behind the scenes, the `Program` type is set up to handle some level of on-line compilation and layout work. The `Program` knows the `Linkage` it belongs to, and allows for a `TargetProgram` to be looked up based on a specific `TargetRequest`. A `TargetProgram` then allows layout information and compiled kernel code to be asked for on-demand, in order to support eventual "live" compilation scenarios. * The `EndToEndCompileRequest` type is a composition/coordination type that replaces the old `CompileRequest` in a way that uses the services of the various other types. It owns a few pieces of state that only make sense in the context of an end-to-end compile (e.g., there is really no way to "pass through" code when the front- and back-ends are run separately) or a command-line compile (everything to do with specifying output paths for files is really just for the benefit of `slangc`, and might even be moved there over time). * One important detail is that the `EndToEndCompilRequest` owns all of the string-based generic arguments for both global and entry-point generic parameters. The logic in `check.cpp` for dealing with those arguments has been heavily refactored to separate out the parsings steps that are specific to end-to-end compilation with string-based type arguments, and the semantic checking steps that result in a specialized `Program` (which can be exposed through new APIs that aren't tied to end-to-end compilation). It is perhaps not surprising that this change had a lot of consequences, so I'll briefly run over some of the main categories of changes required: * I changed the way that global generic arguments are passed via API (use `spSetGlobalGenericArgs` instead of the generic arguments for `spAddEntryPointEx`, which are not just for entry-point generics), which has been a change that we've needed for a long time. This is technically a breaking API change, although we should have very few client applications that care about it. * A bunch of places that used to take "big" objects like `CompileRequest` now just take the sub-pieces they care about (e.g., a function might have only needed a `Linkage` and a `DiagnosticSink`). This makes many subroutines or "context" struct types more generally useful, at the cost of taking more parameters. * In a few cases the conceptually clean separation of the layers breaks down (often for edge-case or compatibility features), and so we may pass along additional objects that are allowed to be null, but are used when present. A big example of this is how the back-end code generation routines accept an `EndToEndCompileRequest` that is optional, and only used to check whether "pass through" compilation is needed. We should probably look into cleaning this kind of logic up over time so that we don't need to violate the apparent separation of phases of compilation. * In cases where separation of layers was being broken for the sake of GLSL features, I went ahead and ripped them out, since all of that should be dead code anyway. * In many cases I increased the encapsulation of data in the core types to help track down use sites and make sure they are following invariants better. * In cases where code was doing, e.g., `context->shared->compileRequest->session->getThing()` I have tried to introduce convenience routines so that the usage site is just `context->getThing()` to improve encapsulation and allow changes to be made more easily going forward. * The `noteInternalErrorLoc` functionality was moved off of the compile request and into `DiagnosticSink`, since that is the one type you can rely on having around when you want to note an internal error. We may consider going forward if (and how) it should reset the counter used for noting locations on internal errors. * A few APIs now take `DiagnosticSink*` arguments where they didn't before, and as a result some public APIs need to create `DiagnosticSink`s to pass in, before going ahead and ignoring the messages. In the future there should be variations of these APIs that accept an `ISlangBlob**` parameter for the output. * fixup: missing include for compilers with accurate template checking (non-VS) * fixup: review feedback
Diffstat (limited to 'source/slang/parameter-binding.cpp')
-rw-r--r--source/slang/parameter-binding.cpp199
1 files changed, 82 insertions, 117 deletions
diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp
index f0abfe31c..56c5d7c1d 100644
--- a/source/slang/parameter-binding.cpp
+++ b/source/slang/parameter-binding.cpp
@@ -309,9 +309,6 @@ struct ParameterInfo : RefObject
ParameterBindingInfo bindingInfo[kLayoutResourceKindCount];
- // The next parameter that has the same name...
- ParameterInfo* nextOfSameName;
-
// The translation unit this parameter is specific to, if any
TranslationUnitRequest* translationUnit = nullptr;
@@ -335,8 +332,22 @@ struct EntryPointParameterBindingContext
// across all translation units
struct SharedParameterBindingContext
{
- // The base compile request
- CompileRequest* compileRequest;
+ SharedParameterBindingContext(
+ LayoutRulesFamilyImpl* defaultLayoutRules,
+ ProgramLayout* programLayout,
+ TargetRequest* targetReq,
+ DiagnosticSink* sink)
+ : defaultLayoutRules(defaultLayoutRules)
+ , programLayout(programLayout)
+ , targetRequest(targetReq)
+ , m_sink(sink)
+ {
+ }
+
+ DiagnosticSink* m_sink = nullptr;
+
+ // The program that we are laying out
+// Program* program = nullptr;
// The target request that is triggering layout
//
@@ -365,20 +376,18 @@ struct SharedParameterBindingContext
UInt defaultSpace = 0;
TargetRequest* getTargetRequest() { return targetRequest; }
+ DiagnosticSink* getSink() { return m_sink; }
};
static DiagnosticSink* getSink(SharedParameterBindingContext* shared)
{
- return &shared->compileRequest->mSink;
+ return shared->getSink();
}
// State that might be specific to a single translation unit
// or event to an entry point.
struct ParameterBindingContext
{
- // The translation unit we are processing right now
- TranslationUnitRequest* translationUnit;
-
// All the shared state needs to be available
SharedParameterBindingContext* shared;
@@ -386,7 +395,7 @@ struct ParameterBindingContext
// the resource usage of shader parameters.
TypeLayoutContext layoutContext;
- // A dictionary to accellerate looking up parameters by name
+ // A dictionary to accelerate looking up parameters by name
Dictionary<Name*, ParameterInfo*> mapNameToParameterInfo;
// What stage (if any) are we compiling for?
@@ -395,9 +404,6 @@ struct ParameterBindingContext
// The entry point that is being processed right now.
EntryPointLayout* entryPointLayout = nullptr;
- // The source language we are trying to use
- SourceLanguage sourceLanguage;
-
TargetRequest* getTargetRequest() { return shared->getTargetRequest(); }
LayoutRulesFamilyImpl* getRulesFamily() { return layoutContext.getRulesFamily(); }
};
@@ -1217,6 +1223,10 @@ static void collectGlobalScopeParameter(
// If that is the case, we want to re-use the same `VarLayout`
// across both parameters.
//
+ // TODO: This logic currently detects *any* global-scope parameters
+ // with matching names, but it should eventually be narrowly
+ // scoped so that it only applies to parameters from unnamed modules.
+ //
// First we look for an existing entry matching the name
// of this parameter:
auto parameterName = getReflectionName(varDecl);
@@ -2477,7 +2487,7 @@ static ParameterBindingAndKindInfo maybeAllocateConstantBufferBinding(
///
static void collectEntryPointParameters(
ParameterBindingContext* context,
- EntryPointRequest* entryPoint,
+ EntryPoint* entryPoint,
SubstitutionSet typeSubst)
{
DeclRef<FuncDecl> entryPointFuncDeclRef = entryPoint->getFuncDeclRef();
@@ -2486,7 +2496,7 @@ static void collectEntryPointParameters(
// the `EntryPointLayout` object here.
//
RefPtr<EntryPointLayout> entryPointLayout = new EntryPointLayout();
- entryPointLayout->profile = entryPoint->profile;
+ entryPointLayout->profile = entryPoint->getProfile();
entryPointLayout->entryPoint = entryPointFuncDeclRef.getDecl();
// The entry point layout must be added to the output
@@ -2501,10 +2511,10 @@ static void collectEntryPointParameters(
// Note: this isn't really the best place for this logic to sit,
// but it is the simplest place where we have a direct correspondence
- // between a single `EntryPointRequest` and its matching `EntryPointLayout`,
+ // between a single `EntryPoint` and its matching `EntryPointLayout`,
// so we'll use it.
//
- for( auto taggedUnionType : entryPoint->taggedUnionTypes )
+ for( auto taggedUnionType : entryPoint->getTaggedUnionTypes() )
{
SLANG_ASSERT(taggedUnionType);
auto substType = taggedUnionType->Substitute(typeSubst).as<Type>();
@@ -2645,59 +2655,9 @@ static void collectEntryPointParameters(
}
}
-// When doing parameter binding for global-scope stuff in GLSL,
-// we may need to know what stage we are compiling for, so that
-// we can handle special cases appropriately (e.g., "arrayed"
-// inputs and outputs).
-static Stage
-inferStageForTranslationUnit(
- TranslationUnitRequest* translationUnit)
-{
- // In the specific case where we are compiling GLSL input,
- // and have only a single entry point, use the stage
- // of the entry point.
- //
- // TODO: now that we've dropped official GLSL support,
- // we probably should drop this as well.
- //
- if( translationUnit->sourceLanguage == SourceLanguage::GLSL )
- {
- if( translationUnit->entryPoints.Count() == 1 )
- {
- return translationUnit->entryPoints[0]->getStage();
- }
- }
-
- return Stage::Unknown;
-}
-
-static void collectModuleParameters(
- ParameterBindingContext* inContext,
- ModuleDecl* module)
-{
- // Each loaded module provides a separate (logical) namespace for
- // parameters, so that two parameters with the same name, in
- // distinct modules, should yield different bindings.
- //
- ParameterBindingContext contextData = *inContext;
- auto context = &contextData;
-
- context->translationUnit = nullptr;
-
- context->stage = Stage::Unknown;
-
- // All imported modules are implicitly Slang code
- context->sourceLanguage = SourceLanguage::Slang;
-
- // A loaded module cannot define entry points that
- // we'll expose (for now), so we just need to
- // consider global-scope parameters.
- collectGlobalScopeParameters(context, module);
-}
-
static void collectParameters(
ParameterBindingContext* inContext,
- CompileRequest* request)
+ Program* program)
{
// All of the parameters in translation units directly
// referenced in the compile request are part of one
@@ -2707,29 +2667,21 @@ static void collectParameters(
ParameterBindingContext contextData = *inContext;
auto context = &contextData;
- for( auto& translationUnit : request->translationUnits )
+ for(RefPtr<Module> module : program->getModuleDependencies())
{
- context->translationUnit = translationUnit;
- context->stage = inferStageForTranslationUnit(translationUnit.Ptr());
- context->sourceLanguage = translationUnit->sourceLanguage;
+ context->stage = Stage::Unknown;
// First look at global-scope parameters
- collectGlobalScopeParameters(context, translationUnit->SyntaxNode.Ptr());
-
- // Next consider parameters for entry points
- for( auto& entryPoint : translationUnit->entryPoints )
- {
- context->stage = entryPoint->getStage();
- collectEntryPointParameters(context, entryPoint.Ptr(), SubstitutionSet());
- }
- context->entryPointLayout = nullptr;
+ collectGlobalScopeParameters(context, module->getModuleDecl());
}
- // Now collect parameters from loaded modules
- for (auto& loadedModule : request->loadedModulesList)
+ // Next consider parameters for entry points
+ for(auto entryPoint : program->getEntryPoints())
{
- collectModuleParameters(context, loadedModule->moduleDecl.Ptr());
+ context->stage = entryPoint->getStage();
+ collectEntryPointParameters(context, entryPoint, SubstitutionSet());
}
+ context->entryPointLayout = nullptr;
}
/// Emit a diagnostic about a uniform parameter at global scope.
@@ -2770,41 +2722,40 @@ static int _calcTotalNumUsedRegistersForLayoutResourceKind(ParameterBindingConte
return numUsed;
}
-void generateParameterBindings(
- TargetRequest* targetReq)
+RefPtr<ProgramLayout> generateParameterBindings(
+ TargetProgram* targetProgram,
+ DiagnosticSink* sink)
{
- CompileRequest* compileReq = targetReq->compileRequest;
+ auto program = targetProgram->getProgram();
+ auto targetReq = targetProgram->getTargetReq();
+
+ RefPtr<ProgramLayout> programLayout = new ProgramLayout();
+ programLayout->targetProgram = targetProgram;
// Try to find rules based on the selected code-generation target
- auto layoutContext = getInitialLayoutContextForTarget(targetReq);
+ auto layoutContext = getInitialLayoutContextForTarget(targetReq, programLayout);
// If there was no target, or there are no rules for the target,
// then bail out here.
if (!layoutContext.rules)
- return;
-
- RefPtr<ProgramLayout> programLayout = new ProgramLayout();
- programLayout->targetRequest = targetReq;
-
- targetReq->layout = programLayout;
+ return nullptr;
// Create a context to hold shared state during the process
// of generating parameter bindings
- SharedParameterBindingContext sharedContext;
- sharedContext.compileRequest = compileReq;
- sharedContext.defaultLayoutRules = layoutContext.getRulesFamily();
- sharedContext.programLayout = programLayout;
- sharedContext.targetRequest = targetReq;
+ SharedParameterBindingContext sharedContext(
+ layoutContext.getRulesFamily(),
+ programLayout,
+ targetReq,
+ sink);
// Create a sub-context to collect parameters that get
// declared into the global scope
ParameterBindingContext context;
context.shared = &sharedContext;
- context.translationUnit = nullptr;
context.layoutContext = layoutContext;
// Walk through AST to discover all the parameters
- collectParameters(&context, compileReq);
+ collectParameters(&context, program);
// Now walk through the parameters to generate initial binding information
for( auto& parameter : sharedContext.parameters )
@@ -2978,17 +2929,35 @@ void generateParameterBindings(
const int numShaderRecordRegs = _calcTotalNumUsedRegistersForLayoutResourceKind(&context, LayoutResourceKind::ShaderRecord);
if (numShaderRecordRegs > 1)
{
- compileReq->mSink.diagnose(SourceLoc(), Diagnostics::tooManyShaderRecordConstantBuffers, numShaderRecordRegs);
- return;
+ sink->diagnose(SourceLoc(), Diagnostics::tooManyShaderRecordConstantBuffers, numShaderRecordRegs);
}
}
+ return programLayout;
+}
+
+ProgramLayout* TargetProgram::getOrCreateLayout(DiagnosticSink* sink)
+{
+ if( !m_layout )
+ {
+ m_layout = generateParameterBindings(this, sink);
+ }
+ return m_layout;
+}
+
+void generateParameterBindings(
+ Program* program,
+ TargetRequest* targetReq,
+ DiagnosticSink* sink)
+{
+ program->getTargetProgram(targetReq)->getOrCreateLayout(sink);
}
RefPtr<ProgramLayout> specializeProgramLayout(
TargetRequest* targetReq,
ProgramLayout* oldProgramLayout,
- SubstitutionSet typeSubst)
+ SubstitutionSet typeSubst,
+ DiagnosticSink* sink)
{
// The goal of the layout specialization step is to take an existing `ProgramLayout`,
// and add a layout to any parameter(s) that could not be laid out previously, because
@@ -3006,7 +2975,7 @@ RefPtr<ProgramLayout> specializeProgramLayout(
RefPtr<ProgramLayout> newProgramLayout;
newProgramLayout = new ProgramLayout();
- newProgramLayout->targetRequest = targetReq;
+ newProgramLayout->targetProgram = oldProgramLayout->targetProgram;
newProgramLayout->globalGenericParams = oldProgramLayout->globalGenericParams;
// The basic idea will be to iterate over the parameters in the old layout,
@@ -3020,18 +2989,17 @@ RefPtr<ProgramLayout> specializeProgramLayout(
// We will use the same kind of context type as the original parameter binding
// step did, so we initialize its state here:
- auto layoutContext = getInitialLayoutContextForTarget(targetReq);
+ auto layoutContext = getInitialLayoutContextForTarget(targetReq, newProgramLayout);
SLANG_ASSERT(layoutContext.rules);
- SharedParameterBindingContext sharedContext;
- sharedContext.compileRequest = targetReq->compileRequest;
- sharedContext.defaultLayoutRules = layoutContext.getRulesFamily();
- sharedContext.programLayout = newProgramLayout;
- sharedContext.targetRequest = targetReq;
+ SharedParameterBindingContext sharedContext(
+ layoutContext.getRulesFamily(),
+ newProgramLayout,
+ targetReq,
+ sink);
ParameterBindingContext context;
context.shared = &sharedContext;
- context.translationUnit = nullptr;
context.layoutContext = layoutContext;
// We will also need state for laying out any global-scope parameters
@@ -3119,12 +3087,9 @@ RefPtr<ProgramLayout> specializeProgramLayout(
// parameter, the layout of its parameter list strictly follows
// the declaration order.
//
- for (auto & translationUnit : targetReq->compileRequest->translationUnits)
+ for( auto entryPoint : oldProgramLayout->getProgram()->getEntryPoints() )
{
- for (auto & entryPoint : translationUnit->entryPoints)
- {
- collectEntryPointParameters(&context, entryPoint, typeSubst);
- }
+ collectEntryPointParameters(&context, entryPoint, typeSubst);
context.entryPointLayout = nullptr;
}