diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-03-19 12:45:23 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-03-19 12:45:23 -0700 |
| commit | 72562144254145612c68534c6b7457764c28acf5 (patch) | |
| tree | 1d33abbe3ccf6321b80e2870544b852395a4633c /source | |
| parent | 04c9476622cc22daa0994c8141f686405858c8a7 (diff) | |
Entry point attribute (#447)
* Typo
* Add [shader(...)] and clean up some literal handling
* Add supporting for validating the `[shader(...)]` attribute, by checking that its argument is a string literal that names a known shader stage.
* Split the `ConstantExpr` class into distinct subclasses rooted at `LiteralExpr`, so we have `BoolLiteralExpr`, `IntegerLiteralExpr`, `FloatingPointLiteralExpr`, and `StringLiteralExpr`
* Add a `String` type to the stdlib, to be used as the type of a string literal.
This change allows code using `[shader(...)]` to be accepted by the front-end again, but it does nothing about emitting it in final HLSL.
* Allow entry points to be specified via [shader(...)]
Before this change, the compiler would track a list of `EntryPointRequest` objects, based on what the suer specified via API and/or command-line options. Each entry point request would get matched up with an AST `FuncDecl` as part of semantic checking, and then the back end steps (layout, codegen, etc.) would work from that information.
This change makes the compiler modal, in that it can *either* continue to use an explicit list of entry point requests (this is the mode when the list is non-empty), or it can rely on user-supplied attributes on entry point functions to drive codegen (this is the mode when the list is empty).
User-specified `[shader(...)]` attributes are processed at the same place where the association from `EntryPointRequest`s to `FuncDecl`s would otherwise be made, and basically does the same thing in the opposite direction: looks for `FuncDecl`s with the appropriate attribute and synthesizes an `EntryPointRequest` for them.
Subsequent processing should ideally not know where a given `EntryPointRequest` came from, and should handle both methods of specifying the entry points equivalently.
One design choice that might not make immediate sense is that we do *not* process a function as an entry point (applying further validation, etc.) just because it has a `[shader(...)]` modifier, unless we are in the appropriate mode (which in this case is the mode where the user didn't specify their own entry points via API or command line). This is to handle cases where the user wants to explicitly compile only one entry point, so that they (1) don't want us to spend time validating code they don't care about, (2) don't want do get output they don't expect, and (3) might actually be presenting us with code that violates the language rules due to a combination of `#define`s in effect (e.g., they might have a `[shader("vertex")]` function that transitively executes a `discard` because of how the preprocessor was configured, but they don't care because they are compiling a fragment entry point). This decision might be something we revisit over time.
As part of this work, I had to add some logic to pick a "profile version" to use for a combination of a target and stage (because when you specify `[shader("vertex")]` the compiler can't tell if you want `vs_5_0`, `vs_5_1`, etc.). This isn't really complete right now, because something like `-target dxbc` *also* doesn't determine a profile, so there is a bit of a kludge at present. We need to figure out a good long-term plan here, which might involve keeping target format, feature level/version, and pipeline stage as truly orthogonal concepts, rather than conflating them. That would involve more work in the API and command-line layers to de-compose things when the user specifies, e.g., `vs_5_1`, but might make downstream logic easier to manage.
* Emit [shader(...)] attribute on entry point for SM 6.1 and later
This should help ensure that the output from Slang can be compiled with dxc `lib_*` profiles.
* Fix warning
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/check.cpp | 281 | ||||
| -rw-r--r-- | source/slang/compiler.cpp | 21 | ||||
| -rw-r--r-- | source/slang/compiler.h | 41 | ||||
| -rw-r--r-- | source/slang/core.meta.slang | 4 | ||||
| -rw-r--r-- | source/slang/core.meta.slang.h | 4 | ||||
| -rw-r--r-- | source/slang/diagnostic-defs.h | 8 | ||||
| -rw-r--r-- | source/slang/dxc-support.cpp | 3 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 100 | ||||
| -rw-r--r-- | source/slang/expr-defs.h | 41 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 29 | ||||
| -rw-r--r-- | source/slang/parameter-binding.cpp | 10 | ||||
| -rw-r--r-- | source/slang/parser.cpp | 24 | ||||
| -rw-r--r-- | source/slang/profile.h | 9 | ||||
| -rw-r--r-- | source/slang/slang.cpp | 53 | ||||
| -rw-r--r-- | source/slang/syntax.cpp | 6 | ||||
| -rw-r--r-- | source/slang/type-defs.h | 3 |
16 files changed, 535 insertions, 102 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp index 1af91e9fd..070d4c606 100644 --- a/source/slang/check.cpp +++ b/source/slang/check.cpp @@ -1407,6 +1407,30 @@ namespace Slang return constIntVal; } + // Check an expression, coerce it to the `String` type, and then + // ensure that it has a literal (not just compile-time constant) value. + bool checkLiteralStringVal( + RefPtr<Expr> expr, + String* outVal) + { + // TODO: This should actually perform semantic checking, etc., + // but for now we are just going to look for a direct string + // literal AST node. + + if(auto stringLitExpr = expr.As<StringLiteralExpr>()) + { + if(outVal) + { + *outVal = stringLitExpr->value; + } + return true; + } + + getSink()->diagnose(expr, Diagnostics::expectedAStringLiteral); + + return false; + } + void visitSyntaxDecl(SyntaxDecl*) { // These are only used in the stdlib, so no checking is needed @@ -1456,6 +1480,36 @@ namespace Slang } } + Stage findStageByName(String const& name) + { + static const struct + { + char const* name; + Stage stage; + } kStages[] = + { + { "vertex", Stage::Vertex }, + { "hull", Stage::Hull }, + { "domain", Stage::Domain }, + { "geometry", Stage::Geometry }, + { "fragment", Stage::Fragment }, + { "compute", Stage::Compute }, + + // Allow `pixel` as an alias of `fragment` + { "pixel", Stage::Fragment }, + }; + + for(auto entry : kStages) + { + if(name == entry.name) + { + return entry.stage; + } + } + + return Stage::Unknown; + } + bool validateAttribute(RefPtr<Attribute> attr) { if(auto numThreadsAttr = attr.As<NumThreadsAttribute>()) @@ -1487,6 +1541,24 @@ namespace Slang instanceAttr->value = (int32_t)val->value; } + else if(auto entryPointAttr = attr.As<EntryPointAttribute>()) + { + SLANG_ASSERT(attr->args.Count() == 1); + + String stageName; + if(!checkLiteralStringVal(attr->args[0], &stageName)) + { + return false; + } + + auto stage = findStageByName(stageName); + if(stage == Stage::Unknown) + { + getSink()->diagnose(attr->args[0], Diagnostics::unknownStageName, stageName); + } + + entryPointAttr->stage = stage; + } else { if(attr->args.Count() == 0) @@ -3172,35 +3244,51 @@ namespace Slang stmt->Expression = CheckExpr(stmt->Expression); } - RefPtr<Expr> visitConstantExpr(ConstantExpr *expr) + RefPtr<Expr> visitBoolLiteralExpr(BoolLiteralExpr* expr) { - // The expression might already have a type, determined by its suffix - if(expr->type.type) - return expr; + expr->type = getSession()->getBoolType(); + return expr; + } - switch (expr->ConstType) + RefPtr<Expr> visitIntegerLiteralExpr(IntegerLiteralExpr* expr) + { + // The expression might already have a type, determined by its suffix. + // It it doesn't, we will give it a default type. + // + // TODO: We should be careful to pick a "big enough" type + // based on the size of the value (e.g., don't try to stuff + // a constant in an `int` if it requires 64 or more bits). + // + // The long-term solution here is to give a type to a literal + // based on the context where it is used, but that requires + // a more sophisticated type system than we have today. + // + if(!expr->type.type) { - case ConstantExpr::ConstantType::Int: expr->type = getSession()->getIntType(); - break; - case ConstantExpr::ConstantType::Bool: - expr->type = getSession()->getBoolType(); - break; - case ConstantExpr::ConstantType::Float: + } + return expr; + } + + RefPtr<Expr> visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) + { + if(!expr->type.type) + { expr->type = getSession()->getFloatType(); - break; - default: - expr->type = QualType(getSession()->getErrorType()); - throw "Invalid constant type."; - break; } return expr; } - IntVal* GetIntVal(ConstantExpr* expr) + RefPtr<Expr> visitStringLiteralExpr(StringLiteralExpr* expr) + { + expr->type = getSession()->getStringType(); + return expr; + } + + IntVal* GetIntVal(IntegerLiteralExpr* expr) { // TODO(tfoley): don't keep allocating here! - return new ConstantIntVal(expr->integerValue); + return new ConstantIntVal(expr->value); } Name* getName(String const& text) @@ -3339,9 +3427,9 @@ namespace Slang } // TODO(tfoley): more serious constant folding here - if (auto constExp = dynamic_cast<ConstantExpr*>(expr)) + if (auto intLitExpr = dynamic_cast<IntegerLiteralExpr*>(expr)) { - return GetIntVal(constExp); + return GetIntVal(intLitExpr); } // it is possible that we are referring to a generic value param @@ -6898,7 +6986,46 @@ namespace Slang return type; } + // Validate that an entry point function conforms to any additional + // constraints based on the stage (and profile?) it specifies. void validateEntryPoint( + EntryPointRequest* /*entryPoint*/) + { + // TODO: We currently don't do any checking here, but this is the + // right place to perform the following validation checks: + // + // * Does the entry point specify all of the attributes required + // by the chosen stage (e.g., a `[domain(...)]` attribute for] + // a hull shader. + // + // * Are the function input/output parameters and result type + // all valid for the chosen stage? (e.g., there shouldn't be + // an `OutputStream<X>` type in a vertex shader signature) + // + // * For any varying input/output, are there semantics specified + // (Note: this potentially overlaps with layout logic...), and + // are the system-value semantics valid for the given stage? + // + // There's actually a lot of detail to semantic checking, in + // that the AST-level code should probably be validating the + // use of system-value semantics by linking them to explicit + // declarations in the standard library. We should also be + // using profile information on those declarations to infer + // appropriate profile restrictions on the entry point. + // + // * Is the entry point actually usable on the given stage/profile? + // E.g., if we have a vertex shader that (transitively) calls + // `Texture2D.Sample`, then that should produce an error because + // that function is specific to the fragment profile/stage. + // + } + + // Given an `EntryPointRequest` 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) { // The first step in validating the entry point is to find @@ -7070,10 +7197,116 @@ namespace Slang } if (sink->errorCount != 0) return; - // TODO: after all that work, we are now in a position to start - // validating the declaration itself. E.g., we should check if - // the declared input/output parameters have suitable semantics, - // if they are of types that are appropriate to the stage, etc. + + // Now that we've *found* the entry point, it is time to validate + // that it actually meets the constraints for the chosen stage/profile. + 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. + // + // TODO: We may want to make this choice explicit rather than implicit. + // + // First, check if the user request any entry points explicitly via + // the API or command line. + // + bool anyExplicitEntryPointRequests = false; + for (auto& translationUnit : compileRequest->translationUnits) + { + if( translationUnit->entryPoints.Count() != 0) + { + anyExplicitEntryPointRequests = true; + break; + } + } + + 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) + { + for (auto entryPoint : translationUnit->entryPoints) + { + findAndValidateEntryPoint(entryPoint); + } + } + } + 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) + { + auto translationUnit = compileRequest->translationUnits[tt]; + for( auto globalDecl : translationUnit->SyntaxNode->Members ) + { + auto maybeFuncDecl = globalDecl; + if( auto genericDecl = maybeFuncDecl->As<GenericDecl>() ) + { + maybeFuncDecl = genericDecl->inner; + } + + auto funcDecl = maybeFuncDecl->As<FuncDecl>(); + 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 `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); + + // We manually fill in the entry point request object. + RefPtr<EntryPointRequest> entryPointReq = new EntryPointRequest(); + entryPointReq->compileRequest = compileRequest; + entryPointReq->translationUnitIndex = int(tt); + entryPointReq->decl = funcDecl; + entryPointReq->name = funcDecl->getName(); + entryPointReq->profile = profile; + + // Apply the common validation logic to this entry point. + validateEntryPoint(entryPointReq); + + // 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); + } + } + } } void checkTranslationUnit( diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp index acbf51e2e..b7cbc2327 100644 --- a/source/slang/compiler.cpp +++ b/source/slang/compiler.cpp @@ -213,6 +213,21 @@ namespace Slang char const* GetHLSLProfileName(Profile profile) { + switch( profile.getFamily() ) + { + case ProfileFamily::DX: + // Profile version is a DX one, so stick with it. + break; + + default: + // Profile is a non-DX profile family, so we need to try + // to clobber it with something to get a default. + // + // TODO: This is a huge hack... + profile.setVersion(ProfileVersion::DX_5_0); + break; + } + switch(profile.raw) { #define PROFILE(TAG, NAME, STAGE, VERSION) case Profile::TAG: return #NAME; @@ -262,6 +277,8 @@ namespace Slang auto hlslCode = emitHLSLForEntryPoint(entryPoint, targetReq); maybeDumpIntermediate(entryPoint->compileRequest, hlslCode.Buffer(), CodeGenTarget::HLSL); + auto profile = getEffectiveProfile(entryPoint, targetReq); + ID3DBlob* codeBlob; ID3DBlob* diagnosticsBlob; HRESULT hr = D3DCompile_( @@ -271,7 +288,7 @@ namespace Slang nullptr, nullptr, getText(entryPoint->name).begin(), - GetHLSLProfileName(entryPoint->profile), + GetHLSLProfileName(profile), 0, 0, &codeBlob, @@ -485,7 +502,7 @@ String dissassembleDXILUsingDXC( glslang_CompileRequest request; request.action = GLSLANG_ACTION_COMPILE_GLSL_TO_SPIRV; request.sourcePath = "slang"; - request.slangStage = (SlangStage)entryPoint->profile.GetStage(); + request.slangStage = (SlangStage)entryPoint->getStage(); request.inputBegin = rawGLSL.begin(); request.inputEnd = rawGLSL.end(); diff --git a/source/slang/compiler.h b/source/slang/compiler.h index 6d0475a05..7ab47e6b3 100644 --- a/source/slang/compiler.h +++ b/source/slang/compiler.h @@ -107,10 +107,20 @@ namespace Slang List<String> genericParameterTypeNames; // The profile that the entry point will be compiled for - // (this is a combination of the target state, and also + // (this is a combination of the target stage, and also // a feature level that sets capabilities) + // + // Note: the profile-version part of this should probably + // be moving towards deprecation, in favor of the version + // information (e.g., "Shader Model 5.1") always coming + // from the target, while the stage part is all that is + // intrinsic to the entry point. + // Profile profile; + // Get the stage that the entry point is being compiled for. + Stage getStage() { return profile.GetStage(); } + // The index of the translation unit (within the parent // compile request) that the entry point function is // supposed to be defined in. @@ -118,6 +128,19 @@ namespace Slang // The output path requested for this entry point. // (only used when compiling from the command line) + // + // TODO: This should get dropped. When compiling from the + // command line, the user should be either: + // + // * Compiling a single entry point for a single target, so + // that only a single output path is needed for the whole request. + // + // * Compiling for a target that supports multiple entry points directly + // (e.g., a recent DXIL version), so that only one output file is needed. + // + // * Compiling to a slang module container, so that as many entry + // points and targets as needed can be specified. + // String outputPath; // The translation unit that this entry point came from @@ -203,6 +226,21 @@ namespace Slang Dictionary<Type*, RefPtr<TypeLayout>> typeLayouts; }; + // Compute the "effective" profile to use when outputting the given entry point + // for the chosen code-generation target. + // + // The stage of the effective profile will always come from the entry point, while + // the profile version (aka "shader model") will be computed as follows: + // + // - If the entry point and target belong to the same profile family, then take + // the latest version between the two (e.g., if the entry point specified `ps_5_1` + // and the target specifies `sm_5_0` then use `sm_5_1` as the version). + // + // - If the entry point and target disagree on the profile family, always use the + // profile family and version from the target. + // + Profile getEffectiveProfile(EntryPointRequest* entryPoint, TargetRequest* target); + // A directory to be searched when looking for files (e.g., `#include`) struct SearchDirectory { @@ -468,6 +506,7 @@ namespace Slang Type* getInitializerListType(); Type* getOverloadedType(); Type* getErrorType(); + Type* getStringType(); Type* getConstExprRate(); RefPtr<RateQualifiedType> getRateQualifiedType( diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang index f105b98aa..785ef4406 100644 --- a/source/slang/core.meta.slang +++ b/source/slang/core.meta.slang @@ -114,6 +114,10 @@ __magic_type(InOutType) struct InOut {}; +__magic_type(StringType) +struct String +{}; + ${{{{ // Declare vector and matrix types diff --git a/source/slang/core.meta.slang.h b/source/slang/core.meta.slang.h index 5e1f2ec89..bc0fbb53d 100644 --- a/source/slang/core.meta.slang.h +++ b/source/slang/core.meta.slang.h @@ -114,6 +114,10 @@ SLANG_RAW("__magic_type(InOutType)\n") SLANG_RAW("struct InOut\n") SLANG_RAW("{};\n") SLANG_RAW("\n") +SLANG_RAW("__magic_type(StringType)\n") +SLANG_RAW("struct String\n") +SLANG_RAW("{};\n") +SLANG_RAW("\n") // Declare vector and matrix types diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h index ae815cd40..e74126f1b 100644 --- a/source/slang/diagnostic-defs.h +++ b/source/slang/diagnostic-defs.h @@ -197,11 +197,16 @@ DIAGNOSTIC(30051, Error, invalidValueForArgument, "invalid value for argument '$ DIAGNOSTIC(30052, Error, invalidSwizzleExpr, "invalid swizzle pattern '$0' on type '$1'") DIAGNOSTIC(33070, Error, expectedFunction, "expression preceding parenthesis of apparent call must have function type.") +DIAGNOSTIC(33071, Error, expectedAStringLiteral, "expected a string literal") + // Attributes DIAGNOSTIC(31000, Error, unknownAttributeName, "unknown attribute '$0'") DIAGNOSTIC(31001, Error, attributeArgumentCountMismatch, "attribute '$0' expects $1 arguments ($2 provided)") DIAGNOSTIC(31001, Error, attributeNotApplicable, "attribute '$0' is not valid here") +DIAGNOSTIC(31100, Error, unknownStageName, "unknown stage name '$0'") + + // 303xx: interfaces and associated types DIAGNOSTIC(30300, Error, assocTypeInInterfaceOnly, "'associatedtype' can only be defined in an 'interface'.") DIAGNOSTIC(30301, Error, globalGenParamInGlobalScopeOnly, "'__generic_param' can only be defined global scope.") @@ -299,7 +304,6 @@ DIAGNOSTIC(40007, Internal, irValidationFailed, "IR validation failed: $0") // // 5xxxx - Target code generation. // -DIAGNOSTIC(50020, Error, unknownStageType, "Unknown stage type '$0'.") DIAGNOSTIC(50020, Error, invalidTessCoordType, "TessCoord must have vec2 or vec3 type.") DIAGNOSTIC(50020, Error, invalidFragCoordType, "FragCoord must be a vec4.") DIAGNOSTIC(50020, Error, invalidInvocationIdType, "InvocationId must have int type.") @@ -337,7 +341,7 @@ DIAGNOSTIC(51092, Error, stageDoesntHaveInputWorld, "'$0' doesn't appear to have // 99999 - Internal compiler errors, and not-yet-classified diagnostics. DIAGNOSTIC(99999, Internal, unimplemented, "unimplemented: $0") -DIAGNOSTIC(99999, Internal, unexpected, "uuexpected: $0") +DIAGNOSTIC(99999, Internal, unexpected, "unexpected: $0") DIAGNOSTIC(99999, Internal, internalCompilerError, "internal compiler error") DIAGNOSTIC(99999, Error, compilationAborted, "compilation aborted due to internal error"); diff --git a/source/slang/dxc-support.cpp b/source/slang/dxc-support.cpp index b18a25e43..056fefe2d 100644 --- a/source/slang/dxc-support.cpp +++ b/source/slang/dxc-support.cpp @@ -125,7 +125,8 @@ namespace Slang String entryPointName = getText(entryPoint->name); OSString wideEntryPointName = entryPointName.ToWString(); - String profileName = GetHLSLProfileName(entryPoint->profile); + auto profile = getEffectiveProfile(entryPoint, targetReq); + String profileName = GetHLSLProfileName(profile); OSString wideProfileName = profileName.ToWString(); diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index aee19018a..ace8a1559 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -124,6 +124,10 @@ struct SharedEmitContext HashSet<String> irDeclsVisited; HashSet<String> irTupleTypes; + + // The "effective" profile that is being used to emit code, + // combining information from the target and entry point. + Profile effctiveProfile; }; struct EmitContext @@ -134,6 +138,7 @@ struct EmitContext // +#ifdef DEADCODE void requireGLSLVersion( EntryPointRequest* entryPoint, ProfileVersion version) @@ -155,6 +160,7 @@ void requireGLSLVersion( entryPoint->profile = profile; } } +#endif // @@ -2011,8 +2017,7 @@ struct EmitVisitor if (context->shared->target != CodeGenTarget::GLSL) return; - auto entryPoint = context->shared->entryPoint; - Slang::requireGLSLVersion(entryPoint, version); + Slang::requireGLSLVersionImpl(&context->shared->extensionUsageTracker, version); } void requireGLSLVersion(int version) @@ -2083,12 +2088,14 @@ struct EmitVisitor requireGLSLExtension(requiredGLSLExtensionModifier->extensionNameToken.Content); } +#ifdef DEADCODE // Does this intrinsic requie a particular GLSL extension that wouldn't be available by default? if (auto requiredGLSLVersionModifier = funcDecl->FindModifier<RequiredGLSLVersionModifier>()) { // If so, we had better request the extension. requireGLSLVersion((int) getIntegerLiteralValue(requiredGLSLVersionModifier->versionNumberToken)); } +#endif } @@ -2673,8 +2680,15 @@ struct EmitVisitor ExprVisitorWithArg::dispatch(derefExpr->base, arg); } - void visitConstantExpr(ConstantExpr* litExpr, ExprEmitArg const& arg) + void visitLiteralExpr(LiteralExpr*, ExprEmitArg const&) { + // Disabling because we no longer use the AST-based emit path. + // + // Note: I'm keeping this code around for a while just in case + // we want to borrow any of the logic here for how to apply + // suffixes to numeric literals we emit. + // +#if 0 auto outerPrec = arg.outerPrec; bool needClose = MaybeEmitParens(outerPrec, kEOp_Atomic); @@ -2736,6 +2750,7 @@ struct EmitVisitor break; } if(needClose) Emit(")"); +#endif } void visitTypeCastExpr(TypeCastExpr* castExpr, ExprEmitArg const& arg) @@ -4301,8 +4316,9 @@ struct EmitVisitor } void emitGLSLVersionDirective( - ModuleDecl* program) + ModuleDecl* /*program*/) { +#ifdef DEADCODE // Did the user provide an explicit `#version` directive in their code? if( auto versionDirective = program->FindModifier<GLSLVersionDirective>() ) { @@ -4350,6 +4366,48 @@ struct EmitVisitor break; } } +#endif + + auto effctiveProfile = context->shared->effctiveProfile; + if(effctiveProfile.getFamily() == ProfileFamily::GLSL) + { + requireGLSLVersion(effctiveProfile.GetVersion()); + } + + // HACK: We aren't picking GLSL versions carefully right now, + // and so we might end up only requiring the initial 1.10 version, + // even though even basic functionality needs a higher version. + // + // For now, we'll work around this by just setting the minimum required + // version to a high one: + // + // TODO: Either correctly compute a minimum required version, or require + // the user to specify a version as part of the target. + requireGLSLVersionImpl(&context->shared->extensionUsageTracker, ProfileVersion::GLSL_450); + + auto requiredProfileVersion = context->shared->extensionUsageTracker.profileVersion; + switch (requiredProfileVersion) + { +#define CASE(TAG, VALUE) \ + case ProfileVersion::TAG: Emit("#version " #VALUE "\n"); return + + CASE(GLSL_110, 110); + CASE(GLSL_120, 120); + CASE(GLSL_130, 130); + CASE(GLSL_140, 140); + CASE(GLSL_150, 150); + CASE(GLSL_330, 330); + CASE(GLSL_400, 400); + CASE(GLSL_410, 410); + CASE(GLSL_420, 420); + CASE(GLSL_430, 430); + CASE(GLSL_440, 440); + CASE(GLSL_450, 450); +#undef CASE + + default: + break; + } // No information is available for us to guess a profile, // so it seems like we need to pick one out of thin air. @@ -6694,6 +6752,32 @@ emitDeclImpl(decl, nullptr); auto profile = entryPointLayout->profile; auto stage = profile.GetStage(); + if(profile.getFamily() == ProfileFamily::DX) + { + if(profile.GetVersion() >= ProfileVersion::DX_6_1 ) + { + char const* stageName = nullptr; + switch(stage) + { + case Stage::Compute: stageName = "compute"; + case Stage::Vertex: stageName = "vertex"; + case Stage::Hull: stageName = "hull"; + case Stage::Domain: stageName = "domain"; + case Stage::Geometry: stageName = "geometry"; + case Stage::Fragment: stageName = "pixel"; + default: + break; + } + + if(stageName) + { + emit("[shader(\""); + emit(stageName); + emit("\")]"); + } + } + } + switch (stage) { case Stage::Compute: @@ -8061,6 +8145,13 @@ EntryPointLayout* findEntryPointLayout( if(entryPointLayout->entryPoint->getName() != entryPointRequest->name) 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 + // `EntryPointRequest` directly to the `EntryPointLayout`, and maybe + // that is precisely what we should build... + // if(entryPointLayout->profile != entryPointRequest->profile) continue; @@ -8139,6 +8230,7 @@ String emitEntryPoint( sharedContext.target = target; sharedContext.finalTarget = targetRequest->target; sharedContext.entryPoint = entryPoint; + sharedContext.effctiveProfile = getEffectiveProfile(entryPoint, targetRequest); if (entryPoint) { diff --git a/source/slang/expr-defs.h b/source/slang/expr-defs.h index 3909c6b63..278196421 100644 --- a/source/slang/expr-defs.h +++ b/source/slang/expr-defs.h @@ -42,25 +42,32 @@ SYNTAX_CLASS(OverloadedExpr2, Expr) FIELD(List<RefPtr<Expr>>, candidiateExprs) END_SYNTAX_CLASS() -SYNTAX_CLASS(ConstantExpr, Expr) +ABSTRACT_SYNTAX_CLASS(LiteralExpr, Expr) + // The token that was used to express the literal. This can be + // used to get the raw text of the literal, including any suffix. FIELD(Token, token) +END_SYNTAX_CLASS() + +SYNTAX_CLASS(IntegerLiteralExpr, LiteralExpr) + FIELD(IntegerLiteralValue, value) +END_SYNTAX_CLASS() + +SYNTAX_CLASS(FloatingPointLiteralExpr, LiteralExpr) + FIELD(FloatingPointLiteralValue, value) +END_SYNTAX_CLASS() + +SYNTAX_CLASS(BoolLiteralExpr, LiteralExpr) + FIELD(bool, value) +END_SYNTAX_CLASS() - RAW( - enum class ConstantType - { - Int, - Bool, - Float, - String, - }; - ConstantType ConstType; - union - { - IntegerLiteralValue integerValue; - FloatingPointLiteralValue floatingPointValue; - }; - String stringValue; - ) +SYNTAX_CLASS(StringLiteralExpr, LiteralExpr) + // TODO: consider storing the "segments" of the string + // literal, in the case where multiple literals were + //lined up at the lexer level, e.g.: + // + // "first" "second" "third" + // + FIELD(String, value) END_SYNTAX_CLASS() // An initializer list, e.g. `{ 1, 2, 3 }` diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 0970eef98..24181c75a 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -1390,23 +1390,26 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo> UNREACHABLE_RETURN(LoweredValInfo()); } - LoweredValInfo visitConstantExpr(ConstantExpr* expr) + LoweredValInfo visitBoolLiteralExpr(BoolLiteralExpr* expr) + { + return LoweredValInfo::simple(context->irBuilder->getBoolValue(expr->value)); + } + + LoweredValInfo visitIntegerLiteralExpr(IntegerLiteralExpr* expr) { auto type = lowerSimpleType(context, expr->type); + return LoweredValInfo::simple(context->irBuilder->getIntValue(type, expr->value)); + } - switch( expr->ConstType ) - { - case ConstantExpr::ConstantType::Bool: - return LoweredValInfo::simple(context->irBuilder->getBoolValue(expr->integerValue != 0)); - case ConstantExpr::ConstantType::Int: - return LoweredValInfo::simple(context->irBuilder->getIntValue(type, expr->integerValue)); - case ConstantExpr::ConstantType::Float: - return LoweredValInfo::simple(context->irBuilder->getFloatValue(type, expr->floatingPointValue)); - case ConstantExpr::ConstantType::String: - break; - } + LoweredValInfo visitFloatingPointLiteralExpr(FloatingPointLiteralExpr* expr) + { + auto type = lowerSimpleType(context, expr->type); + return LoweredValInfo::simple(context->irBuilder->getFloatValue(type, expr->value)); + } - SLANG_UNEXPECTED("unexpected constant type"); + LoweredValInfo visitStringLiteralExpr(StringLiteralExpr*) + { + SLANG_UNEXPECTED("string literal encountered during code emit"); } LoweredValInfo visitAggTypeCtorExpr(AggTypeCtorExpr* /*expr*/) diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp index 3ff2be61f..572235280 100644 --- a/source/slang/parameter-binding.cpp +++ b/source/slang/parameter-binding.cpp @@ -1922,7 +1922,7 @@ static void collectEntryPointParameters( state.ioSemanticIndex = &defaultSemanticIndex; state.optSemanticName = nullptr; state.semanticSlotCount = 0; - state.stage = entryPoint->profile.GetStage(); + state.stage = entryPoint->getStage(); for( auto m : entryPointFuncDecl->Members ) { @@ -2020,12 +2020,14 @@ inferStageForTranslationUnit( // and have only a single entry point, use the stage // of the entry point. // - // TODO: can we generalize this at all? + // 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]->profile.GetStage(); + return translationUnit->entryPoints[0]->getStage(); } } @@ -2080,7 +2082,7 @@ static void collectParameters( // Next consider parameters for entry points for( auto& entryPoint : translationUnit->entryPoints ) { - context->stage = entryPoint->profile.GetStage(); + context->stage = entryPoint->getStage(); collectEntryPointParameters(context, entryPoint.Ptr(), SubstitutionSet()); } context->entryPointLayout = nullptr; diff --git a/source/slang/parser.cpp b/source/slang/parser.cpp index 5fb7445e9..a3afbe87a 100644 --- a/source/slang/parser.cpp +++ b/source/slang/parser.cpp @@ -3603,10 +3603,9 @@ namespace Slang static RefPtr<Expr> parseBoolLitExpr(Parser* /*parser*/, bool value) { - RefPtr<ConstantExpr> constExpr = new ConstantExpr(); - constExpr->ConstType = ConstantExpr::ConstantType::Bool; - constExpr->integerValue = value ? 1 : 0; - return constExpr; + RefPtr<BoolLiteralExpr> expr = new BoolLiteralExpr(); + expr->value = value; + return expr; } static RefPtr<RefObject> parseTrueExpr(Parser* parser, void* /*userData*/) @@ -3696,7 +3695,7 @@ namespace Slang case TokenType::IntegerLiteral: { - RefPtr<ConstantExpr> constExpr = new ConstantExpr(); + RefPtr<IntegerLiteralExpr> constExpr = new IntegerLiteralExpr(); parser->FillPosition(constExpr.Ptr()); auto token = parser->tokenReader.AdvanceToken(); @@ -3756,8 +3755,7 @@ namespace Slang } } - constExpr->ConstType = ConstantExpr::ConstantType::Int; - constExpr->integerValue = value; + constExpr->value = value; constExpr->type = QualType(suffixType); return constExpr; @@ -3766,7 +3764,7 @@ namespace Slang case TokenType::FloatingPointLiteral: { - RefPtr<ConstantExpr> constExpr = new ConstantExpr(); + RefPtr<FloatingPointLiteralExpr> constExpr = new FloatingPointLiteralExpr(); parser->FillPosition(constExpr.Ptr()); auto token = parser->tokenReader.AdvanceToken(); @@ -3825,8 +3823,7 @@ namespace Slang } } - constExpr->ConstType = ConstantExpr::ConstantType::Float; - constExpr->floatingPointValue = value; + constExpr->value = value; constExpr->type = QualType(suffixType); return constExpr; @@ -3834,16 +3831,15 @@ namespace Slang case TokenType::StringLiteral: { - RefPtr<ConstantExpr> constExpr = new ConstantExpr(); + RefPtr<StringLiteralExpr> constExpr = new StringLiteralExpr(); auto token = parser->tokenReader.AdvanceToken(); constExpr->token = token; parser->FillPosition(constExpr.Ptr()); - constExpr->ConstType = ConstantExpr::ConstantType::String; if (!parser->LookAheadToken(TokenType::StringLiteral)) { // Easy/common case: a single string - constExpr->stringValue = getStringLiteralTokenValue(token); + constExpr->value = getStringLiteralTokenValue(token); } else { @@ -3854,7 +3850,7 @@ namespace Slang token = parser->tokenReader.AdvanceToken(); sb << getStringLiteralTokenValue(token); } - constExpr->stringValue = sb.ProduceString(); + constExpr->value = sb.ProduceString(); } return constExpr; diff --git a/source/slang/profile.h b/source/slang/profile.h index 3b5242ada..d6a2a39af 100644 --- a/source/slang/profile.h +++ b/source/slang/profile.h @@ -67,14 +67,19 @@ namespace Slang bool operator!=(Profile const& other) const { return raw != other.raw; } Stage GetStage() const { return Stage((uint32_t(raw) >> 16) & 0xFFFF); } - ProfileVersion GetVersion() const { return ProfileVersion(uint32_t(raw) & 0xFFFF); } - ProfileFamily getFamily() const { return getProfileFamily(GetVersion()); } + void setStage(Stage stage) + { + raw = (raw & 0x0000FFFF) | (uint32_t(stage) << 16); + } + ProfileVersion GetVersion() const { return ProfileVersion(uint32_t(raw) & 0xFFFF); } void setVersion(ProfileVersion version) { raw = (raw & ~0xFFFF) | uint32_t(version); } + ProfileFamily getFamily() const { return getProfileFamily(GetVersion()); } + static Profile LookUp(char const* name); RawVal raw = Unknown; diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 1d888dcb5..4fa6f5130 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -112,6 +112,38 @@ struct IncludeHandlerImpl : IncludeHandler } }; +// + + +Profile getEffectiveProfile(EntryPointRequest* entryPoint, TargetRequest* target) +{ + auto entryPointProfile = entryPoint->profile; + auto targetProfile = target->targetProfile; + + auto entryPointProfileVersion = entryPointProfile.GetVersion(); + auto targetProfileVersion = targetProfile.GetVersion(); + + // Default to the entry point profile, since we know that has the right stage. + Profile effectiveProfile = entryPointProfile; + + // Ignore the input from the target profile if it is missing. + if( targetProfile.getFamily() != ProfileFamily::Unknown ) + { + // If the target comes from a different profile family, *or* it is from + // the same family but has a greater version number, then use the target's version. + if( targetProfile.getFamily() != entryPointProfile.getFamily() + || (targetProfileVersion > entryPointProfileVersion) ) + { + effectiveProfile.setVersion(targetProfileVersion); + } + } + + return effectiveProfile; +} + + +// + CompileRequest::CompileRequest(Session* session) : mSession(session) { @@ -224,7 +256,7 @@ void CompileRequest::parseTranslationUnit( } } -void validateEntryPoint(EntryPointRequest*); +void validateEntryPoints(CompileRequest*); void CompileRequest::checkAllTranslationUnits() { @@ -235,23 +267,8 @@ void CompileRequest::checkAllTranslationUnits() checkTranslationUnit(translationUnit.Ptr()); } - for (auto& translationUnit : translationUnits) - { - // Next, do follow-up validation on any entry - // points that the user declared via API or - // command line, to ensure that they meet - // requirements. - // - // Note: We may eventually have syntax to - // identify entry points via a modifier on - // declarations, and in this case they should - // probably get validated as part of orindary - // checking above. - for (auto entryPoint : translationUnit->entryPoints) - { - validateEntryPoint(entryPoint); - } - } + // Next, do follow-up validation on any entry points. + validateEntryPoints(this); } void CompileRequest::generateIR() diff --git a/source/slang/syntax.cpp b/source/slang/syntax.cpp index 88c23f314..4881f301e 100644 --- a/source/slang/syntax.cpp +++ b/source/slang/syntax.cpp @@ -296,6 +296,12 @@ void Type::accept(IValVisitor* visitor, void* extra) return constExprRate; } + Type* Session::getStringType() + { + auto stringTypeDecl = findMagicDecl(this, "StringType"); + return DeclRefType::Create(this, makeDeclRef<Decl>(stringTypeDecl)); + } + RefPtr<RateQualifiedType> Session::getRateQualifiedType( Type* rate, Type* valueType) diff --git a/source/slang/type-defs.h b/source/slang/type-defs.h index 8b61953bc..433c5e15c 100644 --- a/source/slang/type-defs.h +++ b/source/slang/type-defs.h @@ -388,6 +388,9 @@ protected: ) END_SYNTAX_CLASS() +// The built-in `String` type +SIMPLE_SYNTAX_CLASS(StringType, DeclRefType) + // Base class for types that map down to // simple pointers as part of code generation. SYNTAX_CLASS(PtrTypeBase, DeclRefType) |
