[clang] [HLSL] Default and Relaxed Availability Diagnostics (PR #92704)
Chris B via cfe-commits
cfe-commits at lists.llvm.org
Thu May 23 09:52:33 PDT 2024
================
@@ -290,3 +294,295 @@ void SemaHLSL::DiagnoseAttrStageMismatch(
<< A << HLSLShaderAttr::ConvertShaderTypeToStr(Stage)
<< (AllowedStages.size() != 1) << join(StageStrings, ", ");
}
+
+namespace {
+
+/// This class implements HLSL availability diagnostics for default
+/// and relaxed mode
+///
+/// The goal of this diagnostic is to emit an error or warning when an
+/// unavailable API is found in a code that is reachable from the shader
+/// entry function or from an exported function (when compiling shader
+/// library).
+///
+/// This is done by traversing the AST of all shader entry point functions
+/// and of all exported functions, and any functions that are refrenced
+/// from this AST. In other words, any function that are reachable from
+/// the entry points.
+class DiagnoseHLSLAvailability
+ : public RecursiveASTVisitor<DiagnoseHLSLAvailability> {
+ // HEKOTAS this is probably not needed
+ // typedef RecursiveASTVisitor<DiagnoseHLSLAvailability> Base;
+
+ Sema &SemaRef;
+
+ // Stack of functions to be scaned
+ llvm::SmallVector<const FunctionDecl *, 8> DeclsToScan;
+
+ // List of functions that were already scanned and in which environment.
+ //
+ // Maps FunctionDecl to a unsigned number that represents a set of shader
+ // environments the function has been scanned for.
+ // Since HLSLShaderAttr::ShaderType enum is generated from Attr.td and is
+ // defined without any assigned values, it is guaranteed to be numbered
+ // sequentially from 0 up and we can use it to 'index' individual bits
+ // in the set.
+ // The N'th bit in the set will be set if the function has been scanned
+ // in shader environment whose ShaderType integer value equals N.
+ // For example, if a function has been scanned in compute and pixel stage
+ // environment, the value will be 0x21 (100001 binary) because
+ // (int)HLSLShaderAttr::ShaderType::Pixel == 1 and
+ // (int)HLSLShaderAttr::ShaderType::Compute == 5.
+ llvm::DenseMap<const FunctionDecl *, unsigned> ScannedDecls;
+
+ // Do not access these directly, use the get/set methods below to make
+ // sure the values are in sync
+ llvm::Triple::EnvironmentType CurrentShaderEnvironment;
+ unsigned CurrentShaderStageBit;
+
+ // True if scanning a function that was already scanned in a different
+ // shader stage context, and therefore we should not report issues that
+ // depend only on shader model version because they would be duplicate.
+ bool ReportOnlyShaderStageIssues;
+
+ void SetShaderStageContext(HLSLShaderAttr::ShaderType ShaderType) {
+ assert((((unsigned)1) << (unsigned)ShaderType) != 0 &&
+ "ShaderType is too big for this bitmap");
+ CurrentShaderEnvironment = HLSLShaderAttr::getTypeAsEnvironment(ShaderType);
+ CurrentShaderStageBit = (1 << ShaderType);
+ }
+ void SetUnknownShaderStageContext() {
+ CurrentShaderEnvironment =
+ llvm::Triple::EnvironmentType::UnknownEnvironment;
+ CurrentShaderStageBit = (1 << 31);
+ }
+ llvm::Triple::EnvironmentType GetCurrentShaderEnvironment() {
+ return CurrentShaderEnvironment;
+ }
+ bool InUnknownShaderStageContext() {
+ return CurrentShaderEnvironment ==
+ llvm::Triple::EnvironmentType::UnknownEnvironment;
+ }
+
+ // Scanning methods
+ void HandleFunctionOrMethodRef(FunctionDecl *FD, Expr *RefExpr);
+ void CheckDeclAvailability(NamedDecl *D, const AvailabilityAttr *AA,
+ SourceRange Range);
+ const AvailabilityAttr *FindAvailabilityAttr(const Decl *D);
+ bool HasMatchingEnvironmentOrNone(const AvailabilityAttr *AA);
+ bool WasAlreadyScannedInCurrentShaderStage(const FunctionDecl *FD,
+ bool *WasNeverScanned = nullptr);
+ void AddToScannedFunctions(const FunctionDecl *FD);
+
+public:
+ DiagnoseHLSLAvailability(Sema &SemaRef) : SemaRef(SemaRef) {}
+
+ // AST traversal methods
+ void RunOnTranslationUnit(const TranslationUnitDecl *TU);
+ void RunOnFunction(const FunctionDecl *FD);
+
+ bool VisitDeclRefExpr(DeclRefExpr *DRE) {
+ FunctionDecl *FD = llvm::dyn_cast<FunctionDecl>(DRE->getDecl());
+ if (FD)
+ HandleFunctionOrMethodRef(FD, DRE);
+ return true;
+ }
+
+ bool VisitMemberExpr(MemberExpr *ME) {
+ FunctionDecl *FD = llvm::dyn_cast<FunctionDecl>(ME->getMemberDecl());
+ if (FD)
+ HandleFunctionOrMethodRef(FD, ME);
+ return true;
+ }
+};
+
+// Returns true if the function has already been scanned in the current
+// shader environment. WasNeverScanned will be set to true if the function
+// has never been scanned before for any shader environment.
+bool DiagnoseHLSLAvailability::WasAlreadyScannedInCurrentShaderStage(
+ const FunctionDecl *FD, bool *WasNeverScanned) {
+ const unsigned &ScannedStages = ScannedDecls.getOrInsertDefault(FD);
+ if (WasNeverScanned)
+ *WasNeverScanned = (ScannedStages == 0);
+ return ScannedStages & CurrentShaderStageBit;
+}
+
+// Marks the function as scanned in the current shader environment
+void DiagnoseHLSLAvailability::AddToScannedFunctions(const FunctionDecl *FD) {
+ unsigned &ScannedStages = ScannedDecls.getOrInsertDefault(FD);
+ ScannedStages |= CurrentShaderStageBit;
+}
+
+void DiagnoseHLSLAvailability::HandleFunctionOrMethodRef(FunctionDecl *FD,
+ Expr *RefExpr) {
+ assert((isa<DeclRefExpr>(RefExpr) || isa<MemberExpr>(RefExpr)) &&
+ "expected DeclRefExpr or MemberExpr");
+
+ // has a definition -> add to stack to be scanned
+ const FunctionDecl *FDWithBody = nullptr;
+ if (FD->hasBody(FDWithBody)) {
+ if (!WasAlreadyScannedInCurrentShaderStage(FDWithBody))
+ DeclsToScan.push_back(FDWithBody);
+ return;
+ }
+
+ // no body -> diagnose availability
+ const AvailabilityAttr *AA = FindAvailabilityAttr(FD);
+ if (AA)
+ CheckDeclAvailability(
+ FD, AA, SourceRange(RefExpr->getBeginLoc(), RefExpr->getEndLoc()));
+}
+
+void DiagnoseHLSLAvailability::RunOnTranslationUnit(
+ const TranslationUnitDecl *TU) {
+ // Iterate over all shader entry functions and library exports, and for those
+ // that have a body (definiton), run diag scan on each, setting appropriate
+ // shader environment context based on whether it is a shader entry function
+ // or an exported function.
+ for (auto &D : TU->decls()) {
+ const FunctionDecl *FD = llvm::dyn_cast<FunctionDecl>(D);
+ if (!FD || !FD->isThisDeclarationADefinition())
+ continue;
+
+ // shader entry point
+ auto ShaderAttr = FD->getAttr<HLSLShaderAttr>();
+ if (ShaderAttr) {
+ SetShaderStageContext(ShaderAttr->getType());
+ RunOnFunction(FD);
+ continue;
+ }
+ // exported library function with definition
+ // FIXME: tracking issue #92073
+#if 0
+ if (FD->getFormalLinkage() == Linkage::External) {
+ SetUnknownShaderStageContext();
+ RunOnFunction(FD);
+ }
+#endif
+ }
+}
+
+void DiagnoseHLSLAvailability::RunOnFunction(const FunctionDecl *FD) {
+ assert(DeclsToScan.empty() && "DeclsToScan should be empty");
+ DeclsToScan.push_back(FD);
+
+ while (!DeclsToScan.empty()) {
+ // Take one decl from the stack and check it by traversing its AST.
+ // For any CallExpr found during the traversal add it's callee to the top of
+ // the stack to be processed next. Functions already processed are stored in
+ // ScannedDecls.
+ const FunctionDecl *FD = DeclsToScan.back();
+ DeclsToScan.pop_back();
+
+ // Decl was already scanned
+ bool WasNeverScanned;
+ if (WasAlreadyScannedInCurrentShaderStage(FD, &WasNeverScanned))
+ continue;
+
+ ReportOnlyShaderStageIssues = !WasNeverScanned;
+
+ AddToScannedFunctions(FD);
+
+ Stmt *Body = FD->getBody();
+ assert(Body && "full definition with body expected here");
----------------
llvm-beanz wrote:
nit: This assert probably doesn't need to be here since you do explicitly check that the decl has a body before adding it to DeclsToScan.
https://github.com/llvm/llvm-project/pull/92704
More information about the cfe-commits
mailing list