Skip to content

Commit

Permalink
[flang] Lower boxed procedure
Browse files Browse the repository at this point in the history
In FIR, we want to wrap function pointers in a special box known as a
boxproc value. Fortran has a limited form of dynamic scoping
[https://tinyurl.com/2p8v2hw7] between "host procedures" and "internal
procedures". There are a number of implementations possible.

Boxproc typed values abstract away the implementation details of when a
function pointer can be passed directly (as a raw address) and when a
function pointer has to account for the presence of a dynamic scope.
When lowering Fortran syntax to FIR, all function pointers are emboxed
as boxproc values.

When creating LLVM IR, we must strip away the abstraction and produce
low-level LLVM "assembly" code. This patch implements that
transformation as converting the boxproc values to either raw function
pointers or executable trampolines on the stack as needed. The
trampoline then captures the dynamic scope context within an executable
thunk that can be passed instead of the function's raw address.

Some extra handling is required for Fortran functions that return a
character value to deal with LEN values here.

Some of the code in Bridge.cpp and ConvertExpr.cpp and be re-arranged to
faciliate the upstreaming effort.

This patch is part of the upstreaming effort from fir-dev branch.

Reviewed By: jeanPerier, PeteSteinfeld

Differential Revision: https://reviews.llvm.org/D122223

Co-authored-by: mleair <leairmark@gmail.com>
Co-authored-by: Jean Perier <jperier@nvidia.com>
Co-authored-by: Eric Schweitz <eschweitz@nvidia.com>
Co-authored-by: V Donaldson <vdonaldson@nvidia.com>
Co-authored-by: Kiran Chandramohan <kiran.chandramohan@arm.com>
  • Loading branch information
6 people committed Mar 22, 2022
1 parent 01a2ba5 commit fe252f8
Show file tree
Hide file tree
Showing 41 changed files with 5,297 additions and 3,351 deletions.
12 changes: 5 additions & 7 deletions flang/include/flang/Lower/Bridge.h
Expand Up @@ -5,13 +5,9 @@
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Implements lowering. Convert Fortran source to
/// [MLIR](https://github.com/tensorflow/mlir).
///
/// [Coding style](https://llvm.org/docs/CodingStandards.html)
///
//
// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_LOWER_BRIDGE_H
Expand Down Expand Up @@ -84,6 +80,8 @@ class LoweringBridge {
/// Create a folding context. Careful: this is very expensive.
Fortran::evaluate::FoldingContext createFoldingContext() const;

bool validModule() { return getModule(); }

//===--------------------------------------------------------------------===//
// Perform the creation of an mlir::ModuleOp
//===--------------------------------------------------------------------===//
Expand Down
48 changes: 26 additions & 22 deletions flang/include/flang/Lower/CallInterface.h
Expand Up @@ -12,10 +12,10 @@
//
// Utility that defines fir call interface for procedure both on caller and
// and callee side and get the related FuncOp.
// It does not emit any FIR code but for the created mlir::FuncOp, instead it
// provides back a container of Symbol (callee side)/ActualArgument (caller
// It does not emit any FIR code but for the created mlir::func::FuncOp, instead
// it provides back a container of Symbol (callee side)/ActualArgument (caller
// side) with additional information for each element describing how it must be
// plugged with the mlir::FuncOp.
// plugged with the mlir::func::FuncOp.
// It handles the fact that hidden arguments may be inserted for the result.
// while lowering.
//
Expand Down Expand Up @@ -76,8 +76,8 @@ template <typename T>
class CallInterfaceImpl;

/// CallInterface defines all the logic to determine FIR function interfaces
/// from a characteristic, build the mlir::FuncOp and describe back the argument
/// mapping to its user.
/// from a characteristic, build the mlir::func::FuncOp and describe back the
/// argument mapping to its user.
/// The logic is shared between the callee and caller sides that it accepts as
/// a curiously recursive template to handle the few things that cannot be
/// shared between both sides (getting characteristics, mangled name, location).
Expand Down Expand Up @@ -131,7 +131,7 @@ class CallInterface {
using FirValue = typename PassedEntityTypes<T>::FirValue;

/// FirPlaceHolder are place holders for the mlir inputs and outputs that are
/// created during the first pass before the mlir::FuncOp is created.
/// created during the first pass before the mlir::func::FuncOp is created.
struct FirPlaceHolder {
FirPlaceHolder(mlir::Type t, int passedPosition, Property p,
llvm::ArrayRef<mlir::NamedAttribute> attrs)
Expand Down Expand Up @@ -162,8 +162,8 @@ class CallInterface {
/// How entity is passed by.
PassEntityBy passBy;
/// What is the entity (SymbolRef for callee/ActualArgument* for caller)
/// What is the related mlir::FuncOp argument(s) (mlir::Value for callee /
/// index for the caller).
/// What is the related mlir::func::FuncOp argument(s) (mlir::Value for
/// callee / index for the caller).
FortranEntity entity;
FirValue firArgument;
FirValue firLength; /* only for AddressAndLength */
Expand All @@ -173,17 +173,17 @@ class CallInterface {
nullptr;
};

/// Return the mlir::FuncOp. Note that front block is added by this
/// Return the mlir::func::FuncOp. Note that front block is added by this
/// utility if callee side.
mlir::FuncOp getFuncOp() const { return func; }
mlir::func::FuncOp getFuncOp() const { return func; }
/// Number of MLIR inputs/outputs of the created FuncOp.
std::size_t getNumFIRArguments() const { return inputs.size(); }
std::size_t getNumFIRResults() const { return outputs.size(); }
/// Return the MLIR output types.
llvm::SmallVector<mlir::Type> getResultType() const;

/// Return a container of Symbol/ActualArgument* and how they must
/// be plugged with the mlir::FuncOp.
/// be plugged with the mlir::func::FuncOp.
llvm::ArrayRef<PassedEntity> getPassedArguments() const {
return passedArguments;
}
Expand All @@ -194,7 +194,7 @@ class CallInterface {
mlir::FunctionType genFunctionType();

/// determineInterface is the entry point of the first pass that defines the
/// interface and is required to get the mlir::FuncOp.
/// interface and is required to get the mlir::func::FuncOp.
void
determineInterface(bool isImplicit,
const Fortran::evaluate::characteristics::Procedure &);
Expand All @@ -219,16 +219,16 @@ class CallInterface {
/// CRTP handle.
T &side() { return *static_cast<T *>(this); }
/// Entry point to be called by child ctor to analyze the signature and
/// create/find the mlir::FuncOp. Child needs to be initialized first.
/// create/find the mlir::func::FuncOp. Child needs to be initialized first.
void declare();
/// Second pass entry point, once the mlir::FuncOp is created.
/// Second pass entry point, once the mlir::func::FuncOp is created.
/// Nothing is done if it was already called.
void mapPassedEntities();
void mapBackInputToPassedEntity(const FirPlaceHolder &, FirValue);

llvm::SmallVector<FirPlaceHolder> outputs;
llvm::SmallVector<FirPlaceHolder> inputs;
mlir::FuncOp func;
mlir::func::FuncOp func;
llvm::SmallVector<PassedEntity> passedArguments;
std::optional<PassedEntity> passedResult;
bool saveResult = false;
Expand Down Expand Up @@ -270,6 +270,10 @@ class CallerInterface : public CallInterface<CallerInterface> {
return procRef;
}

/// Get the SubprogramDetails that defines the interface of this call if it is
/// known at the call site. Return nullptr if it is not known.
const Fortran::semantics::SubprogramDetails *getInterfaceDetails() const;

bool isMainProgram() const { return false; }

/// Returns true if this is a call to a procedure pointer of a dummy
Expand Down Expand Up @@ -368,9 +372,9 @@ class CalleeInterface : public CallInterface<CalleeInterface> {
/// procedure.
const Fortran::semantics::Symbol *getProcedureSymbol() const;

/// Add mlir::FuncOp entry block and map fir block arguments to Fortran dummy
/// argument symbols.
mlir::FuncOp addEntryBlockAndMapArguments();
/// Add mlir::func::FuncOp entry block and map fir block arguments to Fortran
/// dummy argument symbols.
mlir::func::FuncOp addEntryBlockAndMapArguments();

bool hasHostAssociated() const;
mlir::Type getHostAssociatedTy() const;
Expand All @@ -385,13 +389,13 @@ mlir::FunctionType
translateSignature(const Fortran::evaluate::ProcedureDesignator &,
Fortran::lower::AbstractConverter &);

/// Declare or find the mlir::FuncOp named \p name. If the mlir::FuncOp does
/// not exist yet, declare it with the signature translated from the
/// ProcedureDesignator argument.
/// Declare or find the mlir::func::FuncOp named \p name. If the
/// mlir::func::FuncOp does not exist yet, declare it with the signature
/// translated from the ProcedureDesignator argument.
/// Due to Fortran implicit function typing rules, the returned FuncOp is not
/// guaranteed to have the signature from ProcedureDesignator if the FuncOp was
/// already declared.
mlir::FuncOp
mlir::func::FuncOp
getOrDeclareFunction(llvm::StringRef name,
const Fortran::evaluate::ProcedureDesignator &,
Fortran::lower::AbstractConverter &);
Expand Down
76 changes: 42 additions & 34 deletions flang/include/flang/Lower/ConvertExpr.h
Expand Up @@ -23,24 +23,22 @@

namespace mlir {
class Location;
}
class Value;
} // namespace mlir

namespace Fortran::evaluate {
template <typename>
class Expr;
struct SomeType;
} // namespace Fortran::evaluate
namespace fir {
class AllocMemOp;
class ArrayLoadOp;
class ShapeOp;
} // namespace fir

namespace Fortran::lower {

class AbstractConverter;
class StatementContext;
class SymMap;
class ExplicitIterSpace;
class ImplicitIterSpace;
class StatementContext;

using SomeExpr = Fortran::evaluate::Expr<Fortran::evaluate::SomeType>;
class SymMap;

/// Create an extended expression value.
fir::ExtendedValue createSomeExtendedExpression(mlir::Location loc,
Expand Down Expand Up @@ -87,30 +85,6 @@ fir::MutableBoxValue createMutableBox(mlir::Location loc,
AbstractConverter &converter,
const SomeExpr &expr, SymMap &symMap);

/// Lower an array expression to a value of type box. The expression must be a
/// variable.
fir::ExtendedValue createSomeArrayBox(AbstractConverter &converter,
const SomeExpr &expr, SymMap &symMap,
StatementContext &stmtCtx);

/// Lower a subroutine call. This handles both elemental and non elemental
/// subroutines. \p isUserDefAssignment must be set if this is called in the
/// context of a user defined assignment. For subroutines with alternate
/// returns, the returned value indicates which label the code should jump to.
/// The returned value is null otherwise.
mlir::Value createSubroutineCall(AbstractConverter &converter,
const evaluate::ProcedureRef &call,
ExplicitIterSpace &explicitIterSpace,
ImplicitIterSpace &implicitIterSpace,
SymMap &symMap, StatementContext &stmtCtx,
bool isUserDefAssignment);

/// Create the address of the box.
/// \p expr must be the designator of an allocatable/pointer entity.
fir::MutableBoxValue createMutableBox(mlir::Location loc,
AbstractConverter &converter,
const SomeExpr &expr, SymMap &symMap);

/// Create a fir::BoxValue describing the value of \p expr.
/// If \p expr is a variable without vector subscripts, the fir::BoxValue
/// described the variable storage. Otherwise, the created fir::BoxValue
Expand Down Expand Up @@ -190,6 +164,22 @@ void createAnyMaskedArrayAssignment(AbstractConverter &converter,
ImplicitIterSpace &implicitIterSpace,
SymMap &symMap, StatementContext &stmtCtx);

/// In the context of a FORALL, a pointer assignment is allowed. The pointer
/// assignment can be elementwise on an array of pointers. The bounds
/// expressions as well as the component path may contain references to the
/// concurrent control variables. The explicit iteration space must be defined.
void createAnyArrayPointerAssignment(
AbstractConverter &converter, const SomeExpr &lhs, const SomeExpr &rhs,
const evaluate::Assignment::BoundsSpec &bounds,
ExplicitIterSpace &explicitIterSpace, ImplicitIterSpace &implicitIterSpace,
SymMap &symMap);
/// Support the bounds remapping flavor of pointer assignment.
void createAnyArrayPointerAssignment(
AbstractConverter &converter, const SomeExpr &lhs, const SomeExpr &rhs,
const evaluate::Assignment::BoundsRemapping &bounds,
ExplicitIterSpace &explicitIterSpace, ImplicitIterSpace &implicitIterSpace,
SymMap &symMap);

/// Lower an assignment to an allocatable array, allocating the array if
/// it is not allocated yet or reallocation it if it does not conform
/// with the right hand side.
Expand Down Expand Up @@ -220,6 +210,24 @@ void createLazyArrayTempValue(AbstractConverter &converter,
const SomeExpr &expr, mlir::Value raggedHeader,
SymMap &symMap, StatementContext &stmtCtx);

/// Lower an array expression to a value of type box. The expression must be a
/// variable.
fir::ExtendedValue createSomeArrayBox(AbstractConverter &converter,
const SomeExpr &expr, SymMap &symMap,
StatementContext &stmtCtx);

/// Lower a subroutine call. This handles both elemental and non elemental
/// subroutines. \p isUserDefAssignment must be set if this is called in the
/// context of a user defined assignment. For subroutines with alternate
/// returns, the returned value indicates which label the code should jump to.
/// The returned value is null otherwise.
mlir::Value createSubroutineCall(AbstractConverter &converter,
const evaluate::ProcedureRef &call,
ExplicitIterSpace &explicitIterSpace,
ImplicitIterSpace &implicitIterSpace,
SymMap &symMap, StatementContext &stmtCtx,
bool isUserDefAssignment);

// Attribute for an alloca that is a trivial adaptor for converting a value to
// pass-by-ref semantics for a VALUE parameter. The optimizer may be able to
// eliminate these.
Expand Down
4 changes: 4 additions & 0 deletions flang/include/flang/Lower/IntrinsicCall.h
Expand Up @@ -100,6 +100,10 @@ getUnrestrictedIntrinsicSymbolRefAttr(fir::FirOpBuilder &, mlir::Location,
mlir::Value genMax(fir::FirOpBuilder &, mlir::Location,
llvm::ArrayRef<mlir::Value> args);

/// Generate minimum. Same constraints as genMax.
mlir::Value genMin(fir::FirOpBuilder &, mlir::Location,
llvm::ArrayRef<mlir::Value> args);

/// Generate power function x**y with the given expected
/// result type.
mlir::Value genPow(fir::FirOpBuilder &, mlir::Location, mlir::Type resultType,
Expand Down
30 changes: 12 additions & 18 deletions flang/include/flang/Optimizer/Builder/Character.h
Expand Up @@ -14,15 +14,19 @@
#define FORTRAN_OPTIMIZER_BUILDER_CHARACTER_H

#include "flang/Optimizer/Builder/BoxValue.h"
#include "flang/Optimizer/Builder/FIRBuilder.h"
#include "flang/Optimizer/Builder/LowLevelIntrinsics.h"

namespace fir {
class FirOpBuilder;
}

namespace fir::factory {

/// Helper to facilitate lowering of CHARACTER in FIR.
class CharacterExprHelper {
public:
/// Constructor.
explicit CharacterExprHelper(fir::FirOpBuilder &builder, mlir::Location loc)
explicit CharacterExprHelper(FirOpBuilder &builder, mlir::Location loc)
: builder{builder}, loc{loc} {}
CharacterExprHelper(const CharacterExprHelper &) = delete;

Expand Down Expand Up @@ -107,11 +111,15 @@ class CharacterExprHelper {
/// Extract the kind of a character or array of character type.
static fir::KindTy getCharacterOrSequenceKind(mlir::Type type);

// TODO: Do we really need all these flavors of unwrapping to get the fir.char
// type? Or can we merge these? It would be better to merge them and eliminate
// the confusion.

/// Determine the inner character type. Unwraps references, boxes, and
/// sequences to find the !fir.char element type.
static fir::CharacterType getCharType(mlir::Type type);

/// Determine the base character type
/// Get fir.char<kind> type with the same kind as inside str.
static fir::CharacterType getCharacterType(mlir::Type type);
static fir::CharacterType getCharacterType(const fir::CharBoxValue &box);
static fir::CharacterType getCharacterType(mlir::Value str);
Expand Down Expand Up @@ -181,16 +189,11 @@ class CharacterExprHelper {
void createAssign(const fir::CharBoxValue &lhs, const fir::CharBoxValue &rhs);
mlir::Value createBlankConstantCode(fir::CharacterType type);

private:
FirOpBuilder &builder;
mlir::Location loc;
};

// FIXME: Move these to Optimizer
mlir::FuncOp getLlvmMemcpy(FirOpBuilder &builder);
mlir::FuncOp getLlvmMemmove(FirOpBuilder &builder);
mlir::FuncOp getLlvmMemset(FirOpBuilder &builder);
mlir::FuncOp getRealloc(FirOpBuilder &builder);

//===----------------------------------------------------------------------===//
// Tools to work with Character dummy procedures
//===----------------------------------------------------------------------===//
Expand All @@ -200,15 +203,6 @@ mlir::FuncOp getRealloc(FirOpBuilder &builder);
/// one provided by \p funcPointerType.
mlir::Type getCharacterProcedureTupleType(mlir::Type funcPointerType);

/// Is this tuple type holding a character function and its result length ?
bool isCharacterProcedureTuple(mlir::Type type);

/// Is \p tuple a value holding a character function address and its result
/// length ?
inline bool isCharacterProcedureTuple(mlir::Value tuple) {
return isCharacterProcedureTuple(tuple.getType());
}

/// Create a tuple<addr, len> given \p addr and \p len as well as the tuple
/// type \p argTy. \p addr must be any function address, and \p len must be
/// any integer. Converts will be inserted if needed if \addr and \p len
Expand Down

0 comments on commit fe252f8

Please sign in to comment.