[flang-commits] [flang] [Flang] Add `INLINEALWAYS` Compiler Directive (PR #192674)

Jack Styles via flang-commits flang-commits at lists.llvm.org
Thu Apr 23 02:09:30 PDT 2026


https://github.com/Stylie777 updated https://github.com/llvm/llvm-project/pull/192674

>From b508e88064df7680369bd2eca4bf495086bb3701 Mon Sep 17 00:00:00 2001
From: Jack Styles <jack.styles at arm.com>
Date: Wed, 1 Apr 2026 16:34:19 +0100
Subject: [PATCH 1/8] [Flang] Add `INLINEALWAYS` Compiler Directive

Adds support for the INLINEALWAYS Compiler Directive to Flang. This
was previously supported in Classic-Flang, and works in the same way
as FORCEINLINE.

It can either be defined at the call site, or within the function the
user wishes to inline.

The missing support was highlighted while building an opensource
benchmark, as build warnings were indicating that this compiler
directive was being ignored.
---
 flang/include/flang/Parser/dump-parse-tree.h  |  1 +
 flang/include/flang/Parser/parse-tree.h       |  7 ++-
 flang/lib/Lower/Bridge.cpp                    | 27 +++++++++
 flang/lib/Parser/Fortran-parsers.cpp          |  3 +
 flang/lib/Parser/unparse.cpp                  |  7 +++
 .../lib/Semantics/canonicalize-directives.cpp |  1 +
 flang/lib/Semantics/resolve-names.cpp         |  1 +
 flang/test/Lower/inlinealways-directive.f90   | 26 +++++++++
 flang/test/Parser/inlinealways-directive.f90  | 58 +++++++++++++++++++
 9 files changed, 130 insertions(+), 1 deletion(-)
 create mode 100644 flang/test/Lower/inlinealways-directive.f90
 create mode 100644 flang/test/Parser/inlinealways-directive.f90

diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index 3002edc0fc5de..211419a13f410 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -239,6 +239,7 @@ class ParseTreeDumper {
   NODE(CompilerDirective, NoUnroll)
   NODE(CompilerDirective, NoUnrollAndJam)
   NODE(CompilerDirective, Prefetch)
+  NODE(CompilerDirective, InlineAlways)
   NODE(CompilerDirective, Simd)
   NODE(parser, ComplexLiteralConstant)
   NODE(parser, ComplexPart)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index dde83e0c71d7c..35df44e816e55 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -3344,6 +3344,7 @@ struct StmtFunctionStmt {
 // !DIR$ FORCEINLINE
 // !DIR$ INLINE
 // !DIR$ NOINLINE
+// !DIR$ INLINEALWAYS
 // !DIR$ IVDEP
 // !DIR$ SIMD
 // !DIR$ <anything else>
@@ -3381,6 +3382,10 @@ struct CompilerDirective {
     WRAPPER_CLASS_BOILERPLATE(
         Prefetch, std::list<common::Indirection<Designator>>);
   };
+  struct InlineAlways {
+    WRAPPER_CLASS_BOILERPLATE(
+      InlineAlways, std::optional<Name>);
+  };
   EMPTY_CLASS(NoVector);
   EMPTY_CLASS(NoUnroll);
   EMPTY_CLASS(NoUnrollAndJam);
@@ -3394,7 +3399,7 @@ struct CompilerDirective {
   std::variant<std::list<IgnoreTKR>, LoopCount, std::list<AssumeAligned>,
       VectorAlways, VectorLength, std::list<NameValue>, Unroll, UnrollAndJam,
       Unrecognized, NoVector, NoUnroll, NoUnrollAndJam, ForceInline, Inline,
-      NoInline, Prefetch, IVDep, Simd>
+      NoInline, InlineAlways, Prefetch, IVDep, Simd>
       u;
 };
 
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 23bcc077f865b..a9f73b225ae3b 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -2117,6 +2117,9 @@ class FirConverter : public Fortran::lower::AbstractConverter {
               [&](const Fortran::parser::CompilerDirective::ForceInline &) {
                 stmt.typedCall->setAlwaysInline(true);
               },
+              [&](const Fortran::parser::CompilerDirective::InlineAlways &) {
+                stmt.typedCall->setAlwaysInline(true);
+              },
               [&](const Fortran::parser::CompilerDirective::Inline &) {
                 stmt.typedCall->setInlineHint(true);
               },
@@ -2468,6 +2471,9 @@ class FirConverter : public Fortran::lower::AbstractConverter {
                 [&](const Fortran::parser::CompilerDirective::ForceInline &) {
                   callOp.setInlineAttr(fir::FortranInlineEnum::always_inline);
                 },
+                [&](const Fortran::parser::CompilerDirective::InlineAlways &) {
+                  callOp.setInlineAttr(fir::FortranInlineEnum::always_inline);
+                },
                 [&](const auto &) {}},
             dir->u);
       }
@@ -3557,6 +3563,21 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       e->dirs.push_back(&dir);
   }
 
+  void markCurrentFuncAsAlwaysInline(const Fortran::parser::CompilerDirective::InlineAlways &dir) {
+    mlir::func::FuncOp func = builder->getFunction();
+    if (currentFunctionUnit && !currentFunctionUnit->isMainProgram()) {
+      const std::string symName =
+          currentFunctionUnit->getSubprogramSymbol().name().ToString();
+      if (!llvm::StringRef(dir.v->ToString()).equals_insensitive(symName)) {
+        mlir::emitWarning(toLocation())
+            << "Directive Ignored: INLINEALWAYS directive function name '" << dir.v->ToString()
+            << "' does not match the function '" << symName << "' where this is declared.";
+        return;
+      }
+    }
+    func->setAttr("llvm.always_inline", builder->getUnitAttr());
+  }
+
   void
   attachInliningDirectiveToStmt(const Fortran::parser::CompilerDirective &dir,
                                 Fortran::lower::pft::Evaluation *e) {
@@ -3635,6 +3656,12 @@ class FirConverter : public Fortran::lower::AbstractConverter {
             [&](const Fortran::parser::CompilerDirective::IVDep &) {
               attachDirectiveToLoop(dir, &eval);
             },
+            [&](const Fortran::parser::CompilerDirective::InlineAlways &inlineAlways) {
+              if (inlineAlways.v.has_value())
+                markCurrentFuncAsAlwaysInline(inlineAlways);
+              else
+                attachInliningDirectiveToStmt(dir, &eval);
+            },
             [&](const Fortran::parser::CompilerDirective::Simd &) {
               attachDirectiveToLoop(dir, &eval);
             },
diff --git a/flang/lib/Parser/Fortran-parsers.cpp b/flang/lib/Parser/Fortran-parsers.cpp
index 3ad5f0b995219..4c57a13a64f74 100644
--- a/flang/lib/Parser/Fortran-parsers.cpp
+++ b/flang/lib/Parser/Fortran-parsers.cpp
@@ -1339,6 +1339,8 @@ constexpr auto forceinlineDir{
     "FORCEINLINE" >> construct<CompilerDirective::ForceInline>()};
 constexpr auto noinlineDir{
     "NOINLINE" >> construct<CompilerDirective::NoInline>()};
+constexpr auto inlinealwaysDir{
+    "INLINEALWAYS" >> construct<CompilerDirective::InlineAlways>(maybe(name))};
 constexpr auto inlineDir{"INLINE" >> construct<CompilerDirective::Inline>()};
 constexpr auto ivdep{"IVDEP" >> construct<CompilerDirective::IVDep>()};
 constexpr auto simd{"SIMD" >> construct<CompilerDirective::Simd>()};
@@ -1356,6 +1358,7 @@ TYPE_PARSER(beginDirective >> some(letter) >> "$ "_tok >>
                 construct<CompilerDirective>(nounroll) ||
                 construct<CompilerDirective>(noinlineDir) ||
                 construct<CompilerDirective>(forceinlineDir) ||
+                construct<CompilerDirective>(inlinealwaysDir) ||
                 construct<CompilerDirective>(inlineDir) ||
                 construct<CompilerDirective>(simd) ||
                 construct<CompilerDirective>(ivdep) ||
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 48a49433d78d0..3fddaa8bca034 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -1886,6 +1886,13 @@ class UnparseVisitor {
               Word("!DIR$ NOINLINE");
             },
             [&](const CompilerDirective::IVDep &) { Word("!DIR$ IVDEP"); },
+            [&](const CompilerDirective::InlineAlways &InlineAlways) {
+              Word("!DIR$ INLINEALWAYS");
+              if (InlineAlways.v.has_value()){
+                Word(" ");
+                Word (InlineAlways.v->ToString());
+              }
+            },
             [&](const CompilerDirective::Simd &) { Word("!DIR$ SIMD"); },
             [&](const CompilerDirective::Unrecognized &) {
               Word("!DIR$ ");
diff --git a/flang/lib/Semantics/canonicalize-directives.cpp b/flang/lib/Semantics/canonicalize-directives.cpp
index b69d3499ebc53..43405ba71db10 100644
--- a/flang/lib/Semantics/canonicalize-directives.cpp
+++ b/flang/lib/Semantics/canonicalize-directives.cpp
@@ -67,6 +67,7 @@ static bool IsExecutionDirective(const parser::CompilerDirective &dir) {
       std::holds_alternative<parser::CompilerDirective::Inline>(dir.u) ||
       std::holds_alternative<parser::CompilerDirective::NoInline>(dir.u) ||
       std::holds_alternative<parser::CompilerDirective::IVDep>(dir.u) ||
+      std::holds_alternative<parser::CompilerDirective::InlineAlways>(dir.u) ||
       std::holds_alternative<parser::CompilerDirective::Simd>(dir.u);
 }
 
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index f39650da81053..ca0917da6cce4 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10403,6 +10403,7 @@ void ResolveNamesVisitor::Post(const parser::CompilerDirective &x) {
       std::holds_alternative<parser::CompilerDirective::Prefetch>(x.u) ||
       std::holds_alternative<parser::CompilerDirective::NoInline>(x.u) ||
       std::holds_alternative<parser::CompilerDirective::IVDep>(x.u) ||
+      std::holds_alternative<parser::CompilerDirective::InlineAlways>(x.u) ||
       std::holds_alternative<parser::CompilerDirective::Simd>(x.u)) {
     return;
   }
diff --git a/flang/test/Lower/inlinealways-directive.f90 b/flang/test/Lower/inlinealways-directive.f90
new file mode 100644
index 0000000000000..6ebefbdf4b43c
--- /dev/null
+++ b/flang/test/Lower/inlinealways-directive.f90
@@ -0,0 +1,26 @@
+! Check the appropriate flags are added to inline functions when inlinealways is used
+
+! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s
+! RUN: %flang_fc1 -emit-hlfir %s 2>&1 | FileCheck %s --check-prefix=CHECK-WARN
+
+subroutine test_function()
+  !DIR$ INLINEALWAYS test_function
+end subroutine
+
+subroutine test_function2()
+end subroutine
+
+subroutine test_function3()
+  !DIR$ INLINEALWAYS wrong_func
+end subroutine
+
+subroutine test()
+  !DIR$ INLINEALWAYS
+  call test_function2()
+end subroutine
+
+! CHECK: func.func @_QPtest_function() attributes {llvm.always_inline} {
+! CHECK: fir.call @_QPtest_function2() fastmath<contract> {inline_attr = #fir.inline_attrs<always_inline>} : () -> ()
+
+! CHECK-WARN:      warning: loc({{.*}}inlinealways-directive.f90{{.*}}): Directive Ignored:
+! CHECK-WARN-SAME: INLINEALWAYS directive function name 'wrong_func' does not match the function 'test_function3' where this is declared.
diff --git a/flang/test/Parser/inlinealways-directive.f90 b/flang/test/Parser/inlinealways-directive.f90
new file mode 100644
index 0000000000000..28ec3e7f01aff
--- /dev/null
+++ b/flang/test/Parser/inlinealways-directive.f90
@@ -0,0 +1,58 @@
+! Test that the INLINEALWAYS directive can be parsed, both at callsite and within the function
+
+! RUN: %flang_fc1 -fdebug-dump-parse-tree %s 2>&1 | FileCheck %s --check-prefix=PARSE-TREE
+! RUN: %flang_fc1 -fdebug-unparse %s 2>&1 | FileCheck %s --check-prefix=UNPARSE
+
+subroutine test_function()
+  !DIR$ INLINEALWAYS test_function
+end subroutine
+
+subroutine test_function2()
+end subroutine
+
+subroutine test()
+  call test_function()
+  !DIR$ INLINEALWAYS
+  call test_function2()
+end subroutine
+
+! UNPARSE: SUBROUTINE test_function
+! UNPARSE:  !DIR$ INLINEALWAYS TEST_FUNCTION
+! UNPARSE: END SUBROUTINE
+! UNPARSE: SUBROUTINE test_function2
+! UNPARSE: END SUBROUTINE
+! UNPARSE: SUBROUTINE test
+! UNPARSE:   CALL test_function()
+! UNPARSE:  !DIR$ INLINEALWAYS
+! UNPARSE:   CALL test_function2()
+! UNPARSE: END SUBROUTINE
+
+! PARSE-TREE: Program -> ProgramUnit -> SubroutineSubprogram
+! PARSE-TREE: | SubroutineStmt
+! PARSE-TREE: | | Name = 'test_function'
+! PARSE-TREE: | SpecificationPart
+! PARSE-TREE: | | ImplicitPart -> 
+! PARSE-TREE: | ExecutionPart -> Block
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> CompilerDirective -> InlineAlways -> Name = 'test_function'
+! PARSE-TREE: | EndSubroutineStmt -> 
+! PARSE-TREE: ProgramUnit -> SubroutineSubprogram
+! PARSE-TREE: | SubroutineStmt
+! PARSE-TREE: | | Name = 'test_function2'
+! PARSE-TREE: | SpecificationPart
+! PARSE-TREE: | | ImplicitPart -> 
+! PARSE-TREE: | ExecutionPart -> Block
+! PARSE-TREE: | EndSubroutineStmt -> 
+! PARSE-TREE: ProgramUnit -> SubroutineSubprogram
+! PARSE-TREE: | SubroutineStmt
+! PARSE-TREE: | | Name = 'test'
+! PARSE-TREE: | SpecificationPart
+! PARSE-TREE: | | ImplicitPart -> 
+! PARSE-TREE: | ExecutionPart -> Block
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> CallStmt = 'CALL test_function()'
+! PARSE-TREE: | | | Call
+! PARSE-TREE: | | | | ProcedureDesignator -> Name = 'test_function'
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> CompilerDirective -> InlineAlways -> 
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> CallStmt = 'CALL test_function2()'
+! PARSE-TREE: | | | Call
+! PARSE-TREE: | | | | ProcedureDesignator -> Name = 'test_function2'
+! PARSE-TREE: | EndSubroutineStmt -> 

>From 5ba184e3cb23446e84d0c2a9d4647d6e2bddee66 Mon Sep 17 00:00:00 2001
From: Jack Styles <jack.styles at arm.com>
Date: Fri, 17 Apr 2026 15:50:40 +0100
Subject: [PATCH 2/8] format

---
 flang/include/flang/Parser/parse-tree.h |  3 +--
 flang/lib/Lower/Bridge.cpp              | 11 +++++++----
 flang/lib/Parser/unparse.cpp            |  4 ++--
 3 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 35df44e816e55..08e715b7aa3cb 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -3383,8 +3383,7 @@ struct CompilerDirective {
         Prefetch, std::list<common::Indirection<Designator>>);
   };
   struct InlineAlways {
-    WRAPPER_CLASS_BOILERPLATE(
-      InlineAlways, std::optional<Name>);
+    WRAPPER_CLASS_BOILERPLATE(InlineAlways, std::optional<Name>);
   };
   EMPTY_CLASS(NoVector);
   EMPTY_CLASS(NoUnroll);
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index a9f73b225ae3b..ff34c319715c9 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3563,15 +3563,17 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       e->dirs.push_back(&dir);
   }
 
-  void markCurrentFuncAsAlwaysInline(const Fortran::parser::CompilerDirective::InlineAlways &dir) {
+  void markCurrentFuncAsAlwaysInline(
+      const Fortran::parser::CompilerDirective::InlineAlways &dir) {
     mlir::func::FuncOp func = builder->getFunction();
     if (currentFunctionUnit && !currentFunctionUnit->isMainProgram()) {
       const std::string symName =
           currentFunctionUnit->getSubprogramSymbol().name().ToString();
       if (!llvm::StringRef(dir.v->ToString()).equals_insensitive(symName)) {
         mlir::emitWarning(toLocation())
-            << "Directive Ignored: INLINEALWAYS directive function name '" << dir.v->ToString()
-            << "' does not match the function '" << symName << "' where this is declared.";
+            << "Directive Ignored: INLINEALWAYS directive function name '"
+            << dir.v->ToString() << "' does not match the function '" << symName
+            << "' where this is declared.";
         return;
       }
     }
@@ -3656,7 +3658,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
             [&](const Fortran::parser::CompilerDirective::IVDep &) {
               attachDirectiveToLoop(dir, &eval);
             },
-            [&](const Fortran::parser::CompilerDirective::InlineAlways &inlineAlways) {
+            [&](const Fortran::parser::CompilerDirective::InlineAlways
+                    &inlineAlways) {
               if (inlineAlways.v.has_value())
                 markCurrentFuncAsAlwaysInline(inlineAlways);
               else
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 3fddaa8bca034..d7d9dd2ad7580 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -1888,9 +1888,9 @@ class UnparseVisitor {
             [&](const CompilerDirective::IVDep &) { Word("!DIR$ IVDEP"); },
             [&](const CompilerDirective::InlineAlways &InlineAlways) {
               Word("!DIR$ INLINEALWAYS");
-              if (InlineAlways.v.has_value()){
+              if (InlineAlways.v.has_value()) {
                 Word(" ");
-                Word (InlineAlways.v->ToString());
+                Word(InlineAlways.v->ToString());
               }
             },
             [&](const CompilerDirective::Simd &) { Word("!DIR$ SIMD"); },

>From 9830262a7e5e9b43901b60978c3a6882e1216c6b Mon Sep 17 00:00:00 2001
From: Jack Styles <jack.styles at arm.com>
Date: Fri, 17 Apr 2026 16:20:32 +0100
Subject: [PATCH 3/8] Add Directives.md entry

---
 flang/docs/Directives.md | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index 1ee263e833a7b..163a0961ff29d 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -102,6 +102,16 @@ A list of non-standard directives supported by Flang
   assignment statement.
 * `!dir$ forceinline` works in the same way as the `inline` directive, but it forces
    inlining by the compiler on a function call statement.
+* `!dir$ inlinealways <name>`. An alternative spelling to `forceinline`, providing compatibility
+  with older Fortran compilers, such as classic-flang. Can be defined at the callsite, or
+  in the function you want to inline. `name` is optional and should only be used when
+  defining the directive within a function, example:
+  ```
+  function test
+    !DIR$ INLINEALWAYS test
+    ...
+  end function
+  ```
 * `!dir$ noinline` works in the same way as the `inline` directive, but prevents
   any attempt of inlining by the compiler on a function call statement.
 

>From c0d5e40c8a1678afa9df3200460945bd09499a0a Mon Sep 17 00:00:00 2001
From: Jack Styles <jack.styles at arm.com>
Date: Mon, 20 Apr 2026 11:06:52 +0100
Subject: [PATCH 4/8] Respond to review comments

---
 flang/docs/Directives.md                         |  4 ++--
 flang/lib/Lower/Bridge.cpp                       |  9 ++-------
 flang/lib/Semantics/resolve-names.cpp            | 16 +++++++++++++++-
 flang/test/Lower/inlinealways-directive.f90      | 10 +++-------
 .../test/Semantics/inlinealways-directive01.f90  | 12 ++++++++++++
 5 files changed, 34 insertions(+), 17 deletions(-)
 create mode 100644 flang/test/Semantics/inlinealways-directive01.f90

diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index 163a0961ff29d..5f8fc38b153fc 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -103,9 +103,9 @@ A list of non-standard directives supported by Flang
 * `!dir$ forceinline` works in the same way as the `inline` directive, but it forces
    inlining by the compiler on a function call statement.
 * `!dir$ inlinealways <name>`. An alternative spelling to `forceinline`, providing compatibility
-  with older Fortran compilers, such as classic-flang. Can be defined at the callsite, or
+  with older Fortran compilers, such as classic-flang. It can be defined at the callsite, or
   in the function you want to inline. `name` is optional and should only be used when
-  defining the directive within a function, example:
+  specifying the directive within a function, example:
   ```
   function test
     !DIR$ INLINEALWAYS test
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index ff34c319715c9..135839fb7b9c8 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3569,15 +3569,10 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     if (currentFunctionUnit && !currentFunctionUnit->isMainProgram()) {
       const std::string symName =
           currentFunctionUnit->getSubprogramSymbol().name().ToString();
-      if (!llvm::StringRef(dir.v->ToString()).equals_insensitive(symName)) {
-        mlir::emitWarning(toLocation())
-            << "Directive Ignored: INLINEALWAYS directive function name '"
-            << dir.v->ToString() << "' does not match the function '" << symName
-            << "' where this is declared.";
-        return;
+      if (dir.v->ToString() == symName) {
+        func->setAttr("llvm.always_inline", builder->getUnitAttr());
       }
     }
-    func->setAttr("llvm.always_inline", builder->getUnitAttr());
   }
 
   void
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index ca0917da6cce4..84b8132cf321f 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10403,7 +10403,6 @@ void ResolveNamesVisitor::Post(const parser::CompilerDirective &x) {
       std::holds_alternative<parser::CompilerDirective::Prefetch>(x.u) ||
       std::holds_alternative<parser::CompilerDirective::NoInline>(x.u) ||
       std::holds_alternative<parser::CompilerDirective::IVDep>(x.u) ||
-      std::holds_alternative<parser::CompilerDirective::InlineAlways>(x.u) ||
       std::holds_alternative<parser::CompilerDirective::Simd>(x.u)) {
     return;
   }
@@ -10496,6 +10495,21 @@ void ResolveNamesVisitor::Post(const parser::CompilerDirective &x) {
         }
       }
     }
+  } else if (const auto *inlineAlways{
+                 std::get_if<parser::CompilerDirective::InlineAlways>(&x.u)}) {
+    if (!inlineAlways->v.has_value()) {
+      return;
+    }
+
+    Symbol *sym{currScope().symbol()};
+    if (!sym || !sym->has<SubprogramDetails>()) {
+      Say(x.source,
+          "!DIR$ INLINEALWAYS directive with name must appear in a subroutine or function"_err_en_US);
+    }
+    if (inlineAlways->v->ToString() != sym->name().ToString()) {
+      context().Warn(common::UsageWarning::IgnoredDirective, x.source,
+          "IGNOREALWAYS function name does not match the function"_warn_en_US);
+    }
   } else if (context().ShouldWarn(common::UsageWarning::IgnoredDirective)) {
     Say(x.source, "Unrecognized compiler directive was ignored"_warn_en_US)
         .set_usageWarning(common::UsageWarning::IgnoredDirective);
diff --git a/flang/test/Lower/inlinealways-directive.f90 b/flang/test/Lower/inlinealways-directive.f90
index 6ebefbdf4b43c..e610eafb2ae9b 100644
--- a/flang/test/Lower/inlinealways-directive.f90
+++ b/flang/test/Lower/inlinealways-directive.f90
@@ -1,11 +1,11 @@
-! Check the appropriate flags are added to inline functions when inlinealways is used
+! Check the appropriate flags are added to inline functions when inlinealways is used, or ignored when the name is incorrect.
 
 ! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s
-! RUN: %flang_fc1 -emit-hlfir %s 2>&1 | FileCheck %s --check-prefix=CHECK-WARN
 
 subroutine test_function()
   !DIR$ INLINEALWAYS test_function
 end subroutine
+! CHECK: func.func @_QPtest_function() attributes {llvm.always_inline} {
 
 subroutine test_function2()
 end subroutine
@@ -13,14 +13,10 @@ subroutine test_function2()
 subroutine test_function3()
   !DIR$ INLINEALWAYS wrong_func
 end subroutine
+! CHECK: func.func @_QPtest_function2() {
 
 subroutine test()
   !DIR$ INLINEALWAYS
   call test_function2()
 end subroutine
-
-! CHECK: func.func @_QPtest_function() attributes {llvm.always_inline} {
 ! CHECK: fir.call @_QPtest_function2() fastmath<contract> {inline_attr = #fir.inline_attrs<always_inline>} : () -> ()
-
-! CHECK-WARN:      warning: loc({{.*}}inlinealways-directive.f90{{.*}}): Directive Ignored:
-! CHECK-WARN-SAME: INLINEALWAYS directive function name 'wrong_func' does not match the function 'test_function3' where this is declared.
diff --git a/flang/test/Semantics/inlinealways-directive01.f90 b/flang/test/Semantics/inlinealways-directive01.f90
new file mode 100644
index 0000000000000..324ed7f5436e1
--- /dev/null
+++ b/flang/test/Semantics/inlinealways-directive01.f90
@@ -0,0 +1,12 @@
+! Check that the appropriate warnings are emitted when using INLINEALWAYS incorrectly
+! RUN: %python %S/test_errors.py %s %flang_fc1
+
+module m
+! ERROR: !DIR$ INLINEALWAYS directive with name must appear in a subroutine or function
+  !DIR$ INLINEALWAYS m
+end module
+
+subroutine test_function3()
+! WARNING: IGNOREALWAYS function name does not match the function [-Wignored-directive]
+  !DIR$ INLINEALWAYS wrong_func
+end subroutine

>From 4c6fe8585fc495a202eb55e253808ed701e9cfee Mon Sep 17 00:00:00 2001
From: Jack Styles <jack.styles at arm.com>
Date: Mon, 20 Apr 2026 11:34:48 +0100
Subject: [PATCH 5/8] Fix/Update warning if name does not match
 function/subroutine

---
 flang/lib/Semantics/resolve-names.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index 84b8132cf321f..7a0d864071659 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10508,7 +10508,7 @@ void ResolveNamesVisitor::Post(const parser::CompilerDirective &x) {
     }
     if (inlineAlways->v->ToString() != sym->name().ToString()) {
       context().Warn(common::UsageWarning::IgnoredDirective, x.source,
-          "IGNOREALWAYS function name does not match the function"_warn_en_US);
+          "INLINEALWAYS name does not match the subroutine or function name"_warn_en_US);
     }
   } else if (context().ShouldWarn(common::UsageWarning::IgnoredDirective)) {
     Say(x.source, "Unrecognized compiler directive was ignored"_warn_en_US)

>From c7cfd5a75712f58710bc59005882c040e0dafa4d Mon Sep 17 00:00:00 2001
From: Jack Styles <jack.styles at arm.com>
Date: Mon, 20 Apr 2026 11:47:04 +0100
Subject: [PATCH 6/8] Fix test

---
 flang/test/Semantics/inlinealways-directive01.f90 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/test/Semantics/inlinealways-directive01.f90 b/flang/test/Semantics/inlinealways-directive01.f90
index 324ed7f5436e1..5f882f5ea37dd 100644
--- a/flang/test/Semantics/inlinealways-directive01.f90
+++ b/flang/test/Semantics/inlinealways-directive01.f90
@@ -7,6 +7,6 @@ module m
 end module
 
 subroutine test_function3()
-! WARNING: IGNOREALWAYS function name does not match the function [-Wignored-directive]
+! WARNING: INLINEALWAYS function name does not match the function [-Wignored-directive]
   !DIR$ INLINEALWAYS wrong_func
 end subroutine

>From d58ea6659bdcae251845db0c9e1320ae60058838 Mon Sep 17 00:00:00 2001
From: Jack Styles <jack.styles at arm.com>
Date: Mon, 20 Apr 2026 12:09:57 +0100
Subject: [PATCH 7/8] Correct test warning

---
 flang/test/Semantics/inlinealways-directive01.f90 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/flang/test/Semantics/inlinealways-directive01.f90 b/flang/test/Semantics/inlinealways-directive01.f90
index 5f882f5ea37dd..5f6664ba8d88a 100644
--- a/flang/test/Semantics/inlinealways-directive01.f90
+++ b/flang/test/Semantics/inlinealways-directive01.f90
@@ -7,6 +7,6 @@ module m
 end module
 
 subroutine test_function3()
-! WARNING: INLINEALWAYS function name does not match the function [-Wignored-directive]
+! WARNING: INLINEALWAYS name does not match the subroutine or function name [-Wignored-directive]
   !DIR$ INLINEALWAYS wrong_func
 end subroutine

>From c95f35d897aed12f0a15e8c0961f6c51ee5640b8 Mon Sep 17 00:00:00 2001
From: Jack Styles <jack.styles at arm.com>
Date: Tue, 21 Apr 2026 09:48:06 +0100
Subject: [PATCH 8/8] Respond to review comments

---
 flang/docs/Directives.md                      |   6 +-
 flang/test/Lower/inlinealways-directive.f90   |  40 ++++--
 flang/test/Parser/inlinealways-directive.f90  | 117 +++++++++++++++---
 .../Semantics/inlinealways-directive01.f90    |   9 +-
 4 files changed, 140 insertions(+), 32 deletions(-)

diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index 5f8fc38b153fc..3ff56dbded1d7 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -103,9 +103,9 @@ A list of non-standard directives supported by Flang
 * `!dir$ forceinline` works in the same way as the `inline` directive, but it forces
    inlining by the compiler on a function call statement.
 * `!dir$ inlinealways <name>`. An alternative spelling to `forceinline`, providing compatibility
-  with older Fortran compilers, such as classic-flang. It can be defined at the callsite, or
-  in the function you want to inline. `name` is optional and should only be used when
-  specifying the directive within a function, example:
+  with older Fortran compilers, such as classic-flang. It can be specified at the callsite, or
+  in the function or subroutine you want to inline. `name` is optional and should only be used
+  when specifying the directive within a function, example:
   ```
   function test
     !DIR$ INLINEALWAYS test
diff --git a/flang/test/Lower/inlinealways-directive.f90 b/flang/test/Lower/inlinealways-directive.f90
index e610eafb2ae9b..befca5a9824c8 100644
--- a/flang/test/Lower/inlinealways-directive.f90
+++ b/flang/test/Lower/inlinealways-directive.f90
@@ -2,21 +2,43 @@
 
 ! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s
 
-subroutine test_function()
-  !DIR$ INLINEALWAYS test_function
+subroutine test_subroutine()
+  !DIR$ INLINEALWAYS test_subroutine
 end subroutine
-! CHECK: func.func @_QPtest_function() attributes {llvm.always_inline} {
+! CHECK: func.func @_QPtest_subroutine() attributes {llvm.always_inline} {
 
-subroutine test_function2()
+subroutine test_subroutine2()
+  !DIR$ INLINEALWAYS wrong_subroutine
 end subroutine
+! CHECK: func.func @_QPtest_subroutine2() {
 
-subroutine test_function3()
-  !DIR$ INLINEALWAYS wrong_func
+subroutine test_subroutine3()
 end subroutine
-! CHECK: func.func @_QPtest_function2() {
+
+function test_func1()
+!DIR$ INLINEALWAYS test_func1
+end function
+! CHECK: func.func @_QPtest_func1() -> f32 attributes {llvm.always_inline} {
+
+function test_func2()
+!DIR$ INLINEALWAYS wrong_func
+end function
+! CHCEK: func.func @_QPtest_func2() -> f32 {
+
+integer function test_func3() result(res)
+  res = 10
+end function
 
 subroutine test()
+  implicit none
+  integer:: result, test_func3
+
+  !DIR$ INLINEALWAYS
+  call test_subroutine3
+! CHECK: fir.call @_QPtest_subroutine3() fastmath<contract> {inline_attr = #fir.inline_attrs<always_inline>} : () -> ()
+
+  result = 0
   !DIR$ INLINEALWAYS
-  call test_function2()
+  result = test_func3()
+! CHCEK: %[[.*]] = fir.call @_QPtest_func3() fastmath<contract> {inline_attr = #fir.inline_attrs<always_inline>} : () -> i32
 end subroutine
-! CHECK: fir.call @_QPtest_function2() fastmath<contract> {inline_attr = #fir.inline_attrs<always_inline>} : () -> ()
diff --git a/flang/test/Parser/inlinealways-directive.f90 b/flang/test/Parser/inlinealways-directive.f90
index 28ec3e7f01aff..fd7aa53bc0ed8 100644
--- a/flang/test/Parser/inlinealways-directive.f90
+++ b/flang/test/Parser/inlinealways-directive.f90
@@ -3,56 +3,137 @@
 ! RUN: %flang_fc1 -fdebug-dump-parse-tree %s 2>&1 | FileCheck %s --check-prefix=PARSE-TREE
 ! RUN: %flang_fc1 -fdebug-unparse %s 2>&1 | FileCheck %s --check-prefix=UNPARSE
 
-subroutine test_function()
-  !DIR$ INLINEALWAYS test_function
+subroutine test_subroutine()
+  !DIR$ INLINEALWAYS test_subroutine
 end subroutine
 
-subroutine test_function2()
+subroutine test_subroutine2()
 end subroutine
 
+integer function test_func() result(res)
+  !DIR$ INLINEALWAYS test_func
+  res = 10
+end function
+
+integer function test_func2() result(res)
+  res = 10
+end function
+
 subroutine test()
-  call test_function()
+  implicit none
+  integer :: i, test_func1, test_func2
+
+  call test_subroutine()
   !DIR$ INLINEALWAYS
-  call test_function2()
+  call test_subroutine2()
+
+  i = test_func1()
+  !DIR$ INLINEALWAYS
+  i = test_func2()
 end subroutine
 
-! UNPARSE: SUBROUTINE test_function
-! UNPARSE:  !DIR$ INLINEALWAYS TEST_FUNCTION
+! UNPARSE: SUBROUTINE test_subroutine
+! UNPARSE:  !DIR$ INLINEALWAYS TEST_SUBROUTINE
 ! UNPARSE: END SUBROUTINE
-! UNPARSE: SUBROUTINE test_function2
+! UNPARSE: SUBROUTINE test_subroutine2
 ! UNPARSE: END SUBROUTINE
+! UNPARSE: INTEGER FUNCTION test_func() RESULT(res)
+! UNPARSE:  !DIR$ INLINEALWAYS TEST_FUNC
+! UNPARSE:   res=10_4
+! UNPARSE: END FUNCTION
+! UNPARSE: INTEGER FUNCTION test_func2() RESULT(res)
+! UNPARSE:   res=10_4
+! UNPARSE: END FUNCTION
 ! UNPARSE: SUBROUTINE test
-! UNPARSE:   CALL test_function()
+! UNPARSE:  IMPLICIT NONE
+! UNPARSE:  INTEGER i, test_func1, test_func2
+! UNPARSE:   CALL test_subroutine()
+! UNPARSE:  !DIR$ INLINEALWAYS
+! UNPARSE:   CALL test_subroutine2()
+! UNPARSE:   i=test_func1()
 ! UNPARSE:  !DIR$ INLINEALWAYS
-! UNPARSE:   CALL test_function2()
+! UNPARSE:   i=test_func2()
 ! UNPARSE: END SUBROUTINE
 
+! PARSE-TREE: ======================== Flang: parse tree dump ========================
 ! PARSE-TREE: Program -> ProgramUnit -> SubroutineSubprogram
 ! PARSE-TREE: | SubroutineStmt
-! PARSE-TREE: | | Name = 'test_function'
+! PARSE-TREE: | | Name = 'test_subroutine'
 ! PARSE-TREE: | SpecificationPart
 ! PARSE-TREE: | | ImplicitPart -> 
 ! PARSE-TREE: | ExecutionPart -> Block
-! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> CompilerDirective -> InlineAlways -> Name = 'test_function'
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> CompilerDirective -> InlineAlways -> Name = 'test_subroutine'
 ! PARSE-TREE: | EndSubroutineStmt -> 
 ! PARSE-TREE: ProgramUnit -> SubroutineSubprogram
 ! PARSE-TREE: | SubroutineStmt
-! PARSE-TREE: | | Name = 'test_function2'
+! PARSE-TREE: | | Name = 'test_subroutine2'
 ! PARSE-TREE: | SpecificationPart
 ! PARSE-TREE: | | ImplicitPart -> 
 ! PARSE-TREE: | ExecutionPart -> Block
 ! PARSE-TREE: | EndSubroutineStmt -> 
+! PARSE-TREE: ProgramUnit -> FunctionSubprogram
+! PARSE-TREE: | FunctionStmt
+! PARSE-TREE: | | PrefixSpec -> DeclarationTypeSpec -> IntrinsicTypeSpec -> IntegerTypeSpec -> 
+! PARSE-TREE: | | Name = 'test_func'
+! PARSE-TREE: | | Suffix
+! PARSE-TREE: | | | Name = 'res'
+! PARSE-TREE: | SpecificationPart
+! PARSE-TREE: | | ImplicitPart -> 
+! PARSE-TREE: | ExecutionPart -> Block
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> CompilerDirective -> InlineAlways -> Name = 'test_func'
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> AssignmentStmt = 'res=10_4'
+! PARSE-TREE: | | | Variable = 'res'
+! PARSE-TREE: | | | | Designator -> DataRef -> Name = 'res'
+! PARSE-TREE: | | | Expr = '10_4'
+! PARSE-TREE: | | | | LiteralConstant -> IntLiteralConstant = '10'
+! PARSE-TREE: | EndFunctionStmt -> 
+! PARSE-TREE: ProgramUnit -> FunctionSubprogram
+! PARSE-TREE: | FunctionStmt
+! PARSE-TREE: | | PrefixSpec -> DeclarationTypeSpec -> IntrinsicTypeSpec -> IntegerTypeSpec -> 
+! PARSE-TREE: | | Name = 'test_func2'
+! PARSE-TREE: | | Suffix
+! PARSE-TREE: | | | Name = 'res'
+! PARSE-TREE: | SpecificationPart
+! PARSE-TREE: | | ImplicitPart -> 
+! PARSE-TREE: | ExecutionPart -> Block
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> AssignmentStmt = 'res=10_4'
+! PARSE-TREE: | | | Variable = 'res'
+! PARSE-TREE: | | | | Designator -> DataRef -> Name = 'res'
+! PARSE-TREE: | | | Expr = '10_4'
+! PARSE-TREE: | | | | LiteralConstant -> IntLiteralConstant = '10'
+! PARSE-TREE: | EndFunctionStmt -> 
 ! PARSE-TREE: ProgramUnit -> SubroutineSubprogram
 ! PARSE-TREE: | SubroutineStmt
 ! PARSE-TREE: | | Name = 'test'
 ! PARSE-TREE: | SpecificationPart
-! PARSE-TREE: | | ImplicitPart -> 
+! PARSE-TREE: | | ImplicitPart -> ImplicitPartStmt -> ImplicitStmt -> 
+! PARSE-TREE: | | DeclarationConstruct -> SpecificationConstruct -> TypeDeclarationStmt
+! PARSE-TREE: | | | DeclarationTypeSpec -> IntrinsicTypeSpec -> IntegerTypeSpec -> 
+! PARSE-TREE: | | | EntityDecl
+! PARSE-TREE: | | | | Name = 'i'
+! PARSE-TREE: | | | EntityDecl
+! PARSE-TREE: | | | | Name = 'test_func1'
+! PARSE-TREE: | | | EntityDecl
+! PARSE-TREE: | | | | Name = 'test_func2'
 ! PARSE-TREE: | ExecutionPart -> Block
-! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> CallStmt = 'CALL test_function()'
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> CallStmt = 'CALL test_subroutine()'
 ! PARSE-TREE: | | | Call
-! PARSE-TREE: | | | | ProcedureDesignator -> Name = 'test_function'
+! PARSE-TREE: | | | | ProcedureDesignator -> Name = 'test_subroutine'
 ! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> CompilerDirective -> InlineAlways -> 
-! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> CallStmt = 'CALL test_function2()'
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> CallStmt = 'CALL test_subroutine2()'
 ! PARSE-TREE: | | | Call
-! PARSE-TREE: | | | | ProcedureDesignator -> Name = 'test_function2'
+! PARSE-TREE: | | | | ProcedureDesignator -> Name = 'test_subroutine2'
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> AssignmentStmt = 'i=test_func1()'
+! PARSE-TREE: | | | Variable = 'i'
+! PARSE-TREE: | | | | Designator -> DataRef -> Name = 'i'
+! PARSE-TREE: | | | Expr = 'test_func1()'
+! PARSE-TREE: | | | | FunctionReference -> Call
+! PARSE-TREE: | | | | | ProcedureDesignator -> Name = 'test_func1'
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> CompilerDirective -> InlineAlways -> 
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> AssignmentStmt = 'i=test_func2()'
+! PARSE-TREE: | | | Variable = 'i'
+! PARSE-TREE: | | | | Designator -> DataRef -> Name = 'i'
+! PARSE-TREE: | | | Expr = 'test_func2()'
+! PARSE-TREE: | | | | FunctionReference -> Call
+! PARSE-TREE: | | | | | ProcedureDesignator -> Name = 'test_func2'
 ! PARSE-TREE: | EndSubroutineStmt -> 
diff --git a/flang/test/Semantics/inlinealways-directive01.f90 b/flang/test/Semantics/inlinealways-directive01.f90
index 5f6664ba8d88a..e4e569e9e4362 100644
--- a/flang/test/Semantics/inlinealways-directive01.f90
+++ b/flang/test/Semantics/inlinealways-directive01.f90
@@ -6,7 +6,12 @@ module m
   !DIR$ INLINEALWAYS m
 end module
 
-subroutine test_function3()
+subroutine test_subroutine()
 ! WARNING: INLINEALWAYS name does not match the subroutine or function name [-Wignored-directive]
-  !DIR$ INLINEALWAYS wrong_func
+  !DIR$ INLINEALWAYS wrong_subroutine
 end subroutine
+
+function test_func()
+! WARNING: INLINEALWAYS name does not match the subroutine or function name [-Wignored-directive]
+  !DIR$ INLINEALWAYS wrong_func
+end function



More information about the flang-commits mailing list