llvm-project/flang/lib/Optimizer/HLFIR/Transforms/ScheduleOrderedAssignments.h
Jean Perier 4f30a63ca2 [flang][hlfir] Implement the scheduling part of hlfir.forall codegen
The lowering of hlfir.forall to loops (and later hlfir.where) requires
doing a data dependency analysis to avoid creating temporary storage for
every control/mask/rhs/lhs expressions.

The added code implements a data dependency analysis for the hlfir
ordered assignment trees (it is not specific to Forall since these nodes
includes Where, user defined assignments, and assignment to vector
subscripted entities, but the added code is only plugged and tested
with hlfir.forall in this patch).

This data dependency analysis returns a "schedule", which is a list of
runs containing actions. Each runs will result in a single loop nest
evaluating all its action "at the same time" inside the loop body.
Actions may either evaluating an assignment, or saving some expression
evaluation (the value yielded inside the ordered assignment hlfir operations)
in a temporary storage before doing the assignment that requires this
expression value but may "conflict" with it.

A "conflict" is a read in an expression E to a variable that is, or may
be (analysis is conservative), written by an assignment that depends on
E.

The analysis is based on MLIR SideEffectInterface and fir AliasAnalysis
which makes it generic.

For now, the codegen that will apply the schedule and rewrite the
hlfir.forall into a set of loops is not implemented, but the scheduling
is tested on its own (from Fortran, because it allows testing many cases
in very readable fashions).

The current scheduling has limitations, for instance
"forall(i=1, 10) x(i)=2*x(i)" does not require saving the RHS values for
all "i" before doing the assignments since the RHS does not depend
on values computed during previous iterations. Any user call will also
trigger a conservative assumption that there is a conflict. Finally,
a lot of operations are missing memory effect interfaces (especially
in HLFIR). This patch adds a few so that it can be tested, but more
will be added in later patches.

Differential Revision: https://reviews.llvm.org/D150455
2023-05-17 14:23:33 +02:00

97 lines
4.1 KiB
C++

//===- ScheduleOrderedAssignments.h --- Assignment scheduling ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// This file defines a utility to analyze and schedule the evaluation of
// of hlfir::OrderedAssignmentTreeOpInterface trees that represent Fortran
// Forall, Where, user defined assignments and assignments to vector
// subscripted entities.
//===----------------------------------------------------------------------===//
#ifndef OPTIMIZER_HLFIR_TRANSFORM_SCHEDULEORDEREDASSIGNMENTS_H
#define OPTIMIZER_HLFIR_TRANSFORM_SCHEDULEORDEREDASSIGNMENTS_H
#include "flang/Optimizer/HLFIR/HLFIROps.h"
namespace hlfir {
/// Structure to represent that the value yielded by some region
/// must be fully evaluated and saved for all index values at
/// a given point of the ordered assignment tree evaluation.
/// All subsequent evaluation depending on the value yielded
/// by this region will use the value that was saved.
struct SaveEntity {
mlir::Region *yieldRegion;
/// Returns the hlfir.yield op argument.
mlir::Value getSavedValue();
};
/// A run is a list of actions required to evaluate an ordered assignment tree
/// that can be done in the same loop nest.
/// The actions can evaluate and saves element values into temporary or evaluate
/// assignments.
/// The evaluation of an action in a run will cause the evaluation of all the
/// regions that yield entities required to implement the action, except if the
/// region was saved in a previous run, in which case it will use the previously
/// saved value.
struct Run {
/// An action is either saving the values yielded by a region, or evaluating
/// the assignment part of an hlfir::RegionAssignOp.
using Action = std::variant<hlfir::RegionAssignOp, SaveEntity>;
llvm::SmallVector<Action> actions;
llvm::SmallVector<mlir::MemoryEffects::EffectInstance> memoryEffects;
};
/// List of runs to be executed in order to evaluate an order assignment tree.
using Schedule = llvm::SmallVector<Run>;
/// Example of schedules and run, and what they mean:
/// Fortran: forall (i=i:10) x(i) = y(i)
///
/// hlfir.forall lb { hlfir.yield %c1} ub { hlfir.yield %c10} do {
/// ^bb1(%i: index)
/// hlfir.region_assign {
/// %yi_addr = hlfir.designate %y(%i)
/// %yi = fir.load %yi_addr
/// hlfir.yield %yi
/// } to {
/// %xi = hlfir.designate %x(%i)
/// hlfir.yield %xi
/// }
/// }
///
/// If the scheduling analysis cannot prove that %x and %y do not overlap, it
/// will generate 2 runs for the schdule. The first containing
/// SaveEntity{rhs_region}, and the second one containing the
/// hlfir.region_assign.
///
/// The lowering of that schedule will have to:
/// For the first run:
/// 1. create a temporary to contain all the %yi for all %i
/// 2. create a loop nest for the forall, evaluate the %yi and save them
/// inside the loop, but do not evaluate the LHS or assignment.
/// For the second run:
/// 3. create a loop nest again for the forall, evaluate the LHS, get the
/// saved %yi, and evaluate %yi to %xi. After all runs:
/// 4. clean the temporary for the %yi.
///
/// If the scheduling analysis can prove %x and %y do not overlap, it will
/// generate only one run with the hlfir.region_assign, which will be
/// implemented as a single loop that evaluate %xi, %yi and does %xi = %yi in
/// the loop body.
/// Core function that analyzes an ordered assignment tree and builds a
/// schedule for its evaluation.
/// The main goal of the scheduler is to avoid creating temporary storage
/// (required for SaveEntity). But it can optionally be asked to fuse Forall
/// and Where assignments in the same loop nests when possible since it has the
/// memory effects analysis at hand.
Schedule buildEvaluationSchedule(hlfir::OrderedAssignmentTreeOpInterface root,
bool tryFusingAssignments);
} // namespace hlfir
#endif // OPTIMIZER_HLFIR_TRANSFORM_SCHEDULEORDERASSIGNMENTS_H