Skip to content

Commit c6b6e18

Browse files
authored
[flang] Implement !DIR$ VECTOR ALWAYS (#93830)
This patch implements support for the VECTOR ALWAYS directive, which forces vectorization to occurr when possible regardless of a decision by the cost model. This is done by adding an attribute to the branch into the loop in LLVM to indicate that the loop should always be vectorized. This patch only implements this directive on plan structured do loops without labels. Support for unstructured loops and array expressions is planned for future patches.
1 parent 8ab3f8a commit c6b6e18

21 files changed

+396
-9
lines changed

flang/docs/Directives.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,62 @@ A list of non-standard directives supported by Flang
3636
and is limited to 256.
3737
[This directive is currently recognised by the parser, but not
3838
handled by the other parts of the compiler].
39+
* `!dir$ vector always` forces vectorization on the following loop regardless
40+
of cost model decisions. The loop must still be vectorizable.
41+
[This directive currently only works on plain do loops without labels].
42+
43+
# Directive Details
44+
45+
## Introduction
46+
Directives are commonly used in Fortran programs to specify additional actions
47+
to be performed by the compiler. The directives are always specified with the
48+
`!dir$` or `cdir$` prefix.
49+
50+
## Loop Directives
51+
Some directives are associated with the following construct, for example loop
52+
directives. Directives on loops are used to specify additional transformation to
53+
be performed by the compiler like enabling vectorisation, unrolling, interchange
54+
etc.
55+
56+
Currently loop directives are not accepted in the presence of OpenMP or OpenACC
57+
constructs on the loop. This should be implemented as it is used in some
58+
applications.
59+
60+
### Array Expressions
61+
It is to be decided whether loop directives should also be able to be associated
62+
with array expressions.
63+
64+
## Semantics
65+
Directives that are associated with constructs must appear in the same section
66+
as the construct they are associated with, for example loop directives must
67+
appear in the executable section as the loops appear there. To facilitate this
68+
the parse tree is corrected to move such directives that appear in the
69+
specification part into the execution part.
70+
71+
When a directive that must be associated with a construct appears, a search
72+
forward from that directive to the next non-directive construct is performed to
73+
check that that construct matches the expected construct for the directive.
74+
Skipping other intermediate directives allows multiple directives to appear on
75+
the same construct.
76+
77+
## Lowering
78+
Evaluation is extended with a new field called dirs for representing directives
79+
associated with that Evaluation. When lowering loop directives, the associated
80+
Do Loop's evaluation is found and the directive is added to it. This information
81+
is used only during the lowering of the loop.
82+
83+
### Representation in LLVM
84+
The `llvm.loop` metadata is used in LLVM to provide information to the optimizer
85+
about the loop. For example, the `llvm.loop.vectorize.enable` metadata informs
86+
the optimizer that a loop can be vectorized without considering its cost-model.
87+
This attribute is added to the loop condition branch.
88+
89+
### Representation in MLIR
90+
The MLIR LLVM dialect models this by an attribute called LoopAnnotation
91+
Attribute. The attribute can be added to the latch of the loop in the cf
92+
dialect and is then carried through lowering to the LLVM dialect.
93+
94+
## Testing
95+
Since directives must maintain a flow from source to LLVM IR, an integration
96+
test is provided that tests the `vector always` directive, as well as individual
97+
lit tests for each of the parsing, semantics and lowering stages.

flang/include/flang/Lower/PFTBuilder.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,8 @@ struct Evaluation : EvaluationVariant {
350350
parser::CharBlock position{};
351351
std::optional<parser::Label> label{};
352352
std::unique_ptr<EvaluationList> evaluationList; // nested evaluations
353+
// associated compiler directives
354+
llvm::SmallVector<const parser::CompilerDirective *, 1> dirs;
353355
Evaluation *parentConstruct{nullptr}; // set for nodes below the top level
354356
Evaluation *lexicalSuccessor{nullptr}; // set for leaf nodes, some directives
355357
Evaluation *controlSuccessor{nullptr}; // set for some leaf nodes

flang/include/flang/Optimizer/Dialect/FIROps.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "flang/Optimizer/Dialect/FortranVariableInterface.h"
1717
#include "mlir/Dialect/Arith/IR/Arith.h"
1818
#include "mlir/Dialect/Func/IR/FuncOps.h"
19+
#include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
1920
#include "mlir/Interfaces/LoopLikeInterface.h"
2021
#include "mlir/Interfaces/SideEffectInterfaces.h"
2122

flang/include/flang/Optimizer/Dialect/FIROps.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2160,7 +2160,8 @@ def fir_DoLoopOp : region_Op<"do_loop", [AttrSizedOperandSegments,
21602160
Variadic<AnyType>:$initArgs,
21612161
OptionalAttr<UnitAttr>:$unordered,
21622162
OptionalAttr<UnitAttr>:$finalValue,
2163-
OptionalAttr<ArrayAttr>:$reduceAttrs
2163+
OptionalAttr<ArrayAttr>:$reduceAttrs,
2164+
OptionalAttr<LoopAnnotationAttr>:$loopAnnotation
21642165
);
21652166
let results = (outs Variadic<AnyType>:$results);
21662167
let regions = (region SizedRegion<1>:$region);

flang/include/flang/Parser/dump-parse-tree.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,12 @@ class ParseTreeDumper {
201201
NODE(parser, CommonStmt)
202202
NODE(CommonStmt, Block)
203203
NODE(parser, CompilerDirective)
204+
NODE(CompilerDirective, AssumeAligned)
204205
NODE(CompilerDirective, IgnoreTKR)
205206
NODE(CompilerDirective, LoopCount)
206-
NODE(CompilerDirective, AssumeAligned)
207207
NODE(CompilerDirective, NameValue)
208208
NODE(CompilerDirective, Unrecognized)
209+
NODE(CompilerDirective, VectorAlways)
209210
NODE(parser, ComplexLiteralConstant)
210211
NODE(parser, ComplexPart)
211212
NODE(parser, ComponentArraySpec)

flang/include/flang/Parser/parse-tree.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3337,14 +3337,15 @@ struct CompilerDirective {
33373337
TUPLE_CLASS_BOILERPLATE(AssumeAligned);
33383338
std::tuple<common::Indirection<Designator>, uint64_t> t;
33393339
};
3340+
EMPTY_CLASS(VectorAlways);
33403341
struct NameValue {
33413342
TUPLE_CLASS_BOILERPLATE(NameValue);
33423343
std::tuple<Name, std::optional<std::uint64_t>> t;
33433344
};
33443345
EMPTY_CLASS(Unrecognized);
33453346
CharBlock source;
33463347
std::variant<std::list<IgnoreTKR>, LoopCount, std::list<AssumeAligned>,
3347-
std::list<NameValue>, Unrecognized>
3348+
VectorAlways, std::list<NameValue>, Unrecognized>
33483349
u;
33493350
};
33503351

flang/lib/Lower/Bridge.cpp

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1935,7 +1935,7 @@ class FirConverter : public Fortran::lower::AbstractConverter {
19351935

19361936
// Increment loop begin code. (Infinite/while code was already generated.)
19371937
if (!infiniteLoop && !whileCondition)
1938-
genFIRIncrementLoopBegin(incrementLoopNestInfo);
1938+
genFIRIncrementLoopBegin(incrementLoopNestInfo, doStmtEval.dirs);
19391939

19401940
// Loop body code.
19411941
auto iter = eval.getNestedEvaluations().begin();
@@ -1980,8 +1980,20 @@ class FirConverter : public Fortran::lower::AbstractConverter {
19801980
return builder->createIntegerConstant(loc, controlType, 1); // step
19811981
}
19821982

1983+
void addLoopAnnotationAttr(IncrementLoopInfo &info) {
1984+
mlir::BoolAttr f = mlir::BoolAttr::get(builder->getContext(), false);
1985+
mlir::LLVM::LoopVectorizeAttr va = mlir::LLVM::LoopVectorizeAttr::get(
1986+
builder->getContext(), /*disable=*/f, {}, {}, {}, {}, {}, {});
1987+
mlir::LLVM::LoopAnnotationAttr la = mlir::LLVM::LoopAnnotationAttr::get(
1988+
builder->getContext(), {}, /*vectorize=*/va, {}, {}, {}, {}, {}, {}, {},
1989+
{}, {}, {}, {}, {}, {});
1990+
info.doLoop.setLoopAnnotationAttr(la);
1991+
}
1992+
19831993
/// Generate FIR to begin a structured or unstructured increment loop nest.
1984-
void genFIRIncrementLoopBegin(IncrementLoopNestInfo &incrementLoopNestInfo) {
1994+
void genFIRIncrementLoopBegin(
1995+
IncrementLoopNestInfo &incrementLoopNestInfo,
1996+
llvm::SmallVectorImpl<const Fortran::parser::CompilerDirective *> &dirs) {
19851997
assert(!incrementLoopNestInfo.empty() && "empty loop nest");
19861998
mlir::Location loc = toLocation();
19871999
for (IncrementLoopInfo &info : incrementLoopNestInfo) {
@@ -2046,6 +2058,15 @@ class FirConverter : public Fortran::lower::AbstractConverter {
20462058
}
20472059
if (info.hasLocalitySpecs())
20482060
handleLocalitySpecs(info);
2061+
2062+
for (const auto *dir : dirs) {
2063+
std::visit(
2064+
Fortran::common::visitors{
2065+
[&](const Fortran::parser::CompilerDirective::VectorAlways
2066+
&d) { addLoopAnnotationAttr(info); },
2067+
[&](const auto &) {}},
2068+
dir->u);
2069+
}
20492070
continue;
20502071
}
20512072

@@ -2579,8 +2600,28 @@ class FirConverter : public Fortran::lower::AbstractConverter {
25792600
}
25802601
}
25812602

2582-
void genFIR(const Fortran::parser::CompilerDirective &) {
2583-
// TODO
2603+
void attachDirectiveToLoop(const Fortran::parser::CompilerDirective &dir,
2604+
Fortran::lower::pft::Evaluation *e) {
2605+
while (e->isDirective())
2606+
e = e->lexicalSuccessor;
2607+
2608+
if (e->isA<Fortran::parser::NonLabelDoStmt>())
2609+
e->dirs.push_back(&dir);
2610+
else
2611+
fir::emitFatalError(toLocation(),
2612+
"loop directive must appear before a loop");
2613+
}
2614+
2615+
void genFIR(const Fortran::parser::CompilerDirective &dir) {
2616+
Fortran::lower::pft::Evaluation &eval = getEval();
2617+
2618+
std::visit(
2619+
Fortran::common::visitors{
2620+
[&](const Fortran::parser::CompilerDirective::VectorAlways &) {
2621+
attachDirectiveToLoop(dir, &eval);
2622+
},
2623+
[&](const auto &) {}},
2624+
dir.u);
25842625
}
25852626

25862627
void genFIR(const Fortran::parser::OpenACCConstruct &acc) {

flang/lib/Optimizer/Transforms/ControlFlowConverter.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,14 @@ class CfgLoopConv : public mlir::OpRewritePattern<fir::DoLoopOp> {
132132
auto comparison = rewriter.create<mlir::arith::CmpIOp>(
133133
loc, arith::CmpIPredicate::sgt, itersLeft, zero);
134134

135-
rewriter.create<mlir::cf::CondBranchOp>(
135+
auto cond = rewriter.create<mlir::cf::CondBranchOp>(
136136
loc, comparison, firstBlock, llvm::ArrayRef<mlir::Value>(), endBlock,
137137
llvm::ArrayRef<mlir::Value>());
138138

139+
// Copy loop annotations from the do loop to the loop entry condition.
140+
if (auto ann = loop.getLoopAnnotation())
141+
cond->setAttr("loop_annotation", *ann);
142+
139143
// The result of the loop operation is the values of the condition block
140144
// arguments except the induction variable on the last iteration.
141145
auto args = loop.getFinalValue()

flang/lib/Parser/Fortran-parsers.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,10 +1277,13 @@ constexpr auto loopCount{
12771277
constexpr auto assumeAligned{"ASSUME_ALIGNED" >>
12781278
optionalList(construct<CompilerDirective::AssumeAligned>(
12791279
indirect(designator), ":"_tok >> digitString64))};
1280+
constexpr auto vectorAlways{
1281+
"VECTOR ALWAYS" >> construct<CompilerDirective::VectorAlways>()};
12801282
TYPE_PARSER(beginDirective >> "DIR$ "_tok >>
12811283
sourced((construct<CompilerDirective>(ignore_tkr) ||
12821284
construct<CompilerDirective>(loopCount) ||
12831285
construct<CompilerDirective>(assumeAligned) ||
1286+
construct<CompilerDirective>(vectorAlways) ||
12841287
construct<CompilerDirective>(
12851288
many(construct<CompilerDirective::NameValue>(
12861289
name, maybe(("="_tok || ":"_tok) >> digitString64))))) /

flang/lib/Parser/unparse.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1834,6 +1834,9 @@ class UnparseVisitor {
18341834
Word("!DIR$ ASSUME_ALIGNED ");
18351835
Walk(" ", assumeAligned, ", ");
18361836
},
1837+
[&](const CompilerDirective::VectorAlways &valways) {
1838+
Word("!DIR$ VECTOR ALWAYS");
1839+
},
18371840
[&](const std::list<CompilerDirective::NameValue> &names) {
18381841
Walk("!DIR$ ", names, " ");
18391842
},

flang/lib/Semantics/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_flang_library(FortranSemantics
22
assignment.cpp
33
attr.cpp
44
canonicalize-acc.cpp
5+
canonicalize-directives.cpp
56
canonicalize-do.cpp
67
canonicalize-omp.cpp
78
check-acc-structure.cpp
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//===-- lib/Semantics/canonicalize-directives.cpp -------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "canonicalize-directives.h"
10+
#include "flang/Parser/parse-tree-visitor.h"
11+
12+
namespace Fortran::semantics {
13+
14+
using namespace parser::literals;
15+
16+
// Check that directives are associated with the correct constructs.
17+
// Directives that need to be associated with other constructs in the execution
18+
// part are moved to the execution part so they can be checked there.
19+
class CanonicalizationOfDirectives {
20+
public:
21+
CanonicalizationOfDirectives(parser::Messages &messages)
22+
: messages_{messages} {}
23+
24+
template <typename T> bool Pre(T &) { return true; }
25+
template <typename T> void Post(T &) {}
26+
27+
// Move directives that must appear in the Execution part out of the
28+
// Specification part.
29+
void Post(parser::SpecificationPart &spec);
30+
bool Pre(parser::ExecutionPart &x);
31+
32+
// Ensure that directives associated with constructs appear accompanying the
33+
// construct.
34+
void Post(parser::Block &block);
35+
36+
private:
37+
// Ensure that loop directives appear immediately before a loop.
38+
void CheckLoopDirective(parser::CompilerDirective &dir, parser::Block &block,
39+
std::list<parser::ExecutionPartConstruct>::iterator it);
40+
41+
parser::Messages &messages_;
42+
43+
// Directives to be moved to the Execution part from the Specification part.
44+
std::list<common::Indirection<parser::CompilerDirective>>
45+
directivesToConvert_;
46+
};
47+
48+
bool CanonicalizeDirectives(
49+
parser::Messages &messages, parser::Program &program) {
50+
CanonicalizationOfDirectives dirs{messages};
51+
Walk(program, dirs);
52+
return !messages.AnyFatalError();
53+
}
54+
55+
static bool IsExecutionDirective(const parser::CompilerDirective &dir) {
56+
return std::holds_alternative<parser::CompilerDirective::VectorAlways>(dir.u);
57+
}
58+
59+
void CanonicalizationOfDirectives::Post(parser::SpecificationPart &spec) {
60+
auto &list{
61+
std::get<std::list<common::Indirection<parser::CompilerDirective>>>(
62+
spec.t)};
63+
for (auto it{list.begin()}; it != list.end();) {
64+
if (IsExecutionDirective(it->value())) {
65+
directivesToConvert_.emplace_back(std::move(*it));
66+
it = list.erase(it);
67+
} else {
68+
++it;
69+
}
70+
}
71+
}
72+
73+
bool CanonicalizationOfDirectives::Pre(parser::ExecutionPart &x) {
74+
auto origFirst{x.v.begin()};
75+
for (auto &dir : directivesToConvert_) {
76+
x.v.insert(origFirst,
77+
parser::ExecutionPartConstruct{
78+
parser::ExecutableConstruct{std::move(dir)}});
79+
}
80+
81+
directivesToConvert_.clear();
82+
return true;
83+
}
84+
85+
template <typename T> T *GetConstructIf(parser::ExecutionPartConstruct &x) {
86+
if (auto *y{std::get_if<parser::ExecutableConstruct>(&x.u)}) {
87+
if (auto *z{std::get_if<common::Indirection<T>>(&y->u)}) {
88+
return &z->value();
89+
}
90+
}
91+
return nullptr;
92+
}
93+
94+
void CanonicalizationOfDirectives::CheckLoopDirective(
95+
parser::CompilerDirective &dir, parser::Block &block,
96+
std::list<parser::ExecutionPartConstruct>::iterator it) {
97+
98+
// Skip over this and other compiler directives
99+
while (GetConstructIf<parser::CompilerDirective>(*it)) {
100+
++it;
101+
}
102+
103+
if (it == block.end() || !GetConstructIf<parser::DoConstruct>(*it)) {
104+
std::string s{parser::ToUpperCaseLetters(dir.source.ToString())};
105+
s.pop_back(); // Remove trailing newline from source string
106+
messages_.Say(
107+
dir.source, "A DO loop must follow the %s directive"_err_en_US, s);
108+
}
109+
}
110+
111+
void CanonicalizationOfDirectives::Post(parser::Block &block) {
112+
for (auto it{block.begin()}; it != block.end(); ++it) {
113+
if (auto *dir{GetConstructIf<parser::CompilerDirective>(*it)}) {
114+
std::visit(
115+
common::visitors{[&](parser::CompilerDirective::VectorAlways &) {
116+
CheckLoopDirective(*dir, block, it);
117+
},
118+
[&](auto &) {}},
119+
dir->u);
120+
}
121+
}
122+
}
123+
124+
} // namespace Fortran::semantics
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//===-- lib/Semantics/canonicalize-directives.h -----------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef FORTRAN_SEMANTICS_CANONICALIZE_DIRECTIVES_H_
10+
#define FORTRAN_SEMANTICS_CANONICALIZE_DIRECTIVES_H_
11+
12+
namespace Fortran::parser {
13+
struct Program;
14+
class Messages;
15+
} // namespace Fortran::parser
16+
17+
namespace Fortran::semantics {
18+
bool CanonicalizeDirectives(
19+
parser::Messages &messages, parser::Program &program);
20+
}
21+
22+
#endif // FORTRAN_SEMANTICS_CANONICALIZE_DIRECTIVES_H_

0 commit comments

Comments
 (0)