diff --git a/flang/include/flang/Optimizer/Builder/HLFIRTools.h b/flang/include/flang/Optimizer/Builder/HLFIRTools.h index 0684ad0f926ec..8b1235b50cc6f 100644 --- a/flang/include/flang/Optimizer/Builder/HLFIRTools.h +++ b/flang/include/flang/Optimizer/Builder/HLFIRTools.h @@ -125,7 +125,7 @@ class Entity : public mlir::Value { bool isSimplyContiguous() const { // If this can be described without a fir.box in FIR, this must // be contiguous. - if (!hlfir::isBoxAddressOrValueType(getFirBase().getType())) + if (!hlfir::isBoxAddressOrValueType(getFirBase().getType()) || isScalar()) return true; // Otherwise, if this entity has a visible declaration in FIR, // or is the dereference of an allocatable or contiguous pointer @@ -150,10 +150,7 @@ class Entity : public mlir::Value { return base.getDefiningOp(); } - bool isOptional() const { - auto varIface = getIfVariableInterface(); - return varIface ? varIface.isOptional() : false; - } + bool mayBeOptional() const; bool isParameter() const { auto varIface = getIfVariableInterface(); @@ -210,7 +207,8 @@ class EntityWithAttributes : public Entity { using CleanupFunction = std::function; std::pair> translateToExtendedValue(mlir::Location loc, fir::FirOpBuilder &builder, - Entity entity, bool contiguousHint = false); + Entity entity, bool contiguousHint = false, + bool keepScalarOptionalBoxed = false); /// Function to translate FortranVariableOpInterface to fir::ExtendedValue. /// It may generates IR to unbox fir.boxchar, but has otherwise no side effects diff --git a/flang/lib/Optimizer/Builder/HLFIRTools.cpp b/flang/lib/Optimizer/Builder/HLFIRTools.cpp index f71adf123511d..8993065c2bb64 100644 --- a/flang/lib/Optimizer/Builder/HLFIRTools.cpp +++ b/flang/lib/Optimizer/Builder/HLFIRTools.cpp @@ -221,6 +221,25 @@ bool hlfir::Entity::mayHaveNonDefaultLowerBounds() const { return true; } +mlir::Operation *traverseConverts(mlir::Operation *op) { + while (auto convert = llvm::dyn_cast_or_null(op)) + op = convert.getValue().getDefiningOp(); + return op; +} + +bool hlfir::Entity::mayBeOptional() const { + if (!isVariable()) + return false; + // TODO: introduce a fir type to better identify optionals. + if (mlir::Operation *op = traverseConverts(getDefiningOp())) { + if (auto varIface = llvm::dyn_cast(op)) + return varIface.isOptional(); + return !llvm::isa(op); + } + return true; +} + fir::FortranVariableOpInterface hlfir::genDeclare(mlir::Location loc, fir::FirOpBuilder &builder, const fir::ExtendedValue &exv, llvm::StringRef name, @@ -963,9 +982,69 @@ llvm::SmallVector hlfir::genLoopNestWithReductions( return outerLoop->getResults(); } +template +static fir::ExtendedValue +conditionallyEvaluate(mlir::Location loc, fir::FirOpBuilder &builder, + mlir::Value condition, const Lambda &genIfTrue) { + mlir::OpBuilder::InsertPoint insertPt = builder.saveInsertionPoint(); + + // Evaluate in some region that will be moved into the actual ifOp (the actual + // ifOp can only be created when the result types are known). + auto badIfOp = builder.create(loc, condition.getType(), condition, + /*withElseRegion=*/false); + mlir::Block *preparationBlock = &badIfOp.getThenRegion().front(); + builder.setInsertionPointToStart(preparationBlock); + fir::ExtendedValue result = genIfTrue(); + fir::ResultOp resultOp = result.match( + [&](const fir::CharBoxValue &box) -> fir::ResultOp { + return builder.create( + loc, mlir::ValueRange{box.getAddr(), box.getLen()}); + }, + [&](const mlir::Value &addr) -> fir::ResultOp { + return builder.create(loc, addr); + }, + [&](const auto &) -> fir::ResultOp { + TODO(loc, "unboxing non scalar optional fir.box"); + }); + builder.restoreInsertionPoint(insertPt); + + // Create actual fir.if operation. + auto ifOp = + builder.create(loc, resultOp->getOperandTypes(), condition, + /*withElseRegion=*/true); + // Move evaluation into Then block, + preparationBlock->moveBefore(&ifOp.getThenRegion().back()); + ifOp.getThenRegion().back().erase(); + // Create absent result in the Else block. + builder.setInsertionPointToStart(&ifOp.getElseRegion().front()); + llvm::SmallVector absentValues; + for (mlir::Type resTy : ifOp->getResultTypes()) { + if (fir::isa_ref_type(resTy) || fir::isa_box_type(resTy)) + absentValues.emplace_back(builder.create(loc, resTy)); + else + absentValues.emplace_back(builder.create(loc, resTy)); + } + builder.create(loc, absentValues); + badIfOp->erase(); + + // Build fir::ExtendedValue from the result values. + builder.setInsertionPointAfter(ifOp); + return result.match( + [&](const fir::CharBoxValue &box) -> fir::ExtendedValue { + return fir::CharBoxValue{ifOp.getResult(0), ifOp.getResult(1)}; + }, + [&](const mlir::Value &) -> fir::ExtendedValue { + return ifOp.getResult(0); + }, + [&](const auto &) -> fir::ExtendedValue { + TODO(loc, "unboxing non scalar optional fir.box"); + }); +} + static fir::ExtendedValue translateVariableToExtendedValue( mlir::Location loc, fir::FirOpBuilder &builder, hlfir::Entity variable, - bool forceHlfirBase = false, bool contiguousHint = false) { + bool forceHlfirBase = false, bool contiguousHint = false, + bool keepScalarOptionalBoxed = false) { assert(variable.isVariable() && "must be a variable"); // When going towards FIR, use the original base value to avoid // introducing descriptors at runtime when they are not required. @@ -984,14 +1063,33 @@ static fir::ExtendedValue translateVariableToExtendedValue( const bool contiguous = variable.isSimplyContiguous() || contiguousHint; const bool isAssumedRank = variable.isAssumedRank(); if (!contiguous || variable.isPolymorphic() || - variable.isDerivedWithLengthParameters() || variable.isOptional() || - isAssumedRank) { + variable.isDerivedWithLengthParameters() || isAssumedRank) { llvm::SmallVector nonDefaultLbounds; if (!isAssumedRank) nonDefaultLbounds = getNonDefaultLowerBounds(loc, builder, variable); return fir::BoxValue(base, nonDefaultLbounds, getExplicitTypeParams(variable)); } + if (variable.mayBeOptional()) { + if (!keepScalarOptionalBoxed && variable.isScalar()) { + mlir::Value isPresent = builder.create( + loc, builder.getI1Type(), variable); + return conditionallyEvaluate( + loc, builder, isPresent, [&]() -> fir::ExtendedValue { + mlir::Value base = genVariableRawAddress(loc, builder, variable); + if (variable.isCharacter()) { + mlir::Value len = + genCharacterVariableLength(loc, builder, variable); + return fir::CharBoxValue{base, len}; + } + return base; + }); + } + llvm::SmallVector nonDefaultLbounds = + getNonDefaultLowerBounds(loc, builder, variable); + return fir::BoxValue(base, nonDefaultLbounds, + getExplicitTypeParams(variable)); + } // Otherwise, the variable can be represented in a fir::ExtendedValue // without the overhead of a fir.box. base = genVariableRawAddress(loc, builder, variable); @@ -1035,10 +1133,12 @@ hlfir::translateToExtendedValue(mlir::Location loc, fir::FirOpBuilder &builder, std::pair> hlfir::translateToExtendedValue(mlir::Location loc, fir::FirOpBuilder &builder, - hlfir::Entity entity, bool contiguousHint) { + hlfir::Entity entity, bool contiguousHint, + bool keepScalarOptionalBoxed) { if (entity.isVariable()) return {translateVariableToExtendedValue(loc, builder, entity, false, - contiguousHint), + contiguousHint, + keepScalarOptionalBoxed), std::nullopt}; if (entity.isProcedure()) { @@ -1094,7 +1194,9 @@ hlfir::convertToBox(mlir::Location loc, fir::FirOpBuilder &builder, if (entity.isProcedurePointer()) entity = hlfir::derefPointersAndAllocatables(loc, builder, entity); - auto [exv, cleanup] = translateToExtendedValue(loc, builder, entity); + auto [exv, cleanup] = + translateToExtendedValue(loc, builder, entity, /*contiguousHint=*/false, + /*keepScalarOptionalBoxed=*/true); // Procedure entities should not go through createBoxValue that embox // object entities. Return the fir.boxproc directly. if (entity.isProcedure()) diff --git a/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIRIntrinsics.cpp b/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIRIntrinsics.cpp index bd12700f13838..7c0fcba806869 100644 --- a/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIRIntrinsics.cpp +++ b/flang/lib/Optimizer/HLFIR/Transforms/LowerHLFIRIntrinsics.cpp @@ -121,8 +121,14 @@ class HlfirIntrinsicConversion : public mlir::OpRewritePattern { // simplified since the fir.box lowered here are now guarenteed to // contain the local lower bounds thanks to the hlfir.declare (the extra // rebox can be removed). - auto [exv, cleanup] = - hlfir::translateToExtendedValue(loc, builder, entity); + // When taking arguments as descriptors, the runtime expect absent + // OPTIONAL to be a nullptr to a descriptor, lowering has already + // prepared such descriptors as needed, hence set + // keepScalarOptionalBoxed to avoid building descriptors with a null + // address for them. + auto [exv, cleanup] = hlfir::translateToExtendedValue( + loc, builder, entity, /*contiguous=*/false, + /*keepScalarOptionalBoxed=*/true); if (cleanup) cleanupFns.push_back(*cleanup); ret.emplace_back(exv); diff --git a/flang/test/HLFIR/assign-codegen.fir b/flang/test/HLFIR/assign-codegen.fir index 581d1ab0e7739..7e03aa0bd464d 100644 --- a/flang/test/HLFIR/assign-codegen.fir +++ b/flang/test/HLFIR/assign-codegen.fir @@ -427,3 +427,57 @@ func.func @test_upoly_expr_assignment(%arg0: !fir.class> {fir // CHECK: } // CHECK: return // CHECK: } + +func.func @test_scalar_box(%arg0: f32, %arg1: !fir.box>) { + %x = fir.declare %arg1 {uniq_name = "x"} : (!fir.box>) -> !fir.box> + hlfir.assign %arg0 to %x : f32, !fir.box> + return +} +// CHECK-LABEL: func.func @test_scalar_box( +// CHECK-SAME: %[[VAL_0:.*]]: f32, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.box>) { +// CHECK: %[[VAL_2:.*]] = fir.declare %[[VAL_1]] {uniq_name = "x"} : (!fir.box>) -> !fir.box> +// CHECK: %[[VAL_3:.*]] = fir.box_addr %[[VAL_2]] : (!fir.box>) -> !fir.ptr +// CHECK: fir.store %[[VAL_0]] to %[[VAL_3]] : !fir.ptr + +func.func @test_scalar_opt_box(%arg0: f32, %arg1: !fir.box>) { + %x = fir.declare %arg1 {fortran_attrs = #fir.var_attrs, uniq_name = "x"} : (!fir.box>) -> !fir.box> + hlfir.assign %arg0 to %x : f32, !fir.box> + return +} +// CHECK-LABEL: func.func @test_scalar_opt_box( +// CHECK-SAME: %[[VAL_0:.*]]: f32, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.box>) { +// CHECK: %[[VAL_2:.*]] = fir.declare %[[VAL_1]] {fortran_attrs = #fir.var_attrs, uniq_name = "x"} : (!fir.box>) -> !fir.box> +// CHECK: %[[VAL_3:.*]] = fir.is_present %[[VAL_2]] : (!fir.box>) -> i1 +// CHECK: %[[VAL_4:.*]] = fir.if %[[VAL_3]] -> (!fir.ptr) { +// CHECK: %[[VAL_5:.*]] = fir.box_addr %[[VAL_2]] : (!fir.box>) -> !fir.ptr +// CHECK: fir.result %[[VAL_5]] : !fir.ptr +// CHECK: } else { +// CHECK: %[[VAL_6:.*]] = fir.absent !fir.ptr +// CHECK: fir.result %[[VAL_6]] : !fir.ptr +// CHECK: } +// CHECK: fir.store %[[VAL_0]] to %[[VAL_4]] : !fir.ptr + +func.func @test_scalar_opt_char_box(%arg0: !fir.ref>, %arg1: !fir.box>) { + %x = fir.declare %arg1 {fortran_attrs = #fir.var_attrs, uniq_name = "x"} : (!fir.box>) -> !fir.box> + hlfir.assign %arg0 to %x : !fir.ref>, !fir.box> + return +} +// CHECK-LABEL: func.func @test_scalar_opt_char_box( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref>, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.box>) { +// CHECK: %[[VAL_2:.*]] = fir.declare %[[VAL_1]] {fortran_attrs = #fir.var_attrs, uniq_name = "x"} : (!fir.box>) -> !fir.box> +// CHECK: %[[VAL_3:.*]] = arith.constant 10 : index +// CHECK: %[[VAL_4:.*]] = fir.is_present %[[VAL_2]] : (!fir.box>) -> i1 +// CHECK: %[[VAL_5:.*]]:2 = fir.if %[[VAL_4]] -> (!fir.ref>, index) { +// CHECK: %[[VAL_6:.*]] = fir.box_addr %[[VAL_2]] : (!fir.box>) -> !fir.ref> +// CHECK: %[[VAL_7:.*]] = fir.box_elesize %[[VAL_2]] : (!fir.box>) -> index +// CHECK: fir.result %[[VAL_6]], %[[VAL_7]] : !fir.ref>, index +// CHECK: } else { +// CHECK: %[[VAL_8:.*]] = fir.absent !fir.ref> +// CHECK: %[[VAL_9:.*]] = fir.zero_bits index +// CHECK: fir.result %[[VAL_8]], %[[VAL_9]] : !fir.ref>, index +// CHECK: } +// ... +// CHECK: fir.call @llvm.memmove.p0.p0.i64( diff --git a/flang/test/HLFIR/maxval-lowering.fir b/flang/test/HLFIR/maxval-lowering.fir index 7e025c41c6aeb..fbf75d9050544 100644 --- a/flang/test/HLFIR/maxval-lowering.fir +++ b/flang/test/HLFIR/maxval-lowering.fir @@ -216,3 +216,25 @@ func.func @_QPmaxval6(%arg0: !fir.box>> {fir.bindc_n // CHECK: hlfir.destroy %[[ASEXPR]] // CHECK-NEXT: return // CHECK-NEXT: } + +func.func @_QPmaxval_opt_mask(%arg0: !fir.box> {fir.bindc_name = "input"}, %arg1: !fir.ref> {fir.bindc_name = "mask", fir.optional}) -> f32 { + %0 = fir.dummy_scope : !fir.dscope + %1:2 = hlfir.declare %arg0 dummy_scope %0 {fortran_attrs = #fir.var_attrs, uniq_name = "_QFmaxval_opt_maskEinput"} : (!fir.box>, !fir.dscope) -> (!fir.box>, !fir.box>) + %2:2 = hlfir.declare %arg1 dummy_scope %0 {fortran_attrs = #fir.var_attrs, uniq_name = "_QFmaxval_opt_maskEmask"} : (!fir.ref>, !fir.dscope) -> (!fir.ref>, !fir.ref>) + %3 = fir.alloca f32 {bindc_name = "maxval_1", uniq_name = "_QFmaxval_opt_maskEmaxval_1"} + %4:2 = hlfir.declare %3 {uniq_name = "_QFmaxval_opt_maskEmaxval_1"} : (!fir.ref) -> (!fir.ref, !fir.ref) + %5 = fir.is_present %2#0 : (!fir.ref>) -> i1 + %6 = fir.embox %2#1 : (!fir.ref>) -> !fir.box> + %7 = fir.absent !fir.box> + %8 = arith.select %5, %6, %7 : !fir.box> + %9 = hlfir.maxval %1#0 mask %8 : (!fir.box>, !fir.box>) -> f32 + hlfir.assign %9 to %4#0 : f32, !fir.ref + %10 = fir.load %4#1 : !fir.ref + return %10 : f32 +} +// CHECK-LABEL: func.func @_QPmaxval_opt_mask( +// CHECK: %[[VAL_10:.*]] = fir.embox %{{.*}} : (!fir.ref>) -> !fir.box> +// CHECK: %[[VAL_11:.*]] = fir.absent !fir.box> +// CHECK: %[[VAL_12:.*]] = arith.select %{{.*}}, %[[VAL_10]], %[[VAL_11]] : !fir.box> +// CHECK: %[[VAL_17:.*]] = fir.convert %[[VAL_12]] : (!fir.box>) -> !fir.box +// CHECK: %[[VAL_18:.*]] = fir.call @_FortranAMaxvalReal4(%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) : (!fir.box, !fir.ref, i32, i32, !fir.box) -> f32