Skip to content

Conversation

@andykaylor
Copy link
Contributor

This adds support for a CIR operation to represent runtime data member access.

This adds support for a CIR operation to represent runtime data member access.
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Dec 9, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 9, 2025

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

This adds support for a CIR operation to represent runtime data member access.


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

12 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+57)
  • (modified) clang/lib/CIR/CodeGen/CIRGenBuilder.h (+13)
  • (modified) clang/lib/CIR/CodeGen/CIRGenClass.cpp (+19)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+25-4)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+2-4)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+6)
  • (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+15)
  • (modified) clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h (+8)
  • (modified) clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp (+21)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+11)
  • (modified) clang/test/CIR/CodeGen/pointer-to-data-member.cpp (+166)
  • (modified) clang/test/CIR/IR/invalid-data-member.cir (+31)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 9bd24cf0bcf27..8ba23ccda58d0 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3489,6 +3489,63 @@ def CIR_ArrayDtor : CIR_ArrayInitDestroy<"array.dtor"> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// GetRuntimeMemberOp
+//===----------------------------------------------------------------------===//
+
+def CIR_GetRuntimeMemberOp : CIR_Op<"get_runtime_member"> {
+  let summary = "Get the address of a member of a record";
+  let description = [{
+    The `cir.get_runtime_member` operation gets the address of a member from
+    the input record. The target member is given by a value of type
+    `!cir.data_member` (i.e. a pointer-to-data-member value).
+
+    This operation differs from `cir.get_member` in when the target member can
+    be determined. For the `cir.get_member` operation, the target member is
+    specified as a constant index so the member it returns access to is known
+    when the operation is constructed. For the `cir.get_runtime_member`
+    operation, the target member is given through a pointer-to-data-member
+    value which is unknown until the program being compiled is executed. In
+    other words, `cir.get_member` represents a normal member access through the
+    `.` operator in C/C++:
+
+    ```cpp
+    struct Foo { int x; };
+    Foo f;
+    (void)f.x;  // cir.get_member
+    ```
+
+    And `cir.get_runtime_member` represents a member access through the `.*` or
+    the `->*` operator in C++:
+
+    ```cpp
+    struct Foo { int x; }
+    Foo f;
+    Foo *p;
+    int Foo::*member;
+
+    (void)f.*member;   // cir.get_runtime_member
+    (void)p->*member;  // cir.get_runtime_member
+    ```
+
+    This operation expects a pointer to the base record as well as the pointer
+    to the target member.
+  }];
+
+  let arguments = (ins
+    Arg<CIR_PtrToRecordType, "address of the record object", [MemRead]>:$addr,
+    Arg<CIR_DataMemberType, "pointer to the target member">:$member);
+
+  let results = (outs Res<CIR_PointerType, "">:$result);
+
+  let assemblyFormat = [{
+    $addr `[` $member `:` qualified(type($member)) `]` attr-dict
+    `:` qualified(type($addr)) `->` qualified(type($result))
+  }];
+
+  let hasVerifier = 1;
+}
+
 //===----------------------------------------------------------------------===//
 // VecCreate
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index bf13eeeaea60a..f3716fb02c51b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -526,6 +526,19 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
                    addr.getAlignment()};
   }
 
+  cir::GetRuntimeMemberOp createGetIndirectMember(mlir::Location loc,
+                                                  mlir::Value objectPtr,
+                                                  mlir::Value memberPtr) {
+    auto memberPtrTy = mlir::cast<cir::DataMemberType>(memberPtr.getType());
+
+    // TODO(cir): consider address space.
+    assert(!cir::MissingFeatures::addressSpace());
+    cir::PointerType resultTy = getPointerTo(memberPtrTy.getMemberTy());
+
+    return cir::GetRuntimeMemberOp::create(*this, loc, resultTy, objectPtr,
+                                           memberPtr);
+  }
+
   /// Create a cir.ptr_stride operation to get access to an array element.
   /// \p idx is the index of the element to access, \p shouldDecay is true if
   /// the result should decay to a pointer to the element type.
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 2a26e38bb214d..3ab3be3706205 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -584,6 +584,25 @@ void CIRGenFunction::emitInitializerForField(FieldDecl *field, LValue lhs,
   assert(!cir::MissingFeatures::requiresCleanups());
 }
 
+Address CIRGenFunction::emitCXXMemberDataPointerAddress(
+    const Expr *e, Address base, mlir::Value memberPtr,
+    const MemberPointerType *memberPtrType, LValueBaseInfo *baseInfo) {
+  assert(!cir::MissingFeatures::cxxABI());
+
+  cir::GetRuntimeMemberOp op = builder.createGetIndirectMember(
+      getLoc(e->getSourceRange()), base.getPointer(), memberPtr);
+
+  QualType memberType = memberPtrType->getPointeeType();
+  assert(!cir::MissingFeatures::opTBAA());
+  CharUnits memberAlign = cgm.getNaturalTypeAlignment(memberType, baseInfo);
+  memberAlign = cgm.getDynamicOffsetAlignment(
+      base.getAlignment(), memberPtrType->getMostRecentCXXRecordDecl(),
+      memberAlign);
+
+  return Address(op, convertTypeForMem(memberPtrType->getPointeeType()),
+                 memberAlign);
+}
+
 CharUnits
 CIRGenModule::getDynamicOffsetAlignment(CharUnits actualBaseAlign,
                                         const CXXRecordDecl *baseDecl,
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 5d509e37f4621..87ff73a074240 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -684,6 +684,29 @@ RValue CIRGenFunction::emitLoadOfExtVectorElementLValue(LValue lv) {
   return RValue::get(resultVec);
 }
 
+LValue
+CIRGenFunction::emitPointerToDataMemberBinaryExpr(const BinaryOperator *e) {
+  assert((e->getOpcode() == BO_PtrMemD || e->getOpcode() == BO_PtrMemI) &&
+         "unexpected binary operator opcode");
+
+  Address baseAddr = Address::invalid();
+  if (e->getOpcode() == BO_PtrMemD)
+    baseAddr = emitLValue(e->getLHS()).getAddress();
+  else
+    baseAddr = emitPointerWithAlignment(e->getLHS());
+
+  const auto *memberPtrTy = e->getRHS()->getType()->castAs<MemberPointerType>();
+
+  mlir::Value memberPtr = emitScalarExpr(e->getRHS());
+
+  LValueBaseInfo baseInfo;
+  assert(!cir::MissingFeatures::opTBAA());
+  Address memberAddr = emitCXXMemberDataPointerAddress(e, baseAddr, memberPtr,
+                                                       memberPtrTy, &baseInfo);
+
+  return makeAddrLValue(memberAddr, memberPtrTy->getPointeeType(), baseInfo);
+}
+
 /// Generates lvalue for partial ext_vector access.
 Address CIRGenFunction::emitExtVectorElementLValue(LValue lv,
                                                    mlir::Location loc) {
@@ -1763,10 +1786,8 @@ LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) {
     return emitLValue(e->getRHS());
   }
 
-  if (e->getOpcode() == BO_PtrMemD || e->getOpcode() == BO_PtrMemI) {
-    cgm.errorNYI(e->getSourceRange(), "member pointers");
-    return {};
-  }
+  if (e->getOpcode() == BO_PtrMemD || e->getOpcode() == BO_PtrMemI)
+    return emitPointerToDataMemberBinaryExpr(e);
 
   assert(e->getOpcode() == BO_Assign && "unexpected binary l-value");
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index 25ce1ba26da09..187e07f4c4188 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -1333,13 +1333,11 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
   }
 
   mlir::Value VisitBinPtrMemD(const BinaryOperator *e) {
-    cgf.cgm.errorNYI(e->getSourceRange(), "ScalarExprEmitter: ptr mem d");
-    return {};
+    return emitLoadOfLValue(e);
   }
 
   mlir::Value VisitBinPtrMemI(const BinaryOperator *e) {
-    cgf.cgm.errorNYI(e->getSourceRange(), "ScalarExprEmitter: ptr mem i");
-    return {};
+    return emitLoadOfLValue(e);
   }
 
   // Other Operators.
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 0df812bcfb94e..59999c5b6b73d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1508,6 +1508,10 @@ class CIRGenFunction : public CIRGenTypeCache {
   RValue emitCXXMemberCallExpr(const clang::CXXMemberCallExpr *e,
                                ReturnValueSlot returnValue);
 
+  Address emitCXXMemberDataPointerAddress(
+      const Expr *e, Address base, mlir::Value memberPtr,
+      const MemberPointerType *memberPtrType, LValueBaseInfo *baseInfo);
+
   RValue emitCXXMemberOrOperatorCall(
       const clang::CXXMethodDecl *md, const CIRGenCallee &callee,
       ReturnValueSlot returnValue, mlir::Value thisPtr,
@@ -1690,6 +1694,8 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   mlir::Value emitOpOnBoolExpr(mlir::Location loc, const clang::Expr *cond);
 
+  LValue emitPointerToDataMemberBinaryExpr(const BinaryOperator *e);
+
   mlir::LogicalResult emitLabel(const clang::LabelDecl &d);
   mlir::LogicalResult emitLabelStmt(const clang::LabelStmt &s);
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 38a2cecbb8617..e0a7045f2f7fe 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -2501,6 +2501,21 @@ LogicalResult cir::CopyOp::verify() {
   return mlir::success();
 }
 
+//===----------------------------------------------------------------------===//
+// GetRuntimeMemberOp Definitions
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::GetRuntimeMemberOp::verify() {
+  auto recordTy = mlir::cast<RecordType>(getAddr().getType().getPointee());
+  cir::DataMemberType memberPtrTy = getMember().getType();
+
+  if (recordTy != memberPtrTy.getClassTy())
+    return emitError() << "record type does not match the member pointer type";
+  if (getType().getPointee() != memberPtrTy.getMemberTy())
+    return emitError() << "result type does not match the member pointer type";
+  return mlir::success();
+}
+
 //===----------------------------------------------------------------------===//
 // GetMemberOp Definitions
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h
index 003cd78eb3f26..90f0ac3478f9d 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h
@@ -15,6 +15,7 @@
 #define CLANG_LIB_CIR_DIALECT_TRANSFORMS_TARGETLOWERING_CIRCXXABI_H
 
 #include "mlir/Transforms/DialectConversion.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/Dialect/IR/CIRTypes.h"
 
 namespace cir {
@@ -45,6 +46,13 @@ class CIRCXXABI {
   lowerDataMemberConstant(cir::DataMemberAttr attr,
                           const mlir::DataLayout &layout,
                           const mlir::TypeConverter &typeConverter) const = 0;
+
+  /// Lower the given cir.get_runtime_member op to a sequence of more
+  /// "primitive" CIR operations that act on the ABI types.
+  virtual mlir::Operation *
+  lowerGetRuntimeMember(cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy,
+                        mlir::Value loweredAddr, mlir::Value loweredMember,
+                        mlir::OpBuilder &builder) const = 0;
 };
 
 /// Creates an Itanium-family ABI.
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
index 7089990343dc0..ce4b0c7e92d09 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
@@ -42,6 +42,11 @@ class LowerItaniumCXXABI : public CIRCXXABI {
   mlir::TypedAttr lowerDataMemberConstant(
       cir::DataMemberAttr attr, const mlir::DataLayout &layout,
       const mlir::TypeConverter &typeConverter) const override;
+
+  mlir::Operation *
+  lowerGetRuntimeMember(cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy,
+                        mlir::Value loweredAddr, mlir::Value loweredMember,
+                        mlir::OpBuilder &builder) const override;
 };
 
 } // namespace
@@ -87,4 +92,20 @@ mlir::TypedAttr LowerItaniumCXXABI::lowerDataMemberConstant(
   return cir::IntAttr::get(abiTy, memberOffset);
 }
 
+mlir::Operation *LowerItaniumCXXABI::lowerGetRuntimeMember(
+    cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy,
+    mlir::Value loweredAddr, mlir::Value loweredMember,
+    mlir::OpBuilder &builder) const {
+  auto byteTy = cir::IntType::get(op.getContext(), 8, true);
+  auto bytePtrTy = cir::PointerType::get(
+      byteTy,
+      mlir::cast<cir::PointerType>(op.getAddr().getType()).getAddrSpace());
+  auto objectBytesPtr = cir::CastOp::create(
+      builder, op.getLoc(), bytePtrTy, cir::CastKind::bitcast, op.getAddr());
+  auto memberBytesPtr = cir::PtrStrideOp::create(
+      builder, op.getLoc(), bytePtrTy, objectBytesPtr, loweredMember);
+  return cir::CastOp::create(builder, op.getLoc(), op.getType(),
+                             cir::CastKind::bitcast, memberBytesPtr);
+}
+
 } // namespace cir
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 88ca8033b48ea..9ab04f0fd9368 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -3223,6 +3223,17 @@ mlir::LogicalResult CIRToLLVMGetMemberOpLowering::matchAndRewrite(
   }
 }
 
+mlir::LogicalResult CIRToLLVMGetRuntimeMemberOpLowering::matchAndRewrite(
+    cir::GetRuntimeMemberOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  assert(lowerMod && "lowering module is not available");
+  mlir::Type llvmResTy = getTypeConverter()->convertType(op.getType());
+  mlir::Operation *llvmOp = lowerMod->getCXXABI().lowerGetRuntimeMember(
+      op, llvmResTy, adaptor.getAddr(), adaptor.getMember(), rewriter);
+  rewriter.replaceOp(op, llvmOp);
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRToLLVMUnreachableOpLowering::matchAndRewrite(
     cir::UnreachableOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/clang/test/CIR/CodeGen/pointer-to-data-member.cpp b/clang/test/CIR/CodeGen/pointer-to-data-member.cpp
index b116d21f01170..14d1befeed67f 100644
--- a/clang/test/CIR/CodeGen/pointer-to-data-member.cpp
+++ b/clang/test/CIR/CodeGen/pointer-to-data-member.cpp
@@ -30,3 +30,169 @@ auto test1() -> int Point::* {
 
 // OGCG: define {{.*}} i64 @_Z5test1v()
 // OGCG:   ret i64 4
+
+int test2(const Point &pt, int Point::*member) {
+  return pt.*member;
+}
+
+// CIR:       cir.func {{.*}} @_Z5test2RK5PointMS_i(
+// CIR-SAME:        %[[PT_ARG:.*]]: !cir.ptr<!rec_Point>
+// CIR-SAME:        %[[MEMBER_ARG:.*]]: !cir.data_member<!s32i in !rec_Point>
+// CIR:         %[[PT_ADDR:.*]] = cir.alloca {{.*}} ["pt", init, const]
+// CIR:         %[[MEMBER_ADDR:.*]] = cir.alloca {{.*}} ["member", init]
+// CIR:         %[[RETVAL_ADDR:.*]] = cir.alloca {{.*}} ["__retval"]
+// CIR:         cir.store %[[PT_ARG]], %[[PT_ADDR]]
+// CIR:         cir.store %[[MEMBER_ARG]], %[[MEMBER_ADDR]]
+// CIR:         %[[PT:.*]] = cir.load %[[PT_ADDR]]
+// CIR:         %[[MEMBER:.*]] = cir.load{{.*}} %[[MEMBER_ADDR]]
+// CIR:         %[[RT_MEMBER:.*]] = cir.get_runtime_member %[[PT]][%[[MEMBER]] : !cir.data_member<!s32i in !rec_Point>] : !cir.ptr<!rec_Point> -> !cir.ptr<!s32i>
+// CIR:         %[[VAL:.*]] = cir.load{{.*}} %[[RT_MEMBER]]
+// CIR:         cir.store %[[VAL]], %[[RETVAL_ADDR]]
+// CIR:         %[[RET:.*]] = cir.load{{.*}} %[[RETVAL_ADDR]]
+// CIR:         cir.return %[[RET]]
+
+// LLVM: define {{.*}} i32 @_Z5test2RK5PointMS_i(ptr %[[PT_ARG:.*]], i64 %[[MEMBER_ARG:.*]])
+// LLVM:   %[[PT_ADDR:.*]] = alloca ptr
+// LLVM:   %[[MEMBER_ADDR:.*]] = alloca i64
+// LLVM:   %[[RETVAL_ADDR:.*]] = alloca i32
+// LLVM:   store ptr %[[PT_ARG]], ptr %[[PT_ADDR]]
+// LLVM:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[PT:.*]] = load ptr, ptr %[[PT_ADDR]]
+// LLVM:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[RT_MEMBER:.*]] = getelementptr i8, ptr %[[PT]], i64 %[[MEMBER]]
+// LLVM:   %[[VAL:.*]] = load i32, ptr %[[RT_MEMBER]]
+// LLVM:   store i32 %[[VAL]], ptr %[[RETVAL_ADDR]]
+// LLVM:   %[[RET:.*]] = load i32, ptr %[[RETVAL_ADDR]]
+// LLVM:   ret i32 %[[RET]]
+
+// OGCG: define {{.*}} i32 @_Z5test2RK5PointMS_i(ptr {{.*}} %[[PT_ARG:.*]], i64 %[[MEMBER_ARG:.*]])
+// OGCG:   %[[PT_ADDR:.*]] = alloca ptr
+// OGCG:   %[[MEMBER_ADDR:.*]] = alloca i64
+// OGCG:   store ptr %[[PT_ARG]], ptr %[[PT_ADDR]]
+// OGCG:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// OGCG:   %[[PT:.*]] = load ptr, ptr %[[PT_ADDR]]
+// OGCG:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// OGCG:   %[[RT_MEMBER:.*]] = getelementptr inbounds i8, ptr %[[PT]], i64 %[[MEMBER]]
+// OGCG:   %[[RET:.*]] = load i32, ptr %[[RT_MEMBER]]
+// OGCG:   ret i32 %[[RET]]
+
+int test3(const Point *pt, int Point::*member) {
+  return pt->*member;
+}
+
+// CIR:       cir.func {{.*}} @_Z5test3PK5PointMS_i(
+// CIR-SAME:        %[[PT_ARG:.*]]: !cir.ptr<!rec_Point>
+// CIR-SAME:        %[[MEMBER_ARG:.*]]: !cir.data_member<!s32i in !rec_Point>
+// CIR:         %[[PT_ADDR:.*]] = cir.alloca {{.*}} ["pt", init]
+// CIR:         %[[MEMBER_ADDR:.*]] = cir.alloca {{.*}} ["member", init]
+// CIR:         %[[RETVAL_ADDR:.*]] = cir.alloca {{.*}} ["__retval"]
+// CIR:         cir.store %[[PT_ARG]], %[[PT_ADDR]]
+// CIR:         cir.store %[[MEMBER_ARG]], %[[MEMBER_ADDR]]
+// CIR:         %[[PT:.*]] = cir.load{{.*}} %[[PT_ADDR]]
+// CIR:         %[[MEMBER:.*]] = cir.load{{.*}} %[[MEMBER_ADDR]]
+// CIR:         %[[RT_MEMBER:.*]] = cir.get_runtime_member %[[PT]][%[[MEMBER]] : !cir.data_member<!s32i in !rec_Point>] : !cir.ptr<!rec_Point> -> !cir.ptr<!s32i>
+// CIR:         %[[VAL:.*]] = cir.load{{.*}} %[[RT_MEMBER]]
+// CIR:         cir.store %[[VAL]], %[[RETVAL_ADDR]]
+// CIR:         %[[RET:.*]] = cir.load{{.*}} %[[RETVAL_ADDR]]
+// CIR:         cir.return %[[RET]]
+
+// LLVM: define {{.*}} i32 @_Z5test3PK5PointMS_i(ptr %[[PT_ARG:.*]], i64 %[[MEMBER_ARG:.*]])
+// LLVM:   %[[PT_ADDR:.*]] = alloca ptr
+// LLVM:   %[[MEMBER_ADDR:.*]] = alloca i64
+// LLVM:   %[[RETVAL_ADDR:.*]] = alloca i32
+// LLVM:   store ptr %[[PT_ARG]], ptr %[[PT_ADDR]]
+// LLVM:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[PT:.*]] = load ptr, ptr %[[PT_ADDR]]
+// LLVM:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[RT_MEMBER:.*]] = getelementptr i8, ptr %[[PT]], i64 %[[MEMBER]]
+// LLVM:   %[[VAL:.*]] = load i32, ptr %[[RT_MEMBER]]
+// LLVM:   store i32 %[[VAL]], ptr %[[RETVAL_ADDR]]
+// LLVM:   %[[RET:.*]] = load i32, ptr %[[RETVAL_ADDR]]
+// LLVM:   ret i32 %[[RET]]
+
+// OGCG: define {{.*}} i32 @_Z5test3PK5PointMS_i(ptr {{.*}} %[[PT_ARG:.*]], i64 %[[MEMBER_ARG:.*]])
+// OGCG:   %[[PT_ADDR:.*]] = alloca ptr
+// OGCG:   %[[MEMBER_ADDR:.*]] = alloca i64
+// OGCG:   store ptr %[[PT_ARG]], ptr %[[PT_ADDR]]
+// OGCG:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// OGCG:   %[[PT:.*]] = load ptr, ptr %[[PT_ADDR]]
+// OGCG:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// OGCG:   %[[RT_MEMBER:.*]] = getelementptr inbounds i8, ptr %[[PT]], i64 %[[MEMBER]]
+// OGCG:   %[[RET:.*]] = load i32, ptr %[[RT_MEMBER]]
+// OGCG:   ret i32 %[[RET]]
+
+struct Incomplete;
+
+auto test4(int Incomplete::*member) -> int Incomplete::* {
+  return member;
+}
+
+// CIR:       cir.func {{.*}} @_Z5test4M10Incompletei(
+// CIR-SAME:        %[[MEMBER_ARG:.*]]: !cir.data_member<!s32i in !rec_Incomplete>
+// CIR:         %[[MEMBER_ADDR:.*]] = cir.alloca {{.*}} ["member", init]
+// CIR:         %[[RETVAL_ADDR:.*]] = cir.alloca {{.*}} ["__retval"]
+// CIR:         cir.store %[[MEMBER_ARG]], %[[MEMBER_ADDR]]
+// CIR:         %[[MEMBER:.*]] = cir.load{{.*}} %[[MEMBER_ADDR]]
+// CIR:         cir.store %[[MEMBER]], %[[RETVAL_ADDR]]
+// CIR:         %[[RET:.*]] = cir.load{{.*}} %[[RETVAL_ADDR]]
+// CIR:         cir.return %[[RET]]
+
+// LLVM: define {{.*}} i64 @_Z5test4M10Incompletei(i64 %[[MEMBER_ARG:.*]])
+// LLVM:   %[[MEMBER_ADDR:.*]] = alloca i64
+// LLVM:   %[[RETVAL_ADDR:.*]] = alloca i64
+// LLVM:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// LLVM:   store i64 %[[MEMBER]], ptr %[[RETVAL_ADDR]]
+// LLVM:   %[[RET:.*]] = load i64, ptr %[[RETVAL_ADDR]]
+// LLVM:   ret i64...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Dec 9, 2025

@llvm/pr-subscribers-clangir

Author: Andy Kaylor (andykaylor)

Changes

This adds support for a CIR operation to represent runtime data member access.


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

12 Files Affected:

  • (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+57)
  • (modified) clang/lib/CIR/CodeGen/CIRGenBuilder.h (+13)
  • (modified) clang/lib/CIR/CodeGen/CIRGenClass.cpp (+19)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+25-4)
  • (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+2-4)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+6)
  • (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+15)
  • (modified) clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h (+8)
  • (modified) clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp (+21)
  • (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+11)
  • (modified) clang/test/CIR/CodeGen/pointer-to-data-member.cpp (+166)
  • (modified) clang/test/CIR/IR/invalid-data-member.cir (+31)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 9bd24cf0bcf27..8ba23ccda58d0 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3489,6 +3489,63 @@ def CIR_ArrayDtor : CIR_ArrayInitDestroy<"array.dtor"> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// GetRuntimeMemberOp
+//===----------------------------------------------------------------------===//
+
+def CIR_GetRuntimeMemberOp : CIR_Op<"get_runtime_member"> {
+  let summary = "Get the address of a member of a record";
+  let description = [{
+    The `cir.get_runtime_member` operation gets the address of a member from
+    the input record. The target member is given by a value of type
+    `!cir.data_member` (i.e. a pointer-to-data-member value).
+
+    This operation differs from `cir.get_member` in when the target member can
+    be determined. For the `cir.get_member` operation, the target member is
+    specified as a constant index so the member it returns access to is known
+    when the operation is constructed. For the `cir.get_runtime_member`
+    operation, the target member is given through a pointer-to-data-member
+    value which is unknown until the program being compiled is executed. In
+    other words, `cir.get_member` represents a normal member access through the
+    `.` operator in C/C++:
+
+    ```cpp
+    struct Foo { int x; };
+    Foo f;
+    (void)f.x;  // cir.get_member
+    ```
+
+    And `cir.get_runtime_member` represents a member access through the `.*` or
+    the `->*` operator in C++:
+
+    ```cpp
+    struct Foo { int x; }
+    Foo f;
+    Foo *p;
+    int Foo::*member;
+
+    (void)f.*member;   // cir.get_runtime_member
+    (void)p->*member;  // cir.get_runtime_member
+    ```
+
+    This operation expects a pointer to the base record as well as the pointer
+    to the target member.
+  }];
+
+  let arguments = (ins
+    Arg<CIR_PtrToRecordType, "address of the record object", [MemRead]>:$addr,
+    Arg<CIR_DataMemberType, "pointer to the target member">:$member);
+
+  let results = (outs Res<CIR_PointerType, "">:$result);
+
+  let assemblyFormat = [{
+    $addr `[` $member `:` qualified(type($member)) `]` attr-dict
+    `:` qualified(type($addr)) `->` qualified(type($result))
+  }];
+
+  let hasVerifier = 1;
+}
+
 //===----------------------------------------------------------------------===//
 // VecCreate
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index bf13eeeaea60a..f3716fb02c51b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -526,6 +526,19 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
                    addr.getAlignment()};
   }
 
+  cir::GetRuntimeMemberOp createGetIndirectMember(mlir::Location loc,
+                                                  mlir::Value objectPtr,
+                                                  mlir::Value memberPtr) {
+    auto memberPtrTy = mlir::cast<cir::DataMemberType>(memberPtr.getType());
+
+    // TODO(cir): consider address space.
+    assert(!cir::MissingFeatures::addressSpace());
+    cir::PointerType resultTy = getPointerTo(memberPtrTy.getMemberTy());
+
+    return cir::GetRuntimeMemberOp::create(*this, loc, resultTy, objectPtr,
+                                           memberPtr);
+  }
+
   /// Create a cir.ptr_stride operation to get access to an array element.
   /// \p idx is the index of the element to access, \p shouldDecay is true if
   /// the result should decay to a pointer to the element type.
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 2a26e38bb214d..3ab3be3706205 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -584,6 +584,25 @@ void CIRGenFunction::emitInitializerForField(FieldDecl *field, LValue lhs,
   assert(!cir::MissingFeatures::requiresCleanups());
 }
 
+Address CIRGenFunction::emitCXXMemberDataPointerAddress(
+    const Expr *e, Address base, mlir::Value memberPtr,
+    const MemberPointerType *memberPtrType, LValueBaseInfo *baseInfo) {
+  assert(!cir::MissingFeatures::cxxABI());
+
+  cir::GetRuntimeMemberOp op = builder.createGetIndirectMember(
+      getLoc(e->getSourceRange()), base.getPointer(), memberPtr);
+
+  QualType memberType = memberPtrType->getPointeeType();
+  assert(!cir::MissingFeatures::opTBAA());
+  CharUnits memberAlign = cgm.getNaturalTypeAlignment(memberType, baseInfo);
+  memberAlign = cgm.getDynamicOffsetAlignment(
+      base.getAlignment(), memberPtrType->getMostRecentCXXRecordDecl(),
+      memberAlign);
+
+  return Address(op, convertTypeForMem(memberPtrType->getPointeeType()),
+                 memberAlign);
+}
+
 CharUnits
 CIRGenModule::getDynamicOffsetAlignment(CharUnits actualBaseAlign,
                                         const CXXRecordDecl *baseDecl,
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 5d509e37f4621..87ff73a074240 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -684,6 +684,29 @@ RValue CIRGenFunction::emitLoadOfExtVectorElementLValue(LValue lv) {
   return RValue::get(resultVec);
 }
 
+LValue
+CIRGenFunction::emitPointerToDataMemberBinaryExpr(const BinaryOperator *e) {
+  assert((e->getOpcode() == BO_PtrMemD || e->getOpcode() == BO_PtrMemI) &&
+         "unexpected binary operator opcode");
+
+  Address baseAddr = Address::invalid();
+  if (e->getOpcode() == BO_PtrMemD)
+    baseAddr = emitLValue(e->getLHS()).getAddress();
+  else
+    baseAddr = emitPointerWithAlignment(e->getLHS());
+
+  const auto *memberPtrTy = e->getRHS()->getType()->castAs<MemberPointerType>();
+
+  mlir::Value memberPtr = emitScalarExpr(e->getRHS());
+
+  LValueBaseInfo baseInfo;
+  assert(!cir::MissingFeatures::opTBAA());
+  Address memberAddr = emitCXXMemberDataPointerAddress(e, baseAddr, memberPtr,
+                                                       memberPtrTy, &baseInfo);
+
+  return makeAddrLValue(memberAddr, memberPtrTy->getPointeeType(), baseInfo);
+}
+
 /// Generates lvalue for partial ext_vector access.
 Address CIRGenFunction::emitExtVectorElementLValue(LValue lv,
                                                    mlir::Location loc) {
@@ -1763,10 +1786,8 @@ LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) {
     return emitLValue(e->getRHS());
   }
 
-  if (e->getOpcode() == BO_PtrMemD || e->getOpcode() == BO_PtrMemI) {
-    cgm.errorNYI(e->getSourceRange(), "member pointers");
-    return {};
-  }
+  if (e->getOpcode() == BO_PtrMemD || e->getOpcode() == BO_PtrMemI)
+    return emitPointerToDataMemberBinaryExpr(e);
 
   assert(e->getOpcode() == BO_Assign && "unexpected binary l-value");
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index 25ce1ba26da09..187e07f4c4188 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -1333,13 +1333,11 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
   }
 
   mlir::Value VisitBinPtrMemD(const BinaryOperator *e) {
-    cgf.cgm.errorNYI(e->getSourceRange(), "ScalarExprEmitter: ptr mem d");
-    return {};
+    return emitLoadOfLValue(e);
   }
 
   mlir::Value VisitBinPtrMemI(const BinaryOperator *e) {
-    cgf.cgm.errorNYI(e->getSourceRange(), "ScalarExprEmitter: ptr mem i");
-    return {};
+    return emitLoadOfLValue(e);
   }
 
   // Other Operators.
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 0df812bcfb94e..59999c5b6b73d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1508,6 +1508,10 @@ class CIRGenFunction : public CIRGenTypeCache {
   RValue emitCXXMemberCallExpr(const clang::CXXMemberCallExpr *e,
                                ReturnValueSlot returnValue);
 
+  Address emitCXXMemberDataPointerAddress(
+      const Expr *e, Address base, mlir::Value memberPtr,
+      const MemberPointerType *memberPtrType, LValueBaseInfo *baseInfo);
+
   RValue emitCXXMemberOrOperatorCall(
       const clang::CXXMethodDecl *md, const CIRGenCallee &callee,
       ReturnValueSlot returnValue, mlir::Value thisPtr,
@@ -1690,6 +1694,8 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   mlir::Value emitOpOnBoolExpr(mlir::Location loc, const clang::Expr *cond);
 
+  LValue emitPointerToDataMemberBinaryExpr(const BinaryOperator *e);
+
   mlir::LogicalResult emitLabel(const clang::LabelDecl &d);
   mlir::LogicalResult emitLabelStmt(const clang::LabelStmt &s);
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 38a2cecbb8617..e0a7045f2f7fe 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -2501,6 +2501,21 @@ LogicalResult cir::CopyOp::verify() {
   return mlir::success();
 }
 
+//===----------------------------------------------------------------------===//
+// GetRuntimeMemberOp Definitions
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::GetRuntimeMemberOp::verify() {
+  auto recordTy = mlir::cast<RecordType>(getAddr().getType().getPointee());
+  cir::DataMemberType memberPtrTy = getMember().getType();
+
+  if (recordTy != memberPtrTy.getClassTy())
+    return emitError() << "record type does not match the member pointer type";
+  if (getType().getPointee() != memberPtrTy.getMemberTy())
+    return emitError() << "result type does not match the member pointer type";
+  return mlir::success();
+}
+
 //===----------------------------------------------------------------------===//
 // GetMemberOp Definitions
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h
index 003cd78eb3f26..90f0ac3478f9d 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h
@@ -15,6 +15,7 @@
 #define CLANG_LIB_CIR_DIALECT_TRANSFORMS_TARGETLOWERING_CIRCXXABI_H
 
 #include "mlir/Transforms/DialectConversion.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/Dialect/IR/CIRTypes.h"
 
 namespace cir {
@@ -45,6 +46,13 @@ class CIRCXXABI {
   lowerDataMemberConstant(cir::DataMemberAttr attr,
                           const mlir::DataLayout &layout,
                           const mlir::TypeConverter &typeConverter) const = 0;
+
+  /// Lower the given cir.get_runtime_member op to a sequence of more
+  /// "primitive" CIR operations that act on the ABI types.
+  virtual mlir::Operation *
+  lowerGetRuntimeMember(cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy,
+                        mlir::Value loweredAddr, mlir::Value loweredMember,
+                        mlir::OpBuilder &builder) const = 0;
 };
 
 /// Creates an Itanium-family ABI.
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
index 7089990343dc0..ce4b0c7e92d09 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
@@ -42,6 +42,11 @@ class LowerItaniumCXXABI : public CIRCXXABI {
   mlir::TypedAttr lowerDataMemberConstant(
       cir::DataMemberAttr attr, const mlir::DataLayout &layout,
       const mlir::TypeConverter &typeConverter) const override;
+
+  mlir::Operation *
+  lowerGetRuntimeMember(cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy,
+                        mlir::Value loweredAddr, mlir::Value loweredMember,
+                        mlir::OpBuilder &builder) const override;
 };
 
 } // namespace
@@ -87,4 +92,20 @@ mlir::TypedAttr LowerItaniumCXXABI::lowerDataMemberConstant(
   return cir::IntAttr::get(abiTy, memberOffset);
 }
 
+mlir::Operation *LowerItaniumCXXABI::lowerGetRuntimeMember(
+    cir::GetRuntimeMemberOp op, mlir::Type loweredResultTy,
+    mlir::Value loweredAddr, mlir::Value loweredMember,
+    mlir::OpBuilder &builder) const {
+  auto byteTy = cir::IntType::get(op.getContext(), 8, true);
+  auto bytePtrTy = cir::PointerType::get(
+      byteTy,
+      mlir::cast<cir::PointerType>(op.getAddr().getType()).getAddrSpace());
+  auto objectBytesPtr = cir::CastOp::create(
+      builder, op.getLoc(), bytePtrTy, cir::CastKind::bitcast, op.getAddr());
+  auto memberBytesPtr = cir::PtrStrideOp::create(
+      builder, op.getLoc(), bytePtrTy, objectBytesPtr, loweredMember);
+  return cir::CastOp::create(builder, op.getLoc(), op.getType(),
+                             cir::CastKind::bitcast, memberBytesPtr);
+}
+
 } // namespace cir
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 88ca8033b48ea..9ab04f0fd9368 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -3223,6 +3223,17 @@ mlir::LogicalResult CIRToLLVMGetMemberOpLowering::matchAndRewrite(
   }
 }
 
+mlir::LogicalResult CIRToLLVMGetRuntimeMemberOpLowering::matchAndRewrite(
+    cir::GetRuntimeMemberOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  assert(lowerMod && "lowering module is not available");
+  mlir::Type llvmResTy = getTypeConverter()->convertType(op.getType());
+  mlir::Operation *llvmOp = lowerMod->getCXXABI().lowerGetRuntimeMember(
+      op, llvmResTy, adaptor.getAddr(), adaptor.getMember(), rewriter);
+  rewriter.replaceOp(op, llvmOp);
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRToLLVMUnreachableOpLowering::matchAndRewrite(
     cir::UnreachableOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/clang/test/CIR/CodeGen/pointer-to-data-member.cpp b/clang/test/CIR/CodeGen/pointer-to-data-member.cpp
index b116d21f01170..14d1befeed67f 100644
--- a/clang/test/CIR/CodeGen/pointer-to-data-member.cpp
+++ b/clang/test/CIR/CodeGen/pointer-to-data-member.cpp
@@ -30,3 +30,169 @@ auto test1() -> int Point::* {
 
 // OGCG: define {{.*}} i64 @_Z5test1v()
 // OGCG:   ret i64 4
+
+int test2(const Point &pt, int Point::*member) {
+  return pt.*member;
+}
+
+// CIR:       cir.func {{.*}} @_Z5test2RK5PointMS_i(
+// CIR-SAME:        %[[PT_ARG:.*]]: !cir.ptr<!rec_Point>
+// CIR-SAME:        %[[MEMBER_ARG:.*]]: !cir.data_member<!s32i in !rec_Point>
+// CIR:         %[[PT_ADDR:.*]] = cir.alloca {{.*}} ["pt", init, const]
+// CIR:         %[[MEMBER_ADDR:.*]] = cir.alloca {{.*}} ["member", init]
+// CIR:         %[[RETVAL_ADDR:.*]] = cir.alloca {{.*}} ["__retval"]
+// CIR:         cir.store %[[PT_ARG]], %[[PT_ADDR]]
+// CIR:         cir.store %[[MEMBER_ARG]], %[[MEMBER_ADDR]]
+// CIR:         %[[PT:.*]] = cir.load %[[PT_ADDR]]
+// CIR:         %[[MEMBER:.*]] = cir.load{{.*}} %[[MEMBER_ADDR]]
+// CIR:         %[[RT_MEMBER:.*]] = cir.get_runtime_member %[[PT]][%[[MEMBER]] : !cir.data_member<!s32i in !rec_Point>] : !cir.ptr<!rec_Point> -> !cir.ptr<!s32i>
+// CIR:         %[[VAL:.*]] = cir.load{{.*}} %[[RT_MEMBER]]
+// CIR:         cir.store %[[VAL]], %[[RETVAL_ADDR]]
+// CIR:         %[[RET:.*]] = cir.load{{.*}} %[[RETVAL_ADDR]]
+// CIR:         cir.return %[[RET]]
+
+// LLVM: define {{.*}} i32 @_Z5test2RK5PointMS_i(ptr %[[PT_ARG:.*]], i64 %[[MEMBER_ARG:.*]])
+// LLVM:   %[[PT_ADDR:.*]] = alloca ptr
+// LLVM:   %[[MEMBER_ADDR:.*]] = alloca i64
+// LLVM:   %[[RETVAL_ADDR:.*]] = alloca i32
+// LLVM:   store ptr %[[PT_ARG]], ptr %[[PT_ADDR]]
+// LLVM:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[PT:.*]] = load ptr, ptr %[[PT_ADDR]]
+// LLVM:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[RT_MEMBER:.*]] = getelementptr i8, ptr %[[PT]], i64 %[[MEMBER]]
+// LLVM:   %[[VAL:.*]] = load i32, ptr %[[RT_MEMBER]]
+// LLVM:   store i32 %[[VAL]], ptr %[[RETVAL_ADDR]]
+// LLVM:   %[[RET:.*]] = load i32, ptr %[[RETVAL_ADDR]]
+// LLVM:   ret i32 %[[RET]]
+
+// OGCG: define {{.*}} i32 @_Z5test2RK5PointMS_i(ptr {{.*}} %[[PT_ARG:.*]], i64 %[[MEMBER_ARG:.*]])
+// OGCG:   %[[PT_ADDR:.*]] = alloca ptr
+// OGCG:   %[[MEMBER_ADDR:.*]] = alloca i64
+// OGCG:   store ptr %[[PT_ARG]], ptr %[[PT_ADDR]]
+// OGCG:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// OGCG:   %[[PT:.*]] = load ptr, ptr %[[PT_ADDR]]
+// OGCG:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// OGCG:   %[[RT_MEMBER:.*]] = getelementptr inbounds i8, ptr %[[PT]], i64 %[[MEMBER]]
+// OGCG:   %[[RET:.*]] = load i32, ptr %[[RT_MEMBER]]
+// OGCG:   ret i32 %[[RET]]
+
+int test3(const Point *pt, int Point::*member) {
+  return pt->*member;
+}
+
+// CIR:       cir.func {{.*}} @_Z5test3PK5PointMS_i(
+// CIR-SAME:        %[[PT_ARG:.*]]: !cir.ptr<!rec_Point>
+// CIR-SAME:        %[[MEMBER_ARG:.*]]: !cir.data_member<!s32i in !rec_Point>
+// CIR:         %[[PT_ADDR:.*]] = cir.alloca {{.*}} ["pt", init]
+// CIR:         %[[MEMBER_ADDR:.*]] = cir.alloca {{.*}} ["member", init]
+// CIR:         %[[RETVAL_ADDR:.*]] = cir.alloca {{.*}} ["__retval"]
+// CIR:         cir.store %[[PT_ARG]], %[[PT_ADDR]]
+// CIR:         cir.store %[[MEMBER_ARG]], %[[MEMBER_ADDR]]
+// CIR:         %[[PT:.*]] = cir.load{{.*}} %[[PT_ADDR]]
+// CIR:         %[[MEMBER:.*]] = cir.load{{.*}} %[[MEMBER_ADDR]]
+// CIR:         %[[RT_MEMBER:.*]] = cir.get_runtime_member %[[PT]][%[[MEMBER]] : !cir.data_member<!s32i in !rec_Point>] : !cir.ptr<!rec_Point> -> !cir.ptr<!s32i>
+// CIR:         %[[VAL:.*]] = cir.load{{.*}} %[[RT_MEMBER]]
+// CIR:         cir.store %[[VAL]], %[[RETVAL_ADDR]]
+// CIR:         %[[RET:.*]] = cir.load{{.*}} %[[RETVAL_ADDR]]
+// CIR:         cir.return %[[RET]]
+
+// LLVM: define {{.*}} i32 @_Z5test3PK5PointMS_i(ptr %[[PT_ARG:.*]], i64 %[[MEMBER_ARG:.*]])
+// LLVM:   %[[PT_ADDR:.*]] = alloca ptr
+// LLVM:   %[[MEMBER_ADDR:.*]] = alloca i64
+// LLVM:   %[[RETVAL_ADDR:.*]] = alloca i32
+// LLVM:   store ptr %[[PT_ARG]], ptr %[[PT_ADDR]]
+// LLVM:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[PT:.*]] = load ptr, ptr %[[PT_ADDR]]
+// LLVM:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[RT_MEMBER:.*]] = getelementptr i8, ptr %[[PT]], i64 %[[MEMBER]]
+// LLVM:   %[[VAL:.*]] = load i32, ptr %[[RT_MEMBER]]
+// LLVM:   store i32 %[[VAL]], ptr %[[RETVAL_ADDR]]
+// LLVM:   %[[RET:.*]] = load i32, ptr %[[RETVAL_ADDR]]
+// LLVM:   ret i32 %[[RET]]
+
+// OGCG: define {{.*}} i32 @_Z5test3PK5PointMS_i(ptr {{.*}} %[[PT_ARG:.*]], i64 %[[MEMBER_ARG:.*]])
+// OGCG:   %[[PT_ADDR:.*]] = alloca ptr
+// OGCG:   %[[MEMBER_ADDR:.*]] = alloca i64
+// OGCG:   store ptr %[[PT_ARG]], ptr %[[PT_ADDR]]
+// OGCG:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// OGCG:   %[[PT:.*]] = load ptr, ptr %[[PT_ADDR]]
+// OGCG:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// OGCG:   %[[RT_MEMBER:.*]] = getelementptr inbounds i8, ptr %[[PT]], i64 %[[MEMBER]]
+// OGCG:   %[[RET:.*]] = load i32, ptr %[[RT_MEMBER]]
+// OGCG:   ret i32 %[[RET]]
+
+struct Incomplete;
+
+auto test4(int Incomplete::*member) -> int Incomplete::* {
+  return member;
+}
+
+// CIR:       cir.func {{.*}} @_Z5test4M10Incompletei(
+// CIR-SAME:        %[[MEMBER_ARG:.*]]: !cir.data_member<!s32i in !rec_Incomplete>
+// CIR:         %[[MEMBER_ADDR:.*]] = cir.alloca {{.*}} ["member", init]
+// CIR:         %[[RETVAL_ADDR:.*]] = cir.alloca {{.*}} ["__retval"]
+// CIR:         cir.store %[[MEMBER_ARG]], %[[MEMBER_ADDR]]
+// CIR:         %[[MEMBER:.*]] = cir.load{{.*}} %[[MEMBER_ADDR]]
+// CIR:         cir.store %[[MEMBER]], %[[RETVAL_ADDR]]
+// CIR:         %[[RET:.*]] = cir.load{{.*}} %[[RETVAL_ADDR]]
+// CIR:         cir.return %[[RET]]
+
+// LLVM: define {{.*}} i64 @_Z5test4M10Incompletei(i64 %[[MEMBER_ARG:.*]])
+// LLVM:   %[[MEMBER_ADDR:.*]] = alloca i64
+// LLVM:   %[[RETVAL_ADDR:.*]] = alloca i64
+// LLVM:   store i64 %[[MEMBER_ARG]], ptr %[[MEMBER_ADDR]]
+// LLVM:   %[[MEMBER:.*]] = load i64, ptr %[[MEMBER_ADDR]]
+// LLVM:   store i64 %[[MEMBER]], ptr %[[RETVAL_ADDR]]
+// LLVM:   %[[RET:.*]] = load i64, ptr %[[RETVAL_ADDR]]
+// LLVM:   ret i64...
[truncated]

Copy link
Member

@Lancern Lancern left a comment

Choose a reason for hiding this comment

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

LGTM

@andykaylor andykaylor merged commit 4c21e46 into llvm:main Dec 11, 2025
13 checks passed
@andykaylor andykaylor deleted the cir-runtime-data-member branch December 11, 2025 18:19
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.

3 participants