diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td --- a/flang/include/flang/Optimizer/Dialect/FIROps.td +++ b/flang/include/flang/Optimizer/Dialect/FIROps.td @@ -2599,6 +2599,7 @@ static bool isIntegerCompatible(mlir::Type ty); static bool isFloatCompatible(mlir::Type ty); static bool isPointerCompatible(mlir::Type ty); + static bool canBeConverted(mlir::Type inType, mlir::Type outType); }]; let hasCanonicalizer = 1; } diff --git a/flang/lib/Lower/CallInterface.cpp b/flang/lib/Lower/CallInterface.cpp --- a/flang/lib/Lower/CallInterface.cpp +++ b/flang/lib/Lower/CallInterface.cpp @@ -186,6 +186,14 @@ dummy.u); } +static bool isExternalDefinedInSameCompilationUnit( + const Fortran::evaluate::ProcedureDesignator &proc) { + if (const auto *symbol{proc.GetSymbol()}) + return symbol->has() && + symbol->owner().IsGlobal(); + return false; +} + Fortran::evaluate::characteristics::Procedure Fortran::lower::CallerInterface::characterize() const { Fortran::evaluate::FoldingContext &foldingContext = @@ -195,27 +203,58 @@ procRef.proc(), foldingContext); assert(characteristic && "Failed to get characteristic from procRef"); // The characteristic may not contain the argument characteristic if the - // ProcedureDesignator has no interface. - if (!characteristic->HasExplicitInterface()) { + // ProcedureDesignator has no interface, or may mismatch in case of implicit + // interface. + if (!characteristic->HasExplicitInterface() || + (converter.getLoweringOptions().getLowerToHighLevelFIR() && + isExternalDefinedInSameCompilationUnit(procRef.proc()) && + characteristic->CanBeCalledViaImplicitInterface())) { + // In HLFIR lowering, calls to subprogram with implicit interfaces are + // always prepared according to the actual arguments. This is to support + // cases where the implicit interfaces are "abused" in old and not so old + // Fortran code (e.g, passing REAL(8) to CHARACTER(8), passing object + // pointers to procedure dummies, passing regular procedure dummies to + // character procedure dummies, omitted arguments....). + // In all those case, if the subprogram definition is in the same + // compilation unit, the "characteristic" from Characterize will be the one + // from the definition, in case of "abuses" (for which semantics raise a + // warning), lowering will be placed in a difficult position if it is given + // the dummy characteristic from the definition and an actual that has + // seemingly nothing to do with it: it would need to battle to anticipate + // and handle these mismatches (e.g., be able to prepare a fir.boxchar<> + // from a fir.real<> and so one). This was the approach of the lowering to + // FIR, and usually lead to compiler bug every time a new "abuse" was met in + // the wild. + // Instead, in HLFIR, the dummy characteristic is always computed from the + // actual for subprogram with implicit interfaces, and in case of call site + // vs fun.func MLIR function type signature mismatch, a function cast is + // done before placing the call. This is a hammer that should cover all + // cases and behave like existing compiler that "do not see" the definition + // when placing the call. + characteristic->dummyArguments.clear(); for (const std::optional &arg : procRef.arguments()) { - if (arg.value().isAlternateReturn()) { - characteristic->dummyArguments.emplace_back( - Fortran::evaluate::characteristics::AlternateReturn{}); - } else { - // Argument cannot be optional with implicit interface - const Fortran::lower::SomeExpr *expr = arg.value().UnwrapExpr(); - assert( - expr && - "argument in call with implicit interface cannot be assumed type"); - std::optional - argCharacteristic = - Fortran::evaluate::characteristics::DummyArgument::FromActual( - "actual", *expr, foldingContext); - assert(argCharacteristic && - "failed to characterize argument in implicit call"); - characteristic->dummyArguments.emplace_back( - asImplicitArg(std::move(*argCharacteristic))); + // "arg" may be null if this is a call with missing arguments compared + // to the subprogram definition. Do not compute any characteristic + // in this case. + if (arg.has_value()) { + if (arg.value().isAlternateReturn()) { + characteristic->dummyArguments.emplace_back( + Fortran::evaluate::characteristics::AlternateReturn{}); + } else { + // Argument cannot be optional with implicit interface + const Fortran::lower::SomeExpr *expr = arg.value().UnwrapExpr(); + assert(expr && "argument in call with implicit interface cannot be " + "assumed type"); + std::optional + argCharacteristic = + Fortran::evaluate::characteristics::DummyArgument::FromActual( + "actual", *expr, foldingContext); + assert(argCharacteristic && + "failed to characterize argument in implicit call"); + characteristic->dummyArguments.emplace_back( + asImplicitArg(std::move(*argCharacteristic))); + } } } } diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp --- a/flang/lib/Lower/ConvertCall.cpp +++ b/flang/lib/Lower/ConvertCall.cpp @@ -100,6 +100,44 @@ return {}; } +static bool mustCastFuncOpToCopeWithImplicitInterfaceMismatch( + mlir::Location loc, Fortran::lower::AbstractConverter &converter, + mlir::FunctionType callSiteType, mlir::FunctionType funcOpType) { + // Deal with argument number mismatch by making a function pointer so + // that function type cast can be inserted. Do not emit a warning here + // because this can happen in legal program if the function is not + // defined here and it was first passed as an argument without any more + // information. + if (callSiteType.getNumResults() != funcOpType.getNumResults() || + callSiteType.getNumInputs() != funcOpType.getNumInputs()) + return true; + + // Implicit interface result type mismatch are not standard Fortran, but + // some compilers are not complaining about it. The front end is not + // protecting lowering from this currently. Support this with a + // discouraging warning. + // Cast the actual function to the current caller implicit type because + // that is the behavior we would get if we could not see the definition. + if (callSiteType.getResults() != funcOpType.getResults()) { + LLVM_DEBUG(mlir::emitWarning( + loc, "a return type mismatch is not standard compliant and may " + "lead to undefined behavior.")); + return true; + } + + // In HLFIR, there is little attempt to cope with implicit interface + // mismatch on the arguments. The argument are always prepared according + // to the implicit interface. Cast the actual function if any of the + // argument mismatch cannot be dealt with a simple fir.convert. + if (converter.getLoweringOptions().getLowerToHighLevelFIR()) + for (auto [actualType, dummyType] : + llvm::zip(callSiteType.getInputs(), funcOpType.getInputs())) + if (actualType != dummyType && + !fir::ConvertOp::canBeConverted(actualType, dummyType)) + return true; + return false; +} + fir::ExtendedValue Fortran::lower::genCallOpAndResult( mlir::Location loc, Fortran::lower::AbstractConverter &converter, Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx, @@ -244,29 +282,16 @@ converter.hostAssocTupleValue().getType()); addHostAssociations = true; } + // When this is not a call to an internal procedure (where there is a + // mismatch due to the extra argument, but the interface is otherwise + // explicit and safe), handle interface mismatch due to F77 implicit + // interface "abuse" with a function address cast if needed. if (!addHostAssociations && - (callSiteType.getNumResults() != funcOpType.getNumResults() || - callSiteType.getNumInputs() != funcOpType.getNumInputs())) { - // Deal with argument number mismatch by making a function pointer so - // that function type cast can be inserted. Do not emit a warning here - // because this can happen in legal program if the function is not - // defined here and it was first passed as an argument without any more - // information. - funcPointer = builder.create(loc, funcOpType, symbolAttr); - } else if (callSiteType.getResults() != funcOpType.getResults()) { - // Implicit interface result type mismatch are not standard Fortran, but - // some compilers are not complaining about it. The front end is not - // protecting lowering from this currently. Support this with a - // discouraging warning. - LLVM_DEBUG(mlir::emitWarning( - loc, "a return type mismatch is not standard compliant and may " - "lead to undefined behavior.")); - // Cast the actual function to the current caller implicit type because - // that is the behavior we would get if we could not see the definition. + mustCastFuncOpToCopeWithImplicitInterfaceMismatch( + loc, converter, callSiteType, funcOpType)) funcPointer = builder.create(loc, funcOpType, symbolAttr); - } else { + else funcSymbolAttr = symbolAttr; - } } mlir::FunctionType funcType = @@ -761,6 +786,33 @@ }; } // namespace +/// Fix-up the fact that it is supported to pass a character procedure +/// designator to a non character procedure dummy procedure and vice-versa, even +/// in case of explicit interface. Uglier cases where an object is passed as +/// procedure designator or vice versa are handled only for implicit interfaces +/// (refused by semantics with explicit interface), and handled with a funcOp +/// cast like other implicit interface mismatches. +static hlfir::Entity fixProcedureDummyMismatch(mlir::Location loc, + fir::FirOpBuilder &builder, + hlfir::Entity actual, + mlir::Type dummyType) { + if (actual.getType().isa() && + fir::isCharacterProcedureTuple(dummyType)) { + mlir::Value length = + builder.create(loc, builder.getCharacterLengthType()); + mlir::Value tuple = fir::factory::createCharacterProcedureTuple( + builder, loc, dummyType, actual, length); + return hlfir::Entity{tuple}; + } + assert(fir::isCharacterProcedureTuple(actual.getType()) && + dummyType.isa() && + "unsupported dummy procedure mismatch with the actual argument"); + mlir::Value boxProc = fir::factory::extractCharacterProcedureTuple( + builder, loc, actual, /*openBoxProc=*/false) + .first; + return hlfir::Entity{boxProc}; +} + /// When dummy is not ALLOCATABLE, POINTER and is not passed in register, /// prepare the actual argument according to the interface. Do as needed: /// - address element if this is an array argument in an elemental call. @@ -784,8 +836,11 @@ // Do nothing if this is a procedure argument. It is already a // fir.boxproc/fir.tuple as it should. - if (actual.isProcedure()) + if (actual.isProcedure()) { + if (actual.getType() != dummyType) + actual = fixProcedureDummyMismatch(loc, builder, actual, dummyType); return PreparedDummyArgument{actual, std::nullopt}; + } const bool passingPolymorphicToNonPolymorphic = actual.isPolymorphic() && !fir::isPolymorphicType(dummyType); @@ -1019,8 +1074,8 @@ break; case PassBy::CharProcTuple: { hlfir::Entity actual = preparedActual->getActual(loc, builder); - assert(fir::isCharacterProcedureTuple(actual.getType()) && - "character dummy procedure was not prepared as expected"); + if (!fir::isCharacterProcedureTuple(actual.getType())) + actual = fixProcedureDummyMismatch(loc, builder, actual, argTy); caller.placeInput(arg, actual); } break; case PassBy::MutableBox: { diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp --- a/flang/lib/Optimizer/Dialect/FIROps.cpp +++ b/flang/lib/Optimizer/Dialect/FIROps.cpp @@ -940,24 +940,26 @@ fir::TypeDescType>(); } -mlir::LogicalResult fir::ConvertOp::verify() { - auto inType = getValue().getType(); - auto outType = getType(); +bool fir::ConvertOp::canBeConverted(mlir::Type inType, mlir::Type outType) { if (inType == outType) - return mlir::success(); - if ((isPointerCompatible(inType) && isPointerCompatible(outType)) || - (isIntegerCompatible(inType) && isIntegerCompatible(outType)) || - (isIntegerCompatible(inType) && isFloatCompatible(outType)) || - (isFloatCompatible(inType) && isIntegerCompatible(outType)) || - (isFloatCompatible(inType) && isFloatCompatible(outType)) || - (isIntegerCompatible(inType) && isPointerCompatible(outType)) || - (isPointerCompatible(inType) && isIntegerCompatible(outType)) || - (inType.isa() && outType.isa()) || - (inType.isa() && outType.isa()) || - (fir::isa_complex(inType) && fir::isa_complex(outType)) || - (fir::isBoxedRecordType(inType) && fir::isPolymorphicType(outType)) || - (fir::isPolymorphicType(inType) && fir::isPolymorphicType(outType)) || - (fir::isPolymorphicType(inType) && outType.isa())) + return true; + return (isPointerCompatible(inType) && isPointerCompatible(outType)) || + (isIntegerCompatible(inType) && isIntegerCompatible(outType)) || + (isIntegerCompatible(inType) && isFloatCompatible(outType)) || + (isFloatCompatible(inType) && isIntegerCompatible(outType)) || + (isFloatCompatible(inType) && isFloatCompatible(outType)) || + (isIntegerCompatible(inType) && isPointerCompatible(outType)) || + (isPointerCompatible(inType) && isIntegerCompatible(outType)) || + (inType.isa() && outType.isa()) || + (inType.isa() && outType.isa()) || + (fir::isa_complex(inType) && fir::isa_complex(outType)) || + (fir::isBoxedRecordType(inType) && fir::isPolymorphicType(outType)) || + (fir::isPolymorphicType(inType) && fir::isPolymorphicType(outType)) || + (fir::isPolymorphicType(inType) && outType.isa()); +} + +mlir::LogicalResult fir::ConvertOp::verify() { + if (canBeConverted(getValue().getType(), getType())) return mlir::success(); return emitOpError("invalid type conversion"); } diff --git a/flang/test/Lower/HLFIR/implicit-call-mismatch.f90 b/flang/test/Lower/HLFIR/implicit-call-mismatch.f90 new file mode 100644 --- /dev/null +++ b/flang/test/Lower/HLFIR/implicit-call-mismatch.f90 @@ -0,0 +1,156 @@ +! Test questionable but existing abuses of implicit interfaces. +! Lowering must close the eyes and do as if it did not know +! about the function definition since semantic lets these +! programs through with a warning. +! RUN: bbc -emit-fir -hlfir %s -o - | FileCheck %s + +subroutine takes_char(c) + character(8) :: c +end subroutine + +subroutine pass_real_to_char(r) + real(8) :: r + call takes_char(r) +end subroutine +! CHECK-LABEL: func.func @_QPpass_real_to_char( +! CHECK: %[[VAL_1:.*]]:2 = hlfir.declare {{.*}}Er +! CHECK: %[[VAL_2:.*]] = fir.address_of(@_QPtakes_char) : (!fir.boxchar<1>) -> () +! CHECK: %[[VAL_3:.*]] = fir.convert %[[VAL_2]] : ((!fir.boxchar<1>) -> ()) -> ((!fir.ref) -> ()) +! CHECK: fir.call %[[VAL_3]](%[[VAL_1]]#1) {{.*}}: (!fir.ref) -> () + +subroutine pass_char_proc_to_char() + character(8), external :: char_proc + call takes_char(char_proc) +end subroutine +! CHECK: %[[VAL_0:.*]] = fir.address_of(@_QPchar_proc) : (!fir.ref>, index) -> !fir.boxchar<1> +! CHECK: %[[VAL_1:.*]] = arith.constant 8 : i64 +! CHECK: %[[VAL_2:.*]] = fir.emboxproc %[[VAL_0]] : ((!fir.ref>, index) -> !fir.boxchar<1>) -> !fir.boxproc<() -> ()> +! CHECK: %[[VAL_3:.*]] = fir.undefined tuple ()>, i64> +! CHECK: %[[VAL_4:.*]] = fir.insert_value %[[VAL_3]], %[[VAL_2]], [0 : index] : (tuple ()>, i64>, !fir.boxproc<() -> ()>) -> tuple ()>, i64> +! CHECK: %[[VAL_5:.*]] = fir.insert_value %[[VAL_4]], %[[VAL_1]], [1 : index] : (tuple ()>, i64>, i64) -> tuple ()>, i64> +! CHECK: %[[VAL_6:.*]] = fir.address_of(@_QPtakes_char) : (!fir.boxchar<1>) -> () +! CHECK: %[[VAL_7:.*]] = fir.convert %[[VAL_6]] : ((!fir.boxchar<1>) -> ()) -> ((tuple ()>, i64>) -> ()) +! CHECK: fir.call %[[VAL_7]](%[[VAL_5]]) {{.*}}: (tuple ()>, i64>) -> () + +subroutine pass_kind2_char_to_char(c) + character(4, kind=2) :: c + call takes_char(c) +end subroutine +! CHECK-LABEL: func.func @_QPpass_kind2_char_to_char( +! CHECK: %[[VAL_3:.*]]:2 = hlfir.declare {{.*}}Ec +! CHECK: %[[VAL_4:.*]] = fir.address_of(@_QPtakes_char) : (!fir.boxchar<1>) -> () +! CHECK: %[[VAL_5:.*]] = fir.convert %[[VAL_4]] : ((!fir.boxchar<1>) -> ()) -> ((!fir.boxchar<2>) -> ()) +! CHECK: fir.call %[[VAL_5]](%[[VAL_3]]#0) {{.*}}: (!fir.boxchar<2>) -> () + +subroutine takes_real(r) + real(8) :: r +end subroutine + +subroutine pass_int_to_real(i) + integer(8) :: i + call takes_real(i) +end subroutine +! CHECK-LABEL: func.func @_QPpass_int_to_real( +! CHECK: %[[VAL_1:.*]]:2 = hlfir.declare {{.*}}Ei +! CHECK: %[[VAL_2:.*]] = fir.convert %[[VAL_1]]#1 : (!fir.ref) -> !fir.ref +! CHECK: fir.call @_QPtakes_real(%[[VAL_2]]) {{.*}}: (!fir.ref) -> () + +subroutine pass_char_to_real(c) + character(8) :: c + call takes_real(c) +end subroutine +! CHECK-LABEL: func.func @_QPpass_char_to_real( +! CHECK: %[[VAL_3:.*]]:2 = hlfir.declare {{.*}}Ec +! CHECK: %[[VAL_4:.*]] = fir.address_of(@_QPtakes_real) : (!fir.ref) -> () +! CHECK: %[[VAL_5:.*]] = fir.convert %[[VAL_4]] : ((!fir.ref) -> ()) -> ((!fir.boxchar<1>) -> ()) +! CHECK: fir.call %[[VAL_5]](%[[VAL_3]]#0) {{.*}}: (!fir.boxchar<1>) -> () + +subroutine pass_proc_to_real() + real(8), external :: proc + call takes_real(proc) +end subroutine +! CHECK-LABEL: func.func @_QPpass_proc_to_real() { +! CHECK: %[[VAL_0:.*]] = fir.address_of(@_QPproc) : () -> f64 +! CHECK: %[[VAL_1:.*]] = fir.emboxproc %[[VAL_0]] : (() -> f64) -> !fir.boxproc<() -> ()> +! CHECK: %[[VAL_2:.*]] = fir.address_of(@_QPtakes_real) : (!fir.ref) -> () +! CHECK: %[[VAL_3:.*]] = fir.convert %[[VAL_2]] : ((!fir.ref) -> ()) -> ((!fir.boxproc<() -> ()>) -> ()) +! CHECK: fir.call %[[VAL_3]](%[[VAL_1]]) {{.*}}: (!fir.boxproc<() -> ()>) -> () + +subroutine pass_complex_to_real(cmplx) + complex(4) :: cmplx + call takes_real(cmplx) +end subroutine +! CHECK-LABEL: func.func @_QPpass_complex_to_real( +! CHECK: %[[VAL_1:.*]]:2 = hlfir.declare {{.*}}Ecmplx +! CHECK: %[[VAL_2:.*]] = fir.convert %[[VAL_1]]#1 : (!fir.ref>) -> !fir.ref +! CHECK: fir.call @_QPtakes_real(%[[VAL_2]]) {{.*}}: (!fir.ref) -> () + +subroutine takes_char_proc(c) + character(8), external :: c +end subroutine + +subroutine pass_proc_to_char_proc() + external :: proc + call takes_char_proc(proc) +end subroutine +! CHECK-LABEL: func.func @_QPpass_proc_to_char_proc() { +! CHECK: %[[VAL_0:.*]] = fir.address_of(@_QPproc) : () -> f64 +! CHECK: %[[VAL_1:.*]] = fir.emboxproc %[[VAL_0]] : (() -> f64) -> !fir.boxproc<() -> ()> +! CHECK: %[[VAL_2:.*]] = fir.address_of(@_QPtakes_char_proc) : (tuple ()>, i64>) -> () +! CHECK: %[[VAL_3:.*]] = fir.convert %[[VAL_2]] : ((tuple ()>, i64>) -> ()) -> ((!fir.boxproc<() -> ()>) -> ()) +! CHECK: fir.call %[[VAL_3]](%[[VAL_1]]) {{.*}}: (!fir.boxproc<() -> ()>) -> () + +subroutine pass_char_to_char_proc(c) + character(8) :: c + call takes_char_proc(c) +end subroutine +! CHECK-LABEL: func.func @_QPpass_char_to_char_proc( +! CHECK: %[[VAL_3:.*]]:2 = hlfir.declare {{.*}}Ec +! CHECK: %[[VAL_4:.*]] = fir.address_of(@_QPtakes_char_proc) : (tuple ()>, i64>) -> () +! CHECK: %[[VAL_5:.*]] = fir.convert %[[VAL_4]] : ((tuple ()>, i64>) -> ()) -> ((!fir.boxchar<1>) -> ()) +! CHECK: fir.call %[[VAL_5]](%[[VAL_3]]#0) {{.*}}: (!fir.boxchar<1>) -> () + +subroutine takes_proc(proc) + real(8), external :: proc +end subroutine + +subroutine pass_char_proc_to_proc() + character(8), external :: char_proc + call takes_proc(char_proc) +end subroutine +! CHECK-LABEL: func.func @_QPpass_char_proc_to_proc() { +! CHECK: %[[VAL_0:.*]] = fir.address_of(@_QPchar_proc) : (!fir.ref>, index) -> !fir.boxchar<1> +! CHECK: %[[VAL_1:.*]] = arith.constant 8 : i64 +! CHECK: %[[VAL_2:.*]] = fir.emboxproc %[[VAL_0]] : ((!fir.ref>, index) -> !fir.boxchar<1>) -> !fir.boxproc<() -> ()> +! CHECK: %[[VAL_3:.*]] = fir.undefined tuple ()>, i64> +! CHECK: %[[VAL_4:.*]] = fir.insert_value %[[VAL_3]], %[[VAL_2]], [0 : index] : (tuple ()>, i64>, !fir.boxproc<() -> ()>) -> tuple ()>, i64> +! CHECK: %[[VAL_5:.*]] = fir.insert_value %[[VAL_4]], %[[VAL_1]], [1 : index] : (tuple ()>, i64>, i64) -> tuple ()>, i64> +! CHECK: %[[VAL_6:.*]] = fir.address_of(@_QPtakes_proc) : (!fir.boxproc<() -> ()>) -> () +! CHECK: %[[VAL_7:.*]] = fir.convert %[[VAL_6]] : ((!fir.boxproc<() -> ()>) -> ()) -> ((tuple ()>, i64>) -> ()) +! CHECK: fir.call %[[VAL_7]](%[[VAL_5]]) {{.*}}: (tuple ()>, i64>) -> () + +subroutine pass_real_to_proc(r) + real(8) :: r + call takes_proc(r) +end subroutine +! CHECK-LABEL: func.func @_QPpass_real_to_proc( +! CHECK: %[[VAL_1:.*]]:2 = hlfir.declare {{.*}}Er +! CHECK: %[[VAL_2:.*]] = fir.address_of(@_QPtakes_proc) : (!fir.boxproc<() -> ()>) -> () +! CHECK: %[[VAL_3:.*]] = fir.convert %[[VAL_2]] : ((!fir.boxproc<() -> ()>) -> ()) -> ((!fir.ref) -> ()) +! CHECK: fir.call %[[VAL_3]](%[[VAL_1]]#1) {{.*}}: (!fir.ref) -> () + +subroutine pass_too_many_args() + call takes_real(I, Kown, what, I, am, doing) +end subroutine +! CHECK-LABEL: func.func @_QPpass_too_many_args() { +! CHECK: %[[VAL_10:.*]] = fir.address_of(@_QPtakes_real) : (!fir.ref) -> () +! CHECK: %[[VAL_11:.*]] = fir.convert %[[VAL_10]] : ((!fir.ref) -> ()) -> ((!fir.ref, !fir.ref, !fir.ref, !fir.ref, !fir.ref, !fir.ref) -> ()) +! CHECK: fir.call %[[VAL_11]](%{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}, %{{.*}}) {{.*}}: (!fir.ref, !fir.ref, !fir.ref, !fir.ref, !fir.ref, !fir.ref) -> () + +subroutine pass_too_few_args() + call takes_real() +end subroutine +! CHECK-LABEL: func.func @_QPpass_too_few_args() { +! CHECK: %[[VAL_0:.*]] = fir.address_of(@_QPtakes_real) : (!fir.ref) -> () +! CHECK: %[[VAL_1:.*]] = fir.convert %[[VAL_0]] : ((!fir.ref) -> ()) -> (() -> ()) +! CHECK: fir.call %[[VAL_1]]() {{.*}}: () -> ()