Skip to content

Add clang atomic control options and attribute #114841

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

Merged
merged 1 commit into from
Feb 27, 2025
Merged

Conversation

yxsamliu
Copy link
Collaborator

@yxsamliu yxsamliu commented Nov 4, 2024

Add option and statement attribute for controlling emitting of target-specific
metadata to atomicrmw instructions in IR.

The RFC for this attribute and option is
https://discourse.llvm.org/t/rfc-add-clang-atomic-control-options-and-pragmas/80641,
Originally a pragma was proposed, then it was changed to clang attribute.

This attribute allows users to specify one, two, or all three options and must be applied
to a compound statement. The attribute can also be nested, with inner attributes
overriding the options specified by outer attributes or the target's default
options. These options will then determine the target-specific metadata added to atomic
instructions in the IR.

In addition to the attribute, three new compiler options are introduced:
-f[no-]atomic-remote-memory, -f[no-]atomic-fine-grained-memory,
-f[no-]atomic-ignore-denormal-mode.
These compiler options allow users to override the default options through the
Clang driver and front end. -m[no-]unsafe-fp-atomics is aliased to
-f[no-]ignore-denormal-mode.

In terms of implementation, the atomic attribute is represented in the AST by the
existing AttributedStmt, with minimal changes to AST and Sema.

During code generation in Clang, the CodeGenModule maintains the current atomic options,
which are used to emit the relevant metadata for atomic instructions. RAII is used
to manage the saving and restoring of atomic options when entering
and exiting nested AttributedStmt.

@llvmbot llvmbot added clang Clang issues not falling into any other category backend:AMDGPU clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. clang:openmp OpenMP related changes to Clang labels Nov 4, 2024
@llvmbot
Copy link
Member

llvmbot commented Nov 4, 2024

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang-driver

Author: Yaxun (Sam) Liu (yxsamliu)

Changes

Add option and statement attribute for controlling emitting of target-specific metadata to atomicrmw instructions in IR.

The RFC for this attribute and option is
https://discourse.llvm.org/t/rfc-add-clang-atomic-control-options-and-pragmas/80641, Originally a pragma was proposed, then it was changed to clang attribute.

This attribute allows users to specify one, two, or all three options and must be applied to a compound statement. The attribute can also be nested, with inner attributes overriding the options specified by outer attributes or the target's default options. These options will then determine the target-specific metadata added to atomic instructions in the IR.

In addition to the attribute, a new compiler option is introduced: -fatomic=no_remote_memory:{on|off},no_fine_grained_memory:{on|off},ignore_denormal_mode{on|off}. This compiler option allows users to override the target's default options through the Clang driver and front end.

In terms of implementation, the atomic attribute is represented in the AST by the existing AttributedStmt, with minimal changes to AST and Sema.

During code generation in Clang, the CodeGenModule maintains the current atomic options, which are used to emit the relevant metadata for atomic instructions. RAII is used to manage the saving and restoring of atomic options when entering and exiting nested AttributedStmt.


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

26 Files Affected:

  • (added) clang/include/clang/Basic/AtomicOptions.def (+19)
  • (modified) clang/include/clang/Basic/Attr.td (+56)
  • (modified) clang/include/clang/Basic/DiagnosticDriverKinds.td (+7)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Basic/LangOptions.h (+167)
  • (modified) clang/include/clang/Basic/TargetInfo.h (+6)
  • (modified) clang/include/clang/Driver/Options.td (+8)
  • (modified) clang/include/clang/Parse/Parser.h (+5)
  • (modified) clang/lib/Basic/LangOptions.cpp (+52)
  • (modified) clang/lib/Basic/Targets/AMDGPU.cpp (+7)
  • (modified) clang/lib/CodeGen/CGStmt.cpp (+5)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+17)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+8)
  • (modified) clang/lib/CodeGen/Targets/AMDGPU.cpp (+12-10)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+26)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+71)
  • (modified) clang/lib/Sema/SemaStmtAttr.cpp (+43)
  • (added) clang/test/AST/ast-dump-atomic-options.hip (+102)
  • (modified) clang/test/CodeGenCUDA/amdgpu-atomic-ops.cu (+95-100)
  • (modified) clang/test/CodeGenCUDA/atomic-ops.cu (+100-100)
  • (added) clang/test/CodeGenCUDA/atomic-options.hip (+456)
  • (added) clang/test/Driver/atomic-options.hip (+31)
  • (modified) clang/test/OpenMP/amdgpu-unsafe-fp-atomics.cpp (+6-4)
  • (added) clang/test/Parser/Inputs/cuda.h (+54)
  • (added) clang/test/Parser/atomic-options.hip (+30)
diff --git a/clang/include/clang/Basic/AtomicOptions.def b/clang/include/clang/Basic/AtomicOptions.def
new file mode 100644
index 00000000000000..4cf2dab581c8b4
--- /dev/null
+++ b/clang/include/clang/Basic/AtomicOptions.def
@@ -0,0 +1,19 @@
+//===--- AtomicOptions.def - Atomic Options database -------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// This file defines the Atomic language options. Users of this file
+// must define the OPTION macro to make use of this information.
+#ifndef OPTION
+#  error Define the OPTION macro to handle atomic language options
+#endif
+
+// OPTION(name, type, width, previousName)
+OPTION(NoRemoteMemory, bool, 1, First)
+OPTION(NoFineGrainedMemory, bool, 1, NoRemoteMemory)
+OPTION(IgnoreDenormalMode, bool, 1, NoFineGrainedMemory)
+
+#undef OPTION
\ No newline at end of file
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 156fbd1c4442eb..6b5fea1965aec3 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4838,3 +4838,59 @@ def ClspvLibclcBuiltin: InheritableAttr {
   let Documentation = [ClspvLibclcBuiltinDoc];
   let SimpleHandler = 1;
 }
+
+def Atomic : StmtAttr {
+  let Spellings = [Clang<"atomic">];
+  let Args = [
+    EnumArgument<"NoRemoteMemory", "NoRemoteMemoryTy", /*IsString*/ false,
+      ["no_remote_memory", "!no_remote_memory", ""],
+      ["NoRemoteMemoryOn", "NoRemoteMemoryOff", "NoRemoteMemoryUnset"]>,
+    EnumArgument<"NoFineGrainedMemory", "NoFineGrainedMemoryTy", /*IsString*/ false,
+      ["no_fine_grained_memory", "!no_fine_grained_memory", ""],
+      ["NoFineGrainedMemoryOn", "NoFineGrainedMemoryOff", "NoFineGrainedMemoryUnset"]>,
+    EnumArgument<"IgnoreDenormalMode", "IgnoreDenormalModeTy", /*IsString*/ false,
+      ["ignore_denormal_mode", "!ignore_denormal_mode", ""],
+      ["IgnoreDenormalModeOn", "IgnoreDenormalModeOff", "IgnoreDenormalModeUnset"]>
+  ];
+  let Subjects = SubjectList<[CompoundStmt], ErrorDiag, "compound statements">;
+  let HasCustomParsing = 1;
+  let Documentation = [Undocumented];
+  let AdditionalMembers = [{
+    AtomicOptionsOverride AOO;
+    AtomicOptionsOverride getAtomicOptionsOverride() const { return AOO; }
+    void updateAtomicOptionsOverride() {
+      switch (getNoRemoteMemory()) {
+      case NoRemoteMemoryOn:
+        AOO.setNoRemoteMemoryOverride(true);
+        break;
+      case NoRemoteMemoryOff:
+        AOO.setNoRemoteMemoryOverride(false);
+        break;
+      case NoRemoteMemoryUnset:
+        AOO.clearNoRemoteMemoryOverride();
+      }
+
+      switch (getNoFineGrainedMemory()) {
+      case NoFineGrainedMemoryOn:
+        AOO.setNoFineGrainedMemoryOverride(true);
+        break;
+      case NoFineGrainedMemoryOff:
+        AOO.setNoFineGrainedMemoryOverride(false);
+        break;
+      case NoFineGrainedMemoryUnset:
+        AOO.clearNoFineGrainedMemoryOverride();
+      }
+
+      switch (getIgnoreDenormalMode()) {
+      case IgnoreDenormalModeOn:
+        AOO.setIgnoreDenormalModeOverride(true);
+        break;
+      case IgnoreDenormalModeOff:
+        AOO.setIgnoreDenormalModeOverride(false);
+        break;
+      case IgnoreDenormalModeUnset:
+        AOO.clearIgnoreDenormalModeOverride();
+      }
+    }
+  }];
+}
diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index cdfdaa01fb121d..d72d661281d112 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -305,6 +305,13 @@ def err_drv_invalid_int_value : Error<"invalid integral value '%1' in '%0'">;
 def err_drv_invalid_value_with_suggestion : Error<
     "invalid value '%1' in '%0', expected one of: %2">;
 def err_drv_alignment_not_power_of_two : Error<"alignment is not a power of 2 in '%0'">;
+
+def err_drv_invalid_atomic_option : Error<
+  "invalid argument '%0' to -fatomic=; must be a "
+  "comma-separated list of key:value pairs, where allowed keys are "
+  "'no_fine_grained_memory', 'no_remote_memory', 'ignore_denormal_mode', "
+  "and values are 'on' or 'off', and each key must be unique">;
+
 def err_drv_invalid_remap_file : Error<
     "invalid option '%0' not of the form <from-file>;<to-file>">;
 def err_drv_invalid_gcc_install_dir : Error<"'%0' does not contain a GCC installation">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d697e6d61afa9a..98d65d83513025 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3275,6 +3275,8 @@ def err_invalid_branch_protection_spec : Error<
   "invalid or misplaced branch protection specification '%0'">;
 def warn_unsupported_branch_protection_spec : Warning<
   "unsupported branch protection specification '%0'">, InGroup<BranchProtection>;
+def err_attribute_invalid_atomic_argument : Error<
+  "invalid argument '%0' to atomic attribute; valid options are: 'no_remote_memory', 'no_fine_grained_memory', 'ignore_denormal_mode' (optionally prefixed with '!')">;
 
 def warn_unsupported_target_attribute
     : Warning<"%select{unsupported|duplicate|unknown}0%select{| CPU|"
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 949c8f5d448bcf..79862993575fcf 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -622,6 +622,10 @@ class LangOptions : public LangOptionsBase {
   // WebAssembly target.
   bool NoWasmOpt = false;
 
+  /// The default atomic codegen options specified by command line in the
+  /// format of key:{on|off}.
+  std::vector<std::string> AtomicOptionsAsWritten;
+
   LangOptions();
 
   /// Set language defaults for the given input language and
@@ -1093,6 +1097,169 @@ inline void FPOptions::applyChanges(FPOptionsOverride FPO) {
   *this = FPO.applyOverrides(*this);
 }
 
+/// Atomic control options
+class AtomicOptionsOverride;
+class AtomicOptions {
+public:
+  using storage_type = uint16_t;
+
+  static constexpr unsigned StorageBitSize = 8 * sizeof(storage_type);
+
+  static constexpr storage_type FirstShift = 0, FirstWidth = 0;
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  static constexpr storage_type NAME##Shift =                                  \
+      PREVIOUS##Shift + PREVIOUS##Width;                                       \
+  static constexpr storage_type NAME##Width = WIDTH;                           \
+  static constexpr storage_type NAME##Mask = ((1 << NAME##Width) - 1)          \
+                                             << NAME##Shift;
+#include "clang/Basic/AtomicOptions.def"
+
+  static constexpr storage_type TotalWidth = 0
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS) +WIDTH
+#include "clang/Basic/AtomicOptions.def"
+      ;
+  static_assert(TotalWidth <= StorageBitSize,
+                "Too short type for AtomicOptions");
+
+private:
+  storage_type Value;
+
+  AtomicOptionsOverride getChangesSlow(const AtomicOptions &Base) const;
+
+public:
+  AtomicOptions() : Value(0) {
+    setNoRemoteMemory(false);
+    setNoFineGrainedMemory(false);
+    setIgnoreDenormalMode(false);
+  }
+  explicit AtomicOptions(const LangOptions &LO) {
+    Value = 0;
+#if 0
+    setNoRemoteMemory(LO.NoRemoteMemoryAccess);
+    setNoFineGrainedMemory(LO.NoFineGrainedMemoryAccess);
+    setIgnoreDenormalMode(LO.IgnoreDenormals);
+#endif
+  }
+
+  bool operator==(AtomicOptions other) const { return Value == other.Value; }
+
+  /// Return the default value of AtomicOptions that's used when trailing
+  /// storage isn't required.
+  static AtomicOptions defaultWithoutTrailingStorage(const LangOptions &LO);
+
+  storage_type getAsOpaqueInt() const { return Value; }
+  static AtomicOptions getFromOpaqueInt(storage_type Value) {
+    AtomicOptions Opts;
+    Opts.Value = Value;
+    return Opts;
+  }
+
+  /// Return difference with the given option set.
+  AtomicOptionsOverride getChangesFrom(const AtomicOptions &Base) const;
+
+  void applyChanges(AtomicOptionsOverride AO);
+
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  TYPE get##NAME() const {                                                     \
+    return static_cast<TYPE>((Value & NAME##Mask) >> NAME##Shift);             \
+  }                                                                            \
+  void set##NAME(TYPE value) {                                                 \
+    Value = (Value & ~NAME##Mask) | (storage_type(value) << NAME##Shift);      \
+  }
+#include "clang/Basic/AtomicOptions.def"
+  LLVM_DUMP_METHOD void dump();
+};
+
+/// Represents difference between two AtomicOptions values.
+class AtomicOptionsOverride {
+  AtomicOptions Options = AtomicOptions::getFromOpaqueInt(0);
+  AtomicOptions::storage_type OverrideMask = 0;
+
+public:
+  /// The type suitable for storing values of AtomicOptionsOverride. Must be
+  /// twice as wide as bit size of AtomicOption.
+  using storage_type = uint32_t;
+  static_assert(sizeof(storage_type) >= 2 * sizeof(AtomicOptions::storage_type),
+                "Too short type for AtomicOptionsOverride");
+
+  /// Bit mask selecting bits of OverrideMask in serialized representation of
+  /// AtomicOptionsOverride.
+  static constexpr storage_type OverrideMaskBits =
+      (static_cast<storage_type>(1) << AtomicOptions::StorageBitSize) - 1;
+
+  AtomicOptionsOverride() {}
+  AtomicOptionsOverride(const LangOptions &LO);
+  AtomicOptionsOverride(AtomicOptions AO)
+      : Options(AO), OverrideMask(OverrideMaskBits) {}
+  AtomicOptionsOverride(AtomicOptions AO, AtomicOptions::storage_type Mask)
+      : Options(AO), OverrideMask(Mask) {}
+
+  bool requiresTrailingStorage() const { return OverrideMask != 0; }
+
+  storage_type getAsOpaqueInt() const {
+    return (static_cast<storage_type>(Options.getAsOpaqueInt())
+            << AtomicOptions::StorageBitSize) |
+           OverrideMask;
+  }
+
+  static AtomicOptionsOverride getFromOpaqueInt(storage_type I) {
+    AtomicOptionsOverride Opts;
+    Opts.OverrideMask = I & OverrideMaskBits;
+    Opts.Options =
+        AtomicOptions::getFromOpaqueInt(I >> AtomicOptions::StorageBitSize);
+    return Opts;
+  }
+
+  AtomicOptions applyOverrides(AtomicOptions Base) {
+    AtomicOptions Result = AtomicOptions::getFromOpaqueInt(
+        (Base.getAsOpaqueInt() & ~OverrideMask) |
+        (Options.getAsOpaqueInt() & OverrideMask));
+    return Result;
+  }
+
+  AtomicOptions applyOverrides(const LangOptions &LO) {
+    return applyOverrides(AtomicOptions(LO));
+  }
+
+  bool operator==(AtomicOptionsOverride other) const {
+    return Options == other.Options && OverrideMask == other.OverrideMask;
+  }
+  bool operator!=(AtomicOptionsOverride other) const {
+    return !(*this == other);
+  }
+
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  bool has##NAME##Override() const {                                           \
+    return OverrideMask & AtomicOptions::NAME##Mask;                           \
+  }                                                                            \
+  TYPE get##NAME##Override() const {                                           \
+    assert(has##NAME##Override());                                             \
+    return Options.get##NAME();                                                \
+  }                                                                            \
+  void clear##NAME##Override() {                                               \
+    Options.set##NAME(TYPE(0));                                                \
+    OverrideMask &= ~AtomicOptions::NAME##Mask;                                \
+  }                                                                            \
+  void set##NAME##Override(TYPE value) {                                       \
+    Options.set##NAME(value);                                                  \
+    OverrideMask |= AtomicOptions::NAME##Mask;                                 \
+  }
+#include "clang/Basic/AtomicOptions.def"
+
+  LLVM_DUMP_METHOD void dump();
+};
+
+inline AtomicOptionsOverride
+AtomicOptions::getChangesFrom(const AtomicOptions &Base) const {
+  if (Value == Base.Value)
+    return AtomicOptionsOverride();
+  return getChangesSlow(Base);
+}
+
+inline void AtomicOptions::applyChanges(AtomicOptionsOverride AO) {
+  *this = AO.applyOverrides(*this);
+}
+
 /// Describes the kind of translation unit being processed.
 enum TranslationUnitKind {
   /// The translation unit is a complete translation unit.
diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index 25eda907d20a7b..c23ce5d9418f46 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -295,6 +295,9 @@ class TargetInfo : public TransferrableTargetInfo,
   // in function attributes in IR.
   llvm::StringSet<> ReadOnlyFeatures;
 
+  // Default atomic options
+  AtomicOptions AtomicOpts;
+
 public:
   /// Construct a target for the given options.
   ///
@@ -1674,6 +1677,9 @@ class TargetInfo : public TransferrableTargetInfo,
     return CC_C;
   }
 
+  /// Get the default atomic options.
+  AtomicOptions getAtomicOpts() const { return AtomicOpts; }
+
   enum CallingConvCheckResult {
     CCCR_OK,
     CCCR_Warning,
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 805b79491e6ea4..737514724545ae 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2346,6 +2346,14 @@ def fsymbol_partition_EQ : Joined<["-"], "fsymbol-partition=">, Group<f_Group>,
   Visibility<[ClangOption, CC1Option]>,
   MarshallingInfoString<CodeGenOpts<"SymbolPartition">>;
 
+def fatomic_EQ : CommaJoined<["-"], "fatomic=">, Group<f_Group>,
+  Visibility<[ClangOption, CC1Option]>,
+  HelpText<"Specify atomic codegen options as a comma-separated list of "
+           "key:value pairs, allowed keys and values are "
+           "no_fine_grained_memory:on|off, no_remote_memory:on|off, "
+           "ignore_denormal_mode:on|off">,
+  MarshallingInfoStringVector<LangOpts<"AtomicOptionsAsWritten">>;
+
 defm memory_profile : OptInCC1FFlag<"memory-profile", "Enable", "Disable", " heap memory profiling">;
 def fmemory_profile_EQ : Joined<["-"], "fmemory-profile=">,
     Group<f_Group>, Visibility<[ClangOption, CC1Option]>,
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 045ee754a242b3..08bab6104be04d 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3100,6 +3100,11 @@ class Parser : public CodeCompletionHandler {
   std::optional<AvailabilitySpec> ParseAvailabilitySpec();
   ExprResult ParseAvailabilityCheckExpr(SourceLocation StartLoc);
 
+  void ParseAtomicAttribute(IdentifierInfo &AttrName,
+                            SourceLocation AttrNameLoc, ParsedAttributes &Attrs,
+                            SourceLocation *EndLoc, IdentifierInfo *ScopeName,
+                            SourceLocation ScopeLoc, ParsedAttr::Form Form);
+
   void ParseExternalSourceSymbolAttribute(IdentifierInfo &ExternalSourceSymbol,
                                           SourceLocation Loc,
                                           ParsedAttributes &Attrs,
diff --git a/clang/lib/Basic/LangOptions.cpp b/clang/lib/Basic/LangOptions.cpp
index 94caf6a3897bc1..29afcdf03486d2 100644
--- a/clang/lib/Basic/LangOptions.cpp
+++ b/clang/lib/Basic/LangOptions.cpp
@@ -238,3 +238,55 @@ LLVM_DUMP_METHOD void FPOptionsOverride::dump() {
 #include "clang/Basic/FPOptions.def"
   llvm::errs() << "\n";
 }
+
+AtomicOptions
+AtomicOptions::defaultWithoutTrailingStorage(const LangOptions &LO) {
+  AtomicOptions result(LO);
+  return result;
+}
+
+AtomicOptionsOverride
+AtomicOptions::getChangesSlow(const AtomicOptions &Base) const {
+  AtomicOptions::storage_type OverrideMask = 0;
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  if (get##NAME() != Base.get##NAME())                                         \
+    OverrideMask |= NAME##Mask;
+#include "clang/Basic/AtomicOptions.def"
+  return AtomicOptionsOverride(*this, OverrideMask);
+}
+
+LLVM_DUMP_METHOD void AtomicOptions::dump() {
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  llvm::errs() << "\n " #NAME " " << get##NAME();
+#include "clang/Basic/AtomicOptions.def"
+  llvm::errs() << "\n";
+}
+
+LLVM_DUMP_METHOD void AtomicOptionsOverride::dump() {
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  if (has##NAME##Override())                                                   \
+    llvm::errs() << "\n " #NAME " Override is " << get##NAME##Override();
+#include "clang/Basic/AtomicOptions.def"
+  llvm::errs() << "\n";
+}
+
+AtomicOptionsOverride::AtomicOptionsOverride(const LangOptions &LO) {
+  for (const auto &Setting : LO.AtomicOptionsAsWritten) {
+    SmallVector<StringRef, 2> KeyValue;
+    StringRef(Setting).split(KeyValue, ":");
+    // Assuming option string has been checked elsewhere and is valid.
+    assert(KeyValue.size() == 2 && "Invalid atomic option format");
+    StringRef Key = KeyValue[0];
+    StringRef Val = KeyValue[1];
+    bool IsEnabled = (Val == "on");
+
+    if (Key == "no_fine_grained_memory")
+      setNoFineGrainedMemoryOverride(IsEnabled);
+    else if (Key == "no_remote_memory")
+      setNoRemoteMemoryOverride(IsEnabled);
+    else if (Key == "ignore_denormal_mode")
+      setIgnoreDenormalModeOverride(IsEnabled);
+    else
+      assert(false && "Unknown atomic option key");
+  }
+}
diff --git a/clang/lib/Basic/Targets/AMDGPU.cpp b/clang/lib/Basic/Targets/AMDGPU.cpp
index 078819183afdac..3a5e9f3ec0851d 100644
--- a/clang/lib/Basic/Targets/AMDGPU.cpp
+++ b/clang/lib/Basic/Targets/AMDGPU.cpp
@@ -241,6 +241,11 @@ AMDGPUTargetInfo::AMDGPUTargetInfo(const llvm::Triple &Triple,
   WavefrontSize = (GPUFeatures & llvm::AMDGPU::FEATURE_WAVE32) ? 32 : 64;
   AllowAMDGPUUnsafeFPAtomics = Opts.AllowAMDGPUUnsafeFPAtomics;
 
+  // Set the default atomic options
+  AtomicOpts.setNoRemoteMemory(true);
+  AtomicOpts.setNoFineGrainedMemory(true);
+  AtomicOpts.setIgnoreDenormalMode(Opts.AllowAMDGPUUnsafeFPAtomics);
+
   // Set pointer width and alignment for the generic address space.
   PointerWidth = PointerAlign = getPointerWidthV(LangAS::Default);
   if (getMaxPointerWidth() == 64) {
@@ -264,6 +269,8 @@ void AMDGPUTargetInfo::adjust(DiagnosticsEngine &Diags, LangOptions &Opts) {
   // to OpenCL can be removed from the following line.
   setAddressSpaceMap((Opts.OpenCL && !Opts.OpenCLGenericAddressSpace) ||
                      !isAMDGCN(getTriple()));
+
+  AtomicOpts.applyChanges(AtomicOptionsOverride(Opts));
 }
 
 ArrayRef<Builtin::Info> AMDGPUTargetInfo::getTargetBuiltins() const {
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 41dc91c578c800..fee336097ec4dd 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -728,6 +728,7 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
   bool alwaysinline = false;
   bool noconvergent = false;
   const CallExpr *musttail = nullptr;
+  AtomicOptionsOverride AOO;
 
   for (const auto *A : S.getAttrs()) {
     switch (A->getKind()) {
@@ -758,6 +759,9 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
         Builder.CreateAssumption(AssumptionVal);
       }
     } break;
+    case attr::Atomic: {
+      AOO = cast<AtomicAttr>(A)->getAtomicOptionsOverride();
+    } break;
     }
   }
   SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge);
@@ -765,6 +769,7 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
   SaveAndRestore save_alwaysinline(InAlwaysInlineAttributedStmt, alwaysinline);
   SaveAndRestore save_noconvergent(InNoConvergentAttributedStmt, noconvergent);
   SaveAndRestore save_musttail(MustTailCall, musttail);
+  CGAtomicOptionsRAII AORAII(CGM, AOO);
   EmitStmt(S.getSubStmt(), S.getAttrs());
 }
 
diff --git a/clang/...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Nov 4, 2024

@llvm/pr-subscribers-backend-amdgpu

Author: Yaxun (Sam) Liu (yxsamliu)

Changes

Add option and statement attribute for controlling emitting of target-specific metadata to atomicrmw instructions in IR.

The RFC for this attribute and option is
https://discourse.llvm.org/t/rfc-add-clang-atomic-control-options-and-pragmas/80641, Originally a pragma was proposed, then it was changed to clang attribute.

This attribute allows users to specify one, two, or all three options and must be applied to a compound statement. The attribute can also be nested, with inner attributes overriding the options specified by outer attributes or the target's default options. These options will then determine the target-specific metadata added to atomic instructions in the IR.

In addition to the attribute, a new compiler option is introduced: -fatomic=no_remote_memory:{on|off},no_fine_grained_memory:{on|off},ignore_denormal_mode{on|off}. This compiler option allows users to override the target's default options through the Clang driver and front end.

In terms of implementation, the atomic attribute is represented in the AST by the existing AttributedStmt, with minimal changes to AST and Sema.

During code generation in Clang, the CodeGenModule maintains the current atomic options, which are used to emit the relevant metadata for atomic instructions. RAII is used to manage the saving and restoring of atomic options when entering and exiting nested AttributedStmt.


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

26 Files Affected:

  • (added) clang/include/clang/Basic/AtomicOptions.def (+19)
  • (modified) clang/include/clang/Basic/Attr.td (+56)
  • (modified) clang/include/clang/Basic/DiagnosticDriverKinds.td (+7)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Basic/LangOptions.h (+167)
  • (modified) clang/include/clang/Basic/TargetInfo.h (+6)
  • (modified) clang/include/clang/Driver/Options.td (+8)
  • (modified) clang/include/clang/Parse/Parser.h (+5)
  • (modified) clang/lib/Basic/LangOptions.cpp (+52)
  • (modified) clang/lib/Basic/Targets/AMDGPU.cpp (+7)
  • (modified) clang/lib/CodeGen/CGStmt.cpp (+5)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+17)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+8)
  • (modified) clang/lib/CodeGen/Targets/AMDGPU.cpp (+12-10)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+26)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+71)
  • (modified) clang/lib/Sema/SemaStmtAttr.cpp (+43)
  • (added) clang/test/AST/ast-dump-atomic-options.hip (+102)
  • (modified) clang/test/CodeGenCUDA/amdgpu-atomic-ops.cu (+95-100)
  • (modified) clang/test/CodeGenCUDA/atomic-ops.cu (+100-100)
  • (added) clang/test/CodeGenCUDA/atomic-options.hip (+456)
  • (added) clang/test/Driver/atomic-options.hip (+31)
  • (modified) clang/test/OpenMP/amdgpu-unsafe-fp-atomics.cpp (+6-4)
  • (added) clang/test/Parser/Inputs/cuda.h (+54)
  • (added) clang/test/Parser/atomic-options.hip (+30)
diff --git a/clang/include/clang/Basic/AtomicOptions.def b/clang/include/clang/Basic/AtomicOptions.def
new file mode 100644
index 00000000000000..4cf2dab581c8b4
--- /dev/null
+++ b/clang/include/clang/Basic/AtomicOptions.def
@@ -0,0 +1,19 @@
+//===--- AtomicOptions.def - Atomic Options database -------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// This file defines the Atomic language options. Users of this file
+// must define the OPTION macro to make use of this information.
+#ifndef OPTION
+#  error Define the OPTION macro to handle atomic language options
+#endif
+
+// OPTION(name, type, width, previousName)
+OPTION(NoRemoteMemory, bool, 1, First)
+OPTION(NoFineGrainedMemory, bool, 1, NoRemoteMemory)
+OPTION(IgnoreDenormalMode, bool, 1, NoFineGrainedMemory)
+
+#undef OPTION
\ No newline at end of file
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 156fbd1c4442eb..6b5fea1965aec3 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4838,3 +4838,59 @@ def ClspvLibclcBuiltin: InheritableAttr {
   let Documentation = [ClspvLibclcBuiltinDoc];
   let SimpleHandler = 1;
 }
+
+def Atomic : StmtAttr {
+  let Spellings = [Clang<"atomic">];
+  let Args = [
+    EnumArgument<"NoRemoteMemory", "NoRemoteMemoryTy", /*IsString*/ false,
+      ["no_remote_memory", "!no_remote_memory", ""],
+      ["NoRemoteMemoryOn", "NoRemoteMemoryOff", "NoRemoteMemoryUnset"]>,
+    EnumArgument<"NoFineGrainedMemory", "NoFineGrainedMemoryTy", /*IsString*/ false,
+      ["no_fine_grained_memory", "!no_fine_grained_memory", ""],
+      ["NoFineGrainedMemoryOn", "NoFineGrainedMemoryOff", "NoFineGrainedMemoryUnset"]>,
+    EnumArgument<"IgnoreDenormalMode", "IgnoreDenormalModeTy", /*IsString*/ false,
+      ["ignore_denormal_mode", "!ignore_denormal_mode", ""],
+      ["IgnoreDenormalModeOn", "IgnoreDenormalModeOff", "IgnoreDenormalModeUnset"]>
+  ];
+  let Subjects = SubjectList<[CompoundStmt], ErrorDiag, "compound statements">;
+  let HasCustomParsing = 1;
+  let Documentation = [Undocumented];
+  let AdditionalMembers = [{
+    AtomicOptionsOverride AOO;
+    AtomicOptionsOverride getAtomicOptionsOverride() const { return AOO; }
+    void updateAtomicOptionsOverride() {
+      switch (getNoRemoteMemory()) {
+      case NoRemoteMemoryOn:
+        AOO.setNoRemoteMemoryOverride(true);
+        break;
+      case NoRemoteMemoryOff:
+        AOO.setNoRemoteMemoryOverride(false);
+        break;
+      case NoRemoteMemoryUnset:
+        AOO.clearNoRemoteMemoryOverride();
+      }
+
+      switch (getNoFineGrainedMemory()) {
+      case NoFineGrainedMemoryOn:
+        AOO.setNoFineGrainedMemoryOverride(true);
+        break;
+      case NoFineGrainedMemoryOff:
+        AOO.setNoFineGrainedMemoryOverride(false);
+        break;
+      case NoFineGrainedMemoryUnset:
+        AOO.clearNoFineGrainedMemoryOverride();
+      }
+
+      switch (getIgnoreDenormalMode()) {
+      case IgnoreDenormalModeOn:
+        AOO.setIgnoreDenormalModeOverride(true);
+        break;
+      case IgnoreDenormalModeOff:
+        AOO.setIgnoreDenormalModeOverride(false);
+        break;
+      case IgnoreDenormalModeUnset:
+        AOO.clearIgnoreDenormalModeOverride();
+      }
+    }
+  }];
+}
diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index cdfdaa01fb121d..d72d661281d112 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -305,6 +305,13 @@ def err_drv_invalid_int_value : Error<"invalid integral value '%1' in '%0'">;
 def err_drv_invalid_value_with_suggestion : Error<
     "invalid value '%1' in '%0', expected one of: %2">;
 def err_drv_alignment_not_power_of_two : Error<"alignment is not a power of 2 in '%0'">;
+
+def err_drv_invalid_atomic_option : Error<
+  "invalid argument '%0' to -fatomic=; must be a "
+  "comma-separated list of key:value pairs, where allowed keys are "
+  "'no_fine_grained_memory', 'no_remote_memory', 'ignore_denormal_mode', "
+  "and values are 'on' or 'off', and each key must be unique">;
+
 def err_drv_invalid_remap_file : Error<
     "invalid option '%0' not of the form <from-file>;<to-file>">;
 def err_drv_invalid_gcc_install_dir : Error<"'%0' does not contain a GCC installation">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d697e6d61afa9a..98d65d83513025 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3275,6 +3275,8 @@ def err_invalid_branch_protection_spec : Error<
   "invalid or misplaced branch protection specification '%0'">;
 def warn_unsupported_branch_protection_spec : Warning<
   "unsupported branch protection specification '%0'">, InGroup<BranchProtection>;
+def err_attribute_invalid_atomic_argument : Error<
+  "invalid argument '%0' to atomic attribute; valid options are: 'no_remote_memory', 'no_fine_grained_memory', 'ignore_denormal_mode' (optionally prefixed with '!')">;
 
 def warn_unsupported_target_attribute
     : Warning<"%select{unsupported|duplicate|unknown}0%select{| CPU|"
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 949c8f5d448bcf..79862993575fcf 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -622,6 +622,10 @@ class LangOptions : public LangOptionsBase {
   // WebAssembly target.
   bool NoWasmOpt = false;
 
+  /// The default atomic codegen options specified by command line in the
+  /// format of key:{on|off}.
+  std::vector<std::string> AtomicOptionsAsWritten;
+
   LangOptions();
 
   /// Set language defaults for the given input language and
@@ -1093,6 +1097,169 @@ inline void FPOptions::applyChanges(FPOptionsOverride FPO) {
   *this = FPO.applyOverrides(*this);
 }
 
+/// Atomic control options
+class AtomicOptionsOverride;
+class AtomicOptions {
+public:
+  using storage_type = uint16_t;
+
+  static constexpr unsigned StorageBitSize = 8 * sizeof(storage_type);
+
+  static constexpr storage_type FirstShift = 0, FirstWidth = 0;
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  static constexpr storage_type NAME##Shift =                                  \
+      PREVIOUS##Shift + PREVIOUS##Width;                                       \
+  static constexpr storage_type NAME##Width = WIDTH;                           \
+  static constexpr storage_type NAME##Mask = ((1 << NAME##Width) - 1)          \
+                                             << NAME##Shift;
+#include "clang/Basic/AtomicOptions.def"
+
+  static constexpr storage_type TotalWidth = 0
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS) +WIDTH
+#include "clang/Basic/AtomicOptions.def"
+      ;
+  static_assert(TotalWidth <= StorageBitSize,
+                "Too short type for AtomicOptions");
+
+private:
+  storage_type Value;
+
+  AtomicOptionsOverride getChangesSlow(const AtomicOptions &Base) const;
+
+public:
+  AtomicOptions() : Value(0) {
+    setNoRemoteMemory(false);
+    setNoFineGrainedMemory(false);
+    setIgnoreDenormalMode(false);
+  }
+  explicit AtomicOptions(const LangOptions &LO) {
+    Value = 0;
+#if 0
+    setNoRemoteMemory(LO.NoRemoteMemoryAccess);
+    setNoFineGrainedMemory(LO.NoFineGrainedMemoryAccess);
+    setIgnoreDenormalMode(LO.IgnoreDenormals);
+#endif
+  }
+
+  bool operator==(AtomicOptions other) const { return Value == other.Value; }
+
+  /// Return the default value of AtomicOptions that's used when trailing
+  /// storage isn't required.
+  static AtomicOptions defaultWithoutTrailingStorage(const LangOptions &LO);
+
+  storage_type getAsOpaqueInt() const { return Value; }
+  static AtomicOptions getFromOpaqueInt(storage_type Value) {
+    AtomicOptions Opts;
+    Opts.Value = Value;
+    return Opts;
+  }
+
+  /// Return difference with the given option set.
+  AtomicOptionsOverride getChangesFrom(const AtomicOptions &Base) const;
+
+  void applyChanges(AtomicOptionsOverride AO);
+
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  TYPE get##NAME() const {                                                     \
+    return static_cast<TYPE>((Value & NAME##Mask) >> NAME##Shift);             \
+  }                                                                            \
+  void set##NAME(TYPE value) {                                                 \
+    Value = (Value & ~NAME##Mask) | (storage_type(value) << NAME##Shift);      \
+  }
+#include "clang/Basic/AtomicOptions.def"
+  LLVM_DUMP_METHOD void dump();
+};
+
+/// Represents difference between two AtomicOptions values.
+class AtomicOptionsOverride {
+  AtomicOptions Options = AtomicOptions::getFromOpaqueInt(0);
+  AtomicOptions::storage_type OverrideMask = 0;
+
+public:
+  /// The type suitable for storing values of AtomicOptionsOverride. Must be
+  /// twice as wide as bit size of AtomicOption.
+  using storage_type = uint32_t;
+  static_assert(sizeof(storage_type) >= 2 * sizeof(AtomicOptions::storage_type),
+                "Too short type for AtomicOptionsOverride");
+
+  /// Bit mask selecting bits of OverrideMask in serialized representation of
+  /// AtomicOptionsOverride.
+  static constexpr storage_type OverrideMaskBits =
+      (static_cast<storage_type>(1) << AtomicOptions::StorageBitSize) - 1;
+
+  AtomicOptionsOverride() {}
+  AtomicOptionsOverride(const LangOptions &LO);
+  AtomicOptionsOverride(AtomicOptions AO)
+      : Options(AO), OverrideMask(OverrideMaskBits) {}
+  AtomicOptionsOverride(AtomicOptions AO, AtomicOptions::storage_type Mask)
+      : Options(AO), OverrideMask(Mask) {}
+
+  bool requiresTrailingStorage() const { return OverrideMask != 0; }
+
+  storage_type getAsOpaqueInt() const {
+    return (static_cast<storage_type>(Options.getAsOpaqueInt())
+            << AtomicOptions::StorageBitSize) |
+           OverrideMask;
+  }
+
+  static AtomicOptionsOverride getFromOpaqueInt(storage_type I) {
+    AtomicOptionsOverride Opts;
+    Opts.OverrideMask = I & OverrideMaskBits;
+    Opts.Options =
+        AtomicOptions::getFromOpaqueInt(I >> AtomicOptions::StorageBitSize);
+    return Opts;
+  }
+
+  AtomicOptions applyOverrides(AtomicOptions Base) {
+    AtomicOptions Result = AtomicOptions::getFromOpaqueInt(
+        (Base.getAsOpaqueInt() & ~OverrideMask) |
+        (Options.getAsOpaqueInt() & OverrideMask));
+    return Result;
+  }
+
+  AtomicOptions applyOverrides(const LangOptions &LO) {
+    return applyOverrides(AtomicOptions(LO));
+  }
+
+  bool operator==(AtomicOptionsOverride other) const {
+    return Options == other.Options && OverrideMask == other.OverrideMask;
+  }
+  bool operator!=(AtomicOptionsOverride other) const {
+    return !(*this == other);
+  }
+
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  bool has##NAME##Override() const {                                           \
+    return OverrideMask & AtomicOptions::NAME##Mask;                           \
+  }                                                                            \
+  TYPE get##NAME##Override() const {                                           \
+    assert(has##NAME##Override());                                             \
+    return Options.get##NAME();                                                \
+  }                                                                            \
+  void clear##NAME##Override() {                                               \
+    Options.set##NAME(TYPE(0));                                                \
+    OverrideMask &= ~AtomicOptions::NAME##Mask;                                \
+  }                                                                            \
+  void set##NAME##Override(TYPE value) {                                       \
+    Options.set##NAME(value);                                                  \
+    OverrideMask |= AtomicOptions::NAME##Mask;                                 \
+  }
+#include "clang/Basic/AtomicOptions.def"
+
+  LLVM_DUMP_METHOD void dump();
+};
+
+inline AtomicOptionsOverride
+AtomicOptions::getChangesFrom(const AtomicOptions &Base) const {
+  if (Value == Base.Value)
+    return AtomicOptionsOverride();
+  return getChangesSlow(Base);
+}
+
+inline void AtomicOptions::applyChanges(AtomicOptionsOverride AO) {
+  *this = AO.applyOverrides(*this);
+}
+
 /// Describes the kind of translation unit being processed.
 enum TranslationUnitKind {
   /// The translation unit is a complete translation unit.
diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index 25eda907d20a7b..c23ce5d9418f46 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -295,6 +295,9 @@ class TargetInfo : public TransferrableTargetInfo,
   // in function attributes in IR.
   llvm::StringSet<> ReadOnlyFeatures;
 
+  // Default atomic options
+  AtomicOptions AtomicOpts;
+
 public:
   /// Construct a target for the given options.
   ///
@@ -1674,6 +1677,9 @@ class TargetInfo : public TransferrableTargetInfo,
     return CC_C;
   }
 
+  /// Get the default atomic options.
+  AtomicOptions getAtomicOpts() const { return AtomicOpts; }
+
   enum CallingConvCheckResult {
     CCCR_OK,
     CCCR_Warning,
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 805b79491e6ea4..737514724545ae 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2346,6 +2346,14 @@ def fsymbol_partition_EQ : Joined<["-"], "fsymbol-partition=">, Group<f_Group>,
   Visibility<[ClangOption, CC1Option]>,
   MarshallingInfoString<CodeGenOpts<"SymbolPartition">>;
 
+def fatomic_EQ : CommaJoined<["-"], "fatomic=">, Group<f_Group>,
+  Visibility<[ClangOption, CC1Option]>,
+  HelpText<"Specify atomic codegen options as a comma-separated list of "
+           "key:value pairs, allowed keys and values are "
+           "no_fine_grained_memory:on|off, no_remote_memory:on|off, "
+           "ignore_denormal_mode:on|off">,
+  MarshallingInfoStringVector<LangOpts<"AtomicOptionsAsWritten">>;
+
 defm memory_profile : OptInCC1FFlag<"memory-profile", "Enable", "Disable", " heap memory profiling">;
 def fmemory_profile_EQ : Joined<["-"], "fmemory-profile=">,
     Group<f_Group>, Visibility<[ClangOption, CC1Option]>,
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 045ee754a242b3..08bab6104be04d 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3100,6 +3100,11 @@ class Parser : public CodeCompletionHandler {
   std::optional<AvailabilitySpec> ParseAvailabilitySpec();
   ExprResult ParseAvailabilityCheckExpr(SourceLocation StartLoc);
 
+  void ParseAtomicAttribute(IdentifierInfo &AttrName,
+                            SourceLocation AttrNameLoc, ParsedAttributes &Attrs,
+                            SourceLocation *EndLoc, IdentifierInfo *ScopeName,
+                            SourceLocation ScopeLoc, ParsedAttr::Form Form);
+
   void ParseExternalSourceSymbolAttribute(IdentifierInfo &ExternalSourceSymbol,
                                           SourceLocation Loc,
                                           ParsedAttributes &Attrs,
diff --git a/clang/lib/Basic/LangOptions.cpp b/clang/lib/Basic/LangOptions.cpp
index 94caf6a3897bc1..29afcdf03486d2 100644
--- a/clang/lib/Basic/LangOptions.cpp
+++ b/clang/lib/Basic/LangOptions.cpp
@@ -238,3 +238,55 @@ LLVM_DUMP_METHOD void FPOptionsOverride::dump() {
 #include "clang/Basic/FPOptions.def"
   llvm::errs() << "\n";
 }
+
+AtomicOptions
+AtomicOptions::defaultWithoutTrailingStorage(const LangOptions &LO) {
+  AtomicOptions result(LO);
+  return result;
+}
+
+AtomicOptionsOverride
+AtomicOptions::getChangesSlow(const AtomicOptions &Base) const {
+  AtomicOptions::storage_type OverrideMask = 0;
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  if (get##NAME() != Base.get##NAME())                                         \
+    OverrideMask |= NAME##Mask;
+#include "clang/Basic/AtomicOptions.def"
+  return AtomicOptionsOverride(*this, OverrideMask);
+}
+
+LLVM_DUMP_METHOD void AtomicOptions::dump() {
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  llvm::errs() << "\n " #NAME " " << get##NAME();
+#include "clang/Basic/AtomicOptions.def"
+  llvm::errs() << "\n";
+}
+
+LLVM_DUMP_METHOD void AtomicOptionsOverride::dump() {
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  if (has##NAME##Override())                                                   \
+    llvm::errs() << "\n " #NAME " Override is " << get##NAME##Override();
+#include "clang/Basic/AtomicOptions.def"
+  llvm::errs() << "\n";
+}
+
+AtomicOptionsOverride::AtomicOptionsOverride(const LangOptions &LO) {
+  for (const auto &Setting : LO.AtomicOptionsAsWritten) {
+    SmallVector<StringRef, 2> KeyValue;
+    StringRef(Setting).split(KeyValue, ":");
+    // Assuming option string has been checked elsewhere and is valid.
+    assert(KeyValue.size() == 2 && "Invalid atomic option format");
+    StringRef Key = KeyValue[0];
+    StringRef Val = KeyValue[1];
+    bool IsEnabled = (Val == "on");
+
+    if (Key == "no_fine_grained_memory")
+      setNoFineGrainedMemoryOverride(IsEnabled);
+    else if (Key == "no_remote_memory")
+      setNoRemoteMemoryOverride(IsEnabled);
+    else if (Key == "ignore_denormal_mode")
+      setIgnoreDenormalModeOverride(IsEnabled);
+    else
+      assert(false && "Unknown atomic option key");
+  }
+}
diff --git a/clang/lib/Basic/Targets/AMDGPU.cpp b/clang/lib/Basic/Targets/AMDGPU.cpp
index 078819183afdac..3a5e9f3ec0851d 100644
--- a/clang/lib/Basic/Targets/AMDGPU.cpp
+++ b/clang/lib/Basic/Targets/AMDGPU.cpp
@@ -241,6 +241,11 @@ AMDGPUTargetInfo::AMDGPUTargetInfo(const llvm::Triple &Triple,
   WavefrontSize = (GPUFeatures & llvm::AMDGPU::FEATURE_WAVE32) ? 32 : 64;
   AllowAMDGPUUnsafeFPAtomics = Opts.AllowAMDGPUUnsafeFPAtomics;
 
+  // Set the default atomic options
+  AtomicOpts.setNoRemoteMemory(true);
+  AtomicOpts.setNoFineGrainedMemory(true);
+  AtomicOpts.setIgnoreDenormalMode(Opts.AllowAMDGPUUnsafeFPAtomics);
+
   // Set pointer width and alignment for the generic address space.
   PointerWidth = PointerAlign = getPointerWidthV(LangAS::Default);
   if (getMaxPointerWidth() == 64) {
@@ -264,6 +269,8 @@ void AMDGPUTargetInfo::adjust(DiagnosticsEngine &Diags, LangOptions &Opts) {
   // to OpenCL can be removed from the following line.
   setAddressSpaceMap((Opts.OpenCL && !Opts.OpenCLGenericAddressSpace) ||
                      !isAMDGCN(getTriple()));
+
+  AtomicOpts.applyChanges(AtomicOptionsOverride(Opts));
 }
 
 ArrayRef<Builtin::Info> AMDGPUTargetInfo::getTargetBuiltins() const {
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 41dc91c578c800..fee336097ec4dd 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -728,6 +728,7 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
   bool alwaysinline = false;
   bool noconvergent = false;
   const CallExpr *musttail = nullptr;
+  AtomicOptionsOverride AOO;
 
   for (const auto *A : S.getAttrs()) {
     switch (A->getKind()) {
@@ -758,6 +759,9 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
         Builder.CreateAssumption(AssumptionVal);
       }
     } break;
+    case attr::Atomic: {
+      AOO = cast<AtomicAttr>(A)->getAtomicOptionsOverride();
+    } break;
     }
   }
   SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge);
@@ -765,6 +769,7 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
   SaveAndRestore save_alwaysinline(InAlwaysInlineAttributedStmt, alwaysinline);
   SaveAndRestore save_noconvergent(InNoConvergentAttributedStmt, noconvergent);
   SaveAndRestore save_musttail(MustTailCall, musttail);
+  CGAtomicOptionsRAII AORAII(CGM, AOO);
   EmitStmt(S.getSubStmt(), S.getAttrs());
 }
 
diff --git a/clang/...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Nov 4, 2024

@llvm/pr-subscribers-clang

Author: Yaxun (Sam) Liu (yxsamliu)

Changes

Add option and statement attribute for controlling emitting of target-specific metadata to atomicrmw instructions in IR.

The RFC for this attribute and option is
https://discourse.llvm.org/t/rfc-add-clang-atomic-control-options-and-pragmas/80641, Originally a pragma was proposed, then it was changed to clang attribute.

This attribute allows users to specify one, two, or all three options and must be applied to a compound statement. The attribute can also be nested, with inner attributes overriding the options specified by outer attributes or the target's default options. These options will then determine the target-specific metadata added to atomic instructions in the IR.

In addition to the attribute, a new compiler option is introduced: -fatomic=no_remote_memory:{on|off},no_fine_grained_memory:{on|off},ignore_denormal_mode{on|off}. This compiler option allows users to override the target's default options through the Clang driver and front end.

In terms of implementation, the atomic attribute is represented in the AST by the existing AttributedStmt, with minimal changes to AST and Sema.

During code generation in Clang, the CodeGenModule maintains the current atomic options, which are used to emit the relevant metadata for atomic instructions. RAII is used to manage the saving and restoring of atomic options when entering and exiting nested AttributedStmt.


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

26 Files Affected:

  • (added) clang/include/clang/Basic/AtomicOptions.def (+19)
  • (modified) clang/include/clang/Basic/Attr.td (+56)
  • (modified) clang/include/clang/Basic/DiagnosticDriverKinds.td (+7)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/include/clang/Basic/LangOptions.h (+167)
  • (modified) clang/include/clang/Basic/TargetInfo.h (+6)
  • (modified) clang/include/clang/Driver/Options.td (+8)
  • (modified) clang/include/clang/Parse/Parser.h (+5)
  • (modified) clang/lib/Basic/LangOptions.cpp (+52)
  • (modified) clang/lib/Basic/Targets/AMDGPU.cpp (+7)
  • (modified) clang/lib/CodeGen/CGStmt.cpp (+5)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+17)
  • (modified) clang/lib/CodeGen/CodeGenModule.cpp (+2-1)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+8)
  • (modified) clang/lib/CodeGen/Targets/AMDGPU.cpp (+12-10)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+26)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+71)
  • (modified) clang/lib/Sema/SemaStmtAttr.cpp (+43)
  • (added) clang/test/AST/ast-dump-atomic-options.hip (+102)
  • (modified) clang/test/CodeGenCUDA/amdgpu-atomic-ops.cu (+95-100)
  • (modified) clang/test/CodeGenCUDA/atomic-ops.cu (+100-100)
  • (added) clang/test/CodeGenCUDA/atomic-options.hip (+456)
  • (added) clang/test/Driver/atomic-options.hip (+31)
  • (modified) clang/test/OpenMP/amdgpu-unsafe-fp-atomics.cpp (+6-4)
  • (added) clang/test/Parser/Inputs/cuda.h (+54)
  • (added) clang/test/Parser/atomic-options.hip (+30)
diff --git a/clang/include/clang/Basic/AtomicOptions.def b/clang/include/clang/Basic/AtomicOptions.def
new file mode 100644
index 00000000000000..4cf2dab581c8b4
--- /dev/null
+++ b/clang/include/clang/Basic/AtomicOptions.def
@@ -0,0 +1,19 @@
+//===--- AtomicOptions.def - Atomic Options database -------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+// This file defines the Atomic language options. Users of this file
+// must define the OPTION macro to make use of this information.
+#ifndef OPTION
+#  error Define the OPTION macro to handle atomic language options
+#endif
+
+// OPTION(name, type, width, previousName)
+OPTION(NoRemoteMemory, bool, 1, First)
+OPTION(NoFineGrainedMemory, bool, 1, NoRemoteMemory)
+OPTION(IgnoreDenormalMode, bool, 1, NoFineGrainedMemory)
+
+#undef OPTION
\ No newline at end of file
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 156fbd1c4442eb..6b5fea1965aec3 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4838,3 +4838,59 @@ def ClspvLibclcBuiltin: InheritableAttr {
   let Documentation = [ClspvLibclcBuiltinDoc];
   let SimpleHandler = 1;
 }
+
+def Atomic : StmtAttr {
+  let Spellings = [Clang<"atomic">];
+  let Args = [
+    EnumArgument<"NoRemoteMemory", "NoRemoteMemoryTy", /*IsString*/ false,
+      ["no_remote_memory", "!no_remote_memory", ""],
+      ["NoRemoteMemoryOn", "NoRemoteMemoryOff", "NoRemoteMemoryUnset"]>,
+    EnumArgument<"NoFineGrainedMemory", "NoFineGrainedMemoryTy", /*IsString*/ false,
+      ["no_fine_grained_memory", "!no_fine_grained_memory", ""],
+      ["NoFineGrainedMemoryOn", "NoFineGrainedMemoryOff", "NoFineGrainedMemoryUnset"]>,
+    EnumArgument<"IgnoreDenormalMode", "IgnoreDenormalModeTy", /*IsString*/ false,
+      ["ignore_denormal_mode", "!ignore_denormal_mode", ""],
+      ["IgnoreDenormalModeOn", "IgnoreDenormalModeOff", "IgnoreDenormalModeUnset"]>
+  ];
+  let Subjects = SubjectList<[CompoundStmt], ErrorDiag, "compound statements">;
+  let HasCustomParsing = 1;
+  let Documentation = [Undocumented];
+  let AdditionalMembers = [{
+    AtomicOptionsOverride AOO;
+    AtomicOptionsOverride getAtomicOptionsOverride() const { return AOO; }
+    void updateAtomicOptionsOverride() {
+      switch (getNoRemoteMemory()) {
+      case NoRemoteMemoryOn:
+        AOO.setNoRemoteMemoryOverride(true);
+        break;
+      case NoRemoteMemoryOff:
+        AOO.setNoRemoteMemoryOverride(false);
+        break;
+      case NoRemoteMemoryUnset:
+        AOO.clearNoRemoteMemoryOverride();
+      }
+
+      switch (getNoFineGrainedMemory()) {
+      case NoFineGrainedMemoryOn:
+        AOO.setNoFineGrainedMemoryOverride(true);
+        break;
+      case NoFineGrainedMemoryOff:
+        AOO.setNoFineGrainedMemoryOverride(false);
+        break;
+      case NoFineGrainedMemoryUnset:
+        AOO.clearNoFineGrainedMemoryOverride();
+      }
+
+      switch (getIgnoreDenormalMode()) {
+      case IgnoreDenormalModeOn:
+        AOO.setIgnoreDenormalModeOverride(true);
+        break;
+      case IgnoreDenormalModeOff:
+        AOO.setIgnoreDenormalModeOverride(false);
+        break;
+      case IgnoreDenormalModeUnset:
+        AOO.clearIgnoreDenormalModeOverride();
+      }
+    }
+  }];
+}
diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index cdfdaa01fb121d..d72d661281d112 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -305,6 +305,13 @@ def err_drv_invalid_int_value : Error<"invalid integral value '%1' in '%0'">;
 def err_drv_invalid_value_with_suggestion : Error<
     "invalid value '%1' in '%0', expected one of: %2">;
 def err_drv_alignment_not_power_of_two : Error<"alignment is not a power of 2 in '%0'">;
+
+def err_drv_invalid_atomic_option : Error<
+  "invalid argument '%0' to -fatomic=; must be a "
+  "comma-separated list of key:value pairs, where allowed keys are "
+  "'no_fine_grained_memory', 'no_remote_memory', 'ignore_denormal_mode', "
+  "and values are 'on' or 'off', and each key must be unique">;
+
 def err_drv_invalid_remap_file : Error<
     "invalid option '%0' not of the form <from-file>;<to-file>">;
 def err_drv_invalid_gcc_install_dir : Error<"'%0' does not contain a GCC installation">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index d697e6d61afa9a..98d65d83513025 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -3275,6 +3275,8 @@ def err_invalid_branch_protection_spec : Error<
   "invalid or misplaced branch protection specification '%0'">;
 def warn_unsupported_branch_protection_spec : Warning<
   "unsupported branch protection specification '%0'">, InGroup<BranchProtection>;
+def err_attribute_invalid_atomic_argument : Error<
+  "invalid argument '%0' to atomic attribute; valid options are: 'no_remote_memory', 'no_fine_grained_memory', 'ignore_denormal_mode' (optionally prefixed with '!')">;
 
 def warn_unsupported_target_attribute
     : Warning<"%select{unsupported|duplicate|unknown}0%select{| CPU|"
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 949c8f5d448bcf..79862993575fcf 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -622,6 +622,10 @@ class LangOptions : public LangOptionsBase {
   // WebAssembly target.
   bool NoWasmOpt = false;
 
+  /// The default atomic codegen options specified by command line in the
+  /// format of key:{on|off}.
+  std::vector<std::string> AtomicOptionsAsWritten;
+
   LangOptions();
 
   /// Set language defaults for the given input language and
@@ -1093,6 +1097,169 @@ inline void FPOptions::applyChanges(FPOptionsOverride FPO) {
   *this = FPO.applyOverrides(*this);
 }
 
+/// Atomic control options
+class AtomicOptionsOverride;
+class AtomicOptions {
+public:
+  using storage_type = uint16_t;
+
+  static constexpr unsigned StorageBitSize = 8 * sizeof(storage_type);
+
+  static constexpr storage_type FirstShift = 0, FirstWidth = 0;
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  static constexpr storage_type NAME##Shift =                                  \
+      PREVIOUS##Shift + PREVIOUS##Width;                                       \
+  static constexpr storage_type NAME##Width = WIDTH;                           \
+  static constexpr storage_type NAME##Mask = ((1 << NAME##Width) - 1)          \
+                                             << NAME##Shift;
+#include "clang/Basic/AtomicOptions.def"
+
+  static constexpr storage_type TotalWidth = 0
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS) +WIDTH
+#include "clang/Basic/AtomicOptions.def"
+      ;
+  static_assert(TotalWidth <= StorageBitSize,
+                "Too short type for AtomicOptions");
+
+private:
+  storage_type Value;
+
+  AtomicOptionsOverride getChangesSlow(const AtomicOptions &Base) const;
+
+public:
+  AtomicOptions() : Value(0) {
+    setNoRemoteMemory(false);
+    setNoFineGrainedMemory(false);
+    setIgnoreDenormalMode(false);
+  }
+  explicit AtomicOptions(const LangOptions &LO) {
+    Value = 0;
+#if 0
+    setNoRemoteMemory(LO.NoRemoteMemoryAccess);
+    setNoFineGrainedMemory(LO.NoFineGrainedMemoryAccess);
+    setIgnoreDenormalMode(LO.IgnoreDenormals);
+#endif
+  }
+
+  bool operator==(AtomicOptions other) const { return Value == other.Value; }
+
+  /// Return the default value of AtomicOptions that's used when trailing
+  /// storage isn't required.
+  static AtomicOptions defaultWithoutTrailingStorage(const LangOptions &LO);
+
+  storage_type getAsOpaqueInt() const { return Value; }
+  static AtomicOptions getFromOpaqueInt(storage_type Value) {
+    AtomicOptions Opts;
+    Opts.Value = Value;
+    return Opts;
+  }
+
+  /// Return difference with the given option set.
+  AtomicOptionsOverride getChangesFrom(const AtomicOptions &Base) const;
+
+  void applyChanges(AtomicOptionsOverride AO);
+
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  TYPE get##NAME() const {                                                     \
+    return static_cast<TYPE>((Value & NAME##Mask) >> NAME##Shift);             \
+  }                                                                            \
+  void set##NAME(TYPE value) {                                                 \
+    Value = (Value & ~NAME##Mask) | (storage_type(value) << NAME##Shift);      \
+  }
+#include "clang/Basic/AtomicOptions.def"
+  LLVM_DUMP_METHOD void dump();
+};
+
+/// Represents difference between two AtomicOptions values.
+class AtomicOptionsOverride {
+  AtomicOptions Options = AtomicOptions::getFromOpaqueInt(0);
+  AtomicOptions::storage_type OverrideMask = 0;
+
+public:
+  /// The type suitable for storing values of AtomicOptionsOverride. Must be
+  /// twice as wide as bit size of AtomicOption.
+  using storage_type = uint32_t;
+  static_assert(sizeof(storage_type) >= 2 * sizeof(AtomicOptions::storage_type),
+                "Too short type for AtomicOptionsOverride");
+
+  /// Bit mask selecting bits of OverrideMask in serialized representation of
+  /// AtomicOptionsOverride.
+  static constexpr storage_type OverrideMaskBits =
+      (static_cast<storage_type>(1) << AtomicOptions::StorageBitSize) - 1;
+
+  AtomicOptionsOverride() {}
+  AtomicOptionsOverride(const LangOptions &LO);
+  AtomicOptionsOverride(AtomicOptions AO)
+      : Options(AO), OverrideMask(OverrideMaskBits) {}
+  AtomicOptionsOverride(AtomicOptions AO, AtomicOptions::storage_type Mask)
+      : Options(AO), OverrideMask(Mask) {}
+
+  bool requiresTrailingStorage() const { return OverrideMask != 0; }
+
+  storage_type getAsOpaqueInt() const {
+    return (static_cast<storage_type>(Options.getAsOpaqueInt())
+            << AtomicOptions::StorageBitSize) |
+           OverrideMask;
+  }
+
+  static AtomicOptionsOverride getFromOpaqueInt(storage_type I) {
+    AtomicOptionsOverride Opts;
+    Opts.OverrideMask = I & OverrideMaskBits;
+    Opts.Options =
+        AtomicOptions::getFromOpaqueInt(I >> AtomicOptions::StorageBitSize);
+    return Opts;
+  }
+
+  AtomicOptions applyOverrides(AtomicOptions Base) {
+    AtomicOptions Result = AtomicOptions::getFromOpaqueInt(
+        (Base.getAsOpaqueInt() & ~OverrideMask) |
+        (Options.getAsOpaqueInt() & OverrideMask));
+    return Result;
+  }
+
+  AtomicOptions applyOverrides(const LangOptions &LO) {
+    return applyOverrides(AtomicOptions(LO));
+  }
+
+  bool operator==(AtomicOptionsOverride other) const {
+    return Options == other.Options && OverrideMask == other.OverrideMask;
+  }
+  bool operator!=(AtomicOptionsOverride other) const {
+    return !(*this == other);
+  }
+
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  bool has##NAME##Override() const {                                           \
+    return OverrideMask & AtomicOptions::NAME##Mask;                           \
+  }                                                                            \
+  TYPE get##NAME##Override() const {                                           \
+    assert(has##NAME##Override());                                             \
+    return Options.get##NAME();                                                \
+  }                                                                            \
+  void clear##NAME##Override() {                                               \
+    Options.set##NAME(TYPE(0));                                                \
+    OverrideMask &= ~AtomicOptions::NAME##Mask;                                \
+  }                                                                            \
+  void set##NAME##Override(TYPE value) {                                       \
+    Options.set##NAME(value);                                                  \
+    OverrideMask |= AtomicOptions::NAME##Mask;                                 \
+  }
+#include "clang/Basic/AtomicOptions.def"
+
+  LLVM_DUMP_METHOD void dump();
+};
+
+inline AtomicOptionsOverride
+AtomicOptions::getChangesFrom(const AtomicOptions &Base) const {
+  if (Value == Base.Value)
+    return AtomicOptionsOverride();
+  return getChangesSlow(Base);
+}
+
+inline void AtomicOptions::applyChanges(AtomicOptionsOverride AO) {
+  *this = AO.applyOverrides(*this);
+}
+
 /// Describes the kind of translation unit being processed.
 enum TranslationUnitKind {
   /// The translation unit is a complete translation unit.
diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index 25eda907d20a7b..c23ce5d9418f46 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -295,6 +295,9 @@ class TargetInfo : public TransferrableTargetInfo,
   // in function attributes in IR.
   llvm::StringSet<> ReadOnlyFeatures;
 
+  // Default atomic options
+  AtomicOptions AtomicOpts;
+
 public:
   /// Construct a target for the given options.
   ///
@@ -1674,6 +1677,9 @@ class TargetInfo : public TransferrableTargetInfo,
     return CC_C;
   }
 
+  /// Get the default atomic options.
+  AtomicOptions getAtomicOpts() const { return AtomicOpts; }
+
   enum CallingConvCheckResult {
     CCCR_OK,
     CCCR_Warning,
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 805b79491e6ea4..737514724545ae 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -2346,6 +2346,14 @@ def fsymbol_partition_EQ : Joined<["-"], "fsymbol-partition=">, Group<f_Group>,
   Visibility<[ClangOption, CC1Option]>,
   MarshallingInfoString<CodeGenOpts<"SymbolPartition">>;
 
+def fatomic_EQ : CommaJoined<["-"], "fatomic=">, Group<f_Group>,
+  Visibility<[ClangOption, CC1Option]>,
+  HelpText<"Specify atomic codegen options as a comma-separated list of "
+           "key:value pairs, allowed keys and values are "
+           "no_fine_grained_memory:on|off, no_remote_memory:on|off, "
+           "ignore_denormal_mode:on|off">,
+  MarshallingInfoStringVector<LangOpts<"AtomicOptionsAsWritten">>;
+
 defm memory_profile : OptInCC1FFlag<"memory-profile", "Enable", "Disable", " heap memory profiling">;
 def fmemory_profile_EQ : Joined<["-"], "fmemory-profile=">,
     Group<f_Group>, Visibility<[ClangOption, CC1Option]>,
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 045ee754a242b3..08bab6104be04d 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3100,6 +3100,11 @@ class Parser : public CodeCompletionHandler {
   std::optional<AvailabilitySpec> ParseAvailabilitySpec();
   ExprResult ParseAvailabilityCheckExpr(SourceLocation StartLoc);
 
+  void ParseAtomicAttribute(IdentifierInfo &AttrName,
+                            SourceLocation AttrNameLoc, ParsedAttributes &Attrs,
+                            SourceLocation *EndLoc, IdentifierInfo *ScopeName,
+                            SourceLocation ScopeLoc, ParsedAttr::Form Form);
+
   void ParseExternalSourceSymbolAttribute(IdentifierInfo &ExternalSourceSymbol,
                                           SourceLocation Loc,
                                           ParsedAttributes &Attrs,
diff --git a/clang/lib/Basic/LangOptions.cpp b/clang/lib/Basic/LangOptions.cpp
index 94caf6a3897bc1..29afcdf03486d2 100644
--- a/clang/lib/Basic/LangOptions.cpp
+++ b/clang/lib/Basic/LangOptions.cpp
@@ -238,3 +238,55 @@ LLVM_DUMP_METHOD void FPOptionsOverride::dump() {
 #include "clang/Basic/FPOptions.def"
   llvm::errs() << "\n";
 }
+
+AtomicOptions
+AtomicOptions::defaultWithoutTrailingStorage(const LangOptions &LO) {
+  AtomicOptions result(LO);
+  return result;
+}
+
+AtomicOptionsOverride
+AtomicOptions::getChangesSlow(const AtomicOptions &Base) const {
+  AtomicOptions::storage_type OverrideMask = 0;
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  if (get##NAME() != Base.get##NAME())                                         \
+    OverrideMask |= NAME##Mask;
+#include "clang/Basic/AtomicOptions.def"
+  return AtomicOptionsOverride(*this, OverrideMask);
+}
+
+LLVM_DUMP_METHOD void AtomicOptions::dump() {
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  llvm::errs() << "\n " #NAME " " << get##NAME();
+#include "clang/Basic/AtomicOptions.def"
+  llvm::errs() << "\n";
+}
+
+LLVM_DUMP_METHOD void AtomicOptionsOverride::dump() {
+#define OPTION(NAME, TYPE, WIDTH, PREVIOUS)                                    \
+  if (has##NAME##Override())                                                   \
+    llvm::errs() << "\n " #NAME " Override is " << get##NAME##Override();
+#include "clang/Basic/AtomicOptions.def"
+  llvm::errs() << "\n";
+}
+
+AtomicOptionsOverride::AtomicOptionsOverride(const LangOptions &LO) {
+  for (const auto &Setting : LO.AtomicOptionsAsWritten) {
+    SmallVector<StringRef, 2> KeyValue;
+    StringRef(Setting).split(KeyValue, ":");
+    // Assuming option string has been checked elsewhere and is valid.
+    assert(KeyValue.size() == 2 && "Invalid atomic option format");
+    StringRef Key = KeyValue[0];
+    StringRef Val = KeyValue[1];
+    bool IsEnabled = (Val == "on");
+
+    if (Key == "no_fine_grained_memory")
+      setNoFineGrainedMemoryOverride(IsEnabled);
+    else if (Key == "no_remote_memory")
+      setNoRemoteMemoryOverride(IsEnabled);
+    else if (Key == "ignore_denormal_mode")
+      setIgnoreDenormalModeOverride(IsEnabled);
+    else
+      assert(false && "Unknown atomic option key");
+  }
+}
diff --git a/clang/lib/Basic/Targets/AMDGPU.cpp b/clang/lib/Basic/Targets/AMDGPU.cpp
index 078819183afdac..3a5e9f3ec0851d 100644
--- a/clang/lib/Basic/Targets/AMDGPU.cpp
+++ b/clang/lib/Basic/Targets/AMDGPU.cpp
@@ -241,6 +241,11 @@ AMDGPUTargetInfo::AMDGPUTargetInfo(const llvm::Triple &Triple,
   WavefrontSize = (GPUFeatures & llvm::AMDGPU::FEATURE_WAVE32) ? 32 : 64;
   AllowAMDGPUUnsafeFPAtomics = Opts.AllowAMDGPUUnsafeFPAtomics;
 
+  // Set the default atomic options
+  AtomicOpts.setNoRemoteMemory(true);
+  AtomicOpts.setNoFineGrainedMemory(true);
+  AtomicOpts.setIgnoreDenormalMode(Opts.AllowAMDGPUUnsafeFPAtomics);
+
   // Set pointer width and alignment for the generic address space.
   PointerWidth = PointerAlign = getPointerWidthV(LangAS::Default);
   if (getMaxPointerWidth() == 64) {
@@ -264,6 +269,8 @@ void AMDGPUTargetInfo::adjust(DiagnosticsEngine &Diags, LangOptions &Opts) {
   // to OpenCL can be removed from the following line.
   setAddressSpaceMap((Opts.OpenCL && !Opts.OpenCLGenericAddressSpace) ||
                      !isAMDGCN(getTriple()));
+
+  AtomicOpts.applyChanges(AtomicOptionsOverride(Opts));
 }
 
 ArrayRef<Builtin::Info> AMDGPUTargetInfo::getTargetBuiltins() const {
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 41dc91c578c800..fee336097ec4dd 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -728,6 +728,7 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
   bool alwaysinline = false;
   bool noconvergent = false;
   const CallExpr *musttail = nullptr;
+  AtomicOptionsOverride AOO;
 
   for (const auto *A : S.getAttrs()) {
     switch (A->getKind()) {
@@ -758,6 +759,9 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
         Builder.CreateAssumption(AssumptionVal);
       }
     } break;
+    case attr::Atomic: {
+      AOO = cast<AtomicAttr>(A)->getAtomicOptionsOverride();
+    } break;
     }
   }
   SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge);
@@ -765,6 +769,7 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
   SaveAndRestore save_alwaysinline(InAlwaysInlineAttributedStmt, alwaysinline);
   SaveAndRestore save_noconvergent(InNoConvergentAttributedStmt, noconvergent);
   SaveAndRestore save_musttail(MustTailCall, musttail);
+  CGAtomicOptionsRAII AORAII(CGM, AOO);
   EmitStmt(S.getSubStmt(), S.getAttrs());
 }
 
diff --git a/clang/...
[truncated]

@yxsamliu yxsamliu force-pushed the atomic-attr branch 2 times, most recently from d138d70 to 4ab88d1 Compare November 7, 2024 19:07
@yxsamliu
Copy link
Collaborator Author

ping

Copy link
Member

@Artem-B Artem-B left a comment

Choose a reason for hiding this comment

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

Mostly drive-by comments. Don't have strong opinion on either the attributes themselves nor how the values get parsed.

One thing that's missing is the documentation for the attributes. Their format and meaning are far from obvious and should be documented along with other clang-specific attributes.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

The changes should definitely come with a release note in clang/docs/ReleaseNotes.rst, but this also seems like it maybe should get wider documentation in the language extensions manual. I mentioned a need for attribute documentation, that could be simple docs which point into the more verbose docs in the language extensions section, perhaps

@yxsamliu
Copy link
Collaborator Author

Mostly drive-by comments. Don't have strong opinion on either the attributes themselves nor how the values get parsed.

One thing that's missing is the documentation for the attributes. Their format and meaning are far from obvious and should be documented along with other clang-specific attributes.

will add documentation for atomic attributes

@yxsamliu
Copy link
Collaborator Author

The changes should definitely come with a release note in clang/docs/ReleaseNotes.rst, but this also seems like it maybe should get wider documentation in the language extensions manual. I mentioned a need for attribute documentation, that could be simple docs which point into the more verbose docs in the language extensions section, perhaps

will do

@yxsamliu yxsamliu force-pushed the atomic-attr branch 2 times, most recently from 5d4b96b to a6564a1 Compare January 31, 2025 22:46
@yxsamliu yxsamliu force-pushed the atomic-attr branch 2 times, most recently from 85b6a4c to 7faa966 Compare February 5, 2025 16:15
@yxsamliu yxsamliu force-pushed the atomic-attr branch 2 times, most recently from 341472d to 62c7907 Compare February 6, 2025 19:00
@yxsamliu
Copy link
Collaborator Author

ping

@erichkeane
Copy link
Collaborator

ping

As mentioned in the discourse, quite a few of us were out of contact thanks to the WG21 meeting that was last week. I'll take another look now.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

On the right track! I think this is still a little over complicated as implemented, but I have some suggestions above to help.

Add option and statement attribute for controlling emitting of target-specific
metadata to atomicrmw instructions in IR.

The RFC for this attribute and option is
https://discourse.llvm.org/t/rfc-add-clang-atomic-control-options-and-pragmas/80641,
Originally a pragma was proposed, then it was changed to clang attribute.

This attribute allows users to specify one, two, or all three options and must be applied
to a compound statement. The attribute can also be nested, with inner attributes
overriding the options specified by outer attributes or the target's default
options. These options will then determine the target-specific metadata added to atomic
instructions in the IR.

In addition to the attribute, three new compiler options are introduced:
`-f[no-]atomic-remote-memory`, `-f[no-]atomic-fine-grained-memory`,
`-f[no-]atomic-ignore-denormal-mode`.
These compiler options allow users to override the default options through the
Clang driver and front end. `-m[no-]unsafe-fp-atomics` is alised to
`-f[no-]ignore-denormal-mode`.

In terms of implementation, the atomic attribute is represented in the AST by the
existing AttributedStmt, with minimal changes to AST and Sema.

During code generation in Clang, the CodeGenModule maintains the current atomic options,
which are used to emit the relevant metadata for atomic instructions. RAII is used
to manage the saving and restoring of atomic options when entering
and exiting nested AttributedStmt.
@yxsamliu yxsamliu merged commit 240f226 into llvm:main Feb 27, 2025
12 checks passed
joaosaffran pushed a commit to joaosaffran/llvm-project that referenced this pull request Mar 3, 2025
Add option and statement attribute for controlling emitting of
target-specific
metadata to atomicrmw instructions in IR.

The RFC for this attribute and option is

https://discourse.llvm.org/t/rfc-add-clang-atomic-control-options-and-pragmas/80641,
Originally a pragma was proposed, then it was changed to clang
attribute.

This attribute allows users to specify one, two, or all three options
and must be applied
to a compound statement. The attribute can also be nested, with inner
attributes
overriding the options specified by outer attributes or the target's
default
options. These options will then determine the target-specific metadata
added to atomic
instructions in the IR.

In addition to the attribute, three new compiler options are introduced:
`-f[no-]atomic-remote-memory`, `-f[no-]atomic-fine-grained-memory`,
 `-f[no-]atomic-ignore-denormal-mode`.
These compiler options allow users to override the default options
through the
Clang driver and front end. `-m[no-]unsafe-fp-atomics` is aliased to
`-f[no-]ignore-denormal-mode`.

In terms of implementation, the atomic attribute is represented in the
AST by the
existing AttributedStmt, with minimal changes to AST and Sema.

During code generation in Clang, the CodeGenModule maintains the current
atomic options,
which are used to emit the relevant metadata for atomic instructions.
RAII is used
to manage the saving and restoring of atomic options when entering
and exiting nested AttributedStmt.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:AMDGPU clang:codegen IR generation bugs: mangling, exceptions, etc. clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:openmp OpenMP related changes to Clang clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants