Skip to content

[clang] Improve diagnostics for constraints of inline asm (NFC) #96363

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

e-kud
Copy link
Contributor

@e-kud e-kud commented Jun 21, 2024

Introduce more detailed diagnostics for the constrains. Also provide an opportunity for backends to provide detailed diagnostics for target specific constraints based on enabled features. We provide features as a pointer intentionally because they are not available in some of existing uses. So backends need to consider whether features are available or not.

Introduce more detailed diagnostics for the constrains. Also provide an
opportunity for backends to provide detailed diagnostics for target
specific constraints based on enabled features.
@llvmbot
Copy link
Member

llvmbot commented Jun 21, 2024

@llvm/pr-subscribers-backend-webassembly
@llvm/pr-subscribers-backend-sparc
@llvm/pr-subscribers-backend-risc-v
@llvm/pr-subscribers-backend-loongarch
@llvm/pr-subscribers-backend-systemz
@llvm/pr-subscribers-backend-aarch64
@llvm/pr-subscribers-backend-powerpc
@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Evgenii Kudriashov (e-kud)

Changes

Introduce more detailed diagnostics for the constrains. Also provide an opportunity for backends to provide detailed diagnostics for target specific constraints based on enabled features. We provide features as a pointer intentionally because they are not available in some of existing uses. So backends need to consider whether features are available or not.


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

43 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticCommonKinds.td (+33)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (-4)
  • (modified) clang/include/clang/Basic/TargetInfo.h (+12-7)
  • (modified) clang/lib/Basic/TargetInfo.cpp (+45-18)
  • (modified) clang/lib/Basic/Targets/AArch64.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/AArch64.h (+3-1)
  • (modified) clang/lib/Basic/Targets/AMDGPU.h (+5-2)
  • (modified) clang/lib/Basic/Targets/ARC.h (+3-1)
  • (modified) clang/lib/Basic/Targets/ARM.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/ARM.h (+3-1)
  • (modified) clang/lib/Basic/Targets/AVR.h (+3-1)
  • (modified) clang/lib/Basic/Targets/BPF.h (+3-1)
  • (modified) clang/lib/Basic/Targets/CSKY.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/CSKY.h (+3-1)
  • (modified) clang/lib/Basic/Targets/DirectX.h (+3-1)
  • (modified) clang/lib/Basic/Targets/Hexagon.h (+3-1)
  • (modified) clang/lib/Basic/Targets/Lanai.h (+3-1)
  • (modified) clang/lib/Basic/Targets/Le64.h (+3-1)
  • (modified) clang/lib/Basic/Targets/LoongArch.cpp (+2-1)
  • (modified) clang/lib/Basic/Targets/LoongArch.h (+3-1)
  • (modified) clang/lib/Basic/Targets/M68k.cpp (+16-14)
  • (modified) clang/lib/Basic/Targets/M68k.h (+3-1)
  • (modified) clang/lib/Basic/Targets/MSP430.h (+3-1)
  • (modified) clang/lib/Basic/Targets/Mips.h (+3-1)
  • (modified) clang/lib/Basic/Targets/NVPTX.h (+3-1)
  • (modified) clang/lib/Basic/Targets/PNaCl.h (+3-1)
  • (modified) clang/lib/Basic/Targets/PPC.h (+3-1)
  • (modified) clang/lib/Basic/Targets/RISCV.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/RISCV.h (+3-1)
  • (modified) clang/lib/Basic/Targets/SPIR.cpp (+3-2)
  • (modified) clang/lib/Basic/Targets/SPIR.h (+6-2)
  • (modified) clang/lib/Basic/Targets/Sparc.h (+4-2)
  • (modified) clang/lib/Basic/Targets/SystemZ.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/SystemZ.h (+3-1)
  • (modified) clang/lib/Basic/Targets/TCE.h (+3-1)
  • (modified) clang/lib/Basic/Targets/VE.h (+3-1)
  • (modified) clang/lib/Basic/Targets/WebAssembly.h (+3-1)
  • (modified) clang/lib/Basic/Targets/X86.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/X86.h (+3-1)
  • (modified) clang/lib/Basic/Targets/XCore.h (+3-1)
  • (modified) clang/lib/CodeGen/CGStmt.cpp (+15-6)
  • (modified) clang/lib/Sema/SemaStmtAsm.cpp (+10-8)
  • (modified) clang/test/Sema/asm.c (+40-32)
diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td
index de758cbe679dc..d4b0862337165 100644
--- a/clang/include/clang/Basic/DiagnosticCommonKinds.td
+++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td
@@ -309,6 +309,39 @@ def err_asm_invalid_type : Error<
 def err_ms_asm_bitfield_unsupported : Error<
   "an inline asm block cannot have an operand which is a bit-field">;
 
+def asm_invalid_constraint_generic : TextSubstitution<
+  "invalid %select{input|output}0 constraint '%1' in asm">;
+def err_asm_invalid_constraint : Error<
+  "%sub{asm_invalid_constraint_generic}0,1">;
+def err_asm_invalid_constraint_start : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: output constraint must start with"
+  " '=' or '+'">;
+def err_asm_invalid_constraint_rw_clobber : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: early clobber with a read-write"
+  " constraint must be a register">;
+def err_asm_invalid_constraint_mem_or_reg : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: constraint must allow either"
+  " memory or register operands">;
+def err_asm_invalid_constraint_missing_bracket : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: missing ']'">;
+def err_asm_invalid_constraint_wrong_symbol : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: cannot find an output constraint"
+  " with the specified name">;
+def err_asm_invalid_constraint_empty : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: empty constraint has been"
+  " provided">;
+def err_asm_invalid_constraint_oob : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: the index is out of bounds">;
+def err_asm_invalid_constraint_missing : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: references to a non-existing output"
+  " constraint">;
+def err_asm_invalid_constraint_wrongly_tied : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: tied constraint must be tied to"
+  " the same operand referenced to by the number">;
+def err_asm_invalid_constraint_output_only : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: must refer to an output only"
+  " operand">;
+
 def warn_stack_clash_protection_inline_asm : Warning<
   "unable to protect inline asm that clobbers stack pointer against stack "
   "clash">, InGroup<DiagGroup<"stack-protector">>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 25a87078a5709..3cb5b05d23dd0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9280,12 +9280,8 @@ let CategoryName = "Inline Assembly Issue" in {
     : Error<"cannot pass a pointer-to-member through register-constrained "
             "inline assembly parameter">;
   def err_asm_invalid_lvalue_in_output : Error<"invalid lvalue in asm output">;
-  def err_asm_invalid_output_constraint : Error<
-    "invalid output constraint '%0' in asm">;
   def err_asm_invalid_lvalue_in_input : Error<
     "invalid lvalue in asm input for constraint '%0'">;
-  def err_asm_invalid_input_constraint : Error<
-    "invalid input constraint '%0' in asm">;
   def err_asm_tying_incompatible_types : Error<
     "unsupported inline asm: input with type "
     "%diff{$ matching output with type $|}0,1">;
diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index 9b0ae2102e098..0d2773546d2f3 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -17,6 +17,7 @@
 #include "clang/Basic/AddressSpaces.h"
 #include "clang/Basic/BitmaskEnum.h"
 #include "clang/Basic/CodeGenOptions.h"
+#include "clang/Basic/DiagnosticIDs.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/LangOptions.h"
 #include "clang/Basic/Specifiers.h"
@@ -1197,9 +1198,12 @@ class TargetInfo : public TransferrableTargetInfo,
   // validateOutputConstraint, validateInputConstraint - Checks that
   // a constraint is valid and provides information about it.
   // FIXME: These should return a real error instead of just true/false.
-  bool validateOutputConstraint(ConstraintInfo &Info) const;
-  bool validateInputConstraint(MutableArrayRef<ConstraintInfo> OutputConstraints,
-                               ConstraintInfo &info) const;
+  bool validateOutputConstraint(ConstraintInfo &Info,
+                                llvm::StringMap<bool> *FeatureMap,
+                                diag::kind &Diag) const;
+  bool validateInputConstraint(
+      MutableArrayRef<ConstraintInfo> OutputConstraints, ConstraintInfo &Info,
+      llvm::StringMap<bool> *FeatureMap, diag::kind &Diag) const;
 
   virtual bool validateOutputSize(const llvm::StringMap<bool> &FeatureMap,
                                   StringRef /*Constraint*/,
@@ -1219,13 +1223,14 @@ class TargetInfo : public TransferrableTargetInfo,
                              std::string &/*SuggestedModifier*/) const {
     return true;
   }
-  virtual bool
-  validateAsmConstraint(const char *&Name,
-                        TargetInfo::ConstraintInfo &info) const = 0;
+  virtual bool validateAsmConstraint(const char *&Name,
+                                     TargetInfo::ConstraintInfo &Info,
+                                     llvm::StringMap<bool> *FeatureMap,
+                                     diag::kind &Diag) const = 0;
 
   bool resolveSymbolicName(const char *&Name,
                            ArrayRef<ConstraintInfo> OutputConstraints,
-                           unsigned &Index) const;
+                           unsigned &Index, diag::kind &Diag) const;
 
   // Constraint parm will be left pointing at the last character of
   // the constraint.  In practice, it won't be changed unless the
diff --git a/clang/lib/Basic/TargetInfo.cpp b/clang/lib/Basic/TargetInfo.cpp
index 29f5cd14e46e1..adc168410bfe5 100644
--- a/clang/lib/Basic/TargetInfo.cpp
+++ b/clang/lib/Basic/TargetInfo.cpp
@@ -722,11 +722,15 @@ StringRef TargetInfo::getNormalizedGCCRegisterName(StringRef Name,
   return Name;
 }
 
-bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
+bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info,
+                                          llvm::StringMap<bool> *FeatureMap,
+                                          diag::kind &Diag) const {
   const char *Name = Info.getConstraintStr().c_str();
   // An output constraint must start with '=' or '+'
-  if (*Name != '=' && *Name != '+')
+  if (*Name != '=' && *Name != '+') {
+    Diag = diag::err_asm_invalid_constraint_start;
     return false;
+  }
 
   if (*Name == '+')
     Info.setIsReadWrite();
@@ -735,7 +739,7 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
   while (*Name) {
     switch (*Name) {
     default:
-      if (!validateAsmConstraint(Name, Info)) {
+      if (!validateAsmConstraint(Name, Info, FeatureMap, Diag)) {
         // FIXME: We temporarily return false
         // so we can add more constraints as we hit it.
         // Eventually, an unknown constraint should just be treated as 'g'.
@@ -788,17 +792,23 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
 
   // Early clobber with a read-write constraint which doesn't permit registers
   // is invalid.
-  if (Info.earlyClobber() && Info.isReadWrite() && !Info.allowsRegister())
+  if (Info.earlyClobber() && Info.isReadWrite() && !Info.allowsRegister()) {
+    Diag = diag::err_asm_invalid_constraint_rw_clobber;
     return false;
+  }
 
   // If a constraint allows neither memory nor register operands it contains
   // only modifiers. Reject it.
-  return Info.allowsMemory() || Info.allowsRegister();
+  if (!Info.allowsMemory() && !Info.allowsRegister()) {
+    Diag = diag::err_asm_invalid_constraint_mem_or_reg;
+    return false;
+  }
+  return true;
 }
 
 bool TargetInfo::resolveSymbolicName(const char *&Name,
                                      ArrayRef<ConstraintInfo> OutputConstraints,
-                                     unsigned &Index) const {
+                                     unsigned &Index, diag::kind &Diag) const {
   assert(*Name == '[' && "Symbolic name did not start with '['");
   Name++;
   const char *Start = Name;
@@ -807,6 +817,7 @@ bool TargetInfo::resolveSymbolicName(const char *&Name,
 
   if (!*Name) {
     // Missing ']'
+    Diag = diag::err_asm_invalid_constraint_missing_bracket;
     return false;
   }
 
@@ -816,16 +827,19 @@ bool TargetInfo::resolveSymbolicName(const char *&Name,
     if (SymbolicName == OutputConstraints[Index].getName())
       return true;
 
+  Diag = diag::err_asm_invalid_constraint_wrong_symbol;
   return false;
 }
 
 bool TargetInfo::validateInputConstraint(
-                              MutableArrayRef<ConstraintInfo> OutputConstraints,
-                              ConstraintInfo &Info) const {
+    MutableArrayRef<ConstraintInfo> OutputConstraints, ConstraintInfo &Info,
+    llvm::StringMap<bool> *FeatureMap, diag::kind &Diag) const {
   const char *Name = Info.ConstraintStr.c_str();
 
-  if (!*Name)
+  if (!*Name) {
+    Diag = diag::err_asm_invalid_constraint_empty;
     return false;
+  }
 
   while (*Name) {
     switch (*Name) {
@@ -838,25 +852,34 @@ bool TargetInfo::validateInputConstraint(
         const char *DigitEnd = Name;
         unsigned i;
         if (StringRef(DigitStart, DigitEnd - DigitStart + 1)
-                .getAsInteger(10, i))
+                .getAsInteger(10, i)) {
+          Diag = diag::err_asm_invalid_constraint_oob;
           return false;
+        }
 
         // Check if matching constraint is out of bounds.
-        if (i >= OutputConstraints.size()) return false;
+        if (i >= OutputConstraints.size()) {
+          Diag = diag::err_asm_invalid_constraint_missing;
+          return false;
+        }
 
         // A number must refer to an output only operand.
-        if (OutputConstraints[i].isReadWrite())
+        if (OutputConstraints[i].isReadWrite()) {
+          Diag = diag::err_asm_invalid_constraint_output_only;
           return false;
+        }
 
         // If the constraint is already tied, it must be tied to the
         // same operand referenced to by the number.
-        if (Info.hasTiedOperand() && Info.getTiedOperand() != i)
+        if (Info.hasTiedOperand() && Info.getTiedOperand() != i) {
+          Diag = diag::err_asm_invalid_constraint_wrongly_tied;
           return false;
+        }
 
         // The constraint should have the same info as the respective
         // output constraint.
         Info.setTiedOperand(i, OutputConstraints[i]);
-      } else if (!validateAsmConstraint(Name, Info)) {
+      } else if (!validateAsmConstraint(Name, Info, FeatureMap, Diag)) {
         // FIXME: This error return is in place temporarily so we can
         // add more constraints as we hit it.  Eventually, an unknown
         // constraint should just be treated as 'g'.
@@ -865,17 +888,21 @@ bool TargetInfo::validateInputConstraint(
       break;
     case '[': {
       unsigned Index = 0;
-      if (!resolveSymbolicName(Name, OutputConstraints, Index))
+      if (!resolveSymbolicName(Name, OutputConstraints, Index, Diag))
         return false;
 
       // If the constraint is already tied, it must be tied to the
       // same operand referenced to by the number.
-      if (Info.hasTiedOperand() && Info.getTiedOperand() != Index)
+      if (Info.hasTiedOperand() && Info.getTiedOperand() != Index) {
+        Diag = diag::err_asm_invalid_constraint_wrongly_tied;
         return false;
+      }
 
       // A number must refer to an output only operand.
-      if (OutputConstraints[Index].isReadWrite())
+      if (OutputConstraints[Index].isReadWrite()) {
+        Diag = diag::err_asm_invalid_constraint_output_only;
         return false;
+      }
 
       Info.setTiedOperand(Index, OutputConstraints[Index]);
       break;
@@ -896,7 +923,7 @@ bool TargetInfo::validateInputConstraint(
     case 'N':
     case 'O':
     case 'P':
-      if (!validateAsmConstraint(Name, Info))
+      if (!validateAsmConstraint(Name, Info, FeatureMap, Diag))
         return false;
       break;
     case 'r': // general register.
diff --git a/clang/lib/Basic/Targets/AArch64.cpp b/clang/lib/Basic/Targets/AArch64.cpp
index 31d8121b91d10..4cd2b7fa821ca 100644
--- a/clang/lib/Basic/Targets/AArch64.cpp
+++ b/clang/lib/Basic/Targets/AArch64.cpp
@@ -1339,8 +1339,10 @@ AArch64TargetInfo::convertConstraint(const char *&Constraint) const {
   return R;
 }
 
-bool AArch64TargetInfo::validateAsmConstraint(
-    const char *&Name, TargetInfo::ConstraintInfo &Info) const {
+bool AArch64TargetInfo::validateAsmConstraint(const char *&Name,
+                                              TargetInfo::ConstraintInfo &Info,
+                                              llvm::StringMap<bool> *FeatureMap,
+                                              diag::kind &Diag) const {
   switch (*Name) {
   default:
     return false;
diff --git a/clang/lib/Basic/Targets/AArch64.h b/clang/lib/Basic/Targets/AArch64.h
index 71510fe289510..2cea2d4a3852a 100644
--- a/clang/lib/Basic/Targets/AArch64.h
+++ b/clang/lib/Basic/Targets/AArch64.h
@@ -176,7 +176,9 @@ class LLVM_LIBRARY_VISIBILITY AArch64TargetInfo : public TargetInfo {
   std::string convertConstraint(const char *&Constraint) const override;
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override;
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override;
   bool
   validateConstraintModifier(StringRef Constraint, char Modifier, unsigned Size,
                              std::string &SuggestedModifier) const override;
diff --git a/clang/lib/Basic/Targets/AMDGPU.h b/clang/lib/Basic/Targets/AMDGPU.h
index 94d9ba93ed226..75ddcb3d582a2 100644
--- a/clang/lib/Basic/Targets/AMDGPU.h
+++ b/clang/lib/Basic/Targets/AMDGPU.h
@@ -137,7 +137,9 @@ class LLVM_LIBRARY_VISIBILITY AMDGPUTargetInfo final : public TargetInfo {
   /// {s[n:m]}
   /// {a[n:m]}
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override {
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override {
     static const ::llvm::StringSet<> SpecialRegs({
         "exec", "vcc", "flat_scratch", "m0", "scc", "tba", "tma",
         "flat_scratch_lo", "flat_scratch_hi", "vcc_lo", "vcc_hi", "exec_lo",
@@ -232,7 +234,8 @@ class LLVM_LIBRARY_VISIBILITY AMDGPUTargetInfo final : public TargetInfo {
 
     const char *Begin = Constraint;
     TargetInfo::ConstraintInfo Info("", "");
-    if (validateAsmConstraint(Constraint, Info))
+    diag::kind AsmDiag;
+    if (validateAsmConstraint(Constraint, Info, nullptr, AsmDiag))
       return std::string(Begin).substr(0, Constraint - Begin + 1);
 
     Constraint = Begin;
diff --git a/clang/lib/Basic/Targets/ARC.h b/clang/lib/Basic/Targets/ARC.h
index fcbfdd6eec586..fc4755f48fd4a 100644
--- a/clang/lib/Basic/Targets/ARC.h
+++ b/clang/lib/Basic/Targets/ARC.h
@@ -64,7 +64,9 @@ class LLVM_LIBRARY_VISIBILITY ARCTargetInfo : public TargetInfo {
   }
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override {
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override {
     return false;
   }
 
diff --git a/clang/lib/Basic/Targets/ARM.cpp b/clang/lib/Basic/Targets/ARM.cpp
index 7423626d7c3cb..f1f1ccd3ca103 100644
--- a/clang/lib/Basic/Targets/ARM.cpp
+++ b/clang/lib/Basic/Targets/ARM.cpp
@@ -1136,8 +1136,10 @@ ArrayRef<TargetInfo::GCCRegAlias> ARMTargetInfo::getGCCRegAliases() const {
   return llvm::ArrayRef(GCCRegAliases);
 }
 
-bool ARMTargetInfo::validateAsmConstraint(
-    const char *&Name, TargetInfo::ConstraintInfo &Info) const {
+bool ARMTargetInfo::validateAsmConstraint(const char *&Name,
+                                          TargetInfo::ConstraintInfo &Info,
+                                          llvm::StringMap<bool> *FeatureMap,
+                                          diag::kind &Diag) const {
   switch (*Name) {
   default:
     break;
diff --git a/clang/lib/Basic/Targets/ARM.h b/clang/lib/Basic/Targets/ARM.h
index df9855a52e61c..59e1b7ad64d5f 100644
--- a/clang/lib/Basic/Targets/ARM.h
+++ b/clang/lib/Basic/Targets/ARM.h
@@ -204,7 +204,9 @@ class LLVM_LIBRARY_VISIBILITY ARMTargetInfo : public TargetInfo {
   ArrayRef<const char *> getGCCRegNames() const override;
   ArrayRef<TargetInfo::GCCRegAlias> getGCCRegAliases() const override;
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override;
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override;
   std::string convertConstraint(const char *&Constraint) const override;
   bool
   validateConstraintModifier(StringRef Constraint, char Modifier, unsigned Size,
diff --git a/clang/lib/Basic/Targets/AVR.h b/clang/lib/Basic/Targets/AVR.h
index feeb04f37eeba..6e2061960e591 100644
--- a/clang/lib/Basic/Targets/AVR.h
+++ b/clang/lib/Basic/Targets/AVR.h
@@ -94,7 +94,9 @@ class LLVM_LIBRARY_VISIBILITY AVRTargetInfo : public TargetInfo {
   }
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override {
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override {
     // There aren't any multi-character AVR specific constraints.
     if (StringRef(Name).size() > 1)
       return false;
diff --git a/clang/lib/Basic/Targets/BPF.h b/clang/lib/Basic/Targets/BPF.h
index d19b37dd4df7a..94a0fe90a3c68 100644
--- a/clang/lib/Basic/Targets/BPF.h
+++ b/clang/lib/Basic/Targets/BPF.h
@@ -72,7 +72,9 @@ class LLVM_LIBRARY_VISIBILITY BPFTargetInfo : public TargetInfo {
   }
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override {
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override {
     switch (*Name) {
     default:
       break;
diff --git a/clang/lib/Basic/Targets/CSKY.cpp b/clang/lib/Basic/Targets/CSKY.cpp
index c8bf8b9234d24..f6af91bbe278b 100644
--- a/clang/lib/Basic/Targets/CSKY.cpp
+++ b/clang/lib/Basic/Targets/CSKY.cpp
@@ -289,8 +289,10 @@ ArrayRef<TargetInfo::GCCRegAlias> CSKYTargetInfo::getGCCRegAliases() const {
   return llvm::ArrayRef(GCCRegAliases);
 }
 
-bool CSKYTargetInfo::validateAsmConstraint(
-    const char *&Name, TargetInfo::ConstraintInfo &Info) const {
+bool CSKYTargetInfo::validateAsmConstraint(const char *&Name,
+                                           TargetInfo::ConstraintInfo &Info,
+                                           llvm::StringMap<bool> *FeatureMap,
+                                           diag::kind &Diag) const {
   switch (*Name) {
   default:
     return false;
diff --git a/clang/lib/Basic/Targets/CSKY.h b/clang/lib/Basic/Targets/CSKY.h
index 94d4eeb9a1fff..971bdc5e8b18d 100644
--- a/clang/lib/Basic/Targets/CSKY.h
+++ b/clang/lib/Basic/Targets/CSKY.h
@@ -80,7 +80,9 @@ class LLVM_LIBRARY_VISIBILITY CSKYTargetInfo : public TargetInfo {
   }
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &info) const override;
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override;
 
   std::string_view getClobbers() const override { return ""; }
 
diff --git a/clang/lib/Basic/Targets/DirectX.h b/clang/lib/Basic/Targets/DirectX.h
index a084e2823453f..6a10409f844c3 100644
--- a/clang/lib/Basic/Targets/DirectX.h
+++ b/clang/lib/Basic/T...
[truncated]

@e-kud e-kud requested a review from ostannard June 21, 2024 22:16
@llvmbot
Copy link
Member

llvmbot commented Jun 21, 2024

@llvm/pr-subscribers-backend-amdgpu

Author: Evgenii Kudriashov (e-kud)

Changes

Introduce more detailed diagnostics for the constrains. Also provide an opportunity for backends to provide detailed diagnostics for target specific constraints based on enabled features. We provide features as a pointer intentionally because they are not available in some of existing uses. So backends need to consider whether features are available or not.


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

43 Files Affected:

  • (modified) clang/include/clang/Basic/DiagnosticCommonKinds.td (+33)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (-4)
  • (modified) clang/include/clang/Basic/TargetInfo.h (+12-7)
  • (modified) clang/lib/Basic/TargetInfo.cpp (+45-18)
  • (modified) clang/lib/Basic/Targets/AArch64.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/AArch64.h (+3-1)
  • (modified) clang/lib/Basic/Targets/AMDGPU.h (+5-2)
  • (modified) clang/lib/Basic/Targets/ARC.h (+3-1)
  • (modified) clang/lib/Basic/Targets/ARM.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/ARM.h (+3-1)
  • (modified) clang/lib/Basic/Targets/AVR.h (+3-1)
  • (modified) clang/lib/Basic/Targets/BPF.h (+3-1)
  • (modified) clang/lib/Basic/Targets/CSKY.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/CSKY.h (+3-1)
  • (modified) clang/lib/Basic/Targets/DirectX.h (+3-1)
  • (modified) clang/lib/Basic/Targets/Hexagon.h (+3-1)
  • (modified) clang/lib/Basic/Targets/Lanai.h (+3-1)
  • (modified) clang/lib/Basic/Targets/Le64.h (+3-1)
  • (modified) clang/lib/Basic/Targets/LoongArch.cpp (+2-1)
  • (modified) clang/lib/Basic/Targets/LoongArch.h (+3-1)
  • (modified) clang/lib/Basic/Targets/M68k.cpp (+16-14)
  • (modified) clang/lib/Basic/Targets/M68k.h (+3-1)
  • (modified) clang/lib/Basic/Targets/MSP430.h (+3-1)
  • (modified) clang/lib/Basic/Targets/Mips.h (+3-1)
  • (modified) clang/lib/Basic/Targets/NVPTX.h (+3-1)
  • (modified) clang/lib/Basic/Targets/PNaCl.h (+3-1)
  • (modified) clang/lib/Basic/Targets/PPC.h (+3-1)
  • (modified) clang/lib/Basic/Targets/RISCV.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/RISCV.h (+3-1)
  • (modified) clang/lib/Basic/Targets/SPIR.cpp (+3-2)
  • (modified) clang/lib/Basic/Targets/SPIR.h (+6-2)
  • (modified) clang/lib/Basic/Targets/Sparc.h (+4-2)
  • (modified) clang/lib/Basic/Targets/SystemZ.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/SystemZ.h (+3-1)
  • (modified) clang/lib/Basic/Targets/TCE.h (+3-1)
  • (modified) clang/lib/Basic/Targets/VE.h (+3-1)
  • (modified) clang/lib/Basic/Targets/WebAssembly.h (+3-1)
  • (modified) clang/lib/Basic/Targets/X86.cpp (+4-2)
  • (modified) clang/lib/Basic/Targets/X86.h (+3-1)
  • (modified) clang/lib/Basic/Targets/XCore.h (+3-1)
  • (modified) clang/lib/CodeGen/CGStmt.cpp (+15-6)
  • (modified) clang/lib/Sema/SemaStmtAsm.cpp (+10-8)
  • (modified) clang/test/Sema/asm.c (+40-32)
diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td
index de758cbe679dc..d4b0862337165 100644
--- a/clang/include/clang/Basic/DiagnosticCommonKinds.td
+++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td
@@ -309,6 +309,39 @@ def err_asm_invalid_type : Error<
 def err_ms_asm_bitfield_unsupported : Error<
   "an inline asm block cannot have an operand which is a bit-field">;
 
+def asm_invalid_constraint_generic : TextSubstitution<
+  "invalid %select{input|output}0 constraint '%1' in asm">;
+def err_asm_invalid_constraint : Error<
+  "%sub{asm_invalid_constraint_generic}0,1">;
+def err_asm_invalid_constraint_start : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: output constraint must start with"
+  " '=' or '+'">;
+def err_asm_invalid_constraint_rw_clobber : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: early clobber with a read-write"
+  " constraint must be a register">;
+def err_asm_invalid_constraint_mem_or_reg : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: constraint must allow either"
+  " memory or register operands">;
+def err_asm_invalid_constraint_missing_bracket : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: missing ']'">;
+def err_asm_invalid_constraint_wrong_symbol : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: cannot find an output constraint"
+  " with the specified name">;
+def err_asm_invalid_constraint_empty : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: empty constraint has been"
+  " provided">;
+def err_asm_invalid_constraint_oob : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: the index is out of bounds">;
+def err_asm_invalid_constraint_missing : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: references to a non-existing output"
+  " constraint">;
+def err_asm_invalid_constraint_wrongly_tied : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: tied constraint must be tied to"
+  " the same operand referenced to by the number">;
+def err_asm_invalid_constraint_output_only : Error<
+  "%sub{asm_invalid_constraint_generic}0,1: must refer to an output only"
+  " operand">;
+
 def warn_stack_clash_protection_inline_asm : Warning<
   "unable to protect inline asm that clobbers stack pointer against stack "
   "clash">, InGroup<DiagGroup<"stack-protector">>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 25a87078a5709..3cb5b05d23dd0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9280,12 +9280,8 @@ let CategoryName = "Inline Assembly Issue" in {
     : Error<"cannot pass a pointer-to-member through register-constrained "
             "inline assembly parameter">;
   def err_asm_invalid_lvalue_in_output : Error<"invalid lvalue in asm output">;
-  def err_asm_invalid_output_constraint : Error<
-    "invalid output constraint '%0' in asm">;
   def err_asm_invalid_lvalue_in_input : Error<
     "invalid lvalue in asm input for constraint '%0'">;
-  def err_asm_invalid_input_constraint : Error<
-    "invalid input constraint '%0' in asm">;
   def err_asm_tying_incompatible_types : Error<
     "unsupported inline asm: input with type "
     "%diff{$ matching output with type $|}0,1">;
diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index 9b0ae2102e098..0d2773546d2f3 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -17,6 +17,7 @@
 #include "clang/Basic/AddressSpaces.h"
 #include "clang/Basic/BitmaskEnum.h"
 #include "clang/Basic/CodeGenOptions.h"
+#include "clang/Basic/DiagnosticIDs.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/LangOptions.h"
 #include "clang/Basic/Specifiers.h"
@@ -1197,9 +1198,12 @@ class TargetInfo : public TransferrableTargetInfo,
   // validateOutputConstraint, validateInputConstraint - Checks that
   // a constraint is valid and provides information about it.
   // FIXME: These should return a real error instead of just true/false.
-  bool validateOutputConstraint(ConstraintInfo &Info) const;
-  bool validateInputConstraint(MutableArrayRef<ConstraintInfo> OutputConstraints,
-                               ConstraintInfo &info) const;
+  bool validateOutputConstraint(ConstraintInfo &Info,
+                                llvm::StringMap<bool> *FeatureMap,
+                                diag::kind &Diag) const;
+  bool validateInputConstraint(
+      MutableArrayRef<ConstraintInfo> OutputConstraints, ConstraintInfo &Info,
+      llvm::StringMap<bool> *FeatureMap, diag::kind &Diag) const;
 
   virtual bool validateOutputSize(const llvm::StringMap<bool> &FeatureMap,
                                   StringRef /*Constraint*/,
@@ -1219,13 +1223,14 @@ class TargetInfo : public TransferrableTargetInfo,
                              std::string &/*SuggestedModifier*/) const {
     return true;
   }
-  virtual bool
-  validateAsmConstraint(const char *&Name,
-                        TargetInfo::ConstraintInfo &info) const = 0;
+  virtual bool validateAsmConstraint(const char *&Name,
+                                     TargetInfo::ConstraintInfo &Info,
+                                     llvm::StringMap<bool> *FeatureMap,
+                                     diag::kind &Diag) const = 0;
 
   bool resolveSymbolicName(const char *&Name,
                            ArrayRef<ConstraintInfo> OutputConstraints,
-                           unsigned &Index) const;
+                           unsigned &Index, diag::kind &Diag) const;
 
   // Constraint parm will be left pointing at the last character of
   // the constraint.  In practice, it won't be changed unless the
diff --git a/clang/lib/Basic/TargetInfo.cpp b/clang/lib/Basic/TargetInfo.cpp
index 29f5cd14e46e1..adc168410bfe5 100644
--- a/clang/lib/Basic/TargetInfo.cpp
+++ b/clang/lib/Basic/TargetInfo.cpp
@@ -722,11 +722,15 @@ StringRef TargetInfo::getNormalizedGCCRegisterName(StringRef Name,
   return Name;
 }
 
-bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
+bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info,
+                                          llvm::StringMap<bool> *FeatureMap,
+                                          diag::kind &Diag) const {
   const char *Name = Info.getConstraintStr().c_str();
   // An output constraint must start with '=' or '+'
-  if (*Name != '=' && *Name != '+')
+  if (*Name != '=' && *Name != '+') {
+    Diag = diag::err_asm_invalid_constraint_start;
     return false;
+  }
 
   if (*Name == '+')
     Info.setIsReadWrite();
@@ -735,7 +739,7 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
   while (*Name) {
     switch (*Name) {
     default:
-      if (!validateAsmConstraint(Name, Info)) {
+      if (!validateAsmConstraint(Name, Info, FeatureMap, Diag)) {
         // FIXME: We temporarily return false
         // so we can add more constraints as we hit it.
         // Eventually, an unknown constraint should just be treated as 'g'.
@@ -788,17 +792,23 @@ bool TargetInfo::validateOutputConstraint(ConstraintInfo &Info) const {
 
   // Early clobber with a read-write constraint which doesn't permit registers
   // is invalid.
-  if (Info.earlyClobber() && Info.isReadWrite() && !Info.allowsRegister())
+  if (Info.earlyClobber() && Info.isReadWrite() && !Info.allowsRegister()) {
+    Diag = diag::err_asm_invalid_constraint_rw_clobber;
     return false;
+  }
 
   // If a constraint allows neither memory nor register operands it contains
   // only modifiers. Reject it.
-  return Info.allowsMemory() || Info.allowsRegister();
+  if (!Info.allowsMemory() && !Info.allowsRegister()) {
+    Diag = diag::err_asm_invalid_constraint_mem_or_reg;
+    return false;
+  }
+  return true;
 }
 
 bool TargetInfo::resolveSymbolicName(const char *&Name,
                                      ArrayRef<ConstraintInfo> OutputConstraints,
-                                     unsigned &Index) const {
+                                     unsigned &Index, diag::kind &Diag) const {
   assert(*Name == '[' && "Symbolic name did not start with '['");
   Name++;
   const char *Start = Name;
@@ -807,6 +817,7 @@ bool TargetInfo::resolveSymbolicName(const char *&Name,
 
   if (!*Name) {
     // Missing ']'
+    Diag = diag::err_asm_invalid_constraint_missing_bracket;
     return false;
   }
 
@@ -816,16 +827,19 @@ bool TargetInfo::resolveSymbolicName(const char *&Name,
     if (SymbolicName == OutputConstraints[Index].getName())
       return true;
 
+  Diag = diag::err_asm_invalid_constraint_wrong_symbol;
   return false;
 }
 
 bool TargetInfo::validateInputConstraint(
-                              MutableArrayRef<ConstraintInfo> OutputConstraints,
-                              ConstraintInfo &Info) const {
+    MutableArrayRef<ConstraintInfo> OutputConstraints, ConstraintInfo &Info,
+    llvm::StringMap<bool> *FeatureMap, diag::kind &Diag) const {
   const char *Name = Info.ConstraintStr.c_str();
 
-  if (!*Name)
+  if (!*Name) {
+    Diag = diag::err_asm_invalid_constraint_empty;
     return false;
+  }
 
   while (*Name) {
     switch (*Name) {
@@ -838,25 +852,34 @@ bool TargetInfo::validateInputConstraint(
         const char *DigitEnd = Name;
         unsigned i;
         if (StringRef(DigitStart, DigitEnd - DigitStart + 1)
-                .getAsInteger(10, i))
+                .getAsInteger(10, i)) {
+          Diag = diag::err_asm_invalid_constraint_oob;
           return false;
+        }
 
         // Check if matching constraint is out of bounds.
-        if (i >= OutputConstraints.size()) return false;
+        if (i >= OutputConstraints.size()) {
+          Diag = diag::err_asm_invalid_constraint_missing;
+          return false;
+        }
 
         // A number must refer to an output only operand.
-        if (OutputConstraints[i].isReadWrite())
+        if (OutputConstraints[i].isReadWrite()) {
+          Diag = diag::err_asm_invalid_constraint_output_only;
           return false;
+        }
 
         // If the constraint is already tied, it must be tied to the
         // same operand referenced to by the number.
-        if (Info.hasTiedOperand() && Info.getTiedOperand() != i)
+        if (Info.hasTiedOperand() && Info.getTiedOperand() != i) {
+          Diag = diag::err_asm_invalid_constraint_wrongly_tied;
           return false;
+        }
 
         // The constraint should have the same info as the respective
         // output constraint.
         Info.setTiedOperand(i, OutputConstraints[i]);
-      } else if (!validateAsmConstraint(Name, Info)) {
+      } else if (!validateAsmConstraint(Name, Info, FeatureMap, Diag)) {
         // FIXME: This error return is in place temporarily so we can
         // add more constraints as we hit it.  Eventually, an unknown
         // constraint should just be treated as 'g'.
@@ -865,17 +888,21 @@ bool TargetInfo::validateInputConstraint(
       break;
     case '[': {
       unsigned Index = 0;
-      if (!resolveSymbolicName(Name, OutputConstraints, Index))
+      if (!resolveSymbolicName(Name, OutputConstraints, Index, Diag))
         return false;
 
       // If the constraint is already tied, it must be tied to the
       // same operand referenced to by the number.
-      if (Info.hasTiedOperand() && Info.getTiedOperand() != Index)
+      if (Info.hasTiedOperand() && Info.getTiedOperand() != Index) {
+        Diag = diag::err_asm_invalid_constraint_wrongly_tied;
         return false;
+      }
 
       // A number must refer to an output only operand.
-      if (OutputConstraints[Index].isReadWrite())
+      if (OutputConstraints[Index].isReadWrite()) {
+        Diag = diag::err_asm_invalid_constraint_output_only;
         return false;
+      }
 
       Info.setTiedOperand(Index, OutputConstraints[Index]);
       break;
@@ -896,7 +923,7 @@ bool TargetInfo::validateInputConstraint(
     case 'N':
     case 'O':
     case 'P':
-      if (!validateAsmConstraint(Name, Info))
+      if (!validateAsmConstraint(Name, Info, FeatureMap, Diag))
         return false;
       break;
     case 'r': // general register.
diff --git a/clang/lib/Basic/Targets/AArch64.cpp b/clang/lib/Basic/Targets/AArch64.cpp
index 31d8121b91d10..4cd2b7fa821ca 100644
--- a/clang/lib/Basic/Targets/AArch64.cpp
+++ b/clang/lib/Basic/Targets/AArch64.cpp
@@ -1339,8 +1339,10 @@ AArch64TargetInfo::convertConstraint(const char *&Constraint) const {
   return R;
 }
 
-bool AArch64TargetInfo::validateAsmConstraint(
-    const char *&Name, TargetInfo::ConstraintInfo &Info) const {
+bool AArch64TargetInfo::validateAsmConstraint(const char *&Name,
+                                              TargetInfo::ConstraintInfo &Info,
+                                              llvm::StringMap<bool> *FeatureMap,
+                                              diag::kind &Diag) const {
   switch (*Name) {
   default:
     return false;
diff --git a/clang/lib/Basic/Targets/AArch64.h b/clang/lib/Basic/Targets/AArch64.h
index 71510fe289510..2cea2d4a3852a 100644
--- a/clang/lib/Basic/Targets/AArch64.h
+++ b/clang/lib/Basic/Targets/AArch64.h
@@ -176,7 +176,9 @@ class LLVM_LIBRARY_VISIBILITY AArch64TargetInfo : public TargetInfo {
   std::string convertConstraint(const char *&Constraint) const override;
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override;
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override;
   bool
   validateConstraintModifier(StringRef Constraint, char Modifier, unsigned Size,
                              std::string &SuggestedModifier) const override;
diff --git a/clang/lib/Basic/Targets/AMDGPU.h b/clang/lib/Basic/Targets/AMDGPU.h
index 94d9ba93ed226..75ddcb3d582a2 100644
--- a/clang/lib/Basic/Targets/AMDGPU.h
+++ b/clang/lib/Basic/Targets/AMDGPU.h
@@ -137,7 +137,9 @@ class LLVM_LIBRARY_VISIBILITY AMDGPUTargetInfo final : public TargetInfo {
   /// {s[n:m]}
   /// {a[n:m]}
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override {
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override {
     static const ::llvm::StringSet<> SpecialRegs({
         "exec", "vcc", "flat_scratch", "m0", "scc", "tba", "tma",
         "flat_scratch_lo", "flat_scratch_hi", "vcc_lo", "vcc_hi", "exec_lo",
@@ -232,7 +234,8 @@ class LLVM_LIBRARY_VISIBILITY AMDGPUTargetInfo final : public TargetInfo {
 
     const char *Begin = Constraint;
     TargetInfo::ConstraintInfo Info("", "");
-    if (validateAsmConstraint(Constraint, Info))
+    diag::kind AsmDiag;
+    if (validateAsmConstraint(Constraint, Info, nullptr, AsmDiag))
       return std::string(Begin).substr(0, Constraint - Begin + 1);
 
     Constraint = Begin;
diff --git a/clang/lib/Basic/Targets/ARC.h b/clang/lib/Basic/Targets/ARC.h
index fcbfdd6eec586..fc4755f48fd4a 100644
--- a/clang/lib/Basic/Targets/ARC.h
+++ b/clang/lib/Basic/Targets/ARC.h
@@ -64,7 +64,9 @@ class LLVM_LIBRARY_VISIBILITY ARCTargetInfo : public TargetInfo {
   }
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override {
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override {
     return false;
   }
 
diff --git a/clang/lib/Basic/Targets/ARM.cpp b/clang/lib/Basic/Targets/ARM.cpp
index 7423626d7c3cb..f1f1ccd3ca103 100644
--- a/clang/lib/Basic/Targets/ARM.cpp
+++ b/clang/lib/Basic/Targets/ARM.cpp
@@ -1136,8 +1136,10 @@ ArrayRef<TargetInfo::GCCRegAlias> ARMTargetInfo::getGCCRegAliases() const {
   return llvm::ArrayRef(GCCRegAliases);
 }
 
-bool ARMTargetInfo::validateAsmConstraint(
-    const char *&Name, TargetInfo::ConstraintInfo &Info) const {
+bool ARMTargetInfo::validateAsmConstraint(const char *&Name,
+                                          TargetInfo::ConstraintInfo &Info,
+                                          llvm::StringMap<bool> *FeatureMap,
+                                          diag::kind &Diag) const {
   switch (*Name) {
   default:
     break;
diff --git a/clang/lib/Basic/Targets/ARM.h b/clang/lib/Basic/Targets/ARM.h
index df9855a52e61c..59e1b7ad64d5f 100644
--- a/clang/lib/Basic/Targets/ARM.h
+++ b/clang/lib/Basic/Targets/ARM.h
@@ -204,7 +204,9 @@ class LLVM_LIBRARY_VISIBILITY ARMTargetInfo : public TargetInfo {
   ArrayRef<const char *> getGCCRegNames() const override;
   ArrayRef<TargetInfo::GCCRegAlias> getGCCRegAliases() const override;
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override;
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override;
   std::string convertConstraint(const char *&Constraint) const override;
   bool
   validateConstraintModifier(StringRef Constraint, char Modifier, unsigned Size,
diff --git a/clang/lib/Basic/Targets/AVR.h b/clang/lib/Basic/Targets/AVR.h
index feeb04f37eeba..6e2061960e591 100644
--- a/clang/lib/Basic/Targets/AVR.h
+++ b/clang/lib/Basic/Targets/AVR.h
@@ -94,7 +94,9 @@ class LLVM_LIBRARY_VISIBILITY AVRTargetInfo : public TargetInfo {
   }
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override {
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override {
     // There aren't any multi-character AVR specific constraints.
     if (StringRef(Name).size() > 1)
       return false;
diff --git a/clang/lib/Basic/Targets/BPF.h b/clang/lib/Basic/Targets/BPF.h
index d19b37dd4df7a..94a0fe90a3c68 100644
--- a/clang/lib/Basic/Targets/BPF.h
+++ b/clang/lib/Basic/Targets/BPF.h
@@ -72,7 +72,9 @@ class LLVM_LIBRARY_VISIBILITY BPFTargetInfo : public TargetInfo {
   }
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &Info) const override {
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override {
     switch (*Name) {
     default:
       break;
diff --git a/clang/lib/Basic/Targets/CSKY.cpp b/clang/lib/Basic/Targets/CSKY.cpp
index c8bf8b9234d24..f6af91bbe278b 100644
--- a/clang/lib/Basic/Targets/CSKY.cpp
+++ b/clang/lib/Basic/Targets/CSKY.cpp
@@ -289,8 +289,10 @@ ArrayRef<TargetInfo::GCCRegAlias> CSKYTargetInfo::getGCCRegAliases() const {
   return llvm::ArrayRef(GCCRegAliases);
 }
 
-bool CSKYTargetInfo::validateAsmConstraint(
-    const char *&Name, TargetInfo::ConstraintInfo &Info) const {
+bool CSKYTargetInfo::validateAsmConstraint(const char *&Name,
+                                           TargetInfo::ConstraintInfo &Info,
+                                           llvm::StringMap<bool> *FeatureMap,
+                                           diag::kind &Diag) const {
   switch (*Name) {
   default:
     return false;
diff --git a/clang/lib/Basic/Targets/CSKY.h b/clang/lib/Basic/Targets/CSKY.h
index 94d4eeb9a1fff..971bdc5e8b18d 100644
--- a/clang/lib/Basic/Targets/CSKY.h
+++ b/clang/lib/Basic/Targets/CSKY.h
@@ -80,7 +80,9 @@ class LLVM_LIBRARY_VISIBILITY CSKYTargetInfo : public TargetInfo {
   }
 
   bool validateAsmConstraint(const char *&Name,
-                             TargetInfo::ConstraintInfo &info) const override;
+                             TargetInfo::ConstraintInfo &Info,
+                             llvm::StringMap<bool> *FeatureMap,
+                             diag::kind &Diag) const override;
 
   std::string_view getClobbers() const override { return ""; }
 
diff --git a/clang/lib/Basic/Targets/DirectX.h b/clang/lib/Basic/Targets/DirectX.h
index a084e2823453f..6a10409f844c3 100644
--- a/clang/lib/Basic/Targets/DirectX.h
+++ b/clang/lib/Basic/T...
[truncated]

@e-kud e-kud requested a review from phoebewang June 21, 2024 22:16
@e-kud
Copy link
Contributor Author

e-kud commented Jun 21, 2024

Initially I've implemented the target errors through std::string. Then changed to diag::kind after reading InternalsManual. I'm not sure what is better. The drawback of returning diagnoistics by reference is that we can't customize them, only fixed messages. Maybe this is not a big problem because we don't have constantly changing constraints.

Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

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

Frontend changes look sensible, but I can’t really comment on anything target-specific...

Comment on lines 328 to 329
"%sub{asm_invalid_constraint_generic}0,1: cannot find an output constraint"
" with the specified name">;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"%sub{asm_invalid_constraint_generic}0,1: cannot find an output constraint"
" with the specified name">;
"%sub{asm_invalid_constraint_generic}0,1: no matching output constraint">;

There has to be a corresponding output constraint with the same name as I understand it? This confused me a bit just now while I was looking at the tests, so maybe something like this would make that a bit clearer?

Copy link
Contributor Author

@e-kud e-kud Jun 27, 2024

Choose a reason for hiding this comment

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

Yes, this is similar to digits. From GCC reference:

Input constraints can also be digits (for example, "0"). This indicates that the specified input must be in the same place as the output constraint at the (zero-based) index in the output constraint list. When using asmSymbolicName syntax for the output operands, you may use these names (enclosed in brackets []) instead of digits.

Comment on lines 336 to 337
"%sub{asm_invalid_constraint_generic}0,1: references to a non-existing output"
" constraint">;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"%sub{asm_invalid_constraint_generic}0,1: references to a non-existing output"
" constraint">;
"%sub{asm_invalid_constraint_generic}0,1: references non-existent output"
" constraint">;

nit

"%sub{asm_invalid_constraint_generic}0,1: tied constraint must be tied to"
" the same operand referenced to by the number">;
def err_asm_invalid_constraint_output_only : Error<
"%sub{asm_invalid_constraint_generic}0,1: must refer to an output only"
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"%sub{asm_invalid_constraint_generic}0,1: must refer to an output only"
"%sub{asm_invalid_constraint_generic}0,1: must refer to an output-only"

another nit here too

@Sirraide
Copy link
Member

Initially I've implemented the target errors through std::string. Then changed to diag::kind after reading InternalsManual. I'm not sure what is better.

Hmm, if they’re not changing very often (if at all), then I’d also use DiagIDs for this, yeah.

Copy link
Contributor

@arsenm arsenm left a comment

Choose a reason for hiding this comment

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

It's really unfortunate to have to add all this asm handling to clang. Can't it rely on backend diagnostic remarks for this?

@e-kud
Copy link
Contributor Author

e-kud commented Jun 24, 2024

It's really unfortunate to have to add all this asm handling to clang. Can't it rely on backend diagnostic remarks for this?

I've tried to do the similar thing in the backend: https://reviews.llvm.org/D152332. The problem I see is that llc's error handler ignores errors and continues compilation until something further reports a fatal error. In other words we have to return something wrong intentionally to continue compilation. On the other hand, we can prepare all callers to have an error somewhere inside but there will be a lot of refactor because there is no common practice to emit target specific errors. report_fatal_error is more likable.

$ grep -R emitError lib/Target/X86/*
lib/Target/X86/X86FloatingPoint.cpp:      MI.emitError("fixed input regs must be last on the x87 stack");
lib/Target/X86/X86FloatingPoint.cpp:      MI.emitError("output regs must be last on the x87 stack");
lib/Target/X86/X86FloatingPoint.cpp:      MI.emitError("clobbers must be last on the x87 stack");
lib/Target/X86/X86FloatingPoint.cpp:      MI.emitError("implicitly popped regs must be last on the x87 stack");
lib/Target/X86/X86PreTileConfig.cpp:static void emitErrorMsg(MachineFunction &MF) {
lib/Target/X86/X86PreTileConfig.cpp:  Context.emitError(
lib/Target/X86/X86PreTileConfig.cpp:      emitErrorMsg(MF);
lib/Target/X86/X86PreTileConfig.cpp:      emitErrorMsg(MF);
$ grep -R emitError lib/Target/AArch64/*
lib/Target/AArch64/AArch64AsmPrinter.cpp:    BaseGV->getContext().emitError(
$ grep -R emitError lib/Target/RISCV/*
$
$ grep -R emitError lib/Target/* | wc -l
47
$ grep -R report_fatal_error lib/Target/* | wc -l
675

I'm not sure whether it is much better.

@efriedma-quic
Copy link
Collaborator

In a lot of cases, we report_fatal_error because we don't actually expect users to run into the issues... indicate some internal inconsistency in the compiler, or some unsupported configuration, or something like that. Because of this, report_fatal_error prints the standard "report a bug" crash message. For inline asm specifically, emitError from the backend is probably best in most cases, so non-clang frontends get better diagnostics, and we don't have to duplicate code.

In other words we have to return something wrong intentionally to continue compilation

Yes, this can be a bit tricky in some cases, but usually there's something straightforward you can do (for example, drop the inline asm stmt). Note that we don't actually generate an output file, so it doesn't matter what you do as long as it doesn't cause something else to crash.

@e-kud
Copy link
Contributor Author

e-kud commented Jun 25, 2024

So, I think this PR still makes sense but without target changes, right?

I've taken a look at the backend and constraints are checked in getRegForInlineAsmConstraint. We either need to return an error message or pass Context into it. The former is preferrable because a call of getRegForInlineAsmConstraint doesn't always mean an error is requried. However, in both cases we need to touch all the targets...

@efriedma-quic
Copy link
Collaborator

I didn't actually look at this PR closely before I commented.

I think the specific checks clang is doing here have to be part of clang: in particular, clang needs to translate from gcc syntax to LLVM IR asm syntax, and that requires parsing the constraints. So these checks are necessary, and emitting better diagnostics for checks we need to do anyway seems fine.

e-kud and others added 2 commits June 26, 2024 19:13
Co-authored-by: Sirraide <aeternalmail@gmail.com>
@e-kud
Copy link
Contributor Author

e-kud commented Jun 27, 2024

I think the specific checks clang is doing here have to be part of clang: in particular, clang needs to translate from gcc syntax to LLVM IR asm syntax, and that requires parsing the constraints. So these checks are necessary, and emitting better diagnostics for checks we need to do anyway seems fine.

Yes. But constraints are checked. The problem here is that they may conflict with features. Should this "features+constraint" combination checked in the frontend or in the backend?

I like the idea of checking it in the frontend because we may point to the specific constraint. In the backend we can point only to the whole expression or even emit a generic error without a line.

@efriedma-quic
Copy link
Collaborator

What exactly does it mean for a constraint to conflict with a feature? The only thing I can think of is if it somehow involves a register class that doesn't exist on the target with the current set of target features. I guess we could try to diagnose that, but I'm not sure it's worth duplicating that code.

@e-kud
Copy link
Contributor Author

e-kud commented Jun 28, 2024

What exactly does it mean for a constraint to conflict with a feature? The only thing I can think of is if it somehow involves a register class that doesn't exist on the target with the current set of target features. I guess we could try to diagnose that, but I'm not sure it's worth duplicating that code.

Yes, indeed. For instance, we have a constraint x that must provide xmm register. But if we compile with -mno-sse there are no xmms available. And these changes in targets are the prework to handle these cases in clang. This is not a special case for X86. ARM, for example, makes some target constraints invalid if a feature is not enabled:

case 'x': // s0-s31, d0-d15, or q0-q7
if (FPRegsDisabled)
return false;
Info.setAllowsRegister();
return true;

However this implementation is buggy because it doesn't consider function attributes. In case of multiversioning in the same TU we will hit compilation errors.

I'm not sure it's worth duplicating that code.

So, then return to the idea to better diagnose these things from backends?

@efriedma-quic
Copy link
Collaborator

If there's some subset of cases that's easy to diagnose, like SSE registers without SSE, maybe we can do that. I mostly care that we don't write a bunch of code to check complicated edge cases, like trying to diagnose if you're using too many registers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

5 participants