diff --git a/flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h b/flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h --- a/flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h +++ b/flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h @@ -64,6 +64,11 @@ return type && type.isa(); } +/// Is this a fir.box or fir.class address or value type? +inline bool isBoxAddressOrValueType(mlir::Type type) { + return fir::unwrapRefType(type).isa(); +} + } // namespace hlfir #endif // FORTRAN_OPTIMIZER_HLFIR_HLFIRDIALECT_H diff --git a/flang/include/flang/Optimizer/HLFIR/Passes.td b/flang/include/flang/Optimizer/HLFIR/Passes.td --- a/flang/include/flang/Optimizer/HLFIR/Passes.td +++ b/flang/include/flang/Optimizer/HLFIR/Passes.td @@ -10,7 +10,7 @@ #define FORTRAN_DIALECT_HLFIR_PASSES include "mlir/Pass/PassBase.td" -def ConvertHLFIRtoFIR : Pass<"convert-hlfir-to-fir", "::mlir::func::FuncOp"> { +def ConvertHLFIRtoFIR : Pass<"convert-hlfir-to-fir", "::mlir::ModuleOp"> { let summary = "Lower High-Level FIR to FIR"; let constructor = "hlfir::createConvertHLFIRtoFIRPass()"; } diff --git a/flang/lib/Optimizer/Builder/HLFIRTools.cpp b/flang/lib/Optimizer/Builder/HLFIRTools.cpp --- a/flang/lib/Optimizer/Builder/HLFIRTools.cpp +++ b/flang/lib/Optimizer/Builder/HLFIRTools.cpp @@ -73,9 +73,13 @@ hlfir::Entity entity) { if (auto variable = entity.getIfVariableInterface()) return {hlfir::translateToExtendedValue(loc, builder, variable), {}}; - if (entity.isVariable()) + if (entity.isVariable()) { + if (entity.isScalar() && !entity.hasLengthParameters() && + !hlfir::isBoxAddressOrValueType(entity.getType())) + return {fir::ExtendedValue{entity.getBase()}, llvm::None}; TODO(loc, "HLFIR variable to fir::ExtendedValue without a " "FortranVariableOpInterface"); + } if (entity.getType().isa()) { hlfir::AssociateOp associate = hlfir::genAssociateExpr( loc, builder, entity, entity.getType(), "adapt.valuebyref"); diff --git a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp --- a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp +++ b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp @@ -29,6 +29,85 @@ using namespace mlir; namespace { +/// May \p lhs alias with \p rhs? +/// TODO: implement HLFIR alias analysis. +static bool mayAlias(hlfir::Entity lhs, hlfir::Entity rhs) { return true; } + +class AssignOpConversion : public mlir::OpRewritePattern { +public: + explicit AssignOpConversion(mlir::MLIRContext *ctx) : OpRewritePattern{ctx} {} + + mlir::LogicalResult + matchAndRewrite(hlfir::AssignOp assignOp, + mlir::PatternRewriter &rewriter) const override { + mlir::Location loc = assignOp->getLoc(); + hlfir::Entity lhs(assignOp.getLhs()); + hlfir::Entity rhs(assignOp.getRhs()); + auto module = assignOp->getParentOfType(); + fir::FirOpBuilder builder(rewriter, fir::getKindMapping(module)); + + if (rhs.getType().isa()) { + mlir::emitError(loc, "hlfir must be bufferized with --bufferize-hlfir " + "pass before being converted to FIR"); + return mlir::failure(); + } + auto [rhsExv, rhsCleanUp] = + hlfir::translateToExtendedValue(loc, builder, rhs); + auto [lhsExv, lhsCleanUp] = + hlfir::translateToExtendedValue(loc, builder, lhs); + assert(!lhsCleanUp && !rhsCleanUp && + "variable to fir::ExtendedValue must not require cleanup"); + + if (lhs.isArray()) { + // Use the runtime for simplicity. An optimization pass will be added to + // inline array assignment when profitable. + auto to = fir::getBase(builder.createBox(loc, lhsExv)); + auto from = fir::getBase(builder.createBox(loc, rhsExv)); + bool cleanUpTemp = false; + mlir::Type fromHeapType = fir::HeapType::get( + fir::unwrapRefType(from.getType().cast().getEleTy())); + if (mayAlias(rhs, lhs)) { + /// Use the runtime to make a quick and dirty temp with the rhs value. + /// Overkill for scalar rhs that could be done in much more clever ways. + /// Note that temp descriptor must have the allocatable flag set so that + /// the runtime will allocate it with the shape and type parameters of + // the RHS. + mlir::Type fromBoxHeapType = fir::BoxType::get(fromHeapType); + auto fromMutableBox = builder.createTemporary(loc, fromBoxHeapType); + mlir::Value unallocatedBox = fir::factory::createUnallocatedBox( + builder, loc, fromBoxHeapType, {}); + builder.create(loc, unallocatedBox, fromMutableBox); + fir::runtime::genAssign(builder, loc, fromMutableBox, from); + cleanUpTemp = true; + from = builder.create(loc, fromMutableBox); + } + auto toMutableBox = builder.createTemporary(loc, to.getType()); + // As per 10.2.1.2 point 1 (1) polymorphic variables must be allocatable. + // It is assumed here that they have been reallocated with the dynamic + // type and that the mutableBox will not be modified. + builder.create(loc, to, toMutableBox); + fir::runtime::genAssign(builder, loc, toMutableBox, from); + if (cleanUpTemp) { + mlir::Value addr = + builder.create(loc, fromHeapType, from); + builder.create(loc, addr); + } + } else { + // Assume overlap does not matter for scalar (dealt with memmove for + // characters). + // This is not true if this is a derived type with "recursive" allocatable + // components, in which case an overlap would matter because the LHS + // reallocation, if any, may modify the RHS component value before it is + // copied into the LHS. + if (fir::isRecordWithAllocatableMember(lhs.getFortranElementType())) + TODO(loc, "assignment with allocatable components"); + fir::factory::genScalarAssignment(builder, loc, lhsExv, rhsExv); + } + rewriter.eraseOp(assignOp); + return mlir::success(); + } +}; + class DeclareOpConversion : public mlir::OpRewritePattern { public: explicit DeclareOpConversion(mlir::MLIRContext *ctx) @@ -183,16 +262,22 @@ : public hlfir::impl::ConvertHLFIRtoFIRBase { public: void runOnOperation() override { - auto func = this->getOperation(); + // TODO: like "bufferize-hlfir" pass, runtime signature may be added + // by this pass. This require the pass to run on the ModuleOp. It would + // probably be more optimal to have it run on FuncOp and find a way to + // generate the signatures in a thread safe way. + auto module = this->getOperation(); auto *context = &getContext(); mlir::RewritePatternSet patterns(context); - patterns.insert(context); + patterns + .insert( + context); mlir::ConversionTarget target(*context); target.addIllegalDialect(); target.markUnknownOpDynamicallyLegal( [](mlir::Operation *) { return true; }); - if (mlir::failed( - mlir::applyPartialConversion(func, target, std::move(patterns)))) { + if (mlir::failed(mlir::applyPartialConversion(module, target, + std::move(patterns)))) { mlir::emitError(mlir::UnknownLoc::get(context), "failure in HLFIR to FIR conversion pass"); signalPassFailure(); diff --git a/flang/test/HLFIR/assign-codegen.fir b/flang/test/HLFIR/assign-codegen.fir new file mode 100644 --- /dev/null +++ b/flang/test/HLFIR/assign-codegen.fir @@ -0,0 +1,157 @@ +// Test hlfir.assign code generation to FIR + +// RUN: fir-opt %s -convert-hlfir-to-fir | FileCheck %s + +func.func @scalar_int(%arg0: !fir.ref, %arg1: !fir.ref) { + hlfir.assign %arg0 to %arg1 : !fir.ref, !fir.ref + return +} +// CHECK-LABEL: func.func @scalar_int( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.ref) { +// CHECK: %[[VAL_2:.*]] = fir.load %[[VAL_0]] : !fir.ref +// CHECK: fir.store %[[VAL_2]] to %[[VAL_1]] : !fir.ref + +func.func @scalar_int_2(%arg0: !fir.ref) { + %c42_i32 = arith.constant 42 : i32 + hlfir.assign %c42_i32 to %arg0 : i32, !fir.ref + return +} +// CHECK-LABEL: func.func @scalar_int_2( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref) { +// CHECK: %[[VAL_1:.*]] = arith.constant 42 : i32 +// CHECK: fir.store %[[VAL_1]] to %[[VAL_0]] : !fir.ref + +func.func @scalar_logical(%arg0: !fir.ref>, %arg1: !fir.ref>) { + hlfir.assign %arg0 to %arg1 : !fir.ref>, !fir.ref> + return +} +// CHECK-LABEL: func.func @scalar_logical( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref>, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.ref>) { +// CHECK: %[[VAL_2:.*]] = fir.load %[[VAL_0]] : !fir.ref> +// CHECK: fir.store %[[VAL_2]] to %[[VAL_1]] : !fir.ref> + +func.func @scalar_logical_2(%arg0: !fir.ref>) { + %true = arith.constant true + hlfir.assign %true to %arg0 : i1, !fir.ref> + return +} +// CHECK-LABEL: func.func @scalar_logical_2( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref>) { +// CHECK: %[[VAL_1:.*]] = arith.constant true +// CHECK: %[[VAL_2:.*]] = fir.convert %[[VAL_1]] : (i1) -> !fir.logical<4> +// CHECK: fir.store %[[VAL_2]] to %[[VAL_0]] : !fir.ref> + +func.func @scalar_real(%arg0: !fir.ref, %arg1: !fir.ref) { + hlfir.assign %arg0 to %arg1 : !fir.ref, !fir.ref + return +} +// CHECK-LABEL: func.func @scalar_real( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.ref) { +// CHECK: %[[VAL_2:.*]] = fir.load %[[VAL_0]] : !fir.ref +// CHECK: fir.store %[[VAL_2]] to %[[VAL_1]] : !fir.ref + +func.func @scalar_real_2(%arg0: !fir.ref) { + %cst = arith.constant 3.140000e+00 : f32 + hlfir.assign %cst to %arg0 : f32, !fir.ref + return +} +// CHECK-LABEL: func.func @scalar_real_2( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref) { +// CHECK: %[[VAL_1:.*]] = arith.constant 3.140000e+00 : f32 +// CHECK: fir.store %[[VAL_1]] to %[[VAL_0]] : !fir.ref + +func.func @scalar_complex(%arg0: !fir.ref>, %arg1: !fir.ref>) { + hlfir.assign %arg0 to %arg0 : !fir.ref>, !fir.ref> + return +} +// CHECK-LABEL: func.func @scalar_complex( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref>, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.ref>) { +// CHECK: %[[VAL_2:.*]] = fir.load %[[VAL_0]] : !fir.ref> +// CHECK: fir.store %[[VAL_2]] to %[[VAL_0]] : !fir.ref> + +func.func @scalar_complex_2(%arg0: !fir.ref>, %arg1: !fir.complex<4>) { + hlfir.assign %arg1 to %arg0 : !fir.complex<4>, !fir.ref> + return +} +// CHECK-LABEL: func.func @scalar_complex_2( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.ref>, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.complex<4>) { +// CHECK: fir.store %[[VAL_1]] to %[[VAL_0]] : !fir.ref> + +func.func @scalar_character(%arg0: !fir.boxchar<1>, %arg1: !fir.boxchar<1>) { + %0:2 = fir.unboxchar %arg0 : (!fir.boxchar<1>) -> (!fir.ref>, index) + %1:2 = hlfir.declare %0#0 typeparams %0#1 {uniq_name = "x"} : (!fir.ref>, index) -> (!fir.boxchar<1>, !fir.ref>) + %2:2 = fir.unboxchar %arg1 : (!fir.boxchar<1>) -> (!fir.ref>, index) + %3:2 = hlfir.declare %2#0 typeparams %2#1 {uniq_name = "y"} : (!fir.ref>, index) -> (!fir.boxchar<1>, !fir.ref>) + hlfir.assign %3#0 to %1#0 : !fir.boxchar<1>, !fir.boxchar<1> + return +} +// CHECK-LABEL: func.func @scalar_character( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.boxchar<1>, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.boxchar<1>) { +// CHECK: %[[VAL_2:.*]]:2 = fir.unboxchar %[[VAL_0]] : (!fir.boxchar<1>) -> (!fir.ref>, index) +// CHECK: %[[VAL_3:.*]] = fir.declare %[[VAL_2]]#0 typeparams %[[VAL_2]]#1 {uniq_name = "x"} : (!fir.ref>, index) -> !fir.ref> +// CHECK: %[[VAL_4:.*]] = fir.emboxchar %[[VAL_3]], %[[VAL_2]]#1 : (!fir.ref>, index) -> !fir.boxchar<1> +// CHECK: %[[VAL_5:.*]]:2 = fir.unboxchar %[[VAL_1]] : (!fir.boxchar<1>) -> (!fir.ref>, index) +// CHECK: %[[VAL_6:.*]] = fir.declare %[[VAL_5]]#0 typeparams %[[VAL_5]]#1 {uniq_name = "y"} : (!fir.ref>, index) -> !fir.ref> +// CHECK: %[[VAL_7:.*]] = fir.emboxchar %[[VAL_6]], %[[VAL_5]]#1 : (!fir.ref>, index) -> !fir.boxchar<1> +// CHECK: %[[VAL_8:.*]] = arith.cmpi slt, %[[VAL_2]]#1, %[[VAL_5]]#1 : index +// CHECK: %[[VAL_9:.*]] = arith.select %[[VAL_8]], %[[VAL_2]]#1, %[[VAL_5]]#1 : index +// CHECK: %[[VAL_10:.*]] = arith.constant 1 : i64 +// CHECK: %[[VAL_11:.*]] = fir.convert %[[VAL_9]] : (index) -> i64 +// CHECK: %[[VAL_12:.*]] = arith.muli %[[VAL_10]], %[[VAL_11]] : i64 +// CHECK: %[[VAL_13:.*]] = arith.constant false +// CHECK: %[[VAL_14:.*]] = fir.convert %[[VAL_3]] : (!fir.ref>) -> !fir.ref +// CHECK: %[[VAL_15:.*]] = fir.convert %[[VAL_6]] : (!fir.ref>) -> !fir.ref +// CHECK: fir.call @llvm.memmove.p0.p0.i64(%[[VAL_14]], %[[VAL_15]], %[[VAL_12]], %[[VAL_13]]) : (!fir.ref, !fir.ref, i64, i1) -> () +// CHECK: %[[VAL_16:.*]] = arith.constant 1 : index +// CHECK: %[[VAL_17:.*]] = arith.subi %[[VAL_2]]#1, %[[VAL_16]] : index +// CHECK: %[[VAL_18:.*]] = arith.constant 32 : i8 +// CHECK: %[[VAL_19:.*]] = fir.undefined !fir.char<1> +// CHECK: %[[VAL_20:.*]] = fir.insert_value %[[VAL_19]], %[[VAL_18]], [0 : index] : (!fir.char<1>, i8) -> !fir.char<1> +// CHECK: %[[VAL_21:.*]] = arith.constant 1 : index +// CHECK: fir.do_loop %[[VAL_22:.*]] = %[[VAL_9]] to %[[VAL_17]] step %[[VAL_21]] { +// CHECK: %[[VAL_23:.*]] = fir.convert %[[VAL_3]] : (!fir.ref>) -> !fir.ref>> +// CHECK: %[[VAL_24:.*]] = fir.coordinate_of %[[VAL_23]], %[[VAL_22]] : (!fir.ref>>, index) -> !fir.ref> +// CHECK: fir.store %[[VAL_20]] to %[[VAL_24]] : !fir.ref> +// CHECK: } + +func.func @array(%arg0: !fir.box>, %arg1: !fir.ref>) { + %c100 = arith.constant 100 : index + %0:2 = hlfir.declare %arg0 {uniq_name = "x"} : (!fir.box>) -> (!fir.box>, !fir.box>) + %1 = fir.shape %c100 : (index) -> !fir.shape<1> + %2:2 = hlfir.declare %arg1(%1) {uniq_name = "y"} : (!fir.ref>, !fir.shape<1>) -> (!fir.ref>, !fir.ref>) + hlfir.assign %2#0 to %0#0 : !fir.ref>, !fir.box> + return +} +// CHECK-LABEL: func.func @array( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.box>, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.ref>) { +// CHECK: %[[VAL_2:.*]] = fir.alloca !fir.box> +// CHECK: %[[VAL_3:.*]] = fir.alloca !fir.box>> +// CHECK: %[[VAL_4:.*]] = arith.constant 100 : index +// CHECK: %[[VAL_5:.*]] = fir.declare %[[VAL_0]] {uniq_name = "x"} : (!fir.box>) -> !fir.box> +// CHECK: %[[VAL_6:.*]] = fir.rebox %[[VAL_5]] : (!fir.box>) -> !fir.box> +// CHECK: %[[VAL_7:.*]] = fir.shape %[[VAL_4]] : (index) -> !fir.shape<1> +// CHECK: %[[VAL_8:.*]] = fir.declare %[[VAL_1]](%[[VAL_7]]) {uniq_name = "y"} : (!fir.ref>, !fir.shape<1>) -> !fir.ref> +// CHECK: %[[VAL_9:.*]] = fir.shape %[[VAL_4]] : (index) -> !fir.shape<1> +// CHECK: %[[VAL_10:.*]] = fir.embox %[[VAL_8]](%[[VAL_9]]) : (!fir.ref>, !fir.shape<1>) -> !fir.box> +// CHECK: %[[VAL_11:.*]] = fir.zero_bits !fir.heap> +// CHECK: %[[VAL_12:.*]] = arith.constant 0 : index +// CHECK: %[[VAL_13:.*]] = fir.shape %[[VAL_12]] : (index) -> !fir.shape<1> +// CHECK: %[[VAL_14:.*]] = fir.embox %[[VAL_11]](%[[VAL_13]]) : (!fir.heap>, !fir.shape<1>) -> !fir.box>> +// CHECK: fir.store %[[VAL_14]] to %[[VAL_3]] : !fir.ref>>> +// CHECK: %[[VAL_18:.*]] = fir.convert %[[VAL_3]] : (!fir.ref>>>) -> !fir.ref> +// CHECK: %[[VAL_19:.*]] = fir.convert %[[VAL_10]] : (!fir.box>) -> !fir.box +// CHECK: %[[VAL_21:.*]] = fir.call @_FortranAAssign(%[[VAL_18]], %[[VAL_19]], %{{.*}}, %{{.*}}) : (!fir.ref>, !fir.box, !fir.ref, i32) -> none +// CHECK: %[[VAL_22:.*]] = fir.load %[[VAL_3]] : !fir.ref>>> +// CHECK: fir.store %[[VAL_5]] to %[[VAL_2]] : !fir.ref>> +// CHECK: %[[VAL_26:.*]] = fir.convert %[[VAL_2]] : (!fir.ref>>) -> !fir.ref> +// CHECK: %[[VAL_27:.*]] = fir.convert %[[VAL_22]] : (!fir.box>>) -> !fir.box +// CHECK: %[[VAL_29:.*]] = fir.call @_FortranAAssign(%[[VAL_26]], %[[VAL_27]], %{{.*}}, %{{.*}}) : (!fir.ref>, !fir.box, !fir.ref, i32) -> none +// CHECK: %[[VAL_30:.*]] = fir.box_addr %[[VAL_22]] : (!fir.box>>) -> !fir.heap> +// CHECK: fir.freemem %[[VAL_30]] : !fir.heap>