Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Clang] add user-level sizeless attribute #71894

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

wsmoses
Copy link
Member

@wsmoses wsmoses commented Nov 10, 2023

As discussed in this RFC this PR implements a. new user-level sizeless attribute.

This prevents types or variables marked with this attribute from having sizeof or alignof taken, including of struct or other types which contain sizeless types.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Nov 10, 2023
@llvmbot
Copy link
Collaborator

llvmbot commented Nov 10, 2023

@llvm/pr-subscribers-clang

Author: William Moses (wsmoses)

Changes

As discussed in this RFC this PR implements a. new user-level sizeless attribute.

This prevents types or variables marked with this attribute from having sizeof or alignof taken, including of struct or other types which contain sizeless types.


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

9 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+7)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+31)
  • (modified) clang/include/clang/Sema/Sema.h (+19-1)
  • (modified) clang/lib/AST/Type.cpp (+54-1)
  • (modified) clang/lib/AST/TypePrinter.cpp (+1)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+1-1)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+3-3)
  • (modified) clang/lib/Sema/SemaType.cpp (+26-3)
  • (added) clang/test/Sema/sizeless.c (+22)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 60b549999c155e5..591762c8be5a705 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -4285,3 +4285,10 @@ def PreferredType: InheritableAttr {
   let Args = [TypeArgument<"Type", 1>];
   let Documentation = [PreferredTypeDocumentation];
 }
+
+def SizelessType : DeclOrTypeAttr {
+  let Spellings = [Clang<"sizeless">];
+  let Documentation = [SizelessTypeDocs];
+  let HasCustomParsing = 1;
+}
+
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 05703df2129f612..2675aaad5a96a45 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -7095,6 +7095,37 @@ neither type checking rules, nor runtime semantics. In particular:
   }];
 }
 
+def SizelessTypeDocs : Documentation {
+  let Category = DocCatType;
+  let Heading = "sizeless";
+  let Content = [{
+This attribute is used to on types to forbid the use of the sizeof operation.
+This may be useful for code with scalable vectors, which may forbid the use
+of sizeof on that platform already. This attribute enables more consistent
+behavior by forbidding sizeof on all platforms. 
+
+The attribute takes no arguments.
+
+For example:
+
+.. code-block:: c++
+
+  int* [[clang::sizeless]] f();
+
+The attribute does not have any effect on the semantics of the type system,
+neither type checking rules, nor runtime semantics. In particular:
+
+- ``std::is_same<T, T [[clang::sizeless]]>`` is true for all types
+  ``T``.
+
+- It is not permissible for overloaded functions or template specializations
+  to differ merely by an ``sizeless`` attribute.
+
+- The presence of an ``sizeless`` attribute will not affect name
+  mangling.
+  }];
+}
+
 def WeakDocs : Documentation {
   let Category = DocCatDecl;
   let Content = [{
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index a8c41492b61ac4c..db1e00a2747f875 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2275,9 +2275,13 @@ class Sema final {
     Normal,
 
     /// Relax the normal rules for complete types so that they include
-    /// sizeless built-in types.
+    /// sizeless built-in or sizeless user types.
     AcceptSizeless,
 
+    /// Relax the normal rules for complete types so that they include
+    /// sizeless user types, but not sizeless builtin types.
+    AcceptUserSizeless,
+
     // FIXME: Eventually we should flip the default to Normal and opt in
     // to AcceptSizeless rather than opt out of it.
     Default = AcceptSizeless
@@ -2539,6 +2543,13 @@ class Sema final {
   bool RequireCompleteSizedType(SourceLocation Loc, QualType T, unsigned DiagID,
                                 const Ts &... Args) {
     SizelessTypeDiagnoser<Ts...> Diagnoser(DiagID, Args...);
+    return RequireCompleteType(Loc, T, CompleteTypeKind::AcceptUserSizeless, Diagnoser);
+  }
+
+  template <typename... Ts>
+  bool RequireCompleteSizeofType(SourceLocation Loc, QualType T, unsigned DiagID,
+                                const Ts &... Args) {
+    SizelessTypeDiagnoser<Ts...> Diagnoser(DiagID, Args...);
     return RequireCompleteType(Loc, T, CompleteTypeKind::Normal, Diagnoser);
   }
 
@@ -2567,6 +2578,13 @@ class Sema final {
   bool RequireCompleteSizedExprType(Expr *E, unsigned DiagID,
                                     const Ts &... Args) {
     SizelessTypeDiagnoser<Ts...> Diagnoser(DiagID, Args...);
+    return RequireCompleteExprType(E, CompleteTypeKind::AcceptUserSizeless, Diagnoser);
+  }
+
+  template <typename... Ts>
+  bool RequireCompleteSizeofExprType(Expr *E, unsigned DiagID,
+                                    const Ts &... Args) {
+    SizelessTypeDiagnoser<Ts...> Diagnoser(DiagID, Args...);
     return RequireCompleteExprType(E, CompleteTypeKind::Normal, Diagnoser);
   }
 
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index c8e452e2feab0bf..f25e90555ba608d 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -44,6 +44,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/FoldingSet.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/MathExtras.h"
@@ -2381,6 +2382,7 @@ bool Type::isSizelessBuiltinType() const {
       return false;
     }
   }
+
   return false;
 }
 
@@ -2400,7 +2402,58 @@ bool Type::isWebAssemblyTableType() const {
   return false;
 }
 
-bool Type::isSizelessType() const { return isSizelessBuiltinType(); }
+bool Type::isSizelessType() const {
+  // Check if this type or any of its constituents are sizeless, due to
+  // being a builtin type or individually having the user attribute.
+  // As structs can be recursive, we iterate through without repeats.
+  SmallVector<const Type*, 1> todo = { this };
+  llvm::SmallPtrSet<const Type*, 1> done;
+
+  while (todo.size()) {
+      auto current = todo.pop_back_val();
+      if (done.count(current)) continue;
+      done.insert(current);
+
+  // If either this is a known sizeless type from being a builtin
+  // or as marked by the user, this is a sizeless type.
+  if (current->isSizelessBuiltinType()) return true;
+  if (current->hasAttr(attr::SizelessType)) return true;
+  
+  // Otherwise return true if any inner types are sizeless.
+  switch (current->CanonicalType->getTypeClass()) {
+  default: break;
+  case Record: {
+    // A struct with sizeless types is itself sizeless.
+	RecordDecl *Rec = cast<RecordType>(current->CanonicalType)->getDecl();
+	
+    // skip incomplete structs
+    if (!Rec->isCompleteDefinition()) break;
+
+	// a struct marked sizeless explicitly is sizeless
+	if (Rec->hasAttr<clang::SizelessTypeAttr>()) return true;
+
+	// A struct is sizeless if it contains a sizeless field
+    for (auto field : Rec->fields())
+      todo.push_back(field->getType().getTypePtr());
+	
+    // A class is sizeless if it contains a sizeless base
+	if (auto CXXRec = dyn_cast<CXXRecordDecl>(Rec))
+      for (auto base : CXXRec->bases())
+        todo.push_back(base.getType().getTypePtr());
+    break;
+  }
+  case ConstantArray:
+  case VariableArray:
+    // An array is sizeless if its element type is sizeless
+    todo.push_back(cast<ArrayType>(current->CanonicalType)->getElementType().getTypePtr());
+    break;
+  case IncompleteArray:
+    // skip incomplete arrays
+    break;
+  }
+  }
+  return false;
+}
 
 bool Type::isSizelessVectorType() const {
   return isSVESizelessBuiltinType() || isRVVSizelessBuiltinType();
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index e4f5f40cd625996..9c04e8ee9bfa382 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1861,6 +1861,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::MSABI: OS << "ms_abi"; break;
   case attr::SysVABI: OS << "sysv_abi"; break;
   case attr::RegCall: OS << "regcall"; break;
+  case attr::SizelessType: OS << "sizeless"; break;
   case attr::Pcs: {
     OS << "pcs(";
    QualType t = T->getEquivalentType();
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 7a424eaa5fe7d8e..30a1aaefb4fbfaf 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -8837,7 +8837,7 @@ void Sema::CheckVariableDeclarationType(VarDecl *NewVD) {
     return;
   }
 
-  if (!NewVD->hasLocalStorage() && T->isSizelessType() &&
+  if (!NewVD->hasLocalStorage() && T->isSizelessBuiltinType() &&
       !T.isWebAssemblyReferenceType()) {
     Diag(NewVD->getLocation(), diag::err_sizeless_nonlocal) << T;
     NewVD->setInvalidDecl();
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 432e4285e8a01d6..ade81573dfe4271 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -4491,13 +4491,13 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(Expr *E,
   // be complete (and will attempt to complete it if it's an array of unknown
   // bound).
   if (ExprKind == UETT_AlignOf || ExprKind == UETT_PreferredAlignOf) {
-    if (RequireCompleteSizedType(
+    if (RequireCompleteSizeofType(
             E->getExprLoc(), Context.getBaseElementType(E->getType()),
             diag::err_sizeof_alignof_incomplete_or_sizeless_type,
             getTraitSpelling(ExprKind), E->getSourceRange()))
       return true;
   } else {
-    if (RequireCompleteSizedExprType(
+    if (RequireCompleteSizeofExprType(
             E, diag::err_sizeof_alignof_incomplete_or_sizeless_type,
             getTraitSpelling(ExprKind), E->getSourceRange()))
       return true;
@@ -4772,7 +4772,7 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(QualType ExprType,
                                       ExprKind))
     return false;
 
-  if (RequireCompleteSizedType(
+  if (RequireCompleteSizeofType(
           OpLoc, ExprType, diag::err_sizeof_alignof_incomplete_or_sizeless_type,
           KWName, ExprRange))
     return true;
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 560feafa1857cb3..42f6871caa252e3 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -8651,6 +8651,15 @@ static void HandleAnnotateTypeAttr(TypeProcessingState &State,
   CurType = State.getAttributedType(AnnotateTypeAttr, CurType, CurType);
 }
 
+static void HandleSizelessTypeAttr(TypeProcessingState &State,
+                                   QualType &CurType, const ParsedAttr &PA) {
+  Sema &S = State.getSema();
+
+  auto *SizelessTypeAttr =
+      SizelessTypeAttr::Create(S.Context, PA);
+  CurType = State.getAttributedType(SizelessTypeAttr, CurType, CurType);
+}
+
 static void HandleLifetimeBoundAttr(TypeProcessingState &State,
                                     QualType &CurType,
                                     ParsedAttr &Attr) {
@@ -8947,6 +8956,11 @@ static void processTypeAttrs(TypeProcessingState &state, QualType &type,
       attr.setUsedAsTypeAttr();
       break;
     }
+    case ParsedAttr::AT_SizelessType: {
+      HandleSizelessTypeAttr(state, type, attr);
+      attr.setUsedAsTypeAttr();
+      break;
+    }
     }
 
     // Handle attributes that are defined in a macro. We do not want this to be
@@ -9274,9 +9288,18 @@ bool Sema::RequireCompleteTypeImpl(SourceLocation Loc, QualType T,
   }
 
   NamedDecl *Def = nullptr;
-  bool AcceptSizeless = (Kind == CompleteTypeKind::AcceptSizeless);
-  bool Incomplete = (T->isIncompleteType(&Def) ||
-                     (!AcceptSizeless && T->isSizelessBuiltinType()));
+  bool Incomplete = T->isIncompleteType(&Def);
+  if (!Incomplete)
+    switch (Kind) {
+      case CompleteTypeKind::Normal:
+        Incomplete |= T->isSizelessType();
+        break;
+      case CompleteTypeKind::AcceptUserSizeless:
+        Incomplete |= T->isSizelessBuiltinType();
+        break;
+      case CompleteTypeKind::AcceptSizeless:
+        break;
+    }
 
   // Check that any necessary explicit specializations are visible. For an
   // enum, we just need the declaration, so don't check this.
diff --git a/clang/test/Sema/sizeless.c b/clang/test/Sema/sizeless.c
new file mode 100644
index 000000000000000..ea64c89d269bc78
--- /dev/null
+++ b/clang/test/Sema/sizeless.c
@@ -0,0 +1,22 @@
+// RUN: %clang_cc1 %s -fsyntax-only -verify
+
+struct T {
+	int __attribute__((sizeless)) x;
+	float y;
+};
+
+void f(void) {
+  int size_intty[sizeof(int __attribute__((sizeless))) == 0 ? 1 : -1];        // expected-error {{invalid application of 'sizeof' to sizeless type 'int __attribute__((sizeless))'}}
+  int align_intty[__alignof__(int __attribute__((sizeless))) == 16 ? 1 : -1]; // expected-error {{invalid application of '__alignof' to sizeless type 'int __attribute__((sizeless))'}}
+  
+  int __attribute__((sizeless)) var1;
+  int size_int[sizeof(var1) == 0 ? 1 : -1];        // expected-error {{invalid application of 'sizeof' to sizeless type 'int __attribute__((sizeless))'}}
+  int align_int[__alignof__(var1) == 16 ? 1 : -1]; // expected-error {{invalid application of '__alignof' to sizeless type 'int __attribute__((sizeless))'}}
+
+  int size_struct[sizeof(struct T) == 0 ? 1 : -1];        // expected-error {{invalid application of 'sizeof' to sizeless type 'struct T'}}
+  int align_struct[__alignof__(struct T) == 16 ? 1 : -1]; // expected-error {{invalid application of '__alignof' to sizeless type 'struct T'}}
+  
+  struct T var2;
+  int size_structty[sizeof(var2) == 0 ? 1 : -1];        // expected-error {{invalid application of 'sizeof' to sizeless type 'struct T'}}
+  int align_structty[__alignof__(var2) == 16 ? 1 : -1]; // expected-error {{invalid application of '__alignof' to sizeless type 'struct T'}}
+}

@wsmoses wsmoses force-pushed the sizeless branch 2 times, most recently from 3e23feb to 4de8e23 Compare November 10, 2023 05:20
clang/lib/AST/Type.cpp Outdated Show resolved Hide resolved
clang/lib/AST/Type.cpp Outdated Show resolved Hide resolved
clang/lib/AST/Type.cpp Outdated Show resolved Hide resolved
clang/lib/AST/Type.cpp Outdated Show resolved Hide resolved
@@ -2400,7 +2402,66 @@ bool Type::isWebAssemblyTableType() const {
return false;
}

bool Type::isSizelessType() const { return isSizelessBuiltinType(); }
bool Type::isSizelessType() const {
Copy link
Contributor

Choose a reason for hiding this comment

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

The name of this function sounds like this is a simple getter, but it's quite expensive. Are the other Type::is*Type() functions similarly complex or can we rename this?

Copy link
Member Author

Choose a reason for hiding this comment

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

isIncompleteType has a recursive descent open to suggestions and/or renaming it though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants