[flang-commits] [flang] [flang] Promote scalar slots reached through fir.convert and fir.declare (PR #219314)

Vijay Kandiah via flang-commits flang-commits at lists.llvm.org
Mon Aug 31 08:55:34 PDT 2026


================
@@ -2135,6 +2136,131 @@ llvm::LogicalResult fir::ConvertOp::verify() {
          << getValue().getType() << " / " << getType();
 }
 
+/// Pointee of a reference to a simple scalar, or null. Both fir.ref and rank-0
+/// memref qualify, since the storage of a scalar is cast between those forms.
+static mlir::Type getScalarSlotPointeeType(mlir::Type type) {
+  mlir::Type eleTy;
+  if (auto refTy = mlir::dyn_cast<fir::ReferenceType>(type)) {
+    eleTy = refTy.getEleTy();
+  } else if (auto memrefTy = mlir::dyn_cast<mlir::MemRefType>(type)) {
+    // A rank, layout or memory space lets the cast reinterpret the storage.
+    if (memrefTy.getRank() != 0 || !memrefTy.getLayout().isIdentity() ||
+        memrefTy.getMemorySpace())
+      return {};
+    eleTy = memrefTy.getElementType();
+  } else {
+    return {};
+  }
+  if (!mlir::isa<mlir::IntegerType, mlir::FloatType, mlir::ComplexType,
+                 fir::LogicalType>(eleTy))
+    return {};
+  return eleTy;
+}
+
+/// The value a cast or declare aliases, or null if `value` is not one of those.
+static mlir::Value getAliasedSlotPointer(mlir::Value value) {
+  mlir::Operation *def = value.getDefiningOp();
+  if (auto convert = mlir::dyn_cast_or_null<fir::ConvertOp>(def))
+    return convert.getValue();
+  if (auto declare = mlir::dyn_cast_or_null<fir::DeclareOp>(def))
+    return declare.getMemref();
+  return {};
+}
+
+/// Checks whether every read of the slot reached through `pointer` has a write
+/// to it earlier in the same block. Otherwise promotion replaces the read with
+/// the allocation's default value, which is fir.undefined for fir.alloca but
+/// poison for a memref allocation, so promoting through a cast would change
+/// what reading an uninitialized variable does.
+static bool everyReadFollowsAWrite(mlir::Value pointer, mlir::Type pointee) {
+  // Start at the root of the chain, to see writes through a sibling alias.
+  while (mlir::Value aliased = getAliasedSlotPointer(pointer)) {
+    if (getScalarSlotPointeeType(aliased.getType()) != pointee)
+      break;
+    pointer = aliased;
+  }
+
+  llvm::SmallVector<mlir::Operation *> reads;
+  llvm::DenseMap<mlir::Block *, mlir::Operation *> firstWrite;
+  llvm::SmallVector<mlir::Value> worklist{pointer};
+  llvm::SmallPtrSet<mlir::Value, 8> visited{pointer};
+  while (!worklist.empty()) {
+    for (mlir::OpOperand &use : worklist.pop_back_val().getUses()) {
+      mlir::Operation *user = use.getOwner();
+      if (mlir::isa<fir::ConvertOp, fir::DeclareOp>(user)) {
+        mlir::Value alias = user->getResult(0);
+        // A different pointee is another slot, which blocks promotion itself.
+        if (getScalarSlotPointeeType(alias.getType()) == pointee &&
+            visited.insert(alias).second)
+          worklist.push_back(alias);
+        continue;
+      }
+      // A user that is not a promotable access blocks promotion itself.
+      if (auto memOp = mlir::dyn_cast<mlir::PromotableMemOpInterface>(user)) {
+        mlir::MemorySlot slot{use.get(), pointee};
+        if (memOp.storesTo(slot)) {
+          mlir::Operation *&earliest = firstWrite[user->getBlock()];
+          if (!earliest || user->isBeforeInBlock(earliest))
+            earliest = user;
+        } else if (memOp.loadsFrom(slot)) {
+          reads.push_back(user);
+        }
+      }
+    }
+  }
+
+  return llvm::all_of(reads, [&firstWrite](mlir::Operation *read) {
+    auto write = firstWrite.find(read->getBlock());
+    return write != firstWrite.end() && write->second->isBeforeInBlock(read);
+  });
----------------
VijayKandiah wrote:

It was not intentional. In my recent commit, I switched to `DominanceInfo`, so cross-block writes and variables initialized before a loop and updated inside it also promote. The conditionally stored shape stays rejected intentionally because that's where a read with no dominating write became ub.poison and resulted in some crashes I was seeing in real workloads. 

https://github.com/llvm/llvm-project/pull/219314


More information about the flang-commits mailing list