Skip to content

[flang] Implement !DIR$ [NO]INLINE and FORCEINLINE directives #134350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

JDPailleux
Copy link
Contributor

@JDPailleux JDPailleux commented Apr 4, 2025

This patch adds the support of these two directives : !dir$ inline and !dir$ noinline.

  • !dir$ noinline tells to the compiler to not perform inlining on specific function calls by adding the noinline metadata on the call.
  • !dir$ inline tells to the compiler to attempt inlining on specific function calls by adding the inlinehint metadata on the call.
  • !dir$ forceinline tells to the compiler to always perfom inlining on specific function calls by adding the alwaysinline metadata on the call.

Currently, these directives can be placed before a DO LOOP, call functions or assignments. Maybe other statements can be added in the future if needed.

For the inline directive the correct name might be forceinline but I'm not sure ?

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

llvmbot commented Apr 4, 2025

@llvm/pr-subscribers-flang-codegen
@llvm/pr-subscribers-flang-semantics

@llvm/pr-subscribers-flang-parser

Author: Jean-Didier PAILLEUX (JDPailleux)

Changes

This patch adds the support of these two directives : !dir$ inline and !dir$ noinline.

  • !dir$ noinline tells to the compiler to not perform inlining on specific function calls by adding the noinline metadata on the call.
  • !dir$ inline tells to the compiler to not perform inlining on specific function calls by adding the alwaysinline metadata on the call.

Currently, these directives can be placed before a DO LOOP, call functions or assignments. Maybe other statements can be added in the future if needed.

For the inline directive the correct name might be forceinline but I'm not sure ?


Patch is 22.77 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/134350.diff

15 Files Affected:

  • (modified) flang/docs/Directives.md (+6)
  • (modified) flang/include/flang/Evaluate/call.h (+7)
  • (modified) flang/include/flang/Optimizer/Dialect/FIROps.td (+2)
  • (modified) flang/include/flang/Parser/dump-parse-tree.h (+2)
  • (modified) flang/include/flang/Parser/parse-tree.h (+5-1)
  • (modified) flang/lib/Lower/Bridge.cpp (+113-7)
  • (modified) flang/lib/Lower/ConvertCall.cpp (+7-1)
  • (modified) flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp (+3-1)
  • (modified) flang/lib/Parser/Fortran-parsers.cpp (+5)
  • (modified) flang/lib/Parser/unparse.cpp (+4)
  • (modified) flang/lib/Semantics/canonicalize-directives.cpp (+3-1)
  • (modified) flang/lib/Semantics/resolve-names.cpp (+3-1)
  • (added) flang/test/Integration/inline_directive.f90 (+51)
  • (added) flang/test/Lower/inline_directive.f90 (+51)
  • (modified) flang/test/Parser/compiler-directives.f90 (+20)
diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index 91c27cb510ea0..4f3748bf79722 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -53,6 +53,12 @@ A list of non-standard directives supported by Flang
 * `!dir$ novector` disabling vectorization on the following loop.
 * `!dir$ nounroll` disabling unrolling on the following loop.
 * `!dir$ nounroll_and_jam` disabling unrolling and jamming on the following loop.
+* `!dir$ inline` tells the compiler to attempt to inline routines if 
+  this directive is specified before a call statement or for all call function statements
+  within a DO LOOP. This directive can be improved later to support other place(s) for  
+  inlining function calls.
+* `!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.
 
 # Directive Details
 
diff --git a/flang/include/flang/Evaluate/call.h b/flang/include/flang/Evaluate/call.h
index 2a5929b873d74..7a1bad030c88f 100644
--- a/flang/include/flang/Evaluate/call.h
+++ b/flang/include/flang/Evaluate/call.h
@@ -254,6 +254,11 @@ class ProcedureRef {
   bool IsElemental() const { return proc_.IsElemental(); }
   bool hasAlternateReturns() const { return hasAlternateReturns_; }
 
+  bool hasNoInline() const { return noInline_; }
+  void set_noInline(bool ni) { noInline_ = ni; }
+  bool hasAlwaysInline() const { return alwaysInline_; }
+  void set_alwaysInline(bool ai) { alwaysInline_ = ai; }
+
   Expr<SomeType> *UnwrapArgExpr(int n) {
     if (static_cast<std::size_t>(n) < arguments_.size() && arguments_[n]) {
       return arguments_[n]->UnwrapExpr();
@@ -277,6 +282,8 @@ class ProcedureRef {
   ActualArguments arguments_;
   Chevrons chevrons_;
   bool hasAlternateReturns_;
+  bool noInline_{false};
+  bool alwaysInline_{false};
 };
 
 template <typename A> class FunctionRef : public ProcedureRef {
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 753e4bd18dc6d..18a198ca061b7 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -2490,6 +2490,8 @@ def fir_CallOp : fir_Op<"call",
     OptionalAttr<DictArrayAttr>:$arg_attrs,
     OptionalAttr<DictArrayAttr>:$res_attrs,
     OptionalAttr<fir_FortranProcedureFlagsAttr>:$procedure_attrs,
+    OptionalAttr<UnitAttr>:$no_inline,
+    OptionalAttr<UnitAttr>:$always_inline,
     DefaultValuedAttr<Arith_FastMathAttr,
                       "::mlir::arith::FastMathFlags::none">:$fastmath
   );
diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h
index c66f0735f33f0..4e6e2aa4f0f8d 100644
--- a/flang/include/flang/Parser/dump-parse-tree.h
+++ b/flang/include/flang/Parser/dump-parse-tree.h
@@ -204,8 +204,10 @@ class ParseTreeDumper {
   NODE(parser, CompilerDirective)
   NODE(CompilerDirective, AssumeAligned)
   NODE(CompilerDirective, IgnoreTKR)
+  NODE(CompilerDirective, Inline)
   NODE(CompilerDirective, LoopCount)
   NODE(CompilerDirective, NameValue)
+  NODE(CompilerDirective, NoInline)
   NODE(CompilerDirective, Unrecognized)
   NODE(CompilerDirective, VectorAlways)
   NODE(CompilerDirective, Unroll)
diff --git a/flang/include/flang/Parser/parse-tree.h b/flang/include/flang/Parser/parse-tree.h
index eeb438991feee..e86da56bb9d3d 100644
--- a/flang/include/flang/Parser/parse-tree.h
+++ b/flang/include/flang/Parser/parse-tree.h
@@ -3354,6 +3354,8 @@ struct StmtFunctionStmt {
 // !DIR$ NOVECTOR
 // !DIR$ NOUNROLL
 // !DIR$ NOUNROLL_AND_JAM
+// !DIR$ INLINE
+// !DIR$ NOINLINE
 // !DIR$ <anything else>
 struct CompilerDirective {
   UNION_CLASS_BOILERPLATE(CompilerDirective);
@@ -3382,11 +3384,13 @@ struct CompilerDirective {
   EMPTY_CLASS(NoVector);
   EMPTY_CLASS(NoUnroll);
   EMPTY_CLASS(NoUnrollAndJam);
+  EMPTY_CLASS(Inline);
+  EMPTY_CLASS(NoInline);
   EMPTY_CLASS(Unrecognized);
   CharBlock source;
   std::variant<std::list<IgnoreTKR>, LoopCount, std::list<AssumeAligned>,
       VectorAlways, std::list<NameValue>, Unroll, UnrollAndJam, Unrecognized,
-      NoVector, NoUnroll, NoUnrollAndJam>
+      NoVector, NoUnroll, NoUnrollAndJam, Inline, NoInline>
       u;
 };
 
diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 65edf1cea8761..9bad56448a3ea 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -1828,6 +1828,23 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     setCurrentPosition(stmt.source);
     assert(stmt.typedCall && "Call was not analyzed");
     mlir::Value res{};
+
+    // Set 'no_inline' or 'always_inline' to true on the ProcedureRef.
+    // The NoInline and AlwaysInline attribute will be set in genProcedureRef
+    // later.
+    for (const auto *dir : eval.dirs) {
+      Fortran::common::visit(
+          Fortran::common::visitors{
+              [&](const Fortran::parser::CompilerDirective::Inline &) {
+                stmt.typedCall->set_alwaysInline(true);
+              },
+              [&](const Fortran::parser::CompilerDirective::NoInline &) {
+                stmt.typedCall->set_noInline(true);
+              },
+              [&](const auto &) {}},
+          dir->u);
+    }
+
     if (lowerToHighLevelFIR()) {
       std::optional<mlir::Type> resultType;
       if (stmt.typedCall->hasAlternateReturns())
@@ -2053,6 +2070,47 @@ class FirConverter : public Fortran::lower::AbstractConverter {
     // so no clean-up needs to be generated for these entities.
   }
 
+  void attachInlineAttributes(
+      mlir::Operation &op,
+      const llvm::ArrayRef<const Fortran::parser::CompilerDirective *> &dirs) {
+    if (dirs.empty())
+      return;
+
+    for (mlir::Value operand : op.getOperands()) {
+      if (operand.getDefiningOp())
+        attachInlineAttributes(*operand.getDefiningOp(), dirs);
+    }
+
+    if (fir::CallOp callOp = mlir::dyn_cast<fir::CallOp>(op)) {
+      for (const auto *dir : dirs) {
+        Fortran::common::visit(
+            Fortran::common::visitors{
+                [&](const Fortran::parser::CompilerDirective::NoInline &) {
+                  callOp.setNoInlineAttr(builder->getUnitAttr());
+                },
+                [&](const Fortran::parser::CompilerDirective::Inline &) {
+                  callOp.setAlwaysInlineAttr(builder->getUnitAttr());
+                },
+                [&](const auto &) {}},
+            dir->u);
+      }
+    }
+  }
+
+  void attachAttributesToDoLoopOperations(
+      fir::DoLoopOp &doLoop,
+      llvm::SmallVectorImpl<const Fortran::parser::CompilerDirective *> &dirs) {
+    if (!doLoop.getOperation() || dirs.empty())
+      return;
+
+    for (mlir::Block &block : doLoop.getRegion()) {
+      for (mlir::Operation &op : block.getOperations()) {
+        if (!dirs.empty())
+          attachInlineAttributes(op, dirs);
+      }
+    }
+  }
+
   /// Generate FIR for a DO construct. There are six variants:
   ///  - unstructured infinite and while loops
   ///  - structured and unstructured increment loops
@@ -2162,6 +2220,10 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
     // This call may generate a branch in some contexts.
     genFIR(endDoEval, unstructuredContext);
+
+    // Add attribute(s) on operations in fir::DoLoopOp if necessary
+    for (IncrementLoopInfo &info : incrementLoopNestInfo)
+      attachAttributesToDoLoopOperations(info.doLoop, doStmtEval.dirs);
   }
 
   /// Generate FIR to evaluate loop control values (lower, upper and step).
@@ -2935,6 +2997,26 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       e->dirs.push_back(&dir);
   }
 
+  void
+  attachInliningDirectiveToStmt(const Fortran::parser::CompilerDirective &dir,
+                                Fortran::lower::pft::Evaluation *e) {
+    while (e->isDirective())
+      e = e->lexicalSuccessor;
+
+    // If the successor is a statement or a do loop, the compiler
+    // will perform inlining.
+    if (e->isA<Fortran::parser::CallStmt>() ||
+        e->isA<Fortran::parser::NonLabelDoStmt>() ||
+        e->isA<Fortran::parser::AssignmentStmt>()) {
+      e->dirs.push_back(&dir);
+    } else {
+      mlir::Location loc = toLocation();
+      mlir::emitWarning(loc,
+                        "Inlining directive not in front of loops, function"
+                        "call or assignment.\n");
+    }
+  }
+
   void genFIR(const Fortran::parser::CompilerDirective &dir) {
     Fortran::lower::pft::Evaluation &eval = getEval();
 
@@ -2958,6 +3040,12 @@ class FirConverter : public Fortran::lower::AbstractConverter {
             [&](const Fortran::parser::CompilerDirective::NoUnrollAndJam &) {
               attachDirectiveToLoop(dir, &eval);
             },
+            [&](const Fortran::parser::CompilerDirective::Inline &) {
+              attachInliningDirectiveToStmt(dir, &eval);
+            },
+            [&](const Fortran::parser::CompilerDirective::NoInline &) {
+              attachInliningDirectiveToStmt(dir, &eval);
+            },
             [&](const auto &) {}},
         dir.u);
   }
@@ -4763,7 +4851,9 @@ class FirConverter : public Fortran::lower::AbstractConverter {
 
   void genDataAssignment(
       const Fortran::evaluate::Assignment &assign,
-      const Fortran::evaluate::ProcedureRef *userDefinedAssignment) {
+      const Fortran::evaluate::ProcedureRef *userDefinedAssignment,
+      const llvm::ArrayRef<const Fortran::parser::CompilerDirective *> &dirs =
+          {}) {
     mlir::Location loc = getCurrentLocation();
     fir::FirOpBuilder &builder = getFirOpBuilder();
 
@@ -4836,12 +4926,22 @@ class FirConverter : public Fortran::lower::AbstractConverter {
       Fortran::lower::StatementContext localStmtCtx;
       hlfir::Entity rhs = evaluateRhs(localStmtCtx);
       hlfir::Entity lhs = evaluateLhs(localStmtCtx);
-      if (isCUDATransfer && !hasCUDAImplicitTransfer)
+      if (isCUDATransfer && !hasCUDAImplicitTransfer) {
         genCUDADataTransfer(builder, loc, assign, lhs, rhs);
-      else
+      } else {
+        // If RHS or LHS have a CallOp in their expression, this operation will
+        // have the 'no_inline' or 'always_inline' attribute if there is a
+        // directive just before the assignement.
+        if (!dirs.empty()) {
+          if (rhs.getDefiningOp())
+            attachInlineAttributes(*rhs.getDefiningOp(), dirs);
+          if (lhs.getDefiningOp())
+            attachInlineAttributes(*lhs.getDefiningOp(), dirs);
+        }
         builder.create<hlfir::AssignOp>(loc, rhs, lhs,
                                         isWholeAllocatableAssignment,
                                         keepLhsLengthInAllocatableAssignment);
+      }
       if (hasCUDAImplicitTransfer && !isInDeviceContext) {
         localSymbols.popScope();
         for (mlir::Value temp : implicitTemps)
@@ -4909,16 +5009,21 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   }
 
   /// Shared for both assignments and pointer assignments.
-  void genAssignment(const Fortran::evaluate::Assignment &assign) {
+  void
+  genAssignment(const Fortran::evaluate::Assignment &assign,
+                const llvm::ArrayRef<const Fortran::parser::CompilerDirective *>
+                    &dirs = {}) {
     mlir::Location loc = toLocation();
     if (lowerToHighLevelFIR()) {
       Fortran::common::visit(
           Fortran::common::visitors{
               [&](const Fortran::evaluate::Assignment::Intrinsic &) {
-                genDataAssignment(assign, /*userDefinedAssignment=*/nullptr);
+                genDataAssignment(assign, /*userDefinedAssignment=*/nullptr,
+                                  dirs);
               },
               [&](const Fortran::evaluate::ProcedureRef &procRef) {
-                genDataAssignment(assign, /*userDefinedAssignment=*/&procRef);
+                genDataAssignment(assign, /*userDefinedAssignment=*/&procRef,
+                                  dirs);
               },
               [&](const Fortran::evaluate::Assignment::BoundsSpec &lbExprs) {
                 if (isInsideHlfirForallOrWhere())
@@ -5323,7 +5428,8 @@ class FirConverter : public Fortran::lower::AbstractConverter {
   }
 
   void genFIR(const Fortran::parser::AssignmentStmt &stmt) {
-    genAssignment(*stmt.typedAssignment->v);
+    Fortran::lower::pft::Evaluation &eval = getEval();
+    genAssignment(*stmt.typedAssignment->v, eval.dirs);
   }
 
   void genFIR(const Fortran::parser::SyncAllStmt &stmt) {
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 6a0f4d1090adc..fe8d84c2c8c37 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -647,9 +647,15 @@ Fortran::lower::genCallOpAndResult(
       callResult = dispatch.getResult(0);
   } else {
     // Standard procedure call with fir.call.
+    mlir::UnitAttr noinlineAttr, alwaysinlineAttr;
+    if (caller.getCallDescription().hasNoInline())
+      noinlineAttr = builder.getUnitAttr();
+    else if (caller.getCallDescription().hasAlwaysInline())
+      alwaysinlineAttr = builder.getUnitAttr();
     auto call = builder.create<fir::CallOp>(
         loc, funcType.getResults(), funcSymbolAttr, operands,
-        /*arg_attrs=*/nullptr, /*res_attrs=*/nullptr, procAttrs);
+        /*arg_attrs=*/nullptr, /*res_attrs=*/nullptr, procAttrs, noinlineAttr,
+        alwaysinlineAttr);
 
     callNumResults = call.getNumResults();
     if (callNumResults != 0)
diff --git a/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp b/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp
index 0c78a878cdc53..eff623d983535 100644
--- a/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp
+++ b/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp
@@ -207,7 +207,9 @@ struct DispatchOpConv : public OpConversionPattern<fir::DispatchOp> {
     args.append(dispatch.getArgs().begin(), dispatch.getArgs().end());
     rewriter.replaceOpWithNewOp<fir::CallOp>(
         dispatch, resTypes, nullptr, args, dispatch.getArgAttrsAttr(),
-        dispatch.getResAttrsAttr(), dispatch.getProcedureAttrsAttr());
+        dispatch.getResAttrsAttr(), dispatch.getProcedureAttrsAttr(),
+        /*no_inline*/ mlir::UnitAttr{},
+        /*alwais_inline*/ mlir::UnitAttr{});
     return mlir::success();
   }
 
diff --git a/flang/lib/Parser/Fortran-parsers.cpp b/flang/lib/Parser/Fortran-parsers.cpp
index fbe629ab52935..ef9d4b191ad15 100644
--- a/flang/lib/Parser/Fortran-parsers.cpp
+++ b/flang/lib/Parser/Fortran-parsers.cpp
@@ -1314,6 +1314,9 @@ constexpr auto novector{"NOVECTOR" >> construct<CompilerDirective::NoVector>()};
 constexpr auto nounroll{"NOUNROLL" >> construct<CompilerDirective::NoUnroll>()};
 constexpr auto nounrollAndJam{
     "NOUNROLL_AND_JAM" >> construct<CompilerDirective::NoUnrollAndJam>()};
+constexpr auto inlineDir{"INLINE" >> construct<CompilerDirective::Inline>()};
+constexpr auto noinlineDir{
+    "NOINLINE" >> construct<CompilerDirective::NoInline>()};
 TYPE_PARSER(beginDirective >> "DIR$ "_tok >>
     sourced((construct<CompilerDirective>(ignore_tkr) ||
                 construct<CompilerDirective>(loopCount) ||
@@ -1324,6 +1327,8 @@ TYPE_PARSER(beginDirective >> "DIR$ "_tok >>
                 construct<CompilerDirective>(novector) ||
                 construct<CompilerDirective>(nounrollAndJam) ||
                 construct<CompilerDirective>(nounroll) ||
+                construct<CompilerDirective>(noinlineDir) ||
+                construct<CompilerDirective>(inlineDir) ||
                 construct<CompilerDirective>(
                     many(construct<CompilerDirective::NameValue>(
                         name, maybe(("="_tok || ":"_tok) >> digitString64))))) /
diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp
index 47dae0ae753d2..f7eaab45fb77b 100644
--- a/flang/lib/Parser/unparse.cpp
+++ b/flang/lib/Parser/unparse.cpp
@@ -1864,6 +1864,10 @@ class UnparseVisitor {
             [&](const CompilerDirective::NoUnrollAndJam &) {
               Word("!DIR$ NOUNROLL_AND_JAM");
             },
+            [&](const CompilerDirective::Inline &) { Word("!DIR$ INLINE"); },
+            [&](const CompilerDirective::NoInline &) {
+              Word("!DIR$ NOINLINE");
+            },
             [&](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 104df253ab642..ab6f801a6d350 100644
--- a/flang/lib/Semantics/canonicalize-directives.cpp
+++ b/flang/lib/Semantics/canonicalize-directives.cpp
@@ -60,7 +60,9 @@ static bool IsExecutionDirective(const parser::CompilerDirective &dir) {
       std::holds_alternative<parser::CompilerDirective::UnrollAndJam>(dir.u) ||
       std::holds_alternative<parser::CompilerDirective::NoVector>(dir.u) ||
       std::holds_alternative<parser::CompilerDirective::NoUnroll>(dir.u) ||
-      std::holds_alternative<parser::CompilerDirective::NoUnrollAndJam>(dir.u);
+      std::holds_alternative<parser::CompilerDirective::NoUnrollAndJam>(dir.u) ||
+      std::holds_alternative<parser::CompilerDirective::Inline>(dir.u) ||
+      std::holds_alternative<parser::CompilerDirective::NoInline>(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 11c0ecc9e8410..1b67f351b7433 100644
--- a/flang/lib/Semantics/resolve-names.cpp
+++ b/flang/lib/Semantics/resolve-names.cpp
@@ -9576,7 +9576,9 @@ void ResolveNamesVisitor::Post(const parser::CompilerDirective &x) {
       std::holds_alternative<parser::CompilerDirective::UnrollAndJam>(x.u) ||
       std::holds_alternative<parser::CompilerDirective::NoVector>(x.u) ||
       std::holds_alternative<parser::CompilerDirective::NoUnroll>(x.u) ||
-      std::holds_alternative<parser::CompilerDirective::NoUnrollAndJam>(x.u)) {
+      std::holds_alternative<parser::CompilerDirective::NoUnrollAndJam>(x.u) ||
+      std::holds_alternative<parser::CompilerDirective::Inline>(x.u) ||
+      std::holds_alternative<parser::CompilerDirective::NoInline>(x.u)) {
     return;
   }
   if (const auto *tkr{
diff --git a/flang/test/Integration/inline_directive.f90 b/flang/test/Integration/inline_directive.f90
new file mode 100644
index 0000000000000..6e55227e74b9a
--- /dev/null
+++ b/flang/test/Integration/inline_directive.f90
@@ -0,0 +1,51 @@
+! RUN: %flang_fc1 -emit-llvm -o - %s | FileCheck %s
+
+! CHECK-LABEL: test_inline
+subroutine test_inline()
+  integer :: x, y
+!CHECK:  %[[VAL_1:.*]] = alloca i32, i64 1, align 4
+!CHECK:  %[[VAL_2:.*]] = alloca i32, i64 1, align 4
+!CHECK:  %[[VAL_3:.*]] = alloca i32, i64 1, align 4
+!CHECK:  %[[VAL_4:.*]] = alloca i32, i64 1, align 4
+
+  !dir$ inline
+  y = g(x)
+  !dir$ inline
+  call f(x, y)
+!CHECK:  %[[VAL_5:.*]] = load i32, ptr %[[VAL_3]], align 4
+!CHECK:  %[[VAL_6:.*]] = mul i32 %[[VAL_5]], 2
+!CHECK:  store i32 %6, ptr %[[VAL_1]], align 4
+!CHECK:  %[[VAL_7:.*]] = load i32, ptr %[[VAL_1]], align 4
+!CHECK:  store i32 %7, ptr %[[VAL_2]], align 4
+!CHECK:  %[[VAL_8:.]] = load i32, ptr %[[VAL_3]], align 4
+!CHECK:  %[[VAL_9:.]] = mul i32 %[[VAL_8]], 2
+!CHECK:  store i32 %9, ptr %[[VAL_2]], align 4
+
+  !dir$ noinline
+  y = g(x)
+  !dir$ noinline
+  call f(x, y)
+!CHECK:  %[[VAL_10:.*]] = call i32 @_QFtest_inlinePg(ptr %[[VAL_3]]) #[[NOINLINE:.*]]
+!CHECK:  store i32 %[[VAL_10]], ptr %[[VAL_2]], align 4
+!CHECK:  call void @_QFtest_inlinePf(ptr %[[VAL_3]], ptr %[[VAL_2]]) #[[NOINLINE]]
+
+  !dir$ noinline
+  do i = 1, 100
+    call f(x, y)
+    !CHECK: br i1 %[[VAL_14:.*]], label %[[VAL_15:.*]], label %[[VAL_19:.*]]
+    !CHECK: call void @_QFtest_inlinePf(ptr %[[VAL_3]], ptr %[[VAL_2]]) #[[NOINLINE]]
+  enddo
+
+  contains
+    subroutine f(x, y)
+      integer, intent(in) :: x
+      integer, intent(out) :: y
+      y = x*2
+    end subroutine f
+    integer function g(x)
+      integer :: x
+      g = x*2
+    end function g
+end subroutine test_inline
+
+!CHECK: attributes #[[NOINLINE]] = { noinline }
diff --git a/flang/test/Lower/inline_directive.f90 b/flang/test/Lower/inline_directive.f90
new file mode 100644
index 0000000000000..a6b8300f59efe
--- /dev/null
+++ b/flang/test/Lower/inline_directive.f90
@@ -0,0 +1,51 @@
+! RUN: %flang_...
[truncated]

Copy link

github-actions bot commented Apr 4, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@JDPailleux JDPailleux force-pushed the jdp/flang/inline-directives branch from 02e9631 to 5ae8ca3 Compare April 4, 2025 06:56
@kiranktp
Copy link
Contributor

kiranktp commented Apr 4, 2025

Thanks for the patch @JDPailleux
In my opinion, having 3 options under inline family of directive will be more useful.

!dir$ inline
This will be a hint to the optimizer
By adding the "inlinehint" metadata on the call
!dir$ forceinline
This should be used with caution.
By adding the "alwaysinline" metadata on the call
!dir$ noinline
By adding the "noinline" metadata on the call

This matches the pragma's supported by intel compiler as well [Below snippet from intel website]
!DIR$ INLINE [RECURSIVE]
!DIR$ FORCEINLINE [RECURSIVE]
!DIR$ NOINLINE

@JDPailleux
Copy link
Contributor Author

Thanks for the patch @JDPailleux In my opinion, having 3 options under inline family of directive will be more useful.

!dir$ inline This will be a hint to the optimizer By adding the "inlinehint" metadata on the call !dir$ forceinline This should be used with caution. By adding the "alwaysinline" metadata on the call !dir$ noinline By adding the "noinline" metadata on the call

This matches the pragma's supported by intel compiler as well [Below snippet from intel website] !DIR$ INLINE [RECURSIVE] !DIR$ FORCEINLINE [RECURSIVE] !DIR$ NOINLINE

Thanks for your reply ! I'll rename the INLINE into FORCEINLINE.
And adding !dir$ inline will be done in another PR with the addition of the inlinehint attribute into the LLVM mlir dialect just like in this PR : "#133726.

@JDPailleux JDPailleux force-pushed the jdp/flang/inline-directives branch 2 times, most recently from 038819a to 7faed13 Compare April 11, 2025 12:15
@JDPailleux JDPailleux changed the title [flang] Implement !DIR$ [NO]INLINE directives [flang] Implement !DIR$ [NO]INLINE and FORCEINLINE directives Apr 11, 2025
Copy link
Contributor

@kiranchandramohan kiranchandramohan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!dir$ inline tells to the compiler to not perform inlining on specific function calls by adding the inlinehint metadata on the call.

Assuming the not here is a typo.

!dir$ forceinline tells to the compiler to not perform inlining on specific function calls by adding the alwaysinline metadata on the call.

Assuming the not here is a typo.

Does LLVM support inline, forceinline attributes on the call instruction? Is there a similar clang pragma?

When I did a quick look (not in detail) I only saw references of these attributes on the function definition/declaration.

@@ -0,0 +1,67 @@
! RUN: %flang_fc1 -emit-llvm -o - %s | FileCheck %s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should sparingly use tests in the Integration directory. All tests in this directory are supposed to carry a message saying the same.
See Readme.md in this directory

Comment on lines 56 to 63
* `!dir$ inline` tells the compiler to attempt to inline routines if
this directive is specified before a call statement or for all call function statements
within a DO LOOP. This directive can be improved later to support other place(s) for
inlining function calls.
* `!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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have to update this with forceinline and the meaning of inline as well.

Copy link
Contributor

@vzakhari vzakhari left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the changes! I have a couple of remarks.

Comment on lines 2497 to 2499
UnitAttr:$no_inline,
UnitAttr:$always_inline,
UnitAttr:$inline_hint,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is they are exclusive, then maybe we should use a enum attr.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes they are exclusive, so I can make these changes

@@ -2162,6 +2226,10 @@ class FirConverter : public Fortran::lower::AbstractConverter {

// This call may generate a branch in some contexts.
genFIR(endDoEval, unstructuredContext);

// Add attribute(s) on operations in fir::DoLoopOp if necessary
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there special handling for fir.do_loop but not for fir.if and other operations with regions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the moment, I've focused on do_loop, assignment and callstmt. And in doc/Directives.md I've added a message to say that these directives are partially supported. But if it's really necessary I can add support for the other operations.

@JDPailleux
Copy link
Contributor Author

!dir$ inline tells to the compiler to not perform inlining on specific function calls by adding the inlinehint metadata on the call.

Assuming the not here is a typo.

!dir$ forceinline tells to the compiler to not perform inlining on specific function calls by adding the alwaysinline metadata on the call.

Assuming the not here is a typo.

Does LLVM support inline, forceinline attributes on the call instruction? Is there a similar clang pragma?

When I did a quick look (not in detail) I only saw references of these attributes on the function definition/declaration.

Hi,
LLVM does not directly support inline and forceinline attributes (these are directive names used by Intel). The alwaysinline and inlinehint attributes can be found in function definitions/declarations and are attributes to use for theses directives. And for calls, you can directly modify these attributes locally, without adding them to the function definition.

@JDPailleux JDPailleux force-pushed the jdp/flang/inline-directives branch from 7faed13 to 181209a Compare April 28, 2025 08:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants