Skip to content

[flang] Fix volatile attribute propagation on allocatables #139183

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 13, 2025

Conversation

ashermancinelli
Copy link
Contributor

Ensure volatility is reflected not just on the reference to an allocatable, but on the box, too. When we declare a volatile allocatable, we now get a volatile reference to a volatile box.

Some related cleanups:

  • SELECT TYPE constructs check the selector's type for volatility when creating and designating the type used in the selecting block.
  • Refine the verifier for fir.convert. In general, I think it is ok to implicitly drop volatility in any ptr-to-int conversion because it means we are in codegen (and representing volatility on the LLVM ops and intrinsics) or we are calling an external function (are there any cases I'm not thinking of?)
  • An allocatable test that was XFAILed is now passing. Making allocatables' boxes volatile resulted in accesses of those boxes being volatile, which resolved some errors coming from the strict verifier.
  • I noticed a runtime function was missing the fir.runtime attribute.

@ashermancinelli ashermancinelli self-assigned this May 9, 2025
@llvmbot llvmbot added flang Flang issues not falling into any other category flang:fir-hlfir labels May 9, 2025
@llvmbot
Copy link
Member

llvmbot commented May 9, 2025

@llvm/pr-subscribers-flang-fir-hlfir

Author: Asher Mancinelli (ashermancinelli)

Changes

Ensure volatility is reflected not just on the reference to an allocatable, but on the box, too. When we declare a volatile allocatable, we now get a volatile reference to a volatile box.

Some related cleanups:

  • SELECT TYPE constructs check the selector's type for volatility when creating and designating the type used in the selecting block.
  • Refine the verifier for fir.convert. In general, I think it is ok to implicitly drop volatility in any ptr-to-int conversion because it means we are in codegen (and representing volatility on the LLVM ops and intrinsics) or we are calling an external function (are there any cases I'm not thinking of?)
  • An allocatable test that was XFAILed is now passing. Making allocatables' boxes volatile resulted in accesses of those boxes being volatile, which resolved some errors coming from the strict verifier.
  • I noticed a runtime function was missing the fir.runtime attribute.

Patch is 28.10 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/139183.diff

7 Files Affected:

  • (modified) flang/lib/Lower/Bridge.cpp (+12-4)
  • (modified) flang/lib/Optimizer/Dialect/FIROps.cpp (+41-10)
  • (modified) flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp (+10-5)
  • (modified) flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp (+5-1)
  • (modified) flang/test/Fir/invalid.fir (+2-2)
  • (modified) flang/test/Lower/volatile-allocatable.f90 (+11-10)
  • (modified) flang/test/Lower/volatile-allocatable1.f90 (+16-1)
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 0a61f61ab8f75..3706dcf37a204 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3845,6 +3845,10 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     bool hasLocalScope = false;
     llvm::SmallVector<const Fortran::semantics::Scope *> typeCaseScopes;
 
+    const auto selectorIsVolatile = [&selector]() {
+      return fir::isa_volatile_type(fir::getBase(selector).getType());
+    };
+
     const auto &typeCaseList =
         std::get<std::list<Fortran::parser::SelectTypeConstruct::TypeCase>>(
             selectTypeConstruct.t);
@@ -3998,7 +4002,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
             addrTy = fir::HeapType::get(addrTy);
           if (std::holds_alternative<Fortran::parser::IntrinsicTypeSpec>(
                   typeSpec->u)) {
-            mlir::Type refTy = fir::ReferenceType::get(addrTy);
+            mlir::Type refTy =
+                fir::ReferenceType::get(addrTy, selectorIsVolatile());
             if (isPointer || isAllocatable)
               refTy = addrTy;
             exactValue = builder->create<fir::BoxAddrOp>(
@@ -4007,7 +4012,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
                 typeSpec->declTypeSpec->AsIntrinsic();
             if (isArray) {
               mlir::Value exact = builder->create<fir::ConvertOp>(
-                  loc, fir::BoxType::get(addrTy), fir::getBase(selector));
+                  loc, fir::BoxType::get(addrTy, selectorIsVolatile()),
+                  fir::getBase(selector));
               addAssocEntitySymbol(selectorBox->clone(exact));
             } else if (intrinsic->category() ==
                        Fortran::common::TypeCategory::Character) {
@@ -4022,7 +4028,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
           } else if (std::holds_alternative<Fortran::parser::DerivedTypeSpec>(
                          typeSpec->u)) {
             exactValue = builder->create<fir::ConvertOp>(
-                loc, fir::BoxType::get(addrTy), fir::getBase(selector));
+                loc, fir::BoxType::get(addrTy, selectorIsVolatile()),
+                fir::getBase(selector));
             addAssocEntitySymbol(selectorBox->clone(exactValue));
           }
         } else if (std::holds_alternative<Fortran::parser::DerivedTypeSpec>(
@@ -4040,7 +4047,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
             addrTy = fir::PointerType::get(addrTy);
           if (isAllocatable)
             addrTy = fir::HeapType::get(addrTy);
-          mlir::Type classTy = fir::ClassType::get(addrTy);
+          mlir::Type classTy =
+              fir::ClassType::get(addrTy, selectorIsVolatile());
           if (classTy == baseTy) {
             addAssocEntitySymbol(selector);
           } else {
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 332cca1ab9f95..75185e719393f 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -1536,20 +1536,51 @@ bool fir::ConvertOp::canBeConverted(mlir::Type inType, mlir::Type outType) {
          areRecordsCompatible(inType, outType);
 }
 
+// In general, ptrtoint-like conversions are allowed to lose volatility
+// information because they are either:
+//
+// 1. passing an entity to an external function and there's nothing we can do
+//    about volatility after that happens, or
+// 2. for code generation, at which point we represent volatility with
+// attributes
+//    on the LLVM instructions and intrinsics.
+//
+// For all other cases, volatility ought to match exactly.
+static mlir::LogicalResult verifyVolatility(mlir::Type inType,
+                                            mlir::Type outType) {
+  const bool toLLVMPointer = mlir::isa<mlir::LLVM::LLVMPointerType>(outType);
+  const bool toInteger = fir::isa_integer(outType);
+
+  // When converting references to classes or allocatables into boxes for
+  // runtime arguments, we cast away all the volatility information and pass a
+  // box<none>. This is allowed.
+  const bool isBoxNoneLike = [&]() {
+    if (fir::isBoxNone(outType))
+      return true;
+    if (auto referenceType = mlir::dyn_cast<fir::ReferenceType>(outType)) {
+      if (fir::isBoxNone(referenceType.getElementType())) {
+        return true;
+      }
+    }
+    return false;
+  }();
+
+  const bool isPtrToIntLike = toLLVMPointer || toInteger || isBoxNoneLike;
+  if (isPtrToIntLike) {
+    return mlir::success();
+  }
+
+  // In all other cases, we need to check for an exact volatility match.
+  return mlir::success(fir::isa_volatile_type(inType) ==
+                       fir::isa_volatile_type(outType));
+}
+
 llvm::LogicalResult fir::ConvertOp::verify() {
   mlir::Type inType = getValue().getType();
   mlir::Type outType = getType();
-  // If we're converting to an LLVM pointer type or an integer, we don't
-  // need to check for volatility mismatch - volatility will be handled by the
-  // memory operations themselves in llvm code generation and ptr-to-int can't
-  // represent volatility.
-  const bool toLLVMPointer = mlir::isa<mlir::LLVM::LLVMPointerType>(outType);
-  const bool toInteger = fir::isa_integer(outType);
   if (fir::useStrictVolatileVerification()) {
-    if (fir::isa_volatile_type(inType) != fir::isa_volatile_type(outType) &&
-        !toLLVMPointer && !toInteger) {
-      return emitOpError("cannot convert between volatile and non-volatile "
-                         "types, use fir.volatile_cast instead ")
+    if (failed(verifyVolatility(inType, outType))) {
+      return emitOpError("this conversion does not preserve volatility: ")
              << inType << " / " << outType;
     }
   }
diff --git a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
index 711d5d1461b08..52517eef2890d 100644
--- a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
+++ b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
@@ -207,20 +207,25 @@ static bool hasExplicitLowerBounds(mlir::Value shape) {
          mlir::isa<fir::ShapeShiftType, fir::ShiftType>(shape.getType());
 }
 
-static std::pair<mlir::Type, mlir::Value> updateDeclareInputTypeWithVolatility(
+static std::pair<mlir::Type, mlir::Value> updateDeclaredInputTypeWithVolatility(
     mlir::Type inputType, mlir::Value memref, mlir::OpBuilder &builder,
     fir::FortranVariableFlagsAttr fortran_attrs) {
   if (fortran_attrs &&
       bitEnumContainsAny(fortran_attrs.getFlags(),
                          fir::FortranVariableFlagsEnum::fortran_volatile)) {
+    // A volatile pointer's pointee is volatile.
     const bool isPointer = bitEnumContainsAny(
         fortran_attrs.getFlags(), fir::FortranVariableFlagsEnum::pointer);
+    // An allocatable's inner type's volatility matches that of the reference.
+    const bool isAllocatable = bitEnumContainsAny(
+        fortran_attrs.getFlags(), fir::FortranVariableFlagsEnum::allocatable);
     auto updateType = [&](auto t) {
       using FIRT = decltype(t);
-      // A volatile pointer's pointee is volatile.
       auto elementType = t.getEleTy();
-      const bool elementTypeIsVolatile =
-          isPointer || fir::isa_volatile_type(elementType);
+      const bool elementTypeIsBox = mlir::isa<fir::BoxType>(elementType);
+      const bool elementTypeIsVolatile = isPointer || isAllocatable ||
+                                         elementTypeIsBox ||
+                                         fir::isa_volatile_type(elementType);
       auto newEleTy =
           fir::updateTypeWithVolatility(elementType, elementTypeIsVolatile);
       inputType = FIRT::get(newEleTy, true);
@@ -243,7 +248,7 @@ void hlfir::DeclareOp::build(mlir::OpBuilder &builder,
   auto nameAttr = builder.getStringAttr(uniq_name);
   mlir::Type inputType = memref.getType();
   bool hasExplicitLbs = hasExplicitLowerBounds(shape);
-  std::tie(inputType, memref) = updateDeclareInputTypeWithVolatility(
+  std::tie(inputType, memref) = updateDeclaredInputTypeWithVolatility(
       inputType, memref, builder, fortran_attrs);
   mlir::Type hlfirVariableType =
       getHLFIRVariableType(inputType, hasExplicitLbs);
diff --git a/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp b/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp
index 0c78a878cdc53..f9a4c4d0283c7 100644
--- a/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp
+++ b/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp
@@ -401,10 +401,14 @@ llvm::LogicalResult SelectTypeConv::genTypeLadderStep(
     {
       // Since conversion is done in parallel for each fir.select_type
       // operation, the runtime function insertion must be threadsafe.
+      auto runtimeAttr =
+          mlir::NamedAttribute(fir::FIROpsDialect::getFirRuntimeAttrName(),
+                               mlir::UnitAttr::get(rewriter.getContext()));
       callee =
           fir::createFuncOp(rewriter.getUnknownLoc(), mod, fctName,
                             rewriter.getFunctionType({descNoneTy, typeDescTy},
-                                                     rewriter.getI1Type()));
+                                                     rewriter.getI1Type()),
+                            {runtimeAttr});
     }
     cmp = rewriter
               .create<fir::CallOp>(loc, callee,
diff --git a/flang/test/Fir/invalid.fir b/flang/test/Fir/invalid.fir
index 1de48b87365b3..834eea7df8ebe 100644
--- a/flang/test/Fir/invalid.fir
+++ b/flang/test/Fir/invalid.fir
@@ -1260,7 +1260,7 @@ func.func @dc_invalid_reduction(%arg0: index, %arg1: index) {
 
 // Should fail when volatility changes from a fir.convert
 func.func @bad_convert_volatile(%arg0: !fir.ref<i32>) -> !fir.ref<i32, volatile> {
-  // expected-error@+1 {{'fir.convert' op cannot convert between volatile and non-volatile types, use fir.volatile_cast instead}}
+  // expected-error@+1 {{op this conversion does not preserve volatilit}}
   %0 = fir.convert %arg0 : (!fir.ref<i32>) -> !fir.ref<i32, volatile>
   return %0 : !fir.ref<i32, volatile>
 }
@@ -1269,7 +1269,7 @@ func.func @bad_convert_volatile(%arg0: !fir.ref<i32>) -> !fir.ref<i32, volatile>
 
 // Should fail when volatility changes from a fir.convert
 func.func @bad_convert_volatile2(%arg0: !fir.ref<i32, volatile>) -> !fir.ref<i32> {
-  // expected-error@+1 {{'fir.convert' op cannot convert between volatile and non-volatile types, use fir.volatile_cast instead}}
+  // expected-error@+1 {{op this conversion does not preserve volatility}}
   %0 = fir.convert %arg0 : (!fir.ref<i32, volatile>) -> !fir.ref<i32>
   return %0 : !fir.ref<i32>
 }
diff --git a/flang/test/Lower/volatile-allocatable.f90 b/flang/test/Lower/volatile-allocatable.f90
index 5f75a5425422a..e182fe8a4d9c9 100644
--- a/flang/test/Lower/volatile-allocatable.f90
+++ b/flang/test/Lower/volatile-allocatable.f90
@@ -119,10 +119,10 @@ subroutine test_unlimited_polymorphic()
 end subroutine
 
 ! CHECK-LABEL:   func.func @_QPtest_scalar_volatile() {
-! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_scalar_volatileEc1"} : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>, volatile>) -> (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>, volatile>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>, volatile>)
-! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_scalar_volatileEv1"} : (!fir.ref<!fir.class<!fir.heap<!fir.type<{{.*}}>>>, volatile>) -> (!fir.ref<!fir.class<!fir.heap<!fir.type<{{.*}}>>>, volatile>, !fir.ref<!fir.class<!fir.heap<!fir.type<{{.*}}>>>, volatile>)
-! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_scalar_volatileEv2"} : (!fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>>, volatile>) -> (!fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>>, volatile>, !fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>>, volatile>)
-! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_scalar_volatileEv3"} : (!fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>>, volatile>) -> (!fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>>, volatile>, !fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>>, volatile>)
+! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_scalar_volatileEc1"} : (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>, volatile>, volatile>) -> (!fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>, volatile>, volatile>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>, volatile>, volatile>)
+! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_scalar_volatileEv1"} : (!fir.ref<!fir.class<!fir.heap<!fir.type<{{.*}}>>, volatile>, volatile>) -> (!fir.ref<!fir.class<!fir.heap<!fir.type<{{.*}}>>, volatile>, volatile>, !fir.ref<!fir.class<!fir.heap<!fir.type<{{.*}}>>, volatile>, volatile>)
+! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_scalar_volatileEv2"} : (!fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>, volatile>, volatile>) -> (!fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>, volatile>, volatile>, !fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>, volatile>, volatile>)
+! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_scalar_volatileEv3"} : (!fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>, volatile>, volatile>) -> (!fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>, volatile>, volatile>, !fir.ref<!fir.box<!fir.heap<!fir.type<{{.*}}>>, volatile>, volatile>)
 ! CHECK:           fir.call @_FortranAAllocatableInitDerivedForAllocate(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.ref<none>, i32, i32) -> ()
 ! CHECK:           %{{.+}} = fir.call @_FortranAAllocatableAllocate(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, i64, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
 ! CHECK:           fir.call @_FortranAAllocatableInitDerivedForAllocate(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.ref<none>, i32, i32) -> ()
@@ -140,8 +140,8 @@ subroutine test_unlimited_polymorphic()
 ! CHECK:           %{{.+}} = fir.call @_FortranAAllocatableDeallocate(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
 ! CHECK:           %{{.+}} = fir.call @_FortranAAllocatableDeallocatePolymorphic(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.ref<none>, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
 ! CHECK-LABEL:   func.func @_QPtest_volatile_asynchronous() {
-! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, asynchronous, volatile>, uniq_name = "_QFtest_volatile_asynchronousEi1"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>, volatile>) -> (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>, volatile>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>, volatile>)
-! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, asynchronous, volatile>, uniq_name = "_QFtest_volatile_asynchronousEv1"} : (!fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>>, volatile>) -> (!fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>>, volatile>, !fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>>, volatile>)
+! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, asynchronous, volatile>, uniq_name = "_QFtest_volatile_asynchronousEi1"} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>, volatile>, volatile>) -> (!fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>, volatile>, volatile>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>, volatile>, volatile>)
+! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, asynchronous, volatile>, uniq_name = "_QFtest_volatile_asynchronousEv1"} : (!fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>, volatile>) -> (!fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>, volatile>, !fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>, volatile>)
 ! CHECK:           fir.call @_FortranAAllocatableInitDerivedForAllocate(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.ref<none>, i32, i32) -> ()
 ! CHECK:           fir.call @_FortranAAllocatableSetBounds(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, i32, i64, i64) -> ()
 ! CHECK:           %{{.+}} = fir.call @_FortranAAllocatableAllocate(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, i64, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
@@ -151,10 +151,11 @@ subroutine test_unlimited_polymorphic()
 ! CHECK:           %{{.+}} = fir.call @_FortranAAllocatableDeallocatePolymorphic(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.ref<none>, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
 ! CHECK:           %{{.+}} = fir.call @_FortranAAllocatableDeallocatePolymorphic(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.ref<none>, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
 ! CHECK-LABEL:   func.func @_QPtest_select_base_type_volatile() {
-! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_select_base_type_volatileEv"} : (!fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>>, volatile>) -> (!fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>>, volatile>, !fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>>, volatile>)
+! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}} {fortran_attrs = #fir.var_attrs<allocatable, volatile>, uniq_name = "_QFtest_select_base_type_volatileEv"} : (!fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>, volatile>) -> (!fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>, volatile>, !fir.ref<!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>, volatile>)
 ! CHECK:           fir.call @_FortranAAllocatableInitDerivedForAllocate(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.ref<none>, i32, i32) -> ()
 ! CHECK:           fir.call @_FortranAAllocatableSetBounds(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, i32, i64, i64) -> ()
 ! CHECK:           %{{.+}} = fir.call @_FortranAAllocatableAllocate(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, i64, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
+! CHECK:           %{{.+}} = fir.call @_FortranAClassIs(%{{.+}}, %{{.+}}) : (!fir.box<none>, !fir.ref<none>) -> i1
 ! CHECK:           %{{.+}}:2 = hlfir.declare %{{.+}}(%{{.+}}) {fortran_attrs = #fir.var_attrs<volatile>, uniq_name = "_QFtest_select_base_type_volatileEv"} : (!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>, !fir.shift<1>) -> (!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>, !fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>)
 ! CHECK:           %{{.+}} = hlfir.designate %{{.+}}#0 (%{{.+}})  : (!fir.class<!fir.heap<!fir.array<?x!fir.type<{{.*}}>>>, volatile>, index) -> !fir.class<!fir.type<{{.*}}>, volatile>
 ! CHECK:           %{{.+}} = hlfir.designate %{{.+}}{"i"}   : (!fir.class<!fir.type<{{.*}}>, volatile>) -> !fir.ref<i32, volatile>
@@ -162,7 +163,7 @@ subroutine test_unlimited_polymorphic()
 ! CHECK:           %{{.+}} = fir.call @_FortranAAllocatableDeallocatePolymorphic(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) fastmath<contract> : (!fir.ref<!fir.box<none>>, !fir.ref<none>, i1, !fir.box<none>, !fir.ref<i8>, i32) -> i32
 ! CHECK-LABEL:   func.func @_QPtest_mold_allocation() {
 ! CHECK:           %{{.+}}:2 = h...
[truncated]

Copy link
Contributor

@jeanPerier jeanPerier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM.

If you need special handling in the SELECT TYPE, do you also need something for SELECT RANK?

@ashermancinelli
Copy link
Contributor Author

Thanks, LGTM.

If you need special handling in the SELECT TYPE, do you also need something for SELECT RANK?

I see the SELECT RANK cases and statements using only simple reference types, which I handled in earlier patches. I had not propagated volatile on CLASSes in SELECT TYPE because I had not yet enabled the propagation of the VOLATILE attribute to class entities when creating designators. This patch brings SELECT TYPE in line with what is already in SELECT RANK, but I'll reread those cases in the bridge to see if I'm missing anything. Thanks!

Copy link
Contributor

@tblah tblah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

Ensure volatility is reflected not just on the reference of an allocatable,
but on the box, too. When we designate a volatile allocatable, we now get
a volatile reference to a volatile box.

Some related cleanups:
* SELECT TYPE constructs properly handle volatility by checking the
  selector's type for volatility when creating the target type.
* Refine the verifier for fir.convert. In general, I think it should be
  ok to implicitly drop volatility in any ptr-to-int conversion because
  it means we are in codegen or we are calling an external function, and
  it's okay to drop volatility from the Fir type system in these cases.
* An allocatable test that was XFAILed is now passing.
* I noticed a runtime function was missing the fir.runtime attribute. Fix that.
@ashermancinelli ashermancinelli force-pushed the ajm/volatile-allocatable branch from 664243c to 0345444 Compare May 12, 2025 14:10
@ashermancinelli ashermancinelli merged commit bbb7f01 into llvm:main May 13, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flang:fir-hlfir flang Flang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants