https://github.com/xlauko created https://github.com/llvm/llvm-project/pull/187865
Add the RecursiveMemoryEffects trait to cir.if, cir.case, loop ops (cir.while/cir.do/cir.for), cir.ternary, cir.await, cir.array.ctor, cir.array.dtor, and cir.try. Without this trait, MLIR conservatively assumes unknown memory effects for ops with regions, preventing DCE of ops whose bodies are provably pure. Also fix a crash in ConditionOp::getSuccessorRegions where the missing early return after the loop-op case would fall through to cast<AwaitOp>(...), which asserts when the parent is a loop rather than an await op. Add tests verifying that region ops with pure bodies are eliminated and ops with stores or calls are preserved, including two-level nested propagation (cir.if inside cir.while). >From f5de28f72ca21bd8a78c7f4dffacecd01314d7a7 Mon Sep 17 00:00:00 2001 From: xlauko <[email protected]> Date: Sat, 21 Mar 2026 14:50:35 +0100 Subject: [PATCH] [CIR] Add RecursiveMemoryEffects to region-bearing ops Add the RecursiveMemoryEffects trait to cir.if, cir.case, loop ops (cir.while/cir.do/cir.for), cir.ternary, cir.await, cir.array.ctor, cir.array.dtor, and cir.try. Without this trait, MLIR conservatively assumes unknown memory effects for ops with regions, preventing DCE of ops whose bodies are provably pure. Also fix a crash in ConditionOp::getSuccessorRegions where the missing early return after the loop-op case would fall through to cast<AwaitOp>(...), which asserts when the parent is a loop rather than an await op. Add tests verifying that region ops with pure bodies are eliminated and ops with stores or calls are preserved, including two-level nested propagation (cir.if inside cir.while). --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 18 +- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 1 + .../Transforms/recursive-memory-effects.cir | 304 ++++++++++++++++++ 3 files changed, 315 insertions(+), 8 deletions(-) create mode 100644 clang/test/CIR/Transforms/recursive-memory-effects.cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 41858a61480a8..4c063e29e2b1e 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -836,7 +836,8 @@ def CIR_ReturnOp : CIR_Op<"return", [ def CIR_IfOp : CIR_Op<"if", [ DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>, - RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments + RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments, + RecursiveMemoryEffects ]> { let summary = "the if-then-else operation"; let description = [{ @@ -1364,7 +1365,7 @@ def CIR_CaseOpKind : CIR_I32EnumAttr<"CaseOpKind", "case kind", [ def CIR_CaseOp : CIR_Op<"case", [ DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>, - RecursivelySpeculatable, AutomaticAllocationScope + RecursivelySpeculatable, AutomaticAllocationScope, RecursiveMemoryEffects ]> { let summary = "Case operation"; let description = [{ @@ -2004,7 +2005,7 @@ def CIR_IndirectBrOp : CIR_Op<"indirect_br", [ //===----------------------------------------------------------------------===// class CIR_LoopOpBase<string mnemonic> : CIR_Op<mnemonic, [ - LoopOpInterface, NoRegionArguments + LoopOpInterface, NoRegionArguments, RecursiveMemoryEffects ]> { let extraClassDefinition = [{ void $cppClass::getSuccessorRegions( @@ -2703,7 +2704,8 @@ def CIR_SelectOp : CIR_Op<"select", [ def CIR_TernaryOp : CIR_Op<"ternary", [ DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>, - RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments + RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments, + RecursiveMemoryEffects ]> { let summary = "The `cond ? a : b` C/C++ ternary operation"; let description = [{ @@ -4040,7 +4042,7 @@ def CIR_AwaitKind : CIR_I32EnumAttr<"AwaitKind", "await kind", [ def CIR_AwaitOp : CIR_Op<"await",[ DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>, - RecursivelySpeculatable, NoRegionArguments + RecursivelySpeculatable, NoRegionArguments, RecursiveMemoryEffects ]> { let summary = "Wraps C++ co_await implicit logic"; let description = [{ @@ -4531,7 +4533,7 @@ def CIR_TrapOp : CIR_Op<"trap", [Terminator]> { // ArrayCtor & ArrayDtor //===----------------------------------------------------------------------===// -def CIR_ArrayCtor : CIR_Op<"array.ctor"> { +def CIR_ArrayCtor : CIR_Op<"array.ctor", [RecursiveMemoryEffects]> { let summary = "Initialize array elements with C++ constructors"; let description = [{ Initialize each array element using the same C++ constructor. This @@ -4573,7 +4575,7 @@ def CIR_ArrayCtor : CIR_Op<"array.ctor"> { let hasLLVMLowering = false; } -def CIR_ArrayDtor : CIR_Op<"array.dtor"> { +def CIR_ArrayDtor : CIR_Op<"array.dtor", [RecursiveMemoryEffects]> { let summary = "Destroy array elements with C++ destructors"; let description = [{ Destroy each array element using the same C++ destructor. This operation @@ -6765,7 +6767,7 @@ def CIR_AllocExceptionOp : CIR_Op<"alloc.exception"> { def CIR_TryOp : CIR_Op<"try",[ DeclareOpInterfaceMethods<RegionBranchOpInterface, ["getSuccessorInputs"]>, - RecursivelySpeculatable, AutomaticAllocationScope + RecursivelySpeculatable, AutomaticAllocationScope, RecursiveMemoryEffects ]> { let summary = "C++ try block"; let description = [{ diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index bf369bfe69991..8e384e676557c 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -337,6 +337,7 @@ void cir::ConditionOp::getSuccessorRegions( if (auto loopOp = dyn_cast<LoopOpInterface>(getOperation()->getParentOp())) { regions.emplace_back(&loopOp.getBody()); regions.push_back(RegionSuccessor::parent()); + return; } // Parent is an await: condition may branch to resume or suspend regions. diff --git a/clang/test/CIR/Transforms/recursive-memory-effects.cir b/clang/test/CIR/Transforms/recursive-memory-effects.cir new file mode 100644 index 0000000000000..9fbb4b0c9def8 --- /dev/null +++ b/clang/test/CIR/Transforms/recursive-memory-effects.cir @@ -0,0 +1,304 @@ +// RUN: cir-opt %s --remove-dead-values -o - | FileCheck %s +// +// Tests that RecursiveMemoryEffects on region-based ops enables correct DCE: +// - Ops whose regions contain no memory effects can be eliminated when unused. +// - Ops whose regions contain stores/calls are preserved even when unused. + +!s32i = !cir.int<s, 32> +!u8i = !cir.int<u, 8> +!void = !cir.void +!rec_S = !cir.record<struct "S" padded {!u8i}> + +module { + +// --------------------------------------------------------------------------- +// cir.ternary +// --------------------------------------------------------------------------- + +// Unused cir.ternary with a pure body (only cir.const, cir.yield, no stores) +// → no memory effects via RecursiveMemoryEffects → DCE'd. +// +// CHECK-LABEL: cir.func @ternary_pure_body_dce +// CHECK-NOT: cir.ternary +// CHECK: cir.return + cir.func @ternary_pure_body_dce(%arg0: !cir.bool, %arg1: !s32i) -> !s32i { + %unused = cir.ternary (%arg0, true { + %c = cir.const #cir.int<1> : !s32i + cir.yield %c : !s32i + }, false { + cir.yield %arg1 : !s32i + }) : (!cir.bool) -> !s32i + cir.return %arg1 : !s32i + } + +// Unused cir.ternary with a store in one region inherits that write effect +// → preserved despite unused result. +// +// CHECK-LABEL: cir.func @ternary_store_preserved +// CHECK: cir.ternary +// CHECK: cir.store +// CHECK: cir.return + cir.func @ternary_store_preserved(%arg0: !cir.bool, + %arg1: !cir.ptr<!s32i>) -> !s32i { + %c0 = cir.const #cir.int<0> : !s32i + %unused = cir.ternary (%arg0, true { + %c = cir.const #cir.int<42> : !s32i + cir.store %c, %arg1 : !s32i, !cir.ptr<!s32i> + cir.yield %c : !s32i + }, false { + cir.yield %c0 : !s32i + }) : (!cir.bool) -> !s32i + cir.return %c0 : !s32i + } + +// --------------------------------------------------------------------------- +// cir.if +// --------------------------------------------------------------------------- + +// cir.if with a pure body (no store) → no effects → DCE'd. +// +// CHECK-LABEL: cir.func @if_pure_body_dce +// CHECK-NOT: cir.if +// CHECK: cir.return + cir.func @if_pure_body_dce(%arg0: !cir.bool) { + cir.if %arg0 { + %c = cir.const #cir.int<1> : !s32i + cir.yield + } + cir.return + } + +// cir.if with a store in its body → preserved. +// +// CHECK-LABEL: cir.func @if_store_preserved +// CHECK: cir.if +// CHECK: cir.store +// CHECK: cir.return + cir.func @if_store_preserved(%arg0: !cir.bool, %arg1: !cir.ptr<!s32i>) { + %c = cir.const #cir.int<1> : !s32i + cir.if %arg0 { + cir.store %c, %arg1 : !s32i, !cir.ptr<!s32i> + cir.yield + } + cir.return + } + +// --------------------------------------------------------------------------- +// cir.switch / cir.case +// --------------------------------------------------------------------------- + +// cir.switch with all-pure cases (only cir.const, cir.yield) → no effects +// via RecursiveMemoryEffects on cir.case and cir.switch → DCE'd. +// +// CHECK-LABEL: cir.func @switch_pure_body_dce +// CHECK-NOT: cir.switch +// CHECK: cir.return + cir.func @switch_pure_body_dce(%arg0: !s32i) { + cir.switch (%arg0 : !s32i) { + cir.case (equal, [#cir.int<1> : !s32i]) { + %c = cir.const #cir.int<42> : !s32i + cir.yield + } + cir.case (default, []) { + cir.yield + } + cir.yield + } + cir.return + } + +// cir.switch whose cases contain stores → preserved. +// +// CHECK-LABEL: cir.func @switch_store_preserved +// CHECK: cir.switch +// CHECK: cir.store +// CHECK: cir.return + cir.func @switch_store_preserved(%arg0: !s32i, %arg1: !cir.ptr<!s32i>) { + cir.switch (%arg0 : !s32i) { + cir.case (equal, [#cir.int<1> : !s32i]) { + %c = cir.const #cir.int<42> : !s32i + cir.store %c, %arg1 : !s32i, !cir.ptr<!s32i> + cir.yield + } + cir.case (default, []) { + cir.yield + } + cir.yield + } + cir.return + } + +// --------------------------------------------------------------------------- +// cir.while / cir.do / cir.for +// --------------------------------------------------------------------------- + +// cir.while with a store in its body → preserved. +// +// CHECK-LABEL: cir.func @while_store_preserved +// CHECK: cir.while +// CHECK: cir.store +// CHECK: cir.return + cir.func @while_store_preserved(%arg0: !cir.bool, %arg1: !cir.ptr<!s32i>) { + %c = cir.const #cir.int<1> : !s32i + cir.while { + cir.condition(%arg0) + } do { + cir.store %c, %arg1 : !s32i, !cir.ptr<!s32i> + cir.yield + } + cir.return + } + +// cir.do with a store in its body → preserved. +// +// CHECK-LABEL: cir.func @do_store_preserved +// CHECK: cir.do +// CHECK: cir.store +// CHECK: cir.return + cir.func @do_store_preserved(%arg0: !cir.bool, %arg1: !cir.ptr<!s32i>) { + %c = cir.const #cir.int<1> : !s32i + cir.do { + cir.store %c, %arg1 : !s32i, !cir.ptr<!s32i> + cir.yield + } while { + cir.condition(%arg0) + } + cir.return + } + +// cir.for with a store in its body → preserved. +// +// CHECK-LABEL: cir.func @for_store_preserved +// CHECK: cir.for +// CHECK: cir.store +// CHECK: cir.return + cir.func @for_store_preserved(%arg0: !cir.bool, %arg1: !cir.ptr<!s32i>) { + %c = cir.const #cir.int<1> : !s32i + cir.for : cond { + cir.condition(%arg0) + } body { + cir.store %c, %arg1 : !s32i, !cir.ptr<!s32i> + cir.yield + } step { + cir.yield + } + cir.return + } + +// cir.while containing a cir.if that stores: write effect propagates through +// two nesting levels via RecursiveMemoryEffects → while preserved. +// +// CHECK-LABEL: cir.func @nested_if_in_while_store_preserved +// CHECK: cir.while +// CHECK: cir.if +// CHECK: cir.store +// CHECK: cir.return + cir.func @nested_if_in_while_store_preserved(%arg0: !cir.bool, + %arg1: !cir.bool, + %arg2: !cir.ptr<!s32i>) { + %c = cir.const #cir.int<1> : !s32i + cir.while { + cir.condition(%arg0) + } do { + cir.if %arg1 { + cir.store %c, %arg2 : !s32i, !cir.ptr<!s32i> + cir.yield + } + cir.yield + } + cir.return + } + +// --------------------------------------------------------------------------- +// cir.try +// --------------------------------------------------------------------------- + +// cir.try with a store in the try body → preserved. +// +// CHECK-LABEL: cir.func @try_store_preserved +// CHECK: cir.try +// CHECK: cir.store +// CHECK: cir.return + cir.func @try_store_preserved(%arg0: !cir.ptr<!s32i>) { + %c = cir.const #cir.int<1> : !s32i + cir.try { + cir.store %c, %arg0 : !s32i, !cir.ptr<!s32i> + cir.yield + } catch all (%eh_token : !cir.eh_token) { + %catch_token, %exn = cir.begin_catch %eh_token + : !cir.eh_token -> (!cir.catch_token, !cir.ptr<!void>) + cir.cleanup.scope { + cir.yield + } cleanup eh { + cir.end_catch %catch_token : !cir.catch_token + cir.yield + } + cir.yield + } + cir.return + } + +// --------------------------------------------------------------------------- +// cir.await +// --------------------------------------------------------------------------- + +// cir.await whose resume region contains a store → preserved. +// +// CHECK-LABEL: cir.func coroutine @await_store_preserved +// CHECK: cir.await +// CHECK: cir.store +// CHECK: cir.return + cir.func coroutine @await_store_preserved(%arg0: !cir.bool, + %arg1: !cir.ptr<!s32i>) { + %c = cir.const #cir.int<1> : !s32i + cir.await(user, ready : { + cir.condition(%arg0) + }, suspend : { + cir.yield + }, resume : { + cir.store %c, %arg1 : !s32i, !cir.ptr<!s32i> + cir.yield + },) + cir.return + } + +// --------------------------------------------------------------------------- +// cir.array.ctor / cir.array.dtor +// --------------------------------------------------------------------------- + +// cir.array.ctor calls a constructor inside its region → call has effects +// → preserved. +// +// CHECK-LABEL: cir.func @array_ctor_preserved +// CHECK: cir.array.ctor +// CHECK: cir.call +// CHECK: cir.return + cir.func private @_ZN1SC1Ev(!cir.ptr<!rec_S>) + cir.func @array_ctor_preserved( + %arg0: !cir.ptr<!cir.array<!rec_S x 4>>) { + cir.array.ctor %arg0 : !cir.ptr<!cir.array<!rec_S x 4>> { + ^bb0(%elem: !cir.ptr<!rec_S>): + cir.call @_ZN1SC1Ev(%elem) : (!cir.ptr<!rec_S>) -> () + cir.yield + } + cir.return + } + +// cir.array.dtor calls a destructor inside its region → preserved. +// +// CHECK-LABEL: cir.func @array_dtor_preserved +// CHECK: cir.array.dtor +// CHECK: cir.call +// CHECK: cir.return + cir.func private @_ZN1SD1Ev(!cir.ptr<!rec_S>) + cir.func @array_dtor_preserved( + %arg0: !cir.ptr<!cir.array<!rec_S x 4>>) { + cir.array.dtor %arg0 : !cir.ptr<!cir.array<!rec_S x 4>> { + ^bb0(%elem: !cir.ptr<!rec_S>): + cir.call @_ZN1SD1Ev(%elem) : (!cir.ptr<!rec_S>) -> () + cir.yield + } + cir.return + } + +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
