| //===-- ReductionProcessor.cpp ----------------------------------*- 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/ | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "flang/Lower/Support/ReductionProcessor.h" | |
| #include "flang/Lower/AbstractConverter.h" | |
| #include "flang/Lower/ConvertType.h" | |
| #include "flang/Lower/OpenMP.h" | |
| #include "flang/Lower/OpenMP/Clauses.h" | |
| #include "flang/Lower/Support/PrivateReductionUtils.h" | |
| #include "flang/Lower/SymbolMap.h" | |
| #include "flang/Optimizer/Builder/Complex.h" | |
| #include "flang/Optimizer/Builder/HLFIRTools.h" | |
| #include "flang/Optimizer/Builder/Todo.h" | |
| #include "flang/Optimizer/Dialect/FIRType.h" | |
| #include "flang/Optimizer/HLFIR/HLFIROps.h" | |
| #include "flang/Semantics/openmp-utils.h" | |
| #include "mlir/Dialect/OpenMP/OpenMPDialect.h" | |
| #include "mlir/IR/IRMapping.h" | |
| #include "mlir/Interfaces/CallInterfaces.h" | |
| #include "llvm/Support/CommandLine.h" | |
| #include <type_traits> | |
| static llvm::cl::opt<bool> forceByrefReduction( | |
| "force-byref-reduction", | |
| llvm::cl::desc("Pass all reduction arguments by reference"), | |
| llvm::cl::Hidden); | |
| using ReductionModifier = | |
| Fortran::lower::omp::clause::Reduction::ReductionModifier; | |
| namespace Fortran { | |
| namespace lower { | |
| namespace omp { | |
| // explicit template declarations | |
| template bool ReductionProcessor::processReductionArguments< | |
| mlir::omp::DeclareReductionOp, omp::clause::ReductionOperatorList>( | |
| mlir::Location currentLocation, lower::AbstractConverter &converter, | |
| const omp::clause::ReductionOperatorList &redOperatorList, | |
| llvm::SmallVectorImpl<mlir::Value> &reductionVars, | |
| llvm::SmallVectorImpl<bool> &reduceVarByRef, | |
| llvm::SmallVectorImpl<mlir::Attribute> &reductionDeclSymbols, | |
| const llvm::SmallVectorImpl<const semantics::Symbol *> &reductionSymbols, | |
| semantics::SemanticsContext *semaCtx, | |
| llvm::DenseMap<const semantics::Symbol *, mlir::Value> *reductionVarCache); | |
| template bool ReductionProcessor::processReductionArguments< | |
| fir::DeclareReductionOp, llvm::SmallVector<fir::ReduceOperationEnum>>( | |
| mlir::Location currentLocation, lower::AbstractConverter &converter, | |
| const llvm::SmallVector<fir::ReduceOperationEnum> &redOperatorList, | |
| llvm::SmallVectorImpl<mlir::Value> &reductionVars, | |
| llvm::SmallVectorImpl<bool> &reduceVarByRef, | |
| llvm::SmallVectorImpl<mlir::Attribute> &reductionDeclSymbols, | |
| const llvm::SmallVectorImpl<const semantics::Symbol *> &reductionSymbols, | |
| semantics::SemanticsContext *semaCtx, | |
| llvm::DenseMap<const semantics::Symbol *, mlir::Value> *reductionVarCache); | |
| template mlir::omp::DeclareReductionOp | |
| ReductionProcessor::createDeclareReduction<mlir::omp::DeclareReductionOp>( | |
| AbstractConverter &converter, llvm::StringRef reductionOpName, | |
| const ReductionIdentifier redId, mlir::Type type, mlir::Location loc, | |
| bool isByRef); | |
| template fir::DeclareReductionOp | |
| ReductionProcessor::createDeclareReduction<fir::DeclareReductionOp>( | |
| AbstractConverter &converter, llvm::StringRef reductionOpName, | |
| const ReductionIdentifier redId, mlir::Type type, mlir::Location loc, | |
| bool isByRef); | |
| template mlir::omp::DeclareReductionOp | |
| ReductionProcessor::createDeclareReductionHelper<mlir::omp::DeclareReductionOp>( | |
| AbstractConverter &converter, llvm::StringRef reductionOpName, | |
| mlir::Type type, mlir::Location loc, bool isByRef, | |
| GenCombinerCBTy genCombinerCB, GenInitValueCBTy genInitValueCB, | |
| const semantics::Symbol *sym); | |
| ReductionProcessor::ReductionIdentifier ReductionProcessor::getReductionType( | |
| const omp::clause::ProcedureDesignator &pd) { | |
| auto redType = llvm::StringSwitch<std::optional<ReductionIdentifier>>( | |
| getRealName(pd.v.sym()).ToString()) | |
| .Case("max", ReductionIdentifier::MAX) | |
| .Case("min", ReductionIdentifier::MIN) | |
| .Case("iand", ReductionIdentifier::IAND) | |
| .Case("ior", ReductionIdentifier::IOR) | |
| .Case("ieor", ReductionIdentifier::IEOR) | |
| .Default(std::nullopt); | |
| assert(redType && "Invalid Reduction"); | |
| return *redType; | |
| } | |
| ReductionProcessor::ReductionIdentifier ReductionProcessor::getReductionType( | |
| omp::clause::DefinedOperator::IntrinsicOperator intrinsicOp) { | |
| switch (intrinsicOp) { | |
| case omp::clause::DefinedOperator::IntrinsicOperator::Add: | |
| return ReductionIdentifier::ADD; | |
| case omp::clause::DefinedOperator::IntrinsicOperator::Subtract: | |
| return ReductionIdentifier::SUBTRACT; | |
| case omp::clause::DefinedOperator::IntrinsicOperator::Multiply: | |
| return ReductionIdentifier::MULTIPLY; | |
| case omp::clause::DefinedOperator::IntrinsicOperator::AND: | |
| return ReductionIdentifier::AND; | |
| case omp::clause::DefinedOperator::IntrinsicOperator::EQV: | |
| return ReductionIdentifier::EQV; | |
| case omp::clause::DefinedOperator::IntrinsicOperator::OR: | |
| return ReductionIdentifier::OR; | |
| case omp::clause::DefinedOperator::IntrinsicOperator::NEQV: | |
| return ReductionIdentifier::NEQV; | |
| default: | |
| llvm_unreachable("unexpected intrinsic operator in reduction"); | |
| } | |
| } | |
| // Bridge the clause-level intrinsic operator to the parser-level enum consumed | |
| // by semantics' MakeNameFromOperator, so the mangled reduction name ("op.+", | |
| // "op.AND", ...) is produced by the single semantics source of truth rather | |
| // than re-spelled here. Only the operators a user reduction may use reach this | |
| // (the intrinsic-operator clause path filters the rest with a TODO first). | |
| parser::DefinedOperator::IntrinsicOperator | |
| ReductionProcessor::toParserIntrinsicOperator( | |
| omp::clause::DefinedOperator::IntrinsicOperator op) { | |
| using C = omp::clause::DefinedOperator::IntrinsicOperator; | |
| using P = parser::DefinedOperator::IntrinsicOperator; | |
| switch (op) { | |
| case C::Add: | |
| return P::Add; | |
| case C::Multiply: | |
| return P::Multiply; | |
| case C::AND: | |
| return P::AND; | |
| case C::OR: | |
| return P::OR; | |
| case C::EQV: | |
| return P::EQV; | |
| case C::NEQV: | |
| return P::NEQV; | |
| default: | |
| llvm_unreachable("unexpected intrinsic operator in user reduction"); | |
| } | |
| } | |
| ReductionProcessor::ReductionIdentifier | |
| ReductionProcessor::getReductionType(const fir::ReduceOperationEnum &redOp) { | |
| switch (redOp) { | |
| case fir::ReduceOperationEnum::Add: | |
| return ReductionIdentifier::ADD; | |
| case fir::ReduceOperationEnum::Multiply: | |
| return ReductionIdentifier::MULTIPLY; | |
| case fir::ReduceOperationEnum::AND: | |
| return ReductionIdentifier::AND; | |
| case fir::ReduceOperationEnum::OR: | |
| return ReductionIdentifier::OR; | |
| case fir::ReduceOperationEnum::EQV: | |
| return ReductionIdentifier::EQV; | |
| case fir::ReduceOperationEnum::NEQV: | |
| return ReductionIdentifier::NEQV; | |
| case fir::ReduceOperationEnum::IAND: | |
| return ReductionIdentifier::IAND; | |
| case fir::ReduceOperationEnum::IEOR: | |
| return ReductionIdentifier::IEOR; | |
| case fir::ReduceOperationEnum::IOR: | |
| return ReductionIdentifier::IOR; | |
| case fir::ReduceOperationEnum::MAX: | |
| return ReductionIdentifier::MAX; | |
| case fir::ReduceOperationEnum::MIN: | |
| return ReductionIdentifier::MIN; | |
| } | |
| llvm_unreachable("Unhandled ReductionIdentifier case"); | |
| } | |
| bool ReductionProcessor::supportedIntrinsicProcReduction( | |
| const omp::clause::ProcedureDesignator &pd) { | |
| semantics::Symbol *sym = pd.v.sym(); | |
| if (!sym->GetUltimate().attrs().test(semantics::Attr::INTRINSIC)) | |
| return false; | |
| auto redType = llvm::StringSwitch<bool>(getRealName(sym).ToString()) | |
| .Case("max", true) | |
| .Case("min", true) | |
| .Case("iand", true) | |
| .Case("ior", true) | |
| .Case("ieor", true) | |
| .Default(false); | |
| return redType; | |
| } | |
| const semantics::Symbol * | |
| ReductionProcessor::findUserDefinedReductionForIntrinsic( | |
| const semantics::Scope &scope, | |
| const omp::clause::ProcedureDesignator &reductionIntrinsic, | |
| const semantics::DeclTypeSpec *type) { | |
| const semantics::Symbol *symbol = reductionIntrinsic.v.sym(); | |
| if (!symbol || !type) | |
| return nullptr; | |
| std::string mangledName = "op." + getRealName(symbol).ToString(); | |
| const semantics::Symbol *reductionSymbol = | |
| scope.FindSymbol(parser::CharBlock{mangledName}); | |
| if (!reductionSymbol) | |
| return nullptr; | |
| const semantics::Symbol &ultimate = reductionSymbol->GetUltimate(); | |
| const auto *details = ultimate.detailsIf<semantics::UserReductionDetails>(); | |
| return details && details->SupportsType(*type) ? &ultimate : nullptr; | |
| } | |
| std::string | |
| ReductionProcessor::getReductionName(llvm::StringRef name, | |
| const fir::KindMapping &kindMap, | |
| mlir::Type ty, bool isByRef) { | |
| ty = fir::unwrapRefType(ty); | |
| // extra string to distinguish reduction functions for variables passed by | |
| // reference | |
| llvm::StringRef byrefAddition{""}; | |
| if (isByRef) | |
| byrefAddition = "_byref"; | |
| return fir::getTypeAsString(ty, kindMap, (name + byrefAddition).str()); | |
| } | |
| std::string | |
| ReductionProcessor::getReductionName(ReductionIdentifier redId, | |
| const fir::KindMapping &kindMap, | |
| mlir::Type ty, bool isByRef) { | |
| std::string reductionName; | |
| switch (redId) { | |
| case ReductionIdentifier::ADD: | |
| reductionName = "add_reduction"; | |
| break; | |
| case ReductionIdentifier::MULTIPLY: | |
| reductionName = "multiply_reduction"; | |
| break; | |
| case ReductionIdentifier::AND: | |
| reductionName = "and_reduction"; | |
| break; | |
| case ReductionIdentifier::EQV: | |
| reductionName = "eqv_reduction"; | |
| break; | |
| case ReductionIdentifier::OR: | |
| reductionName = "or_reduction"; | |
| break; | |
| case ReductionIdentifier::NEQV: | |
| reductionName = "neqv_reduction"; | |
| break; | |
| case ReductionIdentifier::MAX: | |
| reductionName = "max_reduction"; | |
| break; | |
| case ReductionIdentifier::MIN: | |
| reductionName = "min_reduction"; | |
| break; | |
| case ReductionIdentifier::IAND: | |
| reductionName = "iand_reduction"; | |
| break; | |
| case ReductionIdentifier::IOR: | |
| reductionName = "ior_reduction"; | |
| break; | |
| case ReductionIdentifier::IEOR: | |
| reductionName = "ieor_reduction"; | |
| break; | |
| default: | |
| llvm_unreachable("unsupported reduction identifier"); | |
| } | |
| return getReductionName(reductionName, kindMap, ty, isByRef); | |
| } | |
| std::string ReductionProcessor::getScopedUserReductionName( | |
| AbstractConverter &converter, const semantics::Symbol &reductionSymbol, | |
| mlir::Type reductionType, bool isByRef) { | |
| // Qualify the reduction symbol's ultimate name with its owning scope so that | |
| // user-defined reductions with the same spelling in different modules get | |
| // distinct op names. Use the (name, scope) mangleName overload: the | |
| // (symbol) overload does not handle UserReductionDetails. | |
| const semantics::Symbol &ultimate = reductionSymbol.GetUltimate(); | |
| std::string name = ultimate.name().ToString(); | |
| std::string scopedName = converter.mangleName(name, ultimate.owner()); | |
| // Append the type and by-ref suffix, as the intrinsic reductions do, so a | |
| // declare reduction listing several types produces one op per type and every | |
| // name ends in a type-grammar token. Suffix unconditionally, even for one | |
| // type: otherwise a multi-type myred's per-type op "myred_i32" would collide | |
| // with a single-type reduction named "myred_i32", and the by-name dedup would | |
| // bind one reduction's clause to the other's combiner (a silent miscompile). | |
| // The by-ref token also gives an allocatable/pointer trivial reduction (a | |
| // boxed by-ref operand of a by-value declared type) a name the directive | |
| // never emitted, so it reaches a clean TODO instead of binding a by-value op | |
| // to a box (handled by llvm-project#186765). | |
| return getReductionName(scopedName, converter.getFirOpBuilder().getKindMap(), | |
| reductionType, isByRef); | |
| } | |
| mlir::Value | |
| ReductionProcessor::getReductionInitValue(mlir::Location loc, mlir::Type type, | |
| ReductionIdentifier redId, | |
| fir::FirOpBuilder &builder) { | |
| type = fir::unwrapRefType(type); | |
| if (!fir::isa_integer(type) && !fir::isa_real(type) && | |
| !fir::isa_complex(type) && !mlir::isa<fir::LogicalType>(type)) | |
| TODO(loc, "Reduction of some types is not supported"); | |
| switch (redId) { | |
| case ReductionIdentifier::MAX: { | |
| if (auto ty = mlir::dyn_cast<mlir::FloatType>(type)) { | |
| const llvm::fltSemantics &sem = ty.getFloatSemantics(); | |
| return builder.createRealConstant( | |
| loc, type, llvm::APFloat::getLargest(sem, /*Negative=*/true)); | |
| } | |
| unsigned bits = type.getIntOrFloatBitWidth(); | |
| return builder.createIntegerConstant(loc, type, | |
| llvm::APInt::getSignedMinValue(bits)); | |
| } | |
| case ReductionIdentifier::MIN: { | |
| if (auto ty = mlir::dyn_cast<mlir::FloatType>(type)) { | |
| const llvm::fltSemantics &sem = ty.getFloatSemantics(); | |
| return builder.createRealConstant( | |
| loc, type, llvm::APFloat::getLargest(sem, /*Negative=*/false)); | |
| } | |
| unsigned bits = type.getIntOrFloatBitWidth(); | |
| return builder.createIntegerConstant(loc, type, | |
| llvm::APInt::getSignedMaxValue(bits)); | |
| } | |
| case ReductionIdentifier::IOR: { | |
| unsigned bits = type.getIntOrFloatBitWidth(); | |
| int64_t zeroInt = llvm::APInt::getZero(bits).getSExtValue(); | |
| return builder.createIntegerConstant(loc, type, zeroInt); | |
| } | |
| case ReductionIdentifier::IEOR: { | |
| unsigned bits = type.getIntOrFloatBitWidth(); | |
| int64_t zeroInt = llvm::APInt::getZero(bits).getSExtValue(); | |
| return builder.createIntegerConstant(loc, type, zeroInt); | |
| } | |
| case ReductionIdentifier::IAND: { | |
| unsigned bits = type.getIntOrFloatBitWidth(); | |
| int64_t allOnInt = llvm::APInt::getAllOnes(bits).getSExtValue(); | |
| return builder.createIntegerConstant(loc, type, allOnInt); | |
| } | |
| case ReductionIdentifier::ADD: | |
| case ReductionIdentifier::MULTIPLY: | |
| case ReductionIdentifier::AND: | |
| case ReductionIdentifier::OR: | |
| case ReductionIdentifier::EQV: | |
| case ReductionIdentifier::NEQV: | |
| if (auto cplxTy = mlir::dyn_cast<mlir::ComplexType>(type)) { | |
| mlir::Type realTy = cplxTy.getElementType(); | |
| mlir::Value initRe = builder.createRealConstant( | |
| loc, realTy, getOperationIdentity(redId, loc)); | |
| mlir::Value initIm = builder.createRealConstant(loc, realTy, 0); | |
| return fir::factory::Complex{builder, loc}.createComplex(type, initRe, | |
| initIm); | |
| } | |
| if (mlir::isa<mlir::FloatType>(type)) | |
| return mlir::arith::ConstantOp::create( | |
| builder, loc, type, | |
| builder.getFloatAttr(type, (double)getOperationIdentity(redId, loc))); | |
| if (mlir::isa<fir::LogicalType>(type)) { | |
| mlir::Value intConst = mlir::arith::ConstantOp::create( | |
| builder, loc, builder.getI1Type(), | |
| builder.getIntegerAttr(builder.getI1Type(), | |
| getOperationIdentity(redId, loc))); | |
| return builder.createConvert(loc, type, intConst); | |
| } | |
| return mlir::arith::ConstantOp::create( | |
| builder, loc, type, | |
| builder.getIntegerAttr(type, getOperationIdentity(redId, loc))); | |
| case ReductionIdentifier::ID: | |
| case ReductionIdentifier::USER_DEF_OP: | |
| case ReductionIdentifier::SUBTRACT: | |
| TODO(loc, "Reduction of some identifier types is not supported"); | |
| } | |
| llvm_unreachable("Unhandled Reduction identifier : getReductionInitValue"); | |
| } | |
| mlir::Value ReductionProcessor::createScalarCombiner( | |
| fir::FirOpBuilder &builder, mlir::Location loc, ReductionIdentifier redId, | |
| mlir::Type type, mlir::Value op1, mlir::Value op2) { | |
| mlir::Value reductionOp; | |
| type = fir::unwrapRefType(type); | |
| switch (redId) { | |
| case ReductionIdentifier::MAX: | |
| reductionOp = | |
| getReductionOperation<mlir::arith::MaxNumFOp, mlir::arith::MaxSIOp>( | |
| builder, type, loc, op1, op2); | |
| break; | |
| case ReductionIdentifier::MIN: | |
| reductionOp = | |
| getReductionOperation<mlir::arith::MinNumFOp, mlir::arith::MinSIOp>( | |
| builder, type, loc, op1, op2); | |
| break; | |
| case ReductionIdentifier::IOR: | |
| assert((type.isIntOrIndex()) && "only integer is expected"); | |
| reductionOp = mlir::arith::OrIOp::create(builder, loc, op1, op2); | |
| break; | |
| case ReductionIdentifier::IEOR: | |
| assert((type.isIntOrIndex()) && "only integer is expected"); | |
| reductionOp = mlir::arith::XOrIOp::create(builder, loc, op1, op2); | |
| break; | |
| case ReductionIdentifier::IAND: | |
| assert((type.isIntOrIndex()) && "only integer is expected"); | |
| reductionOp = mlir::arith::AndIOp::create(builder, loc, op1, op2); | |
| break; | |
| case ReductionIdentifier::ADD: | |
| reductionOp = | |
| getReductionOperation<mlir::arith::AddFOp, mlir::arith::AddIOp, | |
| fir::AddcOp>(builder, type, loc, op1, op2); | |
| break; | |
| case ReductionIdentifier::MULTIPLY: | |
| reductionOp = | |
| getReductionOperation<mlir::arith::MulFOp, mlir::arith::MulIOp, | |
| fir::MulcOp>(builder, type, loc, op1, op2); | |
| break; | |
| case ReductionIdentifier::AND: { | |
| mlir::Value v1 = builder.createConvert(loc, type, op1); | |
| mlir::Value v2 = builder.createConvert(loc, type, op2); | |
| reductionOp = fir::LogicalAndOp::create(builder, loc, type, v1, v2); | |
| break; | |
| } | |
| case ReductionIdentifier::OR: { | |
| mlir::Value v1 = builder.createConvert(loc, type, op1); | |
| mlir::Value v2 = builder.createConvert(loc, type, op2); | |
| reductionOp = fir::LogicalOrOp::create(builder, loc, type, v1, v2); | |
| break; | |
| } | |
| case ReductionIdentifier::EQV: { | |
| mlir::Value v1 = builder.createConvert(loc, type, op1); | |
| mlir::Value v2 = builder.createConvert(loc, type, op2); | |
| reductionOp = fir::EqvOp::create(builder, loc, type, v1, v2); | |
| break; | |
| } | |
| case ReductionIdentifier::NEQV: { | |
| mlir::Value v1 = builder.createConvert(loc, type, op1); | |
| mlir::Value v2 = builder.createConvert(loc, type, op2); | |
| reductionOp = fir::NeqvOp::create(builder, loc, type, v1, v2); | |
| break; | |
| } | |
| default: | |
| TODO(loc, "Reduction of some intrinsic operators is not supported"); | |
| } | |
| return reductionOp; | |
| } | |
| template <typename ParentDeclOpType> | |
| static void genYield(fir::FirOpBuilder &builder, mlir::Location loc, | |
| mlir::Value yieldedValue) { | |
| if constexpr (std::is_same_v<ParentDeclOpType, mlir::omp::DeclareReductionOp>) | |
| mlir::omp::YieldOp::create(builder, loc, yieldedValue); | |
| else | |
| fir::YieldOp::create(builder, loc, yieldedValue); | |
| } | |
| /// Create reduction combiner region for reduction variables which are boxed | |
| /// arrays | |
| template <typename DeclRedOpType> | |
| static void genBoxCombiner(fir::FirOpBuilder &builder, mlir::Location loc, | |
| ReductionProcessor::ReductionIdentifier redId, | |
| fir::BaseBoxType boxTy, mlir::Value lhs, | |
| mlir::Value rhs) { | |
| fir::SequenceType seqTy = mlir::dyn_cast_or_null<fir::SequenceType>( | |
| fir::unwrapRefType(boxTy.getEleTy())); | |
| fir::HeapType heapTy = | |
| mlir::dyn_cast_or_null<fir::HeapType>(boxTy.getEleTy()); | |
| fir::PointerType ptrTy = | |
| mlir::dyn_cast_or_null<fir::PointerType>(boxTy.getEleTy()); | |
| if ((!seqTy || seqTy.hasUnknownShape()) && !heapTy && !ptrTy) | |
| TODO(loc, "Unsupported boxed type in OpenMP reduction"); | |
| // load fir.ref<fir.box<...>> | |
| mlir::Value lhsAddr = lhs; | |
| lhs = fir::LoadOp::create(builder, loc, lhs); | |
| rhs = fir::LoadOp::create(builder, loc, rhs); | |
| if ((heapTy || ptrTy) && !seqTy) { | |
| // get box contents (heap pointers) | |
| lhs = fir::BoxAddrOp::create(builder, loc, lhs); | |
| rhs = fir::BoxAddrOp::create(builder, loc, rhs); | |
| mlir::Value lhsValAddr = lhs; | |
| // load heap pointers | |
| lhs = fir::LoadOp::create(builder, loc, lhs); | |
| rhs = fir::LoadOp::create(builder, loc, rhs); | |
| mlir::Type eleTy = heapTy ? heapTy.getEleTy() : ptrTy.getEleTy(); | |
| mlir::Value result = ReductionProcessor::createScalarCombiner( | |
| builder, loc, redId, eleTy, lhs, rhs); | |
| fir::StoreOp::create(builder, loc, result, lhsValAddr); | |
| genYield<DeclRedOpType>(builder, loc, lhsAddr); | |
| return; | |
| } | |
| // Get ShapeShift with default lower bounds. This makes it possible to use | |
| // unmodified LoopNest's indices with ArrayCoorOp. | |
| fir::ShapeShiftOp shapeShift = | |
| getShapeShift(builder, loc, lhs, | |
| /*cannotHaveNonDefaultLowerBounds=*/false, | |
| /*useDefaultLowerBounds=*/true); | |
| // Iterate over array elements, applying the equivalent scalar reduction: | |
| // F2018 5.4.10.2: Unallocated allocatable variables may not be referenced | |
| // and so no null check is needed here before indexing into the (possibly | |
| // allocatable) arrays. | |
| // A hlfir::elemental here gets inlined with a temporary so create the | |
| // loop nest directly. | |
| // This function already controls all of the code in this region so we | |
| // know this won't miss any opportuinties for clever elemental inlining | |
| hlfir::LoopNest nest = hlfir::genLoopNest( | |
| loc, builder, shapeShift.getExtents(), /*isUnordered=*/true); | |
| builder.setInsertionPointToStart(nest.body); | |
| const bool seqIsVolatile = fir::isa_volatile_type(seqTy.getEleTy()); | |
| mlir::Type refTy = fir::ReferenceType::get(seqTy.getEleTy(), seqIsVolatile); | |
| auto lhsEleAddr = fir::ArrayCoorOp::create( | |
| builder, loc, refTy, lhs, shapeShift, /*slice=*/mlir::Value{}, | |
| nest.oneBasedIndices, /*typeparms=*/mlir::ValueRange{}); | |
| auto rhsEleAddr = fir::ArrayCoorOp::create( | |
| builder, loc, refTy, rhs, shapeShift, /*slice=*/mlir::Value{}, | |
| nest.oneBasedIndices, /*typeparms=*/mlir::ValueRange{}); | |
| auto lhsEle = fir::LoadOp::create(builder, loc, lhsEleAddr); | |
| auto rhsEle = fir::LoadOp::create(builder, loc, rhsEleAddr); | |
| mlir::Value scalarReduction = ReductionProcessor::createScalarCombiner( | |
| builder, loc, redId, refTy, lhsEle, rhsEle); | |
| fir::StoreOp::create(builder, loc, scalarReduction, lhsEleAddr); | |
| builder.setInsertionPointAfter(nest.outerOp); | |
| genYield<DeclRedOpType>(builder, loc, lhsAddr); | |
| } | |
| // generate combiner region for reduction operations | |
| template <typename DeclRedOpType> | |
| static void genCombiner(fir::FirOpBuilder &builder, mlir::Location loc, | |
| ReductionProcessor::ReductionIdentifier redId, | |
| mlir::Type ty, mlir::Value lhs, mlir::Value rhs, | |
| bool isByRef) { | |
| ty = fir::unwrapRefType(ty); | |
| if (fir::isa_trivial(ty)) { | |
| mlir::Value lhsLoaded = builder.loadIfRef(loc, lhs); | |
| mlir::Value rhsLoaded = builder.loadIfRef(loc, rhs); | |
| mlir::Value result = ReductionProcessor::createScalarCombiner( | |
| builder, loc, redId, ty, lhsLoaded, rhsLoaded); | |
| if (isByRef) { | |
| fir::StoreOp::create(builder, loc, result, lhs); | |
| genYield<DeclRedOpType>(builder, loc, lhs); | |
| } else { | |
| genYield<DeclRedOpType>(builder, loc, result); | |
| } | |
| return; | |
| } | |
| // all arrays should have been boxed | |
| if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(ty)) { | |
| genBoxCombiner<DeclRedOpType>(builder, loc, redId, boxTy, lhs, rhs); | |
| return; | |
| } | |
| TODO(loc, "OpenMP genCombiner for unsupported reduction variable type"); | |
| } | |
| // like fir::unwrapSeqOrBoxedSeqType except it also works for non-sequence boxes | |
| static mlir::Type unwrapSeqOrBoxedType(mlir::Type ty) { | |
| if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(ty)) | |
| return seqTy.getEleTy(); | |
| if (auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(ty)) { | |
| auto eleTy = fir::unwrapRefType(boxTy.getEleTy()); | |
| if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(eleTy)) | |
| return seqTy.getEleTy(); | |
| return eleTy; | |
| } | |
| return ty; | |
| } | |
| template <typename OpType> | |
| static void createReductionAllocAndInitRegions( | |
| AbstractConverter &converter, mlir::Location loc, OpType &reductionDecl, | |
| ReductionProcessor::GenInitValueCBTy genInitValueCB, mlir::Type type, | |
| bool isByRef, const Fortran::semantics::Symbol *sym) { | |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); | |
| auto yield = [&](mlir::Value ret) { genYield<OpType>(builder, loc, ret); }; | |
| mlir::Block *allocBlock = nullptr; | |
| mlir::Block *initBlock = nullptr; | |
| if (isByRef) { | |
| allocBlock = | |
| builder.createBlock(&reductionDecl.getAllocRegion(), | |
| reductionDecl.getAllocRegion().end(), {}, {}); | |
| initBlock = builder.createBlock(&reductionDecl.getInitializerRegion(), | |
| reductionDecl.getInitializerRegion().end(), | |
| {type, type}, {loc, loc}); | |
| } else { | |
| initBlock = builder.createBlock(&reductionDecl.getInitializerRegion(), | |
| reductionDecl.getInitializerRegion().end(), | |
| {type}, {loc}); | |
| } | |
| mlir::Type ty = fir::unwrapRefType(type); | |
| builder.setInsertionPointToEnd(initBlock); | |
| mlir::Value initValue = | |
| isByRef ? genInitValueCB(builder, loc, ty, initBlock->getArgument(0), | |
| initBlock->getArgument(1)) | |
| : genInitValueCB(builder, loc, ty, initBlock->getArgument(0), | |
| mlir::Value{}); | |
| if (isByRef) { | |
| populateByRefInitAndCleanupRegions( | |
| converter, loc, type, initValue, initBlock, | |
| reductionDecl.getInitializerAllocArg(), | |
| reductionDecl.getInitializerMoldArg(), reductionDecl.getCleanupRegion(), | |
| DeclOperationKind::Reduction, sym, | |
| /*cannotHaveLowerBounds=*/false, | |
| /*isDoConcurrent*/ std::is_same_v<OpType, fir::DeclareReductionOp>); | |
| } | |
| if (fir::isa_trivial(ty) || fir::isa_derived(ty)) { | |
| if (isByRef) { | |
| // alloc region | |
| builder.setInsertionPointToEnd(allocBlock); | |
| mlir::Value alloca = fir::AllocaOp::create(builder, loc, ty); | |
| yield(alloca); | |
| return; | |
| } | |
| // by val | |
| yield(initValue); | |
| return; | |
| } | |
| assert(isByRef && "passing non-trivial types by val is unsupported"); | |
| // alloc region | |
| builder.setInsertionPointToEnd(allocBlock); | |
| mlir::Value boxAlloca = fir::AllocaOp::create(builder, loc, ty); | |
| yield(boxAlloca); | |
| } | |
| template <typename DeclareRedType> | |
| DeclareRedType ReductionProcessor::createDeclareReductionHelper( | |
| AbstractConverter &converter, llvm::StringRef reductionOpName, | |
| mlir::Type type, mlir::Location loc, bool isByRef, | |
| GenCombinerCBTy genCombinerCB, GenInitValueCBTy genInitValueCB, | |
| const semantics::Symbol *sym) { | |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); | |
| mlir::OpBuilder::InsertionGuard guard(builder); | |
| mlir::ModuleOp module = builder.getModule(); | |
| assert(!reductionOpName.empty()); | |
| auto decl = module.lookupSymbol<DeclareRedType>(reductionOpName); | |
| if (decl) | |
| return decl; | |
| mlir::OpBuilder modBuilder(module.getBodyRegion()); | |
| mlir::Type valTy = fir::unwrapRefType(type); | |
| // For by-ref reductions, we want to keep track of the | |
| // boxed/referenced/allocated type. For example, for a `real, allocatable` | |
| // variable, `real` should be stored. | |
| mlir::TypeAttr boxedTyAttr{}; | |
| mlir::Type boxedTy; | |
| if (isByRef) { | |
| boxedTy = fir::unwrapPassByRefType(valTy); | |
| boxedTyAttr = mlir::TypeAttr::get(boxedTy); | |
| // For character types that are not already references, we need to wrap | |
| // them in a reference type for by-ref reductions. | |
| if (fir::isa_char(valTy) && !fir::isa_ref_type(type)) { | |
| type = fir::ReferenceType::get(valTy); | |
| } | |
| } else | |
| type = valTy; | |
| decl = DeclareRedType::create(modBuilder, loc, reductionOpName, | |
| /*sym_visibility=*/nullptr, type, boxedTyAttr); | |
| createReductionAllocAndInitRegions(converter, loc, decl, genInitValueCB, type, | |
| isByRef, sym); | |
| builder.createBlock(&decl.getReductionRegion(), | |
| decl.getReductionRegion().end(), {type, type}, | |
| {loc, loc}); | |
| builder.setInsertionPointToEnd(&decl.getReductionRegion().back()); | |
| mlir::Value op1 = decl.getReductionRegion().front().getArgument(0); | |
| mlir::Value op2 = decl.getReductionRegion().front().getArgument(1); | |
| genCombinerCB(builder, loc, type, op1, op2, isByRef); | |
| if (isByRef && fir::isa_box_type(valTy)) { | |
| mlir::Region &dataPtrPtrRegion = decl.getDataPtrPtrRegion(); | |
| mlir::Block &dataAddrBlock = *builder.createBlock( | |
| &dataPtrPtrRegion, dataPtrPtrRegion.end(), {type}, {loc}); | |
| builder.setInsertionPointToEnd(&dataAddrBlock); | |
| mlir::Value boxRefOperand = dataAddrBlock.getArgument(0); | |
| mlir::Value baseAddrOffset = fir::BoxOffsetOp::create( | |
| builder, loc, boxRefOperand, fir::BoxFieldAttr::base_addr); | |
| genYield<DeclareRedType>(builder, loc, baseAddrOffset); | |
| } | |
| return decl; | |
| } | |
| template <typename OpType> | |
| OpType ReductionProcessor::createDeclareReduction( | |
| AbstractConverter &converter, llvm::StringRef reductionOpName, | |
| const ReductionIdentifier redId, mlir::Type type, mlir::Location loc, | |
| bool isByRef) { | |
| auto genInitValueCB = [&](fir::FirOpBuilder &builder, mlir::Location loc, | |
| mlir::Type type, mlir::Value /*moldArg*/, | |
| mlir::Value /*privArg*/) { | |
| mlir::Type ty = fir::unwrapRefType(type); | |
| mlir::Value initValue = ReductionProcessor::getReductionInitValue( | |
| loc, unwrapSeqOrBoxedType(ty), redId, builder); | |
| return initValue; | |
| }; | |
| auto genCombinerCB = [&](fir::FirOpBuilder &builder, mlir::Location loc, | |
| mlir::Type type, mlir::Value op1, mlir::Value op2, | |
| bool isByRef) { | |
| genCombiner<OpType>(builder, loc, redId, type, op1, op2, isByRef); | |
| }; | |
| return createDeclareReductionHelper<OpType>(converter, reductionOpName, type, | |
| loc, isByRef, genCombinerCB, | |
| genInitValueCB); | |
| } | |
| bool ReductionProcessor::doReductionByRef(mlir::Type reductionType) { | |
| if (forceByrefReduction) | |
| return true; | |
| // Non-trivial, non-derived types (e.g., boxes, arrays) must be by-ref. | |
| // Derived types must also be by-ref because user-defined combiners | |
| // operate on components via side-effects, not by producing a whole value. | |
| if (!fir::isa_trivial(fir::unwrapRefType(reductionType))) | |
| return true; | |
| return false; | |
| } | |
| bool ReductionProcessor::doReductionByRef(mlir::Value reductionVar) { | |
| if (forceByrefReduction) | |
| return true; | |
| if (auto declare = | |
| mlir::dyn_cast<hlfir::DeclareOp>(reductionVar.getDefiningOp())) | |
| reductionVar = declare.getMemref(); | |
| return doReductionByRef(reductionVar.getType()); | |
| } | |
| static bool baseInitReadsOrig(mlir::Block &initBlock) { | |
| bool reads = false; | |
| initBlock.walk([&](hlfir::DeclareOp declOp) { | |
| std::optional<llvm::StringRef> name = declOp.getUniqName(); | |
| if (name && *name == "omp_orig" && | |
| (!declOp.getResult(0).use_empty() || !declOp.getResult(1).use_empty())) | |
| reads = true; | |
| }); | |
| return reads; | |
| } | |
| // A single init value is broadcast to every array element, so an initializer | |
| // that evaluates a call (once, not per element) cannot be broadcast safely. | |
| static bool baseInitHasCall(mlir::Region ®ion) { | |
| bool hasCall = false; | |
| region.walk([&](mlir::Operation *op) { | |
| if (mlir::isa<mlir::CallOpInterface>(op)) | |
| hasCall = true; | |
| }); | |
| return hasCall; | |
| } | |
| template <typename OpType> | |
| static OpType getOrCreateBoxedUserReduction( | |
| lower::AbstractConverter &converter, semantics::SemanticsContext *semaCtx, | |
| const semantics::Symbol &redSym, mlir::Type redType, mlir::Location loc) { | |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); | |
| mlir::ModuleOp module = builder.getModule(); | |
| // Boxed op name encodes the full boxed type (..._byref_box_heap_i32), | |
| // distinct from the base element op. | |
| std::string boxedName = ReductionProcessor::getScopedUserReductionName( | |
| converter, redSym, redType, /*isByRef=*/true); | |
| if (auto existing = module.lookupSymbol<OpType>(boxedName)) | |
| return existing; | |
| // Base op is named from the scalar element type (identical for scalar and | |
| // array); materialize on demand for separate compilation. | |
| mlir::Type elementType = unwrapSeqOrBoxedType(fir::unwrapRefType(redType)); | |
| const bool baseByRef = ReductionProcessor::doReductionByRef(elementType); | |
| std::string baseName = ReductionProcessor::getScopedUserReductionName( | |
| converter, redSym, elementType, baseByRef); | |
| auto baseDecl = module.lookupSymbol<OpType>(baseName); | |
| if (!baseDecl && semaCtx && redSym.owner().symbol() && | |
| redSym.owner().symbol()->test(semantics::Symbol::Flag::ModFile)) { | |
| Fortran::lower::materializeUserReduction(converter, *semaCtx, redSym, | |
| baseName, elementType, baseByRef); | |
| baseDecl = module.lookupSymbol<OpType>(baseName); | |
| } | |
| if (!baseDecl) | |
| return {}; | |
| // Only a by-value base op can be wrapped (its init/combiner yield scalars). | |
| mlir::Region &baseInitRegion = baseDecl.getInitializerRegion(); | |
| mlir::Region &baseCombinerRegion = baseDecl.getReductionRegion(); | |
| mlir::Block &baseInitBlock = baseInitRegion.front(); | |
| if (fir::isa_ref_type(baseInitBlock.getArgument(0).getType()) || | |
| fir::isa_ref_type(baseCombinerRegion.front().getArgument(0).getType())) | |
| return {}; | |
| const bool initReadsOrig = baseInitReadsOrig(baseInitBlock); | |
| // Clone the region's entry block (up to its terminator) mapping block args to | |
| // \p args; return the yielded value. | |
| auto cloneBody = [](fir::FirOpBuilder &b, mlir::Region ®ion, | |
| mlir::ValueRange args) -> mlir::Value { | |
| mlir::Block &block = region.front(); | |
| mlir::IRMapping mapper; | |
| for (auto [blockArg, val] : llvm::zip(block.getArguments(), args)) | |
| mapper.map(blockArg, val); | |
| for (mlir::Operation &op : block.without_terminator()) | |
| b.clone(op, mapper); | |
| mlir::Operation *term = block.getTerminator(); | |
| if (term && term->getNumOperands() > 0) | |
| return mapper.lookupOrDefault(term->getOperand(0)); | |
| return {}; | |
| }; | |
| auto genInitValueCB = [&](fir::FirOpBuilder &b, mlir::Location loc, | |
| mlir::Type ty, mlir::Value moldArg, | |
| mlir::Value /*privArg*/) -> mlir::Value { | |
| mlir::Type scalarTy = unwrapSeqOrBoxedType(ty); | |
| auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(fir::unwrapRefType(ty)); | |
| auto seqTy = boxTy ? mlir::dyn_cast_or_null<fir::SequenceType>( | |
| fir::unwrapRefType(boxTy.getEleTy())) | |
| : nullptr; | |
| // An array init value is broadcast to every element, so it must be a | |
| // constant identity; reading omp_orig or evaluating a call cannot be | |
| // broadcast. | |
| if (seqTy && (initReadsOrig || baseInitHasCall(baseInitRegion))) | |
| TODO(loc, | |
| "OpenMP user-defined reduction on an allocatable/pointer array " | |
| "whose initializer reads omp_orig or evaluates a function call"); | |
| llvm::SmallVector<mlir::Value> initArgs; | |
| if (initReadsOrig) { | |
| // Scalar (arrays handled above): feed the original element to omp_orig. | |
| mlir::Value box = fir::LoadOp::create(b, loc, moldArg); | |
| mlir::Value addr = fir::BoxAddrOp::create(b, loc, box); | |
| initArgs.push_back(fir::LoadOp::create(b, loc, addr)); | |
| } else { | |
| // orig-independent: a placeholder feeds the base's dead temporary stores. | |
| initArgs.push_back(fir::UndefOp::create(b, loc, scalarTy)); | |
| } | |
| return cloneBody(b, baseInitRegion, initArgs); | |
| }; | |
| auto genCombinerCB = [&](fir::FirOpBuilder &b, mlir::Location loc, | |
| mlir::Type type, mlir::Value op1, mlir::Value op2, | |
| bool /*isByRef*/) { | |
| auto boxTy = mlir::dyn_cast<fir::BaseBoxType>(fir::unwrapRefType(type)); | |
| if (!boxTy) { | |
| TODO(loc, "OpenMP user-defined reduction on unsupported boxed type"); | |
| return; | |
| } | |
| auto seqTy = mlir::dyn_cast_or_null<fir::SequenceType>( | |
| fir::unwrapRefType(boxTy.getEleTy())); | |
| mlir::Value lhsBox = fir::LoadOp::create(b, loc, op1); | |
| mlir::Value rhsBox = fir::LoadOp::create(b, loc, op2); | |
| if (!seqTy) { | |
| // Scalar allocatable/pointer: combine the single element in place. | |
| mlir::Value lhsAddr = fir::BoxAddrOp::create(b, loc, lhsBox); | |
| mlir::Value rhsAddr = fir::BoxAddrOp::create(b, loc, rhsBox); | |
| mlir::Value lhsEle = fir::LoadOp::create(b, loc, lhsAddr); | |
| mlir::Value rhsEle = fir::LoadOp::create(b, loc, rhsAddr); | |
| if (mlir::Value res = cloneBody(b, baseCombinerRegion, {lhsEle, rhsEle})) | |
| fir::StoreOp::create(b, loc, res, lhsAddr); | |
| } else { | |
| // Array allocatable/pointer: apply the scalar combiner element-wise. | |
| fir::ShapeShiftOp shapeShift = | |
| getShapeShift(b, loc, lhsBox, | |
| /*cannotHaveNonDefaultLowerBounds=*/false, | |
| /*useDefaultLowerBounds=*/true); | |
| hlfir::LoopNest nest = hlfir::genLoopNest(loc, b, shapeShift.getExtents(), | |
| /*isUnordered=*/true); | |
| b.setInsertionPointToStart(nest.body); | |
| mlir::Type eleTy = seqTy.getEleTy(); | |
| mlir::Type refTy = | |
| fir::ReferenceType::get(eleTy, fir::isa_volatile_type(eleTy)); | |
| auto lhsEleAddr = fir::ArrayCoorOp::create( | |
| b, loc, refTy, lhsBox, shapeShift, /*slice=*/mlir::Value{}, | |
| nest.oneBasedIndices, /*typeparms=*/mlir::ValueRange{}); | |
| auto rhsEleAddr = fir::ArrayCoorOp::create( | |
| b, loc, refTy, rhsBox, shapeShift, /*slice=*/mlir::Value{}, | |
| nest.oneBasedIndices, /*typeparms=*/mlir::ValueRange{}); | |
| mlir::Value lhsEle = fir::LoadOp::create(b, loc, lhsEleAddr); | |
| mlir::Value rhsEle = fir::LoadOp::create(b, loc, rhsEleAddr); | |
| if (mlir::Value res = cloneBody(b, baseCombinerRegion, {lhsEle, rhsEle})) | |
| fir::StoreOp::create(b, loc, res, lhsEleAddr); | |
| b.setInsertionPointAfter(nest.outerOp); | |
| } | |
| genYield<OpType>(b, loc, op1); | |
| }; | |
| return ReductionProcessor::createDeclareReductionHelper<OpType>( | |
| converter, boxedName, redType, loc, /*isByRef=*/true, genCombinerCB, | |
| genInitValueCB, /*sym=*/nullptr); | |
| } | |
| template <typename OpType, typename RedOperatorListTy> | |
| bool ReductionProcessor::processReductionArguments( | |
| mlir::Location currentLocation, lower::AbstractConverter &converter, | |
| const RedOperatorListTy &redOperatorList, | |
| llvm::SmallVectorImpl<mlir::Value> &reductionVars, | |
| llvm::SmallVectorImpl<bool> &reduceVarByRef, | |
| llvm::SmallVectorImpl<mlir::Attribute> &reductionDeclSymbols, | |
| const llvm::SmallVectorImpl<const semantics::Symbol *> &reductionSymbols, | |
| semantics::SemanticsContext *semaCtx, | |
| llvm::DenseMap<const semantics::Symbol *, mlir::Value> *reductionVarCache) { | |
| fir::FirOpBuilder &builder = converter.getFirOpBuilder(); | |
| if constexpr (std::is_same_v<RedOperatorListTy, | |
| omp::clause::ReductionOperatorList>) { | |
| // For OpenMP reduction clauses, check if the reduction operator is | |
| // supported. | |
| assert(redOperatorList.size() == 1 && "Expecting single operator"); | |
| const Fortran::lower::omp::clause::ReductionOperator &redOperator = | |
| redOperatorList.front(); | |
| if (!std::holds_alternative<omp::clause::DefinedOperator>(redOperator.u)) { | |
| // A named (procedure-designator) reduction, the only other alternative. | |
| // Defer validation to the per-variable loop below: a named reduction's op | |
| // is named per the variable's type (getScopedUserReductionName appends | |
| // the type suffix), so its existence and type can only be checked once | |
| // the variable type is known. The loop resolves the symbol, confirms its | |
| // ultimate has UserReductionDetails, looks the op up by the type-specific | |
| // name, and emits a clean TODO if missing or type-mismatched. | |
| assert(std::holds_alternative<omp::clause::ProcedureDesignator>( | |
| redOperator.u) && | |
| "ReductionIdentifier variant has only DefinedOperator and " | |
| "ProcedureDesignator"); | |
| } | |
| } | |
| // Reduction variable processing common to both intrinsic operators and | |
| // procedure designators | |
| mlir::OpBuilder::InsertPoint dcIP; | |
| constexpr bool isDoConcurrent = | |
| std::is_same_v<OpType, fir::DeclareReductionOp>; | |
| if (isDoConcurrent) { | |
| dcIP = builder.saveInsertionPoint(); | |
| builder.setInsertionPoint( | |
| builder.getRegion().getParentOfType<fir::DoConcurrentOp>()); | |
| } | |
| for (const semantics::Symbol *symbol : reductionSymbols) { | |
| // If a cached reduction variable exists for this symbol, reuse it. | |
| // This ensures that composite constructs (e.g. DO SIMD) where both | |
| // the outer wrapper (wsloop) and inner wrapper (simd) process the same | |
| // reduction clause share the same SSA value, enabling genLoopVars()'s | |
| // IRMapping to correctly remap inner wrapper operands to outer wrapper | |
| // block arguments. | |
| if (reductionVarCache) { | |
| auto it = reductionVarCache->find(symbol); | |
| if (it != reductionVarCache->end()) { | |
| reductionVars.push_back(it->second); | |
| reduceVarByRef.push_back(doReductionByRef(it->second)); | |
| continue; | |
| } | |
| } | |
| mlir::Value symVal = converter.getSymbolAddress(*symbol); | |
| if (auto declOp = symVal.getDefiningOp<hlfir::DeclareOp>()) | |
| symVal = declOp.getBase(); | |
| mlir::Type eleType; | |
| auto refType = mlir::dyn_cast_or_null<fir::ReferenceType>(symVal.getType()); | |
| if (refType) | |
| eleType = refType.getEleTy(); | |
| else | |
| eleType = symVal.getType(); | |
| // all arrays must be boxed so that we have convenient access to all the | |
| // information needed to iterate over the array | |
| if (mlir::isa<fir::SequenceType>(eleType)) { | |
| // For Host associated symbols, use `SymbolBox` instead | |
| lower::SymbolBox symBox = converter.lookupOneLevelUpSymbol(*symbol); | |
| hlfir::Entity entity{symBox.getAddr()}; | |
| entity = genVariableBox(currentLocation, builder, entity); | |
| mlir::Value box = entity.getBase(); | |
| // Always pass the box by reference so that the OpenMP dialect | |
| // verifiers don't need to know anything about fir.box | |
| auto alloca = | |
| fir::AllocaOp::create(builder, currentLocation, box.getType()); | |
| fir::StoreOp::create(builder, currentLocation, box, alloca); | |
| symVal = alloca; | |
| } else if (mlir::isa<fir::BaseBoxType>(symVal.getType())) { | |
| // boxed arrays are passed as values not by reference. Unfortunately, | |
| // we can't pass a box by value to omp.redution_declare, so turn it | |
| // into a reference | |
| auto oldIP = builder.saveInsertionPoint(); | |
| builder.setInsertionPointToStart(builder.getAllocaBlock()); | |
| auto alloca = | |
| fir::AllocaOp::create(builder, currentLocation, symVal.getType()); | |
| builder.restoreInsertionPoint(oldIP); | |
| fir::StoreOp::create(builder, currentLocation, symVal, alloca); | |
| symVal = alloca; | |
| } | |
| // this isn't the same as the by-val and by-ref passing later in the | |
| // pipeline. Both styles assume that the variable is a reference at | |
| // this point | |
| assert(fir::isa_ref_type(symVal.getType()) && | |
| "reduction input var is passed by reference"); | |
| mlir::Type elementType = fir::dyn_cast_ptrEleTy(symVal.getType()); | |
| const bool symIsVolatile = fir::isa_volatile_type(symVal.getType()); | |
| mlir::Type refTy = fir::ReferenceType::get(elementType, symIsVolatile); | |
| reductionVars.push_back( | |
| builder.createConvert(currentLocation, refTy, symVal)); | |
| reduceVarByRef.push_back(doReductionByRef(reductionVars.back())); | |
| // Cache the final SSA value for this symbol so that subsequent calls | |
| // (e.g. for the inner wrapper in a composite construct) reuse it. | |
| if (reductionVarCache) | |
| reductionVarCache->try_emplace(symbol, reductionVars.back()); | |
| } | |
| unsigned idx = 0; | |
| for (auto [symVal, isByRef] : llvm::zip(reductionVars, reduceVarByRef)) { | |
| auto redType = mlir::cast<fir::ReferenceType>(symVal.getType()); | |
| const auto &kindMap = builder.getKindMap(); | |
| std::string reductionName; | |
| ReductionIdentifier redId; | |
| if constexpr (std::is_same_v<RedOperatorListTy, | |
| omp::clause::ReductionOperatorList>) { | |
| const Fortran::lower::omp::clause::ReductionOperator &redOperator = | |
| redOperatorList.front(); | |
| // Name user-defined reduction ops from the canonical element type, not | |
| // the raw lowered variable type. An allocatable/pointer variable lowers | |
| // to a boxed reference (!fir.ref<!fir.box<!fir.heap<!fir.char<1>>>>), but | |
| // the directive names its op from the declared element type | |
| // (!fir.char<1>); getReductionName unwraps only one reference level, so | |
| // without stripping the ref/box/heap/pointer wrappers the by-ref suffix | |
| // diverges and a valid allocatable-character reduction is wrongly | |
| // rejected. Keep any array fir::SequenceType. Only naming and the type | |
| // check use namingType; redType stays intact for op binding. | |
| mlir::Type namingType = | |
| fir::unwrapPassByRefType(fir::unwrapRefType(redType)); | |
| // An allocatable/pointer reduction of a trivial element type is a case | |
| // the directive does not materialize: it emits the scalar by-value op, | |
| // and binding the boxed by-ref operand to it is invalid IR. In the | |
| // default mode the by-ref name suffix diverges (boxed clause by-ref, | |
| // scalar op by-value), the lookup misses, and it is a clean TODO. Under | |
| // -mmlir | |
| // --force-byref-reduction both sides are forced by-ref, the names match, | |
| // and the element-only type check (i32 == i32) would bind the box to the | |
| // scalar op. Guard on the inherent triviality of the element type, not | |
| // doReductionByRef (which the flag forces), so it is a clean TODO in | |
| // every mode; boxed character and derived reductions (genuinely by-ref) | |
| // still bind. Deferred to flang PR #186765. | |
| const bool isBoxedTrivialReduction = | |
| mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(redType)) && | |
| fir::isa_trivial(namingType); | |
| // Like isBoxedTrivialReduction but gated on the trivial *element* type, | |
| // so array allocatables/pointers (whose namingType is a non-trivial | |
| // sequence) are also wrapped around the scalar base op (#186765). | |
| mlir::Type boxedElementType = | |
| unwrapSeqOrBoxedType(fir::unwrapRefType(redType)); | |
| const bool isBoxedTrivialElemReduction = | |
| mlir::isa<fir::BaseBoxType>(fir::unwrapRefType(redType)) && | |
| fir::isa_trivial(boxedElementType); | |
| if (const auto &redDefinedOp = | |
| std::get_if<omp::clause::DefinedOperator>(&redOperator.u)) { | |
| if (const auto *definedOpName = | |
| std::get_if<omp::clause::DefinedOperator::DefinedOpName>( | |
| &redDefinedOp->u)) { | |
| // User-defined operator reduction (e.g. reduction(.myop.:x)). Resolve | |
| // the use-site operator to the source reduction symbol as the OpenMP | |
| // semantic checks do (resolving from the clause scope, so it follows | |
| // USE and host association, operator renames, private visibility, and | |
| // merged generics), then reference the | |
| // omp.declare_reduction op named from that symbol's scoped (name, | |
| // owner). Reductions in different source modules have distinct owner | |
| // scopes, so a same-spelling/same-type operator reduction imported | |
| // from two modules yields two distinct ops, each clause binding its | |
| // own combiner instead of colliding onto one. The variable's type | |
| // selects the matching per-type op name (getScopedUserReductionName | |
| // appends the type), so a multiple-declaration/multiple-type operator | |
| // is handled (one op per type). An operator with no reduction for the | |
| // variable's type, or a variable with no type, is a clean TODO rather | |
| // than a crash or a wrong binding. | |
| const semantics::Symbol *opSym = definedOpName->v.sym(); | |
| const semantics::DeclTypeSpec *varType = | |
| reductionSymbols[idx]->GetUltimate().GetType(); | |
| // The variable's type disambiguates an operator that carries | |
| // reductions for several types (e.g. a generic merged from multiple | |
| // single-type modules): the resolver returns only a reduction that | |
| // supports varType. | |
| const semantics::Symbol *resolvedSym = | |
| semantics::omp::FindOperatorUserReductionSymbol( | |
| converter.getCurrentScope(), *opSym, varType); | |
| // resolvedSym is the found symbol, which may be a USE-associated | |
| // wrapper; take its ultimate before reading the reduction details. | |
| const semantics::Symbol *ultimate = | |
| resolvedSym ? &resolvedSym->GetUltimate() : nullptr; | |
| const semantics::UserReductionDetails *userDetails = | |
| ultimate ? ultimate->detailsIf<semantics::UserReductionDetails>() | |
| : nullptr; | |
| if (!varType || !resolvedSym || !userDetails) { | |
| TODO(currentLocation, | |
| "OpenMP user-defined operator reduction is not yet supported " | |
| "when the variable has no type or no matching reduction is " | |
| "visible for its type"); | |
| } | |
| std::string opName = ReductionProcessor::getScopedUserReductionName( | |
| converter, *resolvedSym, namingType, isByRef); | |
| mlir::ModuleOp module = builder.getModule(); | |
| auto existingDecl = module.lookupSymbol<OpType>(opName); | |
| // Separate compilation: the primary pass lowers only this TU's own | |
| // declare reductions, so an imported reduction's op is absent here. | |
| // Materialize it on demand from the resolved symbol when its defining | |
| // module is a mod file, then re-look it up. A same-file op already | |
| // exists, so the ModFile gate keeps this separate-compilation only. | |
| if (!existingDecl && semaCtx && ultimate->owner().symbol() && | |
| ultimate->owner().symbol()->test( | |
| semantics::Symbol::Flag::ModFile)) { | |
| Fortran::lower::materializeUserReduction( | |
| converter, *semaCtx, *ultimate, opName, namingType, isByRef); | |
| existingDecl = module.lookupSymbol<OpType>(opName); | |
| } | |
| // The MLIR verifier does not type-check these ops (they have no | |
| // atomic region), so this is the only guard against binding a | |
| // mismatched declaration. Compare unwrapped element types: namingType | |
| // is the canonical reduction element type (allocatable/pointer | |
| // storage wrappers stripped), matching the type the op was named and | |
| // created from on the directive side. | |
| if (isBoxedTrivialElemReduction) { | |
| // Trivial-element allocatable/pointer: synthesize the boxed op. | |
| if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>( | |
| converter, semaCtx, *ultimate, redType, currentLocation)) { | |
| reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get( | |
| builder.getContext(), boxedDecl.getSymName())); | |
| ++idx; | |
| continue; | |
| } | |
| } | |
| if (!existingDecl || | |
| fir::unwrapRefType(existingDecl.getType()) != | |
| fir::unwrapRefType(namingType) || | |
| isBoxedTrivialReduction) { | |
| TODO(currentLocation, | |
| "OpenMP user-defined operator reduction declaration was not " | |
| "materialized for this type"); | |
| } | |
| reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get( | |
| builder.getContext(), existingDecl.getSymName())); | |
| ++idx; | |
| continue; | |
| } | |
| const auto &intrinsicOp{ | |
| std::get<omp::clause::DefinedOperator::IntrinsicOperator>( | |
| redDefinedOp->u)}; | |
| redId = getReductionType(intrinsicOp); | |
| switch (redId) { | |
| case ReductionIdentifier::ADD: | |
| case ReductionIdentifier::MULTIPLY: | |
| case ReductionIdentifier::AND: | |
| case ReductionIdentifier::EQV: | |
| case ReductionIdentifier::OR: | |
| case ReductionIdentifier::NEQV: | |
| break; | |
| default: | |
| TODO(currentLocation, | |
| "Reduction of some intrinsic operators is not supported"); | |
| break; | |
| } | |
| reductionName = getReductionName(redId, kindMap, redType, isByRef); | |
| // An intrinsic-operator USER reduction (declare reduction(+:t)) is | |
| // scoped by its owning scope, exactly like the defined-operator, | |
| // named, and named-shadowing paths, so two user declarations for the | |
| // same (operator, type) in different scopes get distinct ops instead of | |
| // colliding on the one global builtin name (a silent miscompile: | |
| // whichever lowers first wins the shared name and the other clause | |
| // binds the wrong combiner). This path is the lone unscoped one only | |
| // because the intrinsic-operator parse node carries no reduction | |
| // symbol; recover it by resolving from the current scope under the | |
| // operator's mangled name (MakeNameFromOperator, byte-identical to the | |
| // acceptance check in check-omp-structure). A resolved user reduction | |
| // is bound under its scoped name for ANY owner; an imported (mod-file) | |
| // one is materialized on demand first (a same-file one already exists | |
| // under the scoped name from the primary pass). Only when NO user | |
| // reduction is visible is this a genuine builtin, which keeps the | |
| // global name. | |
| if (semaCtx) { | |
| const semantics::DeclTypeSpec *opVarType = | |
| reductionSymbols[idx]->GetUltimate().GetType(); | |
| parser::CharBlock mangledOpName = | |
| semantics::omp::MangledIntrinsicOperatorReductionName( | |
| toParserIntrinsicOperator(intrinsicOp), *semaCtx); | |
| if (const semantics::Symbol *userSym = | |
| semantics::omp::FindUserReductionSymbol( | |
| converter.getCurrentScope(), mangledOpName, opVarType)) { | |
| const semantics::Symbol &ultimate = userSym->GetUltimate(); | |
| std::string opName = ReductionProcessor::getScopedUserReductionName( | |
| converter, ultimate, namingType, isByRef); | |
| mlir::ModuleOp module = builder.getModule(); | |
| auto existingDecl = module.lookupSymbol<OpType>(opName); | |
| // Separate compilation: materialize the imported reduction on | |
| // demand when its defining module is a mod file, then re-look it up | |
| // (a same-file op already exists here under the scoped name). | |
| if (!existingDecl && ultimate.owner().symbol() && | |
| ultimate.owner().symbol()->test( | |
| semantics::Symbol::Flag::ModFile)) { | |
| Fortran::lower::materializeUserReduction( | |
| converter, *semaCtx, ultimate, opName, namingType, isByRef); | |
| existingDecl = module.lookupSymbol<OpType>(opName); | |
| } | |
| if (isBoxedTrivialElemReduction) { | |
| // Trivial-element allocatable/pointer: synthesize the boxed op. | |
| if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>( | |
| converter, semaCtx, ultimate, redType, currentLocation)) { | |
| reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get( | |
| builder.getContext(), boxedDecl.getSymName())); | |
| ++idx; | |
| continue; | |
| } | |
| } | |
| if (!existingDecl || | |
| fir::unwrapRefType(existingDecl.getType()) != | |
| fir::unwrapRefType(namingType) || | |
| isBoxedTrivialReduction) { | |
| TODO( | |
| currentLocation, | |
| "OpenMP user-defined intrinsic-operator reduction is not yet " | |
| "lowered for this variable's shape (an unsupported element " | |
| "type, a combiner-in-clause form, or an allocatable/pointer " | |
| "trivial reduction; the last deferred to #186765)"); | |
| } | |
| reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get( | |
| builder.getContext(), existingDecl.getSymName())); | |
| ++idx; | |
| continue; | |
| } | |
| } | |
| // No user reduction is visible: a genuine builtin intrinsic reduction. | |
| // Reuse an existing op of the same global name if present; otherwise | |
| // fall through to createDeclareReduction below. | |
| if (auto existingDecl = | |
| builder.getModule().lookupSymbol<OpType>(reductionName)) { | |
| reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get( | |
| builder.getContext(), existingDecl.getSymName())); | |
| ++idx; | |
| continue; | |
| } | |
| } else if (const auto *reductionIntrinsic = | |
| std::get_if<omp::clause::ProcedureDesignator>( | |
| &redOperator.u)) { | |
| if (!ReductionProcessor::supportedIntrinsicProcReduction( | |
| *reductionIntrinsic)) { | |
| // A user-defined named reduction (declare reduction(myred: ...)). The | |
| // clause references the (possibly USE-associated) reduction symbol; | |
| // bind its pre-materialized omp.declare_reduction op instead of | |
| // generating a new one. | |
| semantics::Symbol *sym = reductionIntrinsic->v.sym(); | |
| if (!sym->GetUltimate() | |
| .detailsIf<semantics::UserReductionDetails>()) { | |
| // Not a supported intrinsic proc (checked just above) and not a | |
| // user-defined reduction: a clean TODO, never a wrong binding. (A | |
| // named reduction reusing an intrinsic spelling such as `max` is | |
| // handled by the supportedIntrinsicProcReduction branch, not here.) | |
| // | |
| // For well-formed input this branch is unreachable: | |
| // CheckReductionOperator::visitDesignator (Semantics/ | |
| // check-omp-structure.cpp) accepts a procedure-designator reduction | |
| // identifier only if its name is one of {max,min,iand,ior,ieor} or | |
| // it carries UserReductionDetails, and | |
| // supportedIntrinsicProcReduction above returns true for exactly | |
| // that same {max,min,iand,ior,ieor} set. The one gap: semantics | |
| // accepts by NAME while this path checks the INTRINSIC attribute, | |
| // so a user procedure that shadows an intrinsic reduction name | |
| // (with no declare reduction) can reach here. Keep a TODO rather | |
| // than emitFatalError to stay safe for that case. | |
| TODO(currentLocation, "Lowering unrecognised reduction type"); | |
| } | |
| // Name the op from the resolved symbol's scoped ultimate (name, | |
| // owner) plus the per-type suffix, byte-identical to the | |
| // directive/materializer side, via getScopedUserReductionName. | |
| // Reductions of the same spelling in different scopes refer to | |
| // distinct ops, and a named reduction listing several types binds the | |
| // op for the variable's type instead of colliding on the first type's | |
| // op. namingType is the canonical element type (storage wrappers | |
| // stripped) so the by-ref suffix matches the directive. | |
| std::string reductionName = | |
| ReductionProcessor::getScopedUserReductionName( | |
| converter, *sym, namingType, isByRef); | |
| mlir::ModuleOp module = builder.getModule(); | |
| auto existingDecl = module.lookupSymbol<OpType>(reductionName); | |
| // Separate compilation: materialize an imported named reduction's op | |
| // on demand from the resolved symbol when its defining module is a | |
| // mod file, then re-look it up (same-file ops already exist here). | |
| const semantics::Symbol &namedUltimate = sym->GetUltimate(); | |
| if (!existingDecl && semaCtx && namedUltimate.owner().symbol() && | |
| namedUltimate.owner().symbol()->test( | |
| semantics::Symbol::Flag::ModFile)) { | |
| Fortran::lower::materializeUserReduction( | |
| converter, *semaCtx, namedUltimate, reductionName, namingType, | |
| isByRef); | |
| existingDecl = module.lookupSymbol<OpType>(reductionName); | |
| } | |
| // The MLIR verifier does not type-check these ops, so this is the | |
| // only guard against binding a missing or mismatched declaration | |
| // (e.g. a named declare reduction that does not list the variable's | |
| // type). Compare unwrapped element types against namingType, the type | |
| // the op was named and created from. The guard fires when lowering | |
| // does not (yet) materialize an op for this variable's shape: | |
| // - !existingDecl or a type mismatch: an imported reduction whose | |
| // shape the materializer intentionally skips (a | |
| // combiner-in-clause form, or an unsupported element type), so no | |
| // op was created; better an error here than binding a wrong op. | |
| // - isBoxedTrivialReduction: an allocatable/pointer reduction of a | |
| // trivial element type, deferred to #186765. | |
| if (isBoxedTrivialElemReduction) { | |
| // Trivial-element allocatable/pointer: synthesize the boxed op. | |
| if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>( | |
| converter, semaCtx, namedUltimate, redType, | |
| currentLocation)) { | |
| reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get( | |
| builder.getContext(), boxedDecl.getSymName())); | |
| ++idx; | |
| continue; | |
| } | |
| } | |
| if (!existingDecl || | |
| fir::unwrapRefType(existingDecl.getType()) != | |
| fir::unwrapRefType(namingType) || | |
| isBoxedTrivialReduction) { | |
| TODO(currentLocation, | |
| "OpenMP user-defined named reduction is not yet lowered for " | |
| "this variable's shape (an unsupported element type, a " | |
| "combiner-in-clause form, or an allocatable/pointer trivial " | |
| "reduction; the last deferred to #186765)"); | |
| } | |
| reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get( | |
| builder.getContext(), existingDecl.getSymName())); | |
| ++idx; | |
| continue; | |
| } | |
| // A user-defined reduction may shadow a built-in intrinsic reduction | |
| // of the same name (max/min/iand/ior/ieor). Per the OpenMP spec, such | |
| // reduction has the same visibility as a variable declared at the same | |
| // location, so a visible declaration takes precedence over the | |
| // intrinsic. Semantics names it "op<name>" (MangleSpecialFunctions in | |
| // resolve-names). If one is visible in the current scope and supports | |
| // the variable's type, bind to the omp.declare_reduction op the | |
| // directive materialized for it instead of generating the intrinsic. | |
| const semantics::DeclTypeSpec *varType = | |
| reductionSymbols[idx]->GetUltimate().GetType(); | |
| if (const semantics::Symbol *userReduction = | |
| findUserDefinedReductionForIntrinsic( | |
| converter.getCurrentScope(), *reductionIntrinsic, | |
| varType)) { | |
| const semantics::Symbol &ultimate = *userReduction; | |
| // A user-defined reduction shadows the intrinsic only for the types | |
| // it is declared for. If it does not cover this variable's type, the | |
| // user has not redefined the reduction for that type and the | |
| // implicit intrinsic reduction still applies, so fall through to it. | |
| // The user declaration takes precedence over the intrinsic for this | |
| // type. A declaration listing several types (or several merged | |
| // declarations) is handled the same way as the operator and named | |
| // paths: the directive emits one op per type and the variable's | |
| // type selects the matching per-type name below. A USE-associated | |
| // shadowing reduction is found by FindSymbol as a use wrapper; | |
| // naming from its ultimate (name, owner) below binds the source | |
| // module's op, materialized on demand here for separate | |
| // compilation, exactly as the named path does. A renamed shadowing | |
| // intrinsic does not reach here: the renamed name resolves to the | |
| // intrinsic rather than the user reduction, so semantics rejects | |
| // the clause with a type-incompatibility error before lowering. | |
| std::string opName = ReductionProcessor::getScopedUserReductionName( | |
| converter, ultimate, namingType, isByRef); | |
| mlir::ModuleOp module = builder.getModule(); | |
| auto existingDecl = module.lookupSymbol<OpType>(opName); | |
| // Separate compilation: materialize the imported shadowing | |
| // reduction on demand when its defining module is a mod file, then | |
| // re-look it up (same-file ops already exist here). | |
| if (!existingDecl && semaCtx && ultimate.owner().symbol() && | |
| ultimate.owner().symbol()->test( | |
| semantics::Symbol::Flag::ModFile)) { | |
| Fortran::lower::materializeUserReduction( | |
| converter, *semaCtx, ultimate, opName, namingType, isByRef); | |
| existingDecl = module.lookupSymbol<OpType>(opName); | |
| } | |
| if (isBoxedTrivialElemReduction) { | |
| // Trivial-element allocatable/pointer: synthesize the boxed op. | |
| if (OpType boxedDecl = getOrCreateBoxedUserReduction<OpType>( | |
| converter, semaCtx, ultimate, redType, currentLocation)) { | |
| reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get( | |
| builder.getContext(), boxedDecl.getSymName())); | |
| ++idx; | |
| continue; | |
| } | |
| } | |
| if (!existingDecl || | |
| fir::unwrapRefType(existingDecl.getType()) != | |
| fir::unwrapRefType(namingType) || | |
| isBoxedTrivialReduction) { | |
| TODO(currentLocation, | |
| "OpenMP user-defined reduction declaration was not " | |
| "materialized for this type"); | |
| } | |
| reductionDeclSymbols.push_back(mlir::SymbolRefAttr::get( | |
| builder.getContext(), existingDecl.getSymName())); | |
| ++idx; | |
| continue; | |
| } | |
| redId = getReductionType(*reductionIntrinsic); | |
| reductionName = | |
| getReductionName(getRealName(*reductionIntrinsic).ToString(), | |
| kindMap, redType, isByRef); | |
| } else { | |
| TODO(currentLocation, "Unexpected reduction type"); | |
| } | |
| } else { | |
| // `do concurrent` reductions | |
| redId = getReductionType(redOperatorList[idx]); | |
| reductionName = getReductionName(redId, kindMap, redType, isByRef); | |
| } | |
| OpType decl = createDeclareReduction<OpType>( | |
| converter, reductionName, redId, redType, currentLocation, isByRef); | |
| reductionDeclSymbols.push_back( | |
| mlir::SymbolRefAttr::get(builder.getContext(), decl.getSymName())); | |
| ++idx; | |
| } | |
| if (isDoConcurrent) | |
| builder.restoreInsertionPoint(dcIP); | |
| return true; | |
| } | |
| const semantics::SourceName | |
| ReductionProcessor::getRealName(const semantics::Symbol *symbol) { | |
| return symbol->GetUltimate().name(); | |
| } | |
| const semantics::SourceName | |
| ReductionProcessor::getRealName(const omp::clause::ProcedureDesignator &pd) { | |
| return getRealName(pd.v.sym()); | |
| } | |
| int ReductionProcessor::getOperationIdentity(ReductionIdentifier redId, | |
| mlir::Location loc) { | |
| switch (redId) { | |
| case ReductionIdentifier::ADD: | |
| case ReductionIdentifier::OR: | |
| case ReductionIdentifier::NEQV: | |
| return 0; | |
| case ReductionIdentifier::MULTIPLY: | |
| case ReductionIdentifier::AND: | |
| case ReductionIdentifier::EQV: | |
| return 1; | |
| default: | |
| TODO(loc, "Reduction of some intrinsic operators is not supported"); | |
| } | |
| } | |
| } // namespace omp | |
| } // namespace lower | |
| } // namespace Fortran |