Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HLSL] Default and Relaxed Availability Diagnostics #92704

Merged
merged 13 commits into from
May 30, 2024

Conversation

hekota
Copy link
Member

@hekota hekota commented May 19, 2024

Implements HLSL availability diagnostics' default and relaxed mode.

HLSL availability diagnostics emits errors or warning when unavailable shader APIs are used. Unavailable shader APIs are APIs that are exposed in HLSL code but are not available in the target shader stage or shader model version.

In the default mode the compiler emits an error when an unavailable API is found in a code that is reachable from the shader entry point function. In the future this check will also extended to exported library functions (#92073). The relaxed diagnostic mode is the same except the compiler emits a warning. This mode is enabled by -Wno-error=hlsl-availability.

See HLSL Availability Diagnostics design doc here for more details.

Fixes #90095

Copy link

github-actions bot commented May 19, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@hekota hekota changed the title HLSL Default and Relaxed Availability Diagnostics [HLSL] Default and Relaxed Availability Diagnostics May 19, 2024
@hekota hekota marked this pull request as ready for review May 20, 2024 14:53
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" HLSL HLSL Language Support labels May 20, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented May 20, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-hlsl

Author: Helena Kotas (hekota)

Changes

Implements HLSL availability diagnostics' default and relaxed mode.

HLSL availability diagnostics emits errors or warning when unavailable shader APIs are used. Unavailable shader APIs are APIs that are exposed in HLSL code but are not available in the target shader stage or shader model version.

In the default mode the compiler emits an error when an unavailable API is found in a code that is reachable from the shader entry point function. In the future this check will also extended to exported library functions (#92073). The relaxed diagnostic mode is the same except the compiler emits a warning. This mode is enabled by -Wno-error=hlsl-availability.

See HLSL Availability Diagnostics design doc here for more details.

Fixes #90095


Patch is 64.73 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/92704.diff

20 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+52)
  • (modified) clang/include/clang/Basic/DiagnosticGroups.td (+3)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+7)
  • (modified) clang/include/clang/Sema/SemaHLSL.h (+1)
  • (modified) clang/lib/Sema/Sema.cpp (+4)
  • (modified) clang/lib/Sema/SemaAvailability.cpp (+12-2)
  • (modified) clang/lib/Sema/SemaHLSL.cpp (+296)
  • (added) clang/test/Sema/attr-availability-shadermodel-compute.hlsl (+68)
  • (added) clang/test/Sema/attr-availability-shadermodel-mesh.hlsl (+68)
  • (added) clang/test/Sema/attr-availability-shadermodel-pixel.hlsl (+61)
  • (added) clang/test/Sema/attr-availability-shadermodel.hlsl (+11)
  • (modified) clang/test/SemaHLSL/Availability/attr-availability-compute.hlsl (+5-10)
  • (modified) clang/test/SemaHLSL/Availability/attr-availability-mesh.hlsl (+5-10)
  • (modified) clang/test/SemaHLSL/Availability/attr-availability-pixel.hlsl (+2-4)
  • (added) clang/test/SemaHLSL/Availability/avail-diag-default-compute.hlsl (+98)
  • (added) clang/test/SemaHLSL/Availability/avail-diag-default-lib.hlsl (+108)
  • (added) clang/test/SemaHLSL/Availability/avail-diag-relaxed-compute.hlsl (+98)
  • (added) clang/test/SemaHLSL/Availability/avail-diag-relaxed-lib.hlsl (+108)
  • (added) clang/test/SemaHLSL/Availability/avail-lib-multiple-stages.hlsl (+57)
  • (modified) clang/test/SemaHLSL/WaveBuiltinAvailability.hlsl (+5-4)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 7008bea483c87..e5c23d54ab826 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1068,11 +1068,37 @@ static llvm::StringRef getPrettyEnviromentName(llvm::StringRef Environment) {
              .Case("hull", "hull shader")
              .Case("domain", "domain shader")
              .Case("compute", "compute shader")
+             .Case("raygeneration", "ray generation shader")
+             .Case("intersection", "intersection shader")
+             .Case("anyhit", "anyhit shader")
+             .Case("closesthit", "closesthit shader")
+             .Case("miss", "miss shader")
+             .Case("callable", "callable shader")
              .Case("mesh", "mesh shader")
              .Case("amplification", "amplification shader")
              .Case("library", "shader library")
              .Default(Environment);
 }
+static llvm::StringRef getEnvironmentString(llvm::Triple::EnvironmentType EnvironmentType) {
+  switch (EnvironmentType) {
+    case llvm::Triple::EnvironmentType::Pixel: return "pixel";
+    case llvm::Triple::EnvironmentType::Vertex: return "vertex";
+    case llvm::Triple::EnvironmentType::Geometry: return "geometry";
+    case llvm::Triple::EnvironmentType::Hull: return "hull";
+    case llvm::Triple::EnvironmentType::Domain: return "domain";
+    case llvm::Triple::EnvironmentType::Compute: return "compute";
+    case llvm::Triple::EnvironmentType::RayGeneration: return "ray generation";
+    case llvm::Triple::EnvironmentType::Intersection: return "intersection";
+    case llvm::Triple::EnvironmentType::AnyHit: return "any hit";
+    case llvm::Triple::EnvironmentType::ClosestHit: return "closest hit";
+    case llvm::Triple::EnvironmentType::Miss: return "miss";
+    case llvm::Triple::EnvironmentType::Callable: return "callable";
+    case llvm::Triple::EnvironmentType::Mesh: return "mesh";
+    case llvm::Triple::EnvironmentType::Amplification: return "amplification";
+    case llvm::Triple::EnvironmentType::Library: return "library";
+    default: return "";
+  }
+}
 static llvm::Triple::EnvironmentType getEnvironmentType(llvm::StringRef Environment) {
     return llvm::StringSwitch<llvm::Triple::EnvironmentType>(Environment)
              .Case("pixel", llvm::Triple::Pixel)
@@ -1081,6 +1107,12 @@ static llvm::Triple::EnvironmentType getEnvironmentType(llvm::StringRef Environm
              .Case("hull", llvm::Triple::Hull)
              .Case("domain", llvm::Triple::Domain)
              .Case("compute", llvm::Triple::Compute)
+             .Case("raygeneration", llvm::Triple::RayGeneration)
+             .Case("intersection", llvm::Triple::Intersection)
+             .Case("anyhit", llvm::Triple::AnyHit)
+             .Case("closesthit", llvm::Triple::ClosestHit)
+             .Case("miss", llvm::Triple::Miss)
+             .Case("callable", llvm::Triple::Callable)
              .Case("mesh", llvm::Triple::Mesh)
              .Case("amplification", llvm::Triple::Amplification)
              .Case("library", llvm::Triple::Library)
@@ -4475,6 +4507,26 @@ def HLSLShader : InheritableAttr {
                   "Miss", "Callable", "Mesh", "Amplification"]>
   ];
   let Documentation = [HLSLSV_ShaderTypeAttrDocs];
+  let AdditionalMembers =
+[{static llvm::Triple::EnvironmentType getTypeAsEnvironment(HLSLShaderAttr::ShaderType ShaderType) {
+    switch (ShaderType) {
+      case HLSLShaderAttr::Pixel:         return llvm::Triple::EnvironmentType::Pixel;
+      case HLSLShaderAttr::Vertex:        return llvm::Triple::EnvironmentType::Vertex;
+      case HLSLShaderAttr::Geometry:      return llvm::Triple::EnvironmentType::Geometry;
+      case HLSLShaderAttr::Hull:          return llvm::Triple::EnvironmentType::Hull;
+      case HLSLShaderAttr::Domain:        return llvm::Triple::EnvironmentType::Domain;
+      case HLSLShaderAttr::Compute:       return llvm::Triple::EnvironmentType::Compute;
+      case HLSLShaderAttr::RayGeneration: return llvm::Triple::EnvironmentType::RayGeneration;
+      case HLSLShaderAttr::Intersection:  return llvm::Triple::EnvironmentType::Intersection;
+      case HLSLShaderAttr::AnyHit:        return llvm::Triple::EnvironmentType::AnyHit;
+      case HLSLShaderAttr::ClosestHit:    return llvm::Triple::EnvironmentType::ClosestHit;
+      case HLSLShaderAttr::Miss:          return llvm::Triple::EnvironmentType::Miss;
+      case HLSLShaderAttr::Callable:      return llvm::Triple::EnvironmentType::Callable;
+      case HLSLShaderAttr::Mesh:          return llvm::Triple::EnvironmentType::Mesh;
+      case HLSLShaderAttr::Amplification: return llvm::Triple::EnvironmentType::Amplification;
+    }
+  }
+}];
 }
 
 def HLSLResource : InheritableAttr {
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 4cb4f3d999f7a..f239198b5f090 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1515,6 +1515,9 @@ def HLSLMixPackOffset : DiagGroup<"mix-packoffset">;
 // Warnings for DXIL validation
 def DXILValidation : DiagGroup<"dxil-validation">;
 
+// Warning for HLSL API availability
+def HLSLAvailability : DiagGroup<"hlsl-availability">;
+
 // Warnings and notes related to const_var_decl_type attribute checks
 def ReadOnlyPlacementChecks : DiagGroup<"read-only-types">;
 
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index e3b4186f1b06f..874e7ff187ee2 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12224,6 +12224,13 @@ def err_hlsl_param_qualifier_mismatch :
 def warn_hlsl_impcast_vector_truncation : Warning<
   "implicit conversion truncates vector: %0 to %1">, InGroup<Conversion>;
 
+def warn_hlsl_availability : Warning<
+  "%0 is only available %select{|in %4 environment }3on %1 %2 or newer">,
+  InGroup<HLSLAvailability>, DefaultError;
+def warn_hlsl_availability_unavailable :
+  Warning<err_unavailable.Summary>,
+  InGroup<HLSLAvailability>, DefaultError;
+
 // Layout randomization diagnostics.
 def err_non_designated_init_used : Error<
   "a randomized struct can only be initialized with a designated initializer">;
diff --git a/clang/include/clang/Sema/SemaHLSL.h b/clang/include/clang/Sema/SemaHLSL.h
index 34acaf19517f2..eac1f7c07c85d 100644
--- a/clang/include/clang/Sema/SemaHLSL.h
+++ b/clang/include/clang/Sema/SemaHLSL.h
@@ -49,6 +49,7 @@ class SemaHLSL : public SemaBase {
   void DiagnoseAttrStageMismatch(
       const Attr *A, HLSLShaderAttr::ShaderType Stage,
       std::initializer_list<HLSLShaderAttr::ShaderType> AllowedStages);
+  void DiagnoseAvailabilityViolations(TranslationUnitDecl *TU);
 };
 
 } // namespace clang
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index f847c49920cf3..8ee40d3e0d0e4 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1351,6 +1351,10 @@ void Sema::ActOnEndOfTranslationUnit() {
     Consumer.CompleteExternalDeclaration(D);
   }
 
+  if (LangOpts.HLSL)
+    HLSL().DiagnoseAvailabilityViolations(
+        getASTContext().getTranslationUnitDecl());
+
   // If there were errors, disable 'unused' warnings since they will mostly be
   // noise. Don't warn for a use from a module: either we should warn on all
   // file-scope declarations in modules or not at all, but whether the
diff --git a/clang/lib/Sema/SemaAvailability.cpp b/clang/lib/Sema/SemaAvailability.cpp
index 663b6f35b869d..e5cf3037aedef 100644
--- a/clang/lib/Sema/SemaAvailability.cpp
+++ b/clang/lib/Sema/SemaAvailability.cpp
@@ -15,6 +15,7 @@
 #include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/Basic/DiagnosticSema.h"
 #include "clang/Basic/IdentifierTable.h"
+#include "clang/Basic/LangOptions.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/Lex/Preprocessor.h"
 #include "clang/Sema/DelayedDiagnostic.h"
@@ -228,8 +229,9 @@ shouldDiagnoseAvailabilityByDefault(const ASTContext &Context,
     ForceAvailabilityFromVersion = VersionTuple(/*Major=*/10, /*Minor=*/13);
     break;
   case llvm::Triple::ShaderModel:
-    // Always enable availability diagnostics for shader models.
-    return true;
+    // FIXME: This will be updated when HLSL strict diagnostic mode
+    // is implemented (issue #90096)
+    return false;
   default:
     // New targets should always warn about availability.
     return Triple.getVendor() == llvm::Triple::Apple;
@@ -438,6 +440,10 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
         << S.Context.getTargetInfo().getPlatformMinVersion().getAsString()
         << UseEnvironment << AttrEnvironment << TargetEnvironment;
 
+    // Do not offer to silence the warning or fixits for HLSL
+    if (S.getLangOpts().HLSL)
+      return;
+
     if (const auto *Enclosing = findEnclosingDeclToAnnotate(Ctx)) {
       if (const auto *TD = dyn_cast<TagDecl>(Enclosing))
         if (TD->getDeclName().isEmpty()) {
@@ -865,6 +871,10 @@ void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability(
         << SemaRef.Context.getTargetInfo().getPlatformMinVersion().getAsString()
         << UseEnvironment << AttrEnvironment << TargetEnvironment;
 
+    // Do not offer to silence the warning or fixits for HLSL
+    if (SemaRef.getLangOpts().HLSL)
+      return;
+
     auto FixitDiag =
         SemaRef.Diag(Range.getBegin(), diag::note_unguarded_available_silence)
         << Range << D
diff --git a/clang/lib/Sema/SemaHLSL.cpp b/clang/lib/Sema/SemaHLSL.cpp
index 6a12c417e2f3a..6e407cfc4e375 100644
--- a/clang/lib/Sema/SemaHLSL.cpp
+++ b/clang/lib/Sema/SemaHLSL.cpp
@@ -9,6 +9,9 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/Sema/SemaHLSL.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/Basic/DiagnosticSema.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/TargetInfo.h"
@@ -16,6 +19,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Casting.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/TargetParser/Triple.h"
 #include <iterator>
@@ -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");
+
+    TraverseStmt(Body);
+  }
+}
+
+bool DiagnoseHLSLAvailability::HasMatchingEnvironmentOrNone(
+    const AvailabilityAttr *AA) {
+  IdentifierInfo *IIEnvironment = AA->getEnvironment();
+  if (!IIEnvironment)
+    return true;
+
+  llvm::Triple::EnvironmentType CurrentEnv = GetCurrentShaderEnvironment();
+  if (CurrentEnv == llvm::Triple::UnknownEnvironment)
+    return false;
+
+  llvm::Triple::EnvironmentType AttrEnv =
+      AvailabilityAttr::getEnvironmentType(IIEnvironment->getName());
+
+  return CurrentEnv == AttrEnv;
+}
+
+const AvailabilityAttr *
+DiagnoseHLSLAvailability::FindAvailabilityAttr(const Decl *D) {
+  AvailabilityAttr const *PartialMatch = nullptr;
+  // Check each AvailabilityAttr to find the one for this platform.
+  // For multiple attributes with the same platform try to find one for this
+  // environment.
+  for (const auto *A : D->attrs()) {
+    if (const auto *Avail = dyn_cast<AvailabilityAttr>(A)) {
+      StringRef AttrPlatform = Avail->getPlatform()->getName();
+      StringRef TargetPlatform =
+          SemaRef.getASTContext().getTargetInfo().getPlatformName();
+
+      // Match the platform name.
+      if (AttrPlatform == TargetPlatform) {
+        // Find the best matching attribute for this environment
+        if (HasMatchingEnvironmentOrNone(Avail))
+          return Avail;
+        PartialMatch = Avail;
+      }
+    }
+  }
+  return PartialMatch;
+}
+
+// Check availability against target shader model version and current shader
+// stage and emit diagnostic
+void DiagnoseHLSLAvailability::CheckDeclAvailability(NamedDecl *D,
+                                                     const AvailabilityAttr *AA,
+                                                     SourceRange Range) {
+  if (ReportOnlyShaderStageIssues && !AA->getEnvironment())
+    return;
+
+  bool EnvironmentMatches = HasMatchingEnvironmentOrNone(AA);
+  VersionTuple Introduced = AA->getIntroduced();
+  VersionTuple TargetVersion =
+      SemaRef.Context.getTargetInfo().getPlatformMinVersion();
+
+  if (TargetVersion >= Introduced && EnvironmentMatches)
+    return;
+
+  // Do not diagnose shade-stage-specific availability when the shader stage
+  // context is unknown
+  if (InUnknownShaderStageContext() && AA->getEnvironment() != nullptr) {
+    return;
+  }
+
+  // Emit diagnos...
[truncated]

Copy link
Collaborator

@llvm-beanz llvm-beanz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good to me. A few nits that you can take or leave.

The one real concern I have is that I actually don't like getPrettyEnviromentName being a string->string mapping. Is it possible to instead use the Triple environment enum?

clang/include/clang/Basic/Attr.td Outdated Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
clang/include/clang/Basic/Attr.td Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Show resolved Hide resolved
clang/lib/Sema/SemaHLSL.cpp Outdated Show resolved Hide resolved
Co-authored-by: Damyan Pepper <damyanp@microsoft.com>
Copy link
Collaborator

@llvm-beanz llvm-beanz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have one suggested code simplification which will also require test updates, but in general I think this is good.

@hekota hekota merged commit 8890209 into llvm:main May 30, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category HLSL HLSL Language Support
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

[HLSL] Implement availability diagnostic - Default and Relaxed modes
4 participants