https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/136383
This adds ClangIR support for accessing structure members. Access to union members is deferred to a later change. >From 17800eddb70a74cf3966d64ab7bc5ad5508f8bb0 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <akay...@nvidia.com> Date: Fri, 18 Apr 2025 15:02:22 -0700 Subject: [PATCH] [CIR] Upstream support for accessing structure members This adds ClangIR support for accessing structure members. Access to union members is deferred to a later change. --- .../CIR/Dialect/Builder/CIRBaseBuilder.h | 6 + clang/include/clang/CIR/Dialect/IR/CIROps.td | 64 +++++++++ .../include/clang/CIR/Dialect/IR/CIRTypes.td | 3 + clang/include/clang/CIR/MissingFeatures.h | 2 + clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 134 ++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 15 ++ clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 2 + clang/lib/CIR/CodeGen/CIRGenFunction.h | 7 + clang/lib/CIR/CodeGen/CIRGenModule.h | 7 + clang/lib/CIR/CodeGen/CIRGenRecordLayout.h | 8 +- clang/lib/CIR/CodeGen/CIRGenTypes.cpp | 21 +++ clang/lib/CIR/CodeGen/CIRGenTypes.h | 2 + clang/lib/CIR/CodeGen/CIRGenValue.h | 7 + clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 18 +++ clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 31 ++++ .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 24 ++++ .../CIR/Lowering/DirectToLLVM/LowerToLLVM.h | 10 ++ clang/test/CIR/CodeGen/struct.c | 134 +++++++++++++++--- 18 files changed, 471 insertions(+), 24 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index d2a241964f34f..5eac4fedaec75 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -191,6 +191,12 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { return create<cir::StoreOp>(loc, val, dst); } + cir::GetMemberOp createGetMember(mlir::Location loc, mlir::Type resultTy, + mlir::Value base, llvm::StringRef name, + unsigned index) { + return create<cir::GetMemberOp>(loc, resultTy, base, name, index); + } + mlir::Value createDummyValue(mlir::Location loc, mlir::Type type, clang::CharUnits alignment) { auto addr = createAlloca(loc, getPointerTo(type), type, {}, diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 5ba4b33dc1a12..80b875b2c94ce 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -1310,6 +1310,70 @@ def GetGlobalOp : CIR_Op<"get_global", let hasVerifier = 0; } +//===----------------------------------------------------------------------===// +// GetMemberOp +//===----------------------------------------------------------------------===// + +def GetMemberOp : CIR_Op<"get_member"> { + let summary = "Get the address of a member of a record"; + let description = [{ + The `cir.get_member` operation gets the address of a particular named + member from the input record. + + It expects a pointer to the base record as well as the name of the member + and its field index. + + Example: + ```mlir + // Suppose we have a record with multiple members. + !s32i = !cir.int<s, 32> + !s8i = !cir.int<s, 32> + !ty_B = !cir.record<"struct.B" {!s32i, !s8i}> + + // Get the address of the member at index 1. + %1 = cir.get_member %0[1] {name = "i"} : (!cir.ptr<!ty_B>) -> !cir.ptr<!s8i> + ``` + }]; + + let arguments = (ins + Arg<CIR_PointerType, "the address to load from", [MemRead]>:$addr, + StrAttr:$name, + IndexAttr:$index_attr); + + let results = (outs Res<CIR_PointerType, "">:$result); + + let assemblyFormat = [{ + $addr `[` $index_attr `]` attr-dict + `:` qualified(type($addr)) `->` qualified(type($result)) + }]; + + let builders = [ + OpBuilder<(ins "mlir::Type":$type, + "mlir::Value":$value, + "llvm::StringRef":$name, + "unsigned":$index), + [{ + mlir::APInt fieldIdx(64, index); + build($_builder, $_state, type, value, name, fieldIdx); + }]> + ]; + + let extraClassDeclaration = [{ + /// Return the index of the record member being accessed. + uint64_t getIndex() { return getIndexAttr().getZExtValue(); } + + /// Return the record type pointed by the base pointer. + cir::PointerType getAddrTy() { return getAddr().getType(); } + + /// Return the result type. + cir::PointerType getResultTy() { + return mlir::cast<cir::PointerType>(getResult().getType()); + } + }]; + + let hasVerifier = 1; +} + //===----------------------------------------------------------------------===// // FuncOp //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td index b028bc7db4e59..27ee5389723fa 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td @@ -503,6 +503,9 @@ def CIR_RecordType : CIR_Type<"Record", "record", void complete(llvm::ArrayRef<mlir::Type> members, bool packed, bool isPadded); + uint64_t getElementOffset(const mlir::DataLayout &dataLayout, + unsigned idx) const; + private: unsigned computeStructSize(const mlir::DataLayout &dataLayout) const; uint64_t computeStructAlignment(const mlir::DataLayout &dataLayout) const; diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 0105d1bdaf3fd..6bfc1199aea55 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -157,6 +157,8 @@ struct MissingFeatures { static bool emitCheckedInBoundsGEP() { return false; } static bool preservedAccessIndexRegion() { return false; } static bool bitfields() { return false; } + static bool typeChecks() { return false; } + static bool lambdaFieldToName() { return false; } // Missing types static bool dataMemberType() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index aca26526b79f2..0a518c0fd935d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -27,6 +27,38 @@ using namespace clang; using namespace clang::CIRGen; using namespace cir; +/// Get the address of a zero-sized field within a record. The resulting address +/// doesn't necessarily have the right type. +Address CIRGenFunction::emitAddrOfFieldStorage(Address base, + const FieldDecl *field, + llvm::StringRef fieldName, + unsigned fieldIndex) { + if (field->isZeroSize(getContext())) { + cgm.errorNYI(field->getSourceRange(), + "emitAddrOfFieldStorage: zero-sized field"); + return Address::invalid(); + } + + mlir::Location loc = getLoc(field->getLocation()); + + mlir::Type fieldType = convertType(field->getType()); + auto fieldPtr = cir::PointerType::get(builder.getContext(), fieldType); + // For most cases fieldName is the same as field->getName() but for lambdas, + // which do not currently carry the name, so it can be passed down from the + // CaptureStmt. + cir::GetMemberOp memberAddr = builder.createGetMember( + loc, fieldPtr, base.getPointer(), fieldName, fieldIndex); + + // Retrieve layout information, compute alignment and return the final + // address. + const RecordDecl *rec = field->getParent(); + const CIRGenRecordLayout &layout = cgm.getTypes().getCIRGenRecordLayout(rec); + unsigned idx = layout.getCIRFieldNo(field); + CharUnits offset = CharUnits::fromQuantity( + layout.getCIRType().getElementOffset(cgm.getDataLayout().layout, idx)); + return Address(memberAddr, base.getAlignment().alignmentAtOffset(offset)); +} + /// Given an expression of pointer type, try to /// derive a more accurate bound on the alignment of the pointer. Address CIRGenFunction::emitPointerWithAlignment(const Expr *expr, @@ -264,6 +296,66 @@ mlir::Value CIRGenFunction::emitStoreThroughBitfieldLValue(RValue src, return {}; } +LValue CIRGenFunction::emitLValueForField(LValue base, const FieldDecl *field) { + LValueBaseInfo baseInfo = base.getBaseInfo(); + + if (field->isBitField()) { + cgm.errorNYI(field->getSourceRange(), "emitLValueForField: bitfield"); + return LValue(); + } + + QualType fieldType = field->getType(); + const RecordDecl *rec = field->getParent(); + AlignmentSource baseAlignSource = baseInfo.getAlignmentSource(); + LValueBaseInfo fieldBaseInfo(getFieldAlignmentSource(baseAlignSource)); + assert(!cir::MissingFeatures::opTBAA()); + + Address addr = base.getAddress(); + if (auto *classDef = dyn_cast<CXXRecordDecl>(rec)) { + cgm.errorNYI(field->getSourceRange(), "emitLValueForField: C++ class"); + return LValue(); + } + + unsigned recordCVR = base.getVRQualifiers(); + if (rec->isUnion()) { + cgm.errorNYI(field->getSourceRange(), "emitLValueForField: union"); + return LValue(); + } + + assert(!cir::MissingFeatures::preservedAccessIndexRegion()); + llvm::StringRef fieldName = field->getName(); + const CIRGenRecordLayout &layout = + cgm.getTypes().getCIRGenRecordLayout(field->getParent()); + unsigned fieldIndex = layout.getCIRFieldNo(field); + + assert(!cir::MissingFeatures::lambdaFieldToName()); + + addr = emitAddrOfFieldStorage(addr, field, fieldName, fieldIndex); + + // If this is a reference field, load the reference right now. + if (fieldType->isReferenceType()) { + cgm.errorNYI(field->getSourceRange(), "emitLValueForField: reference type"); + return LValue(); + } + + if (field->hasAttr<AnnotateAttr>()) { + cgm.errorNYI(field->getSourceRange(), "emitLValueForField: AnnotateAttr"); + return LValue(); + } + + LValue lv = makeAddrLValue(addr, fieldType, fieldBaseInfo); + lv.getQuals().addCVRQualifiers(recordCVR); + + // __weak attribute on a field is ignored. + if (lv.getQuals().getObjCGCAttr() == Qualifiers::Weak) { + cgm.errorNYI(field->getSourceRange(), + "emitLValueForField: __weak attribute"); + return LValue(); + } + + return lv; +} + mlir::Value CIRGenFunction::emitToMemory(mlir::Value value, QualType ty) { // Bool has a different representation in memory than in registers, // but in ClangIR, it is simply represented as a cir.bool value. @@ -608,6 +700,48 @@ CIRGenFunction::emitArraySubscriptExpr(const clang::ArraySubscriptExpr *e) { return lv; } +LValue CIRGenFunction::emitMemberExpr(const MemberExpr *e) { + if (auto *vd = dyn_cast<VarDecl>(e->getMemberDecl())) { + cgm.errorNYI(e->getSourceRange(), "emitMemberExpr: VarDecl"); + return LValue(); + } + + Expr *baseExpr = e->getBase(); + // If this is s.x, emit s as an lvalue. If it is s->x, emit s as a scalar. + LValue baseLV; + if (e->isArrow()) { + LValueBaseInfo baseInfo; + assert(!cir::MissingFeatures::opTBAA()); + Address addr = emitPointerWithAlignment(baseExpr, &baseInfo); + QualType ptrTy = baseExpr->getType()->getPointeeType(); + assert(!cir::MissingFeatures::typeChecks()); + baseLV = makeAddrLValue(addr, ptrTy, baseInfo); + } else { + assert(!cir::MissingFeatures::typeChecks()); + baseLV = emitLValue(baseExpr); + } + + const NamedDecl *nd = e->getMemberDecl(); + if (auto *field = dyn_cast<FieldDecl>(nd)) { + LValue lv = emitLValueForField(baseLV, field); + assert(!cir::MissingFeatures::setObjCGCLValueClass()); + if (getLangOpts().OpenMP) { + // If the member was explicitly marked as nontemporal, mark it as + // nontemporal. If the base lvalue is marked as nontemporal, mark access + // to children as nontemporal too. + cgm.errorNYI(e->getSourceRange(), "emitMemberExpr: OpenMP"); + } + return lv; + } + + if (const auto *fd = dyn_cast<FunctionDecl>(nd)) { + cgm.errorNYI(e->getSourceRange(), "emitMemberExpr: FunctionDecl"); + return LValue(); + } + + llvm_unreachable("Unhandled member declaration!"); +} + LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) { // Comma expressions just emit their LHS then their RHS as an l-value. if (e->getOpcode() == BO_Comma) { diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 1bef1b976a4b5..05b337b52cbe9 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -171,6 +171,8 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { return emitLoadOfLValue(e); } + mlir::Value VisitMemberExpr(MemberExpr *e); + mlir::Value VisitExplicitCastExpr(ExplicitCastExpr *e) { return VisitCastExpr(e); } @@ -1529,6 +1531,19 @@ mlir::Value ScalarExprEmitter::VisitCallExpr(const CallExpr *e) { return v; } +mlir::Value ScalarExprEmitter::VisitMemberExpr(MemberExpr *e) { + // TODO(cir): The classic codegen calls tryEmitAsConstant() here. Folding + // constants sound like work for MLIR optimizers, but we'll keep an assertion + // for now. + assert(!cir::MissingFeatures::tryEmitAsConstant()); + Expr::EvalResult result; + if (e->EvaluateAsInt(result, cgf.getContext(), Expr::SE_AllowSideEffects)) { + cgf.cgm.errorNYI(e->getSourceRange(), "Constant interger member expr"); + // Fall through to emit this as a non-constant access. + } + return emitLoadOfLValue(e); +} + mlir::Value CIRGenFunction::emitScalarConversion(mlir::Value src, QualType srcTy, QualType dstTy, SourceLocation loc) { diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 76e9ca4fd61a8..5412f9f602711 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -513,6 +513,8 @@ LValue CIRGenFunction::emitLValue(const Expr *e) { return emitArraySubscriptExpr(cast<ArraySubscriptExpr>(e)); case Expr::UnaryOperatorClass: return emitUnaryOpLValue(cast<UnaryOperator>(e)); + case Expr::MemberExprClass: + return emitMemberExpr(cast<MemberExpr>(e)); case Expr::BinaryOperatorClass: return emitBinaryOperatorLValue(cast<BinaryOperator>(e)); case Expr::ParenExprClass: diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 01abd84ce1c85..f533d0ab53cd2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -423,6 +423,10 @@ class CIRGenFunction : public CIRGenTypeCache { clang::CharUnits alignment); public: + Address emitAddrOfFieldStorage(Address base, const FieldDecl *field, + llvm::StringRef fieldName, + unsigned fieldIndex); + mlir::Value emitAlloca(llvm::StringRef name, mlir::Type ty, mlir::Location loc, clang::CharUnits alignment, bool insertIntoFnEntryBlock, @@ -551,6 +555,9 @@ class CIRGenFunction : public CIRGenTypeCache { /// of the expression. /// FIXME: document this function better. LValue emitLValue(const clang::Expr *e); + LValue emitLValueForField(LValue base, const clang::FieldDecl *field); + + LValue emitMemberExpr(const MemberExpr *e); /// Given an expression with a pointer type, emit the value and compute our /// best estimate of the alignment of the pointee. diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 1e0d6623c4f40..1fb97334d7bd2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -19,6 +19,7 @@ #include "CIRGenValue.h" #include "clang/AST/CharUnits.h" +#include "clang/CIR/Dialect/IR/CIRDataLayout.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "TargetInfo.h" @@ -95,6 +96,12 @@ class CIRGenModule : public CIRGenTypeCache { const clang::LangOptions &getLangOpts() const { return langOpts; } mlir::MLIRContext &getMLIRContext() { return *builder.getContext(); } + const cir::CIRDataLayout getDataLayout() const { + // FIXME(cir): instead of creating a CIRDataLayout every time, set it as an + // attribute for the CIRModule class. + return cir::CIRDataLayout(theModule); + } + /// ------- /// Handling globals /// ------- diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h index a51e0460d1074..39a9d16ffd766 100644 --- a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h +++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h @@ -42,10 +42,10 @@ class CIRGenRecordLayout { 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); + unsigned getCIRFieldNo(const clang::FieldDecl *fd) const { + fd = fd->getCanonicalDecl(); + assert(fieldInfo.count(fd) && "Invalid field for record!"); + return fieldInfo.lookup(fd); } }; diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp index 7bd86cf0c7bcd..90993c71be9a6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp @@ -446,6 +446,27 @@ mlir::Type CIRGenTypes::convertTypeForMem(clang::QualType qualType, return convertedType; } +/// Return record layout info for the given record decl. +const CIRGenRecordLayout & +CIRGenTypes::getCIRGenRecordLayout(const RecordDecl *rd) { + const auto *key = astContext.getTagDeclType(rd).getTypePtr(); + + // If we have already computed the layout, return it. + auto it = cirGenRecordLayouts.find(key); + if (it != cirGenRecordLayouts.end()) + return *it->second; + + // Compute the type information. + convertRecordDeclType(rd); + + // Now try again. + it = cirGenRecordLayouts.find(key); + + assert(it != cirGenRecordLayouts.end() && + "Unable to find record layout information for type"); + return *it->second; +} + bool CIRGenTypes::isZeroInitializable(clang::QualType t) { if (t->getAs<PointerType>()) return astContext.getTargetNullPointerValue(t) == 0; diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h index 2bb78420700f8..5b4027601ca3a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenTypes.h +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h @@ -108,6 +108,8 @@ class CIRGenTypes { std::string getRecordTypeName(const clang::RecordDecl *, llvm::StringRef suffix); + const CIRGenRecordLayout &getCIRGenRecordLayout(const clang::RecordDecl *rd); + /// 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/CodeGen/CIRGenValue.h b/clang/lib/CIR/CodeGen/CIRGenValue.h index d4d6f5a44622e..1c453dc9c86b5 100644 --- a/clang/lib/CIR/CodeGen/CIRGenValue.h +++ b/clang/lib/CIR/CodeGen/CIRGenValue.h @@ -140,6 +140,10 @@ class LValue { // TODO: Add support for volatile bool isVolatile() const { return false; } + unsigned getVRQualifiers() const { + return quals.getCVRQualifiers() & ~clang::Qualifiers::Const; + } + clang::QualType getType() const { return type; } mlir::Value getPointer() const { return v; } @@ -154,6 +158,9 @@ class LValue { } const clang::Qualifiers &getQuals() const { return quals; } + clang::Qualifiers &getQuals() { return quals; } + + LValueBaseInfo getBaseInfo() const { return baseInfo; } static LValue makeAddr(Address address, clang::QualType t, LValueBaseInfo baseInfo) { diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index d2313e75870b4..fb85052b465f1 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1096,6 +1096,24 @@ OpFoldResult cir::UnaryOp::fold(FoldAdaptor adaptor) { return {}; } +//===----------------------------------------------------------------------===// +// GetMemberOp Definitions +//===----------------------------------------------------------------------===// + +LogicalResult cir::GetMemberOp::verify() { + const auto recordTy = dyn_cast<RecordType>(getAddrTy().getPointee()); + if (!recordTy) + return emitError() << "expected pointer to a record type"; + + if (recordTy.getMembers().size() <= getIndex()) + return emitError() << "member index out of bounds"; + + if (recordTy.getMembers()[getIndex()] != getResultTy().getPointee()) + return emitError() << "member type mismatch"; + + return mlir::success(); +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp index 8b5646f339bb3..c7ac02b7984a3 100644 --- a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp @@ -309,6 +309,37 @@ RecordType::computeStructAlignment(const mlir::DataLayout &dataLayout) const { return recordAlignment; } +uint64_t RecordType::getElementOffset(const ::mlir::DataLayout &dataLayout, + unsigned idx) const { + assert(idx < getMembers().size() && "access not valid"); + + // All union elements are at offset zero. + if (isUnion() || idx == 0) + return 0; + + assert(isComplete() && "Cannot get layout of incomplete records"); + assert(idx < getNumElements()); + llvm::ArrayRef<mlir::Type> members = getMembers(); + + unsigned offset = 0; + + for (unsigned i = 0, e = idx; i != e; ++i) { + mlir::Type ty = members[i]; + + // This matches LLVM since it uses the ABI instead of preferred alignment. + const llvm::Align tyAlign = + llvm::Align(getPacked() ? 1 : dataLayout.getTypeABIAlignment(ty)); + + // Add padding if necessary to align the data element properly. + offset = llvm::alignTo(offset, tyAlign); + + // Consume space for this data item + offset += dataLayout.getTypeSize(ty); + } + + return offset; +} + //===----------------------------------------------------------------------===// // IntType Definitions //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 8c4a67258df3f..389d1f457fc3b 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -1467,6 +1467,7 @@ void ConvertCIRToLLVMPass::runOnOperation() { CIRToLLVMConstantOpLowering, CIRToLLVMFuncOpLowering, CIRToLLVMGetGlobalOpLowering, + CIRToLLVMGetMemberOpLowering, CIRToLLVMTrapOpLowering, CIRToLLVMUnaryOpLowering // clang-format on @@ -1496,6 +1497,29 @@ mlir::LogicalResult CIRToLLVMBrOpLowering::matchAndRewrite( return mlir::LogicalResult::success(); } +mlir::LogicalResult CIRToLLVMGetMemberOpLowering::matchAndRewrite( + cir::GetMemberOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + mlir::Type llResTy = getTypeConverter()->convertType(op.getType()); + const auto recordTy = + mlir::cast<cir::RecordType>(op.getAddrTy().getPointee()); + assert(recordTy && "expected record type"); + + switch (recordTy.getKind()) { + case cir::RecordType::Struct: { + // Since the base address is a pointer to an aggregate, the first offset + // is always zero. The second offset tell us which member it will access. + llvm::SmallVector<mlir::LLVM::GEPArg, 2> offset{0, op.getIndex()}; + const mlir::Type elementTy = getTypeConverter()->convertType(recordTy); + rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(op, llResTy, elementTy, + adaptor.getAddr(), offset); + return mlir::success(); + } + case cir::RecordType::Union: + return op.emitError() << "NYI: union get_member lowering"; + } +} + mlir::LogicalResult CIRToLLVMTrapOpLowering::matchAndRewrite( cir::TrapOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h index 1de6c9c56b485..90f28d256527f 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h @@ -218,6 +218,16 @@ class CIRToLLVMBrOpLowering : public mlir::OpConversionPattern<cir::BrOp> { mlir::ConversionPatternRewriter &) const override; }; +class CIRToLLVMGetMemberOpLowering + : public mlir::OpConversionPattern<cir::GetMemberOp> { +public: + using mlir::OpConversionPattern<cir::GetMemberOp>::OpConversionPattern; + + mlir::LogicalResult + matchAndRewrite(cir::GetMemberOp op, OpAdaptor, + mlir::ConversionPatternRewriter &) const override; +}; + class CIRToLLVMTrapOpLowering : public mlir::OpConversionPattern<cir::TrapOp> { public: using mlir::OpConversionPattern<cir::TrapOp>::OpConversionPattern; diff --git a/clang/test/CIR/CodeGen/struct.c b/clang/test/CIR/CodeGen/struct.c index 3dc1655e15d2c..b78a2367bda3a 100644 --- a/clang/test/CIR/CodeGen/struct.c +++ b/clang/test/CIR/CodeGen/struct.c @@ -7,23 +7,23 @@ // For LLVM IR checks, the structs are defined before the variables, so these // checks are at the top. -// LLVM: %struct.CompleteS = type { i32, i8 } -// LLVM: %struct.OuterS = type { %struct.InnerS, i32 } -// LLVM: %struct.InnerS = type { i32, i8 } -// LLVM: %struct.PackedS = type <{ i32, i8 }> -// LLVM: %struct.PackedAndPaddedS = type <{ i32, i8, i8 }> -// OGCG: %struct.CompleteS = type { i32, i8 } -// OGCG: %struct.OuterS = type { %struct.InnerS, i32 } -// OGCG: %struct.InnerS = type { i32, i8 } -// OGCG: %struct.PackedS = type <{ i32, i8 }> -// OGCG: %struct.PackedAndPaddedS = type <{ i32, i8, i8 }> +// LLVM-DAG: %struct.CompleteS = type { i32, i8 } +// LLVM-DAG: %struct.OuterS = type { %struct.InnerS, i32 } +// LLVM-DAG: %struct.InnerS = type { i32, i8 } +// LLVM-DAG: %struct.PackedS = type <{ i32, i8 }> +// LLVM-DAG: %struct.PackedAndPaddedS = type <{ i32, i8, i8 }> +// OGCG-DAG: %struct.CompleteS = type { i32, i8 } +// OGCG-DAG: %struct.OuterS = type { %struct.InnerS, i32 } +// OGCG-DAG: %struct.InnerS = type { i32, i8 } +// OGCG-DAG: %struct.PackedS = type <{ i32, i8 }> +// OGCG-DAG: %struct.PackedAndPaddedS = type <{ i32, i8, i8 }> struct IncompleteS *p; // CIR: cir.global external @p = #cir.ptr<null> : !cir.ptr<!cir.record<struct // CIR-SAME: "IncompleteS" incomplete>> -// LLVM: @p = dso_local global ptr null -// OGCG: @p = global ptr null, align 8 +// LLVM-DAG: @p = dso_local global ptr null +// OGCG-DAG: @p = global ptr null, align 8 struct CompleteS { int a; @@ -32,8 +32,8 @@ struct CompleteS { // CIR: cir.global external @cs = #cir.zero : !cir.record<struct // CIR-SAME: "CompleteS" {!s32i, !s8i}> -// LLVM: @cs = dso_local global %struct.CompleteS zeroinitializer -// OGCG: @cs = global %struct.CompleteS zeroinitializer, align 4 +// LLVM-DAG: @cs = dso_local global %struct.CompleteS zeroinitializer +// OGCG-DAG: @cs = global %struct.CompleteS zeroinitializer, align 4 struct InnerS { int a; @@ -49,8 +49,8 @@ struct OuterS os; // CIR: cir.global external @os = #cir.zero : !cir.record<struct // CIR-SAME: "OuterS" {!cir.record<struct "InnerS" {!s32i, !s8i}>, !s32i}> -// LLVM: @os = dso_local global %struct.OuterS zeroinitializer -// OGCG: @os = global %struct.OuterS zeroinitializer, align 4 +// LLVM-DAG: @os = dso_local global %struct.OuterS zeroinitializer +// OGCG-DAG: @os = global %struct.OuterS zeroinitializer, align 4 #pragma pack(push) #pragma pack(1) @@ -62,8 +62,8 @@ struct PackedS { // CIR: cir.global external @ps = #cir.zero : !cir.record<struct "PackedS" // CIR-SAME: packed {!s32i, !s8i}> -// LLVM: @ps = dso_local global %struct.PackedS zeroinitializer -// OGCG: @ps = global %struct.PackedS zeroinitializer, align 1 +// LLVM-DAG: @ps = dso_local global %struct.PackedS zeroinitializer +// OGCG-DAG: @ps = global %struct.PackedS zeroinitializer, align 1 struct PackedAndPaddedS { int b0; @@ -72,8 +72,8 @@ struct PackedAndPaddedS { // CIR: cir.global external @pps = #cir.zero : !cir.record<struct // CIR-SAME: "PackedAndPaddedS" packed padded {!s32i, !s8i, !u8i}> -// LLVM: @pps = dso_local global %struct.PackedAndPaddedS zeroinitializer -// OGCG: @pps = global %struct.PackedAndPaddedS zeroinitializer, align 2 +// LLVM-DAG: @pps = dso_local global %struct.PackedAndPaddedS zeroinitializer +// OGCG-DAG: @pps = global %struct.PackedAndPaddedS zeroinitializer, align 2 #pragma pack(pop) @@ -114,3 +114,97 @@ void f2(void) { // OGCG-NEXT: entry: // OGCG-NEXT: %[[S:.*]] = alloca %struct.CompleteS, align 4 // OGCG-NEXT: ret void + +char f3(int a) { + cs.a = a; + return cs.b; +} + +// CIR: cir.func @f3(%[[ARG_A:.*]]: !s32i +// CIR-NEXT: %[[A_ADDR:.*]] = cir.alloca {{.*}} ["a", init] {alignment = 4 : i64} +// CIR-NEXT: %[[RETVAL_ADDR:.*]] = cir.alloca {{.*}} ["__retval"] {alignment = 1 : i64} +// CIR-NEXT: cir.store %[[ARG_A]], %[[A_ADDR]] +// CIR-NEXT: %[[A_VAL:.*]] = cir.load %[[A_ADDR]] +// CIR-NEXT: %[[CS:.*]] = cir.get_global @cs +// CIR-NEXT: %[[CS_A:.*]] = cir.get_member %[[CS]][0] {name = "a"} +// CIR-NEXT: cir.store %[[A_VAL]], %[[CS_A]] +// CIR-NEXT: %[[CS2:.*]] = cir.get_global @cs +// CIR-NEXT: %[[CS_B:.*]] = cir.get_member %[[CS2]][1] {name = "b"} +// CIR-NEXT: %[[CS_B_VAL:.*]] = cir.load %[[CS_B]] +// CIR-NEXT: cir.store %[[CS_B_VAL]], %[[RETVAL_ADDR]] +// CIR-NEXT: %[[RETVAL:.*]] = cir.load %[[RETVAL_ADDR]] +// CIR-NEXT: cir.return %[[RETVAL]] + +// LLVM: define i8 @f3(i32 %[[ARG_A:.*]]) +// LLVM-NEXT: %[[A_ADDR:.*]] = alloca i32, i64 1, align 4 +// LLVM-NEXT: %[[RETVAL_ADDR:.*]] = alloca i8, i64 1, align 1 +// LLVM-NEXT: store i32 %[[ARG_A]], ptr %[[A_ADDR]], align 4 +// LLVM-NEXT: %[[A_VAL:.*]] = load i32, ptr %[[A_ADDR]], align 4 +// LLVM-NEXT: store i32 %[[A_VAL]], ptr @cs, align 4 +// LLVM-NEXT: %[[CS_B_VAL:.*]] = load i8, ptr getelementptr inbounds nuw (i8, ptr @cs, i64 4), align 1 +// LLVM-NEXT: store i8 %[[CS_B_VAL]], ptr %[[RETVAL_ADDR]], align 1 +// LLVM-NEXT: %[[RETVAL:.*]] = load i8, ptr %[[RETVAL_ADDR]], align 1 +// LLVM-NEXT: ret i8 %[[RETVAL]] + +// OGCG: define{{.*}} i8 @f3(i32{{.*}} %[[ARG_A:.*]]) +// OGCG-NEXT: entry: +// OGCG-NEXT: %[[A_ADDR:.*]] = alloca i32, align 4 +// OGCG-NEXT: store i32 %[[ARG_A]], ptr %[[A_ADDR]], align 4 +// OGCG-NEXT: %[[A_VAL:.*]] = load i32, ptr %[[A_ADDR]], align 4 +// OGCG-NEXT: store i32 %[[A_VAL]], ptr @cs, align 4 +// OGCG-NEXT: %[[CS_B_VAL:.*]] = load i8, ptr getelementptr inbounds nuw (%struct.CompleteS, ptr @cs, i32 0, i32 1), align 4 +// OGCG-NEXT: ret i8 %[[CS_B_VAL]] + +char f4(int a, struct CompleteS *p) { + p->a = a; + return p->b; +} + +// CIR: cir.func @f4(%[[ARG_A:.*]]: !s32i {{.*}}, %[[ARG_P:.*]]: !cir.ptr<!cir.record<struct "CompleteS" {!s32i, !s8i}>> +// CIR-NEXT: %[[A_ADDR:.*]] = cir.alloca {{.*}} ["a", init] {alignment = 4 : i64} +// CIR-NEXT: %[[P_ADDR:.*]] = cir.alloca {{.*}} ["p", init] {alignment = 8 : i64} +// CIR-NEXT: %[[RETVAL_ADDR:.*]] = cir.alloca {{.*}} ["__retval"] {alignment = 1 : i64} +// CIR-NEXT: cir.store %[[ARG_A]], %[[A_ADDR]] +// CIR-NEXT: cir.store %[[ARG_P]], %[[P_ADDR]] +// CIR-NEXT: %[[A_VAL:.*]] = cir.load %[[A_ADDR]] +// CIR-NEXT: %[[P:.*]] = cir.load %[[P_ADDR]] +// CIR-NEXT: %[[P_A:.*]] = cir.get_member %[[P]][0] {name = "a"} +// CIR-NEXT: cir.store %[[A_VAL]], %[[P_A]] +// CIR-NEXT: %[[P2:.*]] = cir.load %[[P_ADDR]] +// CIR-NEXT: %[[P_B:.*]] = cir.get_member %[[P2]][1] {name = "b"} +// CIR-NEXT: %[[P_B_VAL:.*]] = cir.load %[[P_B]] +// CIR-NEXT: cir.store %[[P_B_VAL]], %[[RETVAL_ADDR]] +// CIR-NEXT: %[[RETVAL:.*]] = cir.load %[[RETVAL_ADDR]] +// CIR-NEXT: cir.return %[[RETVAL]] + +// LLVM: define i8 @f4(i32 %[[ARG_A:.*]], ptr %[[ARG_P:.*]]) +// LLVM-NEXT: %[[A_ADDR:.*]] = alloca i32, i64 1, align 4 +// LLVM-NEXT: %[[P_ADDR:.*]] = alloca ptr, i64 1, align 8 +// LLVM-NEXT: %[[RETVAL_ADDR:.*]] = alloca i8, i64 1, align 1 +// LLVM-NEXT: store i32 %[[ARG_A]], ptr %[[A_ADDR]], align 4 +// LLVM-NEXT: store ptr %[[ARG_P]], ptr %[[P_ADDR]], align 8 +// LLVM-NEXT: %[[A_VAL:.*]] = load i32, ptr %[[A_ADDR]], align 4 +// LLVM-NEXT: %[[P_VAL:.*]] = load ptr, ptr %[[P_ADDR]], align 8 +// LLVM-NEXT: %[[P_A:.*]] = getelementptr %struct.CompleteS, ptr %[[P_VAL]], i32 0, i32 0 +// LLVM-NEXT: store i32 %[[A_VAL]], ptr %[[P_A]], align 4 +// LLVM-NEXT: %[[P_VAL2:.*]] = load ptr, ptr %[[P_ADDR]], align 8 +// LLVM-NEXT: %[[P_B:.*]] = getelementptr %struct.CompleteS, ptr %[[P_VAL2]], i32 0, i32 1 +// LLVM-NEXT: %[[P_B_VAL:.*]] = load i8, ptr %[[P_B]], align 1 +// LLVM-NEXT: store i8 %[[P_B_VAL]], ptr %[[RETVAL_ADDR]], align 1 +// LLVM-NEXT: %[[RETVAL:.*]] = load i8, ptr %[[RETVAL_ADDR]], align 1 +// LLVM-NEXT: ret i8 %[[RETVAL]] + +// OGCG: define{{.*}} i8 @f4(i32{{.*}} %[[ARG_A:.*]], ptr{{.*}} %[[ARG_P:.*]]) +// OGCG-NEXT: entry: +// OGCG-NEXT: %[[A_ADDR:.*]] = alloca i32, align 4 +// OGCG-NEXT: %[[P_ADDR:.*]] = alloca ptr, align 8 +// OGCG-NEXT: store i32 %[[ARG_A]], ptr %[[A_ADDR]], align 4 +// OGCG-NEXT: store ptr %[[ARG_P]], ptr %[[P_ADDR]], align 8 +// OGCG-NEXT: %[[A_VAL:.*]] = load i32, ptr %[[A_ADDR]], align 4 +// OGCG-NEXT: %[[P:.*]] = load ptr, ptr %[[P_ADDR]], align 8 +// OGCG-NEXT: %[[P_A:.*]] = getelementptr inbounds nuw %struct.CompleteS, ptr %[[P]], i32 0, i32 0 +// OGCG-NEXT: store i32 %[[A_VAL]], ptr %[[P_A]], align 4 +// OGCG-NEXT: %[[P2:.*]] = load ptr, ptr %[[P_ADDR]], align 8 +// OGCG-NEXT: %[[P_B:.*]] = getelementptr inbounds nuw %struct.CompleteS, ptr %[[P2]], i32 0, i32 1 +// OGCG-NEXT: %[[P_B_VAL:.*]] = load i8, ptr %[[P_B]], align 4 +// OGCG-NEXT: ret i8 %[[P_B_VAL]] _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits