Skip to content

Commit

Permalink
[flang] Rework host runtime folding and enable REAL(2) folding with it.
Browse files Browse the repository at this point in the history
- Rework the host runtime table so that it is constexpr to avoid
  having to construct it and to store/propagate it.
- Make the interface simpler (remove many templates and a file)
- Enable 16bits float folding using 32bits float host runtime
- Move StaticMultimapView into its own header to use it for host
  folding

Reviewed By: klausler, PeteSteinfeld

Differential Revision: https://reviews.llvm.org/D88981
  • Loading branch information
jeanPerier committed Oct 14, 2020
1 parent 41d85fe commit 94d9a4f
Show file tree
Hide file tree
Showing 13 changed files with 626 additions and 865 deletions.
62 changes: 62 additions & 0 deletions flang/include/flang/Common/static-multimap-view.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//===-- include/flang/Common/static-multimap-view.h -------------*- 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
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_COMMON_STATIC_MULTIMAP_VIEW_H_
#define FORTRAN_COMMON_STATIC_MULTIMAP_VIEW_H_
#include <algorithm>
#include <utility>

/// StaticMultimapView is a constexpr friendly multimap implementation over
/// sorted constexpr arrays. As the View name suggests, it does not duplicate
/// the sorted array but only brings range and search concepts over it. It
/// mainly erases the array size from the type and ensures the array is sorted
/// at compile time. When C++20 brings std::span and constexpr std::is_sorted,
/// this can most likely be replaced by those.

namespace Fortran::common {

template <typename V> class StaticMultimapView {
public:
using Key = typename V::Key;
using const_iterator = const V *;

constexpr const_iterator begin() const { return begin_; }
constexpr const_iterator end() const { return end_; }
// Be sure to static_assert(map.Verify(), "must be sorted"); for
// every instance constexpr created. Sadly this cannot be done in
// the ctor since there is no way to know whether the ctor is actually
// called at compile time or not.
template <std::size_t N>
constexpr StaticMultimapView(const V (&array)[N])
: begin_{&array[0]}, end_{&array[0] + N} {}

// std::equal_range will be constexpr in C++20 only, so far there is actually
// no need for equal_range to be constexpr anyway.
std::pair<const_iterator, const_iterator> equal_range(const Key &key) const {
return std::equal_range(begin_, end_, key);
}

// Check that the array is sorted. This used to assert at compile time that
// the array is indeed sorted. When C++20 is required for flang,
// std::is_sorted can be used here since it will be constexpr.
constexpr bool Verify() const {
const V *lastSeen{begin_};
bool isSorted{true};
for (const auto *x{begin_}; x != end_; ++x) {
isSorted &= lastSeen->key <= x->key;
lastSeen = x;
}
return isSorted;
}

private:
const_iterator begin_{nullptr};
const_iterator end_{nullptr};
};
} // namespace Fortran::common
#endif // FORTRAN_COMMON_STATIC_MULTIMAP_VIEW_H_
5 changes: 0 additions & 5 deletions flang/include/flang/Evaluate/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#ifndef FORTRAN_EVALUATE_COMMON_H_
#define FORTRAN_EVALUATE_COMMON_H_

#include "intrinsics-library.h"
#include "flang/Common/Fortran.h"
#include "flang/Common/default-kinds.h"
#include "flang/Common/enum-set.h"
Expand Down Expand Up @@ -237,9 +236,6 @@ class FoldingContext {
bool flushSubnormalsToZero() const { return flushSubnormalsToZero_; }
bool bigEndian() const { return bigEndian_; }
const semantics::DerivedTypeSpec *pdtInstance() const { return pdtInstance_; }
const HostIntrinsicProceduresLibrary &hostIntrinsicsLibrary() const {
return hostIntrinsicsLibrary_;
}
const evaluate::IntrinsicProcTable &intrinsics() const { return intrinsics_; }

ConstantSubscript &StartImpliedDo(parser::CharBlock, ConstantSubscript = 1);
Expand All @@ -264,7 +260,6 @@ class FoldingContext {
bool bigEndian_{false};
const semantics::DerivedTypeSpec *pdtInstance_{nullptr};
std::map<parser::CharBlock, ConstantSubscript> impliedDos_;
HostIntrinsicProceduresLibrary hostIntrinsicsLibrary_;
};

void RealFlagWarnings(FoldingContext &, const RealFlags &, const char *op);
Expand Down
109 changes: 23 additions & 86 deletions flang/include/flang/Evaluate/intrinsics-library.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,99 +10,36 @@
#define FORTRAN_EVALUATE_INTRINSICS_LIBRARY_H_

// Defines structures to be used in F18 for folding intrinsic function with host
// runtime libraries. To avoid unnecessary header circular dependencies, the
// actual implementation of the templatized member function are defined in
// intrinsics-library-templates.h The header at hand is meant to be included by
// files that need to define intrinsic runtime data structure but that do not
// use them directly. To actually use the runtime data structures,
// intrinsics-library-templates.h must be included.
// runtime libraries.

#include <functional>
#include <map>
#include <optional>
#include <string>
#include <vector>

namespace Fortran::evaluate {
class FoldingContext;

using TypeCode = unsigned char;

template <typename TR, typename... TA> using FuncPointer = TR (*)(TA...);
// This specific type signature prevents GCC complaining about function casts.
using GenericFunctionPointer = void (*)(void);

enum class PassBy { Ref, Val };
template <typename TA, PassBy Pass = PassBy::Ref> struct ArgumentInfo {
using Type = TA;
static constexpr PassBy pass{Pass};
};

template <typename TR, typename... ArgInfo> struct Signature {
// Note valid template argument are of form
//<TR, ArgumentInfo<TA, PassBy>...> where TA and TR belong to RuntimeTypes.
// RuntimeTypes is a type union defined in intrinsics-library-templates.h to
// avoid circular dependencies. Argument of type void cannot be passed by
// value. So far TR cannot be a pointer.
const std::string name;
};

struct IntrinsicProcedureRuntimeDescription {
const std::string name;
const TypeCode returnType;
const std::vector<TypeCode> argumentsType;
const std::vector<PassBy> argumentsPassedBy;
const bool isElemental;
const GenericFunctionPointer callable;
// Construct from description using host independent types (RuntimeTypes)
template <typename TR, typename... ArgInfo>
IntrinsicProcedureRuntimeDescription(
const Signature<TR, ArgInfo...> &signature, bool isElemental = false);
};

// HostRuntimeIntrinsicProcedure allows host runtime function to be called for
// constant folding.
struct HostRuntimeIntrinsicProcedure : IntrinsicProcedureRuntimeDescription {
// Construct from runtime pointer with host types (float, double....)
template <typename HostTR, typename... HostTA>
HostRuntimeIntrinsicProcedure(const std::string &name,
FuncPointer<HostTR, HostTA...> func, bool isElemental = false);
HostRuntimeIntrinsicProcedure(
const IntrinsicProcedureRuntimeDescription &rteProc,
GenericFunctionPointer handle)
: IntrinsicProcedureRuntimeDescription{rteProc}, handle{handle} {}
GenericFunctionPointer handle;
};

// Defines a wrapper type that indirects calls to host runtime functions.
// Valid ConstantContainer are Scalar (only for elementals) and Constant.
template <template <typename> typename ConstantContainer, typename TR,
typename... TA>
using HostProcedureWrapper = std::function<ConstantContainer<TR>(
FoldingContext &, ConstantContainer<TA>...)>;

// HostIntrinsicProceduresLibrary is a data structure that holds
// HostRuntimeIntrinsicProcedure elements. It is meant for constant folding.
// When queried for an intrinsic procedure, it can return a callable object that
// implements this intrinsic if a host runtime function pointer for this
// intrinsic was added to its data structure.
class HostIntrinsicProceduresLibrary {
public:
HostIntrinsicProceduresLibrary();
void AddProcedure(HostRuntimeIntrinsicProcedure &&sym) {
const std::string name{sym.name};
procedures_.insert(std::make_pair(name, std::move(sym)));
}
bool HasEquivalentProcedure(
const IntrinsicProcedureRuntimeDescription &sym) const;
template <template <typename> typename ConstantContainer, typename TR,
typename... TA>
std::optional<HostProcedureWrapper<ConstantContainer, TR, TA...>>
GetHostProcedureWrapper(const std::string &name) const;

private:
std::multimap<std::string, const HostRuntimeIntrinsicProcedure> procedures_;
};

class DynamicType;
struct SomeType;
template <typename> class Expr;

// Define a callable type that is used to fold scalar intrinsic function using
// host runtime. These callables are responsible for the conversions between
// host types and Fortran abstract types (Scalar<T>). They also deal with
// floating point environment (To set it up to match the Fortran compiling
// options and to clean it up after the call). Floating point errors are
// reported to the FoldingContext. For 16bits float types, 32bits float host
// runtime plus conversions may be used to build the host wrappers if no 16bits
// runtime is available. IEEE 128bits float may also be used for x87 float.
// Potential conversion overflows are reported by the HostRuntimeWrapper in the
// FoldingContext.
using HostRuntimeWrapper = std::function<Expr<SomeType>(
FoldingContext &, std::vector<Expr<SomeType>> &&)>;

// Returns the folder using host runtime given the intrinsic function name,
// result and argument types. Nullopt if no host runtime is available for such
// intrinsic function.
std::optional<HostRuntimeWrapper> GetHostRuntimeWrapper(const std::string &name,
DynamicType resultType, const std::vector<DynamicType> &argTypes);
} // namespace Fortran::evaluate
#endif // FORTRAN_EVALUATE_INTRINSICS_LIBRARY_H_
3 changes: 1 addition & 2 deletions flang/lib/Evaluate/fold-complex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ Expr<Type<TypeCategory::Complex, KIND>> FoldIntrinsicFunction(
name == "atan" || name == "atanh" || name == "cos" || name == "cosh" ||
name == "exp" || name == "log" || name == "sin" || name == "sinh" ||
name == "sqrt" || name == "tan" || name == "tanh") {
if (auto callable{context.hostIntrinsicsLibrary()
.GetHostProcedureWrapper<Scalar, T, T>(name)}) {
if (auto callable{GetHostRuntimeWrapper<T, T>(name)}) {
return FoldElementalIntrinsic<T, T>(
context, std::move(funcRef), *callable);
} else {
Expand Down
23 changes: 20 additions & 3 deletions flang/lib/Evaluate/fold-implementation.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#include "character.h"
#include "host.h"
#include "int-power.h"
#include "intrinsics-library-templates.h"
#include "flang/Common/indirection.h"
#include "flang/Common/template.h"
#include "flang/Common/unwrap.h"
Expand All @@ -22,6 +21,7 @@
#include "flang/Evaluate/expression.h"
#include "flang/Evaluate/fold.h"
#include "flang/Evaluate/formatting.h"
#include "flang/Evaluate/intrinsics-library.h"
#include "flang/Evaluate/intrinsics.h"
#include "flang/Evaluate/shape.h"
#include "flang/Evaluate/tools.h"
Expand Down Expand Up @@ -70,6 +70,24 @@ template <typename T> class Folder {
std::optional<Constant<SubscriptInteger>> GetConstantSubscript(
FoldingContext &, Subscript &, const NamedEntity &, int dim);

// Helper to use host runtime on scalars for folding.
template <typename TR, typename... TA>
std::optional<std::function<Scalar<TR>(FoldingContext &, Scalar<TA>...)>>
GetHostRuntimeWrapper(const std::string &name) {
std::vector<DynamicType> argTypes{TA{}.GetType()...};
if (auto hostWrapper{GetHostRuntimeWrapper(name, TR{}.GetType(), argTypes)}) {
return [hostWrapper](
FoldingContext &context, Scalar<TA>... args) -> Scalar<TR> {
std::vector<Expr<SomeType>> genericArgs{
AsGenericExpr(Constant<TA>{args})...};
return GetScalarConstantValue<TR>(
(*hostWrapper)(context, std::move(genericArgs)))
.value();
};
}
return std::nullopt;
}

// FoldOperation() rewrites expression tree nodes.
// If there is any possibility that the rewritten node will
// not have the same representation type, the result of
Expand Down Expand Up @@ -1410,8 +1428,7 @@ Expr<T> FoldOperation(FoldingContext &context, Power<T> &&x) {
}
return Expr<T>{Constant<T>{power.power}};
} else {
if (auto callable{context.hostIntrinsicsLibrary()
.GetHostProcedureWrapper<Scalar, T, T, T>("pow")}) {
if (auto callable{GetHostRuntimeWrapper<T, T, T>("pow")}) {
return Expr<T>{
Constant<T>{(*callable)(context, folded->first, folded->second)}};
} else {
Expand Down
15 changes: 4 additions & 11 deletions flang/lib/Evaluate/fold-real.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
name == "log_gamma" || name == "sin" || name == "sinh" ||
name == "sqrt" || name == "tan" || name == "tanh") {
CHECK(args.size() == 1);
if (auto callable{context.hostIntrinsicsLibrary()
.GetHostProcedureWrapper<Scalar, T, T>(name)}) {
if (auto callable{GetHostRuntimeWrapper<T, T>(name)}) {
return FoldElementalIntrinsic<T, T>(
context, std::move(funcRef), *callable);
} else {
Expand All @@ -44,9 +43,7 @@ Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
name == "mod") {
std::string localName{name == "atan" ? "atan2" : name};
CHECK(args.size() == 2);
if (auto callable{
context.hostIntrinsicsLibrary()
.GetHostProcedureWrapper<Scalar, T, T, T>(localName)}) {
if (auto callable{GetHostRuntimeWrapper<T, T, T>(localName)}) {
return FoldElementalIntrinsic<T, T, T>(
context, std::move(funcRef), *callable);
} else {
Expand All @@ -58,9 +55,7 @@ Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
if (args.size() == 2) { // elemental
// runtime functions use int arg
using Int4 = Type<TypeCategory::Integer, 4>;
if (auto callable{
context.hostIntrinsicsLibrary()
.GetHostProcedureWrapper<Scalar, T, Int4, T>(name)}) {
if (auto callable{GetHostRuntimeWrapper<T, Int4, T>(name)}) {
return FoldElementalIntrinsic<T, Int4, T>(
context, std::move(funcRef), *callable);
} else {
Expand All @@ -75,9 +70,7 @@ Expr<Type<TypeCategory::Real, KIND>> FoldIntrinsicFunction(
return FoldElementalIntrinsic<T, T>(
context, std::move(funcRef), &Scalar<T>::ABS);
} else if (auto *z{UnwrapExpr<Expr<SomeComplex>>(args[0])}) {
if (auto callable{
context.hostIntrinsicsLibrary()
.GetHostProcedureWrapper<Scalar, T, ComplexT>("abs")}) {
if (auto callable{GetHostRuntimeWrapper<T, ComplexT>("abs")}) {
return FoldElementalIntrinsic<T, ComplexT>(
context, std::move(funcRef), *callable);
} else {
Expand Down
Loading

0 comments on commit 94d9a4f

Please sign in to comment.