[flang-commits] [flang] [flang] Inline hlfir.eoshift during HLFIR intrinsics simplication. (PR #153108)
Slava Zakharin via flang-commits
flang-commits at lists.llvm.org
Wed Aug 13 16:37:00 PDT 2025
================
@@ -1342,24 +1370,221 @@ class CShiftConversion : public mlir::OpRewritePattern<hlfir::CShiftOp> {
return builder.createConvert(loc, calcType, shiftVal);
}
- /// Convert \p cshift into an hlfir.elemental using
+ /// The indices computations for the array shifts are done using I64 type.
+ /// For CSHIFT, all computations do not overflow signed and unsigned I64.
+ /// For EOSHIFT, some computations may involve negative shift values,
+ /// so using no-unsigned wrap flag would be incorrect.
+ static void setArithOverflowFlags(Op op, fir::FirOpBuilder &builder) {
+ if constexpr (std::is_same_v<Op, hlfir::EOShiftOp>)
+ builder.setIntegerOverflowFlags(mlir::arith::IntegerOverflowFlags::nsw);
+ else
+ builder.setIntegerOverflowFlags(mlir::arith::IntegerOverflowFlags::nsw |
+ mlir::arith::IntegerOverflowFlags::nuw);
+ }
+
+ /// Return the element type of the EOSHIFT boundary that may be omitted
+ /// statically or dynamically. This element type might be used
+ /// to generate MLIR where we have to select between the default
+ /// boundary value and the dynamically absent/present boundary value.
+ /// If the boundary has a type not defined in Table 16.4 in 16.9.77
+ /// of F2023, then the return value is nullptr.
+ static mlir::Type getDefaultBoundaryValueType(mlir::Type elementType) {
+ // To be able to generate a "select" between the default boundary value
+ // and the dynamic boundary value, use BoxCharType for the CHARACTER
+ // cases. This might be a little bit inefficient, because we may
+ // create unnecessary tuples, but it simplifies the inlining code.
+ if (auto charTy = mlir::dyn_cast<fir::CharacterType>(elementType))
+ return fir::BoxCharType::get(charTy.getContext(), charTy.getFKind());
+
+ if (mlir::isa<fir::LogicalType>(elementType) ||
+ fir::isa_integer(elementType) || fir::isa_real(elementType) ||
+ fir::isa_complex(elementType))
+ return elementType;
+
+ return nullptr;
+ }
+
+ /// Generate the default boundary value as defined in Table 16.4 in 16.9.77
+ /// of F2023.
+ static mlir::Value genDefaultBoundary(mlir::Location loc,
+ fir::FirOpBuilder &builder,
+ mlir::Type elementType) {
+ assert(getDefaultBoundaryValueType(elementType) &&
+ "default boundary value cannot be computed for the given type");
+ if (mlir::isa<fir::CharacterType>(elementType)) {
+ // Create an empty CHARACTER of the same kind. The assignment
+ // of this empty CHARACTER into the result will add the padding
+ // if necessary.
+ fir::factory::CharacterExprHelper charHelper{builder, loc};
+ mlir::Value zeroLen = builder.createIntegerConstant(
+ loc, builder.getCharacterLengthType(), 0);
+ fir::CharBoxValue emptyCharTemp =
+ charHelper.createCharacterTemp(elementType, zeroLen);
+ return charHelper.createEmbox(emptyCharTemp);
+ }
+
+ return fir::factory::createZeroValue(builder, loc, elementType);
+ }
+
+ /// \p entity represents the boundary operand of hlfir.eoshift.
+ /// This method generates a scalar boundary value fetched
+ /// from the boundary entity using \p indices (which may be empty,
+ /// if the boundary operand is scalar).
+ static mlir::Value loadEoshiftVal(mlir::Location loc,
+ fir::FirOpBuilder &builder,
+ hlfir::Entity entity,
+ mlir::ValueRange indices = {}) {
+ hlfir::Entity boundaryVal =
+ hlfir::loadElementAt(loc, builder, entity, indices);
+
+ mlir::Type boundaryValTy =
+ getDefaultBoundaryValueType(entity.getFortranElementType());
+
+ // Boxed !fir.char<KIND,LEN> with known LEN are loaded
+ // as raw references to !fir.char<KIND,LEN>.
+ // We need to wrap them into the !fir.boxchar.
+ if (boundaryVal.isVariable() && boundaryValTy &&
+ mlir::isa<fir::BoxCharType>(boundaryValTy))
+ return hlfir::genVariableBoxChar(loc, builder, boundaryVal);
+ return boundaryVal;
+ }
+
+ /// This method generates a scalar boundary value for the given hlfir.eoshift
+ /// \p op that can be used to initialize cells of the result
+ /// if the scalar/array boundary operand is statically or dynamically
+ /// absent. The first result is the scalar boundary value. The second result
+ /// is a dynamic predicate indicating whether the scalar boundary value
+ /// should actually be used.
+ [[maybe_unused]] static std::pair<mlir::Value, mlir::Value>
+ genScalarBoundaryForEOShift(mlir::Location loc, fir::FirOpBuilder &builder,
+ hlfir::EOShiftOp op) {
+ hlfir::Entity array{op.getArray()};
+ mlir::Type elementType = array.getFortranElementType();
+
+ if (!op.getBoundary()) {
+ // Boundary operand is statically absent.
+ mlir::Value defaultVal = genDefaultBoundary(loc, builder, elementType);
+ mlir::Value boundaryIsScalarPred = builder.createBool(loc, true);
+ return {defaultVal, boundaryIsScalarPred};
+ }
+
+ hlfir::Entity boundary{op.getBoundary()};
+ mlir::Type boundaryValTy = getDefaultBoundaryValueType(elementType);
+
+ if (boundary.isScalar()) {
+ if (!boundaryValTy || !boundary.mayBeOptional()) {
+ // The boundary must be present.
+ mlir::Value boundaryVal = loadEoshiftVal(loc, builder, boundary);
+ mlir::Value boundaryIsScalarPred = builder.createBool(loc, true);
+ return {boundaryVal, boundaryIsScalarPred};
+ }
+
+ // Boundary is a scalar that may be dynamically absent.
+ // If boundary is not present dynamically, we must use the default
+ // value.
+ assert(mlir::isa<fir::BaseBoxType>(boundary.getType()));
+ mlir::Value isPresentPred =
+ fir::IsPresentOp::create(builder, loc, builder.getI1Type(), boundary);
+ mlir::Value boundaryVal =
+ builder
+ .genIfOp(loc, {boundaryValTy}, isPresentPred,
+ /*withElseRegion=*/true)
+ .genThen([&]() {
+ mlir::Value boundaryVal =
+ loadEoshiftVal(loc, builder, boundary);
+ fir::ResultOp::create(builder, loc, boundaryVal);
+ })
+ .genElse([&]() {
+ mlir::Value defaultVal =
+ genDefaultBoundary(loc, builder, elementType);
+ fir::ResultOp::create(builder, loc, defaultVal);
+ })
+ .getResults()[0];
+ mlir::Value boundaryIsScalarPred = builder.createBool(loc, true);
+ return {boundaryVal, boundaryIsScalarPred};
+ }
+ if (!boundaryValTy || !boundary.mayBeOptional()) {
+ // The boundary must be present
+ mlir::Value boundaryIsScalarPred = builder.createBool(loc, false);
+ return {nullptr, boundaryIsScalarPred};
+ }
+
+ // Boundary is an array that may be dynamically absent.
+ mlir::Value defaultVal = genDefaultBoundary(loc, builder, elementType);
+ mlir::Value isPresentPred =
+ fir::IsPresentOp::create(builder, loc, builder.getI1Type(), boundary);
+ // If the array is present, then boundaryIsScalarPred must be equal
+ // to false, otherwise, it should be true.
+ mlir::Value trueVal = builder.createBool(loc, true);
+ mlir::Value falseVal = builder.createBool(loc, false);
+ mlir::Value boundaryIsScalarPred = mlir::arith::SelectOp::create(
+ builder, loc, isPresentPred, falseVal, trueVal);
+ return {defaultVal, boundaryIsScalarPred};
+ }
+
+ /// Generate code that produces the final boundary value to be assigned
+ /// to the result of hlfir.eoshift \p op. \p precomputedScalarBoundary
+ /// specifies the scalar boundary value pre-computed before the elemental
+ /// or the assignment loop. If it is nullptr, then the boundary operand
+ /// of \p op must be a present array. \p boundaryIsScalarPred is a dynamic
+ /// predicate that is true, when the pre-computed scalar value must be used.
+ /// \p oneBasedIndices specify the indices to address into the boundary
+ /// array - they may be empty, if the boundary is scalar.
+ [[maybe_unused]] static mlir::Value selectBoundaryValue(
+ mlir::Location loc, fir::FirOpBuilder &builder, hlfir::EOShiftOp op,
+ mlir::Value precomputedScalarBoundary, mlir::Value boundaryIsScalarPred,
+ mlir::ValueRange oneBasedIndices) {
+ if (!op.getBoundary())
+ return precomputedScalarBoundary;
+
+ hlfir::Entity boundary{op.getBoundary()};
+ if (boundary.isScalar())
+ return precomputedScalarBoundary;
+
+ if (!precomputedScalarBoundary) {
+ // The array boundary must be present, so we just need to load
+ // the scalar boundary value.
+ return loadEoshiftVal(loc, builder, boundary, oneBasedIndices);
+ }
+
+ // The array boundary may be dynamically absent.
+ // In this case, precomputedScalarBoundary is a pre-computed scalar
+ // boundary value that has to be used if boundaryIsScalarPred
+ // is true, otherwise, the boundary value has to be loaded
+ // from the boundary array.
----------------
vzakhari wrote:
Sorry if the comments are confusing.
`precomputedScalarBoundary` actually encodes just one thing that is the pre-computed scalar boundary, if it is ever going to be needed later in the "kernel" code.
It is not null, when:
* The op's boundary is absent (implying that the data type allows absent boundary).
* The op's boundary is scalar (regardless of its dynamic presence).
* The op's boundary is an array that may be dynamically absent and the data type allows absent boundary.
Do you suggest adding a boolean that will be set to true only in the last case?
https://github.com/llvm/llvm-project/pull/153108
More information about the flang-commits
mailing list