[clang] [CIR] Fix spurious MemRead on pure pointer-arithmetic ops (PR #185154)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 6 22:59:01 PST 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Henrich Lauko (xlauko)
<details>
<summary>Changes</summary>
Remove incorrect [MemRead] annotations from seven CIR ops that only perform pointer arithmetic, and add the Pure trait where missing.
- VTableGetVPtrOp computes the address of the vptr slot within an object. Since the vptr is always at offset zero, this is a bitcast — confirmed by the lowering which does replaceOp(op, srcVal). No memory access.
- VTableGetVirtualFnAddrOp takes an already-loaded !cir.vptr value and an index, computes the address of the nth vtable entry. Lowers to a GEPOp. No memory access.
- VTableGetTypeInfoOp takes an already-loaded !cir.vptr value, computes the address of the type_info entry at a known ABI offset. Pointer arithmetic only. No memory access.
- GetMemberOp computes the address of a struct/class member given a base pointer and a constant index. Lowers to GEPOp (struct/class) or BitcastOp (union). No memory access.
- GetRuntimeMemberOp computes a member address using a runtime pointer-to-data-member value. Lowers to CastOp + PtrStrideOp (byte-level pointer arithmetic). No memory access.
- BaseClassAddrOp computes the base class address from a derived class pointer using a byte offset. Lowers to GEPOp/BitcastOp with a null check when needed. No memory access.
- DerivedClassAddrOp computes the derived class address from a base class pointer using a negative byte offset. Lowers to GEPOp with a null check when needed. No memory access.
In all cases, the actual memory reads are performed by separate cir.load ops that follow these address computations.
---
Full diff: https://github.com/llvm/llvm-project/pull/185154.diff
2 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+12-13)
- (added) clang/test/CIR/Transforms/pure-ptr-arithmetic.cir (+65)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 79eef71229192..e5268a69b15e0 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -204,7 +204,6 @@ def CIR_CastKind : CIR_I32EnumAttr<"CastKind", "cast kind", [
def CIR_CastOp : CIR_Op<"cast", [
Pure, DeclareOpInterfaceMethods<PromotableOpInterface>
]> {
- // FIXME: not all conversions are free of side effects.
let summary = "Conversion between values of different types";
let description = [{
Apply the usual C/C++ conversion rules between values. This operation models
@@ -2804,7 +2803,7 @@ def CIR_VTableGetVPtrOp : CIR_Op<"vtable.get_vptr", [Pure]> {
}];
let arguments = (ins
- Arg<CIR_PointerType, "the vptr address", [MemRead]>:$src
+ CIR_PointerType:$src
);
let results = (outs CIR_PtrToVPtr:$result);
@@ -2851,7 +2850,7 @@ def CIR_VTableGetVirtualFnAddrOp : CIR_Op<"vtable.get_virtual_fn_addr", [
}];
let arguments = (ins
- Arg<CIR_VPtrType, "vptr", [MemRead]>:$vptr,
+ CIR_VPtrType:$vptr,
I64Attr:$index);
let results = (outs CIR_PointerType:$result);
@@ -2890,7 +2889,7 @@ def CIR_VTableGetTypeInfoOp : CIR_Op<"vtable.get_type_info", [
```
}];
- let arguments = (ins Arg<CIR_VPtrType, "vptr", [MemRead]>:$vptr);
+ let arguments = (ins CIR_VPtrType:$vptr);
let results = (outs CIR_PointerType:$result);
let assemblyFormat = [{
@@ -3149,7 +3148,7 @@ def CIR_GetBitfieldOp : CIR_Op<"get_bitfield"> {
// GetMemberOp
//===----------------------------------------------------------------------===//
-def CIR_GetMemberOp : CIR_Op<"get_member"> {
+def CIR_GetMemberOp : CIR_Op<"get_member", [Pure]> {
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
@@ -3171,7 +3170,7 @@ def CIR_GetMemberOp : CIR_Op<"get_member"> {
}];
let arguments = (ins
- Arg<CIR_PointerType, "the address to load from", [MemRead]>:$addr,
+ CIR_PointerType:$addr,
StrAttr:$name,
IndexAttr:$index_attr);
@@ -4317,7 +4316,7 @@ def CIR_ArrayDtor : CIR_ArrayInitDestroy<"array.dtor"> {
// GetRuntimeMemberOp
//===----------------------------------------------------------------------===//
-def CIR_GetRuntimeMemberOp : CIR_Op<"get_runtime_member"> {
+def CIR_GetRuntimeMemberOp : CIR_Op<"get_runtime_member", [Pure]> {
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
@@ -4357,8 +4356,8 @@ def CIR_GetRuntimeMemberOp : CIR_Op<"get_runtime_member"> {
}];
let arguments = (ins
- Arg<CIR_PtrToRecordType, "address of the record object", [MemRead]>:$addr,
- Arg<CIR_DataMemberType, "pointer to the target member">:$member);
+ CIR_PtrToRecordType:$addr,
+ CIR_DataMemberType:$member);
let results = (outs Res<CIR_PointerType, "">:$result);
@@ -4714,7 +4713,7 @@ def CIR_VecSplatOp : CIR_Op<"vec.splat", [
// BaseClassAddrOp
//===----------------------------------------------------------------------===//
-def CIR_BaseClassAddrOp : CIR_Op<"base_class_addr"> {
+def CIR_BaseClassAddrOp : CIR_Op<"base_class_addr", [Pure]> {
let summary = "Get the base class address for a class/struct";
let description = [{
The `cir.base_class_addr` operaration gets the address of a particular
@@ -4743,7 +4742,7 @@ def CIR_BaseClassAddrOp : CIR_Op<"base_class_addr"> {
}];
let arguments = (ins
- Arg<CIR_PointerType, "derived class pointer", [MemRead]>:$derived_addr,
+ CIR_PointerType:$derived_addr,
IndexAttr:$offset, UnitAttr:$assume_not_null);
let results = (outs Res<CIR_PointerType, "">:$base_addr);
@@ -4759,7 +4758,7 @@ def CIR_BaseClassAddrOp : CIR_Op<"base_class_addr"> {
// DerivedClassAddrOp
//===----------------------------------------------------------------------===//
-def CIR_DerivedClassAddrOp : CIR_Op<"derived_class_addr"> {
+def CIR_DerivedClassAddrOp : CIR_Op<"derived_class_addr", [Pure]> {
let summary = "Get the derived class address for a class/struct";
let description = [{
The `cir.derived_class_addr` operaration gets the address of a particular
@@ -4793,7 +4792,7 @@ def CIR_DerivedClassAddrOp : CIR_Op<"derived_class_addr"> {
}];
let arguments = (ins
- Arg<CIR_PointerType, "base class pointer", [MemRead]>:$base_addr,
+ CIR_PointerType:$base_addr,
IndexAttr:$offset, UnitAttr:$assume_not_null);
let results = (outs Res<CIR_PointerType, "">:$derived_addr);
diff --git a/clang/test/CIR/Transforms/pure-ptr-arithmetic.cir b/clang/test/CIR/Transforms/pure-ptr-arithmetic.cir
new file mode 100644
index 0000000000000..4c7e79923d0db
--- /dev/null
+++ b/clang/test/CIR/Transforms/pure-ptr-arithmetic.cir
@@ -0,0 +1,65 @@
+// RUN: cir-opt %s -canonicalize -o - | FileCheck %s
+
+// Verify that pointer-arithmetic ops marked Pure are eliminated when their
+// results are unused (trivially dead).
+
+!s32i = !cir.int<s, 32>
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+
+!rec_S = !cir.record<struct "S" {!s32i, !s32i}>
+!rec_Base = !cir.record<struct "Base" {!u8i}>
+!rec_Derived = !cir.record<struct "Derived" {!rec_Base, !s32i}>
+
+module {
+
+// CHECK-LABEL: @dead_get_member
+// CHECK-NOT: cir.get_member
+cir.func @dead_get_member(%arg0: !cir.ptr<!rec_S>) {
+ %0 = cir.get_member %arg0[1] {name = "y"} : !cir.ptr<!rec_S> -> !cir.ptr<!s32i>
+ cir.return
+}
+
+// CHECK-LABEL: @dead_base_class_addr
+// CHECK-NOT: cir.base_class_addr
+cir.func @dead_base_class_addr(%arg0: !cir.ptr<!rec_Derived>) {
+ %0 = cir.base_class_addr %arg0 : !cir.ptr<!rec_Derived> nonnull [0] -> !cir.ptr<!rec_Base>
+ cir.return
+}
+
+// CHECK-LABEL: @dead_derived_class_addr
+// CHECK-NOT: cir.derived_class_addr
+cir.func @dead_derived_class_addr(%arg0: !cir.ptr<!rec_Base>) {
+ %0 = cir.derived_class_addr %arg0 : !cir.ptr<!rec_Base> nonnull [0] -> !cir.ptr<!rec_Derived>
+ cir.return
+}
+
+// CHECK-LABEL: @dead_vtable_get_vptr
+// CHECK-NOT: cir.vtable.get_vptr
+cir.func @dead_vtable_get_vptr(%arg0: !cir.ptr<!rec_Derived>) {
+ %0 = cir.vtable.get_vptr %arg0 : !cir.ptr<!rec_Derived> -> !cir.ptr<!cir.vptr>
+ cir.return
+}
+
+// CHECK-LABEL: @dead_vtable_get_virtual_fn_addr
+// CHECK-NOT: cir.vtable.get_virtual_fn_addr
+cir.func @dead_vtable_get_virtual_fn_addr(%arg0: !cir.vptr) {
+ %0 = cir.vtable.get_virtual_fn_addr %arg0[0] : !cir.vptr -> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>>
+ cir.return
+}
+
+// CHECK-LABEL: @dead_vtable_get_type_info
+// CHECK-NOT: cir.vtable.get_type_info
+cir.func @dead_vtable_get_type_info(%arg0: !cir.vptr) {
+ %0 = cir.vtable.get_type_info %arg0 : !cir.vptr -> !cir.ptr<!cir.ptr<!rec_S>>
+ cir.return
+}
+
+// CHECK-LABEL: @dead_get_runtime_member
+// CHECK-NOT: cir.get_runtime_member
+cir.func @dead_get_runtime_member(%arg0: !cir.ptr<!rec_S>, %arg1: !cir.data_member<!s32i in !rec_S>) {
+ %0 = cir.get_runtime_member %arg0[%arg1 : !cir.data_member<!s32i in !rec_S>] : !cir.ptr<!rec_S> -> !cir.ptr<!s32i>
+ cir.return
+}
+
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/185154
More information about the cfe-commits
mailing list