Skip to content

[flang] Add lowering of volatile references #132486

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 27 commits into from
Apr 30, 2025

Conversation

ashermancinelli
Copy link
Contributor

@ashermancinelli ashermancinelli commented Mar 21, 2025

RFC on discourse

Flang currently lacks support for volatile variables. For some cases, the compiler produces TODO error messages and others are ignored. Some of our tests are like the example from C.4 Clause 8 notes: The VOLATILE attribute (8.5.20) and require volatile variables.

Prior commits:

c9ec1bc753b0 [flang] Handle volatility in lowering and codegen (#135311)
e42f8609858f [flang][nfc] Support volatility in Fir ops (#134858)
b2711e1526f9 [flang][nfc] Support volatile on ref, box, and class types (#134386)

@llvmbot
Copy link
Member

llvmbot commented Mar 21, 2025

@llvm/pr-subscribers-flang-codegen

Author: Asher Mancinelli (ashermancinelli)

Changes

RFC on discourse

Flang currently lacks support for volatile variables. For some cases, the compiler produces TODO error messages and others are ignored. Some of our tests are like the example from C.4 Clause 8 notes: The VOLATILE attribute (8.5.20) and require volatile variables.

This change is a minimal draft of support for volatility in Fortran. This PR does not include some important features, like support for volatility on boxes and other non-reference reference-like types. This commit only supports volatility for !fir.ref<T> and is the minimum needed to get end-to-end examples working to see if this is the right direction.

If this is the right direction, I'll break this up into a few chunks, add more tests, and share a smaller PR.


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

15 Files Affected:

  • (modified) flang/include/flang/Optimizer/Builder/FIRBuilder.h (+1-1)
  • (modified) flang/include/flang/Optimizer/Dialect/FIRType.h (+6)
  • (modified) flang/include/flang/Optimizer/Dialect/FIRTypes.td (+7-3)
  • (modified) flang/lib/Lower/CallInterface.cpp (-1)
  • (modified) flang/lib/Lower/ConvertExprToHLFIR.cpp (+42-6)
  • (modified) flang/lib/Optimizer/Builder/FIRBuilder.cpp (+2-2)
  • (modified) flang/lib/Optimizer/Builder/HLFIRTools.cpp (+2-1)
  • (modified) flang/lib/Optimizer/CodeGen/CodeGen.cpp (+12-5)
  • (modified) flang/lib/Optimizer/Dialect/FIRType.cpp (+43-14)
  • (modified) flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp (+7)
  • (modified) flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp (+5-2)
  • (modified) flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp (+6-3)
  • (added) flang/test/Fir/volatile.fir (+18)
  • (added) flang/test/Integration/volatile.f90 (+11)
  • (added) flang/test/Lower/volatile.fir (+21)
diff --git a/flang/include/flang/Optimizer/Builder/FIRBuilder.h b/flang/include/flang/Optimizer/Builder/FIRBuilder.h
index 003b4358572c1..870709a5d55b6 100644
--- a/flang/include/flang/Optimizer/Builder/FIRBuilder.h
+++ b/flang/include/flang/Optimizer/Builder/FIRBuilder.h
@@ -150,7 +150,7 @@ class FirOpBuilder : public mlir::OpBuilder, public mlir::OpBuilder::Listener {
   mlir::Block *getAllocaBlock();
 
   /// Safely create a reference type to the type `eleTy`.
-  mlir::Type getRefType(mlir::Type eleTy);
+  mlir::Type getRefType(mlir::Type eleTy, bool isVolatile = false);
 
   /// Create a sequence of `eleTy` with `rank` dimensions of unknown size.
   mlir::Type getVarLenSeqTy(mlir::Type eleTy, unsigned rank = 1);
diff --git a/flang/include/flang/Optimizer/Dialect/FIRType.h b/flang/include/flang/Optimizer/Dialect/FIRType.h
index 76e0aa352bcd9..8261c67e4559d 100644
--- a/flang/include/flang/Optimizer/Dialect/FIRType.h
+++ b/flang/include/flang/Optimizer/Dialect/FIRType.h
@@ -111,6 +111,12 @@ inline bool isa_ref_type(mlir::Type t) {
                    fir::LLVMPointerType>(t);
 }
 
+inline bool isa_volatile_ref_type(mlir::Type t) {
+  if (auto refTy = mlir::dyn_cast_or_null<fir::ReferenceType>(t))
+    return refTy.isVolatile();
+  return false;
+}
+
 /// Is `t` a boxed type?
 inline bool isa_box_type(mlir::Type t) {
   return mlir::isa<fir::BaseBoxType, fir::BoxCharType, fir::BoxProcType>(t);
diff --git a/flang/include/flang/Optimizer/Dialect/FIRTypes.td b/flang/include/flang/Optimizer/Dialect/FIRTypes.td
index fd5bbbe44751f..0584c175b36ff 100644
--- a/flang/include/flang/Optimizer/Dialect/FIRTypes.td
+++ b/flang/include/flang/Optimizer/Dialect/FIRTypes.td
@@ -363,18 +363,22 @@ def fir_ReferenceType : FIR_Type<"Reference", "ref"> {
     The type of a reference to an entity in memory.
   }];
 
-  let parameters = (ins "mlir::Type":$eleTy);
+  let parameters = (ins
+    "mlir::Type":$eleTy,
+    DefaultValuedParameter<"bool", "false">:$isVol);
 
   let skipDefaultBuilders = 1;
 
   let builders = [
-    TypeBuilderWithInferredContext<(ins "mlir::Type":$elementType), [{
-      return Base::get(elementType.getContext(), elementType);
+    TypeBuilderWithInferredContext<(ins "mlir::Type":$elementType, CArg<"bool", "false">:$isVol), [{
+      return Base::get(elementType.getContext(), elementType, isVol);
     }]>,
   ];
 
   let extraClassDeclaration = [{
     mlir::Type getElementType() const { return getEleTy(); }
+    bool isVolatile() const { return (bool)getIsVol(); }
+    static llvm::StringRef getVolatileKeyword() { return "volatile"; }
   }];
 
   let genVerifyDecl = 1;
diff --git a/flang/lib/Lower/CallInterface.cpp b/flang/lib/Lower/CallInterface.cpp
index 226ba1e52c968..4ee28fbeb9a0c 100644
--- a/flang/lib/Lower/CallInterface.cpp
+++ b/flang/lib/Lower/CallInterface.cpp
@@ -1112,7 +1112,6 @@ class Fortran::lower::CallInterfaceImpl {
     if (obj.attrs.test(Attrs::Value))
       isValueAttr = true; // TODO: do we want an mlir::Attribute as well?
     if (obj.attrs.test(Attrs::Volatile)) {
-      TODO(loc, "VOLATILE in procedure interface");
       addMLIRAttr(fir::getVolatileAttrName());
     }
     // obj.attrs.test(Attrs::Asynchronous) does not impact the way the argument
diff --git a/flang/lib/Lower/ConvertExprToHLFIR.cpp b/flang/lib/Lower/ConvertExprToHLFIR.cpp
index dc00e0b13f583..3ac10596df5ae 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -223,8 +223,37 @@ class HlfirDesignatorBuilder {
             designatorNode, getConverter().getFoldingContext(),
             /*namedConstantSectionsAreAlwaysContiguous=*/false))
       return fir::BoxType::get(resultValueType);
+
+    bool isVolatile = false;
+
+    // Check if the base type is volatile
+    if (partInfo.base.has_value()) {
+      mlir::Type baseType = partInfo.base.value().getType();
+      isVolatile = fir::isa_volatile_ref_type(baseType);
+    }
+
+    auto isVolatileSymbol = [](const Fortran::semantics::Symbol &symbol) {
+      return symbol.GetUltimate().attrs().test(
+          Fortran::semantics::Attr::VOLATILE);
+    };
+
+    // Check if this should be a volatile reference
+    if constexpr (std::is_same_v<std::decay_t<T>,
+                                 Fortran::evaluate::SymbolRef>) {
+      if (isVolatileSymbol(designatorNode.get()))
+        isVolatile = true;
+    } else if constexpr (std::is_same_v<std::decay_t<T>,
+                                        Fortran::evaluate::Component>) {
+      if (isVolatileSymbol(designatorNode.GetLastSymbol()))
+        isVolatile = true;
+    }
+
+    // If it's a reference to a ref, account for it
+    if (auto refTy = mlir::dyn_cast<fir::ReferenceType>(resultValueType))
+      resultValueType = refTy.getEleTy();
+
     // Other designators can be handled as raw addresses.
-    return fir::ReferenceType::get(resultValueType);
+    return fir::ReferenceType::get(resultValueType, isVolatile);
   }
 
   template <typename T>
@@ -414,10 +443,16 @@ class HlfirDesignatorBuilder {
         .Case<fir::SequenceType>([&](fir::SequenceType seqTy) -> mlir::Type {
           return fir::SequenceType::get(seqTy.getShape(), newEleTy);
         })
-        .Case<fir::PointerType, fir::HeapType, fir::ReferenceType, fir::BoxType,
-              fir::ClassType>([&](auto t) -> mlir::Type {
-          using FIRT = decltype(t);
-          return FIRT::get(changeElementType(t.getEleTy(), newEleTy));
+        // TODO: handle volatility for other types
+        .Case<fir::PointerType, fir::HeapType, fir::BoxType, fir::ClassType>(
+            [&](auto t) -> mlir::Type {
+              using FIRT = decltype(t);
+              return FIRT::get(changeElementType(t.getEleTy(), newEleTy));
+            })
+        .Case<fir::ReferenceType>([&](fir::ReferenceType refTy) -> mlir::Type {
+          return fir::ReferenceType::get(
+              changeElementType(refTy.getEleTy(), newEleTy),
+              refTy.isVolatile());
         })
         .Default([newEleTy](mlir::Type t) -> mlir::Type { return newEleTy; });
   }
@@ -1808,6 +1843,7 @@ class HlfirBuilder {
       auto &expr = std::get<const Fortran::lower::SomeExpr &>(iter);
       auto &baseOp = std::get<hlfir::EntityWithAttributes>(iter);
       std::string name = converter.getRecordTypeFieldName(sym);
+      const bool isVolatile = fir::isa_volatile_ref_type(baseOp.getType());
 
       // Generate DesignateOp for the component.
       // The designator's result type is just a reference to the component type,
@@ -1818,7 +1854,7 @@ class HlfirBuilder {
       assert(compType && "failed to retrieve component type");
       mlir::Value compShape =
           designatorBuilder.genComponentShape(sym, compType);
-      mlir::Type designatorType = builder.getRefType(compType);
+      mlir::Type designatorType = builder.getRefType(compType, isVolatile);
 
       mlir::Type fieldElemType = hlfir::getFortranElementType(compType);
       llvm::SmallVector<mlir::Value, 1> typeParams;
diff --git a/flang/lib/Optimizer/Builder/FIRBuilder.cpp b/flang/lib/Optimizer/Builder/FIRBuilder.cpp
index b7f8a8d3a9d56..02ded29606885 100644
--- a/flang/lib/Optimizer/Builder/FIRBuilder.cpp
+++ b/flang/lib/Optimizer/Builder/FIRBuilder.cpp
@@ -104,9 +104,9 @@ fir::FirOpBuilder::getNamedGlobal(mlir::ModuleOp modOp,
   return modOp.lookupSymbol<fir::GlobalOp>(name);
 }
 
-mlir::Type fir::FirOpBuilder::getRefType(mlir::Type eleTy) {
+mlir::Type fir::FirOpBuilder::getRefType(mlir::Type eleTy, bool isVolatile) {
   assert(!mlir::isa<fir::ReferenceType>(eleTy) && "cannot be a reference type");
-  return fir::ReferenceType::get(eleTy);
+  return fir::ReferenceType::get(eleTy, isVolatile);
 }
 
 mlir::Type fir::FirOpBuilder::getVarLenSeqTy(mlir::Type eleTy, unsigned rank) {
diff --git a/flang/lib/Optimizer/Builder/HLFIRTools.cpp b/flang/lib/Optimizer/Builder/HLFIRTools.cpp
index 85fd742db6beb..aec88ec97b514 100644
--- a/flang/lib/Optimizer/Builder/HLFIRTools.cpp
+++ b/flang/lib/Optimizer/Builder/HLFIRTools.cpp
@@ -819,7 +819,8 @@ mlir::Type hlfir::getVariableElementType(hlfir::Entity variable) {
   } else if (fir::isRecordWithTypeParameters(eleTy)) {
     return fir::BoxType::get(eleTy);
   }
-  return fir::ReferenceType::get(eleTy);
+  const bool isVolatile = fir::isa_volatile_ref_type(variable.getType());
+  return fir::ReferenceType::get(eleTy, isVolatile);
 }
 
 mlir::Type hlfir::getEntityElementType(hlfir::Entity entity) {
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index b54b497ee4ba1..90f2474dafca3 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -3224,6 +3224,8 @@ struct LoadOpConversion : public fir::FIROpConversion<fir::LoadOp> {
                   mlir::ConversionPatternRewriter &rewriter) const override {
 
     mlir::Type llvmLoadTy = convertObjectType(load.getType());
+    const bool isVolatile =
+        fir::isa_volatile_ref_type(load.getMemref().getType());
     if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(load.getType())) {
       // fir.box is a special case because it is considered an ssa value in
       // fir, but it is lowered as a pointer to a descriptor. So
@@ -3253,7 +3255,7 @@ struct LoadOpConversion : public fir::FIROpConversion<fir::LoadOp> {
       mlir::Value boxSize =
           computeBoxSize(loc, boxTypePair, inputBoxStorage, rewriter);
       auto memcpy = rewriter.create<mlir::LLVM::MemcpyOp>(
-          loc, newBoxStorage, inputBoxStorage, boxSize, /*isVolatile=*/false);
+          loc, newBoxStorage, inputBoxStorage, boxSize, isVolatile);
 
       if (std::optional<mlir::ArrayAttr> optionalTag = load.getTbaa())
         memcpy.setTBAATags(*optionalTag);
@@ -3261,8 +3263,10 @@ struct LoadOpConversion : public fir::FIROpConversion<fir::LoadOp> {
         attachTBAATag(memcpy, boxTy, boxTy, nullptr);
       rewriter.replaceOp(load, newBoxStorage);
     } else {
+      // TODO: are we losing any attributes from the load op?
+      auto memref = adaptor.getOperands()[0];
       auto loadOp = rewriter.create<mlir::LLVM::LoadOp>(
-          load.getLoc(), llvmLoadTy, adaptor.getOperands(), load->getAttrs());
+          load.getLoc(), llvmLoadTy, memref, /*alignment=*/0, isVolatile);
       if (std::optional<mlir::ArrayAttr> optionalTag = load.getTbaa())
         loadOp.setTBAATags(*optionalTag);
       else
@@ -3540,6 +3544,8 @@ struct StoreOpConversion : public fir::FIROpConversion<fir::StoreOp> {
     mlir::Value llvmValue = adaptor.getValue();
     mlir::Value llvmMemref = adaptor.getMemref();
     mlir::LLVM::AliasAnalysisOpInterface newOp;
+    const bool isVolatile =
+        fir::isa_volatile_ref_type(store.getMemref().getType());
     if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(storeTy)) {
       mlir::Type llvmBoxTy = lowerTy().convertBoxTypeAsStruct(boxTy);
       // Always use memcpy because LLVM is not as effective at optimizing
@@ -3547,10 +3553,11 @@ struct StoreOpConversion : public fir::FIROpConversion<fir::StoreOp> {
       TypePair boxTypePair{boxTy, llvmBoxTy};
       mlir::Value boxSize =
           computeBoxSize(loc, boxTypePair, llvmValue, rewriter);
-      newOp = rewriter.create<mlir::LLVM::MemcpyOp>(
-          loc, llvmMemref, llvmValue, boxSize, /*isVolatile=*/false);
+      newOp = rewriter.create<mlir::LLVM::MemcpyOp>(loc, llvmMemref, llvmValue,
+                                                    boxSize, isVolatile);
     } else {
-      newOp = rewriter.create<mlir::LLVM::StoreOp>(loc, llvmValue, llvmMemref);
+      newOp = rewriter.create<mlir::LLVM::StoreOp>(loc, llvmValue, llvmMemref,
+                                                   /*alignment=*/0, isVolatile);
     }
     if (std::optional<mlir::ArrayAttr> optionalTag = store.getTbaa())
       newOp.setTBAATags(*optionalTag);
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index f3f969ba401e5..90942522d9073 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -649,12 +649,17 @@ mlir::Type changeElementType(mlir::Type type, mlir::Type newElementType,
       .Case<fir::SequenceType>([&](fir::SequenceType seqTy) -> mlir::Type {
         return fir::SequenceType::get(seqTy.getShape(), newElementType);
       })
-      .Case<fir::PointerType, fir::HeapType, fir::ReferenceType,
-            fir::ClassType>([&](auto t) -> mlir::Type {
-        using FIRT = decltype(t);
-        return FIRT::get(
-            changeElementType(t.getEleTy(), newElementType, turnBoxIntoClass));
+      .Case<fir::ReferenceType>([&](fir::ReferenceType refTy) -> mlir::Type {
+        auto newEleTy = changeElementType(refTy.getEleTy(), newElementType,
+                                          turnBoxIntoClass);
+        return fir::ReferenceType::get(newEleTy, refTy.isVolatile());
       })
+      .Case<fir::PointerType, fir::HeapType, fir::ClassType>(
+          [&](auto t) -> mlir::Type {
+            using FIRT = decltype(t);
+            return FIRT::get(changeElementType(t.getEleTy(), newElementType,
+                                               turnBoxIntoClass));
+          })
       .Case<fir::BoxType>([&](fir::BoxType t) -> mlir::Type {
         mlir::Type newInnerType =
             changeElementType(t.getEleTy(), newElementType, false);
@@ -1057,18 +1062,38 @@ unsigned fir::RecordType::getFieldIndex(llvm::StringRef ident) {
 // ReferenceType
 //===----------------------------------------------------------------------===//
 
-// `ref` `<` type `>`
+// `ref` `<` type (`, volatile` $volatile^)? `>`
 mlir::Type fir::ReferenceType::parse(mlir::AsmParser &parser) {
-  return parseTypeSingleton<fir::ReferenceType>(parser);
+  if (parser.parseLess())
+    return {};
+
+  mlir::Type eleTy;
+  if (parser.parseType(eleTy))
+    return {};
+
+  bool isVolatile = false;
+  if (!parser.parseOptionalComma()) {
+    if (parser.parseKeyword(getVolatileKeyword())) {
+      return {};
+    }
+    isVolatile = true;
+  }
+
+  if (parser.parseGreater())
+    return {};
+  return get(eleTy, isVolatile);
 }
 
 void fir::ReferenceType::print(mlir::AsmPrinter &printer) const {
-  printer << "<" << getEleTy() << '>';
+  printer << "<" << getEleTy();
+  if (isVolatile())
+    printer << ", " << getVolatileKeyword();
+  printer << '>';
 }
 
 llvm::LogicalResult fir::ReferenceType::verify(
-    llvm::function_ref<mlir::InFlightDiagnostic()> emitError,
-    mlir::Type eleTy) {
+    llvm::function_ref<mlir::InFlightDiagnostic()> emitError, mlir::Type eleTy,
+    bool isVolatile) {
   if (mlir::isa<ShapeType, ShapeShiftType, SliceType, FieldType, LenType,
                 ReferenceType, TypeDescType>(eleTy))
     return emitError() << "cannot build a reference to type: " << eleTy << '\n';
@@ -1319,11 +1344,15 @@ changeTypeShape(mlir::Type type,
           return fir::SequenceType::get(*newShape, seqTy.getEleTy());
         return seqTy.getEleTy();
       })
-      .Case<fir::PointerType, fir::HeapType, fir::ReferenceType, fir::BoxType,
-            fir::ClassType>([&](auto t) -> mlir::Type {
-        using FIRT = decltype(t);
-        return FIRT::get(changeTypeShape(t.getEleTy(), newShape));
+      .Case<fir::ReferenceType>([&](fir::ReferenceType rt) -> mlir::Type {
+        return fir::ReferenceType::get(changeTypeShape(rt.getEleTy(), newShape),
+                                       rt.isVolatile());
       })
+      .Case<fir::PointerType, fir::HeapType, fir::BoxType, fir::ClassType>(
+          [&](auto t) -> mlir::Type {
+            using FIRT = decltype(t);
+            return FIRT::get(changeTypeShape(t.getEleTy(), newShape));
+          })
       .Default([&](mlir::Type t) -> mlir::Type {
         assert((fir::isa_trivial(t) || llvm::isa<fir::RecordType>(t) ||
                 llvm::isa<mlir::NoneType>(t) ||
diff --git a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
index 8851a3a7187b9..4a3308ff4e747 100644
--- a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
+++ b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
@@ -214,6 +214,13 @@ void hlfir::DeclareOp::build(mlir::OpBuilder &builder,
   auto nameAttr = builder.getStringAttr(uniq_name);
   mlir::Type inputType = memref.getType();
   bool hasExplicitLbs = hasExplicitLowerBounds(shape);
+  if (fortran_attrs && mlir::isa<fir::ReferenceType>(inputType) &&
+      bitEnumContainsAny(fortran_attrs.getFlags(),
+                         fir::FortranVariableFlagsEnum::fortran_volatile)) {
+    auto refType = mlir::cast<fir::ReferenceType>(inputType);
+    inputType = fir::ReferenceType::get(refType.getEleTy(), true);
+    memref = builder.create<fir::ConvertOp>(memref.getLoc(), inputType, memref);
+  }
   mlir::Type hlfirVariableType =
       getHLFIRVariableType(inputType, hasExplicitLbs);
   build(builder, result, {hlfirVariableType, inputType}, memref, shape,
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
index 496a5560ac615..aa151f90ed0d1 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
@@ -418,7 +418,9 @@ class DesignateOpConversion
       firstElementIndices.push_back(indices[i]);
       i = i + (isTriplet ? 3 : 1);
     }
-    mlir::Type arrayCoorType = fir::ReferenceType::get(baseEleTy);
+    mlir::Type originalDesignateType = designate.getResult().getType();
+    const bool isVolatile = fir::isa_volatile_ref_type(originalDesignateType);
+    mlir::Type arrayCoorType = fir::ReferenceType::get(baseEleTy, isVolatile);
     base = builder.create<fir::ArrayCoorOp>(
         loc, arrayCoorType, base, shape,
         /*slice=*/mlir::Value{}, firstElementIndices, firBaseTypeParameters);
@@ -441,6 +443,7 @@ class DesignateOpConversion
       TODO(loc, "hlfir::designate load of pointer or allocatable");
 
     mlir::Type designateResultType = designate.getResult().getType();
+    const bool isVolatile = fir::isa_volatile_ref_type(designateResultType);
     llvm::SmallVector<mlir::Value> firBaseTypeParameters;
     auto [base, shape] = hlfir::genVariableFirBaseShapeAndParams(
         loc, builder, baseEntity, firBaseTypeParameters);
@@ -464,7 +467,7 @@ class DesignateOpConversion
         mlir::Type componentType =
             mlir::cast<fir::RecordType>(baseEleTy).getType(
                 designate.getComponent().value());
-        mlir::Type coorTy = fir::ReferenceType::get(componentType);
+        mlir::Type coorTy = fir::ReferenceType::get(componentType, isVolatile);
         base = builder.create<fir::CoordinateOp>(loc, coorTy, base, fieldIndex);
         if (mlir::isa<fir::BaseBoxType>(componentType)) {
           auto variableInterface = mlir::cast<fir::FortranVariableOpInterface>(
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp b/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp
index 96a3622f4afee..020915179a670 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp
@@ -1126,7 +1126,8 @@ class ReductionMaskConversion : public mlir::OpRewritePattern<Op> {
       builder.create<fir::StoreOp>(loc, flagSet, flagRef);
       mlir::Type resultElemTy =
           hlfir::getFortranElementType(resultArr.getType());
-      mlir::Type returnRefTy = builder.getRefType(resultElemTy);
+      mlir::Type returnRefTy = builder.getRefType(
+          resultElemTy, fir::isa_volatile_ref_type(flagRef.getType()));
       mlir::IndexType idxTy = builder.getIndexType();
 
       for (unsigned int i = 0; i < rank; ++i) {
@@ -1153,7 +1154,8 @@ class ReductionMaskConversion : public mlir::OpRewritePattern<Op> {
     auto getAddrFn = [](fir::FirOpBuilder builder, mlir::Location loc,
                         const mlir::Type &resultElemType, mlir::Value resultArr,
                         mlir::Value index) {
-      mlir::Type resultRefTy = builder.getRefType(resultElemType);
+      mlir::Type resultRefTy = builder.getRefType(
+          resultElemType, fir::isa_volatile_ref_type(resultArr.getType()));
       mlir::Value oneIdx =
           builder.createIntegerConstant(loc, builder.getIndexType(), 1);
       index = builder.create<mlir::arith::AddIOp>(loc, index, oneIdx);
@@ -1162,8 +1164,9 @@ class ReductionMaskConversion : public mlir::OpRewritePattern<Op> {
     };
 
     // Initialize the resu...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Mar 21, 2025

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

Author: Asher Mancinelli (ashermancinelli)

Changes

RFC on discourse

Flang currently lacks support for volatile variables. For some cases, the compiler produces TODO error messages and others are ignored. Some of our tests are like the example from C.4 Clause 8 notes: The VOLATILE attribute (8.5.20) and require volatile variables.

This change is a minimal draft of support for volatility in Fortran. This PR does not include some important features, like support for volatility on boxes and other non-reference reference-like types. This commit only supports volatility for !fir.ref&lt;T&gt; and is the minimum needed to get end-to-end examples working to see if this is the right direction.

If this is the right direction, I'll break this up into a few chunks, add more tests, and share a smaller PR.


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

15 Files Affected:

  • (modified) flang/include/flang/Optimizer/Builder/FIRBuilder.h (+1-1)
  • (modified) flang/include/flang/Optimizer/Dialect/FIRType.h (+6)
  • (modified) flang/include/flang/Optimizer/Dialect/FIRTypes.td (+7-3)
  • (modified) flang/lib/Lower/CallInterface.cpp (-1)
  • (modified) flang/lib/Lower/ConvertExprToHLFIR.cpp (+42-6)
  • (modified) flang/lib/Optimizer/Builder/FIRBuilder.cpp (+2-2)
  • (modified) flang/lib/Optimizer/Builder/HLFIRTools.cpp (+2-1)
  • (modified) flang/lib/Optimizer/CodeGen/CodeGen.cpp (+12-5)
  • (modified) flang/lib/Optimizer/Dialect/FIRType.cpp (+43-14)
  • (modified) flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp (+7)
  • (modified) flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp (+5-2)
  • (modified) flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp (+6-3)
  • (added) flang/test/Fir/volatile.fir (+18)
  • (added) flang/test/Integration/volatile.f90 (+11)
  • (added) flang/test/Lower/volatile.fir (+21)
diff --git a/flang/include/flang/Optimizer/Builder/FIRBuilder.h b/flang/include/flang/Optimizer/Builder/FIRBuilder.h
index 003b4358572c1..870709a5d55b6 100644
--- a/flang/include/flang/Optimizer/Builder/FIRBuilder.h
+++ b/flang/include/flang/Optimizer/Builder/FIRBuilder.h
@@ -150,7 +150,7 @@ class FirOpBuilder : public mlir::OpBuilder, public mlir::OpBuilder::Listener {
   mlir::Block *getAllocaBlock();
 
   /// Safely create a reference type to the type `eleTy`.
-  mlir::Type getRefType(mlir::Type eleTy);
+  mlir::Type getRefType(mlir::Type eleTy, bool isVolatile = false);
 
   /// Create a sequence of `eleTy` with `rank` dimensions of unknown size.
   mlir::Type getVarLenSeqTy(mlir::Type eleTy, unsigned rank = 1);
diff --git a/flang/include/flang/Optimizer/Dialect/FIRType.h b/flang/include/flang/Optimizer/Dialect/FIRType.h
index 76e0aa352bcd9..8261c67e4559d 100644
--- a/flang/include/flang/Optimizer/Dialect/FIRType.h
+++ b/flang/include/flang/Optimizer/Dialect/FIRType.h
@@ -111,6 +111,12 @@ inline bool isa_ref_type(mlir::Type t) {
                    fir::LLVMPointerType>(t);
 }
 
+inline bool isa_volatile_ref_type(mlir::Type t) {
+  if (auto refTy = mlir::dyn_cast_or_null<fir::ReferenceType>(t))
+    return refTy.isVolatile();
+  return false;
+}
+
 /// Is `t` a boxed type?
 inline bool isa_box_type(mlir::Type t) {
   return mlir::isa<fir::BaseBoxType, fir::BoxCharType, fir::BoxProcType>(t);
diff --git a/flang/include/flang/Optimizer/Dialect/FIRTypes.td b/flang/include/flang/Optimizer/Dialect/FIRTypes.td
index fd5bbbe44751f..0584c175b36ff 100644
--- a/flang/include/flang/Optimizer/Dialect/FIRTypes.td
+++ b/flang/include/flang/Optimizer/Dialect/FIRTypes.td
@@ -363,18 +363,22 @@ def fir_ReferenceType : FIR_Type<"Reference", "ref"> {
     The type of a reference to an entity in memory.
   }];
 
-  let parameters = (ins "mlir::Type":$eleTy);
+  let parameters = (ins
+    "mlir::Type":$eleTy,
+    DefaultValuedParameter<"bool", "false">:$isVol);
 
   let skipDefaultBuilders = 1;
 
   let builders = [
-    TypeBuilderWithInferredContext<(ins "mlir::Type":$elementType), [{
-      return Base::get(elementType.getContext(), elementType);
+    TypeBuilderWithInferredContext<(ins "mlir::Type":$elementType, CArg<"bool", "false">:$isVol), [{
+      return Base::get(elementType.getContext(), elementType, isVol);
     }]>,
   ];
 
   let extraClassDeclaration = [{
     mlir::Type getElementType() const { return getEleTy(); }
+    bool isVolatile() const { return (bool)getIsVol(); }
+    static llvm::StringRef getVolatileKeyword() { return "volatile"; }
   }];
 
   let genVerifyDecl = 1;
diff --git a/flang/lib/Lower/CallInterface.cpp b/flang/lib/Lower/CallInterface.cpp
index 226ba1e52c968..4ee28fbeb9a0c 100644
--- a/flang/lib/Lower/CallInterface.cpp
+++ b/flang/lib/Lower/CallInterface.cpp
@@ -1112,7 +1112,6 @@ class Fortran::lower::CallInterfaceImpl {
     if (obj.attrs.test(Attrs::Value))
       isValueAttr = true; // TODO: do we want an mlir::Attribute as well?
     if (obj.attrs.test(Attrs::Volatile)) {
-      TODO(loc, "VOLATILE in procedure interface");
       addMLIRAttr(fir::getVolatileAttrName());
     }
     // obj.attrs.test(Attrs::Asynchronous) does not impact the way the argument
diff --git a/flang/lib/Lower/ConvertExprToHLFIR.cpp b/flang/lib/Lower/ConvertExprToHLFIR.cpp
index dc00e0b13f583..3ac10596df5ae 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -223,8 +223,37 @@ class HlfirDesignatorBuilder {
             designatorNode, getConverter().getFoldingContext(),
             /*namedConstantSectionsAreAlwaysContiguous=*/false))
       return fir::BoxType::get(resultValueType);
+
+    bool isVolatile = false;
+
+    // Check if the base type is volatile
+    if (partInfo.base.has_value()) {
+      mlir::Type baseType = partInfo.base.value().getType();
+      isVolatile = fir::isa_volatile_ref_type(baseType);
+    }
+
+    auto isVolatileSymbol = [](const Fortran::semantics::Symbol &symbol) {
+      return symbol.GetUltimate().attrs().test(
+          Fortran::semantics::Attr::VOLATILE);
+    };
+
+    // Check if this should be a volatile reference
+    if constexpr (std::is_same_v<std::decay_t<T>,
+                                 Fortran::evaluate::SymbolRef>) {
+      if (isVolatileSymbol(designatorNode.get()))
+        isVolatile = true;
+    } else if constexpr (std::is_same_v<std::decay_t<T>,
+                                        Fortran::evaluate::Component>) {
+      if (isVolatileSymbol(designatorNode.GetLastSymbol()))
+        isVolatile = true;
+    }
+
+    // If it's a reference to a ref, account for it
+    if (auto refTy = mlir::dyn_cast<fir::ReferenceType>(resultValueType))
+      resultValueType = refTy.getEleTy();
+
     // Other designators can be handled as raw addresses.
-    return fir::ReferenceType::get(resultValueType);
+    return fir::ReferenceType::get(resultValueType, isVolatile);
   }
 
   template <typename T>
@@ -414,10 +443,16 @@ class HlfirDesignatorBuilder {
         .Case<fir::SequenceType>([&](fir::SequenceType seqTy) -> mlir::Type {
           return fir::SequenceType::get(seqTy.getShape(), newEleTy);
         })
-        .Case<fir::PointerType, fir::HeapType, fir::ReferenceType, fir::BoxType,
-              fir::ClassType>([&](auto t) -> mlir::Type {
-          using FIRT = decltype(t);
-          return FIRT::get(changeElementType(t.getEleTy(), newEleTy));
+        // TODO: handle volatility for other types
+        .Case<fir::PointerType, fir::HeapType, fir::BoxType, fir::ClassType>(
+            [&](auto t) -> mlir::Type {
+              using FIRT = decltype(t);
+              return FIRT::get(changeElementType(t.getEleTy(), newEleTy));
+            })
+        .Case<fir::ReferenceType>([&](fir::ReferenceType refTy) -> mlir::Type {
+          return fir::ReferenceType::get(
+              changeElementType(refTy.getEleTy(), newEleTy),
+              refTy.isVolatile());
         })
         .Default([newEleTy](mlir::Type t) -> mlir::Type { return newEleTy; });
   }
@@ -1808,6 +1843,7 @@ class HlfirBuilder {
       auto &expr = std::get<const Fortran::lower::SomeExpr &>(iter);
       auto &baseOp = std::get<hlfir::EntityWithAttributes>(iter);
       std::string name = converter.getRecordTypeFieldName(sym);
+      const bool isVolatile = fir::isa_volatile_ref_type(baseOp.getType());
 
       // Generate DesignateOp for the component.
       // The designator's result type is just a reference to the component type,
@@ -1818,7 +1854,7 @@ class HlfirBuilder {
       assert(compType && "failed to retrieve component type");
       mlir::Value compShape =
           designatorBuilder.genComponentShape(sym, compType);
-      mlir::Type designatorType = builder.getRefType(compType);
+      mlir::Type designatorType = builder.getRefType(compType, isVolatile);
 
       mlir::Type fieldElemType = hlfir::getFortranElementType(compType);
       llvm::SmallVector<mlir::Value, 1> typeParams;
diff --git a/flang/lib/Optimizer/Builder/FIRBuilder.cpp b/flang/lib/Optimizer/Builder/FIRBuilder.cpp
index b7f8a8d3a9d56..02ded29606885 100644
--- a/flang/lib/Optimizer/Builder/FIRBuilder.cpp
+++ b/flang/lib/Optimizer/Builder/FIRBuilder.cpp
@@ -104,9 +104,9 @@ fir::FirOpBuilder::getNamedGlobal(mlir::ModuleOp modOp,
   return modOp.lookupSymbol<fir::GlobalOp>(name);
 }
 
-mlir::Type fir::FirOpBuilder::getRefType(mlir::Type eleTy) {
+mlir::Type fir::FirOpBuilder::getRefType(mlir::Type eleTy, bool isVolatile) {
   assert(!mlir::isa<fir::ReferenceType>(eleTy) && "cannot be a reference type");
-  return fir::ReferenceType::get(eleTy);
+  return fir::ReferenceType::get(eleTy, isVolatile);
 }
 
 mlir::Type fir::FirOpBuilder::getVarLenSeqTy(mlir::Type eleTy, unsigned rank) {
diff --git a/flang/lib/Optimizer/Builder/HLFIRTools.cpp b/flang/lib/Optimizer/Builder/HLFIRTools.cpp
index 85fd742db6beb..aec88ec97b514 100644
--- a/flang/lib/Optimizer/Builder/HLFIRTools.cpp
+++ b/flang/lib/Optimizer/Builder/HLFIRTools.cpp
@@ -819,7 +819,8 @@ mlir::Type hlfir::getVariableElementType(hlfir::Entity variable) {
   } else if (fir::isRecordWithTypeParameters(eleTy)) {
     return fir::BoxType::get(eleTy);
   }
-  return fir::ReferenceType::get(eleTy);
+  const bool isVolatile = fir::isa_volatile_ref_type(variable.getType());
+  return fir::ReferenceType::get(eleTy, isVolatile);
 }
 
 mlir::Type hlfir::getEntityElementType(hlfir::Entity entity) {
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index b54b497ee4ba1..90f2474dafca3 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -3224,6 +3224,8 @@ struct LoadOpConversion : public fir::FIROpConversion<fir::LoadOp> {
                   mlir::ConversionPatternRewriter &rewriter) const override {
 
     mlir::Type llvmLoadTy = convertObjectType(load.getType());
+    const bool isVolatile =
+        fir::isa_volatile_ref_type(load.getMemref().getType());
     if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(load.getType())) {
       // fir.box is a special case because it is considered an ssa value in
       // fir, but it is lowered as a pointer to a descriptor. So
@@ -3253,7 +3255,7 @@ struct LoadOpConversion : public fir::FIROpConversion<fir::LoadOp> {
       mlir::Value boxSize =
           computeBoxSize(loc, boxTypePair, inputBoxStorage, rewriter);
       auto memcpy = rewriter.create<mlir::LLVM::MemcpyOp>(
-          loc, newBoxStorage, inputBoxStorage, boxSize, /*isVolatile=*/false);
+          loc, newBoxStorage, inputBoxStorage, boxSize, isVolatile);
 
       if (std::optional<mlir::ArrayAttr> optionalTag = load.getTbaa())
         memcpy.setTBAATags(*optionalTag);
@@ -3261,8 +3263,10 @@ struct LoadOpConversion : public fir::FIROpConversion<fir::LoadOp> {
         attachTBAATag(memcpy, boxTy, boxTy, nullptr);
       rewriter.replaceOp(load, newBoxStorage);
     } else {
+      // TODO: are we losing any attributes from the load op?
+      auto memref = adaptor.getOperands()[0];
       auto loadOp = rewriter.create<mlir::LLVM::LoadOp>(
-          load.getLoc(), llvmLoadTy, adaptor.getOperands(), load->getAttrs());
+          load.getLoc(), llvmLoadTy, memref, /*alignment=*/0, isVolatile);
       if (std::optional<mlir::ArrayAttr> optionalTag = load.getTbaa())
         loadOp.setTBAATags(*optionalTag);
       else
@@ -3540,6 +3544,8 @@ struct StoreOpConversion : public fir::FIROpConversion<fir::StoreOp> {
     mlir::Value llvmValue = adaptor.getValue();
     mlir::Value llvmMemref = adaptor.getMemref();
     mlir::LLVM::AliasAnalysisOpInterface newOp;
+    const bool isVolatile =
+        fir::isa_volatile_ref_type(store.getMemref().getType());
     if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(storeTy)) {
       mlir::Type llvmBoxTy = lowerTy().convertBoxTypeAsStruct(boxTy);
       // Always use memcpy because LLVM is not as effective at optimizing
@@ -3547,10 +3553,11 @@ struct StoreOpConversion : public fir::FIROpConversion<fir::StoreOp> {
       TypePair boxTypePair{boxTy, llvmBoxTy};
       mlir::Value boxSize =
           computeBoxSize(loc, boxTypePair, llvmValue, rewriter);
-      newOp = rewriter.create<mlir::LLVM::MemcpyOp>(
-          loc, llvmMemref, llvmValue, boxSize, /*isVolatile=*/false);
+      newOp = rewriter.create<mlir::LLVM::MemcpyOp>(loc, llvmMemref, llvmValue,
+                                                    boxSize, isVolatile);
     } else {
-      newOp = rewriter.create<mlir::LLVM::StoreOp>(loc, llvmValue, llvmMemref);
+      newOp = rewriter.create<mlir::LLVM::StoreOp>(loc, llvmValue, llvmMemref,
+                                                   /*alignment=*/0, isVolatile);
     }
     if (std::optional<mlir::ArrayAttr> optionalTag = store.getTbaa())
       newOp.setTBAATags(*optionalTag);
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index f3f969ba401e5..90942522d9073 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -649,12 +649,17 @@ mlir::Type changeElementType(mlir::Type type, mlir::Type newElementType,
       .Case<fir::SequenceType>([&](fir::SequenceType seqTy) -> mlir::Type {
         return fir::SequenceType::get(seqTy.getShape(), newElementType);
       })
-      .Case<fir::PointerType, fir::HeapType, fir::ReferenceType,
-            fir::ClassType>([&](auto t) -> mlir::Type {
-        using FIRT = decltype(t);
-        return FIRT::get(
-            changeElementType(t.getEleTy(), newElementType, turnBoxIntoClass));
+      .Case<fir::ReferenceType>([&](fir::ReferenceType refTy) -> mlir::Type {
+        auto newEleTy = changeElementType(refTy.getEleTy(), newElementType,
+                                          turnBoxIntoClass);
+        return fir::ReferenceType::get(newEleTy, refTy.isVolatile());
       })
+      .Case<fir::PointerType, fir::HeapType, fir::ClassType>(
+          [&](auto t) -> mlir::Type {
+            using FIRT = decltype(t);
+            return FIRT::get(changeElementType(t.getEleTy(), newElementType,
+                                               turnBoxIntoClass));
+          })
       .Case<fir::BoxType>([&](fir::BoxType t) -> mlir::Type {
         mlir::Type newInnerType =
             changeElementType(t.getEleTy(), newElementType, false);
@@ -1057,18 +1062,38 @@ unsigned fir::RecordType::getFieldIndex(llvm::StringRef ident) {
 // ReferenceType
 //===----------------------------------------------------------------------===//
 
-// `ref` `<` type `>`
+// `ref` `<` type (`, volatile` $volatile^)? `>`
 mlir::Type fir::ReferenceType::parse(mlir::AsmParser &parser) {
-  return parseTypeSingleton<fir::ReferenceType>(parser);
+  if (parser.parseLess())
+    return {};
+
+  mlir::Type eleTy;
+  if (parser.parseType(eleTy))
+    return {};
+
+  bool isVolatile = false;
+  if (!parser.parseOptionalComma()) {
+    if (parser.parseKeyword(getVolatileKeyword())) {
+      return {};
+    }
+    isVolatile = true;
+  }
+
+  if (parser.parseGreater())
+    return {};
+  return get(eleTy, isVolatile);
 }
 
 void fir::ReferenceType::print(mlir::AsmPrinter &printer) const {
-  printer << "<" << getEleTy() << '>';
+  printer << "<" << getEleTy();
+  if (isVolatile())
+    printer << ", " << getVolatileKeyword();
+  printer << '>';
 }
 
 llvm::LogicalResult fir::ReferenceType::verify(
-    llvm::function_ref<mlir::InFlightDiagnostic()> emitError,
-    mlir::Type eleTy) {
+    llvm::function_ref<mlir::InFlightDiagnostic()> emitError, mlir::Type eleTy,
+    bool isVolatile) {
   if (mlir::isa<ShapeType, ShapeShiftType, SliceType, FieldType, LenType,
                 ReferenceType, TypeDescType>(eleTy))
     return emitError() << "cannot build a reference to type: " << eleTy << '\n';
@@ -1319,11 +1344,15 @@ changeTypeShape(mlir::Type type,
           return fir::SequenceType::get(*newShape, seqTy.getEleTy());
         return seqTy.getEleTy();
       })
-      .Case<fir::PointerType, fir::HeapType, fir::ReferenceType, fir::BoxType,
-            fir::ClassType>([&](auto t) -> mlir::Type {
-        using FIRT = decltype(t);
-        return FIRT::get(changeTypeShape(t.getEleTy(), newShape));
+      .Case<fir::ReferenceType>([&](fir::ReferenceType rt) -> mlir::Type {
+        return fir::ReferenceType::get(changeTypeShape(rt.getEleTy(), newShape),
+                                       rt.isVolatile());
       })
+      .Case<fir::PointerType, fir::HeapType, fir::BoxType, fir::ClassType>(
+          [&](auto t) -> mlir::Type {
+            using FIRT = decltype(t);
+            return FIRT::get(changeTypeShape(t.getEleTy(), newShape));
+          })
       .Default([&](mlir::Type t) -> mlir::Type {
         assert((fir::isa_trivial(t) || llvm::isa<fir::RecordType>(t) ||
                 llvm::isa<mlir::NoneType>(t) ||
diff --git a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
index 8851a3a7187b9..4a3308ff4e747 100644
--- a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
+++ b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
@@ -214,6 +214,13 @@ void hlfir::DeclareOp::build(mlir::OpBuilder &builder,
   auto nameAttr = builder.getStringAttr(uniq_name);
   mlir::Type inputType = memref.getType();
   bool hasExplicitLbs = hasExplicitLowerBounds(shape);
+  if (fortran_attrs && mlir::isa<fir::ReferenceType>(inputType) &&
+      bitEnumContainsAny(fortran_attrs.getFlags(),
+                         fir::FortranVariableFlagsEnum::fortran_volatile)) {
+    auto refType = mlir::cast<fir::ReferenceType>(inputType);
+    inputType = fir::ReferenceType::get(refType.getEleTy(), true);
+    memref = builder.create<fir::ConvertOp>(memref.getLoc(), inputType, memref);
+  }
   mlir::Type hlfirVariableType =
       getHLFIRVariableType(inputType, hasExplicitLbs);
   build(builder, result, {hlfirVariableType, inputType}, memref, shape,
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
index 496a5560ac615..aa151f90ed0d1 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
@@ -418,7 +418,9 @@ class DesignateOpConversion
       firstElementIndices.push_back(indices[i]);
       i = i + (isTriplet ? 3 : 1);
     }
-    mlir::Type arrayCoorType = fir::ReferenceType::get(baseEleTy);
+    mlir::Type originalDesignateType = designate.getResult().getType();
+    const bool isVolatile = fir::isa_volatile_ref_type(originalDesignateType);
+    mlir::Type arrayCoorType = fir::ReferenceType::get(baseEleTy, isVolatile);
     base = builder.create<fir::ArrayCoorOp>(
         loc, arrayCoorType, base, shape,
         /*slice=*/mlir::Value{}, firstElementIndices, firBaseTypeParameters);
@@ -441,6 +443,7 @@ class DesignateOpConversion
       TODO(loc, "hlfir::designate load of pointer or allocatable");
 
     mlir::Type designateResultType = designate.getResult().getType();
+    const bool isVolatile = fir::isa_volatile_ref_type(designateResultType);
     llvm::SmallVector<mlir::Value> firBaseTypeParameters;
     auto [base, shape] = hlfir::genVariableFirBaseShapeAndParams(
         loc, builder, baseEntity, firBaseTypeParameters);
@@ -464,7 +467,7 @@ class DesignateOpConversion
         mlir::Type componentType =
             mlir::cast<fir::RecordType>(baseEleTy).getType(
                 designate.getComponent().value());
-        mlir::Type coorTy = fir::ReferenceType::get(componentType);
+        mlir::Type coorTy = fir::ReferenceType::get(componentType, isVolatile);
         base = builder.create<fir::CoordinateOp>(loc, coorTy, base, fieldIndex);
         if (mlir::isa<fir::BaseBoxType>(componentType)) {
           auto variableInterface = mlir::cast<fir::FortranVariableOpInterface>(
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp b/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp
index 96a3622f4afee..020915179a670 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/OptimizedBufferization.cpp
@@ -1126,7 +1126,8 @@ class ReductionMaskConversion : public mlir::OpRewritePattern<Op> {
       builder.create<fir::StoreOp>(loc, flagSet, flagRef);
       mlir::Type resultElemTy =
           hlfir::getFortranElementType(resultArr.getType());
-      mlir::Type returnRefTy = builder.getRefType(resultElemTy);
+      mlir::Type returnRefTy = builder.getRefType(
+          resultElemTy, fir::isa_volatile_ref_type(flagRef.getType()));
       mlir::IndexType idxTy = builder.getIndexType();
 
       for (unsigned int i = 0; i < rank; ++i) {
@@ -1153,7 +1154,8 @@ class ReductionMaskConversion : public mlir::OpRewritePattern<Op> {
     auto getAddrFn = [](fir::FirOpBuilder builder, mlir::Location loc,
                         const mlir::Type &resultElemType, mlir::Value resultArr,
                         mlir::Value index) {
-      mlir::Type resultRefTy = builder.getRefType(resultElemType);
+      mlir::Type resultRefTy = builder.getRefType(
+          resultElemType, fir::isa_volatile_ref_type(resultArr.getType()));
       mlir::Value oneIdx =
           builder.createIntegerConstant(loc, builder.getIndexType(), 1);
       index = builder.create<mlir::arith::AddIOp>(loc, index, oneIdx);
@@ -1162,8 +1164,9 @@ class ReductionMaskConversion : public mlir::OpRewritePattern<Op> {
     };
 
     // Initialize the resu...
[truncated]

Copy link
Contributor

@vzakhari vzakhari left a comment

Choose a reason for hiding this comment

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

Thank you, Asher! It looks great! I just have a couple of comments.

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 Asher, the direction looks good to me!

Copy link
Contributor

@kiranchandramohan kiranchandramohan left a comment

Choose a reason for hiding this comment

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

A few drive through Nit comments.

Comment on lines 13 to 14
hlfir.assign %7 to %2#0 : !fir.logical<4>, !fir.ref<!fir.logical<4>, volatile>
%8 = fir.load %2#0 : !fir.ref<!fir.logical<4>, volatile>
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Do we have to augment the semantics/docs of hlfir.assign, fir.load, fir.storeon how they behave with a volatile type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe the semantic differences should be handled by updating memory effects, which are modeled by adding generic read and write effects to all ops taking a volatile reference (even those that technically only read). We might want to use read+write effects on a specific memory resource for volatile variables as Slava suggested.

The docs should be updated to reflect this too, thanks!

@ashermancinelli
Copy link
Contributor Author

ashermancinelli commented Mar 24, 2025

Thanks for the reviews, everyone!

In response to @kiranchandramohan and @jeanPerier 's questions/comments:

What about array_merge_store?

fir_CopyOp will also need this interface.

There are several ops (copy, array_merge, embox, ext_embox) and at least one more type (fir.box) that will need to be updated to handle volatility. I will update this PR with those changes. I don't intend to merge this all at once, but it will be nice to have the full change in one diff to agree on the design before I start sending smaller patches.

Also, Jean suggested on the discourse thread that the volatile argument should not have a default:

it is easier to control/make sure code is aware of VOLATILE aspect by adding a flag in the existing memory type and making it a mandatory (not optional) value in the type builder/ctor of that type. That way, people have to provide some VOLATILE parameter when construction new result types and think for a minute where this information should come from in their case.

I like this idea and will include it in the next revision. This will make the diff much larger, but I agree that this will make it harder for users of the type to forget to handle volatility. edit: this would touch about 500 lines, so I'd rather make those changes once the design is agreed upon.

@kiranchandramohan
Copy link
Contributor

In response to @kiranchandramohan and @jeanPerier 's questions/comments:

What about array_merge_store?

fir_CopyOp will also need this interface.

There are several ops (copy, array_merge, embox, ext_embox) and at least one more type (fir.box) that will need to be updated to handle volatility. I will update this PR with those changes. I don't intend to merge this all at once, but it will be nice to have the full change in one diff to agree on the design before I start sending smaller patches.

Would you also need to change the runtime functions like Assign?

@ashermancinelli
Copy link
Contributor Author

Would you also need to change the runtime functions like Assign?

Great question. Let me dig some more and get back to you.

@ashermancinelli
Copy link
Contributor Author

ashermancinelli commented Mar 25, 2025

For Assign in particular, I think we can use fir::factory::genScalarAssignment to get volatile loads/stores instead of doing it in the runtime at all. I'm still going through the other runtime routines.

Another thought: the only time casts between volatile and non-volatile types should happen automatically is at callsites. IIUC, if a variable is volatile in the caller's scope but not in the callee scope (or any other time there's a mismatch between argument types and the function declaration's types), then we should automatically add conversions.

program p
integer,volatile::v
v=0
! casting from volatile ref to non-volatile ref should be okay here
call not_declared_volatile_in_this_scope(v)
print*,v
contains
subroutine not_declared_volatile_in_this_scope(v)
integer::v
v=1
end subroutine
end program

Kiran's suggestion of using a special cast op separate from fir.convert seems good to me - fir.convert can check that it's not adding or dropping volatility.

@vzakhari
Copy link
Contributor

vzakhari commented Mar 25, 2025

For Assign in particular, I think we can use fir::factory::genScalarAssignment to get volatile loads/stores instead of doing it in the runtime at all. I'm still going through the other runtime routines.

Arrays may be volatile as well, right, so we will have the runtime calls in some cases?

I believe our calls have the most conservative memory effects (currently), so passing volatile to them should not be a problem. I think we will have to account for VOLATILE in flang/lib/Optimizer/Transforms/SetRuntimeCallAttributes.cpp.

at least one more type (fir.box) that will need to be updated to handle volatility

Sounds right. I do not have a ready answer, but we will need to clarify how VOLATILE is applied to different cases like !fir.box<...>, !fir.ref<!fir.box<...>>, ... In the latter case, I think VOLATILE applies to the object and not to the box reference itself, but I am not sure.

@ashermancinelli
Copy link
Contributor Author

I think we will have to account for VOLATILE in flang/lib/Optimizer/Transforms/SetRuntimeCallAttributes.cpp.

Looking into this, thanks!

Also: I'm not sure we will need the volatile attribute on the dummy arguments as they are set right now:

! source
  subroutine declared_volatile_in_this_scope(v,n)
    integer,intent(in)::n
    integer,volatile,intent(inout)::v(n)
    v=1
  end subroutine

// IR
  func.func private @_QFPdeclared_volatile_in_this_scope(%arg0: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "v", fir.volatile}, %arg1: !fir.ref<i32> {fir.bindc_name = "n"}) attributes {fir.host_symbol = @_QQmain, llvm.linkage = #llvm.linkage<internal>} {
    %c1_i32 = arith.constant 1 : i32
    %c0 = arith.constant 0 : index
    %0 = fir.dummy_scope : !fir.dscope
    %1:2 = hlfir.declare %arg1 dummy_scope %0 {fortran_attrs = #fir.var_attrs<intent_in>, uniq_name = "_QFFdeclared_volatile_in_this_scopeEn"} : (!fir.ref<i32>, !fir.dscope) -> (!fir.ref<i32>, !fir.ref<i32>)
    %2 = fir.load %1#0 : !fir.ref<i32>
    %3 = fir.convert %2 : (i32) -> index
    %4 = arith.cmpi sgt, %3, %c0 : index
    %5 = arith.select %4, %3, %c0 : index
    %6 = fir.shape %5 : (index) -> !fir.shape<1>
    %7 = fir.volatile_cast %arg0 : (!fir.ref<!fir.array<?xi32>>) -> !fir.ref<!fir.array<?xi32>, volatile>
    %8:2 = hlfir.declare %7(%6) dummy_scope %0 {fortran_attrs = #fir.var_attrs<intent_inout, volatile>, uniq_name = "_QFFdeclared_volatile_in_this_scopeEv"} : (!fir.ref<!fir.array<?xi32>, volatile>, !fir.shape<1>, !fir.dscope) -> (!fir.box<!fir.array<?xi32>, volatile>, !fir.ref<!fir.array<?xi32>, volatile>)
    hlfir.assign %c1_i32 to %8#0 : i32, !fir.box<!fir.array<?xi32>, volatile>
    return
  }

The fir.volatile unit attribute is set on the argument, but if the dummy is declared volatile we should always get an hlfir.declare that makes it volatile anyways, right?

@ashermancinelli ashermancinelli force-pushed the ajm/flang-support-volatile branch from 63d7672 to e079baf Compare March 31, 2025 20:42
Copy link

github-actions bot commented Mar 31, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Copy link
Contributor

@vzakhari vzakhari left a comment

Choose a reason for hiding this comment

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

Thank you for the update, Asher! I have a couple of questions about missing BoxType handling. I guess those places might not be triggered in the testing, but maybe it is worth adding TODO() assertions instead of skipping them.

@ashermancinelli
Copy link
Contributor Author

Thanks for the review Slava! I'll address these in the morning.

Copy link
Contributor

@vzakhari vzakhari left a comment

Choose a reason for hiding this comment

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

Looks great! Thank you for all the work, Asher!


// -----

// Check that volatile hlfir assignments are PRESERVED.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jeanPerier Thanks for the review on the other PR! This is the test I added for hlfir assignments. It may be worth adding tests for all/most other hlfir operations as well.

@ashermancinelli ashermancinelli changed the title [flang][rfc] Add represention of volatile references [flang] Add lowering of volatile references Apr 24, 2025
@kiranchandramohan
Copy link
Contributor

If this is the final patch in the volatile series, would you mind adding an entry in https://github.com/llvm/llvm-project/blob/main/flang/docs/ReleaseNotes.md and removing the entry for volatile in https://github.com/llvm/llvm-project/blob/main/flang/docs/FortranStandardsSupport.md ?

@ashermancinelli
Copy link
Contributor Author

If this is the final patch in the volatile series, would you mind adding an entry in https://github.com/llvm/llvm-project/blob/main/flang/docs/ReleaseNotes.md and removing the entry for volatile in https://github.com/llvm/llvm-project/blob/main/flang/docs/FortranStandardsSupport.md ?

Yes! Please let me know if the docs look okay to you.

@ashermancinelli
Copy link
Contributor Author

I updated the docs.

@kiranchandramohan
Copy link
Contributor

If this is the final patch in the volatile series, would you mind adding an entry in https://github.com/llvm/llvm-project/blob/main/flang/docs/ReleaseNotes.md and removing the entry for volatile in https://github.com/llvm/llvm-project/blob/main/flang/docs/FortranStandardsSupport.md ?

Yes! Please let me know if the docs look okay to you.

Thank. Looks good.

@ashermancinelli ashermancinelli merged commit 8836bce into llvm:main Apr 30, 2025
12 checks passed
@DanielCChen
Copy link
Contributor

@ashermancinelli
We still have a few regressions from this patch after I pulled the latest source just now (May 5).
The following is a reducer.

    type base
        integer :: p(10)
    end type

    type another
        class(*), pointer :: p(:)
    end type

    type(another), volatile :: a

    type(base), volatile, target :: b


    a%p => b%p

end

Flang complains

./t4.f:14:5: warning: VOLATILE target associated with non-VOLATILE pointer
      a%p => b%p
      ^^^^^^^^^^
./t4.f:6:30: Declaration of 'p'
          class(*), pointer :: p(:)
                               ^

The standard says in [8.5.20 VOLATILE attribute]
If an object has the VOLATILE attribute, then all of its subobjects also have the VOLATILE attribute.

So this code should be valid. The full test cases fails at verification of lowering.

@ashermancinelli
Copy link
Contributor Author

Hello @DanielCChen, thank you for the report. #138339 fixes your test case on the machines I just tested on, and I'd like to merge that as soon as I can.

@DanielCChen
Copy link
Contributor

Got it! Thanks! I will verify the rest of the regression with that patch.

@DanielCChen
Copy link
Contributor

@ashermancinelli
The above code still issues the incorrect warning with this patch.

@ashermancinelli
Copy link
Contributor Author

Ah, you were asking about the warning. This series of patches only deals with the lowering of volatile entities, not anything in the frontend. @akuhlens added a warning last week I think, I'll ask him about this.

@DanielCChen
Copy link
Contributor

Ah, you were asking about the warning. This series of patches only deals with the lowering of volatile entities, not anything in the frontend. @akuhlens added a warning last week I think, I'll ask him about this.

Oh I see. Yeah, the warning should be removed. Thanks!

I got the assert in lowering for another case.

  MODULE M

  TYPE :: DT
    CHARACTER :: C0="!"
    INTEGER   :: I=0
    CHARACTER :: C1="!"
  END TYPE

  END MODULE

  PROGRAM dataPtrVolatile
  USE M
  IMPLICIT NONE

  TYPE(DT),  VOLATILE , TARGET  :: Arr(100, 100), Arr1(10000), T(100,100)
  CLASS(DT), VOLATILE , POINTER :: Ptr(:, :)
  INTEGER             :: I, J


  DO I =1, 100
  DO J =I, 100

    Arr(I:, J:) = DT(I=-I)

    Ptr(I:, J:) => Arr(I:, J:)
    T(I:, J:) = Ptr(I:, J:)
  END DO
  END DO


  END
  
error: loc("/home/cdchen/temp/t5.f":26:5): failed to legalize unresolved materialization from ('!fir.class<!fir.array<?x?x!fir.type<_QMmTdt{c0:!fir.char<1>,i:i32,c1:!fir.char<1>}>>, volatile>') to ('!fir.class<!fir.array<?x?x!fir.type<_QMmTdt{c0:!fir.char<1>,i:i32,c1:!fir.char<1>}>>>') that remained live after conversion
error: failure in HLFIR to FIR conversion pass
error: Lowering to LLVM IR failed
error: loc("/home/cdchen/temp/t5.f":1:3): LLVM Translation failed for operation: fir.global
error: failed to create the LLVM module

@akuhlens
Copy link
Contributor

akuhlens commented May 5, 2025

@DanielCChen PR #138611 fixes your issue. Sorry for not catching that.

IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
[RFC on
discourse](https://discourse.llvm.org/t/rfc-volatile-representation-in-flang/85404/1)

Flang currently lacks support for volatile variables. For some cases,
the compiler produces TODO error messages and others are ignored. Some
of our tests are like the example from _C.4 Clause 8 notes: The VOLATILE
attribute (8.5.20)_ and require volatile variables.

Prior commits:
```
c9ec1bc [flang] Handle volatility in lowering and codegen (llvm#135311)
e42f860 [flang][nfc] Support volatility in Fir ops (llvm#134858)
b2711e1 [flang][nfc] Support volatile on ref, box, and class types (llvm#134386)
```
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
[RFC on
discourse](https://discourse.llvm.org/t/rfc-volatile-representation-in-flang/85404/1)

Flang currently lacks support for volatile variables. For some cases,
the compiler produces TODO error messages and others are ignored. Some
of our tests are like the example from _C.4 Clause 8 notes: The VOLATILE
attribute (8.5.20)_ and require volatile variables.

Prior commits:
```
c9ec1bc [flang] Handle volatility in lowering and codegen (llvm#135311)
e42f860 [flang][nfc] Support volatility in Fir ops (llvm#134858)
b2711e1 [flang][nfc] Support volatile on ref, box, and class types (llvm#134386)
```
IanWood1 pushed a commit to IanWood1/llvm-project that referenced this pull request May 6, 2025
[RFC on
discourse](https://discourse.llvm.org/t/rfc-volatile-representation-in-flang/85404/1)

Flang currently lacks support for volatile variables. For some cases,
the compiler produces TODO error messages and others are ignored. Some
of our tests are like the example from _C.4 Clause 8 notes: The VOLATILE
attribute (8.5.20)_ and require volatile variables.

Prior commits:
```
c9ec1bc [flang] Handle volatility in lowering and codegen (llvm#135311)
e42f860 [flang][nfc] Support volatility in Fir ops (llvm#134858)
b2711e1 [flang][nfc] Support volatile on ref, box, and class types (llvm#134386)
```
GeorgeARM pushed a commit to GeorgeARM/llvm-project that referenced this pull request May 7, 2025
[RFC on
discourse](https://discourse.llvm.org/t/rfc-volatile-representation-in-flang/85404/1)

Flang currently lacks support for volatile variables. For some cases,
the compiler produces TODO error messages and others are ignored. Some
of our tests are like the example from _C.4 Clause 8 notes: The VOLATILE
attribute (8.5.20)_ and require volatile variables.

Prior commits:
```
c9ec1bc [flang] Handle volatility in lowering and codegen (llvm#135311)
e42f860 [flang][nfc] Support volatility in Fir ops (llvm#134858)
b2711e1 [flang][nfc] Support volatile on ref, box, and class types (llvm#134386)
```
Ankur-0429 pushed a commit to Ankur-0429/llvm-project that referenced this pull request May 9, 2025
[RFC on
discourse](https://discourse.llvm.org/t/rfc-volatile-representation-in-flang/85404/1)

Flang currently lacks support for volatile variables. For some cases,
the compiler produces TODO error messages and others are ignored. Some
of our tests are like the example from _C.4 Clause 8 notes: The VOLATILE
attribute (8.5.20)_ and require volatile variables.

Prior commits:
```
c9ec1bc [flang] Handle volatility in lowering and codegen (llvm#135311)
e42f860 [flang][nfc] Support volatility in Fir ops (llvm#134858)
b2711e1 [flang][nfc] Support volatile on ref, box, and class types (llvm#134386)
```
akuhlens added a commit that referenced this pull request May 10, 2025
…component symbol (#138611)

The standard says in [8.5.20 VOLATILE attribute]:
If an object has the VOLATILE attribute, then all of its sub-objects
also have the VOLATILE attribute.

This code takes this into account and uses the volatility of the base of
the designator instead of that of the component. In fact, fields in a
structure are not allowed to have the volatile attribute. So given the
code, `A%B => t`, symbol `B` could never directly have the volatile
attribute, and the volatility of `A` indicates the volatility of `B`.


This PR should address [the
comments](#132486 (comment))
on this PR #132486
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request May 10, 2025
…instead of component symbol (#138611)

The standard says in [8.5.20 VOLATILE attribute]:
If an object has the VOLATILE attribute, then all of its sub-objects
also have the VOLATILE attribute.

This code takes this into account and uses the volatility of the base of
the designator instead of that of the component. In fact, fields in a
structure are not allowed to have the volatile attribute. So given the
code, `A%B => t`, symbol `B` could never directly have the volatile
attribute, and the volatility of `A` indicates the volatility of `B`.

This PR should address [the
comments](llvm/llvm-project#132486 (comment))
on this PR #132486
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
flang:codegen flang:fir-hlfir flang:frontend flang:ir flang Flang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants