summaryrefslogtreecommitdiffstats
path: root/source/slang/check.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/check.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/check.cpp')
-rw-r--r--source/slang/check.cpp1041
1 files changed, 578 insertions, 463 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp
index 3485afeea..483db60bb 100644
--- a/source/slang/check.cpp
+++ b/source/slang/check.cpp
@@ -400,22 +400,18 @@ namespace Slang
else
return DeclCheckState::CheckedHeader;
}
- DiagnosticSink* sink = nullptr;
+
+ Linkage* m_linkage = nullptr;
+ DiagnosticSink* m_sink = nullptr;
+
DiagnosticSink* getSink()
{
- return sink;
+ return m_sink;
}
// ModuleDecl * program = nullptr;
FuncDecl * function = nullptr;
- CompileRequest* request = nullptr;
- TranslationUnitRequest* translationUnit = nullptr;
-
- SourceLanguage getSourceLanguage()
- {
- return translationUnit->sourceLanguage;
- }
// lexical outer statements
List<Stmt*> outerStmts;
@@ -429,20 +425,15 @@ namespace Slang
public:
SemanticsVisitor(
- DiagnosticSink* sink,
- CompileRequest* request,
- TranslationUnitRequest* translationUnit)
- : sink(sink)
- , request(request)
- , translationUnit(translationUnit)
- {
- }
+ Linkage* linkage,
+ DiagnosticSink* sink)
+ : m_linkage(linkage)
+ , m_sink(sink)
+ {}
- CompileRequest* getCompileRequest() { return request; }
- TranslationUnitRequest* getTranslationUnit() { return translationUnit; }
Session* getSession()
{
- return getCompileRequest()->mSession;
+ return m_linkage->getSession();
}
public:
@@ -985,7 +976,7 @@ namespace Slang
catch(AbortCompilationException&) { throw; }
catch(...)
{
- getCompileRequest()->noteInternalErrorLoc(decl->loc);
+ getSink()->noteInternalErrorLoc(decl->loc);
throw;
}
}
@@ -998,7 +989,7 @@ namespace Slang
catch(AbortCompilationException&) { throw; }
catch(...)
{
- getCompileRequest()->noteInternalErrorLoc(stmt->loc);
+ getSink()->noteInternalErrorLoc(stmt->loc);
throw;
}
}
@@ -1011,7 +1002,7 @@ namespace Slang
catch(AbortCompilationException&) { throw; }
catch(...)
{
- getCompileRequest()->noteInternalErrorLoc(expr->loc);
+ getSink()->noteInternalErrorLoc(expr->loc);
throw;
}
}
@@ -1030,7 +1021,7 @@ namespace Slang
// being checked on the stack, so that we can report the full
// chain that leads from this declaration back to itself.
//
- sink->diagnose(decl, Diagnostics::cyclicReference, decl);
+ getSink()->diagnose(decl, Diagnostics::cyclicReference, decl);
return;
}
@@ -1050,7 +1041,7 @@ namespace Slang
// TODO: This diagnostic should be emitted on the line that is referencing
// the declaration. That requires `EnsureDecl` to take the requesting
// location as a parameter.
- sink->diagnose(decl, Diagnostics::localVariableUsedBeforeDeclared, decl);
+ getSink()->diagnose(decl, Diagnostics::localVariableUsedBeforeDeclared, decl);
return;
}
}
@@ -3019,7 +3010,7 @@ namespace Slang
checkDecl(func);
}
- if (sink->GetErrorCount() != 0)
+ if (getSink()->GetErrorCount() != 0)
return;
// Force everything to be fully checked, just in case
@@ -4921,9 +4912,12 @@ namespace Slang
return new ConstantIntVal(expr->value);
}
+ Linkage* getLinkage() { return m_linkage; }
+ NamePool* getNamePool() { return getLinkage()->getNamePool(); }
+
Name* getName(String const& text)
{
- return getCompileRequest()->getNamePool()->getName(text);
+ return getNamePool()->getName(text);
}
RefPtr<IntVal> TryConstantFoldExpr(
@@ -5079,58 +5073,18 @@ namespace Slang
{
auto varDecl = varRef.getDecl();
- switch(getSourceLanguage())
+ // In HLSL, `static const` is used to mark compile-time constant expressions
+ if(auto staticAttr = varDecl->FindModifier<HLSLStaticModifier>())
{
- default:
- case SourceLanguage::Slang:
- case SourceLanguage::HLSL:
- // HLSL: `static const` is used to mark compile-time constant expressions
- if(auto staticAttr = varDecl->FindModifier<HLSLStaticModifier>())
- {
- if(auto constAttr = varDecl->FindModifier<ConstModifier>())
- {
- // HLSL `static const` can be used as a constant expression
- if(auto initExpr = getInitExpr(varRef))
- {
- return TryConstantFoldExpr(initExpr.Ptr());
- }
- }
- }
- break;
-
- case SourceLanguage::GLSL:
- // GLSL: `const` indicates compile-time constant expression
- //
- // TODO(tfoley): The current logic here isn't robust against
- // GLSL "specialization constants" - we will extract the
- // initializer for a `const` variable and use it to extract
- // a value, when we really should be using an opaque
- // reference to the variable.
if(auto constAttr = varDecl->FindModifier<ConstModifier>())
{
- // We need to handle a "specialization constant" (with a `constant_id` layout modifier)
- // differently from an ordinary compile-time constant. The latter can/should be reduced
- // to a value, while the former should be kept as a symbolic reference
-
- if(auto constantIDModifier = varDecl->FindModifier<GLSLConstantIDLayoutModifier>())
- {
- // Retain the specialization constant as a symbolic reference
- //
- // TODO(tfoley): handle the case of non-`int` value parameters...
- //
- // TODO(tfoley): this is cloned from the case above that handles generic value parameters
- return new GenericParamIntVal(varRef);
- }
- else if(auto initExpr = getInitExpr(varRef))
+ // HLSL `static const` can be used as a constant expression
+ if(auto initExpr = getInitExpr(varRef))
{
- // This is an ordinary constant, and not a specialization constant, so we
- // can try to fold its value right now.
return TryConstantFoldExpr(initExpr.Ptr());
}
}
- break;
}
-
}
else if(auto enumRef = declRef.as<EnumCaseDecl>())
{
@@ -9060,17 +9014,32 @@ namespace Slang
auto scope = decl->scope;
// Try to load a module matching the name
- auto importedModuleDecl = findOrImportModule(request, name, decl->moduleNameAndLoc.loc);
+ auto importedModule = findOrImportModule(
+ getLinkage(),
+ name,
+ decl->moduleNameAndLoc.loc,
+ getSink());
// If we didn't find a matching module, then bail out
- if (!importedModuleDecl)
+ if (!importedModule)
return;
// Record the module that was imported, so that we can use
// it later during code generation.
+ auto importedModuleDecl = importedModule->getModuleDecl();
decl->importedModuleDecl = importedModuleDecl;
- importModuleIntoScope(scope.Ptr(), importedModuleDecl.Ptr());
+ // Add the declarations from the imported module into the scope
+ // that the `import` declaration is set to extend.
+ //
+ importModuleIntoScope(scope.Ptr(), importedModuleDecl);
+
+ // Record the `import`ed module (and everything it depends on)
+ // as a dependency of the module we are compiling.
+ if(auto module = getModule(decl))
+ {
+ module->addModuleDependency(importedModule);
+ }
decl->SetCheckState(getCheckedState());
}
@@ -9142,29 +9111,25 @@ namespace Slang
return (!decl->primaryDecl) || (decl == decl->primaryDecl);
}
- RefPtr<Type> checkProperType(TranslationUnitRequest * tu, TypeExp typeExp)
+ RefPtr<Type> checkProperType(
+ Linkage* linkage,
+ TypeExp typeExp,
+ DiagnosticSink* sink)
{
- RefPtr<Type> type;
- DiagnosticSink nSink;
- nSink.sourceManager = tu->compileRequest->sourceManager;
SemanticsVisitor visitor(
- &nSink,
- tu->compileRequest,
- tu);
+ linkage,
+ sink);
auto typeOut = visitor.CheckProperType(typeExp);
- if (!nSink.errorCount)
- {
- type = typeOut.type;
- }
- return type;
+ return typeOut.type;
}
- FuncDecl* findFunctionDeclByName(EntryPointRequest* entryPoint, Name* name)
+ FuncDecl* findFunctionDeclByName(
+ Module* translationUnit,
+ Name* name,
+ DiagnosticSink* sink)
{
- auto translationUnit = entryPoint->getTranslationUnit();
- auto sink = &entryPoint->compileRequest->mSink;
- auto translationUnitSyntax = translationUnit->SyntaxNode;
+ auto translationUnitSyntax = translationUnit->getModuleDecl();
// Make sure we've got a query-able member dictionary
buildMemberDictionary(translationUnitSyntax);
@@ -9270,7 +9235,9 @@ namespace Slang
// Validate that an entry point function conforms to any additional
// constraints based on the stage (and profile?) it specifies.
void validateEntryPoint(
- EntryPointRequest* entryPoint)
+ FuncDecl* entryPointFuncDecl,
+ Stage stage,
+ DiagnosticSink* sink)
{
// TODO: We currently do minimal checking here, but this is the
// right place to perform the following validation checks:
@@ -9297,28 +9264,32 @@ namespace Slang
// that function is specific to the fragment profile/stage.
//
- auto sink = &entryPoint->compileRequest->mSink;
+ auto entryPointName = entryPointFuncDecl->getName();
+
+ auto module = getModule(entryPointFuncDecl);
+ auto linkage = module->getLinkage();
+
// Every entry point needs to have a stage specified either via
// command-line/API options, or via an explicit `[shader("...")]` attribute.
//
- if( entryPoint->getStage() == Stage::Unknown )
+ if( stage == Stage::Unknown )
{
- sink->diagnose(entryPoint->getFuncDecl(), Diagnostics::entryPointHasNoStage, entryPoint->name);
+ sink->diagnose(entryPointFuncDecl, Diagnostics::entryPointHasNoStage, entryPointName);
}
- if (entryPoint->getStage() == Stage::Hull)
+ if( stage == Stage::Hull )
{
- auto translationUnit = entryPoint->getTranslationUnit();
- auto translationUnitSyntax = translationUnit->SyntaxNode;
+ // TODO: We could consider *always* checking any `[patchconsantfunc("...")]`
+ // attributes, so that they need to resolve to a function.
- auto attr = entryPoint->getFuncDecl()->FindModifier<PatchConstantFuncAttribute>();
+ auto attr = entryPointFuncDecl->FindModifier<PatchConstantFuncAttribute>();
if (attr)
{
if (attr->args.Count() != 1)
{
- sink->diagnose(translationUnitSyntax, Diagnostics::badlyDefinedPatchConstantFunc, entryPoint->name);
+ sink->diagnose(attr, Diagnostics::badlyDefinedPatchConstantFunc, entryPointName);
return;
}
@@ -9327,40 +9298,52 @@ namespace Slang
if (!stringLit)
{
- sink->diagnose(translationUnitSyntax, Diagnostics::badlyDefinedPatchConstantFunc, entryPoint->name);
+ sink->diagnose(expr, Diagnostics::badlyDefinedPatchConstantFunc, entryPointName);
return;
}
- Name* name = entryPoint->compileRequest->getNamePool()->getName(stringLit->value);
- FuncDecl* funcDecl = findFunctionDeclByName(entryPoint, name);
- if (!funcDecl)
+ // We look up the patch-constant function by its name in the module
+ // scope of the translation unit that declared the HS entry point.
+ //
+ // TODO: Eventually we probably want to do the lookup in the scope
+ // of the parent declarations of the entry point. E.g., if the entry
+ // point is a member function of a `struct`, then its patch-constant
+ // function should be allowed to be another member function of
+ // the same `struct`.
+ //
+ // In the extremely long run we may want to support an alternative to
+ // this attribute-based linkage between the two functions that
+ // make up the entry point.
+ //
+ Name* name = linkage->getNamePool()->getName(stringLit->value);
+ FuncDecl* patchConstantFuncDecl = findFunctionDeclByName(
+ module,
+ name,
+ sink);
+ if (!patchConstantFuncDecl)
{
- sink->diagnose(translationUnitSyntax, Diagnostics::attributeFunctionNotFound, name, "patchconstantfunc");
+ sink->diagnose(expr, Diagnostics::attributeFunctionNotFound, name, "patchconstantfunc");
return;
}
- attr->patchConstantFuncDecl = funcDecl;
+ attr->patchConstantFuncDecl = patchConstantFuncDecl;
}
}
- else if (entryPoint->getStage() == Stage::Compute)
+ else if(stage == Stage::Compute)
{
- auto funcDecl = entryPoint->getFuncDecl();
-
- auto params = funcDecl->GetParameters();
-
- for (const auto& param : params)
+ for(const auto& param : entryPointFuncDecl->GetParameters())
{
- if (auto semantic = param->FindModifier<HLSLSimpleSemantic>())
+ if(auto semantic = param->FindModifier<HLSLSimpleSemantic>())
{
const auto& semanticToken = semantic->name;
String lowerName = String(semanticToken.Content).ToLower();
- if (lowerName == "sv_dispatchthreadid")
+ if(lowerName == "sv_dispatchthreadid")
{
Type* paramType = param->getType();
- if (!isValidThreadDispatchIDType(paramType))
+ if(!isValidThreadDispatchIDType(paramType))
{
String typeString = paramType->ToString();
sink->diagnose(param->loc, Diagnostics::invalidDispatchThreadIDType, typeString);
@@ -9372,26 +9355,30 @@ namespace Slang
}
}
- // Given an `EntryPointRequest` specified via API or command line options,
+ // Given an entry point specified via API or command line options,
// attempt to find a matching AST declaration that implements the specified
// entry point. If such a function is found, then validate that it actually
// meets the requirements for the selected stage/profile.
//
- void findAndValidateEntryPoint(
- EntryPointRequest* entryPoint)
+ // Returns an `EntryPoint` object representing the (unspecialized)
+ // entry point if it is found and validated, and null otherwise.
+ //
+ RefPtr<EntryPoint> findAndValidateEntryPoint(
+ FrontEndEntryPointRequest* entryPointReq)
{
// The first step in validating the entry point is to find
// the (unique) function declaration that matches its name.
//
- // TODO: We will eventually need to update this logic
- // to work by parsing the provided `entryPoint->name` string
- // as an expression, so that we can handle more complex
- // names like `foo<int>` or `SomeType.vs`.
-
- auto translationUnit = entryPoint->getTranslationUnit();
- auto sink = &entryPoint->compileRequest->mSink;
- auto translationUnitSyntax = translationUnit->SyntaxNode;
+ // TODO: We may eventually want/need to extend this to
+ // account for nested names like `SomeStruct.vsMain`, or
+ // indeed even to handle generics.
+ //
+ auto compileRequest = entryPointReq->getCompileRequest();
+ auto translationUnit = entryPointReq->getTranslationUnit();
+ auto sink = compileRequest->getSink();
+ auto translationUnitSyntax = translationUnit->getModuleDecl();
+ auto entryPointName = entryPointReq->getName();
// Make sure we've got a query-able member dictionary
buildMemberDictionary(translationUnitSyntax);
@@ -9399,12 +9386,12 @@ namespace Slang
// We will look up any global-scope declarations in the translation
// unit that match the name of our entry point.
Decl* firstDeclWithName = nullptr;
- if( !translationUnitSyntax->memberDictionary.TryGetValue(entryPoint->name, firstDeclWithName) )
+ if( !translationUnitSyntax->memberDictionary.TryGetValue(entryPointName, firstDeclWithName) )
{
// If there doesn't appear to be any such declaration, then
// we need to diagnose it as an error, and then bail out.
- sink->diagnose(translationUnitSyntax, Diagnostics::entryPointFunctionNotFound, entryPoint->name);
- return;
+ sink->diagnose(translationUnitSyntax, Diagnostics::entryPointFunctionNotFound, entryPointName);
+ return nullptr;
}
// We found at least one global-scope declaration with the right name,
@@ -9448,7 +9435,7 @@ namespace Slang
// name before, so the whole thing is ambiguous. We need
// to diagnose and bail out.
- sink->diagnose(translationUnitSyntax, Diagnostics::ambiguousEntryPoint, entryPoint->name);
+ sink->diagnose(translationUnitSyntax, Diagnostics::ambiguousEntryPoint, entryPointName);
// List all of the declarations that the user *might* mean
for (auto ff = firstDeclWithName; ff; ff = ff->nextInContainerWithSameName)
@@ -9460,7 +9447,7 @@ namespace Slang
}
// Bail out.
- return;
+ return nullptr;
}
}
}
@@ -9471,127 +9458,197 @@ namespace Slang
// If not, then we need to diagnose the error.
// For convenience, we will point to the first
// declaration with the right name, that wasn't a function.
- sink->diagnose(firstDeclWithName, Diagnostics::entryPointSymbolNotAFunction, entryPoint->name);
- return;
+ sink->diagnose(firstDeclWithName, Diagnostics::entryPointSymbolNotAFunction, entryPointName);
+ return nullptr;
}
+ // TODO: it is possible that the entry point was declared with
+ // profile or target overloading. Is there anything that we need
+ // to do at this point to filter out declarations that aren't
+ // relevant to the selected profile for the entry point?
+
+ // We found something, and can start doing some basic checking.
+ //
// If the entry point specifies a stage via a `[shader("...")]` attribute,
// then we might be able to infer a stage for the entry point request if
// it didn't have one, *or* issue a diagnostic if there is a mismatch.
//
+ auto entryPointProfile = entryPointReq->getProfile();
if( auto entryPointAttribute = entryPointFuncDecl->FindModifier<EntryPointAttribute>() )
{
- if( entryPoint->getStage() == Stage::Unknown )
+ auto entryPointStage = entryPointProfile.GetStage();
+ if( entryPointStage == Stage::Unknown )
{
- entryPoint->profile.setStage(entryPointAttribute->stage);
+ entryPointProfile.setStage(entryPointAttribute->stage);
}
- else if( entryPointAttribute->stage != entryPoint->getStage() )
+ else if( entryPointAttribute->stage != entryPointStage )
{
- sink->diagnose(entryPointFuncDecl, Diagnostics::specifiedStageDoesntMatchAttribute, entryPoint->name, entryPoint->getStage(), entryPointAttribute->stage);
+ sink->diagnose(entryPointFuncDecl, Diagnostics::specifiedStageDoesntMatchAttribute, entryPointName, entryPointStage, entryPointAttribute->stage);
}
}
+ else
+ {
+ // TODO: Should we attach a `[shader(...)]` attribute to an
+ // entry point that didn't have one, so that we can have
+ // a more uniform representation in the AST?
+ }
- // TODO: it is possible that the entry point was declared with
- // profile or target overloading. Is there anything that we need
- // to do at this point to filter out declarations that aren't
- // relevant to the selected profile for the entry point?
- // Phew, we have at least found a suitable decl.
- // Let's record that in the entry-point request so
- // that we don't have to re-do this effort again later.
- //
- // Note: we may replace the decl-ref we store at this point
- // later in this function, when we (potentially) specialize
- // a generic entry point to generic arguments provided
- // via the API.
+ // Now that we've *found* the entry point, it is time to validate
+ // that it actually meets the constraints for the chosen stage/profile.
//
- entryPoint->funcDeclRef = makeDeclRef(entryPointFuncDecl);
+ validateEntryPoint(
+ entryPointFuncDecl,
+ entryPointProfile.GetStage(),
+ sink);
- // If the user specified generic arguments for the entry point,
- // then we will want to parse those arguments as expressions
- // in a scope that includes the tanslation unit that holds
- // the entry point, as well as any other modules that got
- // transitively loaded via `import`.
- //
- // TODO: This would be better handled by giving the user
- // more explicit ways to parse/build types at the API level,
- // rather than keeping things string-based this far along.
+ RefPtr<EntryPoint> entryPoint = EntryPoint::create(
+ makeDeclRef(entryPointFuncDecl),
+ entryPointProfile);
+
+ return entryPoint;
+ }
+
+ /// Create a `Program` to represent the compiled code.
+ ///
+ /// The created program will comprise all of the translation
+ /// units that were compiled as part of the request, as
+ /// well as any entry points in those translation units.
+ ///
+ RefPtr<Program> createUnspecializedProgram(
+ FrontEndCompileRequest* compileRequest)
+ {
+ // We want our resulting program to depend on
+ // all the translation units the user specified,
+ // even if some of them don't contain entry points
+ // (this is important for parameter layout/binding).
//
- // TODO: Building a list of `scopesToTry` here shouldn't
- // be required, since the `Scope` type itself has the ability
- // for form chains for lookup purposes (e.g., the way that
- // `import` is handled by modifying a scope).
+ // We also want to ensure that the modules for the
+ // translation units comes first in the enumerated
+ // order for dependencies, to match the pre-existing
+ // compiler behavior (at least for now).
//
- List<RefPtr<Scope>> scopesToTry;
- scopesToTry.Add(entryPoint->getTranslationUnit()->SyntaxNode->scope);
- for (auto & module : entryPoint->compileRequest->loadedModulesList)
- scopesToTry.Add(module->moduleDecl->scope);
+ auto linkage = compileRequest->getLinkage();
+ auto sink = compileRequest->getSink();
+ auto program = new Program(linkage);
+ for(auto translationUnit : compileRequest->translationUnits )
+ {
+ program->addReferencedLeafModule(translationUnit->getModule());
+ }
+ for(auto translationUnit : compileRequest->translationUnits )
+ {
+ program->addReferencedModule(translationUnit->getModule());
+ }
- // We are going to do some semantic checking, so we need to
- // set up a `SemanticsVistitor` that we can use.
- //
- SemanticsVisitor semantics(
- &entryPoint->compileRequest->mSink,
- entryPoint->compileRequest,
- entryPoint->getTranslationUnit());
- // We will be looping over the generic argument strings
- // that the user provided via the API (or command line),
- // and parsing+checking each into an `Expr`.
+ // The validation of entry points here will be modal, and controlled
+ // by whether the user specified any entry points directly via
+ // API or command-line options.
//
- // This loop will *not* handle coercing the arguments
- // to be types.
+ // TODO: We may want to make this choice explicit rather than implicit.
//
- List<RefPtr<Expr>> genericArgs;
- for (auto name : entryPoint->genericArgStrings)
+ // First, check if the user requested any entry points explicitly via
+ // the API or command line.
+ //
+ bool anyExplicitEntryPoints = compileRequest->getEntryPointReqCount() != 0;
+
+ if( anyExplicitEntryPoints )
{
- RefPtr<Expr> argExpr;
- for (auto & s : scopesToTry)
+ // If there were any explicit requests for entry points to be
+ // checked, then we will *only* check those.
+ //
+ for(auto entryPointReq : compileRequest->getEntryPointReqs())
{
- argExpr = entryPoint->compileRequest->parseTypeString(
- entryPoint->getTranslationUnit(),
- name,
- s);
- argExpr = semantics.CheckTerm(argExpr);
- if( argExpr )
+ auto entryPoint = findAndValidateEntryPoint(
+ entryPointReq);
+ if( entryPoint )
{
- break;
+ program->addEntryPoint(entryPoint);
+ entryPointReq->getTranslationUnit()->entryPoints.Add(entryPoint);
}
}
- // The following is a bit of a hack.
- //
- // Back-end code generation relies on us having computed layouts for all tagged
- // unions that end up being used in the code, which means we need a way to find
- // all such types that get used in a module (and the stuff it imports).
+ // TODO: We should consider always processing both categories,
+ // and just making sure to only check each entry point function
+ // declaration once...
+ }
+ else
+ {
+ // Otherwise, scan for any `[shader(...)]` attributes in
+ // the user's code, and construct `EntryPoint`s to
+ // represent them.
//
- // The Right Way to handle this would probably be to have each `ModuleDecl` track
- // any tagged union types that get created in the context of that module, and
- // then combine those lists later.
+ // This ensures that downstream code only has to consider
+ // the central list of entry point requests, and doesn't
+ // have to know where they came from.
+
+ // TODO: A comprehensive approach here would need to search
+ // recursively for entry points, because they might appear
+ // as, e.g., member function of a `struct` type.
//
- // For now we are assuming a tagged union type only comes into existence
- // as a (top-level) argument for a generic type parameter, so that we
- // can check for them here and cache them on the entry point request.
+ // For now we'll start with an extremely basic approach that
+ // should work for typical HLSL code.
//
- if( auto typeType = as<TypeType>(argExpr->type) )
+ UInt translationUnitCount = compileRequest->translationUnits.Count();
+ for(UInt tt = 0; tt < translationUnitCount; ++tt)
{
- auto type = typeType->type;
- if( auto taggedUnionType = as<TaggedUnionType>(type) )
+ auto translationUnit = compileRequest->translationUnits[tt];
+ for( auto globalDecl : translationUnit->getModuleDecl()->Members )
{
- entryPoint->taggedUnionTypes.Add(taggedUnionType);
+ auto maybeFuncDecl = globalDecl;
+ if( auto genericDecl = as<GenericDecl>(maybeFuncDecl) )
+ {
+ maybeFuncDecl = genericDecl->inner;
+ }
+
+ auto funcDecl = as<FuncDecl>(maybeFuncDecl);
+ if(!funcDecl)
+ continue;
+
+ auto entryPointAttr = funcDecl->FindModifier<EntryPointAttribute>();
+ if(!entryPointAttr)
+ continue;
+
+ // We've discovered a valid entry point. It is a function (possibly
+ // generic) that has a `[shader(...)]` attribute to mark it as an
+ // entry point.
+ //
+ // We will now register that entry point as an `EntryPoint`
+ // with an appropriately chosen profile.
+ //
+ // The profile will only include a stage, so that the profile "family"
+ // and "version" are left unspecified. Downstream code will need
+ // to be able to handle this case.
+ //
+ Profile profile;
+ profile.setStage(entryPointAttr->stage);
+
+ validateEntryPoint(funcDecl, entryPointAttr->stage, sink);
+
+ RefPtr<EntryPoint> entryPoint = EntryPoint::create(
+ makeDeclRef(funcDecl),
+ profile);
+ program->addEntryPoint(entryPoint);
+ translationUnit->entryPoints.Add(entryPoint);
}
}
-
- genericArgs.Add(argExpr);
}
- // There are two cases we care about here, and we are going to treat them
- // as mutually exclusive for simplicity.
- //
- // The first case is when the entry point function is itself generic,
- // in which case we will assume that `genericArgs` lines up one-to-one
- // with the explicit generic parameters of the entry point.
- //
+ return program;
+ }
+
+ /// Create a specialization an existing entry point based on generic arguments.
+ DeclRef<FuncDecl> specializeEntryPoint(
+ Linkage* linkage,
+ FuncDecl* entryPointFuncDecl,
+ List<RefPtr<Expr>> const& genericArgs,
+ DiagnosticSink* sink)
+ {
+ SemanticsVisitor semantics(
+ linkage,
+ sink);
+
+ DeclRef<FuncDecl> entryPointFuncDeclRef = makeDeclRef(entryPointFuncDecl);
if( auto genericDecl = as<GenericDecl>(entryPointFuncDecl->ParentDecl) )
{
// We will construct a suitable `GenericAppExpr` to represent
@@ -9601,7 +9658,7 @@ namespace Slang
// generic application like `F<A,B,C>` if it were
// encountered in the source code.
- auto session = entryPoint->compileRequest->mSession;
+ auto session = linkage->getSession();
auto genericDeclRef = makeDeclRef(genericDecl);
// The first pieces is a `VarExpr` that refers to `genericDecl`.
@@ -9639,12 +9696,13 @@ namespace Slang
// The basic `VarExpr` and `StaticMemberExpr` cases
// should be allow-able.
- entryPoint->funcDeclRef = declRefExpr->declRef.as<FuncDecl>();
+ entryPointFuncDeclRef = declRefExpr->declRef.as<FuncDecl>();
}
else if( semantics.IsErrorExpr(checkedExpr) )
{
// Any semantic error that occured should have been
// reported already.
+ return DeclRef<FuncDecl>();
}
else
{
@@ -9652,302 +9710,359 @@ namespace Slang
// function should always be a `DeclRefExpr`
//
SLANG_UNEXPECTED("reference to generic decl wasn't a `DeclRefExpr`");
+ UNREACHABLE_RETURN(DeclRef<FuncDecl>());
}
}
- else
- {
- // The other case is when the entry point function is *not* itself
- // generic, so we assume that any generic arguments must have been intended
- // to match up with global generic parameters instead.
- //
- // We will only validate global generic type arguments when we are going
- // to generate code, since in a no-codegen pass we will typically *not*
- // have arguments to associate with the parameters.
- //
- if ((entryPoint->compileRequest->compileFlags & SLANG_COMPILE_FLAG_NO_CODEGEN) == 0)
- {
- // check that user-provioded type arguments conforms to the generic type
- // parameter declaration of this translation unit
- // collect global generic parameters from all imported modules
- List<RefPtr<GlobalGenericParamDecl>> globalGenericParams;
- // add current translation unit first
- {
- auto globalGenParams = translationUnit->SyntaxNode->getMembersOfType<GlobalGenericParamDecl>();
- for (auto p : globalGenParams)
- globalGenericParams.Add(p);
- }
- // add imported modules
- for (auto loadedModule : entryPoint->compileRequest->loadedModulesList)
- {
- auto moduleDecl = loadedModule->moduleDecl;
- auto globalGenParams = moduleDecl->getMembersOfType<GlobalGenericParamDecl>();
- for (auto p : globalGenParams)
- globalGenericParams.Add(p);
- }
-
- if (globalGenericParams.Count() != genericArgs.Count())
- {
- sink->diagnose(entryPoint->getFuncDecl(), Diagnostics::mismatchEntryPointTypeArgument,
- globalGenericParams.Count(),
- genericArgs.Count());
- return;
- }
-
- // We have an appropriate number of arguments for the global generic parameters,
- // and now we need to check that the arguments conform to the declared constraints.
- //
- // Along the way, we will build up an appropriate set of substitutions to represent
- // the generic arguments and their conformances.
- //
- RefPtr<Substitutions> globalGenericSubsts;
- auto globalGenericSubstLink = &globalGenericSubsts;
- //
- // TODO: There is a serious flaw to this checking logic if we ever have cases where
- // the constraints on one `type_param` can depend on another `type_param`, e.g.:
- //
- // type_param A;
- // type_param B : ISidekick<A>;
- //
- // In that case, if a user tries to set `B` to `Robin` and `Robin` conforms to
- // `ISidekick<Batman>`, then the compiler needs to know whether `A` is being
- // set to `Batman` to know whether the setting for `B` is valid. In this limit
- // the constraints can be mutually recursive (so `A : IMentor<B>`).
- //
- // The only way to check things correctly is to validate each conformance under
- // a set of assumptions (substitutions) that includes all the type substitutions,
- // and possibly also all the other constraints *except* the one to be validated.
- //
- // We will punt on this for now, and just check each constraint in isolation.
- //
- UInt argCounter = 0;
- for(auto& globalGenericParam : globalGenericParams)
- {
- // Get the argument that matches this parameter.
- UInt argIndex = argCounter++;
- SLANG_ASSERT(argIndex < genericArgs.Count());
- auto globalGenericArg = checkProperType(translationUnit, TypeExp(genericArgs[argIndex]));
- if (!globalGenericArg)
- {
- sink->diagnose(firstDeclWithName, Diagnostics::entryPointTypeSymbolNotAType, entryPoint->genericArgStrings[argIndex]);
- return;
- }
+ return entryPointFuncDeclRef;
+ }
- // As a quick sanity check, see if the argument that is being supplied for a parameter
- // is just the parameter itself, because this should always be an error:
- //
- if( auto argDeclRefType = globalGenericArg.as<DeclRefType>() )
- {
- auto argDeclRef = argDeclRefType->declRef;
- if(auto argGenericParamDeclRef = argDeclRef.as<GlobalGenericParamDecl>())
- {
- if(argGenericParamDeclRef.getDecl() == globalGenericParam)
- {
- // We are trying to specialize a generic parameter using itself.
- sink->diagnose(globalGenericParam,
- Diagnostics::cannotSpecializeGlobalGenericToItself,
- globalGenericParam->getName());
- sink->diagnose(entryPointFuncDecl,
- Diagnostics::noteWhenCompilingEntryPoint,
- entryPointFuncDecl->getName());
- continue;
- }
- else
- {
- // We are trying to specialize a generic parameter using a *different*
- // global generic type parameter.
- sink->diagnose(globalGenericParam,
- Diagnostics::cannotSpecializeGlobalGenericToAnotherGenericParam,
- globalGenericParam->getName(),
- argGenericParamDeclRef.GetName());
- sink->diagnose(entryPointFuncDecl,
- Diagnostics::noteWhenCompilingEntryPoint,
- entryPointFuncDecl->getName());
- continue;
- }
- }
- }
+ /// Parse an array of strings as generic arguments.
+ ///
+ /// Names in the strings will be parsed in the context of
+ /// the code loaded into the given compile request.
+ ///
+ void parseGenericArgStrings(
+ EndToEndCompileRequest* endToEndReq,
+ List<String> const& genericArgStrings,
+ List<RefPtr<Expr>>& outGenericArgs)
+ {
+ auto unspecialiedProgram = endToEndReq->getUnspecializedProgram();
- // Create a substitution for this parameter/argument.
- RefPtr<GlobalGenericParamSubstitution> subst = new GlobalGenericParamSubstitution();
- subst->paramDecl = globalGenericParam;
- subst->actualType = globalGenericArg;
+ // TODO: Building a list of `scopesToTry` here shouldn't
+ // be required, since the `Scope` type itself has the ability
+ // for form chains for lookup purposes (e.g., the way that
+ // `import` is handled by modifying a scope).
+ //
+ List<RefPtr<Scope>> scopesToTry;
+ for( auto module : unspecialiedProgram->getModuleDependencies() )
+ scopesToTry.Add(module->getModuleDecl()->scope);
- // Walk through the declared constraints for the parameter,
- // and check that the argument actually satisfies them.
- for(auto constraint : globalGenericParam->getMembersOfType<GenericTypeConstraintDecl>())
- {
- // Get the type that the constraint is enforcing conformance to
- auto interfaceType = GetSup(DeclRef<GenericTypeConstraintDecl>(constraint, nullptr));
+ // We are going to do some semantic checking, so we need to
+ // set up a `SemanticsVistitor` that we can use.
+ //
+ auto linkage = endToEndReq->getLinkage();
+ auto sink = endToEndReq->getSink();
+ SemanticsVisitor semantics(
+ linkage,
+ sink);
- // Use our semantic-checking logic to search for a witness to the required conformance
- SemanticsVisitor visitor(sink, entryPoint->compileRequest, translationUnit);
- auto witness = visitor.tryGetSubtypeWitness(globalGenericArg, interfaceType);
- if (!witness)
- {
- // If no witness was found, then we will be unable to satisfy
- // the conformances required.
- sink->diagnose(globalGenericParam,
- Diagnostics::typeArgumentDoesNotConformToInterface,
- globalGenericParam->nameAndLoc.name,
- globalGenericArg,
- interfaceType);
- }
+ // We will be looping over the generic argument strings
+ // that the user provided via the API (or command line),
+ // and parsing+checking each into an `Expr`.
+ //
+ // This loop will *not* handle coercing the arguments
+ // to be types.
+ //
+ for(auto name : genericArgStrings)
+ {
+ RefPtr<Expr> argExpr;
+ for (auto & s : scopesToTry)
+ {
+ argExpr = linkage->parseTypeString(name, s);
+ argExpr = semantics.CheckTerm(argExpr);
+ if( argExpr )
+ {
+ break;
+ }
+ }
- // Attach the concrete witness for this conformance to the
- // substutiton
- GlobalGenericParamSubstitution::ConstraintArg constraintArg;
- constraintArg.decl = constraint;
- constraintArg.val = witness;
- subst->constraintArgs.Add(constraintArg);
- }
+ outGenericArgs.Add(argExpr);
+ }
+ }
- // Add the substitution for this parameter to the global substitution
- // set that we are building.
+ /// Specialize a program to global generic arguments
+ RefPtr<Program> createSpecializedProgram(
+ Linkage* linkage,
+ Program* unspecializedProgram,
+ List<RefPtr<Expr>> const& globalGenericArgs,
+ DiagnosticSink* sink)
+ {
+ // 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
+ // the requirements of the global generic parameters.
+ //
+ // The new program needs to start off with the same
+ // module dependency list as the original.
+ //
+ RefPtr<Program> specializedProgram = new Program(linkage);
+ for(auto module : unspecializedProgram->getModuleDependencies())
+ {
+ specializedProgram->addReferencedLeafModule(module);
+ }
- *globalGenericSubstLink = subst;
- globalGenericSubstLink = &subst->outer;
- }
- entryPoint->globalGenericSubst = globalGenericSubsts;
- }
+ // We will collect all the global generic parameters
+ // defined in the modules being referenced, to find
+ // the global generic parameter signature of the
+ // program.
+ //
+ // TODO: Note that this doesn't handle the case where one
+ // or more of the type *arguments* that we are specifying
+ // ends up requiring additional modules to be referenced,
+ // which might in turn introduce new global generic parameters.
+ //
+ List<RefPtr<GlobalGenericParamDecl>> globalGenericParams;
+ for(auto module : unspecializedProgram->getModuleDependencies())
+ {
+ for(auto param : module->getModuleDecl()->getMembersOfType<GlobalGenericParamDecl>())
+ globalGenericParams.Add(param);
}
- // If any errors occured while we were checking the generic arguments
- // of the entry point, then we should bail out rather than try to
- // perform the next step of validation.
+ // Next, we will check whether the supplied arguments can
+ // satisfy those parameters.
+ //
+ // An easy early-out case will be if the number of
+ // arguments isn't correct.
//
- if (sink->errorCount != 0)
- return;
+ if (globalGenericParams.Count() != globalGenericArgs.Count())
+ {
+ sink->diagnose(SourceLoc(), Diagnostics::mismatchGlobalGenericArguments,
+ globalGenericParams.Count(),
+ globalGenericArgs.Count());
+ return nullptr;
+ }
- // Now that we've *found* the entry point, it is time to validate
- // that it actually meets the constraints for the chosen stage/profile.
+ // We have an appropriate number of arguments for the global generic parameters,
+ // and now we need to check that the arguments conform to the declared constraints.
//
- // TODO: This validation should (probably?) be performed "under" any global generic
- // parameter substitution we might have created, so that we can validate
- // based on knowledge of actual types.
+ // Along the way, we will build up an appropriate set of substitutions to represent
+ // the generic arguments and their conformances.
//
- validateEntryPoint(entryPoint);
- }
-
- void validateEntryPoints(
- CompileRequest* compileRequest)
- {
- // The validation of entry points here will be modal, and controlled
- // by whether the user specified any entry points directly via
- // API or command-line options.
+ RefPtr<Substitutions> globalGenericSubsts;
+ auto globalGenericSubstLink = &globalGenericSubsts;
//
- // TODO: We may want to make this choice explicit rather than implicit.
+ // TODO: There is a serious flaw to this checking logic if we ever have cases where
+ // the constraints on one `type_param` can depend on another `type_param`, e.g.:
//
- // First, check if the user request any entry points explicitly via
- // the API or command line.
+ // type_param A;
+ // type_param B : ISidekick<A>;
+ //
+ // In that case, if a user tries to set `B` to `Robin` and `Robin` conforms to
+ // `ISidekick<Batman>`, then the compiler needs to know whether `A` is being
+ // set to `Batman` to know whether the setting for `B` is valid. In this limit
+ // the constraints can be mutually recursive (so `A : IMentor<B>`).
//
- bool anyExplicitEntryPointRequests = false;
- for (auto& translationUnit : compileRequest->translationUnits)
+ // The only way to check things correctly is to validate each conformance under
+ // a set of assumptions (substitutions) that includes all the type substitutions,
+ // and possibly also all the other constraints *except* the one to be validated.
+ //
+ // We will punt on this for now, and just check each constraint in isolation.
+ //
+ UInt argCounter = 0;
+ for(auto& globalGenericParam : globalGenericParams)
{
- if( translationUnit->entryPoints.Count() != 0)
+ // Get the argument that matches this parameter.
+ UInt argIndex = argCounter++;
+ SLANG_ASSERT(argIndex < globalGenericArgs.Count());
+ auto globalGenericArg = checkProperType(linkage, TypeExp(globalGenericArgs[argIndex]), sink);
+ if (!globalGenericArg)
{
- anyExplicitEntryPointRequests = true;
- break;
+ sink->diagnose(globalGenericParam, Diagnostics::globalGenericArgumentNotAType, globalGenericParam->getName());
+ return nullptr;
}
- }
- if( anyExplicitEntryPointRequests )
- {
- // If there were any explicit requests for entry points to be
- // checked, then we will *only* check those.
-
- for (auto& translationUnit : compileRequest->translationUnits)
+ // As a quick sanity check, see if the argument that is being supplied for a parameter
+ // is just the parameter itself, because this should always be an error:
+ //
+ if( auto argDeclRefType = globalGenericArg.as<DeclRefType>() )
{
- for (auto entryPoint : translationUnit->entryPoints)
+ auto argDeclRef = argDeclRefType->declRef;
+ if(auto argGenericParamDeclRef = argDeclRef.as<GlobalGenericParamDecl>())
{
- findAndValidateEntryPoint(entryPoint);
+ if(argGenericParamDeclRef.getDecl() == globalGenericParam)
+ {
+ // We are trying to specialize a generic parameter using itself.
+ sink->diagnose(globalGenericParam,
+ Diagnostics::cannotSpecializeGlobalGenericToItself,
+ globalGenericParam->getName());
+ continue;
+ }
+ else
+ {
+ // We are trying to specialize a generic parameter using a *different*
+ // global generic type parameter.
+ sink->diagnose(globalGenericParam,
+ Diagnostics::cannotSpecializeGlobalGenericToAnotherGenericParam,
+ globalGenericParam->getName(),
+ argGenericParamDeclRef.GetName());
+ continue;
+ }
}
}
- }
- else
- {
- // Otherwise, scan for any `[shader(...)]` attributes in
- // the user's code, and construct `EntryPointRequest`s to
- // represent them.
- //
- // This ensures that downstream code only has to consider
- // the central list of entry point requests, and doesn't
- // have to know where they came from.
- // TODO: A comprehensive approach here would need to search
- // recursively for entry points, because they might appear
- // as, e.g., member function of a `struct` type.
- //
- // For now we'll start with an extremely basic approach that
- // should work for typical HLSL code.
- //
- UInt translationUnitCount = compileRequest->translationUnits.Count();
- for(UInt tt = 0; tt < translationUnitCount; ++tt)
+ // Create a substitution for this parameter/argument.
+ RefPtr<GlobalGenericParamSubstitution> subst = new GlobalGenericParamSubstitution();
+ subst->paramDecl = globalGenericParam;
+ subst->actualType = globalGenericArg;
+
+ // Walk through the declared constraints for the parameter,
+ // and check that the argument actually satisfies them.
+ for(auto constraint : globalGenericParam->getMembersOfType<GenericTypeConstraintDecl>())
{
- auto translationUnit = compileRequest->translationUnits[tt];
- for( auto globalDecl : translationUnit->SyntaxNode->Members )
+ // Get the type that the constraint is enforcing conformance to
+ auto interfaceType = GetSup(DeclRef<GenericTypeConstraintDecl>(constraint, nullptr));
+
+ // Use our semantic-checking logic to search for a witness to the required conformance
+ SemanticsVisitor visitor(linkage, sink);
+ auto witness = visitor.tryGetSubtypeWitness(globalGenericArg, interfaceType);
+ if (!witness)
{
- auto maybeFuncDecl = globalDecl;
- if( auto genericDecl = as<GenericDecl>(maybeFuncDecl) )
- {
- maybeFuncDecl = genericDecl->inner;
- }
+ // If no witness was found, then we will be unable to satisfy
+ // the conformances required.
+ sink->diagnose(globalGenericParam,
+ Diagnostics::typeArgumentDoesNotConformToInterface,
+ globalGenericParam->nameAndLoc.name,
+ globalGenericArg,
+ interfaceType);
+ }
- auto funcDecl = as<FuncDecl>(maybeFuncDecl);
- if(!funcDecl)
- continue;
+ // Attach the concrete witness for this conformance to the
+ // substutiton
+ GlobalGenericParamSubstitution::ConstraintArg constraintArg;
+ constraintArg.decl = constraint;
+ constraintArg.val = witness;
+ subst->constraintArgs.Add(constraintArg);
+ }
- auto entryPointAttr = funcDecl->FindModifier<EntryPointAttribute>();
- if(!entryPointAttr)
- continue;
+ // Add the substitution for this parameter to the global substitution
+ // set that we are building.
- // We've discovered a valid entry point. It is a function (possibly
- // generic) that has a `[shader(...)]` attribute to mark it as an
- // entry point.
- //
- // We will now register that entry point as an `EntryPointRequest`
- // with an appropriately chosen profile.
- //
- // The profile will only include a stage, so that the profile "family"
- // and "version" are left unspecified. Downstream code will need
- // to be able to handle this case.
- //
- Profile profile;
- profile.setStage(entryPointAttr->stage);
+ *globalGenericSubstLink = subst;
+ globalGenericSubstLink = &subst->outer;
+ }
+ if(sink->GetErrorCount())
+ return nullptr;
- // We manually fill in the entry point request object.
- RefPtr<EntryPointRequest> entryPointReq = new EntryPointRequest();
- entryPointReq->compileRequest = compileRequest;
- entryPointReq->translationUnitIndex = int(tt);
- entryPointReq->funcDeclRef = makeDeclRef(funcDecl);
- entryPointReq->name = funcDecl->getName();
- entryPointReq->profile = profile;
+ specializedProgram->setGlobalGenericSubsitution(globalGenericSubsts);
- // Apply the common validation logic to this entry point.
- validateEntryPoint(entryPointReq);
+ return specializedProgram;
+ }
- // Add the entry point to the list in the translation unit,
- // and also the global list in the compile request.
- translationUnit->entryPoints.Add(entryPointReq);
- compileRequest->entryPoints.Add(entryPointReq);
- }
- }
+ /// 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
+ /// for this entry point, then they will be parsed, checked, and used
+ /// as arguments to the generic entry point.
+ ///
+ /// Returns a specialized entry point if everything worked as expected.
+ /// Returns null and diagnoses errors if anything goes wrong.
+ ///
+ RefPtr<EntryPoint> specializeEntryPoint(
+ EndToEndCompileRequest* endToEndReq,
+ EntryPoint* unspecializedEntryPoint,
+ EndToEndCompileRequest::EntryPointInfo const& entryPointInfo)
+ {
+ auto linkage = endToEndReq->getLinkage();
+ auto sink = endToEndReq->getSink();
+ auto entryPointFuncDecl = unspecializedEntryPoint->getFuncDecl();
+
+ // If the user specified generic arguments for the entry point,
+ // then we will need to parse the arguments first.
+ //
+ List<RefPtr<Expr>> genericArgs;
+ parseGenericArgStrings(
+ endToEndReq,
+ entryPointInfo.genericArgStrings,
+ genericArgs);
+
+ // Next we specialize the entry point function given the parsed
+ // generic argument expressions.
+ //
+ auto entryPointFuncDeclRef = specializeEntryPoint(
+ linkage,
+ entryPointFuncDecl,
+ genericArgs,
+ sink);
+
+ RefPtr<EntryPoint> entryPoint = EntryPoint::create(
+ entryPointFuncDeclRef,
+ unspecializedEntryPoint->getProfile());
+
+ return entryPoint;
+ }
+
+ /// Create a specialized program based on the given compile request.
+ ///
+ RefPtr<Program> createSpecializedProgram(
+ EndToEndCompileRequest* endToEndReq)
+ {
+ // The compile request must have already completed front-end processing,
+ // so that we have an unspecialized program available, and now only need
+ // to parse and check any generic arguments that are being supplied for
+ // global or entry-point generic parameters.
+ //
+ auto unspecializedProgram = endToEndReq->getUnspecializedProgram();
+
+ // First, let's parse the generic argument strings that were
+ // provided via the API, so taht we can match them
+ // against what was declared in the program.
+ //
+ List<RefPtr<Expr>> globalGenericArgs;
+ parseGenericArgStrings(
+ endToEndReq,
+ endToEndReq->globalGenericArgStrings,
+ globalGenericArgs);
+
+ // Now we create the initial specialized program by
+ // applying the global generic arguments (if any) to the
+ // unspecialized program.
+ //
+ auto specializedProgram = createSpecializedProgram(
+ endToEndReq->getLinkage(),
+ unspecializedProgram,
+ globalGenericArgs,
+ endToEndReq->getSink());
+
+ // If anything went wrong with the global generic
+ // arguments, then bail out now.
+ //
+ if(!specializedProgram)
+ return nullptr;
+
+ // Next we will deal with the entry points for the
+ // new specialized program.
+ //
+ // If the user specified explicit entry points as part of the
+ // end-to-end request, then we only want to process those (and
+ // ignore any other `[shader(...)]`-attributed entry points).
+ //
+ // However, if the user specified *no* entry points as part
+ // of the end-to-end request, then we would like to go
+ // ahead and consider all the entry points that were found
+ // by the front-end.
+ //
+ UInt entryPointCount = endToEndReq->entryPoints.Count();
+ if( entryPointCount == 0 )
+ {
+ entryPointCount = unspecializedProgram->getEntryPointCount();
+ endToEndReq->entryPoints.SetSize(entryPointCount);
}
+
+ for( UInt ii = 0; ii < entryPointCount; ++ii )
+ {
+ auto unspecializedEntryPoint = unspecializedProgram->getEntryPoint(ii);
+ auto& entryPointInfo = endToEndReq->entryPoints[ii];
+
+ auto specializedEntryPoint = specializeEntryPoint(endToEndReq, unspecializedEntryPoint, entryPointInfo);
+ specializedProgram->addEntryPoint(specializedEntryPoint);
+ }
+
+ return specializedProgram;
}
void checkTranslationUnit(
TranslationUnitRequest* translationUnit)
{
SemanticsVisitor visitor(
- &translationUnit->compileRequest->mSink,
- translationUnit->compileRequest,
- translationUnit);
+ translationUnit->compileRequest->getLinkage(),
+ translationUnit->compileRequest->getSink());
// Apply the visitor to do the main semantic
// checking that is required on all declarations
// in the translation unit.
- visitor.checkDecl(translationUnit->SyntaxNode);
+ visitor.checkDecl(translationUnit->getModuleDecl());
}