[flang-commits] [flang] [Flang] Add `INLINEALWAYS` Compiler Directive (PR #192674)
Jack Styles via flang-commits
flang-commits at lists.llvm.org
Tue Apr 21 01:49:36 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/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 | 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/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 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 &) {
>From 70d068b2867cd4b3b486f99889512bfb1e855f17 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 1654a9b2623f2..8f487f3e15898 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -99,6 +99,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 84220ac249138de6457b42c0493b5153caa03844 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 | 18 ++++++++++++++++--
flang/test/Lower/inlinealways-directive.f90 | 10 +++-------
.../Semantics/inlinealways-directive01.f90 | 12 ++++++++++++
5 files changed, 35 insertions(+), 18 deletions(-)
create mode 100644 flang/test/Semantics/inlinealways-directive01.f90
diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index 8f487f3e15898..b128acd9273b4 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -100,9 +100,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 575989e64d11d..cc9024a87fa40 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -3520,15 +3520,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 42556954d7126..4db4edd3b8d50 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10279,8 +10279,7 @@ 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::InlineAlways>(x.u)) {
+ std::holds_alternative<parser::CompilerDirective::IVDep>(x.u)) {
return;
}
if (const auto *tkr{
@@ -10372,6 +10371,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 4492044686e50b919129554e8cdaae52f7a1da54 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 4db4edd3b8d50..3ae6dedbffb05 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -10384,7 +10384,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 19ddecc8d2987f9364ceae93fd1dea3359e839db 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 ed7949a3a6cccb64028c2ed3bd9165ba76464f3c 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 6f1376829d441b74e18b6d9d836abe1dbaed09cb 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 b128acd9273b4..711e40c7a6463 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -100,9 +100,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