Author: jeanPerier Date: 2026-06-17T12:12:10+02:00 New Revision: 85ec3af65318d5efe5922932ac5fc80ab518e602
URL: https://github.com/llvm/llvm-project/commit/85ec3af65318d5efe5922932ac5fc80ab518e602 DIFF: https://github.com/llvm/llvm-project/commit/85ec3af65318d5efe5922932ac5fc80ab518e602.diff LOG: Revert "[flang][mem2reg] promote memory slots through declares (#196975)" This reverts commit c1ec4b3c79967ae5ef824f7194540f6529405a03. Added: Modified: flang/include/flang/Optimizer/Dialect/FIROps.h flang/include/flang/Optimizer/Dialect/FIROps.td flang/lib/Optimizer/Dialect/FIROps.cpp flang/test/Fir/mem2reg.mlir Removed: ################################################################################ diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.h b/flang/include/flang/Optimizer/Dialect/FIROps.h index 54aa3d58abe8c..9d771b1e60604 100644 --- a/flang/include/flang/Optimizer/Dialect/FIROps.h +++ b/flang/include/flang/Optimizer/Dialect/FIROps.h @@ -19,7 +19,6 @@ #include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/LLVMIR/LLVMAttrs.h" #include "mlir/Interfaces/LoopLikeInterface.h" -#include "mlir/Interfaces/MemorySlotInterfaces.h" #include "mlir/Interfaces/SideEffectInterfaces.h" #include "mlir/Interfaces/ViewLikeInterface.h" diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td index 5a17db1c0ef1e..35a87c29d0cb6 100644 --- a/flang/include/flang/Optimizer/Dialect/FIROps.td +++ b/flang/include/flang/Optimizer/Dialect/FIROps.td @@ -3070,15 +3070,9 @@ def fir_VolatileCastOp } def fir_ConvertOp - : fir_SimpleOneResultOp<"convert", - [NoMemoryEffect, ViewLikeOpInterface, - ConditionallySpeculatable, - DeclareOpInterfaceMethods<PromotableOpInterface>, - DeclareOpInterfaceMethods< - PromotableAliaserInterface, - ["projectSlotValueToAliasValue", - "projectAliasValueToSlotValue"]>, - fir_FortranObjectViewOpInterface]> { + : fir_SimpleOneResultOp<"convert", [NoMemoryEffect, ViewLikeOpInterface, + ConditionallySpeculatable, + fir_FortranObjectViewOpInterface]> { let summary = "encapsulates all Fortran entity type conversions"; let description = [{ @@ -3607,7 +3601,6 @@ def fir_DeclareOp DeclareOpInterfaceMethods<PromotableOpInterface, ["requiresReplacedValues", "visitReplacedValues"]>, - DeclareOpInterfaceMethods<PromotableAliaserInterface>, fir_FortranObjectViewOpInterface]> { let summary = "declare a variable"; diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp index 6a64b76736cf5..1d61989eba6cf 100644 --- a/flang/lib/Optimizer/Dialect/FIROps.cpp +++ b/flang/lib/Optimizer/Dialect/FIROps.cpp @@ -2012,89 +2012,6 @@ void fir::ConvertOp::getCanonicalizationPatterns( context); } -// Returns the pointee element type of a FIR reference-like type or a memref; -// returns null if `ty` is not pointer-like or has no extractable element -// type (e.g. unranked memref, box). -static mlir::Type slotPointeeElemType(mlir::Type ty) { - if (mlir::Type fEle = fir::dyn_cast_ptrEleTy(ty)) - return fEle; - if (auto memrefTy = mlir::dyn_cast<mlir::MemRefType>(ty)) - return memrefTy.getElementType(); - return {}; -} - -// Returns true if a `fir.convert` between pointers with element types -// `srcElem` and `dstElem` is a no-op at the value level or can be replaced by -// a bicast. -static bool isAcceptableSlotElemTypePair(mlir::Type srcElem, - mlir::Type dstElem) { - if (srcElem == dstElem) - return true; - if (!isBitcastCompatibleType(srcElem) || !isBitcastCompatibleType(dstElem)) - return false; - auto srcBits = getBitcastBitSize(srcElem); - auto dstBits = getBitcastBitSize(dstElem); - return srcBits && dstBits && *srcBits == *dstBits; -} - -static bool isPromotableSlotAliasConvert(fir::ConvertOp op) { - mlir::Type srcElem = slotPointeeElemType(op.getValue().getType()); - mlir::Type dstElem = slotPointeeElemType(op.getResult().getType()); - return srcElem && dstElem && isAcceptableSlotElemTypePair(srcElem, dstElem); -} - -void fir::ConvertOp::getPromotableSlotAliases( - mlir::OpOperand &aliasedSlotPointerOperand, - const mlir::MemorySlot & /*parentSlot*/, - llvm::SmallVectorImpl<mlir::MemorySlot> &newMemorySlots) { - if (aliasedSlotPointerOperand.get() != getValue()) - return; - if (!isPromotableSlotAliasConvert(*this)) - return; - mlir::Type dstElem = slotPointeeElemType(getResult().getType()); - newMemorySlots.push_back(mlir::MemorySlot{getResult(), dstElem}); -} - -mlir::Value fir::ConvertOp::projectSlotValueToAliasValue( - mlir::OpOperand & /*aliasedSlotPointerOperand*/, - const mlir::MemorySlot & /*parentSlot*/, const mlir::MemorySlot &aliasSlot, - mlir::Value slotValue, mlir::OpBuilder &builder) { - if (slotValue.getType() == aliasSlot.elemType) - return slotValue; - return fir::BitcastOp::create(builder, getLoc(), aliasSlot.elemType, - slotValue); -} - -mlir::Value fir::ConvertOp::projectAliasValueToSlotValue( - mlir::OpOperand & /*aliasedSlotPointerOperand*/, - const mlir::MemorySlot &parentSlot, const mlir::MemorySlot & /*aliasSlot*/, - mlir::Value aliasValue, mlir::Value /*reachingDef*/, - mlir::OpBuilder &builder) { - if (aliasValue.getType() == parentSlot.elemType) - return aliasValue; - return fir::BitcastOp::create(builder, getLoc(), parentSlot.elemType, - aliasValue); -} - -bool fir::ConvertOp::canUsesBeRemoved( - const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, - mlir::SmallVectorImpl<mlir::OpOperand *> &newBlockingUses, - const mlir::DataLayout &dataLayout) { - // Only participate in promotion when this convert is a slot alias (if the - // address cast is not used, DCE should have removed it). - if (!isPromotableSlotAliasConvert(*this)) - return false; - for (mlir::OpOperand &use : getResult().getUses()) - newBlockingUses.push_back(&use); - return true; -} - -mlir::DeletionKind fir::ConvertOp::removeBlockingUses( - const mlir::SmallPtrSetImpl<mlir::OpOperand *> &blockingUses, - mlir::OpBuilder &builder) { - return mlir::DeletionKind::Delete; -} - static bool isI1(mlir::Type ty) { if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(ty)) return intTy.getWidth() == 1; @@ -3671,8 +3588,17 @@ llvm::SmallVector<mlir::Attribute> fir::LenParamIndexOp::getAttributes() { // LoadOp //===----------------------------------------------------------------------===// +static bool isSlotOrDeclaredSlot(mlir::Value val, + const mlir::MemorySlot &slot) { + if (val == slot.ptr) + return true; + if (auto declareOp = val.getDefiningOp<fir::DeclareOp>()) + return declareOp.getMemref() == slot.ptr; + return false; +} + bool fir::LoadOp::loadsFrom(const mlir::MemorySlot &slot) { - return getMemref() == slot.ptr; + return isSlotOrDeclaredSlot(getMemref(), slot); } bool fir::LoadOp::storesTo(const mlir::MemorySlot &slot) { return false; } @@ -3692,8 +3618,7 @@ bool fir::LoadOp::canUsesBeRemoved( if (blockingUses.size() != 1) return false; mlir::Value blockingUse = (*blockingUses.begin())->get(); - return blockingUse == slot.ptr && getMemref() == slot.ptr && - getResult().getType() == slot.elemType; + return isSlotOrDeclaredSlot(blockingUse, slot) && getMemref() == blockingUse; } mlir::DeletionKind fir::LoadOp::removeBlockingUses( @@ -5243,7 +5168,7 @@ llvm::LogicalResult fir::SliceOp::verify() { bool fir::StoreOp::loadsFrom(const mlir::MemorySlot &slot) { return false; } bool fir::StoreOp::storesTo(const mlir::MemorySlot &slot) { - return getMemref() == slot.ptr; + return isSlotOrDeclaredSlot(getMemref(), slot); } mlir::Value fir::StoreOp::getStored(const mlir::MemorySlot &slot, @@ -5261,8 +5186,8 @@ bool fir::StoreOp::canUsesBeRemoved( if (blockingUses.size() != 1) return false; mlir::Value blockingUse = (*blockingUses.begin())->get(); - return blockingUse == slot.ptr && getMemref() == slot.ptr && - getValue() != slot.ptr && getValue().getType() == slot.elemType; + return isSlotOrDeclaredSlot(blockingUse, slot) && + getMemref() == blockingUse && getValue() != blockingUse; } mlir::DeletionKind fir::StoreOp::removeBlockingUses( @@ -6125,8 +6050,17 @@ bool fir::DeclareOp::canUsesBeRemoved( const mlir::DataLayout &dataLayout) { if (!isLegalTypeForValueDeclare(fir::unwrapRefType(getType()))) return false; - for (mlir::OpOperand &use : getResult().getUses()) + // MLIR's mem2reg computes defining blocks only from direct users of + // the slot pointer. Stores through fir.declare are not direct users, + // so they are not registered as defining blocks. This causes missing + // phi nodes at join points (e.g., loop headers). Restrict promotion + // to the single-block case where no phi nodes are needed. + mlir::Block *declBlock = getOperation()->getBlock(); + for (mlir::OpOperand &use : getResult().getUses()) { + if (use.getOwner()->getBlock() != declBlock) + return false; newBlockingUses.push_back(&use); + } return true; } @@ -6136,38 +6070,17 @@ mlir::DeletionKind fir::DeclareOp::removeBlockingUses( return mlir::DeletionKind::Delete; } -void fir::DeclareOp::getPromotableSlotAliases( - mlir::OpOperand &aliasedSlotPointerOperand, - const mlir::MemorySlot & /*parentSlot*/, - llvm::SmallVectorImpl<mlir::MemorySlot> &newMemorySlots) { - if (aliasedSlotPointerOperand.get() != getMemref()) - return; - // fir.declare is a transparent alias of its memref operand at the same - // element type. - mlir::Type aliasElemType = fir::dyn_cast_ptrEleTy(getResult().getType()); - if (!aliasElemType) - return; - newMemorySlots.push_back(mlir::MemorySlot{getResult(), aliasElemType}); -} - bool fir::DeclareOp::requiresReplacedValues() { return true; } void fir::DeclareOp::visitReplacedValues( llvm::ArrayRef<std::pair<mlir::Operation *, mlir::Value>> definitions, mlir::OpBuilder &builder) { - // TODO: extend `fir.declare_value` to carry the variable's declared type - // independently of the value's type. Once that is in place the type - // mismatch check below can be dropped (a bit-cast equivalent will be - // recorded on the op instead of skipping emission). - mlir::Type declaredElemType = fir::dyn_cast_ptrEleTy(getResult().getType()); for (auto [op, value] : definitions) { // Do not emit DeclareValue when we have a dummy scope as this can // potentially result in us generating it where the DummyScope does not // dominate it. This can happen after inlining. if (getDummyScope()) continue; - if (value.getType() != declaredElemType) - continue; builder.setInsertionPointAfter(op); fir::DeclareValueOp::create(builder, getLoc(), value, nullptr, getUniqNameAttr(), getFortranAttrsAttr(), diff --git a/flang/test/Fir/mem2reg.mlir b/flang/test/Fir/mem2reg.mlir index 2aaf856c620d8..154580c626e7c 100644 --- a/flang/test/Fir/mem2reg.mlir +++ b/flang/test/Fir/mem2reg.mlir @@ -158,24 +158,17 @@ func.func @box_not_mem2reg(%arg0: !fir.ref<!fir.box<f32>> {fir.bindc_name = "i"} // ----- -// Conditional store in a diff erent block through fir.declare is promoted: -// the through-declare store is discovered as a defining block via the -// `getPromotableSlotView` interface and a block argument is added at the -// merge point. +// Conditional store in a diff erent block through fir.declare is not promoted +// because MLIR mem2reg would not place the needed phi nodes correctly. // CHECK-LABEL: func.func @block_argument_value( // CHECK-SAME: %[[ARG0:.*]]: i32, // CHECK-SAME: %[[ARG1:.*]]: i1) -> i32 { -// CHECK-NOT: fir.alloca -// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>) -// CHECK: %[[C42:.*]] = arith.constant 42 : i32 -// CHECK: fir.declare_value %[[C42]] {{.*}} -// CHECK: llvm.cond_br %[[ARG1]], ^bb1, ^bb2(%[[C42]] : i32) -// CHECK: ^bb1: -// CHECK: fir.declare_value %[[ARG0]] {{.*}} -// CHECK: llvm.br ^bb2(%[[ARG0]] : i32) -// CHECK: ^bb2(%[[MERGE:.*]]: i32): -// CHECK: return %[[MERGE]] : i32 +// CHECK: fir.alloca i32 +// CHECK: fir.declare +// CHECK: fir.store +// CHECK: fir.store +// CHECK: fir.load func.func @block_argument_value(%arg0: i32, %cdt: i1) -> i32 { %c42_i32 = arith.constant 42 : i32 %3 = fir.alloca i32 {bindc_name = "jlocal", uniq_name = "_QFfooEjlocal"} @@ -192,26 +185,19 @@ func.func @block_argument_value(%arg0: i32, %cdt: i1) -> i32 { // ----- -// Conditional store inside a loop through fir.declare is promoted: the -// loop header gets a block argument carrying the conditional update from -// the loop body. This is the case that motivated the previous same-block -// restriction in fir::DeclareOp::canUsesBeRemoved. +// Conditional store inside a loop through fir.declare must not be promoted. +// MLIR's mem2reg does not register stores through declares as defining blocks, +// so phi nodes at the loop header would be missing, losing the update. // CHECK-LABEL: func.func @loop_conditional_update( // CHECK-SAME: %[[ARG0:.*]]: i32, // CHECK-SAME: %[[ARG1:.*]]: i1) -> i32 { -// CHECK-NOT: fir.alloca -// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>) -// CHECK: fir.declare_value %[[ARG0]] {{.*}} -// CHECK: llvm.br ^bb1(%[[ARG0]] : i32) -// CHECK: ^bb1(%[[LOOP_ARG:.*]]: i32): -// CHECK: llvm.cond_br %[[ARG1]], ^bb2, ^bb3 -// CHECK: ^bb2: -// CHECK: %[[NEW:.*]] = arith.subi %[[LOOP_ARG]], {{.*}} : i32 -// CHECK: fir.declare_value %[[NEW]] {{.*}} -// CHECK: llvm.br ^bb1(%[[NEW]] : i32) -// CHECK: ^bb3: -// CHECK: return %[[LOOP_ARG]] : i32 +// CHECK: fir.alloca i32 +// CHECK: fir.declare +// CHECK: fir.store +// CHECK: fir.load +// CHECK: fir.store +// CHECK: fir.load func.func @loop_conditional_update(%arg0: i32, %cdt: i1) -> i32 { %c1 = arith.constant 1 : i32 %alloca = fir.alloca i32 {bindc_name = "mywatch", uniq_name = "_QFkernelEmywatch"} @@ -232,171 +218,6 @@ func.func @loop_conditional_update(%arg0: i32, %cdt: i1) -> i32 { // ----- -// fir.convert at the same element type is a transparent view; the slot is -// fully promoted with no value conversion needed. - -// CHECK-LABEL: func.func @convert_same_type( -// CHECK-SAME: %[[ARG0:.*]]: i32) -> i32 { -// CHECK-NOT: fir.alloca -// CHECK-NOT: fir.convert -// CHECK: return %[[ARG0]] : i32 -func.func @convert_same_type(%arg0: i32) -> i32 { - %alloca = fir.alloca i32 - %ptr = fir.convert %alloca : (!fir.ref<i32>) -> !fir.ref<i32> - fir.store %arg0 to %ptr : !fir.ref<i32> - %v = fir.load %ptr : !fir.ref<i32> - return %v : i32 -} - -// ----- - -// A type-changing fir.convert exposes the slot at a diff erent element type; -// mem2reg materialises fir.bitcast value conversions at the store and load. - -// CHECK-LABEL: func.func @convert_type_changing( -// CHECK-SAME: %[[ARG0:.*]]: f32) -> f32 -// CHECK-NOT: fir.alloca -// CHECK: %[[I32:.*]] = fir.bitcast %[[ARG0]] : (f32) -> i32 -// CHECK: fir.bitcast %[[I32]] : (i32) -> f32 -// CHECK: return %{{.*}} : f32 -func.func @convert_type_changing(%arg0: f32) -> f32 { - %alloca = fir.alloca i32 - %ptr = fir.convert %alloca : (!fir.ref<i32>) -> !fir.ref<f32> - fir.store %arg0 to %ptr : !fir.ref<f32> - %v = fir.load %ptr : !fir.ref<f32> - return %v : f32 -} - -// ----- - -// Chained view: alloca -> fir.declare -> fir.convert -> load/store. The -// declare's element type (i32) diff ers from the value type the store sees -// at the leaf view (f32), so `fir.declare_value` is skipped until -// `fir.declare_value` can carry the declared type independently of the -// value type (see the TODO in fir::DeclareOp::visitReplacedValues). - -// CHECK-LABEL: func.func @declare_then_convert( -// CHECK-SAME: %[[ARG0:.*]]: f32) -> f32 -// CHECK-NOT: fir.alloca -// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>) -// CHECK-NOT: fir.declare_value -// CHECK: %[[I32:.*]] = fir.bitcast %[[ARG0]] : (f32) -> i32 -// CHECK: %[[F32:.*]] = fir.bitcast %[[I32]] : (i32) -> f32 -// CHECK: return %[[F32]] : f32 -func.func @declare_then_convert(%arg0: f32) -> f32 { - %alloca = fir.alloca i32 {bindc_name = "x", uniq_name = "_QFEx"} - %declare = fir.declare %alloca {uniq_name = "_QFEx"} : (!fir.ref<i32>) -> !fir.ref<i32> - %ptr = fir.convert %declare : (!fir.ref<i32>) -> !fir.ref<f32> - fir.store %arg0 to %ptr : !fir.ref<f32> - %v = fir.load %ptr : !fir.ref<f32> - return %v : f32 -} - -// ----- - -// Inverse chain: alloca -> fir.convert -> fir.declare -> load/store. The -// declare's memref is the result of a type-changing convert, so the -// declare sees the slot at f32 — the same type as the store value, so -// `fir.declare_value` is emitted. - -// CHECK-LABEL: func.func @convert_then_declare( -// CHECK-SAME: %[[ARG0:.*]]: f32) -> f32 -// CHECK-NOT: fir.alloca -// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<f32>) -// CHECK: %[[I32:.*]] = fir.bitcast %[[ARG0]] : (f32) -> i32 -// CHECK: fir.declare_value %[[ARG0]] {{.*}} : f32 -// CHECK: %[[F32:.*]] = fir.bitcast %[[I32]] : (i32) -> f32 -// CHECK: return %[[F32]] : f32 -func.func @convert_then_declare(%arg0: f32) -> f32 { - %alloca = fir.alloca i32 {bindc_name = "x", uniq_name = "_QFEx"} - %ptr = fir.convert %alloca : (!fir.ref<i32>) -> !fir.ref<f32> - %declare = fir.declare %ptr {uniq_name = "_QFEx"} : (!fir.ref<f32>) -> !fir.ref<f32> - fir.store %arg0 to %declare : !fir.ref<f32> - %v = fir.load %declare : !fir.ref<f32> - return %v : f32 -} - -// ----- - -// A memref.alloca slot with a mixed memref/FIR view chain -// (memref -> fir.convert -> fir.declare -> fir.convert -> memref). All -// element types are the same, so the slot promotes with no value -// conversions; only `fir.declare_value` is emitted from the declare. - -// CHECK-LABEL: func.func @memref_with_declare_chain( -// CHECK-SAME: %[[ARG0:.*]]: i32) -> i32 -// CHECK-NOT: memref.alloca -// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>) -// CHECK-NOT: fir.convert -// CHECK-NOT: memref.store -// CHECK-NOT: memref.load -// CHECK: fir.declare_value %[[ARG0]] {{.*}} : i32 -// CHECK: return %[[ARG0]] : i32 -func.func @memref_with_declare_chain(%arg0: i32) -> i32 { - %alloca = memref.alloca() : memref<i32> - %fref = fir.convert %alloca : (memref<i32>) -> !fir.ref<i32> - %decl = fir.declare %fref {uniq_name = "_QFEx"} : (!fir.ref<i32>) -> !fir.ref<i32> - %mref = fir.convert %decl : (!fir.ref<i32>) -> memref<i32> - memref.store %arg0, %mref[] : memref<i32> - %v = memref.load %mref[] : memref<i32> - return %v : i32 -} - -// ----- - -// Same mixed memref/FIR chain with a conditional store across blocks. The -// merge-point block argument is at the root slot's element type (i32), and -// `fir.declare_value` is emitted at each store site. - -// CHECK-LABEL: func.func @memref_with_declare_chain_blocks( -// CHECK-SAME: %[[ARG0:.*]]: i32, -// CHECK-SAME: %[[COND:.*]]: i1) -> i32 -// CHECK-NOT: memref.alloca -// CHECK-NOT: fir.declare {{.*}} : (!fir.ref<i32>) -// CHECK: %[[C42:.*]] = arith.constant 42 : i32 -// CHECK: fir.declare_value %[[C42]] {{.*}} : i32 -// CHECK: cf.cond_br %[[COND]], ^[[BB1:.*]], ^[[BB2:.*]](%[[C42]] : i32) -// CHECK: ^[[BB1]]: -// CHECK: fir.declare_value %[[ARG0]] {{.*}} : i32 -// CHECK: cf.br ^[[BB2]](%[[ARG0]] : i32) -// CHECK: ^[[BB2]](%[[MERGE:.*]]: i32): -// CHECK: return %[[MERGE]] : i32 -func.func @memref_with_declare_chain_blocks(%arg0: i32, %cond: i1) -> i32 { - %c42 = arith.constant 42 : i32 - %alloca = memref.alloca() : memref<i32> - %fref = fir.convert %alloca : (memref<i32>) -> !fir.ref<i32> - %decl = fir.declare %fref {uniq_name = "_QFEx"} : (!fir.ref<i32>) -> !fir.ref<i32> - %mref = fir.convert %decl : (!fir.ref<i32>) -> memref<i32> - memref.store %c42, %mref[] : memref<i32> - cf.cond_br %cond, ^bb1, ^bb2 -^bb1: - memref.store %arg0, %mref[] : memref<i32> - cf.br ^bb2 -^bb2: - %v = memref.load %mref[] : memref<i32> - return %v : i32 -} - -// ----- - -// A ref->integer fir.convert is not a memory view; it must block promotion. - -// CHECK-LABEL: func.func @convert_to_integer_blocks_promotion( -// CHECK: %[[ALLOCA:.*]] = fir.alloca i32 -// CHECK: fir.store -// CHECK: %[[INT:.*]] = fir.convert %[[ALLOCA]] : (!fir.ref<i32>) -> i64 -// CHECK: fir.call @use_addr(%[[INT]]) -func.func private @use_addr(%a: i64) -func.func @convert_to_integer_blocks_promotion(%arg0: i32) { - %alloca = fir.alloca i32 - fir.store %arg0 to %alloca : !fir.ref<i32> - %addr = fir.convert %alloca : (!fir.ref<i32>) -> i64 - fir.call @use_addr(%addr) : (i64) -> () - return -} - -// ----- - // Make sure we do not generate fir.declare_value for a replaced value // fir.declare with dummy_scope. This can result in the declare_value being // inserted before the dummy_scope it uses as would be the case here. _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
