[clang] [CIR] Upstream minimal support for structure types (PR #135105)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 9 17:09:09 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Andy Kaylor (andykaylor)
<details>
<summary>Changes</summary>
This change adds minimal support for structure types. To keep the initial change small, only incomplete declarations are being supported in this patch. More complete support will follow.
---
Patch is 25.95 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/135105.diff
11 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIRTypes.h (+4)
- (modified) clang/include/clang/CIR/Dialect/IR/CIRTypes.td (+114-1)
- (added) clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h (+116)
- (modified) clang/include/clang/CIR/MissingFeatures.h (+3)
- (modified) clang/lib/CIR/CodeGen/CIRGenBuilder.h (+39)
- (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (+4)
- (modified) clang/lib/CIR/CodeGen/CIRGenTypes.cpp (+75-2)
- (modified) clang/lib/CIR/CodeGen/CIRGenTypes.h (+8)
- (modified) clang/lib/CIR/Dialect/IR/CIRTypes.cpp (+173-3)
- (added) clang/test/CIR/CodeGen/struct.c (+13)
- (added) clang/test/CIR/IR/struct.cir (+9)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
index 7b0fcbc7cc98f..d2c407b2fd686 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h
@@ -20,6 +20,10 @@
namespace cir {
+namespace detail {
+struct StructTypeStorage;
+} // namespace detail
+
bool isAnyFloatingPointType(mlir::Type t);
bool isFPOrFPVectorTy(mlir::Type);
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index e285c0f28f113..bcdaf54bbd84f 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -400,13 +400,126 @@ def VoidPtr : Type<
"cir::VoidType::get($_builder.getContext()))"> {
}
+//===----------------------------------------------------------------------===//
+// StructType
+//
+// The base type for all RecordDecls.
+//===----------------------------------------------------------------------===//
+
+def CIR_StructType : CIR_Type<"Struct", "struct",
+ [
+ DeclareTypeInterfaceMethods<DataLayoutTypeInterface>,
+ MutableType,
+ ]> {
+ let summary = "CIR struct type";
+ let description = [{
+ Each unique clang::RecordDecl is mapped to a `cir.struct` and any object in
+ C/C++ that has a struct type will have a `cir.struct` in CIR.
+
+ There are three possible formats for this type:
+
+ - Identified and complete structs: unique name and a known body.
+ - Identified and incomplete structs: unique name and unknown body.
+ - Anonymous structs: no name and a known body.
+
+ Identified structs are uniqued by their name, and anonymous structs are
+ uniqued by their body. This means that two anonymous structs with the same
+ body will be the same type, and two identified structs with the same name
+ will be the same type. Attempting to build a struct with an existing name,
+ but a different body will result in an error.
+
+ A few examples:
+
+ ```mlir
+ !complete = !cir.struct<struct "complete" {!cir.int<u, 8>}>
+ !incomplete = !cir.struct<struct "incomplete" incomplete>
+ !anonymous = !cir.struct<struct {!cir.int<u, 8>}>
+ ```
+
+ Incomplete structs are mutable, meaning they can be later completed with a
+ body automatically updating in place every type in the code that uses the
+ incomplete struct. Mutability allows for recursive types to be represented,
+ meaning the struct can have members that refer to itself. This is useful for
+ representing recursive records and is implemented through a special syntax.
+ In the example below, the `Node` struct has a member that is a pointer to a
+ `Node` struct:
+
+ ```mlir
+ !struct = !cir.struct<struct "Node" {!cir.ptr<!cir.struct<struct
+ "Node">>}>
+ ```
+ }];
+
+ let parameters = (ins
+ OptionalArrayRefParameter<"mlir::Type">:$members,
+ OptionalParameter<"mlir::StringAttr">:$name,
+ "bool":$incomplete,
+ "bool":$packed,
+ "bool":$padded,
+ "StructType::RecordKind":$kind
+ );
+
+ // StorageClass is defined in C++ for mutability.
+ let storageClass = "StructTypeStorage";
+ let genStorageClass = 0;
+
+ let skipDefaultBuilders = 1;
+ let genVerifyDecl = 1;
+
+ let builders = [
+ // Create an identified and incomplete struct type.
+ TypeBuilder<(ins
+ "mlir::StringAttr":$name,
+ "RecordKind":$kind
+ ), [{
+ return $_get($_ctxt, /*members=*/llvm::ArrayRef<Type>{}, name,
+ /*incomplete=*/true, /*packed=*/false,
+ /*padded=*/false, kind);
+ }]>];
+
+ let extraClassDeclaration = [{
+ using Base::verifyInvariants;
+
+ enum RecordKind : uint32_t { Class, Union, Struct };
+
+ bool isClass() const { return getKind() == RecordKind::Class; };
+ bool isStruct() const { return getKind() == RecordKind::Struct; };
+ bool isUnion() const { return getKind() == RecordKind::Union; };
+ bool isComplete() const { return !isIncomplete(); };
+ bool isIncomplete() const;
+
+ size_t getNumElements() const { return getMembers().size(); };
+ std::string getKindAsStr() {
+ switch (getKind()) {
+ case RecordKind::Class:
+ return "class";
+ case RecordKind::Union:
+ return "union";
+ case RecordKind::Struct:
+ return "struct";
+ }
+ llvm_unreachable("Invalid value for StructType::getKind()");
+ }
+ std::string getPrefixedName() {
+ return getKindAsStr() + "." + getName().getValue().str();
+ }
+ }];
+
+ let hasCustomAssemblyFormat = 1;
+}
+
+// Note CIRStructType is used instead of CIR_StructType
+// because of tablegen conflicts.
+def CIRStructType : Type<
+ CPred<"::mlir::isa<::cir::StructType>($_self)">, "CIR struct type">;
+
//===----------------------------------------------------------------------===//
// Global type constraints
//===----------------------------------------------------------------------===//
def CIR_AnyType : AnyTypeOf<[
CIR_VoidType, CIR_BoolType, CIR_ArrayType, CIR_IntType, CIR_AnyFloat,
- CIR_PointerType, CIR_FuncType
+ CIR_PointerType, CIR_FuncType, CIR_StructType
]>;
#endif // MLIR_CIR_DIALECT_CIR_TYPES
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
new file mode 100644
index 0000000000000..91bfb814a609c
--- /dev/null
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
@@ -0,0 +1,116 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 contains implementation details, such as storage structures, of
+// CIR dialect types.
+//
+//===----------------------------------------------------------------------===//
+#ifndef CIR_DIALECT_IR_CIRTYPESDETAILS_H
+#define CIR_DIALECT_IR_CIRTYPESDETAILS_H
+
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/Support/LogicalResult.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "llvm/ADT/Hashing.h"
+
+namespace cir {
+namespace detail {
+
+//===----------------------------------------------------------------------===//
+// CIR StructTypeStorage
+//===----------------------------------------------------------------------===//
+
+/// Type storage for CIR record types.
+struct StructTypeStorage : public mlir::TypeStorage {
+ struct KeyTy {
+ llvm::ArrayRef<mlir::Type> members;
+ mlir::StringAttr name;
+ bool incomplete;
+ bool packed;
+ bool padded;
+ StructType::RecordKind kind;
+
+ KeyTy(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
+ bool incomplete, bool packed, bool padded,
+ StructType::RecordKind kind)
+ : members(members), name(name), incomplete(incomplete), packed(packed),
+ padded(padded), kind(kind) {}
+ };
+
+ llvm::ArrayRef<mlir::Type> members;
+ mlir::StringAttr name;
+ bool incomplete;
+ bool packed;
+ bool padded;
+ StructType::RecordKind kind;
+
+ StructTypeStorage(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
+ bool incomplete, bool packed, bool padded,
+ StructType::RecordKind kind)
+ : members(members), name(name), incomplete(incomplete), packed(packed),
+ padded(padded), kind(kind) {}
+
+ KeyTy getAsKey() const {
+ return KeyTy(members, name, incomplete, packed, padded, kind);
+ }
+
+ bool operator==(const KeyTy &key) const {
+ if (name)
+ return (name == key.name) && (kind == key.kind);
+ return (members == key.members) && (name == key.name) &&
+ (incomplete == key.incomplete) && (packed == key.packed) &&
+ (padded == key.padded) && (kind == key.kind);
+ }
+
+ static llvm::hash_code hashKey(const KeyTy &key) {
+ if (key.name)
+ return llvm::hash_combine(key.name, key.kind);
+ return llvm::hash_combine(key.members, key.incomplete, key.packed,
+ key.padded, key.kind);
+ }
+
+ static StructTypeStorage *construct(mlir::TypeStorageAllocator &allocator,
+ const KeyTy &key) {
+ return new (allocator.allocate<StructTypeStorage>())
+ StructTypeStorage(allocator.copyInto(key.members), key.name,
+ key.incomplete, key.packed, key.padded, key.kind);
+ }
+
+ /// Mutates the members and attributes an identified struct.
+ ///
+ /// Once a record is mutated, it is marked as complete, preventing further
+ /// mutations. Anonymous structs are always complete and cannot be mutated.
+ /// This method does not fail if a mutation of a complete struct does not
+ /// change the struct.
+ llvm::LogicalResult mutate(mlir::TypeStorageAllocator &allocator,
+ llvm::ArrayRef<mlir::Type> members, bool packed,
+ bool padded) {
+ // Anonymous structs cannot mutate.
+ if (!name)
+ return llvm::failure();
+
+ // Mutation of complete structs are allowed if they change nothing.
+ if (!incomplete)
+ return mlir::success((this->members == members) &&
+ (this->packed == packed) &&
+ (this->padded == padded));
+
+ // Mutate incomplete struct.
+ this->members = allocator.copyInto(members);
+ this->packed = packed;
+ this->padded = padded;
+
+ incomplete = false;
+ return llvm::success();
+ }
+};
+
+} // namespace detail
+} // namespace cir
+
+#endif // CIR_DIALECT_IR_CIRTYPESDETAILS_H
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 3188429ea3b1b..bc52988dfc750 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -82,6 +82,9 @@ struct MissingFeatures {
static bool mayHaveIntegerOverflow() { return false; }
static bool shouldReverseUnaryCondOnBoolExpr() { return false; }
+ // StructType
+ static bool structTypeLayoutInfo() { return false; }
+
// Misc
static bool cxxABI() { return false; }
static bool tryEmitAsConstant() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index 61a747254b3d0..0a9ef5fcd5471 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -20,11 +20,24 @@ namespace clang::CIRGen {
class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
const CIRGenTypeCache &typeCache;
+ llvm::StringMap<unsigned> recordNames;
public:
CIRGenBuilderTy(mlir::MLIRContext &mlirContext, const CIRGenTypeCache &tc)
: CIRBaseBuilderTy(mlirContext), typeCache(tc) {}
+ std::string getUniqueAnonRecordName() { return getUniqueRecordName("anon"); }
+
+ std::string getUniqueRecordName(const std::string &baseName) {
+ auto it = recordNames.find(baseName);
+ if (it == recordNames.end()) {
+ recordNames[baseName] = 0;
+ return baseName;
+ }
+
+ return baseName + "." + std::to_string(recordNames[baseName]++);
+ }
+
cir::LongDoubleType getLongDoubleTy(const llvm::fltSemantics &format) const {
if (&format == &llvm::APFloat::IEEEdouble())
return cir::LongDoubleType::get(getContext(), typeCache.DoubleTy);
@@ -37,6 +50,32 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
llvm_unreachable("Unsupported format for long double");
}
+ /// Get a CIR record kind from a AST declaration tag.
+ cir::StructType::RecordKind getRecordKind(const clang::TagTypeKind kind) {
+ switch (kind) {
+ case clang::TagTypeKind::Struct:
+ return cir::StructType::Struct;
+ case clang::TagTypeKind::Union:
+ return cir::StructType::Union;
+ case clang::TagTypeKind::Class:
+ return cir::StructType::Class;
+ case clang::TagTypeKind::Interface:
+ llvm_unreachable("interface records are NYI");
+ case clang::TagTypeKind::Enum:
+ llvm_unreachable("enum records are NYI");
+ }
+ }
+
+ /// Get an incomplete CIR struct type.
+ cir::StructType getIncompleteStructTy(llvm::StringRef name,
+ const clang::RecordDecl *rd) {
+ const mlir::StringAttr nameAttr = getStringAttr(name);
+ cir::StructType::RecordKind kind = cir::StructType::RecordKind::Struct;
+ if (rd)
+ kind = getRecordKind(rd->getTagKind());
+ return getType<cir::StructType>(nameAttr, kind);
+ }
+
bool isSized(mlir::Type ty) {
if (mlir::isa<cir::PointerType, cir::ArrayType, cir::BoolType,
cir::IntType>(ty))
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index d2259a9c41d22..4bd186f68f8c9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -490,6 +490,10 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) {
case Decl::OpenACCDeclare:
emitGlobalOpenACCDecl(cast<OpenACCDeclareDecl>(decl));
break;
+
+ case Decl::Record:
+ assert(!cir::MissingFeatures::generateDebugInfo());
+ break;
}
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
index 68aee63c2b22d..8644b9a62320a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
@@ -86,10 +86,80 @@ mlir::Type CIRGenTypes::convertFunctionTypeInternal(QualType qft) {
return cir::FuncType::get(SmallVector<mlir::Type, 1>{}, cgm.VoidTy);
}
+// This is CIR's version of CodeGenTypes::addRecordTypeName. It isn't shareable
+// because CIR has different uniquing requirements.
+std::string CIRGenTypes::getRecordTypeName(const clang::RecordDecl *recordDecl,
+ StringRef suffix) {
+ llvm::SmallString<256> typeName;
+ llvm::raw_svector_ostream outStream(typeName);
+
+ PrintingPolicy policy = recordDecl->getASTContext().getPrintingPolicy();
+ policy.SuppressInlineNamespace = false;
+
+ if (recordDecl->getIdentifier()) {
+ if (recordDecl->getDeclContext())
+ recordDecl->printQualifiedName(outStream, policy);
+ else
+ recordDecl->printName(outStream, policy);
+
+ // Ensure each template specialization has a unique name.
+ if (auto *templateSpecialization =
+ llvm::dyn_cast<ClassTemplateSpecializationDecl>(recordDecl)) {
+ outStream << '<';
+ const ArrayRef<TemplateArgument> args =
+ templateSpecialization->getTemplateArgs().asArray();
+ const auto printer = [&policy, &outStream](const TemplateArgument &arg) {
+ /// Print this template argument to the given output stream.
+ arg.print(policy, outStream, /*IncludeType=*/true);
+ };
+ llvm::interleaveComma(args, outStream, printer);
+ outStream << '>';
+ }
+ } else if (auto *typedefNameDecl = recordDecl->getTypedefNameForAnonDecl()) {
+ if (typedefNameDecl->getDeclContext())
+ typedefNameDecl->printQualifiedName(outStream, policy);
+ else
+ typedefNameDecl->printName(outStream);
+ } else {
+ outStream << builder.getUniqueAnonRecordName();
+ }
+
+ if (!suffix.empty())
+ outStream << suffix;
+
+ return builder.getUniqueRecordName(std::string(typeName));
+}
+
+/// Lay out a tagged decl type like struct or union.
+mlir::Type CIRGenTypes::convertRecordDeclType(const clang::RecordDecl *rd) {
+ // TagDecl's are not necessarily unique, instead use the (clang) type
+ // connected to the decl.
+ const Type *key = astContext.getTagDeclType(rd).getTypePtr();
+ cir::StructType entry = recordDeclTypes[key];
+
+ // Handle forward decl / incomplete types.
+ if (!entry) {
+ auto name = getRecordTypeName(rd, "");
+ entry = builder.getIncompleteStructTy(name, rd);
+ recordDeclTypes[key] = entry;
+ }
+
+ rd = rd->getDefinition();
+ if (!rd || !rd->isCompleteDefinition() || entry.isComplete())
+ return entry;
+
+ cgm.errorNYI(rd->getSourceRange(), "Complete record type");
+ return entry;
+}
+
mlir::Type CIRGenTypes::convertType(QualType type) {
type = astContext.getCanonicalType(type);
const Type *ty = type.getTypePtr();
+ // Process record types before the type cache lookup.
+ if (const auto *recordType = dyn_cast<RecordType>(type))
+ return convertRecordDeclType(recordType->getDecl());
+
// Has the type already been processed?
TypeCacheTy::iterator tci = typeCache.find(ty);
if (tci != typeCache.end())
@@ -100,9 +170,11 @@ mlir::Type CIRGenTypes::convertType(QualType type) {
mlir::Type resultType = nullptr;
switch (ty->getTypeClass()) {
+ case Type::Record:
+ llvm_unreachable("Should have been handled above");
+
case Type::Builtin: {
switch (cast<BuiltinType>(ty)->getKind()) {
-
// void
case BuiltinType::Void:
resultType = cgm.VoidTy;
@@ -236,7 +308,8 @@ mlir::Type CIRGenTypes::convertType(QualType type) {
}
default:
- cgm.errorNYI(SourceLocation(), "processing of type", type);
+ cgm.errorNYI(SourceLocation(), "processing of type",
+ type->getTypeClassName());
resultType = cgm.SInt32Ty;
break;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h
index 4021206e979e1..a3b7eae6d0767 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.h
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h
@@ -43,6 +43,9 @@ class CIRGenTypes {
clang::ASTContext &astContext;
CIRGenBuilderTy &builder;
+ /// Contains the CIR type for any converted RecordDecl
+ llvm::DenseMap<const clang::Type *, cir::StructType> recordDeclTypes;
+
/// Heper for convertType.
mlir::Type convertFunctionTypeInternal(clang::QualType ft);
@@ -65,6 +68,11 @@ class CIRGenTypes {
/// Convert a Clang type into a mlir::Type.
mlir::Type convertType(clang::QualType type);
+ mlir::Type convertRecordDeclType(const clang::RecordDecl *recordDecl);
+
+ std::string getRecordTypeName(const clang::RecordDecl *,
+ llvm::StringRef suffix);
+
/// Convert type T into an mlir::Type. This differs from convertType in that
/// it is used to convert to the memory representation for a type. For
/// example, the scalar representation for bool is i1, but the memory
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 356f7f6244db8..4ff7e1fdbb7c8 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -14,6 +14,7 @@
#include "mlir/IR/DialectImplementation.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/Dialect/IR/CIRTypesDetails.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/ADT/TypeSwitch.h"
@@ -53,9 +54,13 @@ Type CIRDialect::parseType(DialectAsmParser &parser) const {
if (parseResult.has_value())
return genType;
- // TODO(CIR) Attempt to parse as a raw C++ type.
- parser.emitError(typeLoc) << "unknown CIR type: " << mnemonic;
- return Type();
+ // Type is not tablegen'd: try to parse as a raw C++ type.
+ return StringSwitch<function_ref<Type()>>(mnemonic)
+ .Case("struct", [&] { return StructType::parse(parser); })
+ .Default([&] {
+ parser.emitError(typeLoc) << "unknown CIR type: " << mnemonic;
+ return Type();
+ })();
}
void CIRDialect::printType(Type type, DialectAsmPrinter &os) const {
@@ -67,6 +72,171 @@ void CIRDialect::printType(Type type, DialectAsmPrinter &os) const {
llvm::report_fatal_error("printer is missing a handler for this type");
}
+//===----------------------------------------------------------------------===//
+// StructType Definitions
+//===----------------------------------------------------------------------===//
+
+Type StructType::parse(mlir::AsmParser &parser) {
+ FailureOr<AsmParser::CyclicParseReset> cyclicParseGuard;
+ const auto loc = parser.getCurrentLocation();
+ const auto eLoc = parser.getEncodedSourceLoc(loc);
+ RecordKind kind;
+ auto *context = parser.getContext();
+
+ if (parser.parseLess())
+ return {};
+
+ // TODO(cir): in the future we should probably separate types for different
+ // source language declarations such as cir.class, cir.union, and cir.struct
+ if (parser.parseOptionalKeyword("struct").succeeded())
+ kind...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/135105
More information about the cfe-commits
mailing list