Skip to content

[CIR] Upstream initial support for complete record types #135844

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 4 commits into from
Apr 16, 2025

Conversation

andykaylor
Copy link
Contributor

This adds basic support for populating record types. In order to keep the change small, everything non-essential was deferred to a later change set. Only non-recursive structures are handled. Structures padding is not yet implemented. Bitfields are not supported. No attempt is made to handle ABI requirements for passing structure arguments.

This adds basic support for populating record types. In order to keep the
change small, everything non-essential was deferred to a later change set.
Only non-recursive structures are handled. Structures padding is not yet
implemented. Bitfields are not supported. No attempt is made to handle ABI
requirements for passing structure arguments.
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Apr 15, 2025
@llvmbot
Copy link
Member

llvmbot commented Apr 15, 2025

@llvm/pr-subscribers-clangir

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

This adds basic support for populating record types. In order to keep the change small, everything non-essential was deferred to a later change set. Only non-recursive structures are handled. Structures padding is not yet implemented. Bitfields are not supported. No attempt is made to handle ABI requirements for passing structure arguments.


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

12 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h (+2)
  • (modified) clang/include/clang/CIR/Dialect/IR/CIRTypes.td (+3)
  • (modified) clang/include/clang/CIR/MissingFeatures.h (+7)
  • (added) clang/lib/CIR/CodeGen/CIRGenRecordLayout.h (+54)
  • (added) clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp (+212)
  • (modified) clang/lib/CIR/CodeGen/CIRGenTypes.cpp (+46-1)
  • (modified) clang/lib/CIR/CodeGen/CIRGenTypes.h (+21-1)
  • (modified) clang/lib/CIR/CodeGen/CMakeLists.txt (+1)
  • (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+1-1)
  • (modified) clang/lib/CIR/Dialect/IR/CIRTypes.cpp (+6)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+29)
  • (modified) clang/test/CIR/CodeGen/struct.c (+27)
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index 68a4505ca7a5a..6b2c938b8275d 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -95,6 +95,8 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
       return getZeroAttr(arrTy);
     if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(ty))
       return getConstNullPtrAttr(ptrTy);
+    if (auto RecordTy = mlir::dyn_cast<cir::RecordType>(ty))
+      return getZeroAttr(RecordTy);
     if (mlir::isa<cir::BoolType>(ty)) {
       return getCIRBoolAttr(false);
     }
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
index c60af47f09def..23e20755dca95 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td
@@ -499,6 +499,9 @@ def CIR_RecordType : CIR_Type<"Record", "record",
     std::string getPrefixedName() {
       return getKindAsStr() + "." + getName().getValue().str();
     }
+
+    void complete(llvm::ArrayRef<mlir::Type> members, bool packed,
+                  bool isPadded);
   }];
 
   let hasCustomAssemblyFormat = 1;
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 6f2fd2cb2b3ad..cdc56edd5e409 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -103,6 +103,13 @@ struct MissingFeatures {
 
   // RecordType
   static bool recordTypeLayoutInfo() { return false; }
+  static bool recursiveRecordLayout() { return false; }
+  static bool skippedLayout() { return false; }
+  static bool astRecordDeclAttr() { return false; }
+  static bool cxxSupport() { return false; }
+  static bool packedRecords() { return false; }
+  static bool recordPadding() { return false; }
+  static bool recordZeroInit() { return false; }
 
   // Misc
   static bool cxxABI() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
new file mode 100644
index 0000000000000..a51e0460d1074
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_CIR_CIRGENRECORDLAYOUT_H
+#define LLVM_CLANG_LIB_CIR_CIRGENRECORDLAYOUT_H
+
+#include "clang/AST/Decl.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+
+namespace clang::CIRGen {
+
+/// This class handles record and union layout info while lowering AST types
+/// to CIR types.
+///
+/// These layout objects are only created on demand as CIR generation requires.
+class CIRGenRecordLayout {
+  friend class CIRGenTypes;
+
+  CIRGenRecordLayout(const CIRGenRecordLayout &) = delete;
+  void operator=(const CIRGenRecordLayout &) = delete;
+
+private:
+  /// The CIR type corresponding to this record layout; used when laying it out
+  /// as a complete object.
+  cir::RecordType completeObjectType;
+
+  /// Map from (non-bit-field) record field to the corresponding cir record type
+  /// field no. This info is populated by the record builder.
+  llvm::DenseMap<const clang::FieldDecl *, unsigned> fieldInfo;
+
+public:
+  CIRGenRecordLayout(cir::RecordType completeObjectType)
+      : completeObjectType(completeObjectType) {}
+
+  /// Return the "complete object" LLVM type associated with
+  /// this record.
+  cir::RecordType getCIRType() const { return completeObjectType; }
+
+  /// Return cir::RecordType element number that corresponds to the field FD.
+  unsigned getCIRFieldNo(const clang::FieldDecl *FD) const {
+    FD = FD->getCanonicalDecl();
+    assert(fieldInfo.count(FD) && "Invalid field for record!");
+    return fieldInfo.lookup(FD);
+  }
+};
+
+} // namespace clang::CIRGen
+
+#endif
diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
new file mode 100644
index 0000000000000..d14eb2f3e2f51
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayoutBuilder.cpp
@@ -0,0 +1,212 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 contains code to compute the layout of a record.
+//
+//===----------------------------------------------------------------------===//
+
+#include "CIRGenBuilder.h"
+#include "CIRGenModule.h"
+#include "CIRGenTypes.h"
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/RecordLayout.h"
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
+#include "llvm/Support/Casting.h"
+
+#include <memory>
+
+using namespace llvm;
+using namespace clang;
+using namespace clang::CIRGen;
+
+namespace {
+/// The CIRRecordLowering is responsible for lowering an ASTRecordLayout to an
+/// mlir::Type. Some of the lowering is straightforward, some is not.
+// TODO: Detail some of the complexities and weirdnesses?
+// (See CGRecordLayoutBuilder.cpp)
+struct CIRRecordLowering final {
+
+  // MemberInfo is a helper structure that contains information about a record
+  // member. In addition to the standard member types, there exists a sentinel
+  // member type that ensures correct rounding.
+  struct MemberInfo final {
+    CharUnits offset;
+    enum class InfoKind { Field } kind;
+    mlir::Type data;
+    union {
+      const FieldDecl *fieldDecl;
+      // CXXRecordDecl will be used here when supported.
+    };
+    MemberInfo(CharUnits offset, InfoKind kind, mlir::Type data,
+               const FieldDecl *fieldDecl = nullptr)
+        : offset(offset), kind(kind), data(data), fieldDecl(fieldDecl) {};
+    // MemberInfos are sorted so we define a < operator.
+    bool operator<(const MemberInfo &other) const {
+      return offset < other.offset;
+    }
+  };
+  // The constructor.
+  CIRRecordLowering(CIRGenTypes &cirGenTypes, const RecordDecl *recordDecl,
+                    bool isPacked);
+
+  void lower();
+
+  void accumulateFields();
+
+  CharUnits bitsToCharUnits(uint64_t bitOffset) {
+    return astContext.toCharUnitsFromBits(bitOffset);
+  }
+
+  CharUnits getSize(mlir::Type Ty) {
+    assert(!cir::MissingFeatures::recordTypeLayoutInfo());
+    return CharUnits::One();
+  }
+  CharUnits getAlignment(mlir::Type Ty) {
+    assert(!cir::MissingFeatures::recordTypeLayoutInfo());
+    return CharUnits::One();
+  }
+
+  mlir::Type getStorageType(const FieldDecl *fieldDecl) {
+    mlir::Type type = cirGenTypes.convertTypeForMem(fieldDecl->getType());
+    if (fieldDecl->isBitField()) {
+      cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
+                                         "getStorageType for bitfields");
+    }
+    return type;
+  }
+
+  uint64_t getFieldBitOffset(const FieldDecl *fieldDecl) {
+    return astRecordLayout.getFieldOffset(fieldDecl->getFieldIndex());
+  }
+
+  /// Fills out the structures that are ultimately consumed.
+  void fillOutputFields();
+
+  CIRGenTypes &cirGenTypes;
+  CIRGenBuilderTy &builder;
+  const ASTContext &astContext;
+  const RecordDecl *recordDecl;
+  const ASTRecordLayout &astRecordLayout;
+  // Helpful intermediate data-structures
+  std::vector<MemberInfo> members;
+  // Output fields, consumed by CIRGenTypes::computeRecordLayout
+  llvm::SmallVector<mlir::Type, 16> fieldTypes;
+  llvm::DenseMap<const FieldDecl *, unsigned> fields;
+  bool zeroInitializable : 1;
+  bool packed : 1;
+  bool padded : 1;
+
+private:
+  CIRRecordLowering(const CIRRecordLowering &) = delete;
+  void operator=(const CIRRecordLowering &) = delete;
+}; // CIRRecordLowering
+} // namespace
+
+CIRRecordLowering::CIRRecordLowering(CIRGenTypes &cirGenTypes,
+                                     const RecordDecl *recordDecl,
+                                     bool isPacked)
+    : cirGenTypes(cirGenTypes), builder(cirGenTypes.getBuilder()),
+      astContext(cirGenTypes.getASTContext()), recordDecl(recordDecl),
+      astRecordLayout(
+          cirGenTypes.getASTContext().getASTRecordLayout(recordDecl)),
+      zeroInitializable(true), packed(isPacked), padded(false) {}
+
+void CIRRecordLowering::lower() {
+  if (recordDecl->isUnion()) {
+    cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
+                                       "lower: union");
+    return;
+  }
+
+  assert(!cir::MissingFeatures::cxxSupport());
+
+  accumulateFields();
+
+  llvm::stable_sort(members);
+  // TODO: implement clipTailPadding once bitfields are implemented
+  assert(!cir::MissingFeatures::bitfields());
+  // TODO: implemented packed records
+  assert(!cir::MissingFeatures::packedRecords());
+  // TODO: implement padding
+  assert(!cir::MissingFeatures::recordPadding());
+  // TODO: support zeroInit
+  assert(!cir::MissingFeatures::recordZeroInit());
+
+  fillOutputFields();
+}
+
+void CIRRecordLowering::fillOutputFields() {
+  for (const MemberInfo &member : members) {
+    if (member.data)
+      fieldTypes.push_back(member.data);
+    if (member.kind == MemberInfo::InfoKind::Field) {
+      if (member.fieldDecl)
+        fields[member.fieldDecl->getCanonicalDecl()] = fieldTypes.size() - 1;
+      // A field without storage must be a bitfield.
+      assert(!cir::MissingFeatures::bitfields());
+    }
+    assert(!cir::MissingFeatures::cxxSupport());
+  }
+}
+
+void CIRRecordLowering::accumulateFields() {
+  for (const FieldDecl *field : recordDecl->fields()) {
+    if (field->isBitField()) {
+      cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
+                                         "accumulate bitfields");
+      ++field;
+    } else if (!field->isZeroSize(astContext)) {
+      members.push_back(MemberInfo(bitsToCharUnits(getFieldBitOffset(field)),
+                                   MemberInfo::InfoKind::Field,
+                                   getStorageType(field), field));
+      ++field;
+    } else {
+      // TODO(cir): do we want to do anything special about zero size members?
+      ++field;
+    }
+  }
+}
+
+std::unique_ptr<CIRGenRecordLayout>
+CIRGenTypes::computeRecordLayout(const RecordDecl *rd, cir::RecordType *ty) {
+  CIRRecordLowering lowering(*this, rd, /*packed=*/false);
+  assert(ty->isIncomplete() && "recomputing record layout?");
+  lowering.lower();
+
+  // If we're in C++, compute the base subobject type.
+  if (llvm::isa<CXXRecordDecl>(rd) && !rd->isUnion() &&
+      !rd->hasAttr<FinalAttr>()) {
+    cgm.errorNYI(rd->getSourceRange(), "computeRecordLayout: CXXRecordDecl");
+  }
+
+  // Fill in the record *after* computing the base type.  Filling in the body
+  // signifies that the type is no longer opaque and record layout is complete,
+  // but we may need to recursively layout rd while laying D out as a base type.
+  assert(!cir::MissingFeatures::astRecordDeclAttr());
+  ty->complete(lowering.fieldTypes, lowering.packed, lowering.padded);
+
+  auto rl = std::make_unique<CIRGenRecordLayout>(ty ? *ty : cir::RecordType());
+
+  assert(!cir::MissingFeatures::recordZeroInit());
+  assert(!cir::MissingFeatures::cxxSupport());
+  assert(!cir::MissingFeatures::bitfields());
+
+  // Add all the field numbers.
+  rl->fieldInfo.swap(lowering.fields);
+
+  // Dump the layout, if requested.
+  if (getASTContext().getLangOpts().DumpRecordLayouts) {
+    cgm.errorNYI(rd->getSourceRange(), "computeRecordLayout: dump layout");
+  }
+
+  // TODO: implement verification
+  return rl;
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
index f625f83257859..ec77c4428d43b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp
@@ -112,6 +112,18 @@ std::string CIRGenTypes::getRecordTypeName(const clang::RecordDecl *recordDecl,
   return builder.getUniqueRecordName(std::string(typeName));
 }
 
+// Return true if it is safe to convert the specified record decl to CIR and lay
+// it out, false if doing so would cause us to get into a recursive compilation
+// mess.
+static bool isSafeToConvert(const RecordDecl *RD, CIRGenTypes &CGT) {
+  // If no records are being laid out, we can certainly do this one.
+  if (CGT.noRecordsBeingLaidOut())
+    return true;
+
+  assert(!cir::MissingFeatures::recursiveRecordLayout());
+  return false;
+}
+
 /// 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
@@ -132,7 +144,40 @@ mlir::Type CIRGenTypes::convertRecordDeclType(const clang::RecordDecl *rd) {
   if (!rd || !rd->isCompleteDefinition() || entry.isComplete())
     return entry;
 
-  cgm.errorNYI(rd->getSourceRange(), "Complete record type");
+  // If converting this type would cause us to infinitely loop, don't do it!
+  if (!isSafeToConvert(rd, *this)) {
+    cgm.errorNYI(rd->getSourceRange(), "recursive record layout");
+    return entry;
+  }
+
+  // Okay, this is a definition of a type. Compile the implementation now.
+  bool insertResult = recordsBeingLaidOut.insert(key).second;
+  (void)insertResult;
+  assert(insertResult && "isSafeToCovert() should have caught this.");
+
+  // Force conversion of non-virtual base classes recursively.
+  if (const auto *cxxRecordDecl = dyn_cast<CXXRecordDecl>(rd)) {
+    cgm.errorNYI(rd->getSourceRange(), "CXXRecordDecl");
+  }
+
+  // Layout fields.
+  std::unique_ptr<CIRGenRecordLayout> layout = computeRecordLayout(rd, &entry);
+  recordDeclTypes[key] = entry;
+  cirGenRecordLayouts[key] = std::move(layout);
+
+  // We're done laying out this record.
+  bool eraseResult = recordsBeingLaidOut.erase(key);
+  (void)eraseResult;
+  assert(eraseResult && "record not in RecordsBeingLaidOut set?");
+
+  // If this record blocked a FunctionType conversion, then recompute whatever
+  // was derived from that.
+  assert(!cir::MissingFeatures::skippedLayout());
+
+  // If we're done converting the outer-most record, then convert any deferred
+  // records as well.
+  assert(!cir::MissingFeatures::recursiveRecordLayout());
+
   return entry;
 }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h
index fd855bf958ccb..38e4bb2f688ab 100644
--- a/clang/lib/CIR/CodeGen/CIRGenTypes.h
+++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h
@@ -14,9 +14,10 @@
 #define LLVM_CLANG_LIB_CODEGEN_CODEGENTYPES_H
 
 #include "CIRGenFunctionInfo.h"
-#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "CIRGenRecordLayout.h"
 
 #include "clang/AST/Type.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
 
 #include "llvm/ADT/SmallPtrSet.h"
 
@@ -45,12 +46,22 @@ class CIRGenTypes {
   clang::ASTContext &astContext;
   CIRGenBuilderTy &builder;
 
+  /// Contains the CIR type for any converted RecordDecl.
+  llvm::DenseMap<const clang::Type *, std::unique_ptr<CIRGenRecordLayout>>
+      cirGenRecordLayouts;
+
   /// Contains the CIR type for any converted RecordDecl
   llvm::DenseMap<const clang::Type *, cir::RecordType> recordDeclTypes;
 
   /// Hold memoized CIRGenFunctionInfo results
   llvm::FoldingSet<CIRGenFunctionInfo> functionInfos;
 
+  /// This set keeps track of records that we're currently converting to a CIR
+  /// type. For example, when converting:
+  /// struct A { struct B { int x; } } when processing 'x', the 'A' and 'B'
+  /// types will be in this set.
+  llvm::SmallPtrSet<const clang::Type *, 4> recordsBeingLaidOut;
+
   llvm::SmallPtrSet<const CIRGenFunctionInfo *, 4> functionsBeingProcessed;
   /// Heper for convertType.
   mlir::Type convertFunctionTypeInternal(clang::QualType ft);
@@ -59,6 +70,9 @@ class CIRGenTypes {
   CIRGenTypes(CIRGenModule &cgm);
   ~CIRGenTypes();
 
+  CIRGenBuilderTy &getBuilder() const { return builder; }
+  CIRGenModule &getCGModule() const { return cgm; }
+
   /// Utility to check whether a function type can be converted to a CIR type
   /// (i.e. doesn't depend on an incomplete tag type).
   bool isFuncTypeConvertible(const clang::FunctionType *ft);
@@ -70,12 +84,18 @@ class CIRGenTypes {
   TypeCacheTy typeCache;
 
   mlir::MLIRContext &getMLIRContext() const;
+  clang::ASTContext &getASTContext() const { return astContext; }
+
+  bool noRecordsBeingLaidOut() const { return recordsBeingLaidOut.empty(); }
 
   /// Convert a Clang type into a mlir::Type.
   mlir::Type convertType(clang::QualType type);
 
   mlir::Type convertRecordDeclType(const clang::RecordDecl *recordDecl);
 
+  std::unique_ptr<CIRGenRecordLayout>
+  computeRecordLayout(const clang::RecordDecl *rd, cir::RecordType *ty);
+
   std::string getRecordTypeName(const clang::RecordDecl *,
                                 llvm::StringRef suffix);
 
diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt
index dc18f7f2af160..418bc2db408cb 100644
--- a/clang/lib/CIR/CodeGen/CMakeLists.txt
+++ b/clang/lib/CIR/CodeGen/CMakeLists.txt
@@ -18,6 +18,7 @@ add_clang_library(clangCIR
   CIRGenExprScalar.cpp
   CIRGenFunction.cpp
   CIRGenModule.cpp
+  CIRGenRecordLayoutBuilder.cpp
   CIRGenStmt.cpp
   CIRGenStmtOpenACC.cpp
   CIRGenTypes.cpp
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index f3e5e572653da..1f4232b9e29ec 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -212,7 +212,7 @@ static LogicalResult checkConstantTypes(mlir::Operation *op, mlir::Type opType,
   }
 
   if (isa<cir::ZeroAttr>(attrType)) {
-    if (::mlir::isa<cir::ArrayType>(opType))
+    if (isa<cir::RecordType, cir::ArrayType>(opType))
       return success();
     return op->emitOpError("zero expects struct or array type");
   }
diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
index 9b0177f159084..160732d9c3610 100644
--- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp
@@ -214,6 +214,12 @@ cir::RecordType::RecordKind RecordType::getKind() const {
   return getImpl()->kind;
 }
 
+void RecordType::complete(ArrayRef<Type> members, bool packed, bool padded) {
+  assert(!cir::MissingFeatures::astRecordDeclAttr());
+  if (mutate(members, packed, padded).failed())
+    llvm_unreachable("failed to complete record");
+}
+
 //===----------------------------------------------------------------------===//
 // Data Layout information for types
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 7159f89c93a53..cb318c88c09c5 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -1337,6 +1337,35 @@ static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
   converter.addConversion([&](cir::BF16Type type) -> mlir::Type {
     return mlir::BFloat16Type::get(type.getContext());
   });
+  converter.addConversion([&](cir::RecordType type) -> mlir::Type {
+    // Convert struct members.
+    llvm::SmallVector<mlir::Type> llvmMembers;
+    switch (type.getKind()) {
+    case cir::RecordType::Struct:
+      for (mlir::Type ty : type.getMembers())
+        llvmMembers.push_back(convertTypeForMemory(converter, dataLayout, ty));
+      break;
+    // Unions are lowered as only the largest member.
+    case cir::RecordType::Union:
+      llvm_unreachable("Lowering of unions is NYI");
+      break;
+    }
+
+    // Record has a name: lower as an identified record.
+    mlir::LLVM::LLVMStructType llvmStruct;
+    if (type.getName()) {
+      llvmStruct = mlir::LLVM::LLVMStructType::getIdentified(
+          type.getContext(), type.getPrefixedName());
+      assert(!cir::MissingFeatures::packedRecords());
+      if (llvmStruct.setBody(llvmMembers, /*isPacked=*/true).failed())
+        llvm_unreacha...
[truncated]

mlir::Type data;
union {
const FieldDecl *fieldDecl;
// CXXRecordDecl will be used here when supported.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this because MemberInfo is also supposed to represent base types?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

Copy link
Collaborator

Choose a reason for hiding this comment

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

Then can we change this to:

Suggested change
// CXXRecordDecl will be used here when supported.
// CXXRecordDecl will be used here when base-types are supported.

Copy link
Collaborator

Choose a reason for hiding this comment

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

See this suggestion/change request here.

// Output fields, consumed by CIRGenTypes::computeRecordLayout
llvm::SmallVector<mlir::Type, 16> fieldTypes;
llvm::DenseMap<const FieldDecl *, unsigned> fields;
bool zeroInitializable : 1;
Copy link
Collaborator

Choose a reason for hiding this comment

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

These can't be bool type, they all have to be unsigned, else they get laid-out funny/inconsistently. However, you can/should use LLVM_PREFERRED_TYPE(bool) on them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both the incubator and the classic codegen implementation declare them as bool. I did notice that I have to explicitly cast them as bool to access them, which seemed kind of odd. Is that a symptom of what you're talking about?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thats a symptom of them being bitfields. But yeah, bool-bitfields are problematic for some ABIs (only sequential bitfields of the same type can be 'joined', and at least 1 ABI will only do so for unsigned and int IIRC). So we use unsigned for them every time we are thinking about it during review. I'm guessing this is something in the classic-codegen that we never noticed.

zeroInitializable(true), packed(isPacked), padded(false) {}

void CIRRecordLowering::lower() {
if (recordDecl->isUnion()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we also NYI on base types? Particularly with virtual base types this gets ugly.

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 cut a lot of code out from here. Anything other than unions will fail somewhere else before we get here, but I guess it wouldn't hurt to add more checks here now.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

still 1 comment request, else LGTM.

@@ -126,6 +130,12 @@ void CIRRecordLowering::lower() {
return;
}

if (isa<CXXRecordDecl>(recordDecl)) {
cirGenTypes.getCGModule().errorNYI(recordDecl->getSourceRange(),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Oh my! This ends up being harsher than I expected! This means no record-types in C++ at all (even ones that don't do anything). But if that is the level of support we have, I'm ok with it.

I presume a soon-to-be used patch will reduce this to '!virtual && !getNumBases` etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Eventually, yes. We aren't handling CXXRecordDecl in the code that gets here yet. I don't expect there to be much more than adding a switch statement for that, but everything in C++ ends up being more involved than I realized, so who knows.

@andykaylor
Copy link
Contributor Author

still 1 comment request, else LGTM.

Comment request? Am I still missing something?

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

Still OK, see the request on line 46 of CIRGenRecordLayoutBuilder.cpp for the comment request previously mentioned.

@andykaylor
Copy link
Contributor Author

Still OK, see the request on line 46 of CIRGenRecordLayoutBuilder.cpp for the comment request previously mentioned.

Thanks. I missed that somehow.

Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

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

LGTM with minor nit

getStorageType(field), field));
++field;
} else {
// TODO(cir): do we want to do anything special about zero size members?
Copy link
Member

@bcardosolopes bcardosolopes Apr 16, 2025

Choose a reason for hiding this comment

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

maybe add an assert on missing feature here? this will lead to LLVM IR lowering differences so might be good to track.

@@ -95,6 +95,8 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return getZeroAttr(arrTy);
if (auto ptrTy = mlir::dyn_cast<cir::PointerType>(ty))
return getConstNullPtrAttr(ptrTy);
if (auto RecordTy = mlir::dyn_cast<cir::RecordType>(ty))
Copy link
Member

Choose a reason for hiding this comment

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

NIT

if (auto recordTy = mlir::dyn_cast<cir::RecordType>(ty))

@andykaylor andykaylor merged commit 80c19b3 into llvm:main Apr 16, 2025
9 of 11 checks passed
@llvm-ci
Copy link
Collaborator

llvm-ci commented Apr 16, 2025

LLVM Buildbot has detected a new failure on builder lldb-aarch64-ubuntu running on linaro-lldb-aarch64-ubuntu while building clang at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/59/builds/16142

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
UNSUPPORTED: lldb-api :: tools/lldb-server/TestGdbRemoteFork.py (1200 of 2124)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteCompletion.py (1201 of 2124)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteAttach.py (1202 of 2124)
UNSUPPORTED: lldb-api :: tools/lldb-server/TestGdbRemoteForkNonStop.py (1203 of 2124)
UNSUPPORTED: lldb-api :: tools/lldb-server/TestGdbRemoteForkResume.py (1204 of 2124)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteExitCode.py (1205 of 2124)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteHostInfo.py (1206 of 2124)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteModuleInfo.py (1207 of 2124)
PASS: lldb-api :: tools/lldb-server/TestGdbRemoteAuxvSupport.py (1208 of 2124)
UNRESOLVED: lldb-api :: tools/lldb-dap/variables/TestDAP_variables.py (1209 of 2124)
******************** TEST 'lldb-api :: tools/lldb-dap/variables/TestDAP_variables.py' FAILED ********************
Script:
--
/usr/bin/python3.10 /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./lib --env LLVM_INCLUDE_DIR=/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/include --env LLVM_TOOLS_DIR=/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin --arch aarch64 --build-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex --lldb-module-cache-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin/lldb --compiler /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin/clang --dsymutil /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./bin --lldb-obj-root /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/tools/lldb --lldb-libs-dir /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/./lib /home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/llvm-project/lldb/test/API/tools/lldb-dap/variables -p TestDAP_variables.py
--
Exit Code: 1

Command Output (stdout):
--
lldb version 21.0.0git (https://github.com/llvm/llvm-project.git revision 80c19b3b1d59294be63d8b55fedc317305abbdbe)
  clang revision 80c19b3b1d59294be63d8b55fedc317305abbdbe
  llvm revision 80c19b3b1d59294be63d8b55fedc317305abbdbe
Skipping the following test categories: ['libc++', 'dsym', 'gmodules', 'debugserver', 'objc']

--
Command Output (stderr):
--
UNSUPPORTED: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_darwin_dwarf_missing_obj (TestDAP_variables.TestDAP_variables) (requires one of macosx, darwin, ios, tvos, watchos, bridgeos, iphonesimulator, watchsimulator, appletvsimulator) 
UNSUPPORTED: LLDB (/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/bin/clang-aarch64) :: test_darwin_dwarf_missing_obj_with_symbol_ondemand_enabled (TestDAP_variables.TestDAP_variables) (requires one of macosx, darwin, ios, tvos, watchos, bridgeos, iphonesimulator, watchsimulator, appletvsimulator) 
========= DEBUG ADAPTER PROTOCOL LOGS =========
1744825446.406437874 --> (stdin/stdout) {"command":"initialize","type":"request","arguments":{"adapterID":"lldb-native","clientID":"vscode","columnsStartAt1":true,"linesStartAt1":true,"locale":"en-us","pathFormat":"path","supportsRunInTerminalRequest":true,"supportsVariablePaging":true,"supportsVariableType":true,"supportsStartDebuggingRequest":true,"supportsProgressReporting":true,"$__lldb_sourceInitFile":false},"seq":1}
1744825446.408450603 <-- (stdin/stdout) {"body":{"$__lldb_version":"lldb version 21.0.0git (https://github.com/llvm/llvm-project.git revision 80c19b3b1d59294be63d8b55fedc317305abbdbe)\n  clang revision 80c19b3b1d59294be63d8b55fedc317305abbdbe\n  llvm revision 80c19b3b1d59294be63d8b55fedc317305abbdbe","completionTriggerCharacters":["."," ","\t"],"exceptionBreakpointFilters":[{"default":false,"filter":"cpp_catch","label":"C++ Catch"},{"default":false,"filter":"cpp_throw","label":"C++ Throw"},{"default":false,"filter":"objc_catch","label":"Objective-C Catch"},{"default":false,"filter":"objc_throw","label":"Objective-C Throw"}],"supportTerminateDebuggee":true,"supportsBreakpointLocationsRequest":true,"supportsCancelRequest":true,"supportsCompletionsRequest":true,"supportsConditionalBreakpoints":true,"supportsConfigurationDoneRequest":true,"supportsDataBreakpoints":true,"supportsDelayedStackTraceLoading":true,"supportsDisassembleRequest":true,"supportsEvaluateForHovers":true,"supportsExceptionInfoRequest":true,"supportsExceptionOptions":true,"supportsFunctionBreakpoints":true,"supportsHitConditionalBreakpoints":true,"supportsInstructionBreakpoints":true,"supportsLogPoints":true,"supportsModulesRequest":true,"supportsReadMemoryRequest":true,"supportsRestartRequest":true,"supportsSetVariable":true,"supportsStepInTargetsRequest":true,"supportsSteppingGranularity":true,"supportsValueFormattingOptions":true},"command":"initialize","request_seq":1,"seq":0,"success":true,"type":"response"}
1744825446.408679008 --> (stdin/stdout) {"command":"launch","type":"request","arguments":{"program":"/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/tools/lldb-dap/variables/TestDAP_variables.test_indexedVariables/a.out","initCommands":["settings clear -all","settings set symbols.enable-external-lookup false","settings set target.inherit-tcc true","settings set target.disable-aslr false","settings set target.detach-on-error false","settings set target.auto-apply-fixits false","settings set plugin.process.gdb-remote.packet-timeout 60","settings set symbols.clang-modules-cache-path \"/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api\"","settings set use-color false","settings set show-statusline false"],"disableASLR":false,"enableAutoVariableSummaries":false,"enableSyntheticChildDebugging":false,"displayExtendedBacktrace":false,"commandEscapePrefix":null},"seq":2}
1744825446.408880711 <-- (stdin/stdout) {"body":{"category":"console","output":"Running initCommands:\n"},"event":"output","seq":0,"type":"event"}
1744825446.408902407 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings clear -all\n"},"event":"output","seq":0,"type":"event"}
1744825446.408912420 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set symbols.enable-external-lookup false\n"},"event":"output","seq":0,"type":"event"}
1744825446.408921003 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.inherit-tcc true\n"},"event":"output","seq":0,"type":"event"}
1744825446.408929348 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.disable-aslr false\n"},"event":"output","seq":0,"type":"event"}
1744825446.408936977 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.detach-on-error false\n"},"event":"output","seq":0,"type":"event"}
1744825446.408944845 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set target.auto-apply-fixits false\n"},"event":"output","seq":0,"type":"event"}
1744825446.408952475 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set plugin.process.gdb-remote.packet-timeout 60\n"},"event":"output","seq":0,"type":"event"}
1744825446.408972263 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set symbols.clang-modules-cache-path \"/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/module-cache-lldb/lldb-api\"\n"},"event":"output","seq":0,"type":"event"}
1744825446.408980370 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set use-color false\n"},"event":"output","seq":0,"type":"event"}
1744825446.408989191 <-- (stdin/stdout) {"body":{"category":"console","output":"(lldb) settings set show-statusline false\n"},"event":"output","seq":0,"type":"event"}
1744825446.485081196 <-- (stdin/stdout) {"command":"launch","request_seq":2,"seq":0,"success":true,"type":"response"}
1744825446.485138893 <-- (stdin/stdout) {"body":{"isLocalProcess":true,"name":"/home/tcwg-buildbot/worker/lldb-aarch64-ubuntu/build/lldb-test-build.noindex/tools/lldb-dap/variables/TestDAP_variables.test_indexedVariables/a.out","startMethod":"launch","systemProcessId":1478465},"event":"process","seq":0,"type":"event"}
1744825446.485148191 <-- (stdin/stdout) {"event":"initialized","seq":0,"type":"event"}
1744825446.485439777 --> (stdin/stdout) {"command":"setBreakpoints","type":"request","arguments":{"source":{"name":"main.cpp","path":"main.cpp"},"sourceModified":false,"lines":[40],"breakpoints":[{"line":40}]},"seq":3}
1744825446.486896992 <-- (stdin/stdout) {"body":{"breakpoints":[{"column":1,"id":1,"instructionReference":"0xAAAAEA560C54","line":41,"source":{"name":"main.cpp","path":"main.cpp"},"verified":true}]},"command":"setBreakpoints","request_seq":3,"seq":0,"success":true,"type":"response"}

var-const pushed a commit to ldionne/llvm-project that referenced this pull request Apr 17, 2025
This adds basic support for populating record types. In order to keep
the change small, everything non-essential was deferred to a later
change set. Only non-recursive structures are handled. Structures
padding is not yet implemented. Bitfields are not supported. No attempt
is made to handle ABI requirements for passing structure arguments.
@andykaylor andykaylor deleted the cir-complete-struct branch April 18, 2025 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants