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

via flang-commits flang-commits at lists.llvm.org
Fri Apr 24 01:58:47 PDT 2026


Author: Jack Styles
Date: 2026-04-24T09:58:43+01:00
New Revision: 839a22f449b3092a32537e9dc1cc474a5e19c909

URL: https://github.com/llvm/llvm-project/commit/839a22f449b3092a32537e9dc1cc474a5e19c909
DIFF: https://github.com/llvm/llvm-project/commit/839a22f449b3092a32537e9dc1cc474a5e19c909.diff

LOG: [Flang] Add `INLINEALWAYS` Compiler Directive (#192674)

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.

Added: 
    flang/test/Lower/inlinealways-directive.f90
    flang/test/Parser/inlinealways-directive.f90
    flang/test/Semantics/inlinealways-directive01.f90

Modified: 
    flang/docs/Directives.md
    flang/include/flang/Parser/dump-parse-tree.h
    flang/include/flang/Parser/parse-tree.h
    flang/lib/Lower/Bridge.cpp
    flang/lib/Parser/Fortran-parsers.cpp
    flang/lib/Parser/unparse.cpp
    flang/lib/Semantics/canonicalize-directives.cpp
    flang/lib/Semantics/resolve-names.cpp

Removed: 
    


################################################################################
diff  --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index 1ee263e833a7b..3ff56dbded1d7 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. 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
+    ...
+  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.
 

diff  --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index 4de25d9f87d00..58bd02ce76918 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 f4a1e30bc13b8..cde779842b4d7 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -3374,6 +3374,7 @@ struct StmtFunctionStmt {
 // !DIR$ FORCEINLINE
 // !DIR$ INLINE
 // !DIR$ NOINLINE
+// !DIR$ INLINEALWAYS
 // !DIR$ IVDEP
 // !DIR$ SIMD
 // !DIR$ <anything else>
@@ -3411,6 +3412,9 @@ 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);
@@ -3424,7 +3428,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 9a995021c9351..e1ff770fb7ad4 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,18 @@ 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 (dir.v->ToString() == symName) {
+        func->setAttr("llvm.always_inline", builder->getUnitAttr());
+      }
+    }
+  }
+
   void
   attachInliningDirectiveToStmt(const Fortran::parser::CompilerDirective &dir,
                                 Fortran::lower::pft::Evaluation *e) {
@@ -3635,6 +3653,13 @@ 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 cc4df74e61e83..33d7475798b8e 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -1907,6 +1907,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..7a0d864071659 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10495,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,
+          "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)
         .set_usageWarning(common::UsageWarning::IgnoredDirective);

diff  --git a/flang/test/Lower/inlinealways-directive.f90 b/flang/test/Lower/inlinealways-directive.f90
new file mode 100644
index 0000000000000..befca5a9824c8
--- /dev/null
+++ b/flang/test/Lower/inlinealways-directive.f90
@@ -0,0 +1,44 @@
+! 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
+
+subroutine test_subroutine()
+  !DIR$ INLINEALWAYS test_subroutine
+end subroutine
+! CHECK: func.func @_QPtest_subroutine() attributes {llvm.always_inline} {
+
+subroutine test_subroutine2()
+  !DIR$ INLINEALWAYS wrong_subroutine
+end subroutine
+! CHECK: func.func @_QPtest_subroutine2() {
+
+subroutine test_subroutine3()
+end subroutine
+
+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
+  result = test_func3()
+! CHCEK: %[[.*]] = fir.call @_QPtest_func3() fastmath<contract> {inline_attr = #fir.inline_attrs<always_inline>} : () -> i32
+end subroutine

diff  --git a/flang/test/Parser/inlinealways-directive.f90 b/flang/test/Parser/inlinealways-directive.f90
new file mode 100644
index 0000000000000..fd7aa53bc0ed8
--- /dev/null
+++ b/flang/test/Parser/inlinealways-directive.f90
@@ -0,0 +1,139 @@
+! 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_subroutine()
+  !DIR$ INLINEALWAYS test_subroutine
+end subroutine
+
+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()
+  implicit none
+  integer :: i, test_func1, test_func2
+
+  call test_subroutine()
+  !DIR$ INLINEALWAYS
+  call test_subroutine2()
+
+  i = test_func1()
+  !DIR$ INLINEALWAYS
+  i = test_func2()
+end subroutine
+
+! UNPARSE: SUBROUTINE test_subroutine
+! UNPARSE:  !DIR$ INLINEALWAYS TEST_SUBROUTINE
+! UNPARSE: END SUBROUTINE
+! 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:  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:   i=test_func2()
+! UNPARSE: END SUBROUTINE
+
+! PARSE-TREE: ======================== Flang: parse tree dump ========================
+! PARSE-TREE: Program -> ProgramUnit -> SubroutineSubprogram
+! PARSE-TREE: | SubroutineStmt
+! PARSE-TREE: | | Name = 'test_subroutine'
+! PARSE-TREE: | SpecificationPart
+! PARSE-TREE: | | ImplicitPart -> 
+! PARSE-TREE: | ExecutionPart -> Block
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> CompilerDirective -> InlineAlways -> Name = 'test_subroutine'
+! PARSE-TREE: | EndSubroutineStmt -> 
+! PARSE-TREE: ProgramUnit -> SubroutineSubprogram
+! PARSE-TREE: | SubroutineStmt
+! 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 -> 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_subroutine()'
+! PARSE-TREE: | | | Call
+! PARSE-TREE: | | | | ProcedureDesignator -> Name = 'test_subroutine'
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> CompilerDirective -> InlineAlways -> 
+! PARSE-TREE: | | ExecutionPartConstruct -> ExecutableConstruct -> ActionStmt -> CallStmt = 'CALL test_subroutine2()'
+! PARSE-TREE: | | | Call
+! 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
new file mode 100644
index 0000000000000..e4e569e9e4362
--- /dev/null
+++ b/flang/test/Semantics/inlinealways-directive01.f90
@@ -0,0 +1,17 @@
+! 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_subroutine()
+! WARNING: INLINEALWAYS name does not match the subroutine or function name [-Wignored-directive]
+  !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