[clang] [CIR] Implement PromotableRegionOpInterface for `cir.if`,`cir.scope` and `cir.ternary` (PR #215780)

David Rivera via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 7 14:07:42 PDT 2026


https://github.com/RiverDave updated https://github.com/llvm/llvm-project/pull/215780

>From df7481cddf10e6d6ef23d37053d061e732873ee8 Mon Sep 17 00:00:00 2001
From: David Rivera <davidriverg at gmail.com>
Date: Wed, 12 Aug 2026 06:58:18 -0400
Subject: [PATCH 1/3] [CIR] Implement PromotableRegionOpInterface for cir.if

mem2reg gives up on a memory slot as soon as one of its uses lives in a
nested region whose parent op does not implement
PromotableRegionOpInterface. Since no CIR op implements it, promoting a
slot read inside a cir.if required running cir-flatten-cfg first, which
is why clang/test/CIR/Transforms/mem2reg.cir has to flatten before it can
promote anything.

Implement the interface for cir.if. Both regions are entered directly
from before the operation, so both see the same reaching definition.

A definition created inside a region has to leave the operation through
one of its results, and cir.if has none, so isRegionPromotable refuses
regions that store to the slot. Supporting those requires giving cir.if
results and is left for later.

Co-Authored-By: Claude Opus 5 <noreply at anthropic.com>
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  1 +
 clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp    | 28 ++++++++
 clang/test/CIR/Transforms/mem2reg-regions.cir | 72 +++++++++++++++++++
 3 files changed, 101 insertions(+)
 create mode 100644 clang/test/CIR/Transforms/mem2reg-regions.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 6d149e6b1ccaa..76f91c17a96aa 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1011,6 +1011,7 @@ def CIR_ReturnOp : CIR_Op<"return", [
 //===----------------------------------------------------------------------===//
 
 def CIR_IfOp : CIR_RegionBranchOpBase<"if", [
+  DeclareOpInterfaceMethods<PromotableRegionOpInterface>,
   RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments
 ]> {
   let summary = "the if-then-else operation";
diff --git a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
index d6de6b6e80799..3ed6784a5da7a 100644
--- a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
@@ -187,3 +187,31 @@ DeletionKind cir::CastOp::removeBlockingUses(
     const SmallPtrSetImpl<OpOperand *> &blockingUses, OpBuilder &builder) {
   return DeletionKind::Delete;
 }
+
+//===----------------------------------------------------------------------===//
+// Interfaces for IfOp
+//===----------------------------------------------------------------------===//
+
+bool cir::IfOp::isRegionPromotable(const MemorySlot &slot, Region *region,
+                                   bool hasValueStores) {
+  // A definition produced inside a region has to leave the operation through
+  // one of its results, and cir.if has no result to receive it.
+  return !hasValueStores;
+}
+
+void cir::IfOp::setupPromotion(
+    const MemorySlot &slot, Value reachingDef, bool hasValueStores,
+    llvm::SmallMapVector<Region *, Value, 2> &regionsToProcess) {
+  // Exactly one region executes, exactly once, entered from before the op, so
+  // both see the same reaching definition.
+  regionsToProcess.insert({&getThenRegion(), reachingDef});
+  regionsToProcess.insert({&getElseRegion(), reachingDef});
+}
+
+Value cir::IfOp::finalizePromotion(
+    const MemorySlot &slot, Value reachingDef, bool hasValueStores,
+    const llvm::DenseMap<Block *, Value> &reachingAtBlockEnd,
+    OpBuilder &builder) {
+  assert(!hasValueStores && "cir.if cannot yield a new definition");
+  return reachingDef;
+}
diff --git a/clang/test/CIR/Transforms/mem2reg-regions.cir b/clang/test/CIR/Transforms/mem2reg-regions.cir
new file mode 100644
index 0000000000000..c71d6282f6b9b
--- /dev/null
+++ b/clang/test/CIR/Transforms/mem2reg-regions.cir
@@ -0,0 +1,72 @@
+// RUN: cir-opt %s -mem2reg -o - | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+
+module {
+  // A slot whose only nested use is a load can be promoted without flattening
+  // the CFG first.
+  // CHECK-LABEL: cir.func @load_in_if
+  cir.func @load_in_if(%cond: !cir.bool) {
+    // CHECK-NOT: cir.alloca
+    // CHECK: %[[CONST:.*]] = cir.const #cir.int<42>
+    // CHECK: cir.if
+    // CHECK-NOT: cir.load
+    // CHECK: cir.call @use(%[[CONST]])
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.const #cir.int<42> : !s32i
+    cir.store %1, %0 : !s32i, !cir.ptr<!s32i>
+    cir.if %cond {
+      %2 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+      cir.call @use(%2) : (!s32i) -> ()
+      cir.yield
+    }
+    cir.return
+  }
+
+  // Both regions are entered with the same reaching definition.
+  // CHECK-LABEL: cir.func @load_in_both_regions
+  cir.func @load_in_both_regions(%cond: !cir.bool) {
+    // CHECK-NOT: cir.alloca
+    // CHECK: %[[CONST:.*]] = cir.const #cir.int<7>
+    // CHECK: cir.if
+    // CHECK: cir.call @use(%[[CONST]])
+    // CHECK: cir.call @use(%[[CONST]])
+    // CHECK-NOT: cir.load
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.const #cir.int<7> : !s32i
+    cir.store %1, %0 : !s32i, !cir.ptr<!s32i>
+    cir.if %cond {
+      %2 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+      cir.call @use(%2) : (!s32i) -> ()
+      cir.yield
+    } else {
+      %3 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+      cir.call @use(%3) : (!s32i) -> ()
+      cir.yield
+    }
+    cir.return
+  }
+
+  // A store inside a region would have to leave the operation through a result,
+  // which cir.if does not have, so the slot is left alone.
+  // CHECK-LABEL: cir.func @store_in_if
+  cir.func @store_in_if(%cond: !cir.bool) {
+    // CHECK: cir.alloca
+    // CHECK: cir.if
+    // CHECK: cir.store
+    // CHECK: cir.load
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.const #cir.int<1> : !s32i
+    cir.store %1, %0 : !s32i, !cir.ptr<!s32i>
+    cir.if %cond {
+      %2 = cir.const #cir.int<2> : !s32i
+      cir.store %2, %0 : !s32i, !cir.ptr<!s32i>
+      cir.yield
+    }
+    %3 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+    cir.call @use(%3) : (!s32i) -> ()
+    cir.return
+  }
+
+  cir.func private @use(!s32i)
+}

>From 008169dd5a63391be115f3e530e368711f373697 Mon Sep 17 00:00:00 2001
From: David Rivera <davidriverg at gmail.com>
Date: Wed, 12 Aug 2026 07:12:52 -0400
Subject: [PATCH 2/3] [CIR] Implement PromotableRegionOpInterface for cir.scope

Same treatment as cir.if: a slot read inside a cir.scope no longer needs
the CFG flattened before mem2reg can promote it. The scope region is
entered directly from before the operation, so it sees the reaching
definition unchanged.

cir.scope yields at most one value and may already be using it for the
scope's own result, so regions that store to the slot are refused.

Co-Authored-By: Claude Opus 5 <noreply at anthropic.com>
---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  1 +
 clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp    | 24 ++++++++
 clang/test/CIR/Transforms/mem2reg-regions.cir | 57 +++++++++++++++++++
 3 files changed, 82 insertions(+)

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 76f91c17a96aa..40dd2be7bc519 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1303,6 +1303,7 @@ def CIR_ResumeFlatOp : CIR_Op<"resume.flat", [
 //===----------------------------------------------------------------------===//
 
 def CIR_ScopeOp : CIR_RegionBranchOpBase<"scope", [
+  DeclareOpInterfaceMethods<PromotableRegionOpInterface>,
   RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments,
   RecursiveMemoryEffects
 ]> {
diff --git a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
index 3ed6784a5da7a..ca8260ee673f6 100644
--- a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
@@ -215,3 +215,27 @@ Value cir::IfOp::finalizePromotion(
   assert(!hasValueStores && "cir.if cannot yield a new definition");
   return reachingDef;
 }
+
+//===----------------------------------------------------------------------===//
+// Interfaces for ScopeOp
+//===----------------------------------------------------------------------===//
+
+bool cir::ScopeOp::isRegionPromotable(const MemorySlot &slot, Region *region,
+                                      bool hasValueStores) {
+  // cir.scope yields at most one value, which it may already be using.
+  return !hasValueStores;
+}
+
+void cir::ScopeOp::setupPromotion(
+    const MemorySlot &slot, Value reachingDef, bool hasValueStores,
+    llvm::SmallMapVector<Region *, Value, 2> &regionsToProcess) {
+  regionsToProcess.insert({&getScopeRegion(), reachingDef});
+}
+
+Value cir::ScopeOp::finalizePromotion(
+    const MemorySlot &slot, Value reachingDef, bool hasValueStores,
+    const llvm::DenseMap<Block *, Value> &reachingAtBlockEnd,
+    OpBuilder &builder) {
+  assert(!hasValueStores && "cir.scope cannot yield a new definition");
+  return reachingDef;
+}
diff --git a/clang/test/CIR/Transforms/mem2reg-regions.cir b/clang/test/CIR/Transforms/mem2reg-regions.cir
index c71d6282f6b9b..8f5b5d91bbf9b 100644
--- a/clang/test/CIR/Transforms/mem2reg-regions.cir
+++ b/clang/test/CIR/Transforms/mem2reg-regions.cir
@@ -68,5 +68,62 @@ module {
     cir.return
   }
 
+  // CHECK-LABEL: cir.func @load_in_scope
+  cir.func @load_in_scope(%cond: !cir.bool) {
+    // CHECK-NOT: cir.alloca
+    // CHECK: %[[CONST:.*]] = cir.const #cir.int<3>
+    // CHECK: cir.scope
+    // CHECK-NOT: cir.load
+    // CHECK: cir.call @use(%[[CONST]])
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.const #cir.int<3> : !s32i
+    cir.store %1, %0 : !s32i, !cir.ptr<!s32i>
+    cir.scope {
+      %2 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+      cir.call @use(%2) : (!s32i) -> ()
+    }
+    cir.return
+  }
+
+  // A cir.scope between the slot and a nested cir.if is threaded through.
+  // CHECK-LABEL: cir.func @load_in_if_inside_scope
+  cir.func @load_in_if_inside_scope(%cond: !cir.bool) {
+    // CHECK-NOT: cir.alloca
+    // CHECK: %[[CONST:.*]] = cir.const #cir.int<5>
+    // CHECK: cir.scope
+    // CHECK: cir.if
+    // CHECK-NOT: cir.load
+    // CHECK: cir.call @use(%[[CONST]])
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.const #cir.int<5> : !s32i
+    cir.store %1, %0 : !s32i, !cir.ptr<!s32i>
+    cir.scope {
+      cir.if %cond {
+        %2 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+        cir.call @use(%2) : (!s32i) -> ()
+        cir.yield
+      }
+    }
+    cir.return
+  }
+
+  // CHECK-LABEL: cir.func @store_in_scope
+  cir.func @store_in_scope(%cond: !cir.bool) {
+    // CHECK: cir.alloca
+    // CHECK: cir.scope
+    // CHECK: cir.store
+    // CHECK: cir.load
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.const #cir.int<1> : !s32i
+    cir.store %1, %0 : !s32i, !cir.ptr<!s32i>
+    cir.scope {
+      %2 = cir.const #cir.int<2> : !s32i
+      cir.store %2, %0 : !s32i, !cir.ptr<!s32i>
+    }
+    %3 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+    cir.call @use(%3) : (!s32i) -> ()
+    cir.return
+  }
+
   cir.func private @use(!s32i)
 }

>From 2f2f4d8c675536388a32085a1383332f3143cb7f Mon Sep 17 00:00:00 2001
From: David Rivera <davidriverg at gmail.com>
Date: Thu, 3 Sep 2026 00:12:40 -0400
Subject: [PATCH 3/3] Extend PromotableRegionOpInterface for TernaryOp and add
 extra test coverage

---
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |   1 +
 clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp    |  38 +++++-
 clang/test/CIR/Transforms/mem2reg-regions.c   |  25 ++++
 clang/test/CIR/Transforms/mem2reg-regions.cir | 118 +++++++++++++++++-
 4 files changed, 177 insertions(+), 5 deletions(-)
 create mode 100644 clang/test/CIR/Transforms/mem2reg-regions.c

diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 40dd2be7bc519..71a997893272c 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3233,6 +3233,7 @@ def CIR_SelectOp : CIR_Op<"select", [
 //===----------------------------------------------------------------------===//
 
 def CIR_TernaryOp : CIR_RegionBranchOpBase<"ternary", [
+  DeclareOpInterfaceMethods<PromotableRegionOpInterface>,
   RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments
 ]> {
   let summary = "The `cond ? a : b` C/C++ ternary operation";
diff --git a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
index ca8260ee673f6..c39fb569b6e83 100644
--- a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp
@@ -194,8 +194,11 @@ DeletionKind cir::CastOp::removeBlockingUses(
 
 bool cir::IfOp::isRegionPromotable(const MemorySlot &slot, Region *region,
                                    bool hasValueStores) {
-  // A definition produced inside a region has to leave the operation through
-  // one of its results, and cir.if has no result to receive it.
+  // A definition produced inside a region has to leave through a result, and
+  // cir.if has none. Parameters and enclosing locals have their alloca
+  // outside and are only read here; those promote. A variable declared inside
+  // the region (`if (c) { int x = 42; use(x); }`) has its initializing store
+  // inside after cir-hoist-allocas, which is hasValueStores, so it is refused.
   return !hasValueStores;
 }
 
@@ -222,7 +225,8 @@ Value cir::IfOp::finalizePromotion(
 
 bool cir::ScopeOp::isRegionPromotable(const MemorySlot &slot, Region *region,
                                       bool hasValueStores) {
-  // cir.scope yields at most one value, which it may already be using.
+  // cir.scope yields at most one value, which it may already be using for the
+  // scope's own result. Same load-only restriction as cir.if.
   return !hasValueStores;
 }
 
@@ -239,3 +243,31 @@ Value cir::ScopeOp::finalizePromotion(
   assert(!hasValueStores && "cir.scope cannot yield a new definition");
   return reachingDef;
 }
+
+//===----------------------------------------------------------------------===//
+// Interfaces for TernaryOp
+//===----------------------------------------------------------------------===//
+
+bool cir::TernaryOp::isRegionPromotable(const MemorySlot &slot, Region *region,
+                                        bool hasValueStores) {
+  // Same load-only restriction as cir.if. The existing result is the
+  // ternary's own yielded value; a store would need an extra result.
+  return !hasValueStores;
+}
+
+void cir::TernaryOp::setupPromotion(
+    const MemorySlot &slot, Value reachingDef, bool hasValueStores,
+    llvm::SmallMapVector<Region *, Value, 2> &regionsToProcess) {
+  // Exactly one region executes, exactly once, entered from before the op, so
+  // both see the same reaching definition.
+  regionsToProcess.insert({&getTrueRegion(), reachingDef});
+  regionsToProcess.insert({&getFalseRegion(), reachingDef});
+}
+
+Value cir::TernaryOp::finalizePromotion(
+    const MemorySlot &slot, Value reachingDef, bool hasValueStores,
+    const llvm::DenseMap<Block *, Value> &reachingAtBlockEnd,
+    OpBuilder &builder) {
+  assert(!hasValueStores && "cir.ternary cannot yield a new definition");
+  return reachingDef;
+}
diff --git a/clang/test/CIR/Transforms/mem2reg-regions.c b/clang/test/CIR/Transforms/mem2reg-regions.c
new file mode 100644
index 0000000000000..b6c10aba2544e
--- /dev/null
+++ b/clang/test/CIR/Transforms/mem2reg-regions.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: cir-opt %t.cir -mem2reg -o - | FileCheck %s \
+// RUN:   --implicit-check-not=cir.alloca --implicit-check-not=cir.load
+
+void use(int);
+
+// Parameter loaded inside `if`: slot is declared further out and only read
+// inside the region, so mem2reg promotes it without flattening the CFG.
+void load_enclosing(int c, int x) {
+  if (c)
+    use(x);
+}
+
+// CHECK-LABEL: cir.func {{.*}}@load_enclosing
+// CHECK: cir.call @use(%arg1)
+
+// `a && b` is a cir.ternary; the RHS load of `b` lives in a nested region.
+void land_enclosing(int a, int b) {
+  if (a && b)
+    use(b);
+}
+
+// CHECK-LABEL: cir.func {{.*}}@land_enclosing
+// CHECK: cir.ternary
+// CHECK: cir.call @use(%arg1)
diff --git a/clang/test/CIR/Transforms/mem2reg-regions.cir b/clang/test/CIR/Transforms/mem2reg-regions.cir
index 8f5b5d91bbf9b..28d334eda3705 100644
--- a/clang/test/CIR/Transforms/mem2reg-regions.cir
+++ b/clang/test/CIR/Transforms/mem2reg-regions.cir
@@ -47,8 +47,10 @@ module {
     cir.return
   }
 
-  // A store inside a region would have to leave the operation through a result,
-  // which cir.if does not have, so the slot is left alone.
+  // A store inside a region would have to leave through a result, which cir.if
+  // does not have, so the slot is left alone. After cir-hoist-allocas this is
+  // the `if (c) { int x = 42; use(x); }` shape: slot outside, init store
+  // inside.
   // CHECK-LABEL: cir.func @store_in_if
   cir.func @store_in_if(%cond: !cir.bool) {
     // CHECK: cir.alloca
@@ -68,6 +70,52 @@ module {
     cir.return
   }
 
+  // hasValueStores is per slot. A store to one slot must not prevent promoting
+  // another slot that is only loaded in the same region.
+  // CHECK-LABEL: cir.func @store_one_load_other
+  cir.func @store_one_load_other(%cond: !cir.bool) {
+    // CHECK: %[[A:.*]] = cir.alloca "a"
+    // CHECK-NOT: cir.alloca "b"
+    // CHECK: %[[BVAL:.*]] = cir.const #cir.int<2>
+    // CHECK: cir.if
+    // CHECK: cir.store %{{.*}}, %[[A]]
+    // CHECK: cir.call @use(%[[BVAL]])
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.alloca "b" align(4) : !cir.ptr<!s32i>
+    %2 = cir.const #cir.int<1> : !s32i
+    cir.store %2, %0 : !s32i, !cir.ptr<!s32i>
+    %3 = cir.const #cir.int<2> : !s32i
+    cir.store %3, %1 : !s32i, !cir.ptr<!s32i>
+    cir.if %cond {
+      %4 = cir.const #cir.int<3> : !s32i
+      cir.store %4, %0 : !s32i, !cir.ptr<!s32i>
+      %5 = cir.load %1 : !cir.ptr<!s32i>, !s32i
+      cir.call @use(%5) : (!s32i) -> ()
+      cir.yield
+    }
+    %6 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+    cir.call @use(%6) : (!s32i) -> ()
+    cir.return
+  }
+
+  // A load with no store ahead of the region takes the default-value path:
+  // mem2reg materializes cir.const #cir.undef immediately before the op.
+  // CHECK-LABEL: cir.func @load_uninitialized_in_if
+  cir.func @load_uninitialized_in_if(%cond: !cir.bool) {
+    // CHECK-NOT: cir.alloca
+    // CHECK: %[[UNDEF:.*]] = cir.const #cir.undef
+    // CHECK: cir.if
+    // CHECK-NOT: cir.load
+    // CHECK: cir.call @use(%[[UNDEF]])
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    cir.if %cond {
+      %1 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+      cir.call @use(%1) : (!s32i) -> ()
+      cir.yield
+    }
+    cir.return
+  }
+
   // CHECK-LABEL: cir.func @load_in_scope
   cir.func @load_in_scope(%cond: !cir.bool) {
     // CHECK-NOT: cir.alloca
@@ -125,5 +173,71 @@ module {
     cir.return
   }
 
+  // CHECK-LABEL: cir.func @load_in_ternary
+  cir.func @load_in_ternary(%cond: !cir.bool) {
+    // CHECK-NOT: cir.alloca
+    // CHECK: %[[CONST:.*]] = cir.const #cir.int<9>
+    // CHECK: cir.ternary
+    // CHECK-NOT: cir.load
+    // CHECK: cir.call @use(%[[CONST]])
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.const #cir.int<9> : !s32i
+    cir.store %1, %0 : !s32i, !cir.ptr<!s32i>
+    %2 = cir.ternary(%cond, true {
+      %3 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+      cir.call @use(%3) : (!s32i) -> ()
+      cir.yield %3 : !s32i
+    }, false {
+      %4 = cir.const #cir.int<0> : !s32i
+      cir.yield %4 : !s32i
+    }) : (!cir.bool) -> !s32i
+    cir.return
+  }
+
+  // CHECK-LABEL: cir.func @load_in_both_ternary_regions
+  cir.func @load_in_both_ternary_regions(%cond: !cir.bool) {
+    // CHECK-NOT: cir.alloca
+    // CHECK: %[[CONST:.*]] = cir.const #cir.int<11>
+    // CHECK: cir.ternary
+    // CHECK: cir.call @use(%[[CONST]])
+    // CHECK: cir.call @use(%[[CONST]])
+    // CHECK-NOT: cir.load
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.const #cir.int<11> : !s32i
+    cir.store %1, %0 : !s32i, !cir.ptr<!s32i>
+    %2 = cir.ternary(%cond, true {
+      %3 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+      cir.call @use(%3) : (!s32i) -> ()
+      cir.yield %3 : !s32i
+    }, false {
+      %4 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+      cir.call @use(%4) : (!s32i) -> ()
+      cir.yield %4 : !s32i
+    }) : (!cir.bool) -> !s32i
+    cir.return
+  }
+
+  // CHECK-LABEL: cir.func @store_in_ternary
+  cir.func @store_in_ternary(%cond: !cir.bool) {
+    // CHECK: cir.alloca
+    // CHECK: cir.ternary
+    // CHECK: cir.store
+    // CHECK: cir.load
+    %0 = cir.alloca "a" align(4) : !cir.ptr<!s32i>
+    %1 = cir.const #cir.int<1> : !s32i
+    cir.store %1, %0 : !s32i, !cir.ptr<!s32i>
+    %2 = cir.ternary(%cond, true {
+      %3 = cir.const #cir.int<2> : !s32i
+      cir.store %3, %0 : !s32i, !cir.ptr<!s32i>
+      cir.yield %3 : !s32i
+    }, false {
+      %4 = cir.const #cir.int<0> : !s32i
+      cir.yield %4 : !s32i
+    }) : (!cir.bool) -> !s32i
+    %5 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+    cir.call @use(%5) : (!s32i) -> ()
+    cir.return
+  }
+
   cir.func private @use(!s32i)
 }



More information about the cfe-commits mailing list