diff --git a/flang/include/flang/Optimizer/Dialect/FIRTypes.td b/flang/include/flang/Optimizer/Dialect/FIRTypes.td --- a/flang/include/flang/Optimizer/Dialect/FIRTypes.td +++ b/flang/include/flang/Optimizer/Dialect/FIRTypes.td @@ -561,6 +561,7 @@ // Whether a type is a BaseBoxType def IsBaseBoxTypePred : CPred<"$_self.isa<::fir::BaseBoxType>()">; +def fir_BaseBoxType : Type; // Generalized FIR and standard dialect types representing intrinsic types def AnyIntegerLike : TypeConstraint { + let summary = "copy a variable into a contiguous temporary if it is not contiguous"; + let description = [{ + Copy a variable into a contiguous temporary if the variable is not + an absent optional and is not contiguous at runtime. + Otherwise, returns the potentially absent variable storage. + This is meant to be used in combination with the hlfir.copy_out operation + that deletes the temporary if it was created and copy the data back if needed. + This operation allows passing non contiguous arrays as procedure reference + dummy arguments that must be contiguous, which is possible in Fortran (that + mandates that any pointers made to these dummy arguments are invalidated after + the call). + + To deal with the optional case, this operation can optionally support a + null input box or an input box containing a null address (but it cannot + support both at the same time). In such case, if the input box matches + the absence criterion at runtime, the operation will return a null output + address or box and "false". This allows implementing the passing of non + contiguous OPTIONAL assumed shapes (null box) to contiguous OPTIONAL + dummies, or the passing of disassociated pointers (null address in box) + to contiguous OPTIONAL dummies. + }]; + + let arguments = (ins fir_BaseBoxType:$var, + OptionalAttr:$handle_optional); + let results = (outs AnyRefOrBoxLike, I1); + + let assemblyFormat = [{ + $var custom($handle_optional) + attr-dict `:` functional-type(operands, results) + }]; + + let builders = [ + OpBuilder<(ins "mlir::Value":$var, "std::optional":$handle_optional, + "bool":$return_box)> + ]; + + let extraClassDeclaration = [{ + mlir::Value getCopiedIn() { + return getResult(0); + } + mlir::Value getWasCopied() { + return getResult(1); + } + + /// "handle_optional" value to be passed to the operation builder when input + /// box may be a null box. + static constexpr bool kHandleNullBox = true; + + /// "handle_optional" value to be passed to the operation builder when input + /// box may contain a null address. + static constexpr bool kHandleNullAddr = !kHandleNullBox; + + /// Does this operation handle a null input box? + bool getHandleNullBox() { + return getHandleOptional() && *getHandleOptional() == kHandleNullBox; + } + + /// Does this operation handle an input box containing a null address? + bool getHandleNullAddr() { + return getHandleOptional() && *getHandleOptional() == kHandleNullAddr; + } + }]; +} + +def hlfir_CopyOutOp : hlfir_Op<"copy_out", []> { + let summary = "copy out a variable after a copy in"; + let description = [{ + If the variable was copied in a temporary in the related hlfir.copy_in, + optionally copy back the temporary value to it (that may have been + modified between the hlfir.copy_in and hlfir.copy_out). Then deallocate + the temporary. + The copy back is done if $var is provided $was_copied is true. + The deallocation of $temp is done if $was_copied is true. + }]; + + let arguments = (ins AnyRefOrBoxLike:$temp, + I1:$was_copied, + Optional:$var); + + let assemblyFormat = [{ + $temp `,` $was_copied (`to` $var^)? + attr-dict `:` functional-type(operands, results) + }]; +} + #endif // FORTRAN_DIALECT_HLFIR_OPS diff --git a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp --- a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp +++ b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp @@ -515,5 +515,52 @@ fir::ReferenceType::get(builder.getNoneType())); } +//===----------------------------------------------------------------------===// +// CopyInOp +//===----------------------------------------------------------------------===// + +static mlir::ParseResult +parseCopyInHandleOptional(mlir::OpAsmParser &parser, + mlir::BoolAttr &handleOptional) { + if (mlir::succeeded(parser.parseOptionalKeyword("handle_null_box"))) + handleOptional = mlir::BoolAttr::get(parser.getContext(), + hlfir::CopyInOp::kHandleNullBox); + else if (mlir::succeeded(parser.parseOptionalKeyword("handle_null_addr"))) + handleOptional = mlir::BoolAttr::get(parser.getContext(), + hlfir::CopyInOp::kHandleNullAddr); + return mlir::success(); +} + +static void printCopyInHandleOptional(mlir::OpAsmPrinter &p, hlfir::CopyInOp, + mlir::BoolAttr handleOptionalAttr) { + if (handleOptionalAttr) { + if (handleOptionalAttr.getValue() == hlfir::CopyInOp::kHandleNullBox) + p << "handle_null_box"; + else + p << "handle_null_addr"; + } +} + +void hlfir::CopyInOp::build(mlir::OpBuilder &builder, + mlir::OperationState &odsState, mlir::Value var, + std::optional handleOptional, + bool returnBox) { + mlir::Type baseType = hlfir::getFortranElementOrSequenceType(var.getType()); + mlir::Type resType; + if (returnBox) { + resType = fir::ReferenceType::get(baseType); + } else { + if (var.getType().isa()) + resType = fir::ClassType::get(resType); + else + resType = fir::BoxType::get(resType); + } + auto handleOptionalAttr = + handleOptional.has_value() + ? mlir::BoolAttr::get(builder.getContext(), *handleOptional) + : mlir::BoolAttr{}; + return build(builder, odsState, resType, var, handleOptionalAttr); +} + #define GET_OP_CLASSES #include "flang/Optimizer/HLFIR/HLFIROps.cpp.inc" diff --git a/flang/test/HLFIR/copy-in-out.fir b/flang/test/HLFIR/copy-in-out.fir new file mode 100644 --- /dev/null +++ b/flang/test/HLFIR/copy-in-out.fir @@ -0,0 +1,46 @@ +// Test hlfir.copy_in and hlfir.copy_out operation parse, verify (no errors), +// and unparse. + +// RUN: fir-opt %s | fir-opt | FileCheck %s + +func.func @test_copy_in(%box: !fir.box>) { + %0:2 = hlfir.copy_in %box : (!fir.box>) -> (!fir.ref>, i1) + %1:2 = hlfir.copy_in %box : (!fir.box>) -> (!fir.box>, i1) + %2:2 = hlfir.copy_in %box handle_null_box: (!fir.box>) -> (!fir.ref>, i1) + %3:2 = hlfir.copy_in %box handle_null_box: (!fir.box>) -> (!fir.box>, i1) + %4:2 = hlfir.copy_in %box handle_null_addr: (!fir.box>) -> (!fir.ref>, i1) + %5:2 = hlfir.copy_in %box handle_null_addr: (!fir.box>) -> (!fir.box>, i1) + return +} +// CHECK-LABEL: func.func @test_copy_in( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.box>) { +// CHECK: %[[VAL_1:.*]]:2 = hlfir.copy_in %[[VAL_0]] : (!fir.box>) -> (!fir.ref>, i1) +// CHECK: %[[VAL_2:.*]]:2 = hlfir.copy_in %[[VAL_0]] : (!fir.box>) -> (!fir.box>, i1) +// CHECK: %[[VAL_3:.*]]:2 = hlfir.copy_in %[[VAL_0]] handle_null_box : (!fir.box>) -> (!fir.ref>, i1) +// CHECK: %[[VAL_4:.*]]:2 = hlfir.copy_in %[[VAL_0]] handle_null_box : (!fir.box>) -> (!fir.box>, i1) +// CHECK: %[[VAL_5:.*]]:2 = hlfir.copy_in %[[VAL_0]] handle_null_addr : (!fir.box>) -> (!fir.ref>, i1) +// CHECK: %[[VAL_6:.*]]:2 = hlfir.copy_in %[[VAL_0]] handle_null_addr : (!fir.box>) -> (!fir.box>, i1) + +func.func @test_copy_out_with_raw_temp(%box: !fir.box>, %temp: !fir.ref>, %was_copied: i1) { + hlfir.copy_out %temp, %was_copied : (!fir.ref>, i1) -> () + hlfir.copy_out %temp, %was_copied to %box : (!fir.ref>, i1, !fir.box>) -> () + return +} +// CHECK-LABEL: func.func @test_copy_out_with_raw_temp( +// CHECK-SAME: %[[VAL_0:.*]]: !fir.box>, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.ref>, +// CHECK-SAME: %[[VAL_2:.*]]: i1) { +// CHECK: hlfir.copy_out %[[VAL_1]], %[[VAL_2]] : (!fir.ref>, i1) -> () +// CHECK: hlfir.copy_out %[[VAL_1]], %[[VAL_2]] to %[[VAL_0]] : (!fir.ref>, i1, !fir.box>) -> () + +func.func @test_copy_out_with_box_temp(%box: !fir.box>, %temp: !fir.box>, %was_copied: i1) { + hlfir.copy_out %temp, %was_copied : (!fir.box>, i1) -> () + hlfir.copy_out %temp, %was_copied to %box : (!fir.box>, i1, !fir.box>) -> () + return +} +// CHECK-LABEL: func.func @test_copy_out_with_box_temp( +// CHECK-SAME: %[[VAL_0:[^:]*]]: !fir.box>, +// CHECK-SAME: %[[VAL_1:.*]]: !fir.box>, +// CHECK-SAME: %[[VAL_2:.*]]: i1) { +// CHECK: hlfir.copy_out %[[VAL_1]], %[[VAL_2]] : (!fir.box>, i1) -> () +// CHECK: hlfir.copy_out %[[VAL_1]], %[[VAL_2]] to %[[VAL_0]] : (!fir.box>, i1, !fir.box>) -> ()