[flang-commits] [flang] [Flang] Add `INLINEALWAYS` Compiler Directive (PR #192674)
Jack Styles via flang-commits
flang-commits at lists.llvm.org
Fri Apr 17 07:51:48 PDT 2026
https://github.com/Stylie777 updated https://github.com/llvm/llvm-project/pull/192674
>From a76b05fe5cfb8eea5bda7f79fa997e3dbe552d1d 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/2] [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 | 3 +-
flang/lib/Semantics/resolve-names.cpp | 3 +-
flang/test/Lower/inlinealways-directive.f90 | 26 +++++++++
flang/test/Parser/inlinealways-directive.f90 | 58 +++++++++++++++++++
9 files changed, 132 insertions(+), 3 deletions(-)
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 eefab487413da..e13fbefcc8c2e 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(parser, ComplexLiteralConstant)
NODE(parser, ComplexPart)
NODE(parser, ComponentArraySpec)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index 960ebbcc99efb..bc4f7fd443972 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$ <anything else>
struct CompilerDirective {
@@ -3380,6 +3381,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);
@@ -3392,7 +3397,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>
+ NoInline, InlineAlways, Prefetch, IVDep>
u;
};
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 28c82a6ca99ce..733e02cd58558 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -2083,6 +2083,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);
},
@@ -2434,6 +2437,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);
}
@@ -3508,6 +3514,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) {
@@ -3586,6 +3607,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 auto &) {}},
dir.u);
}
diff --git a/flang/lib/Parser/Fortran-parsers.cpp b/flang/lib/Parser/Fortran-parsers.cpp
index b67475074217c..bba8adae42950 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>()};
TYPE_PARSER(beginDirective >> some(letter) >> "$ "_tok >>
@@ -1355,6 +1357,7 @@ TYPE_PARSER(beginDirective >> some(letter) >> "$ "_tok >>
construct<CompilerDirective>(nounroll) ||
construct<CompilerDirective>(noinlineDir) ||
construct<CompilerDirective>(forceinlineDir) ||
+ construct<CompilerDirective>(inlinealwaysDir) ||
construct<CompilerDirective>(inlineDir) ||
construct<CompilerDirective>(ivdep) ||
construct<CompilerDirective>(
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 5ddd0cfc3a1ef..e9ee0576eb4ad 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::Unrecognized &) {
Word("!DIR$ ");
Word(x.source.ToString());
diff --git a/flang/lib/Semantics/canonicalize-directives.cpp b/flang/lib/Semantics/canonicalize-directives.cpp
index f32a3d34c6572..8a1c4d235a666 100644
--- a/flang/lib/Semantics/canonicalize-directives.cpp
+++ b/flang/lib/Semantics/canonicalize-directives.cpp
@@ -66,7 +66,8 @@ static bool IsExecutionDirective(const parser::CompilerDirective &dir) {
std::holds_alternative<parser::CompilerDirective::ForceInline>(dir.u) ||
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::IVDep>(dir.u) ||
+ std::holds_alternative<parser::CompilerDirective::InlineAlways>(dir.u);
}
void CanonicalizationOfDirectives::Post(parser::SpecificationPart &spec) {
diff --git a/flang/lib/Semantics/resolve-names.cpp b/flang/lib/Semantics/resolve-names.cpp
index e1c1167af1604..42556954d7126 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10279,7 +10279,8 @@ void ResolveNamesVisitor::Post(const parser::CompilerDirective &x) {
std::holds_alternative<parser::CompilerDirective::Inline>(x.u) ||
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::IVDep>(x.u) ||
+ std::holds_alternative<parser::CompilerDirective::InlineAlways>(x.u)) {
return;
}
if (const auto *tkr{
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 6ef1ba69eced86ba3cf9c6c2f16f957666aa63cd 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/2] 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 bc4f7fd443972..9c7813122f0d6 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -3382,8 +3382,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 733e02cd58558..575989e64d11d 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3514,15 +3514,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;
}
}
@@ -3607,7 +3609,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 e9ee0576eb4ad..21abd7de1c7bb 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::Unrecognized &) {
More information about the flang-commits
mailing list