diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md index 91c27cb510ea0..e6d45415b55e8 100644 --- a/flang/docs/Directives.md +++ b/flang/docs/Directives.md @@ -53,6 +53,14 @@ 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$ forceinline` works in the same way as the `inline` directive, but it force + inlining by the compiler on a function call statement. +* `!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..b09f203c0de37 100644 --- a/flang/include/flang/Evaluate/call.h +++ b/flang/include/flang/Evaluate/call.h @@ -254,6 +254,13 @@ 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; } + bool hasInlineHint() const { return inlineHint_; } + void set_inlineHint(bool ih) { inlineHint_ = ih; } + Expr *UnwrapArgExpr(int n) { if (static_cast(n) < arguments_.size() && arguments_[n]) { return arguments_[n]->UnwrapExpr(); @@ -277,6 +284,9 @@ class ProcedureRef { ActualArguments arguments_; Chevrons chevrons_; bool hasAlternateReturns_; + bool noInline_{false}; + bool alwaysInline_{false}; + bool inlineHint_{false}; }; template class FunctionRef : public ProcedureRef { diff --git a/flang/include/flang/Optimizer/Dialect/FIRAttr.td b/flang/include/flang/Optimizer/Dialect/FIRAttr.td index 3ebc24951cfff..ce1683bf5b386 100644 --- a/flang/include/flang/Optimizer/Dialect/FIRAttr.td +++ b/flang/include/flang/Optimizer/Dialect/FIRAttr.td @@ -200,4 +200,24 @@ def fir_OpenMPSafeTempArrayCopyAttr : fir_Attr<"OpenMPSafeTempArrayCopy"> { }]; } +/// Fortran inline attribute +def FIRinlineNone : I32BitEnumAttrCaseNone<"none">; +def FIRinlineNo : I32BitEnumAttrCaseBit<"no_inline", 0>; +def FIRinlineAlways : I32BitEnumAttrCaseBit<"always_inline", 1>; +def FIRinlineHint : I32BitEnumAttrCaseBit<"inline_hint", 2>; + +def fir_FortranInlineEnum + : I32BitEnumAttr<"FortranInlineEnum", "Fortran inline attributes", + [FIRinlineNone, FIRinlineNo, FIRinlineAlways, + FIRinlineHint]> { + let separator = ", "; + let cppNamespace = "::fir"; + let genSpecializedAttr = 0; + let printBitEnumPrimaryGroups = 1; +} + +def fir_FortranInlineAttr + : EnumAttr { + let assemblyFormat = "`<` $value `>`"; +} #endif // FIR_DIALECT_FIR_ATTRS diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td index f9dc2e51a396c..0c7e3f982e1a2 100644 --- a/flang/include/flang/Optimizer/Dialect/FIROps.td +++ b/flang/include/flang/Optimizer/Dialect/FIROps.td @@ -2494,6 +2494,7 @@ def fir_CallOp : fir_Op<"call", OptionalAttr:$arg_attrs, OptionalAttr:$res_attrs, OptionalAttr:$procedure_attrs, + OptionalAttr:$inline_attr, DefaultValuedAttr:$fastmath ); diff --git a/flang/include/flang/Parser/dump-parse-tree.h b/flang/include/flang/Parser/dump-parse-tree.h index c66f0735f33f0..3f109fd502c06 100644 --- a/flang/include/flang/Parser/dump-parse-tree.h +++ b/flang/include/flang/Parser/dump-parse-tree.h @@ -204,8 +204,11 @@ class ParseTreeDumper { NODE(parser, CompilerDirective) NODE(CompilerDirective, AssumeAligned) NODE(CompilerDirective, IgnoreTKR) + NODE(CompilerDirective, Inline) + NODE(CompilerDirective, ForceInline) 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..96f2fe4744bf8 100644 --- a/flang/include/flang/Parser/parse-tree.h +++ b/flang/include/flang/Parser/parse-tree.h @@ -3354,6 +3354,9 @@ struct StmtFunctionStmt { // !DIR$ NOVECTOR // !DIR$ NOUNROLL // !DIR$ NOUNROLL_AND_JAM +// !DIR$ FORCEINLINE +// !DIR$ INLINE +// !DIR$ NOINLINE // !DIR$ struct CompilerDirective { UNION_CLASS_BOILERPLATE(CompilerDirective); @@ -3382,11 +3385,14 @@ struct CompilerDirective { EMPTY_CLASS(NoVector); EMPTY_CLASS(NoUnroll); EMPTY_CLASS(NoUnrollAndJam); + EMPTY_CLASS(ForceInline); + EMPTY_CLASS(Inline); + EMPTY_CLASS(NoInline); EMPTY_CLASS(Unrecognized); CharBlock source; std::variant, LoopCount, std::list, VectorAlways, std::list, Unroll, UnrollAndJam, Unrecognized, - NoVector, NoUnroll, NoUnrollAndJam> + NoVector, NoUnroll, NoUnrollAndJam, ForceInline, Inline, NoInline> u; }; diff --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp index b4d1197822a43..2c09a0140da73 100644 --- a/flang/lib/Lower/Bridge.cpp +++ b/flang/lib/Lower/Bridge.cpp @@ -1828,6 +1828,26 @@ class FirConverter : public Fortran::lower::AbstractConverter { setCurrentPosition(stmt.source); assert(stmt.typedCall && "Call was not analyzed"); mlir::Value res{}; + + // Set 'no_inline', 'inline_hint' 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::ForceInline &) { + stmt.typedCall->set_alwaysInline(true); + }, + [&](const Fortran::parser::CompilerDirective::Inline &) { + stmt.typedCall->set_inlineHint(true); + }, + [&](const Fortran::parser::CompilerDirective::NoInline &) { + stmt.typedCall->set_noInline(true); + }, + [&](const auto &) {}}, + dir->u); + } + if (lowerToHighLevelFIR()) { std::optional resultType; if (stmt.typedCall->hasAlternateReturns()) @@ -2053,6 +2073,50 @@ 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 &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(op)) { + for (const auto *dir : dirs) { + Fortran::common::visit( + Fortran::common::visitors{ + [&](const Fortran::parser::CompilerDirective::NoInline &) { + callOp.setInlineAttr(fir::FortranInlineEnum::no_inline); + }, + [&](const Fortran::parser::CompilerDirective::Inline &) { + callOp.setInlineAttr(fir::FortranInlineEnum::inline_hint); + }, + [&](const Fortran::parser::CompilerDirective::ForceInline &) { + callOp.setInlineAttr(fir::FortranInlineEnum::always_inline); + }, + [&](const auto &) {}}, + dir->u); + } + } + } + + void attachAttributesToDoLoopOperations( + fir::DoLoopOp &doLoop, + llvm::SmallVectorImpl &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 +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 + for (IncrementLoopInfo &info : incrementLoopNestInfo) + attachAttributesToDoLoopOperations(info.doLoop, doStmtEval.dirs); } /// Generate FIR to evaluate loop control values (lower, upper and step). @@ -2935,6 +3003,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() || + e->isA() || + e->isA()) { + 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 +3046,15 @@ class FirConverter : public Fortran::lower::AbstractConverter { [&](const Fortran::parser::CompilerDirective::NoUnrollAndJam &) { attachDirectiveToLoop(dir, &eval); }, + [&](const Fortran::parser::CompilerDirective::ForceInline &) { + attachInliningDirectiveToStmt(dir, &eval); + }, + [&](const Fortran::parser::CompilerDirective::Inline &) { + attachInliningDirectiveToStmt(dir, &eval); + }, + [&](const Fortran::parser::CompilerDirective::NoInline &) { + attachInliningDirectiveToStmt(dir, &eval); + }, [&](const auto &) {}}, dir.u); } @@ -4761,7 +4858,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 &dirs = + {}) { mlir::Location loc = getCurrentLocation(); fir::FirOpBuilder &builder = getFirOpBuilder(); @@ -4834,12 +4933,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(loc, rhs, lhs, isWholeAllocatableAssignment, keepLhsLengthInAllocatableAssignment); + } if (hasCUDAImplicitTransfer && !isInDeviceContext) { localSymbols.popScope(); for (mlir::Value temp : implicitTemps) @@ -4907,16 +5016,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 + &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()) @@ -5321,7 +5435,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 d674775ffb522..59e1b89475d3e 100644 --- a/flang/lib/Lower/ConvertCall.cpp +++ b/flang/lib/Lower/ConvertCall.cpp @@ -645,9 +645,20 @@ Fortran::lower::genCallOpAndResult( callResult = dispatch.getResult(0); } else { // Standard procedure call with fir.call. + fir::FortranInlineEnumAttr inlineAttr; + + if (caller.getCallDescription().hasNoInline()) + inlineAttr = fir::FortranInlineEnumAttr::get( + builder.getContext(), fir::FortranInlineEnum::no_inline); + else if (caller.getCallDescription().hasInlineHint()) + inlineAttr = fir::FortranInlineEnumAttr::get( + builder.getContext(), fir::FortranInlineEnum::inline_hint); + else if (caller.getCallDescription().hasAlwaysInline()) + inlineAttr = fir::FortranInlineEnumAttr::get( + builder.getContext(), fir::FortranInlineEnum::always_inline); auto call = builder.create( loc, funcType.getResults(), funcSymbolAttr, operands, - /*arg_attrs=*/nullptr, /*res_attrs=*/nullptr, procAttrs); + /*arg_attrs=*/nullptr, /*res_attrs=*/nullptr, procAttrs, inlineAttr); callNumResults = call.getNumResults(); if (callNumResults != 0) diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp index 907cac2bcaf65..8964f9f64f6a7 100644 --- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp +++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp @@ -634,6 +634,18 @@ struct CallOpConversion : public fir::FIROpConversion { if (mlir::ArrayAttr resAttrs = call.getResAttrsAttr()) llvmCall.setResAttrsAttr(resAttrs); + if (auto inlineAttr = call.getInlineAttrAttr()) { + llvmCall->removeAttr("inline_attr"); + if (inlineAttr.getValue() == fir::FortranInlineEnum::no_inline) { + llvmCall.setNoInlineAttr(rewriter.getUnitAttr()); + } else if (inlineAttr.getValue() == fir::FortranInlineEnum::inline_hint) { + llvmCall.setInlineHintAttr(rewriter.getUnitAttr()); + } else if (inlineAttr.getValue() == + fir::FortranInlineEnum::always_inline) { + llvmCall.setAlwaysInlineAttr(rewriter.getUnitAttr()); + } + } + if (memAttr) llvmCall.setMemoryEffectsAttr( mlir::cast(memAttr)); diff --git a/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp b/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp index 0c78a878cdc53..5965135a43973 100644 --- a/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp +++ b/flang/lib/Optimizer/Transforms/PolymorphicOpConversion.cpp @@ -207,7 +207,8 @@ struct DispatchOpConv : public OpConversionPattern { args.append(dispatch.getArgs().begin(), dispatch.getArgs().end()); rewriter.replaceOpWithNewOp( dispatch, resTypes, nullptr, args, dispatch.getArgAttrsAttr(), - dispatch.getResAttrsAttr(), dispatch.getProcedureAttrsAttr()); + dispatch.getResAttrsAttr(), dispatch.getProcedureAttrsAttr(), + /*inline_attr*/ fir::FortranInlineEnumAttr{}); return mlir::success(); } diff --git a/flang/lib/Parser/Fortran-parsers.cpp b/flang/lib/Parser/Fortran-parsers.cpp index fbe629ab52935..970882051bdb4 100644 --- a/flang/lib/Parser/Fortran-parsers.cpp +++ b/flang/lib/Parser/Fortran-parsers.cpp @@ -1314,6 +1314,11 @@ constexpr auto novector{"NOVECTOR" >> construct()}; constexpr auto nounroll{"NOUNROLL" >> construct()}; constexpr auto nounrollAndJam{ "NOUNROLL_AND_JAM" >> construct()}; +constexpr auto forceinlineDir{ + "FORCEINLINE" >> construct()}; +constexpr auto noinlineDir{ + "NOINLINE" >> construct()}; +constexpr auto inlineDir{"INLINE" >> construct()}; TYPE_PARSER(beginDirective >> "DIR$ "_tok >> sourced((construct(ignore_tkr) || construct(loopCount) || @@ -1324,6 +1329,9 @@ TYPE_PARSER(beginDirective >> "DIR$ "_tok >> construct(novector) || construct(nounrollAndJam) || construct(nounroll) || + construct(noinlineDir) || + construct(forceinlineDir) || + construct(inlineDir) || construct( many(construct( name, maybe(("="_tok || ":"_tok) >> digitString64))))) / diff --git a/flang/lib/Parser/unparse.cpp b/flang/lib/Parser/unparse.cpp index 47dae0ae753d2..2a1a71e7de80b 100644 --- a/flang/lib/Parser/unparse.cpp +++ b/flang/lib/Parser/unparse.cpp @@ -1864,6 +1864,13 @@ class UnparseVisitor { [&](const CompilerDirective::NoUnrollAndJam &) { Word("!DIR$ NOUNROLL_AND_JAM"); }, + [&](const CompilerDirective::ForceInline &) { + Word("!DIR$ FORCEINLINE"); + }, + [&](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..a651a873d3b40 100644 --- a/flang/lib/Semantics/canonicalize-directives.cpp +++ b/flang/lib/Semantics/canonicalize-directives.cpp @@ -60,7 +60,11 @@ static bool IsExecutionDirective(const parser::CompilerDirective &dir) { std::holds_alternative(dir.u) || std::holds_alternative(dir.u) || std::holds_alternative(dir.u) || - std::holds_alternative(dir.u); + std::holds_alternative( + dir.u) || + std::holds_alternative(dir.u) || + std::holds_alternative(dir.u) || + std::holds_alternative(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 74367b5229548..466b38d136ab7 100644 --- a/flang/lib/Semantics/resolve-names.cpp +++ b/flang/lib/Semantics/resolve-names.cpp @@ -9595,7 +9595,10 @@ void ResolveNamesVisitor::Post(const parser::CompilerDirective &x) { std::holds_alternative(x.u) || std::holds_alternative(x.u) || std::holds_alternative(x.u) || - std::holds_alternative(x.u)) { + std::holds_alternative(x.u) || + std::holds_alternative(x.u) || + std::holds_alternative(x.u) || + std::holds_alternative(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..1f053846778cd --- /dev/null +++ b/flang/test/Integration/inline_directive.f90 @@ -0,0 +1,69 @@ +! This directory can be used to add Integration tests involving multiple stages of the compiler (for eg. from Fortran to LLVM IR). +! It should not contain executable tests. We should only add tests here sparingly and only if there is no other way to test. +! 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$ forceinline + y = g(x) + !dir$ forceinline + 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$ inline + y = g(x) + !dir$ inline + call f(x, y) +!CHECK: %[[VAL_10:.*]] = call i32 @_QFtest_inlinePg(ptr %[[VAL_3]]) #[[INLINE:.*]] +!CHECK: store i32 %[[VAL_10]], ptr %[[VAL_2]], align 4 +!CHECK: call void @_QFtest_inlinePf(ptr %[[VAL_3]], ptr %[[VAL_2]]) #[[INLINE]] + + !dir$ inline + 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]]) #[[INLINE]] + enddo + + !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 #[[INLINE]] = { inlinehint } +!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..49a426995328a --- /dev/null +++ b/flang/test/Lower/inline_directive.f90 @@ -0,0 +1,61 @@ +! RUN: %flang_fc1 -emit-fir -o - %s | FileCheck %s + +subroutine test_inline() + integer :: x, y +!CHECK: %[[VAL_0:.*]] = fir.alloca i32 {bindc_name = "x", uniq_name = "_QFtest_inlineEx"} +!CHECK: %[[VAL_1:.*]] = fir.declare %[[VAL_0]] {uniq_name = "_QFtest_inlineEx"} : (!fir.ref) -> !fir.ref +!CHECK: %[[VAL_2:.*]] = fir.alloca i32 {bindc_name = "y", uniq_name = "_QFtest_inlineEy"} +!CHECK: %[[VAL_3:.*]] = fir.declare %[[VAL_2]] {uniq_name = "_QFtest_inlineEy"} : (!fir.ref) -> !fir.ref + + !dir$ forceinline + y = g(x) + !CHECK: %[[VAL_4:.*]] = fir.call @_QFtest_inlinePg(%[[VAL_1]]) fastmath {inline_attr = #fir.inline_attrs} : (!fir.ref) -> i32 + !CHECK: fir.store %[[VAL_4]] to %[[VAL_3]] : !fir.ref + + !dir$ forceinline + call f(x, y) + !CHECK: fir.call @_QFtest_inlinePf(%[[VAL_1]], %[[VAL_3]]) fastmath {inline_attr = #fir.inline_attrs} : (!fir.ref, !fir.ref) -> () + + !dir$ noinline + y = g(x) + 7 * (8 + g(y)) + !CHECK: %[[VAL_8:.*]] = fir.call @_QFtest_inlinePg(%[[VAL_1]]) fastmath {inline_attr = #fir.inline_attrs} : (!fir.ref) -> i32 + !CHECK: %[[VAL_9:.*]] = fir.call @_QFtest_inlinePg(%[[VAL_3]]) fastmath {inline_attr = #fir.inline_attrs} : (!fir.ref) -> i32 + !CHECK: %[[VAL_10:.*]] = arith.addi %[[VAL_9]], %[[C8:.*]] : i32 + !CHECK: %[[VAL_11:.*]] = fir.no_reassoc %[[VAL_10]] : i32 + !CHECK: %[[VAL_12:.*]] = arith.muli %[[VAL_11]], %[[C7:.*]] : i32 + !CHECK: %[[VAL_13:.*]] = arith.addi %[[VAL_8]], %[[VAL_12]] : i32 + !CHECK: fir.store %[[VAL_13]] to %[[VAL_3]] : !fir.ref + + !dir$ noinline + call f(x, y) + !CHECK: fir.call @_QFtest_inlinePf(%[[VAL_1]], %[[VAL_3]]) fastmath {inline_attr = #fir.inline_attrs} : (!fir.ref, !fir.ref) -> () + + !dir$ inline + call f(x, y) + !CHECK: fir.call @_QFtest_inlinePf(%[[VAL_1]], %[[VAL_3]]) fastmath {inline_attr = #fir.inline_attrs} : (!fir.ref, !fir.ref) -> () + + !dir$ forceinline + do i = 1, 100 + !CHECK: fir.do_loop %[[ARG_0:.*]] = {{.*}} -> (index, i32) { + !CHECK: fir.call @_QFtest_inlinePf(%[[VAL_1]], %[[VAL_3]]) fastmath {inline_attr = #fir.inline_attrs} : (!fir.ref, !fir.ref) -> () + call f(x, y) + enddo + + !dir$ inline + do i = 1, 100 + !CHECK: fir.do_loop %[[ARG_0:.*]] = {{.*}} -> (index, i32) { + !CHECK: fir.call @_QFtest_inlinePf(%[[VAL_1]], %[[VAL_3]]) fastmath {inline_attr = #fir.inline_attrs} : (!fir.ref, !fir.ref) -> () + call f(x, y) + enddo +!CHECK: return + 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 diff --git a/flang/test/Parser/compiler-directives.f90 b/flang/test/Parser/compiler-directives.f90 index 04d22ff0fd8ee..b2fe466377f70 100644 --- a/flang/test/Parser/compiler-directives.f90 +++ b/flang/test/Parser/compiler-directives.f90 @@ -72,3 +72,27 @@ subroutine no_vector do i=1,10 enddo end subroutine + +subroutine inline + integer :: a + !dir$ forceinline + ! CHECK: !DIR$ FORCEINLINE + a = f(2) + + !dir$ inline + ! CHECK: !DIR$ INLINE + call g() + + !dir$ noinline + ! CHECK: !DIR$ NOINLINE + call g() + + contains + function f(x) + integer :: x + f = x**2 + end function + + subroutine g() + end subroutine +end subroutine