Skip to content

Conversation

@Saldivarcher
Copy link
Contributor

@Saldivarcher Saldivarcher commented Oct 31, 2025

Previously FLUSH was only recognized in statement form (e.g. flush(unit)); a
subroutine invocation call flush(unit) was treated as a generic user call with
no special semantics. This change teaches lowering/semantics to handle
CALL FLUSH equivalently.

Fixes #119418

@llvmbot llvmbot added flang Flang issues not falling into any other category flang:fir-hlfir flang:semantics labels Nov 1, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 1, 2025

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

@llvm/pr-subscribers-flang-semantics

Author: Miguel Saldivar (Saldivarcher)

Changes

Currently FLUSH was only available as a statement, so something like:

program flush_stmt_example
  implicit none

  integer :: unit

  ! Open a file for writing
  open(unit=10, file="flush_stmt.txt", status="replace", action="write")

  write(10,*) "This line is written to the file."
  flush(10)  ! <-- flush statement, not a CALL

  print *, "The file has been flushed. You can read it now before close."

  close(10)
end program flush_stmt_example

If FLUSH was used as subroutine call, like call flush(10) there was no special handling of it. This PR should add that functionality.

Fixes #119418


Full diff: https://github.com/llvm/llvm-project/pull/165942.diff

7 Files Affected:

  • (modified) flang-rt/lib/runtime/extensions.cpp (+11)
  • (modified) flang/include/flang/Optimizer/Builder/IntrinsicCall.h (+1)
  • (modified) flang/include/flang/Optimizer/Builder/Runtime/Intrinsics.h (+2)
  • (modified) flang/include/flang/Runtime/extensions.h (+1)
  • (modified) flang/lib/Evaluate/intrinsics.cpp (+4)
  • (modified) flang/lib/Optimizer/Builder/IntrinsicCall.cpp (+18)
  • (modified) flang/lib/Optimizer/Builder/Runtime/Intrinsics.cpp (+9)
diff --git a/flang-rt/lib/runtime/extensions.cpp b/flang-rt/lib/runtime/extensions.cpp
index 19e75143705ab..d3a618c1a39ec 100644
--- a/flang-rt/lib/runtime/extensions.cpp
+++ b/flang-rt/lib/runtime/extensions.cpp
@@ -163,6 +163,17 @@ void FORTRAN_PROCEDURE_NAME(flush)(const int &unit) {
   Cookie cookie{IONAME(BeginFlush)(unit, __FILE__, __LINE__)};
   IONAME(EndIoStatement)(cookie);
 }
+
+void RTNAME(Flush)(int unit) {
+  // We set the `unit == -1` on the `flush()` case, so flush all units.
+  if (unit < 0) {
+    Terminator terminator{__FILE__, __LINE__};
+    IoErrorHandler handler{terminator};
+    ExternalFileUnit::FlushAll(handler);
+    return;
+  }
+  FORTRAN_PROCEDURE_NAME(flush)(unit);
+}
 } // namespace io
 
 // CALL FDATE(DATE)
diff --git a/flang/include/flang/Optimizer/Builder/IntrinsicCall.h b/flang/include/flang/Optimizer/Builder/IntrinsicCall.h
index 3407dd01dd504..51a3ae5af7688 100644
--- a/flang/include/flang/Optimizer/Builder/IntrinsicCall.h
+++ b/flang/include/flang/Optimizer/Builder/IntrinsicCall.h
@@ -278,6 +278,7 @@ struct IntrinsicLibrary {
   mlir::Value genExtremum(mlir::Type, llvm::ArrayRef<mlir::Value>);
   void genFenceProxyAsync(llvm::ArrayRef<fir::ExtendedValue>);
   mlir::Value genFloor(mlir::Type, llvm::ArrayRef<mlir::Value>);
+  void genFlush(llvm::ArrayRef<fir::ExtendedValue>);
   mlir::Value genFraction(mlir::Type resultType,
                           mlir::ArrayRef<mlir::Value> args);
   void genFree(mlir::ArrayRef<fir::ExtendedValue> args);
diff --git a/flang/include/flang/Optimizer/Builder/Runtime/Intrinsics.h b/flang/include/flang/Optimizer/Builder/Runtime/Intrinsics.h
index 7a97172cfbb9a..5121ccce921c6 100644
--- a/flang/include/flang/Optimizer/Builder/Runtime/Intrinsics.h
+++ b/flang/include/flang/Optimizer/Builder/Runtime/Intrinsics.h
@@ -51,6 +51,8 @@ mlir::Value genDsecnds(fir::FirOpBuilder &builder, mlir::Location loc,
 void genEtime(fir::FirOpBuilder &builder, mlir::Location loc,
               mlir::Value values, mlir::Value time);
 
+void genFlush(fir::FirOpBuilder &builder, mlir::Location loc, mlir::Value unit);
+
 void genFree(fir::FirOpBuilder &builder, mlir::Location loc, mlir::Value ptr);
 
 mlir::Value genFseek(fir::FirOpBuilder &builder, mlir::Location loc,
diff --git a/flang/include/flang/Runtime/extensions.h b/flang/include/flang/Runtime/extensions.h
index 9fd3e118a0f22..8db68eb9c245c 100644
--- a/flang/include/flang/Runtime/extensions.h
+++ b/flang/include/flang/Runtime/extensions.h
@@ -34,6 +34,7 @@ double RTNAME(Dsecnds)(double *refTime, const char *sourceFile, int line);
 
 // CALL FLUSH(n) antedates the Fortran 2003 FLUSH statement.
 void FORTRAN_PROCEDURE_NAME(flush)(const int &unit);
+void RTNAME(Flush)(int unit);
 
 // GNU extension subroutine FDATE
 void FORTRAN_PROCEDURE_NAME(fdate)(char *string, std::int64_t length);
diff --git a/flang/lib/Evaluate/intrinsics.cpp b/flang/lib/Evaluate/intrinsics.cpp
index 1de5e6b53ba71..d403afe9de307 100644
--- a/flang/lib/Evaluate/intrinsics.cpp
+++ b/flang/lib/Evaluate/intrinsics.cpp
@@ -1597,6 +1597,10 @@ static const IntrinsicInterface intrinsicSubroutine[]{
     {"exit", {{"status", DefaultInt, Rank::scalar, Optionality::optional}}, {},
         Rank::elemental, IntrinsicClass::impureSubroutine},
     {"free", {{"ptr", Addressable}}, {}},
+    {"flush",
+        {{"unit", AnyInt, Rank::scalar, Optionality::optional,
+            common::Intent::In}},
+        {}, Rank::elemental, IntrinsicClass::impureSubroutine},
     {"fseek",
         {{"unit", AnyInt, Rank::scalar}, {"offset", AnyInt, Rank::scalar},
             {"whence", AnyInt, Rank::scalar},
diff --git a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
index 15ea84565dd75..317414ef0fec6 100644
--- a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
+++ b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
@@ -525,6 +525,10 @@ static constexpr IntrinsicHandler handlers[]{
        {"back", asValue, handleDynamicOptional}}},
      /*isElemental=*/false},
     {"floor", &I::genFloor},
+    {"flush",
+     &I::genFlush,
+     {{{"unit", asValue, handleDynamicOptional}}},
+     /*isElemental=*/false},
     {"fraction", &I::genFraction},
     {"free", &I::genFree},
     {"fseek",
@@ -4601,6 +4605,20 @@ mlir::Value IntrinsicLibrary::genFloor(mlir::Type resultType,
   return builder.createConvert(loc, resultType, floor);
 }
 
+// FLUSH
+void IntrinsicLibrary::genFlush(llvm::ArrayRef<fir::ExtendedValue> args) {
+  assert(args.size() == 1);
+
+  mlir::Value unit;
+  if (isStaticallyAbsent(args[0]))
+    // Give a sentinal value of `-1` on the `()` case.
+    unit = builder.createIntegerConstant(loc, builder.getI32Type(), -1);
+  else
+    unit = fir::getBase(args[0]);
+
+  fir::runtime::genFlush(builder, loc, unit);
+}
+
 // FRACTION
 mlir::Value IntrinsicLibrary::genFraction(mlir::Type resultType,
                                           llvm::ArrayRef<mlir::Value> args) {
diff --git a/flang/lib/Optimizer/Builder/Runtime/Intrinsics.cpp b/flang/lib/Optimizer/Builder/Runtime/Intrinsics.cpp
index 110b1b20898c7..9fa3b18a255bd 100644
--- a/flang/lib/Optimizer/Builder/Runtime/Intrinsics.cpp
+++ b/flang/lib/Optimizer/Builder/Runtime/Intrinsics.cpp
@@ -137,6 +137,15 @@ void fir::runtime::genEtime(fir::FirOpBuilder &builder, mlir::Location loc,
   fir::CallOp::create(builder, loc, runtimeFunc, args);
 }
 
+void fir::runtime::genFlush(fir::FirOpBuilder &builder, mlir::Location loc,
+                            mlir::Value unit) {
+  auto runtimeFunc = fir::runtime::getRuntimeFunc<mkRTKey(Flush)>(loc, builder);
+  llvm::SmallVector<mlir::Value> args = fir::runtime::createArguments(
+      builder, loc, runtimeFunc.getFunctionType(), unit);
+
+  fir::CallOp::create(builder, loc, runtimeFunc, args);
+}
+
 void fir::runtime::genFree(fir::FirOpBuilder &builder, mlir::Location loc,
                            mlir::Value ptr) {
   auto runtimeFunc = fir::runtime::getRuntimeFunc<mkRTKey(Free)>(loc, builder);

@Saldivarcher Saldivarcher force-pushed the pr119418 branch 2 times, most recently from 9d3543e to a40172f Compare November 2, 2025 15:51
@Saldivarcher Saldivarcher changed the title [flang] Add functionality for calling FLUSH [flang] Support FLUSH as an intrinsic subroutine Nov 2, 2025
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.

Thank you for contributing this.

Please could you add tests showing the correct lowering for flush with an argument, without an argument, and with a dynamically optional argument. There are examples in flang/test/Lower/Intrinsics.

@Saldivarcher
Copy link
Contributor Author

Thank you for contributing this.

Please could you add tests showing the correct lowering for flush with an argument, without an argument, and with a dynamically optional argument. There are examples in flang/test/Lower/Intrinsics.

Test added, thank you!

@klausler klausler removed their request for review November 11, 2025 17:31
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.

What about something dynamically optional like

subroutine flush_unit(unit)
  integer, optional :: unit
  call flush(unit)
end subroutine

@Saldivarcher
Copy link
Contributor Author

What about something dynamically optional like

subroutine flush_unit(unit)
  integer, optional :: unit
  call flush(unit)
end subroutine

flang and gfortran both don't like the optional. I'm guessing it isn't a part of the standard: https://godbolt.org/z/WTnafdK87

@tblah
Copy link
Contributor

tblah commented Nov 12, 2025

gfortran doesn't support it. classic-flang (armflang 24.04) does support it.

llvm-flang with your patch does not prevent the usage of a dynamically optional argument. I built your branch and tried it and it generates

  func.func @_QPflush_unit(%arg0: !fir.ref<i32> {fir.bindc_name = "unit", fir.optional}) {
    %0 = fir.dummy_scope : !fir.dscope
    %1:2 = hlfir.declare %arg0 dummy_scope %0 {fortran_attrs = #fir.var_attrs<optional>, uniq_name = "_QFflush_unitEunit"} : (!fir.ref<i32>, !fir.dscope) -> (!fir.ref<i32>, !fir.ref<i32>)
    %2 = fir.is_present %1#0 : (!fir.ref<i32>) -> i1
    %3 = fir.if %2 -> (i32) {
      %4 = fir.load %1#0 : !fir.ref<i32>
      fir.result %4 : i32
    } else {
      %c0_i32 = arith.constant 0 : i32
      fir.result %c0_i32 : i32
    }
    fir.call @_FortranAFlush(%3) fastmath<contract> : (i32) -> ()
    return
  }
  func.func private @_FortranAFlush(i32) attributes {fir.runtime}

From this we can see that whilst the dynamic optional is handled, it defaults to 0. I think you will need -1 for how you defined flush.

I don't mind if you choose to support this or not. If you do support it, you need to make sure the default value is -1 and have a test for the lowering of a dynamically optional argument. If you choose not to support this, please make it clear in the documentation and add a test to make sure that this is correctly diagnosed by the compiler (if you just remove handleDynamicOptional, there could be segfaults at runtime because the program will unconditionally try to load the argument): e.g.

  func.func @_QPflush_unit(%arg0: !fir.ref<i32> {fir.bindc_name = "unit", fir.optional}) {
    %0 = fir.dummy_scope : !fir.dscope
    %1:2 = hlfir.declare %arg0 dummy_scope %0 {fortran_attrs = #fir.var_attrs<optional>, uniq_name = "_QFflush_unitEunit"} : (!fir.ref<i32>, !fir.dscope) -> (!fir.ref<i32>, !fir.ref<i32>)
    %2 = fir.load %1#0 : !fir.ref<i32>
    fir.call @_FortranAFlush(%2) fastmath<contract> : (i32) -> ()
    return
  }
  func.func private @_FortranAFlush(i32) attributes {fir.runtime}

@Saldivarcher
Copy link
Contributor Author

gfortran doesn't support it. classic-flang (armflang 24.04) does support it.

llvm-flang with your patch does not prevent the usage of a dynamically optional argument. I built your branch and tried it and it generates

  func.func @_QPflush_unit(%arg0: !fir.ref<i32> {fir.bindc_name = "unit", fir.optional}) {
    %0 = fir.dummy_scope : !fir.dscope
    %1:2 = hlfir.declare %arg0 dummy_scope %0 {fortran_attrs = #fir.var_attrs<optional>, uniq_name = "_QFflush_unitEunit"} : (!fir.ref<i32>, !fir.dscope) -> (!fir.ref<i32>, !fir.ref<i32>)
    %2 = fir.is_present %1#0 : (!fir.ref<i32>) -> i1
    %3 = fir.if %2 -> (i32) {
      %4 = fir.load %1#0 : !fir.ref<i32>
      fir.result %4 : i32
    } else {
      %c0_i32 = arith.constant 0 : i32
      fir.result %c0_i32 : i32
    }
    fir.call @_FortranAFlush(%3) fastmath<contract> : (i32) -> ()
    return
  }
  func.func private @_FortranAFlush(i32) attributes {fir.runtime}

From this we can see that whilst the dynamic optional is handled, it defaults to 0. I think you will need -1 for how you defined flush.

I don't mind if you choose to support this or not. If you do support it, you need to make sure the default value is -1 and have a test for the lowering of a dynamically optional argument. If you choose not to support this, please make it clear in the documentation and add a test to make sure that this is correctly diagnosed by the compiler (if you just remove handleDynamicOptional, there could be segfaults at runtime because the program will unconditionally try to load the argument): e.g.

  func.func @_QPflush_unit(%arg0: !fir.ref<i32> {fir.bindc_name = "unit", fir.optional}) {
    %0 = fir.dummy_scope : !fir.dscope
    %1:2 = hlfir.declare %arg0 dummy_scope %0 {fortran_attrs = #fir.var_attrs<optional>, uniq_name = "_QFflush_unitEunit"} : (!fir.ref<i32>, !fir.dscope) -> (!fir.ref<i32>, !fir.ref<i32>)
    %2 = fir.load %1#0 : !fir.ref<i32>
    fir.call @_FortranAFlush(%2) fastmath<contract> : (i32) -> ()
    return
  }
  func.func private @_FortranAFlush(i32) attributes {fir.runtime}

Ended up adding support for it! I also added the testcase to the flush.f90 test. Let me know how that looks.

Thanks for the review btw, I appreciate it 🙏

@kkwli
Copy link
Collaborator

kkwli commented Nov 17, 2025

I think we need add this non-standard subroutine in https://github.com/llvm/llvm-project/blob/main/flang/docs/Intrinsics.md.

Previously `FLUSH` was only recognized in statement form (e.g. `flush(unit)`); a
subroutine invocation `call flush(unit)` was treated as a generic user call with
no special semantics. This change teaches lowering/semantics to handle
`CALL FLUSH` equivalently.

Fixes llvm#119418
@Saldivarcher
Copy link
Contributor Author

I think we need add this non-standard subroutine in https://github.com/llvm/llvm-project/blob/main/flang/docs/Intrinsics.md.

Done ✔️

@github-actions
Copy link

🐧 Linux x64 Test Results

  • 4059 tests passed
  • 202 tests skipped

@kkwli
Copy link
Collaborator

kkwli commented Nov 18, 2025

I think we need add this non-standard subroutine in https://github.com/llvm/llvm-project/blob/main/flang/docs/Intrinsics.md.

Done ✔️

Thanks. LG

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flang:fir-hlfir flang:semantics flang Flang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[flang] Fortran/gfortran/regression/entry_23.f crashes on several non-x86 platforms

4 participants