Skip to content

[PAC][clang] Define PointerAuthQualifier and PointerAuthenticationMode #84384

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 13 commits into from
Apr 26, 2024

Conversation

kovdan01
Copy link
Contributor

@kovdan01 kovdan01 commented Mar 7, 2024

This brings declarations of PointerAuthQualifier class and PointerAuthenticationMode enum and related functions required for PAuth support in lldb (see #84387) from downstream Apple's code. See #84387 for tests as well.

Co-authored-by: Ahmed Bougacha [email protected]
Co-authored-by: John McCall [email protected]

@kovdan01
Copy link
Contributor Author

kovdan01 commented Mar 7, 2024

I've left 4 commits to distinguish code coming from different sources during
review. Here is the origin of the code for each of the commits:

Commits from @ahmedbougacha's repo are taken from the eng/arm64e-upstream-llvmorg branch.

Copy link

github-actions bot commented Mar 7, 2024

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

@kovdan01 kovdan01 force-pushed the clang-pointer-auth-basic-declarations branch from 0b75c0a to cbfde21 Compare March 7, 2024 21:58
@kovdan01 kovdan01 marked this pull request as ready for review March 7, 2024 22:05
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules labels Mar 7, 2024
@llvmbot
Copy link
Member

llvmbot commented Mar 7, 2024

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-modules

Author: Daniil Kovalev (kovdan01)

Changes

This brings declarations of PointerAuthQualifier class and
PointerAuthenticationMode enum and related functions required for PAuth
support in lldb (see #84387) from downstream Apple's code.

Co-authored-by: Ahmed Bougacha <[email protected]>
Co-authored-by: John McCall <[email protected]>


Full diff: https://github.com/llvm/llvm-project/pull/84384.diff

6 Files Affected:

  • (modified) clang/include/clang/AST/ASTContext.h (+10)
  • (modified) clang/include/clang/AST/AbstractBasicReader.h (+2-2)
  • (modified) clang/include/clang/AST/AbstractBasicWriter.h (+2-2)
  • (modified) clang/include/clang/AST/Type.h (+208-11)
  • (modified) clang/include/clang/Basic/LangOptions.h (+7)
  • (added) clang/include/clang/Basic/PointerAuthOptions.h (+23)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index ff6b64c7f72d57..4fd214592d1f27 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -2182,6 +2182,16 @@ class ASTContext : public RefCountedBase<ASTContext> {
     return getQualifiedType(type.getUnqualifiedType(), Qs);
   }
 
+  /// \brief Return a type with the given __ptrauth qualifier.
+  QualType getPointerAuthType(QualType type, PointerAuthQualifier pointerAuth) {
+    assert(!type.getPointerAuth());
+    assert(pointerAuth);
+
+    Qualifiers qs;
+    qs.setPointerAuth(pointerAuth);
+    return getQualifiedType(type, qs);
+  }
+
   unsigned char getFixedPointScale(QualType Ty) const;
   unsigned char getFixedPointIBits(QualType Ty) const;
   llvm::FixedPointSemantics getFixedPointSemantics(QualType Ty) const;
diff --git a/clang/include/clang/AST/AbstractBasicReader.h b/clang/include/clang/AST/AbstractBasicReader.h
index 1f2797cc701458..ab036f1d445acc 100644
--- a/clang/include/clang/AST/AbstractBasicReader.h
+++ b/clang/include/clang/AST/AbstractBasicReader.h
@@ -213,9 +213,9 @@ class DataStreamBasicReader : public BasicReaderBase<Impl> {
   }
 
   Qualifiers readQualifiers() {
-    static_assert(sizeof(Qualifiers().getAsOpaqueValue()) <= sizeof(uint32_t),
+    static_assert(sizeof(Qualifiers().getAsOpaqueValue()) <= sizeof(uint64_t),
                   "update this if the value size changes");
-    uint32_t value = asImpl().readUInt32();
+    uint64_t value = asImpl().readUInt64();
     return Qualifiers::fromOpaqueValue(value);
   }
 
diff --git a/clang/include/clang/AST/AbstractBasicWriter.h b/clang/include/clang/AST/AbstractBasicWriter.h
index 07afa388de2c17..8e42fcaad1d388 100644
--- a/clang/include/clang/AST/AbstractBasicWriter.h
+++ b/clang/include/clang/AST/AbstractBasicWriter.h
@@ -196,9 +196,9 @@ class DataStreamBasicWriter : public BasicWriterBase<Impl> {
   }
 
   void writeQualifiers(Qualifiers value) {
-    static_assert(sizeof(value.getAsOpaqueValue()) <= sizeof(uint32_t),
+    static_assert(sizeof(value.getAsOpaqueValue()) <= sizeof(uint64_t),
                   "update this if the value size changes");
-    asImpl().writeUInt32(value.getAsOpaqueValue());
+    asImpl().writeUInt64(value.getAsOpaqueValue());
   }
 
   void writeExceptionSpecInfo(
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 1942b0e67f65a3..48a51dd851ac36 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -25,8 +25,10 @@
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/ExceptionSpecificationType.h"
 #include "clang/Basic/LLVM.h"
+#include "clang/Basic/LangOptions.h"
 #include "clang/Basic/Linkage.h"
 #include "clang/Basic/PartialDiagnostic.h"
+#include "clang/Basic/PointerAuthOptions.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/Specifiers.h"
 #include "clang/Basic/Visibility.h"
@@ -138,6 +140,165 @@ using CanQualType = CanQual<Type>;
 #define TYPE(Class, Base) class Class##Type;
 #include "clang/AST/TypeNodes.inc"
 
+/// Pointer-authentication qualifiers.
+class PointerAuthQualifier {
+  enum : uint32_t {
+    EnabledShift = 0,
+    EnabledBits = 1,
+    EnabledMask = 1 << EnabledShift,
+    AddressDiscriminatedShift = EnabledShift + EnabledBits,
+    AddressDiscriminatedBits = 1,
+    AddressDiscriminatedMask = 1 << AddressDiscriminatedShift,
+    AuthenticationModeShift =
+        AddressDiscriminatedShift + AddressDiscriminatedBits,
+    AuthenticationModeBits = 2,
+    AuthenticationModeMask = ((1 << AuthenticationModeBits) - 1)
+                             << AuthenticationModeShift,
+    IsaPointerShift = AuthenticationModeShift + AuthenticationModeBits,
+    IsaPointerBits = 1,
+    IsaPointerMask = ((1 << IsaPointerBits) - 1) << IsaPointerShift,
+    AuthenticatesNullValuesShift = IsaPointerShift + IsaPointerBits,
+    AuthenticatesNullValuesBits = 1,
+    AuthenticatesNullValuesMask = ((1 << AuthenticatesNullValuesBits) - 1)
+                                  << AuthenticatesNullValuesShift,
+    KeyShift = AuthenticatesNullValuesShift + AuthenticatesNullValuesBits,
+    KeyBits = 10,
+    KeyMask = ((1 << KeyBits) - 1) << KeyShift,
+    DiscriminatorShift = KeyShift + KeyBits,
+    DiscriminatorBits = 16,
+    DiscriminatorMask = ((1u << DiscriminatorBits) - 1) << DiscriminatorShift,
+  };
+
+  // bits:     |0      |1      |2..3              |4          |
+  //           |Enabled|Address|AuthenticationMode|ISA pointer|
+  // bits:     |5                |6..15|   16...31   |
+  //           |AuthenticatesNull|Key  |Discriminator|
+  uint32_t Data;
+
+  static_assert((EnabledBits + AddressDiscriminatedBits +
+                 AuthenticationModeBits + IsaPointerBits +
+                 AuthenticatesNullValuesBits + KeyBits + DiscriminatorBits) ==
+                    32,
+                "PointerAuthQualifier should be exactly 32 bits");
+  static_assert((EnabledMask + AddressDiscriminatedMask +
+                 AuthenticationModeMask + IsaPointerMask +
+                 AuthenticatesNullValuesMask + KeyMask + DiscriminatorMask) ==
+                    0xFFFFFFFF,
+                "All masks should cover the entire bits");
+  static_assert((EnabledMask ^ AddressDiscriminatedMask ^
+                 AuthenticationModeMask ^ IsaPointerMask ^
+                 AuthenticatesNullValuesMask ^ KeyMask ^ DiscriminatorMask) ==
+                    0xFFFFFFFF,
+                "All masks should cover the entire bits");
+
+  PointerAuthQualifier(unsigned key, bool isAddressDiscriminated,
+                       unsigned extraDiscriminator,
+                       PointerAuthenticationMode authenticationMode,
+                       bool isIsaPointer, bool authenticatesNullValues)
+      : Data(EnabledMask |
+             (isAddressDiscriminated
+                  ? static_cast<uint32_t>(AddressDiscriminatedMask)
+                  : 0) |
+             (key << KeyShift) |
+             (unsigned(authenticationMode) << AuthenticationModeShift) |
+             (extraDiscriminator << DiscriminatorShift) |
+             (isIsaPointer << IsaPointerShift) |
+             (authenticatesNullValues << AuthenticatesNullValuesShift)) {
+    assert(key <= KeyNoneInternal);
+    assert(extraDiscriminator <= MaxDiscriminator);
+  }
+
+public:
+  enum {
+    KeyNoneInternal = (1u << KeyBits) - 1,
+
+    /// The maximum supported pointer-authentication key.
+    MaxKey = KeyNoneInternal - 1,
+
+    /// The maximum supported pointer-authentication discriminator.
+    MaxDiscriminator = (1u << DiscriminatorBits) - 1
+  };
+
+public:
+  PointerAuthQualifier() : Data(0) {}
+
+  static PointerAuthQualifier
+  Create(int key, bool isAddressDiscriminated, unsigned extraDiscriminator,
+         PointerAuthenticationMode authenticationMode, bool isIsaPointer,
+         bool authenticatesNullValues) {
+    if (key == PointerAuthKeyNone)
+      key = KeyNoneInternal;
+    assert((key >= 0 && key <= KeyNoneInternal) && "out-of-range key value");
+    return PointerAuthQualifier(key, isAddressDiscriminated, extraDiscriminator,
+                                authenticationMode, isIsaPointer,
+                                authenticatesNullValues);
+  }
+
+  bool isPresent() const {
+    return getAuthenticationMode() != PointerAuthenticationMode::None;
+  }
+
+  explicit operator bool() const { return isPresent(); }
+
+  unsigned getKey() const {
+    assert(isPresent());
+    return (Data & KeyMask) >> KeyShift;
+  }
+
+  bool hasKeyNone() const { return isPresent() && getKey() == KeyNoneInternal; }
+
+  bool isAddressDiscriminated() const {
+    assert(isPresent());
+    return (Data & AddressDiscriminatedMask) >> AddressDiscriminatedShift;
+  }
+
+  unsigned getExtraDiscriminator() const {
+    assert(isPresent());
+    return (Data >> DiscriminatorShift);
+  }
+
+  PointerAuthenticationMode getAuthenticationMode() const {
+    return PointerAuthenticationMode((Data & AuthenticationModeMask) >>
+                                     AuthenticationModeShift);
+  }
+
+  bool isIsaPointer() const {
+    assert(isPresent());
+    return (Data & IsaPointerMask) >> IsaPointerShift;
+  }
+
+  bool authenticatesNullValues() const {
+    assert(isPresent());
+    return (Data & AuthenticatesNullValuesMask) >> AuthenticatesNullValuesShift;
+  }
+
+  PointerAuthQualifier withoutKeyNone() const {
+    return hasKeyNone() ? PointerAuthQualifier() : *this;
+  }
+
+  friend bool operator==(PointerAuthQualifier Lhs, PointerAuthQualifier Rhs) {
+    return Lhs.Data == Rhs.Data;
+  }
+  friend bool operator!=(PointerAuthQualifier Lhs, PointerAuthQualifier Rhs) {
+    return Lhs.Data != Rhs.Data;
+  }
+
+  bool isEquivalent(PointerAuthQualifier Other) const {
+    return withoutKeyNone() == Other.withoutKeyNone();
+  }
+
+  uint32_t getAsOpaqueValue() const { return Data; }
+
+  // Deserialize pointer-auth qualifiers from an opaque representation.
+  static PointerAuthQualifier fromOpaqueValue(uint32_t opaque) {
+    PointerAuthQualifier result;
+    result.Data = opaque;
+    return result;
+  }
+
+  void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(Data); }
+};
+
 /// The collection of all-type qualifiers we support.
 /// Clang supports five independent qualifiers:
 /// * C99: const, volatile, and restrict
@@ -193,19 +354,27 @@ class Qualifiers {
     FastMask = (1 << FastWidth) - 1
   };
 
+  Qualifiers() : Mask(0), PtrAuth() {}
+
   /// Returns the common set of qualifiers while removing them from
   /// the given sets.
   static Qualifiers removeCommonQualifiers(Qualifiers &L, Qualifiers &R) {
+    Qualifiers Q;
+    if (L.getPointerAuth().isEquivalent(R.getPointerAuth())) {
+      Q.setPointerAuth(L.getPointerAuth().withoutKeyNone());
+      PointerAuthQualifier Empty;
+      L.setPointerAuth(Empty);
+      R.setPointerAuth(Empty);
+    }
+
     // If both are only CVR-qualified, bit operations are sufficient.
     if (!(L.Mask & ~CVRMask) && !(R.Mask & ~CVRMask)) {
-      Qualifiers Q;
       Q.Mask = L.Mask & R.Mask;
       L.Mask &= ~Q.Mask;
       R.Mask &= ~Q.Mask;
       return Q;
     }
 
-    Qualifiers Q;
     unsigned CommonCRV = L.getCVRQualifiers() & R.getCVRQualifiers();
     Q.addCVRQualifiers(CommonCRV);
     L.removeCVRQualifiers(CommonCRV);
@@ -250,15 +419,16 @@ class Qualifiers {
   }
 
   // Deserialize qualifiers from an opaque representation.
-  static Qualifiers fromOpaqueValue(unsigned opaque) {
+  static Qualifiers fromOpaqueValue(uint64_t opaque) {
     Qualifiers Qs;
-    Qs.Mask = opaque;
+    Qs.Mask = uint32_t(opaque);
+    Qs.PtrAuth = PointerAuthQualifier::fromOpaqueValue(uint32_t(opaque >> 32));
     return Qs;
   }
 
   // Serialize these qualifiers into an opaque representation.
-  unsigned getAsOpaqueValue() const {
-    return Mask;
+  uint64_t getAsOpaqueValue() const {
+    return uint64_t(Mask) | (uint64_t(PtrAuth.getAsOpaqueValue()) << 32);
   }
 
   bool hasConst() const { return Mask & Const; }
@@ -406,6 +576,10 @@ class Qualifiers {
     setAddressSpace(space);
   }
 
+  PointerAuthQualifier getPointerAuth() const { return PtrAuth; }
+  void setPointerAuth(PointerAuthQualifier q) { PtrAuth = q; }
+  void removePtrAuth() { PtrAuth = PointerAuthQualifier(); }
+
   // Fast qualifiers are those that can be allocated directly
   // on a QualType object.
   bool hasFastQualifiers() const { return getFastQualifiers(); }
@@ -428,7 +602,7 @@ class Qualifiers {
 
   /// Return true if the set contains any qualifiers which require an ExtQuals
   /// node to be allocated.
-  bool hasNonFastQualifiers() const { return Mask & ~FastMask; }
+  bool hasNonFastQualifiers() const { return (Mask & ~FastMask) || PtrAuth; }
   Qualifiers getNonFastQualifiers() const {
     Qualifiers Quals = *this;
     Quals.setFastQualifiers(0);
@@ -436,8 +610,8 @@ class Qualifiers {
   }
 
   /// Return true if the set contains any qualifiers.
-  bool hasQualifiers() const { return Mask; }
-  bool empty() const { return !Mask; }
+  bool hasQualifiers() const { return Mask || PtrAuth; }
+  bool empty() const { return !hasQualifiers(); }
 
   /// Add the qualifiers from the given set to this set.
   void addQualifiers(Qualifiers Q) {
@@ -454,6 +628,9 @@ class Qualifiers {
       if (Q.hasObjCLifetime())
         addObjCLifetime(Q.getObjCLifetime());
     }
+
+    if (Q.PtrAuth)
+      PtrAuth = Q.PtrAuth;
   }
 
   /// Remove the qualifiers from the given set from this set.
@@ -471,6 +648,9 @@ class Qualifiers {
       if (getAddressSpace() == Q.getAddressSpace())
         removeAddressSpace();
     }
+
+    if (PtrAuth == Q.PtrAuth)
+      PtrAuth = PointerAuthQualifier();
   }
 
   /// Add the qualifiers from the given set to this set, given that
@@ -482,7 +662,10 @@ class Qualifiers {
            !hasObjCGCAttr() || !qs.hasObjCGCAttr());
     assert(getObjCLifetime() == qs.getObjCLifetime() ||
            !hasObjCLifetime() || !qs.hasObjCLifetime());
+    assert(!PtrAuth || !qs.PtrAuth || PtrAuth == qs.PtrAuth);
     Mask |= qs.Mask;
+    if (qs.PtrAuth)
+      PtrAuth = qs.PtrAuth;
   }
 
   /// Returns true if address space A is equal to or a superset of B.
@@ -535,6 +718,8 @@ class Qualifiers {
            // be changed.
            (getObjCGCAttr() == other.getObjCGCAttr() || !hasObjCGCAttr() ||
             !other.hasObjCGCAttr()) &&
+           // Pointer-auth qualifiers must match exactly.
+           PtrAuth == other.PtrAuth &&
            // ObjC lifetime qualifiers must match exactly.
            getObjCLifetime() == other.getObjCLifetime() &&
            // CVR qualifiers may subset.
@@ -567,8 +752,12 @@ class Qualifiers {
   /// another set of qualifiers, not considering qualifier compatibility.
   bool isStrictSupersetOf(Qualifiers Other) const;
 
-  bool operator==(Qualifiers Other) const { return Mask == Other.Mask; }
-  bool operator!=(Qualifiers Other) const { return Mask != Other.Mask; }
+  bool operator==(Qualifiers Other) const {
+    return Mask == Other.Mask && PtrAuth == Other.PtrAuth;
+  }
+  bool operator!=(Qualifiers Other) const {
+    return Mask != Other.Mask || PtrAuth != Other.PtrAuth;
+  }
 
   explicit operator bool() const { return hasQualifiers(); }
 
@@ -606,6 +795,7 @@ class Qualifiers {
 
   void Profile(llvm::FoldingSetNodeID &ID) const {
     ID.AddInteger(Mask);
+    PtrAuth.Profile(ID);
   }
 
 private:
@@ -613,6 +803,9 @@ class Qualifiers {
   //           |C R V|U|GCAttr|Lifetime|AddressSpace|
   uint32_t Mask = 0;
 
+  PointerAuthQualifier PtrAuth;
+  static_assert(sizeof(PointerAuthQualifier) == sizeof(uint32_t),
+                "PointerAuthQualifier must be 32 bits");
   static const uint32_t UMask = 0x8;
   static const uint32_t UShift = 3;
   static const uint32_t GCAttrMask = 0x30;
@@ -1241,6 +1434,10 @@ class QualType {
   // true when Type is objc's weak and weak is enabled but ARC isn't.
   bool isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const;
 
+  PointerAuthQualifier getPointerAuth() const {
+    return getQualifiers().getPointerAuth();
+  }
+
   enum PrimitiveDefaultInitializeKind {
     /// The type does not fall into any of the following categories. Note that
     /// this case is zero-valued so that values of this enum can be used as a
diff --git a/clang/include/clang/Basic/LangOptions.h b/clang/include/clang/Basic/LangOptions.h
index 862952d336ef31..6fe7472d8ad0ca 100644
--- a/clang/include/clang/Basic/LangOptions.h
+++ b/clang/include/clang/Basic/LangOptions.h
@@ -57,6 +57,13 @@ enum class ShaderStage {
   Invalid,
 };
 
+enum class PointerAuthenticationMode : unsigned {
+  None,
+  Strip,
+  SignAndStrip,
+  SignAndAuth
+};
+
 /// Bitfields of LangOptions, split out from LangOptions in order to ensure that
 /// this large collection of bitfields is a trivial class type.
 class LangOptionsBase {
diff --git a/clang/include/clang/Basic/PointerAuthOptions.h b/clang/include/clang/Basic/PointerAuthOptions.h
new file mode 100644
index 00000000000000..c69847ac704a41
--- /dev/null
+++ b/clang/include/clang/Basic/PointerAuthOptions.h
@@ -0,0 +1,23 @@
+//===--- PointerAuthOptions.h -----------------------------------*- 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 options for configuring pointer-auth technologies
+//  like ARMv8.3.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_BASIC_POINTERAUTHOPTIONS_H
+#define LLVM_CLANG_BASIC_POINTERAUTHOPTIONS_H
+
+namespace clang {
+
+constexpr int PointerAuthKeyNone = -1;
+
+} // end namespace clang
+
+#endif

@kovdan01
Copy link
Contributor Author

A kind reminder regarding the PR - would be glad to see feedback from everyone interested.

@kovdan01
Copy link
Contributor Author

kovdan01 commented Apr 3, 2024

A kind reminder regarding the PR - this is a prerequisite for #84387, so it would be nice if those who are interested leave a feedback and we can merge both this PR and the dependent one.

Copy link
Member

@Michael137 Michael137 left a comment

Choose a reason for hiding this comment

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

Seems reasonable to me but would prefer if someone more familiar with this (e.g., @rjmccall) gave final approval

bool isIsaPointer, bool authenticatesNullValues)
: Data(EnabledMask |
(isAddressDiscriminated
? static_cast<uint32_t>(AddressDiscriminatedMask)
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
? static_cast<uint32_t>(AddressDiscriminatedMask)
? llvm::to_underlying(AddressDiscriminatedMask)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, thanks, see 3517033

? static_cast<uint32_t>(AddressDiscriminatedMask)
: 0) |
(key << KeyShift) |
(unsigned(authenticationMode) << AuthenticationModeShift) |
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
(unsigned(authenticationMode) << AuthenticationModeShift) |
(llvm::to_underlying(authenticationMode) << AuthenticationModeShift) |

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, thanks, see 3517033

PointerAuthQualifier() : Data(0) {}

static PointerAuthQualifier
Create(int key, bool isAddressDiscriminated, unsigned extraDiscriminator,
Copy link
Member

Choose a reason for hiding this comment

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

Why is key an int here but unsigned in the PointerAuthQualifier constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should be one type everywhere, thanks for bringing attention to this. Changed to unsigned to keep things consistent with other parameters.

@Michael137
Copy link
Member

Are there unittests where we could exercise these types?

Qualifiers Qs;
Qs.Mask = opaque;
Qs.Mask = uint32_t(Opaque);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this OK or should we prefer smth like Opaque & 0xFFFFFFFF or even Opaque & std::numeric_limits<uint32_t>::max() (or smth else)?

Copy link
Member

@Michael137 Michael137 Apr 12, 2024

Choose a reason for hiding this comment

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

Either of those look better imo, or could even use maskTrailingOnes from Support/MathExtras.h. Same for the below

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I've used numeric limits stuff since it allows constexpr (while maskTrailingOnes is more flexible for arbitrary bit count but does not have constexpr variant as far as I can see). See df81082

@kovdan01
Copy link
Contributor Author

kovdan01 commented Apr 8, 2024

Are there unittests where we could exercise these types?

@Michael137 Could you clarify a bit, what is the correct place for such a unit test? As for ASTContext methods similar to newly proposed getPointerAuthType (like getQualifiedType and getCVRQualifiedType), I've not found unit tests with them. As for Qualifiers class also touched in this PR, I've not found tests for it at all. Newly proposed PointerAuthQualifier qualifier, obviously, also has no unit tests now, but if we want to implement them, they should be placed somewhere near unit tests for Qualifiers which are absent as well.

@kovdan01
Copy link
Contributor Author

kovdan01 commented Apr 8, 2024

Although functions in this file use different code styles for function arguments (both camelCase and PascalCase), I've changed the code style for function arguments of newly added functions to PascalCase as described in https://llvm.org/docs/CodingStandards.html#name-types-functions-variables-and-enumerators-properly - see 3517033.

I could also submit a subsequent PR fixing the code style of function parameters in clang/include/clang/AST/Type.h and clang/include/clang/AST/ASTContext.h which are not in PascalCase. Please let me know if there are any objections on this.

@kovdan01 kovdan01 requested a review from Michael137 April 8, 2024 16:06
@rjmccall
Copy link
Contributor

rjmccall commented Apr 8, 2024

It'll be tested in a follow-up commit which actually adds parsing and Sema support for these as qualifiers. That should be sufficient rather than adding a bunch of fairly superficial unit tests. Daniil is just trying to avoid landing the entire feature in a single massive commit.

@kovdan01
Copy link
Contributor Author

@AaronBallman Thanks for feedback!

The changes have no testing associated with them -- what's the plan for that?

This PR is a prerequisite for #84387. It uses code from this PR and contains tests.

There was a discussion regarding adding tests to this PR previously (see comments above), but it seems that the consensus was to rely on tests from #84387. I've not found a proper example of tests for similar functionality - see comment #84384 (comment).

I would be glad to see any suggestions on how this could be tested if just relying on a subsequent PR is not enough. If having tests in the subsequent patch is considered to be enough, I'll add a notice for that in the PR description not to confuse anyone more by test absence.

@kovdan01 kovdan01 requested a review from AaronBallman April 16, 2024 17:33
@AaronBallman
Copy link
Collaborator

@AaronBallman Thanks for feedback!

The changes have no testing associated with them -- what's the plan for that?

This PR is a prerequisite for #84387. It uses code from this PR and contains tests.

There was a discussion regarding adding tests to this PR previously (see comments above), but it seems that the consensus was to rely on tests from #84387. I've not found a proper example of tests for similar functionality - see comment #84384 (comment).

I would be glad to see any suggestions on how this could be tested if just relying on a subsequent PR is not enough. If having tests in the subsequent patch is considered to be enough, I'll add a notice for that in the PR description not to confuse anyone more by test absence.

Oh shoot, I missed that this was discussed earlier in the thread. Doing the testing in a follow-up is reasonable.

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.

Barring any surprises regarding compile time overhead, the changes here LGTM. I'll give the final approval once we have confirmation that this isn't a performance hit.

@kovdan01
Copy link
Contributor Author

I've fixed the build failure https://buildkite.com/llvm-project/github-pull-requests/builds/55986 in clang-tidy by only taking fast qualifiers from the uint64_t opaque value and casting the value to unsigned - see e3cb910. It should be OK to strip bits which not present in FastMask since QualType constructor seems to expect fast qualifiers only - see this in QualType definition:

llvm::PointerIntPair<llvm::PointerUnion<const Type *, const ExtQuals *>,
                     Qualifiers::FastWidth> Value;
// ...
QualType(const Type *Ptr, unsigned Quals) : Value(Ptr, Quals) {}
QualType(const ExtQuals *Ptr, unsigned Quals) : Value(Ptr, Quals) {}

@AaronBallman Please let me know if the approach from e3cb910 is correct or if we should fix the issue somehow differently.

@kovdan01
Copy link
Contributor Author

@AaronBallman See test results from compile-time-tracker here: https://llvm-compile-time-tracker.com/compare.php?from=693a458287d019c5c6a66fe3019d099df2978cdb&to=dbb5e29d136a18060ba6759b328ad80fa9cea649.

It looks like that there is a statistically meaningful difference, but it's only about 0.05..0.25% depending on the test. Is it considered OK?

@kovdan01 kovdan01 requested a review from AaronBallman April 17, 2024 20:50
@AaronBallman
Copy link
Collaborator

I've fixed the build failure https://buildkite.com/llvm-project/github-pull-requests/builds/55986 in clang-tidy by only taking fast qualifiers from the uint64_t opaque value and casting the value to unsigned - see e3cb910. It should be OK to strip bits which not present in FastMask since QualType constructor seems to expect fast qualifiers only - see this in QualType definition:

llvm::PointerIntPair<llvm::PointerUnion<const Type *, const ExtQuals *>,
                     Qualifiers::FastWidth> Value;
// ...
QualType(const Type *Ptr, unsigned Quals) : Value(Ptr, Quals) {}
QualType(const ExtQuals *Ptr, unsigned Quals) : Value(Ptr, Quals) {}

@AaronBallman Please let me know if the approach from e3cb910 is correct or if we should fix the issue somehow differently.

This seems like the correct approach to me. Maybe someday we can use a strong typedef for the fast qualifiers so there's less risk of accidents here, but that's by no means something you need to handle.

@AaronBallman
Copy link
Collaborator

@AaronBallman See test results from compile-time-tracker here: https://llvm-compile-time-tracker.com/compare.php?from=693a458287d019c5c6a66fe3019d099df2978cdb&to=dbb5e29d136a18060ba6759b328ad80fa9cea649.

It looks like that there is a statistically meaningful difference, but it's only about 0.05..0.25% depending on the test. Is it considered OK?

Yeah, this seems to have noticeable impact on compile times for every compilation; out of curiosity, have you tried an approach where this information is stored in ExtQuals instead? That's heap allocated, but would mean that the only folks paying the cost are the ones using the functionality.

@kovdan01
Copy link
Contributor Author

have you tried an approach where this information is stored in ExtQuals instead?

Not yet, thanks for suggestion. I'll try and let you know about results

@rjmccall
Copy link
Contributor

@AaronBallman See test results from compile-time-tracker here: https://llvm-compile-time-tracker.com/compare.php?from=693a458287d019c5c6a66fe3019d099df2978cdb&to=dbb5e29d136a18060ba6759b328ad80fa9cea649.
It looks like that there is a statistically meaningful difference, but it's only about 0.05..0.25% depending on the test. Is it considered OK?

Yeah, this seems to have noticeable impact on compile times for every compilation; out of curiosity, have you tried an approach where this information is stored in ExtQuals instead? That's heap allocated, but would mean that the only folks paying the cost are the ones using the functionality.

Qualifiers is an inline value type representing all possible qualifiers, separated from its application to any specific type. ExtQuals represents an application of qualifiers that don't fit into the inline fast-qualifiers bits to a specific type. ExtQuals stores a Qualifiers inline, with a precondition that the fast qualifier bits are clear. Outside of that, we never store Qualifiers long-term AFAIK.

PointerAuthQualifier is 32 bits. Adding it to Qualifiers increases Qualifiers from 32 bits (mostly occupied) to 64 bits. __ptrauth qualifiers are not a fast qualifier, so when applied to a type, they require the use of an ExtQuals node.

Given all that, I'm not sure what you're asking for. Storing uncommon qualifiers out of line is what we already do with QualType and is why ExtQuals exists; doing it again with Qualifiers doesn't seem to serve any purpose. It's certainly not going to make Qualifiers smaller or more efficient to work with, since PointerAuthQualifier is smaller than a pointer. ExtQuals is 128-bit-aligned and starts with two pointers, so there's space for 64 bits of qualifiers on 32-bit hosts and 128 bits of qualifiers on 64-bit hosts before ExtQuals grows.

The overhead is probably from additional checks rather than any cost associated with working with a 64-bit Qualifiers value. We could look into ways to optimize those checks (e.g. qualifier compatibility) in the common cases where there are no extended qualifiers. It's also possible that merging PointerAuthQualifier into Mask inside Qualifiers would make some of the low-level handling more efficient.

@AaronBallman
Copy link
Collaborator

@AaronBallman See test results from compile-time-tracker here: https://llvm-compile-time-tracker.com/compare.php?from=693a458287d019c5c6a66fe3019d099df2978cdb&to=dbb5e29d136a18060ba6759b328ad80fa9cea649.
It looks like that there is a statistically meaningful difference, but it's only about 0.05..0.25% depending on the test. Is it considered OK?

Yeah, this seems to have noticeable impact on compile times for every compilation; out of curiosity, have you tried an approach where this information is stored in ExtQuals instead? That's heap allocated, but would mean that the only folks paying the cost are the ones using the functionality.

Qualifiers is an inline value type representing all possible qualifiers, separated from its application to any specific type. ExtQuals represents an application of qualifiers that don't fit into the inline fast-qualifiers bits to a specific type. ExtQuals stores a Qualifiers inline, with a precondition that the fast qualifier bits are clear. Outside of that, we never store Qualifiers long-term AFAIK.

PointerAuthQualifier is 32 bits. Adding it to Qualifiers increases Qualifiers from 32 bits (mostly occupied) to 64 bits. __ptrauth qualifiers are not a fast qualifier, so when applied to a type, they require the use of an ExtQuals node.

Given all that, I'm not sure what you're asking for. Storing uncommon qualifiers out of line is what we already do with QualType and is why ExtQuals exists; doing it again with Qualifiers doesn't seem to serve any purpose. It's certainly not going to make Qualifiers smaller or more efficient to work with, since PointerAuthQualifier is smaller than a pointer. ExtQuals is 128-bit-aligned and starts with two pointers, so there's space for 64 bits of qualifiers on 32-bit hosts and 128 bits of qualifiers on 64-bit hosts before ExtQuals grows.

Ah, okay, thank you! I had two concerns, but I think neither are valid now that I better understand how ExtQuals works: 1) I thought we were increasing the in-memory size of Qualifiers in a way that matter (like SplitQualType, ExtProtoInfo primarily), 2) I thought we had 32-bit builds of Clang so all the places where we pass/return a Qualifiers would require passing in multiple registers now.

The overhead is probably from additional checks rather than any cost associated with working with a 64-bit Qualifiers value. We could look into ways to optimize those checks (e.g. qualifier compatibility) in the common cases where there are no extended qualifiers. It's also possible that merging PointerAuthQualifier into Mask inside Qualifiers would make some of the low-level handling more efficient.

Do you think the performance concerns are sufficient to warrant doing this optimization work up front? .25% is big enough to warrant concern, but not big enough to need to ask for onerous efforts.

@rjmccall
Copy link
Contributor

Ah, okay, thank you! I had two concerns, but I think neither are valid now that I better understand how ExtQuals works: 1) I thought we were increasing the in-memory size of Qualifiers in a way that matter (like SplitQualType, ExtProtoInfo primarily),

AFAIK, SplitQualType is never stored long-term. I'd forgotten that Qualifiers was used directly in ExtProtoInfo (address-space-qualified methods!), but as it happens, ExtProtoInfo is also not stored long-term: FunctionProtoType reassembles it from its internal storage, and it has the same fast-qualifiers vs. extended-qualifiers optimization as QualType where it uses a trailing Qualifiers as the slow case. So this should only increase memory overhead for C++ methods with address-space qualifiers, and it's not much overhead even there. (IIRC, none of the other extended qualifiers apply to methods — __ptrauth could, actually, but we currently restrict it from appearing in parameter positions.)

  1. I thought we had 32-bit builds of Clang so all the places where we pass/return a Qualifiers would require passing in multiple registers now.

I'm sure we still have 32-bit builds of Clang, and yeah, they're going to be incrementally slower as Qualifiers grows. I think we just have to eat that micro cost, though; most of the operations on Qualifiers are doing set-algebra-ish operations that forcing a heap allocation would really punish. If we're passing and returns Qualifiers by value a lot in tight code, we should consider whether we can avoid that, but the micro cost should vanish in the noise typically.

We're somewhat "saved" here by the fact that many 32-bit ABIs are quite bad. For example, the SysV x86 calling convention always returns structs indirectly, no matter how small they are.

Do you think the performance concerns are sufficient to warrant doing this optimization work up front? .25% is big enough to warrant concern, but not big enough to need to ask for onerous efforts.

I personally don't think so, no. But this patch upstreams an overhead we've been eating at Apple for about 7 years, so one could argue that it's easy for me to say that. It's not unreasonable for you to ask for an investigation to see if we can identify any localized source of the slowdown. If it's across-the-board (which at .25%, I'd say it probably is, but you never know), maybe we just have to accept it.

@kovdan01
Copy link
Contributor Author

It's also possible that merging PointerAuthQualifier into Mask inside Qualifiers would make some of the low-level handling more efficient.

I'm now applying this approach and it looks like that the overhead becomes significantly lower. A new invariant has to be introduced: with PointerAuthenticationMode::None, the whole Data field of PointerAuthQualifier must be 0 (which looks reasonable as for me). I'll publish the changes as soon as I double-check that the observable behavior is not changed and obtain final compiler-time-tracker results - so, stay tuned

@kovdan01
Copy link
Contributor Author

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.

LGTM, thank you for the improvement for performance!

@kovdan01 kovdan01 merged commit f4efa06 into llvm:main Apr 26, 2024
4 checks passed
kovdan01 added a commit to kovdan01/llvm-project that referenced this pull request Apr 27, 2024
After llvm#84384, `Qualifiers::Mask` becomes 64-bit. So, operations like
`Mask &= ~U32` where `U32` is `unsigned` produce undesirable results
since higher 32 bits of `Mask` become zeroed while they should be
preserved. Fix that by explicitly casting `unsigned` values to `uint64_t`
in such operations. Signatures of fixed functions are intentionally left
intact instead of changing the argument itself to `uint64_t` to keep things
consistent with other functions working with the same qualifiers and to
emphasize that 64-bit masks should not be used for these types of
qualifiers.
kovdan01 added a commit that referenced this pull request Apr 29, 2024
…k` (#90329)

After #84384, `Qualifiers::Mask` becomes 64-bit. So, operations like
`Mask &= ~U32` where `U32` is `unsigned` produce undesirable results
since higher 32 bits of `Mask` become zeroed while they should be
preserved. Fix that by explicitly casting `unsigned` values to
`uint64_t` in such operations. Signatures of fixed functions are
intentionally left intact instead of changing the argument itself to
`uint64_t` to keep things consistent with other functions working with
the same qualifiers and to emphasize that 64-bit masks should not be
used for these types of qualifiers.
kovdan01 added a commit that referenced this pull request Apr 30, 2024
…sions (#84387)

Depends on #84384 and #90329

This adds support for `DW_TAG_LLVM_ptrauth_type` entries corresponding
to explicitly signed types (e.g. free function pointers) in lldb user
expressions. Applies PR swiftlang#8239
from Apple's downstream and also adds tests and related code.

---------

Co-authored-by: Jonas Devlieghere <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

5 participants