Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@

namespace cir {

namespace detail {
struct RecordTypeStorage;
} // namespace detail

bool isAnyFloatingPointType(mlir::Type t);
bool isFPOrFPVectorTy(mlir::Type);

Expand Down
111 changes: 110 additions & 1 deletion clang/include/clang/CIR/Dialect/IR/CIRTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -400,13 +400,122 @@ def VoidPtr : Type<
"cir::VoidType::get($_builder.getContext()))"> {
}

//===----------------------------------------------------------------------===//
// RecordType
//
// The base type for all RecordDecls.
//===----------------------------------------------------------------------===//

def CIR_RecordType : CIR_Type<"Record", "record",
[
DeclareTypeInterfaceMethods<DataLayoutTypeInterface>,
MutableType,
]> {
let summary = "CIR record type";
let description = [{
Each unique clang::RecordDecl is mapped to a `cir.record` and any object in
C/C++ that has a struct or class type will have a `cir.record` in CIR.

There are three possible formats for this type:

- Identified and complete records: unique name and a known body.
- Identified and incomplete records: unique name and unknown body.
- Anonymous records: no name and a known body.

Identified records are uniqued by their name, and anonymous records are
uniqued by their body. This means that two anonymous records with the same
body will be the same type, and two identified records with the same name
will be the same type. Attempting to build a record with an existing name,
but a different body will result in an error.

A few examples:

```mlir
!complete = !cir.record<struct "complete" {!cir.int<u, 8>}>
!incomplete = !cir.record<struct "incomplete" incomplete>
!anonymous = !cir.record<struct {!cir.int<u, 8>}>
```

Incomplete records are mutable, meaning they can be later completed with a
body automatically updating in place every type in the code that uses the
incomplete record. Mutability allows for recursive types to be represented,
meaning the record 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` record has a member that is a pointer to a
`Node` record:

```mlir
!s = !cir.record<struct "Node" {!cir.ptr<!cir.record<struct "Node">>}>
```
}];

let parameters = (ins
OptionalArrayRefParameter<"mlir::Type">:$members,
OptionalParameter<"mlir::StringAttr">:$name,
"bool":$incomplete,
"bool":$packed,
"bool":$padded,
"RecordType::RecordKind":$kind
);

// StorageClass is defined in C++ for mutability.
let storageClass = "RecordTypeStorage";
let genStorageClass = 0;

let skipDefaultBuilders = 1;
let genVerifyDecl = 1;

let builders = [
// Create an identified and incomplete record 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 { Struct, Union };

bool isStruct() const { return getKind() == RecordKind::Struct; };
Copy link
Collaborator

Choose a reason for hiding this comment

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

What value is there in differentiating between 'class' and 'struct' ? From a language perspective, they are effectively synonyms.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure. I want to say that it has to do with understanding the intent of the source code, but as you say there is no reason that the user can't have been using them interchangeably. I just verified that if I create a struct with a virtual method and a base class (declared with class), the incubator still calls it a struct here..

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't mind this btw, more for clarification. The only actual difference between 'class' and 'struct' in C++ is that 'class' defaults to private, so it doesn't seem particularly meaningful in CIR, since you're not modeling the access specifiers.

Copy link
Member

Choose a reason for hiding this comment

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

some extra background: the distinction currently tracks some missing CodeGen support (as done by @sitio-couto a year go), but I'm in favor removing in favor of only one (to distinguish from unions) in the future if it's not used by anything else.

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::Union:
return "union";
case RecordKind::Struct:
return "struct";
}
llvm_unreachable("Invalid value for RecordType::getKind()");
}
std::string getPrefixedName() {
return getKindAsStr() + "." + getName().getValue().str();
}
}];

let hasCustomAssemblyFormat = 1;
}

// Note CIRRecordType is used instead of CIR_RecordType
// because of tablegen conflicts.
def CIRRecordType : Type<
CPred<"::mlir::isa<::cir::RecordType>($_self)">, "CIR record 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_RecordType
]>;

#endif // MLIR_CIR_DIALECT_CIR_TYPES
118 changes: 118 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRTypesDetails.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//===----------------------------------------------------------------------===//
//
// 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 RecordTypeStorage
//===----------------------------------------------------------------------===//

/// Type storage for CIR record types.
struct RecordTypeStorage : public mlir::TypeStorage {
struct KeyTy {
llvm::ArrayRef<mlir::Type> members;
mlir::StringAttr name;
bool incomplete;
bool packed;
bool padded;
RecordType::RecordKind kind;

KeyTy(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
bool incomplete, bool packed, bool padded,
RecordType::RecordKind kind)
: members(members), name(name), incomplete(incomplete), packed(packed),
padded(padded), kind(kind) {}
};

llvm::ArrayRef<mlir::Type> members;
mlir::StringAttr name;
bool incomplete;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I findmyself wishing this was bool isComplete intead during review.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can understand that.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Would still, I think, like hte polarity here reversed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK. I was hesitant to do that because I'm not sure how this is all plumbed together, but I'll give it a shot.

Copy link
Contributor Author

@andykaylor andykaylor Apr 11, 2025

Choose a reason for hiding this comment

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

To make sure we're on the same page, are you asking for this to be changed to complete just here or all the way up to the attribute in the CIR type? That is, the type definition looks like this:

  let parameters = (ins
    OptionalArrayRefParameter<"mlir::Type">:$members,
    OptionalParameter<"mlir::StringAttr">:$name,
    "bool":$incomplete,
    "bool":$packed,
    "bool":$padded,
    "StructType::RecordKind":$kind
  );

Do you want to change $incomplete to $complete there? I think I'd be against that, because it makes more sense to print the type with "incomplete" as this PR does than just omitting "complete" when it's incomplete, and to me that's an argument in favor of leaving it as is here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact, it seems that I can't change it here without changing it all the way up to the type in the .td file. I can have it print "incomplete" only when Scomplete is false, but that seems a bit off. The examples in the CIRTypes.td description say that it will print "complete" and "incomplete" depending on which it is, but that's not true in the incubator implementation at the moment, and that also seems like it would be kind of weird.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would prefer all the way up. Though it is a decently strong preference, I'm willing to let this get delayed until a future patch where we discuss it with @bcardosolopes

Copy link
Member

Choose a reason for hiding this comment

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

I'm more in favor of keeping it "incomplete" because it highlights the corner case/state instead of the most common case, but I agree it might be worth pushing that to another PR (since as Andy mentioned this has a deep side-effect with more changes required).

bool packed;
bool padded;
RecordType::RecordKind kind;

RecordTypeStorage(llvm::ArrayRef<mlir::Type> members, mlir::StringAttr name,
bool incomplete, bool packed, bool padded,
RecordType::RecordKind kind)
: members(members), name(name), incomplete(incomplete), packed(packed),
padded(padded), kind(kind) {
assert(name || !incomplete && "Incomplete records must have a name");
}

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 std::tie(members, name, incomplete, packed, padded, kind) ==
std::tie(key.members, key.name, key.incomplete, key.packed,
key.padded, 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 RecordTypeStorage *construct(mlir::TypeStorageAllocator &allocator,
const KeyTy &key) {
return new (allocator.allocate<RecordTypeStorage>())
RecordTypeStorage(allocator.copyInto(key.members), key.name,
key.incomplete, key.packed, key.padded, key.kind);
}

/// Mutates the members and attributes an identified record.
///
/// Once a record is mutated, it is marked as complete, preventing further
/// mutations. Anonymous records are always complete and cannot be mutated.
/// This method does not fail if a mutation of a complete record does not
/// change the record.
llvm::LogicalResult mutate(mlir::TypeStorageAllocator &allocator,
llvm::ArrayRef<mlir::Type> members, bool packed,
bool padded) {
// Anonymous records cannot mutate.
if (!name)
return llvm::failure();

// Mutation of complete records are allowed if they change nothing.
if (!incomplete)
return mlir::success((this->members == members) &&
(this->packed == packed) &&
(this->padded == padded));

// Mutate incomplete record.
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
3 changes: 3 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ struct MissingFeatures {
static bool mayHaveIntegerOverflow() { return false; }
static bool shouldReverseUnaryCondOnBoolExpr() { return false; }

// RecordType
static bool recordTypeLayoutInfo() { return false; }

// Misc
static bool cxxABI() { return false; }
static bool tryEmitAsConstant() { return false; }
Expand Down
40 changes: 40 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -37,6 +50,33 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
llvm_unreachable("Unsupported format for long double");
}

/// Get a CIR record kind from a AST declaration tag.
cir::RecordType::RecordKind getRecordKind(const clang::TagTypeKind kind) {
switch (kind) {
case clang::TagTypeKind::Class:
case clang::TagTypeKind::Struct:
return cir::RecordType::Struct;
case clang::TagTypeKind::Union:
return cir::RecordType::Union;
case clang::TagTypeKind::Interface:
llvm_unreachable("interface records are NYI");
case clang::TagTypeKind::Enum:
llvm_unreachable("enums are not records");
}
}

/// Get an incomplete CIR struct type. If we have a complete record
/// declaration, we may create an incomplete type and then add the
/// members, so \p rd here may be complete.
cir::RecordType getIncompleteRecordTy(llvm::StringRef name,
const clang::RecordDecl *rd) {
const mlir::StringAttr nameAttr = getStringAttr(name);
cir::RecordType::RecordKind kind = cir::RecordType::RecordKind::Struct;
if (rd)
kind = getRecordKind(rd->getTagKind());
return getType<cir::RecordType>(nameAttr, kind);
}

bool isSized(mlir::Type ty) {
if (mlir::isa<cir::PointerType, cir::ArrayType, cir::BoolType,
cir::IntType>(ty))
Expand Down
7 changes: 6 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ void CIRGenFunction::emitExprAsInit(const Expr *init, const ValueDecl *d,

void CIRGenFunction::emitDecl(const Decl &d) {
switch (d.getKind()) {
case Decl::Record: // struct/union/class X;
assert(!cir::MissingFeatures::generateDebugInfo());
return;
case Decl::Var: {
const VarDecl &vd = cast<VarDecl>(d);
assert(vd.isLocalVarDecl() &&
Expand All @@ -274,7 +277,9 @@ void CIRGenFunction::emitDecl(const Decl &d) {
emitOpenACCRoutine(cast<OpenACCRoutineDecl>(d));
return;
default:
cgm.errorNYI(d.getSourceRange(), "emitDecl: unhandled decl type");
cgm.errorNYI(d.getSourceRange(),
std::string("emitDecl: unhandled decl type: ") +
d.getDeclKindName());
}
}

Expand Down
5 changes: 5 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,11 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) {
case Decl::OpenACCDeclare:
emitGlobalOpenACCDecl(cast<OpenACCDeclareDecl>(decl));
break;

case Decl::Record:
case Decl::CXXRecord:
assert(!cir::MissingFeatures::generateDebugInfo());
break;
}
}

Expand Down
Loading
Loading